@@ -73,6 +73,13 @@ import { EditorContainer, EditorHolder } from './MobileEditor';
7373import { FolderIcon } from '../../../../common/icons' ;
7474import { IconButton } from '../../../../common/IconButton' ;
7575
76+ import contextAwareHinter from '../../../../utils/contextAwareHinter' ;
77+ import showRenameDialog from '../../../../utils/showRenameDialog' ;
78+ import handleRename from '../../../../utils/rename-variable' ;
79+ import { jumpToDefinition } from '../../../../utils/jump-to-definition' ;
80+ import { ensureAriaLiveRegion } from '../../../../utils/ScreenReaderHelper' ;
81+ import { isMac } from '../../../../utils/device' ;
82+
7683emmet ( CodeMirror ) ;
7784
7885window . JSHINT = JSHINT ;
@@ -109,6 +116,7 @@ class Editor extends React.Component {
109116
110117 componentDidMount ( ) {
111118 this . beep = new Audio ( beepUrl ) ;
119+ ensureAriaLiveRegion ( ) ;
112120 // this.widgets = [];
113121 this . _cm = CodeMirror ( this . codemirrorContainer , {
114122 theme : `p5-${ this . props . theme } ` ,
@@ -154,6 +162,17 @@ class Editor extends React.Component {
154162
155163 delete this . _cm . options . lint . options . errors ;
156164
165+ this . _cm . getWrapperElement ( ) . addEventListener ( 'click' , ( e ) => {
166+ const isCtrlClick = isMac ( ) ? e . metaKey : e . ctrlKey ;
167+
168+ if ( isCtrlClick ) {
169+ const pos = this . _cm . coordsChar ( { left : e . clientX , top : e . clientY } ) ;
170+ jumpToDefinition . call ( this , pos ) ;
171+ }
172+ } ) ;
173+
174+ const renameKey = isMac ( ) ? 'Ctrl-F2' : 'F2' ;
175+
157176 const replaceCommand =
158177 metaKey === 'Ctrl' ? `${ metaKey } -H` : `${ metaKey } -Option-F` ;
159178 this . _cm . setOption ( 'extraKeys' , {
@@ -172,6 +191,7 @@ class Editor extends React.Component {
172191 [ `Shift-${ metaKey } -E` ] : ( cm ) => {
173192 cm . getInputField ( ) . blur ( ) ;
174193 } ,
194+ [ renameKey ] : ( cm ) => this . renameVariable ( cm ) ,
175195 [ `Shift-Tab` ] : false ,
176196 [ `${ metaKey } -Enter` ] : ( ) => null ,
177197 [ `Shift-${ metaKey } -Enter` ] : ( ) => null ,
@@ -209,7 +229,14 @@ class Editor extends React.Component {
209229 }
210230
211231 this . _cm . on ( 'keydown' , ( _cm , e ) => {
212- // Show hint
232+ // Skip hinting if the user is pasting (Ctrl/Cmd+V) or using modifier keys (Ctrl/Alt)
233+ if (
234+ ( ( e . ctrlKey || e . metaKey ) && e . key === 'v' ) ||
235+ e . ctrlKey ||
236+ e . altKey
237+ ) {
238+ return ;
239+ }
213240 const mode = this . _cm . getOption ( 'mode' ) ;
214241 if ( / ^ [ a - z ] $ / i. test ( e . key ) && ( mode === 'css' || mode === 'javascript' ) ) {
215242 this . showHint ( _cm ) ;
@@ -395,12 +422,15 @@ class Editor extends React.Component {
395422 }
396423
397424 showHint ( _cm ) {
425+ if ( ! _cm ) return ;
426+
398427 if ( ! this . props . autocompleteHinter ) {
399428 CodeMirror . showHint ( _cm , ( ) => { } , { } ) ;
400429 return ;
401430 }
402431
403432 let focusedLinkElement = null ;
433+
404434 const setFocusedLinkElement = ( set ) => {
405435 if ( set && ! focusedLinkElement ) {
406436 const activeItemLink = document . querySelector (
@@ -415,6 +445,7 @@ class Editor extends React.Component {
415445 }
416446 }
417447 } ;
448+
418449 const removeFocusedLinkElement = ( ) => {
419450 if ( focusedLinkElement ) {
420451 focusedLinkElement . classList . remove ( 'focused-hint-link' ) ;
@@ -437,12 +468,8 @@ class Editor extends React.Component {
437468 ) ;
438469 if ( activeItemLink ) activeItemLink . click ( ) ;
439470 } ,
440- Right : ( cm , e ) => {
441- setFocusedLinkElement ( true ) ;
442- } ,
443- Left : ( cm , e ) => {
444- removeFocusedLinkElement ( ) ;
445- } ,
471+ Right : ( cm , e ) => setFocusedLinkElement ( true ) ,
472+ Left : ( cm , e ) => removeFocusedLinkElement ( ) ,
446473 Up : ( cm , e ) => {
447474 const onLink = removeFocusedLinkElement ( ) ;
448475 e . moveFocus ( - 1 ) ;
@@ -461,30 +488,28 @@ class Editor extends React.Component {
461488 closeOnUnfocus : false
462489 } ;
463490
464- if ( _cm . options . mode === 'javascript' ) {
465- // JavaScript
466- CodeMirror . showHint (
467- _cm ,
468- ( ) => {
469- const c = _cm . getCursor ( ) ;
470- const token = _cm . getTokenAt ( c ) ;
471-
472- const hints = this . hinter
473- . search ( token . string )
474- . filter ( ( h ) => h . item . text [ 0 ] === token . string [ 0 ] ) ;
475-
476- return {
477- list : hints ,
478- from : CodeMirror . Pos ( c . line , token . start ) ,
479- to : CodeMirror . Pos ( c . line , c . ch )
480- } ;
481- } ,
482- hintOptions
483- ) ;
484- } else if ( _cm . options . mode === 'css' ) {
485- // CSS
486- CodeMirror . showHint ( _cm , CodeMirror . hint . css , hintOptions ) ;
487- }
491+ const triggerHints = ( ) => {
492+ if ( _cm . options . mode === 'javascript' ) {
493+ CodeMirror . showHint (
494+ _cm ,
495+ ( ) => {
496+ const c = _cm . getCursor ( ) ;
497+ const token = _cm . getTokenAt ( c ) ;
498+ const hints = contextAwareHinter ( _cm , { hinter : this . hinter } ) ;
499+ return {
500+ list : hints ,
501+ from : CodeMirror . Pos ( c . line , token . start ) ,
502+ to : CodeMirror . Pos ( c . line , c . ch )
503+ } ;
504+ } ,
505+ hintOptions
506+ ) ;
507+ } else if ( _cm . options . mode === 'css' ) {
508+ CodeMirror . showHint ( _cm , CodeMirror . hint . css , hintOptions ) ;
509+ }
510+ } ;
511+
512+ setTimeout ( triggerHints , 0 ) ;
488513 }
489514
490515 showReplace ( ) {
@@ -522,6 +547,34 @@ class Editor extends React.Component {
522547 }
523548 }
524549
550+ renameVariable ( cm ) {
551+ const cursorCoords = cm . cursorCoords ( true , 'page' ) ;
552+ const selection = cm . getSelection ( ) ;
553+ const pos = cm . getCursor ( ) ; // or selection start
554+ const token = cm . getTokenAt ( pos ) ;
555+ const tokenType = token . type ;
556+ if ( ! selection ) {
557+ return ;
558+ }
559+
560+ const sel = cm . listSelections ( ) [ 0 ] ;
561+ const fromPos =
562+ CodeMirror . cmpPos ( sel . anchor , sel . head ) <= 0 ? sel . anchor : sel . head ;
563+
564+ showRenameDialog (
565+ cm ,
566+ fromPos ,
567+ tokenType ,
568+ cursorCoords ,
569+ selection ,
570+ ( newName ) => {
571+ if ( newName && newName . trim ( ) !== '' && newName !== selection ) {
572+ handleRename ( fromPos , selection , newName , cm ) ;
573+ }
574+ }
575+ ) ;
576+ }
577+
525578 initializeDocuments ( files ) {
526579 this . _docs = { } ;
527580 files . forEach ( ( file ) => {
0 commit comments