1
1
import PropTypes from 'prop-types' ;
2
- import React , { useRef , useEffect , useState } from 'react' ;
3
- import CodeMirror from 'codemirror' ;
2
+ import React , { useRef , useEffect } from 'react' ;
3
+ import { EditorState } from '@codemirror/state' ;
4
+ import { EditorView , highlightSpecialChars , keymap } from '@codemirror/view' ;
5
+ import {
6
+ bracketMatching ,
7
+ syntaxHighlighting ,
8
+ defaultHighlightStyle
9
+ } from '@codemirror/language' ;
10
+ import { closeBrackets , closeBracketsKeymap } from '@codemirror/autocomplete' ;
11
+ import { defaultKeymap , history , historyKeymap } from '@codemirror/commands' ;
12
+ import { javascript } from '@codemirror/lang-javascript' ;
13
+
4
14
import { useDispatch } from 'react-redux' ;
5
15
import { Encode } from 'console-feed' ;
6
16
@@ -11,31 +21,24 @@ import { dispatchMessage, MessageTypes } from '../../../utils/dispatcher';
11
21
// heavily inspired by
12
22
// https://github.com/codesandbox/codesandbox-client/blob/92a1131f4ded6f7d9c16945dc7c18aa97c8ada27/packages/app/src/app/components/Preview/DevTools/Console/Input/index.tsx
13
23
24
+ // TODO(connie): Add theme support?
14
25
function ConsoleInput ( { theme, fontSize } ) {
15
- const [ commandHistory , setCommandHistory ] = useState ( [ ] ) ;
16
- const [ commandCursor , setCommandCursor ] = useState ( - 1 ) ;
26
+ const commandHistory = useRef ( [ ] ) ;
27
+ const commandCursor = useRef ( - 1 ) ;
17
28
const codemirrorContainer = useRef ( null ) ;
18
- const cmInstance = useRef ( null ) ;
29
+ const cmView = useRef ( null ) ;
19
30
const dispatch = useDispatch ( ) ;
20
31
21
32
useEffect ( ( ) => {
22
- cmInstance . current = CodeMirror ( codemirrorContainer . current , {
23
- theme : `p5-${ theme } ` ,
24
- scrollbarStyle : null ,
25
- keymap : 'sublime' ,
26
- mode : 'javascript' ,
27
- inputStyle : 'contenteditable'
28
- } ) ;
29
- } , [ ] ) ;
30
-
31
- useEffect ( ( ) => {
32
- const handleEnterKey = ( cm , e ) => {
33
- if ( e . key === 'Enter' && ! e . shiftKey ) {
34
- e . preventDefault ( ) ;
35
- e . stopPropagation ( ) ;
36
-
37
- const value = cm . getValue ( ) . trim ( ) ;
38
- if ( value === '' ) return ;
33
+ const enterKeymap = {
34
+ key : 'Enter' ,
35
+ shiftKey : ( ) => false , // Treat like a normal Enter key press if the Shift key is held down.
36
+ preventDefault : true ,
37
+ stopPropogation : true ,
38
+ run : ( view ) => {
39
+ const value = view . state . doc . toString ( ) . trim ( ) ;
40
+ if ( value === '' || view . state . selection . main . empty === false )
41
+ return false ;
39
42
40
43
const messages = [
41
44
{ log : Encode ( { method : 'command' , data : [ value ] } ) }
@@ -48,77 +51,92 @@ function ConsoleInput({ theme, fontSize }) {
48
51
} ) ;
49
52
50
53
dispatch ( dispatchConsoleEvent ( consoleEvent ) ) ;
51
- cm . setValue ( '' ) ;
52
- setCommandHistory ( [ value , ...commandHistory ] ) ;
53
- setCommandCursor ( - 1 ) ;
54
- }
55
- } ;
56
-
57
- if ( cmInstance . current ) {
58
- cmInstance . current . on ( 'keydown' , handleEnterKey ) ;
59
- }
60
-
61
- return ( ) => {
62
- if ( cmInstance . current ) {
63
- cmInstance . current . off ( 'keydown' , handleEnterKey ) ;
54
+ view . dispatch ( {
55
+ changes : { from : 0 , to : view . state . doc . length , insert : '' }
56
+ } ) ;
57
+ console . log ( 'Command history:' , commandHistory . current ) ;
58
+ commandHistory . current . unshift ( value ) ;
59
+ commandCursor . current = - 1 ;
60
+ return true ;
64
61
}
65
62
} ;
66
- } , [ commandHistory ] ) ;
67
63
68
- useEffect ( ( ) => {
69
- const handleUpArrowKey = ( cm , e ) => {
70
- if ( e . key === 'ArrowUp' ) {
71
- const lineNumber = cm . getDoc ( ) . getCursor ( ) . line ;
72
- if ( lineNumber !== 0 ) return ;
64
+ const upArrowKeymap = {
65
+ key : 'ArrowUp' ,
66
+ run : ( view ) => {
67
+ // Just let the cursor go up if we have a multiline input
68
+ // and the cursor isn't at the first line.
69
+ const currentLine = view . state . doc . lineAt (
70
+ view . state . selection . main . head
71
+ ) . number ;
72
+ // CM lines are 1-indexed, so the first line is 1.
73
+ if ( currentLine > 1 ) return false ;
73
74
74
75
const newCursor = Math . min (
75
- commandCursor + 1 ,
76
- commandHistory . length - 1
76
+ commandCursor . current + 1 ,
77
+ commandHistory . current . length - 1
77
78
) ;
78
- cm . setValue ( commandHistory [ newCursor ] || '' ) ;
79
- const cursorPos = cm . getDoc ( ) . getLine ( 0 ) . length - 1 ;
80
- cm . getDoc ( ) . setCursor ( { line : 0 , ch : cursorPos } ) ;
81
- setCommandCursor ( newCursor ) ;
82
- }
83
- } ;
84
-
85
- if ( cmInstance . current ) {
86
- cmInstance . current . on ( 'keydown' , handleUpArrowKey ) ;
87
- }
88
-
89
- return ( ) => {
90
- if ( cmInstance . current ) {
91
- cmInstance . current . off ( 'keydown' , handleUpArrowKey ) ;
79
+ const newValue = commandHistory . current [ newCursor ] || '' ;
80
+ view . dispatch ( {
81
+ changes : { from : 0 , to : view . state . doc . length , insert : newValue }
82
+ } ) ;
83
+ const newCursorPos = newValue . length ;
84
+ view . dispatch ( {
85
+ selection : { anchor : newCursorPos , head : newCursorPos }
86
+ } ) ;
87
+ commandCursor . current = newCursor ;
88
+ return true ;
92
89
}
93
90
} ;
94
- } , [ commandCursor , commandHistory ] ) ;
95
91
96
- useEffect ( ( ) => {
97
- const handleArrowDownKey = ( cm , e ) => {
98
- if ( e . key === 'ArrowDown' ) {
99
- const lineNumber = cm . getDoc ( ) . getCursor ( ) . line ;
100
- const lineCount = cm . lineCount ( ) ;
101
- if ( lineNumber + 1 !== lineCount ) return ;
102
-
103
- const newCursor = Math . max ( commandCursor - 1 , - 1 ) ;
104
- cm . setValue ( commandHistory [ newCursor ] || '' ) ;
105
- const newLine = cm . getDoc ( ) . getLine ( lineCount - 1 ) ;
106
- const cursorPos = newLine ? newLine . length - 1 : 1 ;
107
- cm . getDoc ( ) . setCursor ( { line : lineCount - 1 , ch : cursorPos } ) ;
108
- setCommandCursor ( newCursor ) ;
92
+ const downArrowKeymap = {
93
+ key : 'ArrowDown' ,
94
+ run : ( view ) => {
95
+ // Just let the cursor go down if we have a multiline input
96
+ // and the cursor isn't at the last line.
97
+ const currentLine = view . state . doc . lineAt (
98
+ view . state . selection . main . head
99
+ ) . number ;
100
+ const docLength = view . state . doc . lines ;
101
+ if ( currentLine !== docLength ) return false ;
102
+
103
+ const newCursor = Math . max ( commandCursor . current - 1 , - 1 ) ;
104
+ const newValue = commandHistory . current [ newCursor ] || '' ;
105
+ view . dispatch ( {
106
+ changes : { from : 0 , to : view . state . doc . length , insert : newValue }
107
+ } ) ;
108
+ const newCursorPos = newValue . length ;
109
+ view . dispatch ( {
110
+ selection : { anchor : newCursorPos , head : newCursorPos }
111
+ } ) ;
112
+ commandCursor . current = newCursor ;
113
+ return true ;
109
114
}
110
115
} ;
111
116
112
- if ( cmInstance . current ) {
113
- cmInstance . current . on ( 'keydown' , handleArrowDownKey ) ;
114
- }
115
-
116
- return ( ) => {
117
- if ( cmInstance . current ) {
118
- cmInstance . current . off ( 'keydown' , handleArrowDownKey ) ;
119
- }
120
- } ;
121
- } , [ commandCursor , commandHistory ] ) ;
117
+ const cmState = EditorState . create ( {
118
+ extensions : [
119
+ history ( ) ,
120
+ highlightSpecialChars ( ) ,
121
+ bracketMatching ( ) ,
122
+ closeBrackets ( ) ,
123
+ syntaxHighlighting ( defaultHighlightStyle ) ,
124
+ javascript ( ) ,
125
+ keymap . of ( [
126
+ enterKeymap ,
127
+ upArrowKeymap ,
128
+ downArrowKeymap ,
129
+ ...defaultKeymap ,
130
+ ...closeBracketsKeymap ,
131
+ ...historyKeymap
132
+ ] )
133
+ ]
134
+ } ) ;
135
+ cmView . current = new EditorView ( {
136
+ state : cmState ,
137
+ parent : codemirrorContainer . current
138
+ } ) ;
139
+ } , [ ] ) ;
122
140
123
141
return (
124
142
< div className = "console__input" >
0 commit comments