aboutsummaryrefslogtreecommitdiffstats
path: root/language-web/src/main/js/editor
diff options
context:
space:
mode:
Diffstat (limited to 'language-web/src/main/js/editor')
-rw-r--r--language-web/src/main/js/editor/Editor.jsx52
-rw-r--r--language-web/src/main/js/editor/Editor.tsx20
-rw-r--r--language-web/src/main/js/editor/EditorButtons.tsx (renamed from language-web/src/main/js/editor/EditorButtons.jsx)48
-rw-r--r--language-web/src/main/js/editor/EditorStore.jsx87
-rw-r--r--language-web/src/main/js/editor/EditorStore.ts139
5 files changed, 186 insertions, 160 deletions
diff --git a/language-web/src/main/js/editor/Editor.jsx b/language-web/src/main/js/editor/Editor.jsx
deleted file mode 100644
index 98cf2715..00000000
--- a/language-web/src/main/js/editor/Editor.jsx
+++ /dev/null
@@ -1,52 +0,0 @@
1import { observer } from 'mobx-react-lite';
2import 'mode-problem';
3import React, { useCallback } from 'react';
4import { Controlled as CodeMirror } from 'react-codemirror2';
5import { createServices, removeServices } from 'xtext/xtext-codemirror';
6
7import { useRootStore } from '../RootStore';
8
9export default observer(() => {
10 const editorStore = useRootStore().editorStore;
11
12 const codeMirrorOptions = {
13 mode: 'xtext/problem',
14 indentUnit: 2,
15 theme: 'material-darker',
16 lineNumbers: editorStore.showLineNumbers,
17 };
18
19 const xtextOptions = {
20 xtextLang: 'problem',
21 enableFormattingAction: true,
22 }
23
24 const editorDidMount = useCallback((editor) => {
25 createServices(editor, xtextOptions);
26 editorStore.updateEditor(editor);
27 }, [editorStore]);
28
29 const editorWillUnmount = useCallback((editor) => {
30 editorStore.editor = null;
31 removeServices(editor);
32 }, [editorStore]);
33
34 const onBeforeChange = useCallback((_editor, _data, value) => {
35 editorStore.updateValue(value);
36 }, [editorStore]);
37
38 const onChange = useCallback((_editor, _data, _value) => {
39 editorStore.reportChanged();
40 }, [editorStore]);
41
42 return (
43 <CodeMirror
44 value={editorStore.value}
45 options={codeMirrorOptions}
46 editorDidMount={editorDidMount}
47 editorWillUnmount={editorWillUnmount}
48 onBeforeChange={onBeforeChange}
49 onChange={onChange}
50 />
51 );
52});
diff --git a/language-web/src/main/js/editor/Editor.tsx b/language-web/src/main/js/editor/Editor.tsx
new file mode 100644
index 00000000..9badb6a3
--- /dev/null
+++ b/language-web/src/main/js/editor/Editor.tsx
@@ -0,0 +1,20 @@
1import { observer } from 'mobx-react-lite';
2import React from 'react';
3import { Controlled as CodeMirror } from 'react-codemirror2';
4
5import { useRootStore } from '../RootStore';
6
7export const Editor = observer(() => {
8 const { editorStore } = useRootStore();
9
10 return (
11 <CodeMirror
12 value={editorStore.value}
13 options={editorStore.codeMirrorOptions}
14 editorDidMount={(editor) => editorStore.editorDidMount(editor)}
15 editorWillUnmount={() => editorStore.editorWillUnmount()}
16 onBeforeChange={(_editor, _data, value) => editorStore.updateValue(value)}
17 onChange={() => editorStore.reportChanged()}
18 />
19 );
20});
diff --git a/language-web/src/main/js/editor/EditorButtons.jsx b/language-web/src/main/js/editor/EditorButtons.tsx
index f67afdbf..d3825c07 100644
--- a/language-web/src/main/js/editor/EditorButtons.jsx
+++ b/language-web/src/main/js/editor/EditorButtons.tsx
@@ -1,69 +1,75 @@
1import { observer } from 'mobx-react-lite'; 1import { observer } from 'mobx-react-lite';
2import React from 'react'; 2import React from 'react';
3import { makeStyles } from '@material-ui/core/styles';
4import Button from '@material-ui/core/Button'; 3import Button from '@material-ui/core/Button';
5import ButtonGroup from '@material-ui/core/ButtonGroup'; 4import ButtonGroup from '@material-ui/core/ButtonGroup';
5import ToggleButton from '@material-ui/core/ToggleButton';
6import Divider from '@material-ui/core/Divider'; 6import Divider from '@material-ui/core/Divider';
7import FormatListNumberedIcon from '@material-ui/icons/FormatListNumbered'; 7import FormatListNumberedIcon from '@material-ui/icons/FormatListNumbered';
8import RedoIcon from '@material-ui/icons/Redo'; 8import RedoIcon from '@material-ui/icons/Redo';
9import UndoIcon from '@material-ui/icons/Undo'; 9import UndoIcon from '@material-ui/icons/Undo';
10import ToggleButton from '@material-ui/lab/ToggleButton';
11 10
11import { makeStyles } from '../makeStyles';
12import { useRootStore } from '../RootStore'; 12import { useRootStore } from '../RootStore';
13 13
14const useStyles = makeStyles(theme => ({ 14const useStyles = makeStyles()((theme) => ({
15 iconButton: { 15 iconButton: {
16 padding: 7, 16 padding: 7,
17 minWidth: 36,
18 border: 0, 17 border: 0,
19 color: theme.palette.text.primary, 18 color: theme.palette.text.primary,
20 '&.MuiButtonGroup-groupedTextHorizontal': { 19 '&, &.MuiButtonGroup-grouped': {
20 minWidth: 36,
21 },
22 '&.MuiButtonGroup-grouped:not(:last-of-type)': {
21 borderRight: 0, 23 borderRight: 0,
22 }, 24 },
23 }, 25 },
24 divider: { 26 divider: {
25 margin: theme.spacing(0.5), 27 margin: theme.spacing(0.5),
26 } 28 },
27})); 29}));
28 30
29export default observer(() => { 31export const EditorButtons = observer(() => {
30 const editorStore = useRootStore().editorStore; 32 const { editorStore } = useRootStore();
31 const classes = useStyles(); 33 const { classes, cx } = useStyles();
34
32 return ( 35 return (
33 <> 36 <>
34 <ButtonGroup 37 <ButtonGroup
35 variant='text' 38 variant="text"
36 > 39 >
37 <Button 40 <Button
38 disabled={!editorStore.canUndo} 41 disabled={!editorStore.canUndo}
39 onClick={() => editorStore.undo()} 42 onClick={() => editorStore.undo()}
40 className={classes.iconButton} 43 className={cx(classes.iconButton)}
41 aria-label='Undo' 44 color="inherit"
45 aria-label="Undo"
42 > 46 >
43 <UndoIcon fontSize='small'/> 47 <UndoIcon fontSize="small" />
44 </Button> 48 </Button>
45 <Button 49 <Button
46 disabled={!editorStore.canRedo} 50 disabled={!editorStore.canRedo}
47 onClick={() => editorStore.redo()} 51 onClick={() => editorStore.redo()}
48 className={classes.iconButton} 52 className={cx(classes.iconButton)}
49 aria-label='Redo' 53 color="inherit"
54 aria-label="Redo"
50 > 55 >
51 <RedoIcon fontSize='small'/> 56 <RedoIcon fontSize="small" />
52 </Button> 57 </Button>
53 </ButtonGroup> 58 </ButtonGroup>
54 <Divider 59 <Divider
55 flexItem 60 flexItem
56 orientation='vertical' 61 orientation="vertical"
57 className={classes.divider} 62 className={classes.divider}
58 /> 63 />
59 <ToggleButton 64 <ToggleButton
60 selected={editorStore.showLineNumbers} 65 selected={editorStore.showLineNumbers}
61 onChange={() => editorStore.toggleLineNumbers()} 66 onChange={() => editorStore.toggleLineNumbers()}
62 size='small' 67 size="small"
63 className={classes.iconButton} 68 className={cx(classes.iconButton)}
64 aria-label='Show line numbers' 69 aria-label="Show line numbers"
70 value="show-line-numbers"
65 > 71 >
66 <FormatListNumberedIcon fontSize='small'/> 72 <FormatListNumberedIcon fontSize="small" />
67 </ToggleButton> 73 </ToggleButton>
68 </> 74 </>
69 ); 75 );
diff --git a/language-web/src/main/js/editor/EditorStore.jsx b/language-web/src/main/js/editor/EditorStore.jsx
deleted file mode 100644
index b6f9bc0a..00000000
--- a/language-web/src/main/js/editor/EditorStore.jsx
+++ /dev/null
@@ -1,87 +0,0 @@
1import CodeMirror from 'codemirror';
2import { createAtom, makeAutoObservable, observable } from 'mobx';
3
4export default class EditorStore {
5 atom;
6 /** @type {CodeMirror} */
7 editor = null;
8 /** @type {string} */
9 value = '';
10 /** @type {boolean} */
11 showLineNumbers = false;
12 /** @type {boolean} */
13 showLigatures = true;
14
15 constructor() {
16 this.atom = createAtom('EditorStore');
17 makeAutoObservable(this, {
18 atom: false,
19 editor: observable.ref,
20 });
21 }
22
23 /**
24 * Attaches a new CodeMirror instance.
25 *
26 * The store will node subscribe to any CodeMirror events. Instead,
27 * the editor component should subscribe to them and relay them to the store.
28 *
29 * @param {CodeMirror} newEditor The new CodeMirror instance
30 */
31 updateEditor(newEditor) {
32 this.editor = newEditor;
33 }
34
35 /**
36 * Updates the contents of the editor.
37 *
38 * @param {string} newValue The new contents of the editor
39 */
40 updateValue(newValue) {
41 this.value = newValue;
42 }
43
44 reportChanged() {
45 this.atom.reportChanged();
46 }
47
48 /**
49 * @returns {boolean} `true` if there is history to undo
50 */
51 get canUndo() {
52 this.atom.reportObserved();
53 if (!this.editor) {
54 return false;
55 }
56 const { undo: undoSize } = this.editor.historySize();
57 return undoSize > 0;
58 }
59
60 undo() {
61 this.editor.undo();
62 }
63
64 /**
65 * @returns {boolean} `true` if there is history to redo
66 */
67 get canRedo() {
68 this.atom.reportObserved();
69 if (!this.editor) {
70 return false;
71 }
72 const { redo: redoSize } = this.editor.historySize();
73 return redoSize > 0;
74 }
75
76 redo() {
77 this.editor.redo();
78 }
79
80 toggleLineNumbers() {
81 this.showLineNumbers = !this.showLineNumbers;
82 }
83
84 toggleLigatures() {
85 this.showLigatures = !this.showLigatures;
86 }
87}
diff --git a/language-web/src/main/js/editor/EditorStore.ts b/language-web/src/main/js/editor/EditorStore.ts
new file mode 100644
index 00000000..5da45ac1
--- /dev/null
+++ b/language-web/src/main/js/editor/EditorStore.ts
@@ -0,0 +1,139 @@
1import { Editor, EditorConfiguration } from 'codemirror';
2import 'codemirror/addon/selection/active-line';
3import {
4 createAtom,
5 makeAutoObservable,
6 observable,
7} from 'mobx';
8import 'mode-problem';
9import {
10 IXtextOptions,
11 IXtextServices,
12 createServices,
13 removeServices,
14} from 'xtext/xtext-codemirror';
15
16import { ThemeStore } from '../theme/ThemeStore';
17
18const xtextLang = 'problem';
19
20const xtextOptions: IXtextOptions = {
21 xtextLang,
22 enableFormattingAction: true,
23};
24
25const codeMirrorGlobalOptions: EditorConfiguration = {
26 mode: `xtext/${xtextLang}`,
27 indentUnit: 2,
28 styleActiveLine: true,
29};
30
31export class EditorStore {
32 themeStore;
33
34 atom;
35
36 editor?: Editor;
37
38 xtextServices?: IXtextServices;
39
40 value = '';
41
42 showLineNumbers = false;
43
44 constructor(themeStore: ThemeStore) {
45 this.themeStore = themeStore;
46 this.atom = createAtom('EditorStore');
47 makeAutoObservable(this, {
48 themeStore: false,
49 atom: false,
50 editor: observable.ref,
51 xtextServices: observable.ref,
52 });
53 }
54
55 /**
56 * Attaches a new CodeMirror instance and creates Xtext services.
57 *
58 * The store will not subscribe to any CodeMirror events. Instead,
59 * the editor component should subscribe to them and relay them to the store.
60 *
61 * @param newEditor The new CodeMirror instance
62 */
63 editorDidMount(newEditor: Editor): void {
64 if (this.editor) {
65 throw new Error('CoreMirror editor mounted before unmounting');
66 }
67 this.editor = newEditor;
68 this.xtextServices = createServices(newEditor, xtextOptions);
69 }
70
71 editorWillUnmount(): void {
72 if (this.editor) {
73 removeServices(this.editor);
74 }
75 delete this.editor;
76 delete this.xtextServices;
77 }
78
79 /**
80 * Updates the contents of the editor.
81 *
82 * @param newValue The new contents of the editor
83 */
84 updateValue(newValue: string): void {
85 this.value = newValue;
86 }
87
88 reportChanged(): void {
89 this.atom.reportChanged();
90 }
91
92 protected observeEditorChanges(): void {
93 this.atom.reportObserved();
94 }
95
96 get codeMirrorOptions(): EditorConfiguration {
97 return {
98 ...codeMirrorGlobalOptions,
99 theme: this.themeStore.codeMirrorTheme,
100 lineNumbers: this.showLineNumbers,
101 };
102 }
103
104 /**
105 * @returns `true` if there is history to undo
106 */
107 get canUndo(): boolean {
108 this.observeEditorChanges();
109 if (!this.editor) {
110 return false;
111 }
112 const { undo: undoSize } = this.editor.historySize();
113 return undoSize > 0;
114 }
115
116 undo(): void {
117 this.editor?.undo();
118 }
119
120 /**
121 * @returns `true` if there is history to redo
122 */
123 get canRedo(): boolean {
124 this.observeEditorChanges();
125 if (!this.editor) {
126 return false;
127 }
128 const { redo: redoSize } = this.editor.historySize();
129 return redoSize > 0;
130 }
131
132 redo(): void {
133 this.editor?.redo();
134 }
135
136 toggleLineNumbers(): void {
137 this.showLineNumbers = !this.showLineNumbers;
138 }
139}