vzcode 1.31.0 → 1.33.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.
Files changed (34) hide show
  1. package/README.md +0 -1
  2. package/dist/assets/index-DPIrco_g.js +235 -0
  3. package/dist/assets/index-DnunmWjy.css +1 -0
  4. package/dist/assets/worker-BrwN8AXx.js +330 -0
  5. package/dist/assets/worker-CkHcCP1n.js +136 -0
  6. package/dist/assets/worker-DkYJN5WQ.js +453 -0
  7. package/dist/index.html +2 -2
  8. package/package.json +48 -41
  9. package/src/client/App/index.tsx +3 -0
  10. package/src/client/CodeEditor/getOrCreateEditor.ts +98 -9
  11. package/src/client/CodeEditor/index.tsx +56 -24
  12. package/src/client/CodeEditor/typescriptExtension/worker.ts +32 -0
  13. package/src/client/Icons/MicSVG.tsx +1 -1
  14. package/src/client/VZCodeContext.tsx +18 -2
  15. package/src/client/VZMiddle.tsx +11 -0
  16. package/src/client/VZSidebar/AIChat.tsx +142 -0
  17. package/src/client/VZSidebar/Search.tsx +11 -0
  18. package/src/client/VZSidebar/index.tsx +53 -8
  19. package/src/client/VZSidebar/styles.scss +178 -1
  20. package/src/client/featureFlags.ts +2 -0
  21. package/src/client/useActions.ts +20 -0
  22. package/src/client/useESLint/index.ts +72 -0
  23. package/src/client/useESLint/worker.ts +113 -0
  24. package/src/client/useEditorCache.ts +1 -0
  25. package/src/client/useFileCRUD.ts +47 -0
  26. package/src/client/useOpenDirectories.ts +43 -12
  27. package/src/client/utils/fileExtension.ts +10 -0
  28. package/src/client/vzReducer/aiChatReducer.ts +31 -0
  29. package/src/client/vzReducer/createInitialState.ts +2 -0
  30. package/src/client/vzReducer/index.ts +20 -0
  31. package/src/server/featureFlags.js +2 -0
  32. package/dist/assets/index-D01yzCWj.js +0 -234
  33. package/dist/assets/index-DTtcN2MP.css +0 -1
  34. package/dist/assets/worker-CaP6zYz7.js +0 -335
package/dist/index.html CHANGED
@@ -20,8 +20,8 @@
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-D01yzCWj.js"></script>
24
- <link rel="stylesheet" crossorigin href="/assets/index-DTtcN2MP.css">
23
+ <script type="module" crossorigin src="/assets/index-DPIrco_g.js"></script>
24
+ <link rel="stylesheet" crossorigin href="/assets/index-DnunmWjy.css">
25
25
  </head>
26
26
  <body>
27
27
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "1.31.0",
3
+ "version": "1.33.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.3",
111
- "@codemirror/lang-json": "^6.0.1",
112
- "@codemirror/lang-markdown": "^6.3.2",
110
+ "@codemirror/lang-javascript": "^6.2.4",
111
+ "@codemirror/lang-json": "^6.0.2",
112
+ "@codemirror/lang-markdown": "^6.3.3",
113
+ "@codemirror/lint": "^6.8.5",
113
114
  "@codemirror/state": "^6.5.2",
114
- "@codemirror/theme-one-dark": "^6.1.2",
115
- "@codemirror/view": "^6.36.5",
116
- "@langchain/core": "^0.3.43",
117
- "@langchain/openai": "^0.5.4",
115
+ "@codemirror/theme-one-dark": "^6.1.3",
116
+ "@codemirror/view": "^6.38.1",
117
+ "@langchain/core": "^0.3.65",
118
+ "@langchain/openai": "^0.6.2",
118
119
  "@lezer/highlight": "^1.2.1",
