11import PropTypes from 'prop-types' ;
22import React from 'react' ;
33import CodeMirror from 'codemirror' ;
4+ import Fuse from 'fuse.js' ;
45import emmet from '@emmetio/codemirror-plugin' ;
56import prettier from 'prettier/standalone' ;
67import babelParser from 'prettier/parser-babel' ;
@@ -29,6 +30,7 @@ import 'codemirror/addon/search/jump-to-line';
2930import 'codemirror/addon/edit/matchbrackets' ;
3031import 'codemirror/addon/edit/closebrackets' ;
3132import 'codemirror/addon/selection/mark-selection' ;
33+ import 'codemirror/addon/hint/css-hint' ;
3234import 'codemirror-colorpicker' ;
3335
3436import { JSHINT } from 'jshint' ;
@@ -43,6 +45,8 @@ import '../../../utils/p5-javascript';
4345import Timer from '../components/Timer' ;
4446import EditorAccessibility from '../components/EditorAccessibility' ;
4547import { metaKey } from '../../../utils/metaKey' ;
48+ import './show-hint' ;
49+ import * as hinter from '../../../utils/p5-hinter' ;
4650
4751import '../../../utils/codemirror-search' ;
4852
@@ -94,7 +98,6 @@ class Editor extends React.Component {
9498 this . beep = new Audio ( beepUrl ) ;
9599 this . widgets = [ ] ;
96100 this . _cm = CodeMirror ( this . codemirrorContainer , {
97- // eslint-disable-line
98101 theme : `p5-${ this . props . theme } ` ,
99102 lineNumbers : this . props . lineNumbers ,
100103 styleActiveLine : true ,
@@ -131,6 +134,11 @@ class Editor extends React.Component {
131134 }
132135 } ) ;
133136
137+ this . hinter = new Fuse ( hinter . p5Hinter , {
138+ threshold : 0.05 ,
139+ keys : [ 'text' ]
140+ } ) ;
141+
134142 delete this . _cm . options . lint . options . errors ;
135143
136144 const replaceCommand =
@@ -186,16 +194,21 @@ class Editor extends React.Component {
186194 } ) ;
187195
188196 this . _cm . on ( 'keydown' , ( _cm , e ) => {
189- // 70 === f
190197 if (
191198 ( ( metaKey === 'Cmd' && e . metaKey ) ||
192199 ( metaKey === 'Ctrl' && e . ctrlKey ) ) &&
193200 e . shiftKey &&
194- e . keyCode === 70
201+ e . key === 'f'
195202 ) {
196203 e . preventDefault ( ) ;
197204 this . tidyCode ( ) ;
198205 }
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+ }
199212 } ) ;
200213
201214 this . _cm . getWrapperElement ( ) . style [
@@ -253,6 +266,12 @@ class Editor extends React.Component {
253266 this . props . autocloseBracketsQuotes
254267 ) ;
255268 }
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+ }
256275
257276 if ( this . props . runtimeErrorWarningVisible ) {
258277 if ( this . props . consoleEvents . length !== prevProps . consoleEvents . length ) {
@@ -331,6 +350,99 @@ class Editor extends React.Component {
331350 this . _cm . execCommand ( 'findPersistent' ) ;
332351 }
333352
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+
334446 showReplace ( ) {
335447 this . _cm . execCommand ( 'replace' ) ;
336448 }
@@ -437,6 +549,7 @@ class Editor extends React.Component {
437549
438550Editor . propTypes = {
439551 autocloseBracketsQuotes : PropTypes . bool . isRequired ,
552+ autocompleteHinter : PropTypes . bool . isRequired ,
440553 lineNumbers : PropTypes . bool . isRequired ,
441554 lintWarning : PropTypes . bool . isRequired ,
442555 linewrap : PropTypes . bool . isRequired ,
@@ -482,7 +595,6 @@ Editor.propTypes = {
482595 collapseSidebar : PropTypes . func . isRequired ,
483596 expandSidebar : PropTypes . func . isRequired ,
484597 clearConsole : PropTypes . func . isRequired ,
485- // showRuntimeErrorWarning: PropTypes.func.isRequired,
486598 hideRuntimeErrorWarning : PropTypes . func . isRequired ,
487599 runtimeErrorWarningVisible : PropTypes . bool . isRequired ,
488600 provideController : PropTypes . func . isRequired ,
0 commit comments