Skip to content

Commit ea0c163

Browse files
committed
Apply editor settings in web playground
1 parent 9843794 commit ea0c163

File tree

6 files changed

+174
-75
lines changed

6 files changed

+174
-75
lines changed

www/bun.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"": {
55
"name": "www",
66
"dependencies": {
7+
"@codemirror/lang-rust": "^6.0.1",
78
"@radix-ui/react-dialog": "^1.1.7",
89
"@radix-ui/react-label": "^2.1.3",
910
"@radix-ui/react-select": "^2.1.7",
@@ -91,6 +92,8 @@
9192

9293
"@codemirror/commands": ["@codemirror/[email protected]", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw=="],
9394

95+
"@codemirror/lang-rust": ["@codemirror/[email protected]", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/rust": "^1.0.0" } }, "sha512-344EMWFBzWArHWdZn/NcgkwMvZIWUR1GEBdwG8FEp++6o6vT6KL9V7vGs2ONsKxxFUPXKI0SPcWhyYyl2zPYxQ=="],
96+
9497
"@codemirror/language": ["@codemirror/[email protected]", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.1.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ=="],
9598

9699
"@codemirror/lint": ["@codemirror/[email protected]", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.35.0", "crelt": "^1.0.5" } }, "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA=="],
@@ -185,6 +188,8 @@
185188

186189
"@lezer/lr": ["@lezer/[email protected]", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA=="],
187190

191+
"@lezer/rust": ["@lezer/[email protected]", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg=="],
192+
188193
"@marijn/find-cluster-break": ["@marijn/[email protected]", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="],
189194

190195
"@nodelib/fs.scandir": ["@nodelib/[email protected]", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],

www/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"preview": "vite preview"
1414
},
1515
"dependencies": {
16+
"@codemirror/lang-rust": "^6.0.1",
1617
"@radix-ui/react-dialog": "^1.1.7",
1718
"@radix-ui/react-label": "^2.1.3",
1819
"@radix-ui/react-select": "^2.1.7",

www/src/App.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
} from './components/ui/resizable';
2020

2121
const EXAMPLES = {
22-
Factorial: `fn factorial(n) {
22+
factorial: `fn factorial(n) {
2323
if (n <= 1) {
2424
1
2525
} else {
@@ -32,27 +32,31 @@ print(factorial(5));`,
3232

3333
function App() {
3434
const [wasmLoaded, setWasmLoaded] = useState(false);
35-
const [code, setCode] = useState(EXAMPLES.Factorial);
36-
const [currentExample, setCurrentExample] = useState('Factorial');
35+
3736
const [ast, setAst] = useState<AstNode | null>(null);
38-
const [parseErrors, setParseErrors] = useState<ParseError[]>([]);
37+
const [code, setCode] = useState(EXAMPLES.factorial);
38+
const [errors, setErrors] = useState<ParseError[]>([]);
39+
40+
const [currentExample, setCurrentExample] = useState('factorial');
3941

4042
useEffect(() => {
4143
init()
4244
.then(() => {
4345
setWasmLoaded(true);
46+
setAst(parse(code));
4447
})
4548
.catch((error) => {
4649
toast.error(error);
4750
});
4851
}, []);
4952

5053
useEffect(() => {
54+
if (!wasmLoaded) return;
55+
5156
try {
5257
setAst(parse(code));
53-
setParseErrors([]);
5458
} catch (error) {
55-
setParseErrors(error as ParseError[]);
59+
setErrors(error as ParseError[]);
5660
}
5761
}, [code]);
5862

@@ -102,7 +106,7 @@ function App() {
102106
<EditorSettingsDialog />
103107
</div>
104108
<div className='h-full min-h-0 flex-grow overflow-hidden'>
105-
<Editor errors={parseErrors} value={code} onChange={setCode} />
109+
<Editor errors={errors} value={code} onChange={setCode} />
106110
</div>
107111
</div>
108112
</div>

www/src/components/editor.tsx

Lines changed: 105 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
1+
import { highlightExtension } from '@/lib/cm-highlight-extension';
2+
import { useEditorSettings } from '@/providers/editor-settings-provider';
3+
import { rust } from '@codemirror/lang-rust';
4+
import {
5+
bracketMatching,
6+
defaultHighlightStyle,
7+
indentOnInput,
8+
syntaxHighlighting,
9+
} from '@codemirror/language';
110
import { Diagnostic, lintGutter, linter } from '@codemirror/lint';
2-
import CodeMirror, { EditorView } from '@uiw/react-codemirror';
11+
import CodeMirror, { EditorState, EditorView } from '@uiw/react-codemirror';
312
import { ParseError } from 'packages/val-wasm/val';
413
import { forwardRef, useCallback, useImperativeHandle, useRef } from 'react';
514

@@ -15,6 +24,8 @@ export interface EditorRef {
1524

1625
export const Editor = forwardRef<EditorRef, EditorProps>(
1726
({ value, errors, onChange }, ref) => {
27+
const { settings } = useEditorSettings();
28+
1829
const viewRef = useRef<EditorView | null>(null);
1930

2031
useImperativeHandle(ref, () => ({
@@ -23,86 +34,112 @@ export const Editor = forwardRef<EditorRef, EditorProps>(
2334
},
2435
}));
2536

26-
const theme = EditorView.theme({
27-
'&': {
28-
height: '100%',
29-
fontSize: '14px',
30-
display: 'flex',
31-
flexDirection: 'column',
32-
},
33-
'&.cm-editor': {
34-
height: '100%',
35-
},
36-
'&.cm-focused': {
37-
outline: 'none',
38-
},
39-
'.cm-scroller': {
40-
overflow: 'auto',
41-
flex: '1 1 auto',
42-
fontFamily:
43-
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
44-
},
45-
'.cm-content': {
46-
padding: '10px 0',
47-
},
48-
'.cm-line': {
49-
padding: '0 10px',
50-
},
51-
'.cm-gutters': {
52-
backgroundColor: 'transparent',
53-
borderRight: 'none',
54-
paddingRight: '8px',
55-
},
56-
'.cm-activeLineGutter': {
57-
backgroundColor: 'rgba(59, 130, 246, 0.1)',
58-
},
59-
'.cm-activeLine': {
60-
backgroundColor: 'rgba(59, 130, 246, 0.1)',
61-
},
62-
'.cm-fat-cursor': {
63-
backgroundColor: 'rgba(59, 130, 246, 0.5)',
64-
borderLeft: 'none',
65-
width: '0.6em',
66-
},
67-
'.cm-cursor-secondary': {
68-
backgroundColor: 'rgba(59, 130, 246, 0.3)',
69-
},
70-
});
37+
const createEditorTheme = useCallback(
38+
() =>
39+
EditorView.theme({
40+
'&': {
41+
height: '100%',
42+
fontSize: `${settings.fontSize}px`,
43+
display: 'flex',
44+
flexDirection: 'column',
45+
},
46+
'&.cm-editor': {
47+
height: '100%',
48+
},
49+
'.cm-scroller': {
50+
overflow: 'auto',
51+
flex: '1 1 auto',
52+
fontFamily:
53+
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
54+
},
55+
'.cm-content': {
56+
padding: '10px 0',
57+
},
58+
'.cm-line': {
59+
padding: '0 10px',
60+
},
61+
'.cm-gutters': {
62+
backgroundColor: 'transparent',
63+
borderRight: 'none',
64+
paddingRight: '8px',
65+
},
66+
'.cm-activeLineGutter': {
67+
backgroundColor: 'rgba(59, 130, 246, 0.1)',
68+
},
69+
'.cm-activeLine': {
70+
backgroundColor: 'rgba(59, 130, 246, 0.1)',
71+
},
72+
'.cm-fat-cursor': {
73+
backgroundColor: 'rgba(59, 130, 246, 0.5)',
74+
borderLeft: 'none',
75+
width: '0.6em',
76+
},
77+
'.cm-cursor-secondary': {
78+
backgroundColor: 'rgba(59, 130, 246, 0.3)',
79+
},
80+
}),
81+
[settings]
82+
);
83+
84+
const createExtensions = useCallback(() => {
85+
const extensions = [
86+
EditorState.tabSize.of(settings.tabSize),
87+
bracketMatching(),
88+
highlightExtension,
89+
indentOnInput(),
90+
lintGutter(),
91+
linter(diagnostics()),
92+
rust(),
93+
syntaxHighlighting(defaultHighlightStyle),
94+
];
95+
96+
if (settings.lineWrapping) {
97+
extensions.push(EditorView.lineWrapping);
98+
}
99+
100+
return extensions;
101+
}, [settings]);
71102

72103
const diagnostics = () =>
73104
useCallback(
74105
(_view: EditorView): Diagnostic[] => {
75-
return errors.map((error) => {
76-
try {
77-
return {
78-
from: error.range.start,
79-
to: error.range.end,
80-
severity: 'error',
81-
message: error.message,
82-
source: 'PARSER',
83-
};
84-
} catch (e) {
85-
console.warn('Failed to create diagnostic:', e, error);
106+
return (
107+
errors &&
108+
errors.map((error) => {
109+
try {
110+
return {
111+
from: error.range.start,
112+
to: error.range.end,
113+
severity: 'error',
114+
message: error.message,
115+
source: 'PARSER',
116+
};
117+
} catch (e) {
118+
console.warn('Failed to create diagnostic:', e, error);
86119

87-
return {
88-
from: 0,
89-
to: 0,
90-
severity: 'error',
91-
message: error.message,
92-
source: 'PARSER',
93-
};
94-
}
95-
});
120+
return {
121+
from: 0,
122+
to: 0,
123+
severity: 'error',
124+
message: error.message,
125+
source: 'PARSER',
126+
};
127+
}
128+
})
129+
);
96130
},
97131
[errors]
98132
);
99133

100134
return (
101135
<CodeMirror
102136
value={value}
103-
theme={theme}
137+
theme={createEditorTheme()}
138+
basicSetup={{
139+
lineNumbers: settings.lineNumbers,
140+
}}
104141
height='100%'
105-
extensions={[lintGutter(), linter(diagnostics())]}
142+
extensions={createExtensions()}
106143
onCreateEditor={(view) => {
107144
viewRef.current = view;
108145
}}

www/src/index.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@
119119
}
120120
}
121121

122+
.cm-highlighted-node {
123+
background-color: rgba(59, 130, 246, 0.15);
124+
outline: 1px solid rgba(59, 130, 246, 0.3);
125+
border-radius: 2px;
126+
}
127+
122128
::-webkit-scrollbar {
123129
width: 3px;
124130
height: 3px;

www/src/lib/cm-highlight-extension.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { StateEffect } from '@codemirror/state';
2+
import {
3+
Decoration,
4+
DecorationSet,
5+
ViewPlugin,
6+
ViewUpdate,
7+
} from '@codemirror/view';
8+
9+
const highlightMark = Decoration.mark({ class: 'cm-highlighted-node' });
10+
11+
export const addHighlightEffect = StateEffect.define<{
12+
from: number;
13+
to: number;
14+
}>();
15+
16+
export const removeHighlightEffect = StateEffect.define<null>();
17+
18+
export const highlightExtension = ViewPlugin.fromClass(
19+
class {
20+
decorations: DecorationSet;
21+
22+
constructor() {
23+
this.decorations = Decoration.none;
24+
}
25+
26+
update(update: ViewUpdate) {
27+
const effects = update.transactions
28+
.flatMap((tr) => tr.effects)
29+
.filter((e) => e.is(addHighlightEffect) || e.is(removeHighlightEffect));
30+
31+
if (!effects.length) return;
32+
33+
for (const effect of effects) {
34+
if (effect.is(addHighlightEffect)) {
35+
const { from, to } = effect.value;
36+
this.decorations = Decoration.set([highlightMark.range(from, to)]);
37+
} else if (effect.is(removeHighlightEffect)) {
38+
this.decorations = Decoration.none;
39+
}
40+
}
41+
}
42+
},
43+
{
44+
decorations: (v) => v.decorations,
45+
}
46+
);

0 commit comments

Comments
 (0)