119
- "@livekit/components-react": "^2.9.0",
120
+ "@livekit/components-react": "^2.9.14",
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
- "@uiw/codemirror-theme-abcdef": "^4.23.10",
126
- "@uiw/codemirror-theme-dracula": "^4.23.10",
127
- "@uiw/codemirror-theme-eclipse": "^4.23.10",
128
- "@uiw/codemirror-theme-github": "^4.23.10",
129
- "@uiw/codemirror-theme-material": "^4.23.10",
130
- "@uiw/codemirror-theme-nord": "^4.23.10",
131
- "@uiw/codemirror-theme-okaidia": "^4.23.10",
132
- "@uiw/codemirror-theme-xcode": "^4.23.10",
133
- "@uiw/codemirror-themes": "^4.23.10",
134
- "@vizhub/viz-types": "^0.1.0",
126
+ "@typescript/vfs": "^1.6.1",
127
+ "@uiw/codemirror-theme-abcdef": "^4.24.1",
128
+ "@uiw/codemirror-theme-dracula": "^4.24.1",
129
+ "@uiw/codemirror-theme-eclipse": "^4.24.1",
130
+ "@uiw/codemirror-theme-github": "^4.24.1",
131
+ "@uiw/codemirror-theme-material": "^4.24.1",
132
+ "@uiw/codemirror-theme-nord": "^4.24.1",
133
+ "@uiw/codemirror-theme-okaidia": "^4.24.1",
134
+ "@uiw/codemirror-theme-xcode": "^4.24.1",
135
+ "@uiw/codemirror-themes": "^4.24.1",
136
+ "@valtown/codemirror-ts": "^2.3.1",
137
+ "@vizhub/viz-types": "^0.2.0",
135
138
  "body-parser": "^2.2.0",
136
- "codemirror": "^6.0.1",
139
+ "codemirror": "^6.0.2",
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.4.7",
146
+ "dotenv": "^17.2.0",
147
+ "eslint": "^9.31.0",
148
+ "eslint-linter-browserify": "^9.31.0",
143
149
  "express": "^5.1.0",
144
- "ignore": "^7.0.3",
150
+ "globals": "^16.3.0",
151
+ "ignore": "^7.0.5",
145
152
  "json0-ot-diff": "^1.1.2",
146
- "livekit-server-sdk": "^2.11.0",
153
+ "livekit-server-sdk": "^2.13.1",
147
154
  "ngrok": "^5.0.0-beta.2",
148
- "npm": "^11.2.0",
149
- "open": "^10.1.0",
150
- "prettier-plugin-svelte": "^3.3.3",
155
+ "npm": "^11.4.2",
156
+ "open": "^10.2.0",
157
+ "prettier-plugin-svelte": "^3.4.0",
151
158
  "react": "^18",
152
- "react-bootstrap": "^2.10.9",
159
+ "react-bootstrap": "^2.10.10",
153
160
  "react-dom": "^18",
154
- "react-router-dom": "^7.5.0",
155
- "sharedb": "^5.2.1",
156
- "sharedb-client-browser": "^5.1.1",
161
+ "react-router-dom": "^7.7.0",
162
+ "sharedb": "^5.2.2",
163
+ "sharedb-client-browser": "^5.2.2",
157
164
  "uuid": "^11.1.0",
158
- "vizhub-ui": "^3.23.0",
159
- "ws": "^8.18.1"
165
+ "vizhub-ui": "^3.24.0",
166
+ "ws": "^8.18.3"
160
167
  },
161
168
  "devDependencies": {
162
169
  "@types/react": "^18",
163
170
  "@types/react-dom": "^18",
164
- "@vitejs/plugin-react": "^4.3.4",
165
- "concurrently": "^9.1.2",
171
+ "@vitejs/plugin-react": "^4.7.0",
172
+ "concurrently": "^9.2.0",
166
173
  "cross-env": "^7.0.3",
167
- "npm-check-updates": "^17.1.16",
168
- "prettier": "^3.5.3",
169
- "sass": "^1.86.3",
174
+ "npm-check-updates": "^18.0.1",
175
+ "prettier": "^3.6.2",
176
+ "sass": "^1.89.2",
170
177
  "typescript": "^5.8.3",
171
- "vite": "^6.2.5",
172
- "vitest": "^3.1.1"
178
+ "vite": "^7.0.5",
179
+ "vitest": "^3.2.4"
173
180
  },
