@@ -73,6 +73,13 @@ import { EditorContainer, EditorHolder } from './MobileEditor';
73
73
import { FolderIcon } from '../../../../common/icons' ;
74
74
import { IconButton } from '../../../../common/IconButton' ;
75
75
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
+
76
83
emmet ( CodeMirror ) ;
77
84
78
85
window . JSHINT = JSHINT ;
@@ -109,6 +116,7 @@ class Editor extends React.Component {
109
116
110
117
componentDidMount ( ) {
111
118
this . beep = new Audio ( beepUrl ) ;
119
+ ensureAriaLiveRegion ( ) ;
112
120
// this.widgets = [];
113
121
this . _cm = CodeMirror ( this . codemirrorContainer , {
114
122
theme : `p5-${ this . props . theme } ` ,
@@ -154,6 +162,17 @@ class Editor extends React.Component {
154
162
155
163
delete this . _cm . options . lint . options . errors ;
156
164
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
+
157
176
const replaceCommand =
158
177
metaKey === 'Ctrl' ? `${ metaKey } -H` : `${ metaKey } -Option-F` ;
159
178
this . _cm . setOption ( 'extraKeys' , {
@@ -172,6 +191,7 @@ class Editor extends React.Component {
172
191
[ `Shift-${ metaKey } -E` ] : ( cm ) => {
173
192
cm . getInputField ( ) . blur ( ) ;
174
193
} ,
194
+ [ renameKey ] : ( cm ) => this . renameVariable ( cm ) ,
175
195
[ `Shift-Tab` ] : false ,
176
196
[ `${ metaKey } -Enter` ] : ( ) => null ,
177
197
[ `Shift-${ metaKey } -Enter` ] : ( ) => null ,
@@ -209,7 +229,14 @@ class Editor extends React.Component {
209
229
}
210
230
211
231
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
+ }
213
240
const mode = this . _cm . getOption ( 'mode' ) ;
214
241
if ( / ^ [ a - z ] $ / i. test ( e . key ) && ( mode === 'css' || mode === 'javascript' ) ) {
215
242
this . showHint ( _cm ) ;
@@ -395,12 +422,15 @@ class Editor extends React.Component {
395
422
}
396
423
397
424
showHint ( _cm ) {
425
+ if ( ! _cm ) return ;
426
+
398
427
if ( ! this . props . autocompleteHinter ) {
399
428
CodeMirror . showHint ( _cm , ( ) => { } , { } ) ;
400
429
return ;
401
430
}
402
431
403
432
let focusedLinkElement = null ;
433
+
404
434
const setFocusedLinkElement = ( set ) => {
405
435
if ( set && ! focusedLinkElement ) {
406
436
const activeItemLink = document . querySelector (
@@ -415,6 +445,7 @@ class Editor extends React.Component {
415
445
}
416
446
}
417
447
} ;
448
+
418
449
const removeFocusedLinkElement = ( ) => {
419
450
if ( focusedLinkElement ) {
420
451
focusedLinkElement . classList . remove ( 'focused-hint-link' ) ;
@@ -437,12 +468,8 @@ class Editor extends React.Component {
437
468
) ;
438
469
if ( activeItemLink ) activeItemLink . click ( ) ;
439
470
} ,
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 ( ) ,
446
473
Up : ( cm , e ) => {
447
474
const onLink = removeFocusedLinkElement ( ) ;
448
475
e . moveFocus ( - 1 ) ;
@@ -461,30 +488,28 @@ class Editor extends React.Component {
461
488
closeOnUnfocus : false
462
489
} ;
463
490
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 ) ;
488
513
}
489
514
490
515
showReplace ( ) {
@@ -522,6 +547,34 @@ class Editor extends React.Component {
522
547
}
523
548
}
524
549
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
+
525
578
initializeDocuments ( files ) {
526
579
this . _docs = { } ;
527
580
files . forEach ( ( file ) => {
0 commit comments