Skip to content

Commit 76a8c97

Browse files
committed
change CM fifle to a useCodeMirror custom hook
1 parent 0247b7e commit 76a8c97

File tree

2 files changed

+206
-228
lines changed

2 files changed

+206
-228
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useRef, useEffect } from 'react';
12
import CodeMirror from 'codemirror';
23
import 'codemirror/mode/css/css';
34
import 'codemirror/mode/clike/clike';
@@ -34,171 +35,181 @@ const INDENTATION_AMOUNT = 2;
3435

3536
emmet(CodeMirror);
3637

37-
function setupCodeMirrorHooks(
38-
cmInstance,
39-
{
40-
setUnsavedChanges,
41-
hideRuntimeErrorWarning,
42-
updateFileContent,
43-
file,
44-
autorefresh,
45-
isPlaying,
46-
clearConsole,
47-
startSketch,
48-
autocompleteHinter,
49-
fontSize
50-
},
51-
updateLineNumber
52-
) {
53-
cmInstance.on(
54-
'change',
55-
debounce(() => {
56-
setUnsavedChanges(true);
57-
hideRuntimeErrorWarning();
58-
updateFileContent(file.id, cmInstance.getValue());
59-
if (autorefresh && isPlaying) {
60-
clearConsole();
61-
startSketch();
62-
}
63-
}, 1000)
64-
);
38+
export default function useCodeMirror({
39+
theme,
40+
lineNumbers,
41+
linewrap,
42+
autocloseBracketsQuotes,
43+
setUnsavedChanges,
44+
setCurrentLine,
45+
hideRuntimeErrorWarning,
46+
updateFileContent,
47+
file,
48+
autorefresh,
49+
isPlaying,
50+
clearConsole,
51+
startSketch,
52+
autocompleteHinter,
53+
fontSize,
54+
onUpdateLinting
55+
}) {
56+
const cmInstance = useRef();
6557

66-
cmInstance.on('keyup', () => {
67-
const lineNumber = parseInt(cmInstance.getCursor().line + 1, 10);
68-
updateLineNumber(lineNumber);
69-
});
58+
function onKeyUp() {
59+
const lineNumber = parseInt(cmInstance.current.getCursor().line + 1, 10);
60+
setCurrentLine(lineNumber);
61+
}
7062

71-
cmInstance.on('keydown', (_cm, e) => {
63+
function onKeyDown(_cm, e) {
7264
// Show hint
73-
const mode = cmInstance.getOption('mode');
65+
const mode = cmInstance.current.getOption('mode');
7466
if (/^[a-z]$/i.test(e.key) && (mode === 'css' || mode === 'javascript')) {
7567
showHint(_cm, autocompleteHinter, fontSize);
7668
}
7769
if (e.key === 'Escape') {
7870
e.preventDefault();
79-
const selections = cmInstance.listSelections();
71+
const selections = cmInstance.current.listSelections();
8072

8173
if (selections.length > 1) {
8274
const firstPos = selections[0].head || selections[0].anchor;
83-
cmInstance.setSelection(firstPos);
84-
cmInstance.scrollIntoView(firstPos);
75+
cmInstance.current.setSelection(firstPos);
76+
cmInstance.current.scrollIntoView(firstPos);
8577
} else {
86-
cmInstance.getInputField().blur();
78+
cmInstance.current.getInputField().blur();
8779
}
8880
}
89-
});
81+
}
9082

91-
cmInstance.getWrapperElement().style['font-size'] = `${fontSize}px`;
92-
}
93-
94-
export default function setupCodeMirror(
95-
container,
96-
{
97-
theme,
98-
lineNumbers,
99-
linewrap,
100-
autocloseBracketsQuotes,
101-
setUnsavedChanges,
102-
hideRuntimeErrorWarning,
103-
updateFileContent,
104-
file,
105-
autorefresh,
106-
isPlaying,
107-
clearConsole,
108-
startSketch,
109-
autocompleteHinter,
110-
fontSize
111-
},
112-
onUpdateLinting,
113-
docs,
114-
updateLineNumber
115-
) {
116-
const cm = CodeMirror(container, {
117-
theme: `p5-${theme}`,
118-
lineNumbers,
119-
styleActiveLine: true,
120-
inputStyle: 'contenteditable',
121-
lineWrapping: linewrap,
122-
fixedGutter: false,
123-
foldGutter: true,
124-
foldOptions: { widget: '\u2026' },
125-
gutters: ['CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
126-
keyMap: 'sublime',
127-
highlightSelectionMatches: true, // highlight current search match
128-
matchBrackets: true,
129-
emmet: {
130-
preview: ['html'],
131-
markTagPairs: true,
132-
autoRenameTags: true
133-
},
134-
autoCloseBrackets: autocloseBracketsQuotes,
135-
styleSelectedText: true,
136-
lint: {
137-
onUpdateLinting,
138-
options: {
139-
asi: true,
140-
eqeqeq: false,
141-
'-W041': false,
142-
esversion: 11
83+
function onChange() {
84+
debounce(() => {
85+
setUnsavedChanges(true);
86+
hideRuntimeErrorWarning();
87+
updateFileContent(file.id, cmInstance.current.getValue());
88+
if (autorefresh && isPlaying) {
89+
clearConsole();
90+
startSketch();
14391
}
144-
},
145-
colorpicker: {
146-
type: 'sketch',
147-
mode: 'edit'
148-
}
149-
});
150-
151-
delete cm.options.lint.options.errors;
152-
153-
const replaceCommand =
154-
metaKey === 'Ctrl' ? `${metaKey}-H` : `${metaKey}-Option-F`;
155-
cm.setOption('extraKeys', {
156-
Tab: (tabCm) => {
157-
if (!tabCm.execCommand('emmetExpandAbbreviation')) return;
158-
// might need to specify and indent more?
159-
const selection = tabCm.doc.getSelection();
160-
if (selection.length > 0) {
161-
tabCm.execCommand('indentMore');
162-
} else {
163-
tabCm.replaceSelection(' '.repeat(INDENTATION_AMOUNT));
92+
}, 1000);
93+
}
94+
95+
function setupCodeMirrorOnContainerMounted(container) {
96+
cmInstance.current = CodeMirror(container, {
97+
theme: `p5-${theme}`,
98+
lineNumbers,
99+
styleActiveLine: true,
100+
inputStyle: 'contenteditable',
101+
lineWrapping: linewrap,
102+
fixedGutter: false,
103+
foldGutter: true,
104+
foldOptions: { widget: '\u2026' },
105+
gutters: ['CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
106+
keyMap: 'sublime',
107+
highlightSelectionMatches: true, // highlight current search match
108+
matchBrackets: true,
109+
emmet: {
110+
preview: ['html'],
111+
markTagPairs: true,
112+
autoRenameTags: true
113+
},
114+
autoCloseBrackets: autocloseBracketsQuotes,
115+
styleSelectedText: true,
116+
lint: {
117+
onUpdateLinting,
118+
options: {
119+
asi: true,
120+
eqeqeq: false,
121+
'-W041': false,
122+
esversion: 11
123+
}
124+
},
125+
colorpicker: {
126+
type: 'sketch',
127+
mode: 'edit'
164128
}
165-
},
166-
Enter: 'emmetInsertLineBreak',
167-
Esc: 'emmetResetAbbreviation',
168-
[`Shift-Tab`]: false,
169-
[`${metaKey}-Enter`]: () => null,
170-
[`Shift-${metaKey}-Enter`]: () => null,
171-
[`${metaKey}-F`]: 'findPersistent',
172-
[`Shift-${metaKey}-F`]: () => tidyCode(cm),
173-
[`${metaKey}-G`]: 'findPersistentNext',
174-
[`Shift-${metaKey}-G`]: 'findPersistentPrev',
175-
[replaceCommand]: 'replace',
176-
// Cassie Tarakajian: If you don't set a default color, then when you
177-
// choose a color, it deletes characters inline. This is a
178-
// hack to prevent that.
179-
[`${metaKey}-K`]: (metaCm, event) =>
180-
metaCm.state.colorpicker.popup_color_picker({ length: 0 }),
181-
[`${metaKey}-.`]: 'toggleComment' // Note: most adblockers use the shortcut ctrl+.
182-
});
183-
184-
setupCodeMirrorHooks(
185-
cm,
186-
{
187-
setUnsavedChanges,
188-
hideRuntimeErrorWarning,
189-
updateFileContent,
190-
file,
191-
autorefresh,
192-
isPlaying,
193-
clearConsole,
194-
startSketch,
195-
autocompleteHinter,
196-
fontSize
197-
},
198-
updateLineNumber
199-
);
200-
201-
cm.swapDoc(docs[file.id]);
202-
203-
return cm;
129+
});
130+
131+
delete cmInstance.current.options.lint.options.errors;
132+
133+
const replaceCommand =
134+
metaKey === 'Ctrl' ? `${metaKey}-H` : `${metaKey}-Option-F`;
135+
cmInstance.current.setOption('extraKeys', {
136+
Tab: (tabCm) => {
137+
if (!tabCm.execCommand('emmetExpandAbbreviation')) return;
138+
// might need to specify and indent more?
139+
const selection = tabCm.doc.getSelection();
140+
if (selection.length > 0) {
141+
tabCm.execCommand('indentMore');
142+
} else {
143+
tabCm.replaceSelection(' '.repeat(INDENTATION_AMOUNT));
144+
}
145+
},
146+
Enter: 'emmetInsertLineBreak',
147+
Esc: 'emmetResetAbbreviation',
148+
[`Shift-Tab`]: false,
149+
[`${metaKey}-Enter`]: () => null,
150+
[`Shift-${metaKey}-Enter`]: () => null,
151+
[`${metaKey}-F`]: 'findPersistent',
152+
[`Shift-${metaKey}-F`]: () => tidyCode(cmInstance.current),
153+
[`${metaKey}-G`]: 'findPersistentNext',
154+
[`Shift-${metaKey}-G`]: 'findPersistentPrev',
155+
[replaceCommand]: 'replace',
156+
// Cassie Tarakajian: If you don't set a default color, then when you
157+
// choose a color, it deletes characters inline. This is a
158+
// hack to prevent that.
159+
[`${metaKey}-K`]: (metaCm, event) =>
160+
metaCm.state.colorpicker.popup_color_picker({ length: 0 }),
161+
[`${metaKey}-.`]: 'toggleComment' // Note: most adblockers use the shortcut ctrl+.
162+
});
163+
164+
cmInstance.current.on('change', onChange);
165+
cmInstance.current.on('keyup', onKeyUp);
166+
cmInstance.current.on('keydown', onKeyDown);
167+
168+
cmInstance.current.getWrapperElement().style['font-size'] = `${fontSize}px`;
169+
}
170+
171+
useEffect(() => {
172+
cmInstance.current.getWrapperElement().style['font-size'] = `${fontSize}px`;
173+
}, [fontSize]);
174+
useEffect(() => {
175+
cmInstance.current.setOption('lineWrapping', linewrap);
176+
}, [linewrap]);
177+
useEffect(() => {
178+
cmInstance.current.setOption('theme', `p5-${theme}`);
179+
}, [theme]);
180+
useEffect(() => {
181+
cmInstance.current.setOption('lineNumbers', lineNumbers);
182+
}, [lineNumbers]);
183+
useEffect(() => {
184+
cmInstance.current.setOption('autoCloseBrackets', autocloseBracketsQuotes);
185+
}, [autocloseBracketsQuotes]);
186+
187+
function teardownCodeMirror() {
188+
cmInstance.current.off('keyup', onKeyUp);
189+
cmInstance.current.off('change', onChange);
190+
cmInstance.current.off('keydown', onKeyDown);
191+
}
192+
193+
const getContent = () => {
194+
const content = cmInstance.current.getValue();
195+
const updatedFile = Object.assign({}, file, { content });
196+
return updatedFile;
197+
};
198+
199+
const showFind = () => {
200+
cmInstance.current.execCommand('findPersistent');
201+
};
202+
203+
const showReplace = () => {
204+
cmInstance.current.execCommand('replace');
205+
};
206+
207+
return {
208+
setupCodeMirrorOnContainerMounted,
209+
teardownCodeMirror,
210+
cmInstance,
211+
getContent,
212+
showFind,
213+
showReplace
214+
};
204215
}

0 commit comments

Comments
 (0)