aboutsummaryrefslogtreecommitdiffstats
path: root/language-web/src/main/js/editor/EditorArea.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'language-web/src/main/js/editor/EditorArea.tsx')
-rw-r--r--language-web/src/main/js/editor/EditorArea.tsx142
1 files changed, 112 insertions, 30 deletions
diff --git a/language-web/src/main/js/editor/EditorArea.tsx b/language-web/src/main/js/editor/EditorArea.tsx
index 531a57c9..58d65184 100644
--- a/language-web/src/main/js/editor/EditorArea.tsx
+++ b/language-web/src/main/js/editor/EditorArea.tsx
@@ -1,41 +1,123 @@
1import { Command, EditorView } from '@codemirror/view';
2import { closeSearchPanel, openSearchPanel } from '@codemirror/search';
3import { closeLintPanel, openLintPanel } from '@codemirror/lint';
1import { observer } from 'mobx-react-lite'; 4import { observer } from 'mobx-react-lite';
2import React, { useRef } from 'react'; 5import React, { useEffect, useRef, useState } from 'react';
3 6
7import { EditorParent } from './EditorParent';
8import { getLogger } from '../logging';
4import { useRootStore } from '../RootStore'; 9import { useRootStore } from '../RootStore';
5 10
11const log = getLogger('EditorArea');
12
13function usePanel(
14 label: string,
15 stateToSet: boolean,
16 editorView: EditorView | null,
17 openCommand: Command,
18 closeCommand: Command,
19) {
20 const [cachedViewState, setCachedViewState] = useState<boolean>(false);
21 useEffect(() => {
22 if (editorView === null || cachedViewState === stateToSet) {
23 return;
24 }
25 const success = stateToSet ? openCommand(editorView) : closeCommand(editorView);
26 if (!success) {
27 log.error(
28 'Failed to synchronize',
29 label,
30 'panel state - store state:',
31 cachedViewState,
32 'view state:',
33 stateToSet,
34 );
35 }
36 setCachedViewState(stateToSet);
37 }, [
38 stateToSet,
39 editorView,
40 cachedViewState,
41 label,
42 openCommand,
43 closeCommand,
44 ]);
45 return setCachedViewState;
46}
47
6export const EditorArea = observer(() => { 48export const EditorArea = observer(() => {
7 const { editorStore } = useRootStore(); 49 const { editorStore } = useRootStore();
8 const { CodeMirror } = editorStore.chunk || {}; 50 const editorParentRef = useRef<HTMLDivElement | null>(null);
9 const fallbackTextarea = useRef<HTMLTextAreaElement>(null); 51 const [editorViewState, setEditorViewState] = useState<EditorView | null>(null);
10 52
11 if (!CodeMirror) { 53 const setSearchPanelOpen = usePanel(
12 return ( 54 'search',
13 <textarea 55 editorStore.showSearchPanel,
14 value={editorStore.value} 56 editorViewState,
15 onChange={(e) => editorStore.updateValue(e.target.value)} 57 openSearchPanel,
16 ref={fallbackTextarea} 58 closeSearchPanel,
17 className={`problem-fallback-editor cm-s-${editorStore.codeMirrorTheme}`} 59 );
18 /> 60
19 ); 61 const setLintPanelOpen = usePanel(
20 } 62 'lint',
21 63 editorStore.showLintPanel,
22 const textarea = fallbackTextarea.current; 64 editorViewState,
23 if (textarea) { 65 openLintPanel,
24 editorStore.setInitialSelection( 66 closeLintPanel,
25 textarea.selectionStart, 67 );
26 textarea.selectionEnd, 68
27 document.activeElement === textarea, 69 useEffect(() => {
28 ); 70 if (editorParentRef.current === null) {
29 } 71 // Nothing to clean up.
72 return () => {};
73 }
74
75 const editorView = new EditorView({
76 state: editorStore.state,
77 parent: editorParentRef.current,
78 dispatch: (transaction) => {
79 editorStore.onTransaction(transaction);
80 editorView.update([transaction]);
81 if (editorView.state !== editorStore.state) {
82 log.error(
83 'Failed to synchronize editor state - store state:',
84 editorStore.state,
85 'view state:',
86 editorView.state,
87 );
88 }
89 },
90 });
91 setEditorViewState(editorView);
92 setSearchPanelOpen(false);
93 setLintPanelOpen(false);
94 // `dispatch` is bound to the view instance,
95 // so it does not have to be called as a method.
96 // eslint-disable-next-line @typescript-eslint/unbound-method
97 editorStore.updateDispatcher(editorView.dispatch);
98 log.info('Editor created');
99
100 return () => {
101 editorStore.updateDispatcher(null);
102 editorView.destroy();
103 log.info('Editor destroyed');
104 };
105 }, [
106 editorParentRef,
107 editorStore,
108 setSearchPanelOpen,
109 setLintPanelOpen,
110 ]);
30 111
31 return ( 112 return (
32 <CodeMirror 113 <EditorParent
33 value={editorStore.value} 114 className="dark"
34 options={editorStore.codeMirrorOptions} 115 sx={{
35 editorDidMount={(editor) => editorStore.editorDidMount(editor)} 116 '.cm-lineNumbers': editorStore.showLineNumbers ? {} : {
36 editorWillUnmount={() => editorStore.editorWillUnmount()} 117 display: 'none !important',
37 onBeforeChange={(_editor, _data, value) => editorStore.updateValue(value)} 118 },
38 onChange={() => editorStore.reportChanged()} 119 }}
120 ref={editorParentRef}
39 /> 121 />
40 ); 122 );
41}); 123});