174
181
  "optionalDependencies": {
175
- "@rollup/rollup-darwin-arm64": "^4.39.0",
176
- "@rollup/rollup-win32-x64-msvc": "^4.39.0"
182
+ "@rollup/rollup-darwin-arm64": "^4.45.1",
183
+ "@rollup/rollup-win32-x64-msvc": "^4.45.1"
177
184
  }
178
185
  }
@@ -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 { Diagnostic, linter } from '@codemirror/lint';
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,55 @@ 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 '../CodeEditor/rainbowBrackets';
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';
62
+ import { getFileExtension } from '../utils/fileExtension';
48
63
 
49
64
  const DEBUG = false;
50
65
 
66
+ // Define a StateField to store the file name.
67
+ // This should be defined at the module level if it's to be imported by other modules.
68
+ export const fileNameStateField = StateField.define<string>(
69
+ {
70
+ create: () => '', // Default initial value
71
+ update: (value, tr) => value, // Typically set once at creation for a given editor instance
72
+ },
73
+ );
74
+
51
75
  // Enables TypeScript +JSX support in CodeMirror.
52
76
  const tsx = () =>
53
77
  javascript({ jsx: true, typescript: true });
54
78
 
79
+ // Lazy initialization of worker
80
+ let worker: Comlink.Remote<WorkerShape> | null = null;
81
+ const initializeWorker = async () => {
82
+ if (worker === null) {
83
+ const innerWorker = new Worker(
84
+ new URL(
85
+ './typescriptExtension/worker.ts',
86
+ import.meta.url,
87
+ ),
88
+ {
89
+ type: 'module',
90
+ },
91
+ );
92
+ worker = Comlink.wrap<WorkerShape>(innerWorker);
93
+ await worker.initialize();
94
+ }
95
+ return worker;
96
+ };
97
+
55
98
  const htmlConfig = {
56
99
  matchClosingTags: true,
57
100
  selfClosingTags: false,
@@ -89,6 +132,14 @@ const languageExtensions = {
89
132
  svelte,
90
133
  };
91
134
 
135
+ // Exported utility function to get language extension for a file extension
136
+ export const getLanguageExtension = (
137
+ fileExtension: string,
138
+ ) => {
139
+ const languageFunc = languageExtensions[fileExtension];
140
+ return languageFunc ? languageFunc() : undefined;
141
+ };
142
+
92
143
  // Gets a value at a path in an object.
93
144
  // e.g. getAtPath({a: {b: 1}}, ['a', 'b']) === 1
94
145
  const getAtPath = (obj, path) => {
@@ -104,13 +155,14 @@ const getAtPath = (obj, path) => {
104
155
  interface ExtendedEditorCacheValue
105
156
  extends EditorCacheValue {
106
157
  themeCompartment: Compartment;
158
+ languageCompartment: Compartment;
107
159
  rainbowBracketsCompartment: Compartment;
108
160
  updateRainbowBrackets: (enabled: boolean) => void;
109
161
  }
110
162
 
111
163
  // Gets or creates an `editorCache` entry for the given file id.
112
164
  // Looks in `editorCache` first, and if not found, creates a new editor.
113
- export const getOrCreateEditor = ({
165
+ export const getOrCreateEditor = async ({
114
166
  paneId = 'root',
115
167
  fileId,
116
168
  shareDBDoc,
@@ -126,6 +178,7 @@ export const getOrCreateEditor = ({
126
178
  enableAutoFollowRef,
127
179
  openTab,
128
180
  aiCopilotEndpoint,
181
+ esLintSource,
129
182
  rainbowBracketsEnabled = true,
130
183
  }: {
131
184
  // TODO pass this in from the outside
@@ -162,8 +215,11 @@ export const getOrCreateEditor = ({
162
215
  enableAutoFollowRef: React.MutableRefObject<boolean>;
163
216
  openTab: (tabState: TabState) => void;
164
217
  aiCopilotEndpoint?: string;
218
+ esLintSource: (
219
+ view: EditorView,
220
+ ) => Promise<readonly Diagnostic[]>;
165
221
  rainbowBracketsEnabled?: boolean; // New parameter type
166
- }): ExtendedEditorCacheValue => {
222
+ }): Promise<ExtendedEditorCacheValue> => {
167
223
  // Cache hit
168
224
 
169
225
  const cacheKey = editorCacheKey(fileId, paneId);
@@ -181,7 +237,8 @@ export const getOrCreateEditor = ({
181
237
  const namePath = [...filesPath, fileId, 'name'];
182
238
  const text = getAtPath(content, textPath);
183
239
  const name = getAtPath(content, namePath);
184
- const fileExtension = name.split('.').pop();
240
+
241
+ const fileExtension = getFileExtension(name);
185
242
 
186
243
  // Create a compartment for the theme so that it can be changed dynamically.
187
244
  // Inspired by: https://github.com/craftzdog/cm6-themes/blob/main/example/index.ts
@@ -194,6 +251,9 @@ export const getOrCreateEditor = ({
194
251
  // const extensions = [autocompletion(), html(htmlConfig)]
195
252
  const extensions = [];
196
253
 
254
+ // Initialize the fileNameStateField with the actual file name
255
+ extensions.push(fileNameStateField.init(() => name));
256
+
197
257
  // This plugin implements multiplayer editing,
198
258
  // real-time synchronozation of changes across clients.
199
259
  // Does not deal with showing others' cursors.
@@ -244,6 +304,16 @@ export const getOrCreateEditor = ({
244
304
  // https://github.com/vizhub-core/vzcode/issues/134
245
305
  extensions.push(basicSetup);
246
306
 
307
+ if (esLintSource) {
308
+ extensions.push(lintGutter()); // Show lint icons in the gutter
309
+ extensions.push(
310
+ linter(esLintSource, {
311
+ // You can configure linter options here, e.g., delay
312
+ delay: 750,
313
+ }),
314
+ );
315
+ }
316
+
247
317
  // This supports dynamic changing of the theme.
248
318
  extensions.push(
249
319
  themeCompartment.of(themeOptionsByLabel[theme].value),
@@ -262,10 +332,6 @@ export const getOrCreateEditor = ({
262
332
  // using a Compartment.
263
333
  const languageCompartment = new Compartment();
264
334
  // See https://github.com/vizhub-core/vzcode/issues/55
265
- const getLanguageExtension = (fileExtension: string) => {
266
- const languageFunc = languageExtensions[fileExtension];
267
- return languageFunc ? languageFunc() : undefined;
268
- };
269
335
 
270
336
  const languageExtension =
271
337
  getLanguageExtension(fileExtension);
@@ -287,6 +353,11 @@ export const getOrCreateEditor = ({
287
353
  extensions.push(languageCompartment.of([]));
288
354
  }
289
355
 
356
+ // Enable line wrapping for Markdown files
357
+ if (fileExtension === 'md') {
358
+ extensions.push(EditorView.lineWrapping);
359
+ }
360
+
290
361
  // Add interactive widgets.
291
362
  // Includes the Alt+drag functionality for numbers.
292
363
  // Calls `onInteract` when one of those widgets is interacted with.
@@ -332,6 +403,23 @@ export const getOrCreateEditor = ({
332
403
  }),
333
404
  );
334
405
 
406
+ if (name.endsWith('ts') || name.endsWith('tsx')) {
407
+ // Initialize worker if needed
408
+ const tsWorker = await initializeWorker();
409
+
410
+ extensions.push(
411
+ ...[
412
+ tsFacetWorker.of({ worker: tsWorker, path: name }),
413
+ tsSyncWorker(),
414
+ tsLinterWorker(),
415
+ autocompletion({
416
+ override: [tsAutocompleteWorker()],
417
+ }),
418
+ tsHoverWorker(),
419
+ ],
420
+ );
421
+ }
422
+
335
423
  // Show the minimap
336
424
  // See https://github.com/replit/codemirror-minimap#usage
337
425
  // This extension has poor performance, so it's disabled for now.
@@ -382,6 +470,7 @@ export const getOrCreateEditor = ({
382
470
  const editorCacheValue: ExtendedEditorCacheValue = {
383
471
  editor,
384
472
  themeCompartment,
473
+ languageCompartment,
385
474
  rainbowBracketsCompartment,
386
475
  updateRainbowBrackets: (enabled) => {
387
476
  editor.dispatch({
@@ -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
- const editorCacheValue: EditorCacheValue = useMemo(
84
- () =>
85
- getOrCreateEditor({
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
- activePane,
103
- shareDBDoc,
104
- filesPath,
105
- localPresence,
106
- docPresence,
107
- theme,
108
- onInteract,
109
- editorCache,
110
- usernameRef,
111
- aiCopilotEndpoint,
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
- editorCacheValue.scrollPosition =
136
- editorCacheValue.editor.scrollDOM.scrollTop;
137
- codeEditorRef.current.removeChild(
138
- editorCacheValue.editor.dom,
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
+ );
@@ -7,7 +7,7 @@ export const MicSVG = () => (
7
7
  version="1.1"
8
8
  xmlns="http://www.w3.org/2000/svg"
9
9
  viewBox="0 0 512 512"
10
- enable-background="new 0 0 512 512"
10
+ enableBackground="new 0 0 512 512"
11
11
  >
12
12
  <g>
13
13
  <g>
@@ -113,6 +113,11 @@ export type VZCodeContextValue = {
113
113
  isSearchOpen: boolean;
114
114
  setIsSearchOpen: (isSearchOpen: boolean) => void;
115
115
  setSearch: (pattern: string) => void;
116
+
117
+ isAIChatOpen: boolean;
118
+ setIsAIChatOpen: (isAIChatOpen: boolean) => void;
119
+ aiChatFocused: boolean;
120
+ toggleAIChatFocused: () => void;
116
121
  setSearchResults: (files: ShareDBDoc<VizContent>) => void;
117
122
  setSearchFileVisibility: (
118
123
  files: ShareDBDoc<VizContent>,
@@ -252,6 +257,8 @@ export const VZCodeProvider = ({
252
257
  theme,
253
258
  search,
254
259
  isSearchOpen,
260
+ isAIChatOpen,
261
+ aiChatFocused,
255
262
  isSettingsOpen,
256
263
  isDocOpen,
257
264
  editorWantsFocus,
@@ -260,7 +267,7 @@ export const VZCodeProvider = ({
260
267
  sidebarPresenceIndicators,
261
268
  } = state;
262
269
 
263
- const activePane = findPane(pane, activePaneId);
270
+ const activePane: Pane = findPane(pane, activePaneId);
264
271
  if (activePane.type !== 'leafPane') {
265
272
  // Should never happen
266
273
  throw new Error('Expected leafPane');
@@ -281,6 +288,8 @@ export const VZCodeProvider = ({
281
288
  setSearchLineVisibility,
282
289
  setSearchFocusedIndex,
283
290
  toggleSearchFocused,
291
+ setIsAIChatOpen,
292
+ toggleAIChatFocused,
284
293
  setIsSettingsOpen,
285
294
  setIsDocOpen,
286
295
  closeSettings,
@@ -304,7 +313,7 @@ export const VZCodeProvider = ({
304
313
  // The set of open directories.
305
314
  // TODO move this into reducer/useActions
306
315
  const { isDirectoryOpen, toggleDirectory } =
307
- useOpenDirectories();
316
+ useOpenDirectories({ activePane, content });
308
317
 
309
318
  // Cache of CodeMirror editors by file id.
310
319
  const editorCache: EditorCache = useEditorCache();
@@ -324,6 +333,8 @@ export const VZCodeProvider = ({
324
333
  submitOperation,
325
334
  closeTabs,
326
335
  openTab,
336
+ editorCache,
337
+ content,
327
338
  });
328
339
 
329
340
  // State to control the create file modal's visibility
@@ -435,6 +446,11 @@ export const VZCodeProvider = ({
435
446
  setSearchFocusedIndex,
436
447
  toggleSearchFocused,
437
448
 
449
+ isAIChatOpen,
450
+ setIsAIChatOpen,
451
+ aiChatFocused,
452
+ toggleAIChatFocused,
453
+
438
454
  isSettingsOpen,
439
455
  setIsSettingsOpen,
440
456
  closeSettings,
@@ -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}