aboutsummaryrefslogtreecommitdiffstats
path: root/language-web/src/main/js
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-10-10 01:11:33 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2021-10-31 19:26:09 +0100
commit2ada4a06167b3a00a4c4c69e1b0c78b00ef1db5f (patch)
tree24f0b0bf8f8959ebcf0722456da2d6260c6f1421 /language-web/src/main/js
parentMerge pull request #7 from golej-marci/language-to-store (diff)
downloadrefinery-2ada4a06167b3a00a4c4c69e1b0c78b00ef1db5f.tar.gz
refinery-2ada4a06167b3a00a4c4c69e1b0c78b00ef1db5f.tar.zst
refinery-2ada4a06167b3a00a4c4c69e1b0c78b00ef1db5f.zip
feat(web): add CodeMirror 6 editor
Diffstat (limited to 'language-web/src/main/js')
-rw-r--r--language-web/src/main/js/RootStore.tsx4
-rw-r--r--language-web/src/main/js/editor/EditorArea.tsx142
-rw-r--r--language-web/src/main/js/editor/EditorButtons.tsx35
-rw-r--r--language-web/src/main/js/editor/EditorParent.ts61
-rw-r--r--language-web/src/main/js/editor/EditorStore.ts294
-rw-r--r--language-web/src/main/js/editor/editor.ts18
-rw-r--r--language-web/src/main/js/index.tsx3
-rw-r--r--language-web/src/main/js/theme/ThemeStore.ts4
8 files changed, 363 insertions, 198 deletions
diff --git a/language-web/src/main/js/RootStore.tsx b/language-web/src/main/js/RootStore.tsx
index 88b8a445..96e1b26a 100644
--- a/language-web/src/main/js/RootStore.tsx
+++ b/language-web/src/main/js/RootStore.tsx
@@ -8,9 +8,9 @@ export class RootStore {
8 8
9 themeStore; 9 themeStore;
10 10
11 constructor() { 11 constructor(initialValue: string) {
12 this.themeStore = new ThemeStore(); 12 this.themeStore = new ThemeStore();
13 this.editorStore = new EditorStore(this.themeStore); 13 this.editorStore = new EditorStore(initialValue, this.themeStore);
14 } 14 }
15} 15}
16 16
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});
diff --git a/language-web/src/main/js/editor/EditorButtons.tsx b/language-web/src/main/js/editor/EditorButtons.tsx
index 56577e82..9622475c 100644
--- a/language-web/src/main/js/editor/EditorButtons.tsx
+++ b/language-web/src/main/js/editor/EditorButtons.tsx
@@ -2,8 +2,10 @@ import { observer } from 'mobx-react-lite';
2import Stack from '@mui/material/Stack'; 2import Stack from '@mui/material/Stack';
3import ToggleButton from '@mui/material/ToggleButton'; 3import ToggleButton from '@mui/material/ToggleButton';
4import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; 4import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
5import CheckIcon from '@mui/icons-material/Check';
5import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered'; 6import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered';
6import RedoIcon from '@mui/icons-material/Redo'; 7import RedoIcon from '@mui/icons-material/Redo';
8import SearchIcon from '@mui/icons-material/Search';
7import UndoIcon from '@mui/icons-material/Undo'; 9import UndoIcon from '@mui/icons-material/Undo';
8import React from 'react'; 10import React from 'react';
9 11
@@ -37,15 +39,34 @@ export const EditorButtons = observer(() => {
37 <RedoIcon fontSize="small" /> 39 <RedoIcon fontSize="small" />
38 </ToggleButton> 40 </ToggleButton>
39 </ToggleButtonGroup> 41 </ToggleButtonGroup>
40 <ToggleButton 42 <ToggleButtonGroup
41 selected={editorStore.showLineNumbers}
42 onChange={() => editorStore.toggleLineNumbers()}
43 size="small" 43 size="small"
44 aria-label="Show line numbers"
45 value="show-line-numbers"
46 > 44 >
47 <FormatListNumberedIcon fontSize="small" /> 45 <ToggleButton
48 </ToggleButton> 46 selected={editorStore.showLineNumbers}
47 onClick={() => editorStore.toggleLineNumbers()}
48 aria-label="Show line numbers"
49 value="show-line-numbers"
50 >
51 <FormatListNumberedIcon fontSize="small" />
52 </ToggleButton>
53 <ToggleButton
54 selected={editorStore.showSearchPanel}
55 onClick={() => editorStore.toggleSearchPanel()}
56 aria-label="Show find/replace"
57 value="show-search-panel"
58 >
59 <SearchIcon fontSize="small" />
60 </ToggleButton>
61 <ToggleButton
62 selected={editorStore.showLintPanel}
63 onClick={() => editorStore.toggleLintPanel()}
64 aria-label="Show errors and warnings"
65 value="show-lint-panel"
66 >
67 <CheckIcon fontSize="small" />
68 </ToggleButton>
69 </ToggleButtonGroup>
49 </Stack> 70 </Stack>
50 ); 71 );
51}); 72});
diff --git a/language-web/src/main/js/editor/EditorParent.ts b/language-web/src/main/js/editor/EditorParent.ts
new file mode 100644
index 00000000..bf67522b
--- /dev/null
+++ b/language-web/src/main/js/editor/EditorParent.ts
@@ -0,0 +1,61 @@
1import { styled } from '@mui/material/styles';
2
3export const EditorParent = styled('div')(({ theme }) => ({
4 background: theme.palette.background.default,
5 '&, .cm-editor': {
6 height: '100%',
7 },
8 '.cm-scroller': {
9 fontSize: 16,
10 fontFamily: '"JetBrains MonoVariable", "JetBrains Mono", monospace',
11 fontFeatureSettings: '"liga", "calt"',
12 fontWeight: 400,
13 letterSpacing: 0,
14 textRendering: 'optimizeLegibility',
15 color: theme.palette.text.secondary,
16 },
17 '.cm-gutters': {
18 background: theme.palette.background.default,
19 color: theme.palette.text.disabled,
20 border: 'none',
21 },
22 '.cm-specialChar': {
23 color: theme.palette.secondary.main,
24 },
25 '.cm-activeLine': {
26 background: 'rgba(0, 0, 0, 0.3)',
27 },
28 '.cm-activeLineGutter': {
29 background: 'rgba(0, 0, 0, 0.3)',
30 color: theme.palette.text.primary,
31 },
32 '.cm-cursor, .cm-cursor-primary': {
33 borderColor: theme.palette.primary.main,
34 background: theme.palette.common.black,
35 },
36 '.cm-selectionBackground': {
37 background: '#3e4453',
38 },
39 '.cm-focused': {
40 outline: 'none',
41 '.cm-selectionBackground': {
42 background: '#3e4453',
43 },
44 },
45 '.cm-panels-top': {
46 color: theme.palette.text.secondary,
47 },
48 '.cm-panel': {
49 background: theme.palette.background.paper,
50 borderTop: `1px solid ${theme.palette.divider}`,
51 'button[name="close"]': {
52 // HACK We can't hook the panel close button to go through `EditorStore`,
53 // so we hide it altogether.
54 display: 'none',
55 },
56 },
57 '.cmt-comment': {
58 fontVariant: 'italic',
59 color: theme.palette.text.disabled,
60 },
61}));
diff --git a/language-web/src/main/js/editor/EditorStore.ts b/language-web/src/main/js/editor/EditorStore.ts
index 705020b9..326c02a1 100644
--- a/language-web/src/main/js/editor/EditorStore.ts
+++ b/language-web/src/main/js/editor/EditorStore.ts
@@ -1,201 +1,217 @@
1import type { Editor, EditorConfiguration } from 'codemirror'; 1import { autocompletion, completionKeymap } from '@codemirror/autocomplete';
2import { closeBrackets, closeBracketsKeymap } from '@codemirror/closebrackets';
3import { defaultKeymap, indentWithTab } from '@codemirror/commands';
4import { commentKeymap } from '@codemirror/comment';
5import { foldGutter, foldKeymap } from '@codemirror/fold';
6import { highlightActiveLineGutter, lineNumbers } from '@codemirror/gutter';
7import { classHighlightStyle } from '@codemirror/highlight';
8import {
9 history,
10 historyKeymap,
11 redo,
12 redoDepth,
13 undo,
14 undoDepth,
15} from '@codemirror/history';
16import { indentOnInput } from '@codemirror/language';
17import { lintKeymap } from '@codemirror/lint';
18import { bracketMatching } from '@codemirror/matchbrackets';
19import { rectangularSelection } from '@codemirror/rectangular-selection';
20import { searchConfig, searchKeymap } from '@codemirror/search';
21import {
22 EditorState,
23 StateCommand,
24 StateEffect,
25 Transaction,
26 TransactionSpec,
27} from '@codemirror/state';
28import {
29 drawSelection,
30 EditorView,
31 highlightActiveLine,
32 highlightSpecialChars,
33 keymap,
34} from '@codemirror/view';
2import { 35import {
3 createAtom,
4 makeAutoObservable, 36 makeAutoObservable,
5 observable, 37 observable,
6 runInAction, 38 reaction,
7} from 'mobx'; 39} from 'mobx';
8import type { IXtextOptions, IXtextServices } from 'xtext/xtext-codemirror';
9 40
10import type { IEditorChunk } from './editor';
11import { getLogger } from '../logging'; 41import { getLogger } from '../logging';
12import type { ThemeStore } from '../theme/ThemeStore'; 42import type { ThemeStore } from '../theme/ThemeStore';
13 43
14const log = getLogger('EditorStore'); 44const log = getLogger('EditorStore');
15 45
16const xtextLang = 'problem';
17
18const xtextOptions: IXtextOptions = {
19 xtextLang,
20 enableFormattingAction: true,
21};
22
23const codeMirrorGlobalOptions: EditorConfiguration = {
24 mode: `xtext/${xtextLang}`,
25 indentUnit: 2,
26 styleActiveLine: true,
27 screenReaderLabel: 'Model source code',
28 inputStyle: 'contenteditable',
29};
30
31export class EditorStore { 46export class EditorStore {
32 themeStore; 47 themeStore;
33 48
34 atom; 49 state: EditorState;
35 50
36 chunk?: IEditorChunk; 51 emptyHistory: unknown;
37 52
38 editor?: Editor; 53 showLineNumbers = false;
39 54
40 xtextServices?: IXtextServices; 55 showSearchPanel = false;
41 56
42 value = ''; 57 showLintPanel = false;
43 58
44 showLineNumbers = false; 59 readonly defaultDispatcher = (tr: Transaction): void => {
60 this.onTransaction(tr);
61 };
45 62
46 initialSelection!: { start: number, end: number, focused: boolean }; 63 dispatcher = this.defaultDispatcher;
47 64
48 constructor(themeStore: ThemeStore) { 65 constructor(initialValue: string, themeStore: ThemeStore) {
49 this.themeStore = themeStore; 66 this.themeStore = themeStore;
50 this.atom = createAtom('EditorStore'); 67 this.state = EditorState.create({
51 this.resetInitialSelection(); 68 doc: initialValue,
69 extensions: [
70 autocompletion(),
71 classHighlightStyle.extension,
72 closeBrackets(),
73 bracketMatching(),
74 drawSelection(),
75 EditorState.allowMultipleSelections.of(true),
76 EditorView.theme({}, {
77 dark: this.themeStore.darkMode,
78 }),
79 highlightActiveLine(),
80 highlightActiveLineGutter(),
81 highlightSpecialChars(),
82 history(),
83 indentOnInput(),
84 rectangularSelection(),
85 searchConfig({
86 top: true,
87 matchCase: true,
88 }),
89 // We add the gutters to `extensions` in the order we want them to appear.
90 foldGutter(),
91 lineNumbers(),
92 keymap.of([
93 ...closeBracketsKeymap,
94 ...commentKeymap,
95 ...completionKeymap,
96 ...foldKeymap,
97 ...historyKeymap,
98 indentWithTab,
99 // Override keys in `lintKeymap` to go through the `EditorStore`.
100 { key: 'Mod-Shift-m', run: () => this.setLintPanelOpen(true) },
101 ...lintKeymap,
102 // Override keys in `searchKeymap` to go through the `EditorStore`.
103 { key: 'Mod-f', run: () => this.setSearchPanelOpen(true), scope: 'editor search-panel' },
104 { key: 'Escape', run: () => this.setSearchPanelOpen(false), scope: 'editor search-panel' },
105 ...searchKeymap,
106 ...defaultKeymap,
107 ]),
108 ],
109 });
110 reaction(
111 () => this.themeStore.darkMode,
112 (darkMode) => {
113 log.debug('Update editor dark mode', darkMode);
114 this.dispatch({
115 effects: [
116 StateEffect.appendConfig.of(EditorView.theme({}, {
117 dark: darkMode,
118 })),
119 ],
120 });
121 },
122 );
52 makeAutoObservable(this, { 123 makeAutoObservable(this, {
53 themeStore: false, 124 themeStore: false,
54 atom: false, 125 state: observable.ref,
55 chunk: observable.ref, 126 defaultDispatcher: false,
56 editor: observable.ref, 127 dispatcher: false,
57 xtextServices: observable.ref,
58 initialSelection: false,
59 }); 128 });
60 this.loadChunk();
61 } 129 }
62 130
63 private loadChunk(): void { 131 updateDispatcher(newDispatcher: ((tr: Transaction) => void) | null): void {
64 const loadingStartMillis = Date.now(); 132 this.dispatcher = newDispatcher || this.defaultDispatcher;
65 log.info('Requesting editor chunk');
66 import('./editor').then(({ editorChunk }) => {
67 runInAction(() => {
68 this.chunk = editorChunk;
69 });
70 const loadingDurationMillis = Date.now() - loadingStartMillis;
71 log.info('Loaded editor chunk in', loadingDurationMillis, 'ms');
72 }).catch((error) => {
73 log.error('Error while loading editor', error);
74 });
75 } 133 }
76 134
77 setInitialSelection(start: number, end: number, focused: boolean): void { 135 onTransaction(tr: Transaction): void {
78 this.initialSelection = { start, end, focused }; 136 log.trace('Editor transaction', tr);
79 this.applyInitialSelectionToEditor(); 137 this.state = tr.state;
80 } 138 }
81 139
82 private resetInitialSelection(): void { 140 dispatch(...specs: readonly TransactionSpec[]): void {
83 this.initialSelection = { 141 this.dispatcher(this.state.update(...specs));
84 start: 0,
85 end: 0,
86 focused: false,
87 };
88 } 142 }
89 143
90 private applyInitialSelectionToEditor(): void { 144 doStateCommand(command: StateCommand): boolean {
91 if (this.editor) { 145 return command({
92 const { start, end, focused } = this.initialSelection; 146 state: this.state,
93 const doc = this.editor.getDoc(); 147 dispatch: this.dispatcher,
94 const startPos = doc.posFromIndex(start); 148 });
95 const endPos = doc.posFromIndex(end);
96 doc.setSelection(startPos, endPos, {
97 scroll: true,
98 });
99 if (focused) {
100 this.editor.focus();
101 }
102 this.resetInitialSelection();
103 }
104 } 149 }
105 150
106 /** 151 /**
107 * Attaches a new CodeMirror instance and creates Xtext services. 152 * @returns `true` if there is history to undo
108 *
109 * The store will not subscribe to any CodeMirror events. Instead,
110 * the editor component should subscribe to them and relay them to the store.
111 *
112 * @param newEditor The new CodeMirror instance
113 */ 153 */
114 editorDidMount(newEditor: Editor): void { 154 get canUndo(): boolean {
115 if (!this.chunk) { 155 return undoDepth(this.state) > 0;
116 throw new Error('Editor not loaded yet');
117 }
118 if (this.editor) {
119 throw new Error('CoreMirror editor mounted before unmounting');
120 }
121 this.editor = newEditor;
122 this.xtextServices = this.chunk.createServices(newEditor, xtextOptions);
123 this.applyInitialSelectionToEditor();
124 } 156 }
125 157
126 editorWillUnmount(): void { 158 // eslint-disable-next-line class-methods-use-this
127 if (!this.chunk) { 159 undo(): void {
128 throw new Error('Editor not loaded yet'); 160 log.debug('Undo', this.doStateCommand(undo));
129 }
130 if (this.editor) {
131 this.chunk.removeServices(this.editor);
132 }
133 delete this.editor;
134 delete this.xtextServices;
135 } 161 }
136 162
137 /** 163 /**
138 * Updates the contents of the editor. 164 * @returns `true` if there is history to redo
139 *
140 * @param newValue The new contents of the editor
141 */ 165 */
142 updateValue(newValue: string): void { 166 get canRedo(): boolean {
143 this.value = newValue; 167 return redoDepth(this.state) > 0;
144 }
145
146 reportChanged(): void {
147 this.atom.reportChanged();
148 }
149
150 protected observeEditorChanges(): void {
151 this.atom.reportObserved();
152 } 168 }
153 169
154 get codeMirrorTheme(): string { 170 // eslint-disable-next-line class-methods-use-this
155 return `problem-${this.themeStore.className}`; 171 redo(): void {
172 log.debug('Redo', this.doStateCommand(redo));
156 } 173 }
157 174
158 get codeMirrorOptions(): EditorConfiguration { 175 toggleLineNumbers(): void {
159 return { 176 this.showLineNumbers = !this.showLineNumbers;
160 ...codeMirrorGlobalOptions, 177 log.debug('Show line numbers', this.showLineNumbers);
161 theme: this.codeMirrorTheme,
162 lineNumbers: this.showLineNumbers,
163 };
164 } 178 }
165 179
166 /** 180 /**
167 * @returns `true` if there is history to undo 181 * Sets whether the CodeMirror search panel should be open.
182 *
183 * This method can be used as a CodeMirror command,
184 * because it returns `false` if it didn't execute,
185 * allowing other commands for the same keybind to run instead.
186 * This matches the behavior of the `openSearchPanel` and `closeSearchPanel`
187 * commands from `'@codemirror/search'`.
188 *
189 * @param newShosSearchPanel whether we should show the search panel
190 * @returns `true` if the state was changed, `false` otherwise
168 */ 191 */
169 get canUndo(): boolean { 192 setSearchPanelOpen(newShowSearchPanel: boolean): boolean {
170 this.observeEditorChanges(); 193 if (this.showSearchPanel === newShowSearchPanel) {
171 if (!this.editor) {
172 return false; 194 return false;
173 } 195 }
174 const { undo: undoSize } = this.editor.historySize(); 196 this.showSearchPanel = newShowSearchPanel;
175 return undoSize > 0; 197 log.debug('Show search panel', this.showSearchPanel);
198 return true;
176 } 199 }
177 200
178 undo(): void { 201 toggleSearchPanel(): void {
179 this.editor?.undo(); 202 this.setSearchPanelOpen(!this.showSearchPanel);
180 } 203 }
181 204
182 /** 205 setLintPanelOpen(newShowLintPanel: boolean): boolean {
183 * @returns `true` if there is history to redo 206 if (this.showLintPanel === newShowLintPanel) {
184 */
185 get canRedo(): boolean {
186 this.observeEditorChanges();
187 if (!this.editor) {
188 return false; 207 return false;
189 } 208 }
190 const { redo: redoSize } = this.editor.historySize(); 209 this.showLintPanel = newShowLintPanel;
191 return redoSize > 0; 210 log.debug('Show lint panel', this.showLintPanel);
211 return true;
192 } 212 }
193 213
194 redo(): void { 214 toggleLintPanel(): void {
195 this.editor?.redo(); 215 this.setLintPanelOpen(!this.showLintPanel);
196 }
197
198 toggleLineNumbers(): void {
199 this.showLineNumbers = !this.showLineNumbers;
200 } 216 }
201} 217}
diff --git a/language-web/src/main/js/editor/editor.ts b/language-web/src/main/js/editor/editor.ts
deleted file mode 100644
index fbf8796b..00000000
--- a/language-web/src/main/js/editor/editor.ts
+++ /dev/null
@@ -1,18 +0,0 @@
1import 'codemirror/addon/selection/active-line';
2import 'mode-problem';
3import { Controlled } from 'react-codemirror2';
4import { createServices, removeServices } from 'xtext/xtext-codemirror';
5
6export interface IEditorChunk {
7 CodeMirror: typeof Controlled;
8
9 createServices: typeof createServices;
10
11 removeServices: typeof removeServices;
12}
13
14export const editorChunk: IEditorChunk = {
15 CodeMirror: Controlled,
16 createServices,
17 removeServices,
18};
diff --git a/language-web/src/main/js/index.tsx b/language-web/src/main/js/index.tsx
index 80c70f23..66ad1f28 100644
--- a/language-web/src/main/js/index.tsx
+++ b/language-web/src/main/js/index.tsx
@@ -44,8 +44,7 @@ age(bob, bobAge).
44scope Family = 1, Person += 5..10. 44scope Family = 1, Person += 5..10.
45`; 45`;
46 46
47const rootStore = new RootStore(); 47const rootStore = new RootStore(initialValue);
48rootStore.editorStore.updateValue(initialValue);
49 48
50const app = ( 49const app = (
51 <RootStoreProvider rootStore={rootStore}> 50 <RootStoreProvider rootStore={rootStore}>
diff --git a/language-web/src/main/js/theme/ThemeStore.ts b/language-web/src/main/js/theme/ThemeStore.ts
index 3bbea3a1..db94d9f7 100644
--- a/language-web/src/main/js/theme/ThemeStore.ts
+++ b/language-web/src/main/js/theme/ThemeStore.ts
@@ -51,6 +51,10 @@ export class ThemeStore {
51 return responsiveFontSizes(materialUiTheme); 51 return responsiveFontSizes(materialUiTheme);
52 } 52 }
53 53
54 get darkMode(): boolean {
55 return this.currentThemeData.paletteMode === 'dark';
56 }
57
54 get className(): string { 58 get className(): string {
55 return this.currentThemeData.className; 59 return this.currentThemeData.className;
56 } 60 }