Skip to content

Commit d7cd4f5

Browse files
authored
Merge pull request #3500 from processing/connie-codemirror-upgrade
Converts CM5 into CM6
2 parents 52a3b53 + d2071f7 commit d7cd4f5

File tree

13 files changed

+1676
-1012
lines changed

13 files changed

+1676
-1012
lines changed

.babelrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"presets": [
33
"@babel/preset-react",
4-
"@babel/preset-env"
4+
"@babel/preset-env",
5+
"@babel/preset-typescript"
56
],
67
"env": {
78
"production": {

client/modules/IDE/components/ConsoleInput.jsx

Lines changed: 98 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
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+
414
import { useDispatch } from 'react-redux';
515
import { Encode } from 'console-feed';
616

@@ -11,31 +21,24 @@ import { dispatchMessage, MessageTypes } from '../../../utils/dispatcher';
1121
// heavily inspired by
1222
// https://github.com/codesandbox/codesandbox-client/blob/92a1131f4ded6f7d9c16945dc7c18aa97c8ada27/packages/app/src/app/components/Preview/DevTools/Console/Input/index.tsx
1323

24+
// TODO(connie): Add theme support?
1425
function ConsoleInput({ theme, fontSize }) {
15-
const [commandHistory, setCommandHistory] = useState([]);
16-
const [commandCursor, setCommandCursor] = useState(-1);
26+
const commandHistory = useRef([]);
27+
const commandCursor = useRef(-1);
1728
const codemirrorContainer = useRef(null);
18-
const cmInstance = useRef(null);
29+
const cmView = useRef(null);
1930
const dispatch = useDispatch();
2031

2132
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;
3942

4043
const messages = [
4144
{ log: Encode({ method: 'command', data: [value] }) }
@@ -48,77 +51,91 @@ function ConsoleInput({ theme, fontSize }) {
4851
});
4952

5053
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+
commandHistory.current.unshift(value);
58+
commandCursor.current = -1;
59+
return true;
6460
}
6561
};
66-
}, [commandHistory]);
6762

68-
useEffect(() => {
69-
const handleUpArrowKey = (cm, e) => {
70-
if (e.key === 'ArrowUp') {
71-
const lineNumber = cm.getDoc().getCursor().line;
72-
if (lineNumber !== 0) return;
63+
const upArrowKeymap = {
64+
key: 'ArrowUp',
65+
run: (view) => {
66+
// Just let the cursor go up if we have a multiline input
67+
// and the cursor isn't at the first line.
68+
const currentLine = view.state.doc.lineAt(
69+
view.state.selection.main.head
70+
).number;
71+
// CM lines are 1-indexed, so the first line is 1.
72+
if (currentLine > 1) return false;
7373

7474
const newCursor = Math.min(
75-
commandCursor + 1,
76-
commandHistory.length - 1
75+
commandCursor.current + 1,
76+
commandHistory.current.length - 1
7777
);
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);
78+
const newValue = commandHistory.current[newCursor] || '';
79+
view.dispatch({
80+
changes: { from: 0, to: view.state.doc.length, insert: newValue }
81+
});
82+
const newCursorPos = newValue.length;
83+
view.dispatch({
84+
selection: { anchor: newCursorPos, head: newCursorPos }
85+
});
86+
commandCursor.current = newCursor;
87+
return true;
9288
}
9389
};
94-
}, [commandCursor, commandHistory]);
9590

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);
91+
const downArrowKeymap = {
92+
key: 'ArrowDown',
93+
run: (view) => {
94+
// Just let the cursor go down if we have a multiline input
95+
// and the cursor isn't at the last line.
96+
const currentLine = view.state.doc.lineAt(
97+
view.state.selection.main.head
98+
).number;
99+
const docLength = view.state.doc.lines;
100+
if (currentLine !== docLength) return false;
101+
102+
const newCursor = Math.max(commandCursor.current - 1, -1);
103+
const newValue = commandHistory.current[newCursor] || '';
104+
view.dispatch({
105+
changes: { from: 0, to: view.state.doc.length, insert: newValue }
106+
});
107+
const newCursorPos = newValue.length;
108+
view.dispatch({
109+
selection: { anchor: newCursorPos, head: newCursorPos }
110+
});
111+
commandCursor.current = newCursor;
112+
return true;
109113
}
110114
};
111115

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]);
116+
const cmState = EditorState.create({
117+
extensions: [
118+
history(),
119+
highlightSpecialChars(),
120+
bracketMatching(),
121+
closeBrackets(),
122+
syntaxHighlighting(defaultHighlightStyle),
123+
javascript(),
124+
keymap.of([
125+
enterKeymap,
126+
upArrowKeymap,
127+
downArrowKeymap,
128+
...defaultKeymap,
129+
...closeBracketsKeymap,
130+
...historyKeymap
131+
])
132+
]
133+
});
134+
cmView.current = new EditorView({
135+
state: cmState,
136+
parent: codemirrorContainer.current
137+
});
138+
}, []);
122139

123140
return (
124141
<div className="console__input">

0 commit comments

Comments
 (0)