aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src/editor
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/frontend/src/editor')
-rw-r--r--subprojects/frontend/src/editor/EditorArea.tsx41
-rw-r--r--subprojects/frontend/src/editor/EditorButtons.tsx32
-rw-r--r--subprojects/frontend/src/editor/EditorParent.ts74
-rw-r--r--subprojects/frontend/src/editor/EditorStore.ts81
-rw-r--r--subprojects/frontend/src/editor/GenerateButton.tsx10
-rw-r--r--subprojects/frontend/src/editor/defineDecorationSetExtension.ts (renamed from subprojects/frontend/src/editor/decorationSetExtension.ts)19
-rw-r--r--subprojects/frontend/src/editor/findOccurrences.ts13
-rw-r--r--subprojects/frontend/src/editor/semanticHighlighting.ts22
8 files changed, 173 insertions, 119 deletions
diff --git a/subprojects/frontend/src/editor/EditorArea.tsx b/subprojects/frontend/src/editor/EditorArea.tsx
index dba20f6e..14294371 100644
--- a/subprojects/frontend/src/editor/EditorArea.tsx
+++ b/subprojects/frontend/src/editor/EditorArea.tsx
@@ -1,17 +1,13 @@
1import { Command, EditorView } from '@codemirror/view';
2import { closeSearchPanel, openSearchPanel } from '@codemirror/search';
3import { closeLintPanel, openLintPanel } from '@codemirror/lint'; 1import { closeLintPanel, openLintPanel } from '@codemirror/lint';
2import { closeSearchPanel, openSearchPanel } from '@codemirror/search';
3import { type Command, EditorView } from '@codemirror/view';
4import { observer } from 'mobx-react-lite'; 4import { observer } from 'mobx-react-lite';
5import React, { 5import React, { useCallback, useEffect, useRef, useState } from 'react';
6 useCallback,
7 useEffect,
8 useRef,
9 useState,
10} from 'react';
11 6
12import { EditorParent } from './EditorParent';
13import { useRootStore } from '../RootStore'; 7import { useRootStore } from '../RootStore';
14import { getLogger } from '../utils/logger'; 8import getLogger from '../utils/getLogger';
9
10import EditorParent from './EditorParent';
15 11
16const log = getLogger('editor.EditorArea'); 12const log = getLogger('editor.EditorArea');
17 13
@@ -70,10 +66,12 @@ function fixCodeMirrorAccessibility(editorView: EditorView) {
70 contentDOM.setAttribute('aria-label', 'Code editor'); 66 contentDOM.setAttribute('aria-label', 'Code editor');
71} 67}
72 68
73export const EditorArea = observer(() => { 69function EditorArea(): JSX.Element {
74 const { editorStore } = useRootStore(); 70 const { editorStore } = useRootStore();
75 const editorParentRef = useRef<HTMLDivElement | null>(null); 71 const editorParentRef = useRef<HTMLDivElement | null>(null);
76 const [editorViewState, setEditorViewState] = useState<EditorView | null>(null); 72 const [editorViewState, setEditorViewState] = useState<EditorView | null>(
73 null,
74 );
77 75
78 const setSearchPanelOpen = usePanel( 76 const setSearchPanelOpen = usePanel(
79 'search', 77 'search',
@@ -131,22 +129,21 @@ export const EditorArea = observer(() => {
131 editorView.destroy(); 129 editorView.destroy();
132 log.info('Editor destroyed'); 130 log.info('Editor destroyed');
133 }; 131 };
134 }, [ 132 }, [editorStore, setSearchPanelOpen, setLintPanelOpen]);
135 editorParentRef,
136 editorStore,
137 setSearchPanelOpen,
138 setLintPanelOpen,
139 ]);
140 133
141 return ( 134 return (
142 <EditorParent 135 <EditorParent
143 className="dark" 136 className="dark"
144 sx={{ 137 sx={{
145 '.cm-lineNumbers': editorStore.showLineNumbers ? {} : { 138 '.cm-lineNumbers': editorStore.showLineNumbers
146 display: 'none !important', 139 ? {}
147 }, 140 : {
141 display: 'none !important',
142 },
148 }} 143 }}
149 ref={editorParentRef} 144 ref={editorParentRef}
150 /> 145 />
151 ); 146 );
152}); 147}
148
149export default observer(EditorArea);
diff --git a/subprojects/frontend/src/editor/EditorButtons.tsx b/subprojects/frontend/src/editor/EditorButtons.tsx
index 150aa00d..652ca71e 100644
--- a/subprojects/frontend/src/editor/EditorButtons.tsx
+++ b/subprojects/frontend/src/editor/EditorButtons.tsx
@@ -1,9 +1,4 @@
1import type { Diagnostic } from '@codemirror/lint'; 1import type { Diagnostic } from '@codemirror/lint';
2import { observer } from 'mobx-react-lite';
3import IconButton from '@mui/material/IconButton';
4import Stack from '@mui/material/Stack';
5import ToggleButton from '@mui/material/ToggleButton';
6import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
7import CheckIcon from '@mui/icons-material/Check'; 2import CheckIcon from '@mui/icons-material/Check';
8import ErrorIcon from '@mui/icons-material/Error'; 3import ErrorIcon from '@mui/icons-material/Error';
9import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered'; 4import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered';
@@ -13,6 +8,11 @@ import RedoIcon from '@mui/icons-material/Redo';
13import SearchIcon from '@mui/icons-material/Search'; 8import SearchIcon from '@mui/icons-material/Search';
14import UndoIcon from '@mui/icons-material/Undo'; 9import UndoIcon from '@mui/icons-material/Undo';
15import WarningIcon from '@mui/icons-material/Warning'; 10import WarningIcon from '@mui/icons-material/Warning';
11import IconButton from '@mui/material/IconButton';
12import Stack from '@mui/material/Stack';
13import ToggleButton from '@mui/material/ToggleButton';
14import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
15import { observer } from 'mobx-react-lite';
16import React from 'react'; 16import React from 'react';
17 17
18import { useRootStore } from '../RootStore'; 18import { useRootStore } from '../RootStore';
@@ -27,23 +27,17 @@ function getLintIcon(severity: Diagnostic['severity'] | null) {
27 return <WarningIcon fontSize="small" />; 27 return <WarningIcon fontSize="small" />;
28 case 'info': 28 case 'info':
29 return <InfoOutlinedIcon fontSize="small" />; 29 return <InfoOutlinedIcon fontSize="small" />;
30 case null: 30 default:
31 return <CheckIcon fontSize="small" />; 31 return <CheckIcon fontSize="small" />;
32 } 32 }
33} 33}
34 34
35export const EditorButtons = observer(() => { 35function EditorButtons(): JSX.Element {
36 const { editorStore } = useRootStore(); 36 const { editorStore } = useRootStore();
37 37
38 return ( 38 return (
39 <Stack 39 <Stack direction="row" spacing={1}>
40 direction="row" 40 <Stack direction="row" alignItems="center">
41 spacing={1}
42 >
43 <Stack
44 direction="row"
45 alignItems="center"
46 >
47 <IconButton 41 <IconButton
48 disabled={!editorStore.canUndo} 42 disabled={!editorStore.canUndo}
49 onClick={() => editorStore.undo()} 43 onClick={() => editorStore.undo()}
@@ -59,9 +53,7 @@ export const EditorButtons = observer(() => {
59 <RedoIcon fontSize="small" /> 53 <RedoIcon fontSize="small" />
60 </IconButton> 54 </IconButton>
61 </Stack> 55 </Stack>
62 <ToggleButtonGroup 56 <ToggleButtonGroup size="small">
63 size="small"
64 >
65 <ToggleButton 57 <ToggleButton
66 selected={editorStore.showLineNumbers} 58 selected={editorStore.showLineNumbers}
67 onClick={() => editorStore.toggleLineNumbers()} 59 onClick={() => editorStore.toggleLineNumbers()}
@@ -95,4 +87,6 @@ export const EditorButtons = observer(() => {
95 </IconButton> 87 </IconButton>
96 </Stack> 88 </Stack>
97 ); 89 );
98}); 90}
91
92export default observer(EditorButtons);
diff --git a/subprojects/frontend/src/editor/EditorParent.ts b/subprojects/frontend/src/editor/EditorParent.ts
index 9aaf541a..dbc35a0d 100644
--- a/subprojects/frontend/src/editor/EditorParent.ts
+++ b/subprojects/frontend/src/editor/EditorParent.ts
@@ -1,4 +1,4 @@
1import { styled } from '@mui/material/styles'; 1import { alpha, styled } from '@mui/material/styles';
2 2
3/** 3/**
4 * Returns a squiggly underline background image encoded as a CSS `url()` data URI with Base64. 4 * Returns a squiggly underline background image encoded as a CSS `url()` data URI with Base64.
@@ -17,7 +17,9 @@ function underline(color: string) {
17 return `url('data:image/svg+xml;base64,${svgBase64}')`; 17 return `url('data:image/svg+xml;base64,${svgBase64}')`;
18} 18}
19 19
20export const EditorParent = styled('div')(({ theme }) => { 20export default styled('div', {
21 name: 'EditorParent',
22})(({ theme }) => {
21 const codeMirrorLintStyle: Record<string, unknown> = {}; 23 const codeMirrorLintStyle: Record<string, unknown> = {};
22 (['error', 'warning', 'info'] as const).forEach((severity) => { 24 (['error', 'warning', 'info'] as const).forEach((severity) => {
23 const color = theme.palette[severity].main; 25 const color = theme.palette[severity].main;
@@ -37,19 +39,20 @@ export const EditorParent = styled('div')(({ theme }) => {
37 '.cm-content': { 39 '.cm-content': {
38 padding: 0, 40 padding: 0,
39 }, 41 },
40 '.cm-scroller, .cm-tooltip-autocomplete, .cm-completionLabel, .cm-completionDetail': { 42 '.cm-scroller, .cm-tooltip-autocomplete, .cm-completionLabel, .cm-completionDetail':
41 fontSize: 16, 43 {
42 fontFamily: '"JetBrains MonoVariable", "JetBrains Mono", monospace', 44 fontSize: 16,
43 fontFeatureSettings: '"liga", "calt"', 45 fontFamily: '"JetBrains MonoVariable", "JetBrains Mono", monospace',
44 fontWeight: 400, 46 fontFeatureSettings: '"liga", "calt"',
45 letterSpacing: 0, 47 fontWeight: 400,
46 textRendering: 'optimizeLegibility', 48 letterSpacing: 0,
47 }, 49 textRendering: 'optimizeLegibility',
50 },
48 '.cm-scroller': { 51 '.cm-scroller': {
49 color: theme.palette.text.secondary, 52 color: theme.palette.text.secondary,
50 }, 53 },
51 '.cm-gutters': { 54 '.cm-gutters': {
52 background: 'rgba(255, 255, 255, 0.1)', 55 background: 'transparent',
53 color: theme.palette.text.disabled, 56 color: theme.palette.text.disabled,
54 border: 'none', 57 border: 'none',
55 }, 58 },
@@ -57,7 +60,19 @@ export const EditorParent = styled('div')(({ theme }) => {
57 color: theme.palette.secondary.main, 60 color: theme.palette.secondary.main,
58 }, 61 },
59 '.cm-activeLine': { 62 '.cm-activeLine': {
60 background: 'rgba(0, 0, 0, 0.3)', 63 background: alpha(theme.palette.text.secondary, 0.06),
64 },
65 '.cm-foldGutter': {
66 color: alpha(theme.palette.text.primary, 0),
67 transition: theme.transitions.create('color', {
68 duration: theme.transitions.duration.short,
69 }),
70 '@media (hover: none)': {
71 color: theme.palette.text.primary,
72 },
73 },
74 '.cm-gutters:hover .cm-foldGutter': {
75 color: theme.palette.text.primary,
61 }, 76 },
62 '.cm-activeLineGutter': { 77 '.cm-activeLineGutter': {
63 background: 'transparent', 78 background: 'transparent',
@@ -66,8 +81,7 @@ export const EditorParent = styled('div')(({ theme }) => {
66 color: theme.palette.text.primary, 81 color: theme.palette.text.primary,
67 }, 82 },
68 '.cm-cursor, .cm-cursor-primary': { 83 '.cm-cursor, .cm-cursor-primary': {
69 borderColor: theme.palette.primary.main, 84 borderLeft: `2px solid ${theme.palette.primary.main}`,
70 background: theme.palette.common.black,
71 }, 85 },
72 '.cm-selectionBackground': { 86 '.cm-selectionBackground': {
73 background: '#3e4453', 87 background: '#3e4453',
@@ -115,9 +129,26 @@ export const EditorParent = styled('div')(({ theme }) => {
115 }, 129 },
116 }, 130 },
117 '.cm-foldPlaceholder': { 131 '.cm-foldPlaceholder': {
118 background: theme.palette.background.paper,
119 borderColor: theme.palette.text.disabled,
120 color: theme.palette.text.secondary, 132 color: theme.palette.text.secondary,
133 backgroundColor: alpha(theme.palette.text.secondary, 0),
134 border: `1px solid ${alpha(theme.palette.text.secondary, 0.5)}`,
135 borderRadius: theme.shape.borderRadius,
136 transition: theme.transitions.create(
137 ['background-color', 'border-color', 'color'],
138 {
139 duration: theme.transitions.duration.short,
140 },
141 ),
142 '&:hover': {
143 backgroundColor: alpha(
144 theme.palette.text.secondary,
145 theme.palette.action.hoverOpacity,
146 ),
147 borderColor: theme.palette.text.secondary,
148 '@media (hover: none)': {
149 backgroundColor: 'transparent',
150 },
151 },
121 }, 152 },
122 '.tok-comment': { 153 '.tok-comment': {
123 fontStyle: 'italic', 154 fontStyle: 'italic',
@@ -168,9 +199,14 @@ export const EditorParent = styled('div')(({ theme }) => {
168 }, 199 },
169 '.cm-tooltip-autocomplete': { 200 '.cm-tooltip-autocomplete': {
170 background: theme.palette.background.paper, 201 background: theme.palette.background.paper,
171 boxShadow: `0px 2px 4px -1px rgb(0 0 0 / 20%), 202 ...(theme.palette.mode === 'dark' && {
172 0px 4px 5px 0px rgb(0 0 0 / 14%), 203 overflow: 'hidden',
173 0px 1px 10px 0px rgb(0 0 0 / 12%)`, 204 borderRadius: theme.shape.borderRadius,
205 // https://github.com/mui/material-ui/blob/10c72729c7d03bab8cdce6eb422642684c56dca2/packages/mui-material/src/Paper/Paper.js#L18
206 backgroundImage:
207 'linear-gradient(rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.09))',
208 }),
209 boxShadow: theme.shadows[4],
174 '.cm-completionIcon': { 210 '.cm-completionIcon': {
175 color: theme.palette.text.secondary, 211 color: theme.palette.text.secondary,
176 }, 212 },
diff --git a/subprojects/frontend/src/editor/EditorStore.ts b/subprojects/frontend/src/editor/EditorStore.ts
index 0f4d2936..f75147a4 100644
--- a/subprojects/frontend/src/editor/EditorStore.ts
+++ b/subprojects/frontend/src/editor/EditorStore.ts
@@ -21,18 +21,14 @@ import {
21 indentOnInput, 21 indentOnInput,
22 syntaxHighlighting, 22 syntaxHighlighting,
23} from '@codemirror/language'; 23} from '@codemirror/language';
24import { 24import { type Diagnostic, lintKeymap, setDiagnostics } from '@codemirror/lint';
25 Diagnostic,
26 lintKeymap,
27 setDiagnostics,
28} from '@codemirror/lint';
29import { search, searchKeymap } from '@codemirror/search'; 25import { search, searchKeymap } from '@codemirror/search';
30import { 26import {
31 EditorState, 27 EditorState,
32 StateCommand, 28 type StateCommand,
33 StateEffect, 29 StateEffect,
34 Transaction, 30 type Transaction,
35 TransactionSpec, 31 type TransactionSpec,
36} from '@codemirror/state'; 32} from '@codemirror/state';
37import { 33import {
38 drawSelection, 34 drawSelection,
@@ -45,26 +41,25 @@ import {
45 rectangularSelection, 41 rectangularSelection,
46} from '@codemirror/view'; 42} from '@codemirror/view';
47import { classHighlighter } from '@lezer/highlight'; 43import { classHighlighter } from '@lezer/highlight';
48import { 44import { makeAutoObservable, observable, reaction } from 'mobx';
49 makeAutoObservable, 45
50 observable, 46import problemLanguageSupport from '../language/problemLanguageSupport';
51 reaction, 47import type ThemeStore from '../theme/ThemeStore';
52} from 'mobx'; 48import getLogger from '../utils/getLogger';
53 49import XtextClient from '../xtext/XtextClient';
54import { findOccurrences, IOccurrence, setOccurrences } from './findOccurrences'; 50
55import { problemLanguageSupport } from '../language/problemLanguageSupport'; 51import findOccurrences, {
56import { 52 type IOccurrence,
57 IHighlightRange, 53 setOccurrences,
58 semanticHighlighting, 54} from './findOccurrences';
55import semanticHighlighting, {
56 type IHighlightRange,
59 setSemanticHighlighting, 57 setSemanticHighlighting,
60} from './semanticHighlighting'; 58} from './semanticHighlighting';
61import type { ThemeStore } from '../theme/ThemeStore';
62import { getLogger } from '../utils/logger';
63import { XtextClient } from '../xtext/XtextClient';
64 59
65const log = getLogger('editor.EditorStore'); 60const log = getLogger('editor.EditorStore');
66 61
67export class EditorStore { 62export default class EditorStore {
68 private readonly themeStore; 63 private readonly themeStore;
69 64
70 state: EditorState; 65 state: EditorState;
@@ -96,17 +91,18 @@ export class EditorStore {
96 extensions: [ 91 extensions: [
97 autocompletion({ 92 autocompletion({
98 activateOnTyping: true, 93 activateOnTyping: true,
99 override: [ 94 override: [(context) => this.client.contentAssist(context)],
100 (context) => this.client.contentAssist(context),
101 ],
102 }), 95 }),
103 closeBrackets(), 96 closeBrackets(),
104 bracketMatching(), 97 bracketMatching(),
105 drawSelection(), 98 drawSelection(),
106 EditorState.allowMultipleSelections.of(true), 99 EditorState.allowMultipleSelections.of(true),
107 EditorView.theme({}, { 100 EditorView.theme(
108 dark: this.themeStore.darkMode, 101 {},
109 }), 102 {
103 dark: this.themeStore.darkMode,
104 },
105 ),
110 findOccurrences, 106 findOccurrences,
111 highlightActiveLine(), 107 highlightActiveLine(),
112 highlightActiveLineGutter(), 108 highlightActiveLineGutter(),
@@ -134,8 +130,16 @@ export class EditorStore {
134 { key: 'Mod-Shift-m', run: () => this.setLintPanelOpen(true) }, 130 { key: 'Mod-Shift-m', run: () => this.setLintPanelOpen(true) },
135 ...lintKeymap, 131 ...lintKeymap,
136 // Override keys in `searchKeymap` to go through the `EditorStore`. 132 // Override keys in `searchKeymap` to go through the `EditorStore`.
137 { key: 'Mod-f', run: () => this.setSearchPanelOpen(true), scope: 'editor search-panel' }, 133 {
138 { key: 'Escape', run: () => this.setSearchPanelOpen(false), scope: 'editor search-panel' }, 134 key: 'Mod-f',
135 run: () => this.setSearchPanelOpen(true),
136 scope: 'editor search-panel',
137 },
138 {
139 key: 'Escape',
140 run: () => this.setSearchPanelOpen(false),
141 scope: 'editor search-panel',
142 },
139 ...searchKeymap, 143 ...searchKeymap,
140 ...defaultKeymap, 144 ...defaultKeymap,
141 ]), 145 ]),
@@ -149,9 +153,14 @@ export class EditorStore {
149 log.debug('Update editor dark mode', darkMode); 153 log.debug('Update editor dark mode', darkMode);
150 this.dispatch({ 154 this.dispatch({
151 effects: [ 155 effects: [
152 StateEffect.appendConfig.of(EditorView.theme({}, { 156 StateEffect.appendConfig.of(
153 dark: darkMode, 157 EditorView.theme(
154 })), 158 {},
159 {
160 dark: darkMode,
161 },
162 ),
163 ),
155 ], 164 ],
156 }); 165 });
157 }, 166 },
@@ -198,6 +207,8 @@ export class EditorStore {
198 case 'info': 207 case 'info':
199 this.infoCount += 1; 208 this.infoCount += 1;
200 break; 209 break;
210 default:
211 throw new Error('Unknown severity');
201 } 212 }
202 }); 213 });
203 } 214 }
@@ -261,7 +272,7 @@ export class EditorStore {
261 * This matches the behavior of the `openSearchPanel` and `closeSearchPanel` 272 * This matches the behavior of the `openSearchPanel` and `closeSearchPanel`
262 * commands from `'@codemirror/search'`. 273 * commands from `'@codemirror/search'`.
263 * 274 *
264 * @param newShosSearchPanel whether we should show the search panel 275 * @param newShowSearchPanel whether we should show the search panel
265 * @returns `true` if the state was changed, `false` otherwise 276 * @returns `true` if the state was changed, `false` otherwise
266 */ 277 */
267 setSearchPanelOpen(newShowSearchPanel: boolean): boolean { 278 setSearchPanelOpen(newShowSearchPanel: boolean): boolean {
diff --git a/subprojects/frontend/src/editor/GenerateButton.tsx b/subprojects/frontend/src/editor/GenerateButton.tsx
index 3834cec4..fc337da9 100644
--- a/subprojects/frontend/src/editor/GenerateButton.tsx
+++ b/subprojects/frontend/src/editor/GenerateButton.tsx
@@ -1,13 +1,13 @@
1import { observer } from 'mobx-react-lite';
2import Button from '@mui/material/Button';
3import PlayArrowIcon from '@mui/icons-material/PlayArrow'; 1import PlayArrowIcon from '@mui/icons-material/PlayArrow';
2import Button from '@mui/material/Button';
3import { observer } from 'mobx-react-lite';
4import React from 'react'; 4import React from 'react';
5 5
6import { useRootStore } from '../RootStore'; 6import { useRootStore } from '../RootStore';
7 7
8const GENERATE_LABEL = 'Generate'; 8const GENERATE_LABEL = 'Generate';
9 9
10export const GenerateButton = observer(() => { 10function GenerateButton(): JSX.Element {
11 const { editorStore } = useRootStore(); 11 const { editorStore } = useRootStore();
12 const { errorCount, warningCount } = editorStore; 12 const { errorCount, warningCount } = editorStore;
13 13
@@ -41,4 +41,6 @@ export const GenerateButton = observer(() => {
41 {summary === '' ? GENERATE_LABEL : `${GENERATE_LABEL} (${summary})`} 41 {summary === '' ? GENERATE_LABEL : `${GENERATE_LABEL} (${summary})`}
42 </Button> 42 </Button>
43 ); 43 );
44}); 44}
45
46export default observer(GenerateButton);
diff --git a/subprojects/frontend/src/editor/decorationSetExtension.ts b/subprojects/frontend/src/editor/defineDecorationSetExtension.ts
index 2d630c20..d9c7bc7d 100644
--- a/subprojects/frontend/src/editor/decorationSetExtension.ts
+++ b/subprojects/frontend/src/editor/defineDecorationSetExtension.ts
@@ -1,11 +1,16 @@
1import { StateEffect, StateField, TransactionSpec } from '@codemirror/state'; 1import { StateEffect, StateField, TransactionSpec } from '@codemirror/state';
2import { EditorView, Decoration, DecorationSet } from '@codemirror/view'; 2import { EditorView, Decoration, DecorationSet } from '@codemirror/view';
3 3
4export type TransactionSpecFactory = (decorations: DecorationSet) => TransactionSpec; 4export type TransactionSpecFactory = (
5 decorations: DecorationSet,
6) => TransactionSpec;
5 7
6export function decorationSetExtension(): [TransactionSpecFactory, StateField<DecorationSet>] { 8export default function defineDecorationSetExtension(): [
9 TransactionSpecFactory,
10 StateField<DecorationSet>,
11] {
7 const setEffect = StateEffect.define<DecorationSet>(); 12 const setEffect = StateEffect.define<DecorationSet>();
8 const field = StateField.define<DecorationSet>({ 13 const stateField = StateField.define<DecorationSet>({
9 create() { 14 create() {
10 return Decoration.none; 15 return Decoration.none;
11 }, 16 },
@@ -24,16 +29,14 @@ export function decorationSetExtension(): [TransactionSpecFactory, StateField<De
24 } 29 }
25 return newDecorations; 30 return newDecorations;
26 }, 31 },
27 provide: (f) => EditorView.decorations.from(f), 32 provide: (field) => EditorView.decorations.from(field),
28 }); 33 });
29 34
30 function transactionSpecFactory(decorations: DecorationSet) { 35 function transactionSpecFactory(decorations: DecorationSet) {
31 return { 36 return {
32 effects: [ 37 effects: [setEffect.of(decorations)],
33 setEffect.of(decorations),
34 ],
35 }; 38 };
36 } 39 }
37 40
38 return [transactionSpecFactory, field]; 41 return [transactionSpecFactory, stateField];
39} 42}
diff --git a/subprojects/frontend/src/editor/findOccurrences.ts b/subprojects/frontend/src/editor/findOccurrences.ts
index c4a4e8ec..d7aae8d1 100644
--- a/subprojects/frontend/src/editor/findOccurrences.ts
+++ b/subprojects/frontend/src/editor/findOccurrences.ts
@@ -1,7 +1,7 @@
1import { Range, RangeSet, type TransactionSpec } from '@codemirror/state'; 1import { type Range, RangeSet, type TransactionSpec } from '@codemirror/state';
2import { Decoration } from '@codemirror/view'; 2import { Decoration } from '@codemirror/view';
3 3
4import { decorationSetExtension } from './decorationSetExtension'; 4import defineDecorationSetExtension from './defineDecorationSetExtension';
5 5
6export interface IOccurrence { 6export interface IOccurrence {
7 from: number; 7 from: number;
@@ -9,7 +9,7 @@ export interface IOccurrence {
9 to: number; 9 to: number;
10} 10}
11 11
12const [setOccurrencesInteral, findOccurrences] = decorationSetExtension(); 12const [setOccurrencesInteral, findOccurrences] = defineDecorationSetExtension();
13 13
14const writeDecoration = Decoration.mark({ 14const writeDecoration = Decoration.mark({
15 class: 'cm-problem-write', 15 class: 'cm-problem-write',
@@ -19,7 +19,10 @@ const readDecoration = Decoration.mark({
19 class: 'cm-problem-read', 19 class: 'cm-problem-read',
20}); 20});
21 21
22export function setOccurrences(write: IOccurrence[], read: IOccurrence[]): TransactionSpec { 22export function setOccurrences(
23 write: IOccurrence[],
24 read: IOccurrence[],
25): TransactionSpec {
23 const decorations: Range<Decoration>[] = []; 26 const decorations: Range<Decoration>[] = [];
24 write.forEach(({ from, to }) => { 27 write.forEach(({ from, to }) => {
25 decorations.push(writeDecoration.range(from, to)); 28 decorations.push(writeDecoration.range(from, to));
@@ -31,4 +34,4 @@ export function setOccurrences(write: IOccurrence[], read: IOccurrence[]): Trans
31 return setOccurrencesInteral(rangeSet); 34 return setOccurrencesInteral(rangeSet);
32} 35}
33 36
34export { findOccurrences }; 37export default findOccurrences;
diff --git a/subprojects/frontend/src/editor/semanticHighlighting.ts b/subprojects/frontend/src/editor/semanticHighlighting.ts
index a5d0af7a..2c1bd67d 100644
--- a/subprojects/frontend/src/editor/semanticHighlighting.ts
+++ b/subprojects/frontend/src/editor/semanticHighlighting.ts
@@ -1,7 +1,7 @@
1import { RangeSet, type TransactionSpec } from '@codemirror/state'; 1import { RangeSet, type TransactionSpec } from '@codemirror/state';
2import { Decoration } from '@codemirror/view'; 2import { Decoration } from '@codemirror/view';
3 3
4import { decorationSetExtension } from './decorationSetExtension'; 4import defineDecorationSetExtension from './defineDecorationSetExtension';
5 5
6export interface IHighlightRange { 6export interface IHighlightRange {
7 from: number; 7 from: number;
@@ -11,13 +11,21 @@ export interface IHighlightRange {
11 classes: string[]; 11 classes: string[];
12} 12}
13 13
14const [setSemanticHighlightingInternal, semanticHighlighting] = decorationSetExtension(); 14const [setSemanticHighlightingInternal, semanticHighlighting] =
15 defineDecorationSetExtension();
15 16
16export function setSemanticHighlighting(ranges: IHighlightRange[]): TransactionSpec { 17export function setSemanticHighlighting(
17 const rangeSet = RangeSet.of(ranges.map(({ from, to, classes }) => Decoration.mark({ 18 ranges: IHighlightRange[],
18 class: classes.map((c) => `tok-problem-${c}`).join(' '), 19): TransactionSpec {
19 }).range(from, to)), true); 20 const rangeSet = RangeSet.of(
21 ranges.map(({ from, to, classes }) =>
22 Decoration.mark({
23 class: classes.map((c) => `tok-problem-${c}`).join(' '),
24 }).range(from, to),
25 ),
26 true,
27 );
20 return setSemanticHighlightingInternal(rangeSet); 28 return setSemanticHighlightingInternal(rangeSet);
21} 29}
22 30
23export { semanticHighlighting }; 31export default semanticHighlighting;