1
1
import PropTypes from 'prop-types' ;
2
2
import React from 'react' ;
3
3
import CodeMirror from 'codemirror' ;
4
+ import Fuse from 'fuse.js' ;
4
5
import emmet from '@emmetio/codemirror-plugin' ;
5
6
import prettier from 'prettier/standalone' ;
6
7
import babelParser from 'prettier/parser-babel' ;
@@ -29,6 +30,7 @@ import 'codemirror/addon/search/jump-to-line';
29
30
import 'codemirror/addon/edit/matchbrackets' ;
30
31
import 'codemirror/addon/edit/closebrackets' ;
31
32
import 'codemirror/addon/selection/mark-selection' ;
33
+ import 'codemirror/addon/hint/css-hint' ;
32
34
import 'codemirror-colorpicker' ;
33
35
34
36
import { JSHINT } from 'jshint' ;
@@ -43,6 +45,8 @@ import '../../../utils/p5-javascript';
43
45
import Timer from '../components/Timer' ;
44
46
import EditorAccessibility from '../components/EditorAccessibility' ;
45
47
import { metaKey } from '../../../utils/metaKey' ;
48
+ import './show-hint' ;
49
+ import * as hinter from '../../../utils/p5-hinter' ;
46
50
47
51
import '../../../utils/codemirror-search' ;
48
52
@@ -94,7 +98,6 @@ class Editor extends React.Component {
94
98
this . beep = new Audio ( beepUrl ) ;
95
99
this . widgets = [ ] ;
96
100
this . _cm = CodeMirror ( this . codemirrorContainer , {
97
- // eslint-disable-line
98
101
theme : `p5-${ this . props . theme } ` ,
99
102
lineNumbers : this . props . lineNumbers ,
100
103
styleActiveLine : true ,
@@ -131,6 +134,11 @@ class Editor extends React.Component {
131
134
}
132
135
} ) ;
133
136
137
+ this . hinter = new Fuse ( hinter . p5Hinter , {
138
+ threshold : 0.05 ,
139
+ keys : [ 'text' ]
140
+ } ) ;
141
+
134
142
delete this . _cm . options . lint . options . errors ;
135
143
136
144
const replaceCommand =
@@ -186,16 +194,21 @@ class Editor extends React.Component {
186
194
} ) ;
187
195
188
196
this . _cm . on ( 'keydown' , ( _cm , e ) => {
189
- // 70 === f
190
197
if (
191
198
( ( metaKey === 'Cmd' && e . metaKey ) ||
192
199
( metaKey === 'Ctrl' && e . ctrlKey ) ) &&
193
200
e . shiftKey &&
194
- e . keyCode === 70
201
+ e . key === 'f'
195
202
) {
196
203
e . preventDefault ( ) ;
197
204
this . tidyCode ( ) ;
198
205
}
206
+
207
+ // Show hint
208
+ const mode = this . _cm . getOption ( 'mode' ) ;
209
+ if ( / ^ [ a - z ] $ / i. test ( e . key ) && ( mode === 'css' || mode === 'javascript' ) ) {
210
+ this . showHint ( _cm ) ;
211
+ }
199
212
} ) ;
200
213
201
214
this . _cm . getWrapperElement ( ) . style [
@@ -253,6 +266,12 @@ class Editor extends React.Component {
253
266
this . props . autocloseBracketsQuotes
254
267
) ;
255
268
}
269
+ if ( this . props . autocompleteHinter !== prevProps . autocompleteHinter ) {
270
+ if ( ! this . props . autocompleteHinter ) {
271
+ // close the hinter window once the preference is turned off
272
+ CodeMirror . showHint ( this . _cm , ( ) => { } , { } ) ;
273
+ }
274
+ }
256
275
257
276
if ( this . props . runtimeErrorWarningVisible ) {
258
277
if ( this . props . consoleEvents . length !== prevProps . consoleEvents . length ) {
@@ -331,6 +350,99 @@ class Editor extends React.Component {
331
350
this . _cm . execCommand ( 'findPersistent' ) ;
332
351
}
333
352
353
+ showHint ( _cm ) {
354
+ if ( ! this . props . autocompleteHinter ) {
355
+ CodeMirror . showHint ( _cm , ( ) => { } , { } ) ;
356
+ return ;
357
+ }
358
+
359
+ let focusedLinkElement = null ;
360
+ const setFocusedLinkElement = ( set ) => {
361
+ if ( set && ! focusedLinkElement ) {
362
+ const activeItemLink = document . querySelector (
363
+ `.CodeMirror-hint-active a`
364
+ ) ;
365
+ if ( activeItemLink ) {
366
+ focusedLinkElement = activeItemLink ;
367
+ focusedLinkElement . classList . add ( 'focused-hint-link' ) ;
368
+ focusedLinkElement . parentElement . parentElement . classList . add (
369
+ 'unfocused'
370
+ ) ;
371
+ }
372
+ }
373
+ } ;
374
+ const removeFocusedLinkElement = ( ) => {
375
+ if ( focusedLinkElement ) {
376
+ focusedLinkElement . classList . remove ( 'focused-hint-link' ) ;
377
+ focusedLinkElement . parentElement . parentElement . classList . remove (
378
+ 'unfocused'
379
+ ) ;
380
+ focusedLinkElement = null ;
381
+ return true ;
382
+ }
383
+ return false ;
384
+ } ;
385
+
386
+ const hintOptions = {
387
+ _fontSize : this . props . fontSize ,
388
+ completeSingle : false ,
389
+ extraKeys : {
390
+ 'Shift-Right' : ( cm , e ) => {
391
+ const activeItemLink = document . querySelector (
392
+ `.CodeMirror-hint-active a`
393
+ ) ;
394
+ if ( activeItemLink ) activeItemLink . click ( ) ;
395
+ } ,
396
+ Right : ( cm , e ) => {
397
+ setFocusedLinkElement ( true ) ;
398
+ } ,
399
+ Left : ( cm , e ) => {
400
+ removeFocusedLinkElement ( ) ;
401
+ } ,
402
+ Up : ( cm , e ) => {
403
+ const onLink = removeFocusedLinkElement ( ) ;
404
+ e . moveFocus ( - 1 ) ;
405
+ setFocusedLinkElement ( onLink ) ;
406
+ } ,
407
+ Down : ( cm , e ) => {
408
+ const onLink = removeFocusedLinkElement ( ) ;
409
+ e . moveFocus ( 1 ) ;
410
+ setFocusedLinkElement ( onLink ) ;
411
+ } ,
412
+ Enter : ( cm , e ) => {
413
+ if ( focusedLinkElement ) focusedLinkElement . click ( ) ;
414
+ else e . pick ( ) ;
415
+ }
416
+ } ,
417
+ closeOnUnfocus : false
418
+ } ;
419
+
420
+ if ( _cm . options . mode === 'javascript' ) {
421
+ // JavaScript
422
+ CodeMirror . showHint (
423
+ _cm ,
424
+ ( ) => {
425
+ const c = _cm . getCursor ( ) ;
426
+ const token = _cm . getTokenAt ( c ) ;
427
+
428
+ const hints = this . hinter
429
+ . search ( token . string )
430
+ . filter ( ( h ) => h . item . text [ 0 ] === token . string [ 0 ] ) ;
431
+
432
+ return {
433
+ list : hints ,
434
+ from : CodeMirror . Pos ( c . line , token . start ) ,
435
+ to : CodeMirror . Pos ( c . line , c . ch )
436
+ } ;
437
+ } ,
438
+ hintOptions
439
+ ) ;
440
+ } else if ( _cm . options . mode === 'css' ) {
441
+ // CSS
442
+ CodeMirror . showHint ( _cm , CodeMirror . hint . css , hintOptions ) ;
443
+ }
444
+ }
445
+
334
446
showReplace ( ) {
335
447
this . _cm . execCommand ( 'replace' ) ;
336
448
}
@@ -437,6 +549,7 @@ class Editor extends React.Component {
437
549
438
550
Editor . propTypes = {
439
551
autocloseBracketsQuotes : PropTypes . bool . isRequired ,
552
+ autocompleteHinter : PropTypes . bool . isRequired ,
440
553
lineNumbers : PropTypes . bool . isRequired ,
441
554
lintWarning : PropTypes . bool . isRequired ,
442
555
linewrap : PropTypes . bool . isRequired ,
@@ -482,7 +595,6 @@ Editor.propTypes = {
482
595
collapseSidebar : PropTypes . func . isRequired ,
483
596
expandSidebar : PropTypes . func . isRequired ,
484
597
clearConsole : PropTypes . func . isRequired ,
485
- // showRuntimeErrorWarning: PropTypes.func.isRequired,
486
598
hideRuntimeErrorWarning : PropTypes . func . isRequired ,
487
599
runtimeErrorWarningVisible : PropTypes . bool . isRequired ,
488
600
provideController : PropTypes . func . isRequired ,
0 commit comments