vzcode 1.31.0 → 1.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/index-sreAszZh.js +240 -0
- package/dist/assets/worker-BKfYWklR.js +136 -0
- package/dist/assets/worker-COyDRdqW.js +453 -0
- package/dist/assets/{worker-CaP6zYz7.js → worker-CWsWXMyk.js} +70 -70
- package/dist/index.html +1 -1
- package/package.json +40 -33
- package/src/client/App/index.tsx +3 -0
- package/src/client/CodeEditor/getOrCreateEditor.ts +80 -4
- package/src/client/CodeEditor/index.tsx +56 -24
- package/src/client/CodeEditor/typescriptExtension/worker.ts +32 -0
- package/src/client/VZMiddle.tsx +11 -0
- package/src/client/VZSidebar/Search.tsx +11 -0
- package/src/client/useESLint/index.ts +72 -0
- package/src/client/useESLint/worker.ts +113 -0
- package/src/server/featureFlags.js +2 -0
- package/dist/assets/index-D01yzCWj.js +0 -234
package/dist/index.html
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"
|
|
21
21
|
rel="stylesheet"
|
|
22
22
|
/>
|
|
23
|
-
<script type="module" crossorigin src="/assets/index-
|
|
23
|
+
<script type="module" crossorigin src="/assets/index-sreAszZh.js"></script>
|
|
24
24
|
<link rel="stylesheet" crossorigin href="/assets/index-DTtcN2MP.css">
|
|
25
25
|
</head>
|
|
26
26
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vzcode",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.32.0",
|
|
4
4
|
"description": "Multiplayer code editor system",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -107,72 +107,79 @@
|
|
|
107
107
|
"@codemirror/autocomplete": "^6.18.6",
|
|
108
108
|
"@codemirror/lang-css": "^6.3.1",
|
|
109
109
|
"@codemirror/lang-html": "^6.4.9",
|
|
110
|
-
"@codemirror/lang-javascript": "^6.2.
|
|
110
|
+
"@codemirror/lang-javascript": "^6.2.4",
|
|
111
111
|
"@codemirror/lang-json": "^6.0.1",
|
|
112
112
|
"@codemirror/lang-markdown": "^6.3.2",
|
|
113
|
+
"@codemirror/lint": "^6.8.5",
|
|
113
114
|
"@codemirror/state": "^6.5.2",
|
|
114
115
|
"@codemirror/theme-one-dark": "^6.1.2",
|
|
115
|
-
"@codemirror/view": "^6.
|
|
116
|
-
"@langchain/core": "^0.3.
|
|
117
|
-
"@langchain/openai": "^0.5.
|
|
116
|
+
"@codemirror/view": "^6.37.1",
|
|
117
|
+
"@langchain/core": "^0.3.57",
|
|
118
|
+
"@langchain/openai": "^0.5.12",
|
|
118
119
|
"@lezer/highlight": "^1.2.1",
|
|
119
|
-
"@livekit/components-react": "^2.9.
|
|
120
|
+
"@livekit/components-react": "^2.9.9",
|
|
120
121
|
"@replit/codemirror-indentation-markers": "^6.5.3",
|
|
121
122
|
"@replit/codemirror-interact": "^6.3.1",
|
|
122
123
|
"@replit/codemirror-lang-svelte": "^6.0.0",
|
|
123
124
|
"@replit/codemirror-vscode-keymap": "^6.0.2",
|
|
124
125
|
"@teamwork/websocket-json-stream": "^2.0.0",
|
|
125
|
-
"@
|
|
126
|
-
"@uiw/codemirror-theme-
|
|
127
|
-
"@uiw/codemirror-theme-
|
|
128
|
-
"@uiw/codemirror-theme-
|
|
129
|
-
"@uiw/codemirror-theme-
|
|
130
|
-
"@uiw/codemirror-theme-
|
|
131
|
-
"@uiw/codemirror-theme-
|
|
132
|
-
"@uiw/codemirror-theme-
|
|
133
|
-
"@uiw/codemirror-
|
|
126
|
+
"@typescript/vfs": "^1.6.1",
|
|
127
|
+
"@uiw/codemirror-theme-abcdef": "^4.23.12",
|
|
128
|
+
"@uiw/codemirror-theme-dracula": "^4.23.12",
|
|
129
|
+
"@uiw/codemirror-theme-eclipse": "^4.23.12",
|
|
130
|
+
"@uiw/codemirror-theme-github": "^4.23.12",
|
|
131
|
+
"@uiw/codemirror-theme-material": "^4.23.12",
|
|
132
|
+
"@uiw/codemirror-theme-nord": "^4.23.12",
|
|
133
|
+
"@uiw/codemirror-theme-okaidia": "^4.23.12",
|
|
134
|
+
"@uiw/codemirror-theme-xcode": "^4.23.12",
|
|
135
|
+
"@uiw/codemirror-themes": "^4.23.12",
|
|
136
|
+
"@valtown/codemirror-ts": "^2.3.1",
|
|
134
137
|
"@vizhub/viz-types": "^0.1.0",
|
|
135
138
|
"body-parser": "^2.2.0",
|
|
136
139
|
"codemirror": "^6.0.1",
|
|
137
140
|
"codemirror-copilot": "^0.0.7",
|
|
138
141
|
"codemirror-ot": "^4.4.0",
|
|
139
142
|
"color-hash": "^2.0.2",
|
|
143
|
+
"comlink": "^4.4.2",
|
|
140
144
|
"d3-array": "^3.2.4",
|
|
141
145
|
"diff-match-patch": "^1.0.5",
|
|
142
|
-
"dotenv": "^16.
|
|
146
|
+
"dotenv": "^16.5.0",
|
|
147
|
+
"eslint": "^9.28.0",
|
|
148
|
+
"eslint-linter-browserify": "^9.28.0",
|
|
143
149
|
"express": "^5.1.0",
|
|
144
|
-
"
|
|
150
|
+
"globals": "^16.2.0",
|
|
151
|
+
"ignore": "^7.0.5",
|
|
145
152
|
"json0-ot-diff": "^1.1.2",
|
|
146
|
-
"livekit-server-sdk": "^2.
|
|
153
|
+
"livekit-server-sdk": "^2.13.0",
|
|
147
154
|
"ngrok": "^5.0.0-beta.2",
|
|
148
|
-
"npm": "^11.
|
|
149
|
-
"open": "^10.1.
|
|
150
|
-
"prettier-plugin-svelte": "^3.
|
|
155
|
+
"npm": "^11.4.1",
|
|
156
|
+
"open": "^10.1.2",
|
|
157
|
+
"prettier-plugin-svelte": "^3.4.0",
|
|
151
158
|
"react": "^18",
|
|
152
|
-
"react-bootstrap": "^2.10.
|
|
159
|
+
"react-bootstrap": "^2.10.10",
|
|
153
160
|
"react-dom": "^18",
|
|
154
|
-
"react-router-dom": "^7.
|
|
155
|
-
"sharedb": "^5.2.
|
|
156
|
-
"sharedb-client-browser": "^5.
|
|
161
|
+
"react-router-dom": "^7.6.2",
|
|
162
|
+
"sharedb": "^5.2.2",
|
|
163
|
+
"sharedb-client-browser": "^5.2.2",
|
|
157
164
|
"uuid": "^11.1.0",
|
|
158
165
|
"vizhub-ui": "^3.23.0",
|
|
159
|
-
"ws": "^8.18.
|
|
166
|
+
"ws": "^8.18.2"
|
|
160
167
|
},
|
|
161
168
|
"devDependencies": {
|
|
162
169
|
"@types/react": "^18",
|
|
163
170
|
"@types/react-dom": "^18",
|
|
164
|
-
"@vitejs/plugin-react": "^4.
|
|
171
|
+
"@vitejs/plugin-react": "^4.5.1",
|
|
165
172
|
"concurrently": "^9.1.2",
|
|
166
173
|
"cross-env": "^7.0.3",
|
|
167
|
-
"npm-check-updates": "^
|
|
174
|
+
"npm-check-updates": "^18.0.1",
|
|
168
175
|
"prettier": "^3.5.3",
|
|
169
|
-
"sass": "^1.
|
|
176
|
+
"sass": "^1.89.1",
|
|
170
177
|
"typescript": "^5.8.3",
|
|
171
|
-
"vite": "^6.
|
|
172
|
-
"vitest": "^3.
|
|
178
|
+
"vite": "^6.3.5",
|
|
179
|
+
"vitest": "^3.2.2"
|
|
173
180
|
},
|
|
174
181
|
"optionalDependencies": {
|
|
175
|
-
"@rollup/rollup-darwin-arm64": "^4.
|
|
176
|
-
"@rollup/rollup-win32-x64-msvc": "^4.
|
|
182
|
+
"@rollup/rollup-darwin-arm64": "^4.42.0",
|
|
183
|
+
"@rollup/rollup-win32-x64-msvc": "^4.42.0"
|
|
177
184
|
}
|
|
178
185
|
}
|
package/src/client/App/index.tsx
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from '../usernameLocalStorage';
|
|
26
26
|
import './style.scss';
|
|
27
27
|
import { useShareDB } from './useShareDB';
|
|
28
|
+
import { useESLint } from '../useESLint';
|
|
28
29
|
|
|
29
30
|
// Instantiate the Prettier worker.
|
|
30
31
|
const prettierWorker = new PrettierWorker();
|
|
@@ -75,6 +76,7 @@ function App() {
|
|
|
75
76
|
useState(v4()); //default room name will be an arbitrary uuid
|
|
76
77
|
const [liveKitConnection, setLiveKitConnection] =
|
|
77
78
|
useState(false);
|
|
79
|
+
const { esLintSource } = useESLint();
|
|
78
80
|
// Get the initial username from localStorage.
|
|
79
81
|
const initialUsername: Username = useInitialUsername();
|
|
80
82
|
|
|
@@ -129,6 +131,7 @@ function App() {
|
|
|
129
131
|
aiCopilotEndpoint={
|
|
130
132
|
enableCopilot ? '/ai-copilot' : null
|
|
131
133
|
}
|
|
134
|
+
esLintSource={esLintSource}
|
|
132
135
|
/>
|
|
133
136
|
{enableRightPanel ? <VZRight /> : null}
|
|
134
137
|
<VZResizer side="left" />
|
|
@@ -2,6 +2,7 @@ import { EditorView } from 'codemirror';
|
|
|
2
2
|
import {
|
|
3
3
|
Compartment,
|
|
4
4
|
EditorState,
|
|
5
|
+
StateField,
|
|
5
6
|
} from '@codemirror/state';
|
|
6
7
|
import { javascript } from '@codemirror/lang-javascript';
|
|
7
8
|
import { svelte } from '@replit/codemirror-lang-svelte';
|
|
@@ -15,7 +16,11 @@ import { autocompletion } from '@codemirror/autocomplete';
|
|
|
15
16
|
import { indentationMarkers } from '@replit/codemirror-indentation-markers';
|
|
16
17
|
// import { showMinimap } from '@replit/codemirror-minimap';
|
|
17
18
|
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
|
18
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
Diagnostic,
|
|
21
|
+
linter,
|
|
22
|
+
lintGutter,
|
|
23
|
+
} from '@codemirror/lint';
|
|
19
24
|
|
|
20
25
|
import { json1Presence, textUnicode } from '../../ot';
|
|
21
26
|
import {
|
|
@@ -41,17 +46,54 @@ import { ThemeLabel, themeOptionsByLabel } from '../themes';
|
|
|
41
46
|
import { keymap } from '@codemirror/view';
|
|
42
47
|
import { basicSetup } from './basicSetup';
|
|
43
48
|
import { InteractRule } from '@replit/codemirror-interact';
|
|
44
|
-
import rainbowBrackets from '
|
|
49
|
+
import rainbowBrackets from './rainbowBrackets';
|
|
45
50
|
import { cssLanguage } from '@codemirror/lang-css';
|
|
46
51
|
import { javascriptLanguage } from '@codemirror/lang-javascript';
|
|
47
52
|
import { copilot } from './Copilot';
|
|
53
|
+
import { type WorkerShape } from '@valtown/codemirror-ts/src/worker';
|
|
54
|
+
import * as Comlink from 'comlink';
|
|
55
|
+
import {
|
|
56
|
+
tsAutocompleteWorker,
|
|
57
|
+
tsFacetWorker,
|
|
58
|
+
tsHoverWorker,
|
|
59
|
+
tsLinterWorker,
|
|
60
|
+
tsSyncWorker,
|
|
61
|
+
} from '@valtown/codemirror-ts';
|
|
48
62
|
|
|
49
63
|
const DEBUG = false;
|
|
50
64
|
|
|
65
|
+
// Define a StateField to store the file name.
|
|
66
|
+
// This should be defined at the module level if it's to be imported by other modules.
|
|
67
|
+
export const fileNameStateField = StateField.define<string>(
|
|
68
|
+
{
|
|
69
|
+
create: () => '', // Default initial value
|
|
70
|
+
update: (value, tr) => value, // Typically set once at creation for a given editor instance
|
|
71
|
+
},
|
|
72
|
+
);
|
|
73
|
+
|
|
51
74
|
// Enables TypeScript +JSX support in CodeMirror.
|
|
52
75
|
const tsx = () =>
|
|
53
76
|
javascript({ jsx: true, typescript: true });
|
|
54
77
|
|
|
78
|
+
// Lazy initialization of worker
|
|
79
|
+
let worker: Comlink.Remote<WorkerShape> | null = null;
|
|
80
|
+
const initializeWorker = async () => {
|
|
81
|
+
if (worker === null) {
|
|
82
|
+
const innerWorker = new Worker(
|
|
83
|
+
new URL(
|
|
84
|
+
'./typescriptExtension/worker.ts',
|
|
85
|
+
import.meta.url,
|
|
86
|
+
),
|
|
87
|
+
{
|
|
88
|
+
type: 'module',
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
worker = Comlink.wrap<WorkerShape>(innerWorker);
|
|
92
|
+
await worker.initialize();
|
|
93
|
+
}
|
|
94
|
+
return worker;
|
|
95
|
+
};
|
|
96
|
+
|
|
55
97
|
const htmlConfig = {
|
|
56
98
|
matchClosingTags: true,
|
|
57
99
|
selfClosingTags: false,
|
|
@@ -110,7 +152,7 @@ interface ExtendedEditorCacheValue
|
|
|
110
152
|
|
|
111
153
|
// Gets or creates an `editorCache` entry for the given file id.
|
|
112
154
|
// Looks in `editorCache` first, and if not found, creates a new editor.
|
|
113
|
-
export const getOrCreateEditor = ({
|
|
155
|
+
export const getOrCreateEditor = async ({
|
|
114
156
|
paneId = 'root',
|
|
115
157
|
fileId,
|
|
116
158
|
shareDBDoc,
|
|
@@ -126,6 +168,7 @@ export const getOrCreateEditor = ({
|
|
|
126
168
|
enableAutoFollowRef,
|
|
127
169
|
openTab,
|
|
128
170
|
aiCopilotEndpoint,
|
|
171
|
+
esLintSource,
|
|
129
172
|
rainbowBracketsEnabled = true,
|
|
130
173
|
}: {
|
|
131
174
|
// TODO pass this in from the outside
|
|
@@ -162,8 +205,11 @@ export const getOrCreateEditor = ({
|
|
|
162
205
|
enableAutoFollowRef: React.MutableRefObject<boolean>;
|
|
163
206
|
openTab: (tabState: TabState) => void;
|
|
164
207
|
aiCopilotEndpoint?: string;
|
|
208
|
+
esLintSource: (
|
|
209
|
+
view: EditorView,
|
|
210
|
+
) => Promise<readonly Diagnostic[]>;
|
|
165
211
|
rainbowBracketsEnabled?: boolean; // New parameter type
|
|
166
|
-
}): ExtendedEditorCacheValue => {
|
|
212
|
+
}): Promise<ExtendedEditorCacheValue> => {
|
|
167
213
|
// Cache hit
|
|
168
214
|
|
|
169
215
|
const cacheKey = editorCacheKey(fileId, paneId);
|
|
@@ -194,6 +240,9 @@ export const getOrCreateEditor = ({
|
|
|
194
240
|
// const extensions = [autocompletion(), html(htmlConfig)]
|
|
195
241
|
const extensions = [];
|
|
196
242
|
|
|
243
|
+
// Initialize the fileNameStateField with the actual file name
|
|
244
|
+
extensions.push(fileNameStateField.init(() => name));
|
|
245
|
+
|
|
197
246
|
// This plugin implements multiplayer editing,
|
|
198
247
|
// real-time synchronozation of changes across clients.
|
|
199
248
|
// Does not deal with showing others' cursors.
|
|
@@ -244,6 +293,16 @@ export const getOrCreateEditor = ({
|
|
|
244
293
|
// https://github.com/vizhub-core/vzcode/issues/134
|
|
245
294
|
extensions.push(basicSetup);
|
|
246
295
|
|
|
296
|
+
if (esLintSource) {
|
|
297
|
+
extensions.push(lintGutter()); // Show lint icons in the gutter
|
|
298
|
+
extensions.push(
|
|
299
|
+
linter(esLintSource, {
|
|
300
|
+
// You can configure linter options here, e.g., delay
|
|
301
|
+
delay: 750,
|
|
302
|
+
}),
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
247
306
|
// This supports dynamic changing of the theme.
|
|
248
307
|
extensions.push(
|
|
249
308
|
themeCompartment.of(themeOptionsByLabel[theme].value),
|
|
@@ -332,6 +391,23 @@ export const getOrCreateEditor = ({
|
|
|
332
391
|
}),
|
|
333
392
|
);
|
|
334
393
|
|
|
394
|
+
if (name.endsWith('ts') || name.endsWith('tsx')) {
|
|
395
|
+
// Initialize worker if needed
|
|
396
|
+
const tsWorker = await initializeWorker();
|
|
397
|
+
|
|
398
|
+
extensions.push(
|
|
399
|
+
...[
|
|
400
|
+
tsFacetWorker.of({ worker: tsWorker, path: name }),
|
|
401
|
+
tsSyncWorker(),
|
|
402
|
+
tsLinterWorker(),
|
|
403
|
+
autocompletion({
|
|
404
|
+
override: [tsAutocompleteWorker()],
|
|
405
|
+
}),
|
|
406
|
+
tsHoverWorker(),
|
|
407
|
+
],
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
335
411
|
// Show the minimap
|
|
336
412
|
// See https://github.com/replit/codemirror-minimap#usage
|
|
337
413
|
// This extension has poor performance, so it's disabled for now.
|
|
@@ -2,15 +2,17 @@ import {
|
|
|
2
2
|
useRef,
|
|
3
3
|
useLayoutEffect,
|
|
4
4
|
useCallback,
|
|
5
|
-
useMemo,
|
|
6
5
|
useContext,
|
|
7
6
|
useEffect,
|
|
7
|
+
useState,
|
|
8
8
|
} from 'react';
|
|
9
9
|
import { Username } from '../../types';
|
|
10
10
|
import { EditorCacheValue } from '../useEditorCache';
|
|
11
11
|
import { getOrCreateEditor } from './getOrCreateEditor';
|
|
12
12
|
import { VZCodeContext } from '../VZCodeContext';
|
|
13
13
|
import { InteractRule } from '@replit/codemirror-interact';
|
|
14
|
+
import { EditorView } from 'codemirror'; // Import EditorView
|
|
15
|
+
import { Diagnostic } from '@codemirror/lint'; // Import Diagnostic
|
|
14
16
|
import './style.scss';
|
|
15
17
|
|
|
16
18
|
// The path in the ShareDB document where the files live.
|
|
@@ -19,9 +21,13 @@ const filesPath = ['files'];
|
|
|
19
21
|
export const CodeEditor = ({
|
|
20
22
|
customInteractRules,
|
|
21
23
|
aiCopilotEndpoint,
|
|
24
|
+
esLintSource,
|
|
22
25
|
}: {
|
|
23
26
|
customInteractRules?: Array<InteractRule>;
|
|
24
27
|
aiCopilotEndpoint?: string;
|
|
28
|
+
esLintSource: (
|
|
29
|
+
view: EditorView,
|
|
30
|
+
) => Promise<readonly Diagnostic[]>;
|
|
25
31
|
}) => {
|
|
26
32
|
const {
|
|
27
33
|
activePane,
|
|
@@ -78,11 +84,17 @@ export const CodeEditor = ({
|
|
|
78
84
|
enableAutoFollowRef.current = enableAutoFollow;
|
|
79
85
|
}, [enableAutoFollow]);
|
|
80
86
|
|
|
87
|
+
// State to hold the editor cache value
|
|
88
|
+
const [editorCacheValue, setEditorCacheValue] =
|
|
89
|
+
useState<EditorCacheValue | null>(null);
|
|
90
|
+
|
|
81
91
|
// Get the editor corresponding to the active file.
|
|
82
92
|
// Looks in `editorCache` first, and if not found, creates a new editor.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
let isMounted = true;
|
|
95
|
+
|
|
96
|
+
const initEditor = async () => {
|
|
97
|
+
const editor = await getOrCreateEditor({
|
|
86
98
|
fileId: activePane.activeFileId,
|
|
87
99
|
shareDBDoc,
|
|
88
100
|
content,
|
|
@@ -97,20 +109,32 @@ export const CodeEditor = ({
|
|
|
97
109
|
enableAutoFollowRef,
|
|
98
110
|
openTab,
|
|
99
111
|
aiCopilotEndpoint,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
esLintSource,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (isMounted) {
|
|
116
|
+
setEditorCacheValue(editor);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
initEditor();
|
|
121
|
+
|
|
122
|
+
return () => {
|
|
123
|
+
isMounted = false;
|
|
124
|
+
};
|
|
125
|
+
}, [
|
|
126
|
+
activePane,
|
|
127
|
+
shareDBDoc,
|
|
128
|
+
filesPath,
|
|
129
|
+
localPresence,
|
|
130
|
+
docPresence,
|
|
131
|
+
theme,
|
|
132
|
+
onInteract,
|
|
133
|
+
editorCache,
|
|
134
|
+
usernameRef,
|
|
135
|
+
aiCopilotEndpoint,
|
|
136
|
+
esLintSource,
|
|
137
|
+
]);
|
|
114
138
|
|
|
115
139
|
// Every time the active file switches from one file to another,
|
|
116
140
|
// the editor corresponding to the old file is removed from the DOM,
|
|
@@ -119,6 +143,7 @@ export const CodeEditor = ({
|
|
|
119
143
|
// Guard against cases where page is still loading.
|
|
120
144
|
if (!codeEditorRef.current) return;
|
|
121
145
|
if (!content) return;
|
|
146
|
+
if (!editorCacheValue) return; // Guard against null editorCacheValue
|
|
122
147
|
// if (openSplitPane) return;
|
|
123
148
|
|
|
124
149
|
// Add the editor and apply the prior scroll position to the DOM.
|
|
@@ -132,17 +157,24 @@ export const CodeEditor = ({
|
|
|
132
157
|
return () => {
|
|
133
158
|
// Remove the old editor from the DOM and store the current scroll position.
|
|
134
159
|
// This happens every time `activeFileId` changes.
|
|
135
|
-
|
|
136
|
-
editorCacheValue
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
160
|
+
if (
|
|
161
|
+
editorCacheValue &&
|
|
162
|
+
codeEditorRef.current?.contains(
|
|
163
|
+
editorCacheValue.editor.dom,
|
|
164
|
+
)
|
|
165
|
+
) {
|
|
166
|
+
editorCacheValue.scrollPosition =
|
|
167
|
+
editorCacheValue.editor.scrollDOM.scrollTop;
|
|
168
|
+
codeEditorRef.current.removeChild(
|
|
169
|
+
editorCacheValue.editor.dom,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
140
172
|
};
|
|
141
173
|
}, [shareDBDoc, editorCacheValue]);
|
|
142
174
|
|
|
143
175
|
// Focus the editor
|
|
144
176
|
useEffect(() => {
|
|
145
|
-
if (editorWantsFocus) {
|
|
177
|
+
if (editorWantsFocus && editorCacheValue) {
|
|
146
178
|
editorCacheValue.editor.focus();
|
|
147
179
|
editorNoLongerWantsFocus();
|
|
148
180
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createDefaultMapFromCDN,
|
|
3
|
+
createSystem,
|
|
4
|
+
createVirtualTypeScriptEnvironment,
|
|
5
|
+
} from '@typescript/vfs';
|
|
6
|
+
import ts from 'typescript';
|
|
7
|
+
import * as Comlink from 'comlink';
|
|
8
|
+
|
|
9
|
+
// When importing this module, typescript says that it "cannot find the module or its corresponding type declarations"
|
|
10
|
+
// However, when this file is used as a web worker, no error occurs.
|
|
11
|
+
// As a result, this error is suppressed to avoid future confusion.
|
|
12
|
+
// @ts-expect-error
|
|
13
|
+
import { createWorker } from '@valtown/codemirror-ts/worker';
|
|
14
|
+
|
|
15
|
+
Comlink.expose(
|
|
16
|
+
createWorker(async function () {
|
|
17
|
+
const fsMap = await createDefaultMapFromCDN(
|
|
18
|
+
{ target: ts.ScriptTarget.ES2022 },
|
|
19
|
+
'3.7.3',
|
|
20
|
+
false,
|
|
21
|
+
ts,
|
|
22
|
+
);
|
|
23
|
+
const system = createSystem(fsMap);
|
|
24
|
+
const compilerOpts = {};
|
|
25
|
+
return createVirtualTypeScriptEnvironment(
|
|
26
|
+
system,
|
|
27
|
+
[],
|
|
28
|
+
ts,
|
|
29
|
+
compilerOpts,
|
|
30
|
+
);
|
|
31
|
+
}),
|
|
32
|
+
);
|
package/src/client/VZMiddle.tsx
CHANGED
|
@@ -8,6 +8,10 @@ import { AIAssistWidget } from './AIAssist/AIAssistWidget';
|
|
|
8
8
|
import { VZCodeContext } from './VZCodeContext';
|
|
9
9
|
import { RunCodeWidget } from './RunCodeWidget';
|
|
10
10
|
import { InteractRule } from '@replit/codemirror-interact';
|
|
11
|
+
import { EditorView } from '@codemirror/view';
|
|
12
|
+
import { Diagnostic } from '@codemirror/lint';
|
|
13
|
+
import { VizContent } from '@vizhub/viz-types';
|
|
14
|
+
import { LeafPane } from '../types';
|
|
11
15
|
|
|
12
16
|
// TODO modify this to handle the SplitPane type
|
|
13
17
|
// Recursive structure?
|
|
@@ -23,6 +27,7 @@ const PaneView = ({
|
|
|
23
27
|
aiAssistTooltipText,
|
|
24
28
|
aiAssistClickOverride,
|
|
25
29
|
aiCopilotEndpoint,
|
|
30
|
+
esLintSource,
|
|
26
31
|
}) => {
|
|
27
32
|
// This prevents the CodeEditor from rendering
|
|
28
33
|
// during SSR.
|
|
@@ -49,6 +54,7 @@ const PaneView = ({
|
|
|
49
54
|
<CodeEditor
|
|
50
55
|
customInteractRules={customInteractRules}
|
|
51
56
|
aiCopilotEndpoint={aiCopilotEndpoint}
|
|
57
|
+
esLintSource={esLintSource}
|
|
52
58
|
/>
|
|
53
59
|
)}
|
|
54
60
|
{isClient &&
|
|
@@ -84,6 +90,7 @@ export const VZMiddle = ({
|
|
|
84
90
|
aiAssistClickOverride,
|
|
85
91
|
aiCopilotEndpoint,
|
|
86
92
|
customInteractRules,
|
|
93
|
+
esLintSource,
|
|
87
94
|
}: {
|
|
88
95
|
enableAIAssist?: boolean;
|
|
89
96
|
aiAssistEndpoint?: string;
|
|
@@ -92,6 +99,9 @@ export const VZMiddle = ({
|
|
|
92
99
|
aiAssistClickOverride?: () => void;
|
|
93
100
|
aiCopilotEndpoint?: string;
|
|
94
101
|
customInteractRules?: Array<InteractRule>;
|
|
102
|
+
esLintSource: (
|
|
103
|
+
view: EditorView,
|
|
104
|
+
) => Promise<readonly Diagnostic[]>;
|
|
95
105
|
}) => {
|
|
96
106
|
const { codeEditorWidth } = useContext(
|
|
97
107
|
SplitPaneResizeContext,
|
|
@@ -128,6 +138,7 @@ export const VZMiddle = ({
|
|
|
128
138
|
aiAssistTooltipText={aiAssistTooltipText}
|
|
129
139
|
aiAssistClickOverride={aiAssistClickOverride}
|
|
130
140
|
aiCopilotEndpoint={aiCopilotEndpoint}
|
|
141
|
+
esLintSource={esLintSource}
|
|
131
142
|
/>
|
|
132
143
|
<CodeErrorOverlay
|
|
133
144
|
errorMessage={errorMessage}
|
|
@@ -64,6 +64,7 @@ export const Search = () => {
|
|
|
64
64
|
setSearchFocusedIndex,
|
|
65
65
|
shareDBDoc,
|
|
66
66
|
editorCache,
|
|
67
|
+
openTab,
|
|
67
68
|
} = useContext(VZCodeContext);
|
|
68
69
|
const {
|
|
69
70
|
pattern,
|
|
@@ -227,6 +228,9 @@ export const Search = () => {
|
|
|
227
228
|
case 'Enter':
|
|
228
229
|
case ' ':
|
|
229
230
|
const fileId: VizFileId = files[focusedIndex][0];
|
|
231
|
+
|
|
232
|
+
openTab({ fileId, isTransient: true });
|
|
233
|
+
|
|
230
234
|
setActiveFileId(fileId);
|
|
231
235
|
|
|
232
236
|
if (focusedChildIndex !== null) {
|
|
@@ -441,6 +445,13 @@ export const Search = () => {
|
|
|
441
445
|
childIndex,
|
|
442
446
|
);
|
|
443
447
|
|
|
448
|
+
openTab({
|
|
449
|
+
fileId,
|
|
450
|
+
isTransient: true,
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
setActiveFileId(fileId);
|
|
454
|
+
|
|
444
455
|
const cacheKey =
|
|
445
456
|
editorCacheKey(
|
|
446
457
|
fileId,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { EditorView } from '@codemirror/view';
|
|
3
|
+
import { Diagnostic } from '@codemirror/lint';
|
|
4
|
+
// @ts-ignore - Worker import
|
|
5
|
+
import ESLintWorker from './worker?worker';
|
|
6
|
+
import { enableESLint } from '../../server/featureFlags';
|
|
7
|
+
import { fileNameStateField } from '../CodeEditor/getOrCreateEditor';
|
|
8
|
+
|
|
9
|
+
let requestCounter = 0;
|
|
10
|
+
const pendingRequests = new Map();
|
|
11
|
+
|
|
12
|
+
export const useESLint = () => {
|
|
13
|
+
const [worker, setWorker] = useState(null);
|
|
14
|
+
|
|
15
|
+
// Initialize the work in an effect so it does not run during SSR.
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
setWorker(new ESLintWorker());
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
// Wait for the worker to be initialized
|
|
22
|
+
if (!worker) return;
|
|
23
|
+
|
|
24
|
+
const handleMessage = (event: MessageEvent) => {
|
|
25
|
+
const { diagnostics, requestId, error } = event.data;
|
|
26
|
+
|
|
27
|
+
if (error) {
|
|
28
|
+
console.error('[ESLint] Error:', error);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const resolve = pendingRequests.get(requestId);
|
|
32
|
+
if (resolve) {
|
|
33
|
+
resolve(diagnostics);
|
|
34
|
+
pendingRequests.delete(requestId);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
worker.addEventListener('message', handleMessage);
|
|
39
|
+
|
|
40
|
+
return () => {
|
|
41
|
+
worker.removeEventListener('message', handleMessage);
|
|
42
|
+
worker.terminate();
|
|
43
|
+
};
|
|
44
|
+
}, [worker]);
|
|
45
|
+
|
|
46
|
+
const esLintSource = useCallback(
|
|
47
|
+
async (
|
|
48
|
+
view: EditorView,
|
|
49
|
+
): Promise<readonly Diagnostic[]> => {
|
|
50
|
+
if (!enableESLint) return [];
|
|
51
|
+
|
|
52
|
+
// Retrieve the file name from the editor's state
|
|
53
|
+
const fileName = view.state.field(fileNameStateField);
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const code = view.state.doc.toString();
|
|
57
|
+
const requestId = requestCounter++;
|
|
58
|
+
|
|
59
|
+
return new Promise<Diagnostic[]>((resolve) => {
|
|
60
|
+
pendingRequests.set(requestId, resolve);
|
|
61
|
+
worker.postMessage({ code, requestId, fileName });
|
|
62
|
+
});
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.error('[ESLint] Error in linter:', e);
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
[worker],
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return { esLintSource };
|
|
72
|
+
};
|