vzcode 2.3.0 → 2.4.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 (196) hide show
  1. package/package.json +3 -2
  2. package/src/cli.ts +208 -0
  3. package/src/client/AIAssist/AIAssistCodeMirrorKeyMap.ts +42 -0
  4. package/src/client/AIAssist/AIAssistWidget/index.tsx +135 -0
  5. package/src/client/AIAssist/AIAssistWidget/style.scss +47 -0
  6. package/src/client/AIAssist/Spinner.jsx +76 -0
  7. package/src/client/AIAssist/formatFile.ts +27 -0
  8. package/src/client/AIAssist/generateFilesContext.ts +25 -0
  9. package/src/client/AIAssist/index.ts +2 -0
  10. package/src/client/AIAssist/startAIAssist.ts +95 -0
  11. package/src/client/App/index.tsx +152 -0
  12. package/src/client/App/style.scss +82 -0
  13. package/src/client/App/usePending.ts +51 -0
  14. package/src/client/App/useShareDB.ts +161 -0
  15. package/src/client/CodeEditor/Copilot/index.ts +74 -0
  16. package/src/client/CodeEditor/InteractiveWidgets/boolToggler.ts +12 -0
  17. package/src/client/CodeEditor/InteractiveWidgets/colorPicker.ts +68 -0
  18. package/src/client/CodeEditor/InteractiveWidgets/colorsInTextPlugin.ts +174 -0
  19. package/src/client/CodeEditor/InteractiveWidgets/highlightWidgets.ts +79 -0
  20. package/src/client/CodeEditor/InteractiveWidgets/index.ts +44 -0
  21. package/src/client/CodeEditor/InteractiveWidgets/markdownCheckboxToggler.ts +12 -0
  22. package/src/client/CodeEditor/InteractiveWidgets/numberDragger.ts +20 -0
  23. package/src/client/CodeEditor/InteractiveWidgets/rotateWidget.ts +143 -0
  24. package/src/client/CodeEditor/InteractiveWidgets/urlClicker.test.ts +48 -0
  25. package/src/client/CodeEditor/InteractiveWidgets/urlClicker.ts +12 -0
  26. package/src/client/CodeEditor/InteractiveWidgets/vec2Slider.ts +25 -0
  27. package/src/client/CodeEditor/basicSetup.ts +73 -0
  28. package/src/client/CodeEditor/getOrCreateEditor.tsx +615 -0
  29. package/src/client/CodeEditor/index.tsx +202 -0
  30. package/src/client/CodeEditor/json1PresenceBroadcast.ts +41 -0
  31. package/src/client/CodeEditor/json1PresenceDisplay.ts +350 -0
  32. package/src/client/CodeEditor/rainbowBrackets.ts +122 -0
  33. package/src/client/CodeEditor/style.scss +156 -0
  34. package/src/client/CodeEditor/typescriptExtension/worker.ts +32 -0
  35. package/src/client/CodeErrorOverlay/index.tsx +77 -0
  36. package/src/client/CodeErrorOverlay/style.scss +33 -0
  37. package/src/client/Fonts/fonts.ts +8 -0
  38. package/src/client/Icons/AdjustmentSVG.tsx +19 -0
  39. package/src/client/Icons/BugSVG.tsx +20 -0
  40. package/src/client/Icons/CloseSVG.tsx +19 -0
  41. package/src/client/Icons/CssSVG.tsx +16 -0
  42. package/src/client/Icons/DirectoryArrowSVG.tsx +19 -0
  43. package/src/client/Icons/EditSVG.tsx +19 -0
  44. package/src/client/Icons/FileSVG.tsx +19 -0
  45. package/src/client/Icons/FolderSVG.tsx +20 -0
  46. package/src/client/Icons/GearSVG.tsx +16 -0
  47. package/src/client/Icons/HtmlSVG.tsx +16 -0
  48. package/src/client/Icons/JavaScriptSVG.tsx +16 -0
  49. package/src/client/Icons/JsonSVG.tsx +16 -0
  50. package/src/client/Icons/MarkdownSVG.tsx +16 -0
  51. package/src/client/Icons/MicOffSVG.tsx +35 -0
  52. package/src/client/Icons/MicSVG.tsx +25 -0
  53. package/src/client/Icons/NewSVG.tsx +19 -0
  54. package/src/client/Icons/PinSVG.tsx +14 -0
  55. package/src/client/Icons/PlaySVG.tsx +16 -0
  56. package/src/client/Icons/QuestionMarkSVG.tsx +18 -0
  57. package/src/client/Icons/ReactSVG.tsx +16 -0
  58. package/src/client/Icons/SearchSVG.tsx +20 -0
  59. package/src/client/Icons/SparklesSVG.tsx +23 -0
  60. package/src/client/Icons/SplitEditorSVG.tsx +34 -0
  61. package/src/client/Icons/StopSVG.tsx +15 -0
  62. package/src/client/Icons/SvelteSVG.tsx +16 -0
  63. package/src/client/Icons/TrashSVG.tsx +19 -0
  64. package/src/client/Icons/TypeScriptSVG.tsx +16 -0
  65. package/src/client/Icons/index.tsx +26 -0
  66. package/src/client/ImageViewer/index.tsx +91 -0
  67. package/src/client/ImageViewer/style.scss +41 -0
  68. package/src/client/PresenceNotifications/PresenceNotifications.test.ts +134 -0
  69. package/src/client/PresenceNotifications/index.tsx +158 -0
  70. package/src/client/PresenceNotifications/style.scss +17 -0
  71. package/src/client/RunCodeWidget/index.tsx +117 -0
  72. package/src/client/RunCodeWidget/style.scss +24 -0
  73. package/src/client/SplitPaneResizeContext.tsx +159 -0
  74. package/src/client/TabList/Tab.tsx +73 -0
  75. package/src/client/TabList/index.tsx +35 -0
  76. package/src/client/TabList/style.scss +51 -0
  77. package/src/client/VZCodeContext/index.tsx +29 -0
  78. package/src/client/VZCodeContext/types.ts +248 -0
  79. package/src/client/VZCodeContext/useVZCodeState.ts +602 -0
  80. package/src/client/VZKeyboardShortcutsDoc.tsx +148 -0
  81. package/src/client/VZLeft.tsx +28 -0
  82. package/src/client/VZMiddle.tsx +172 -0
  83. package/src/client/VZResizer/index.tsx +128 -0
  84. package/src/client/VZResizer/styles.scss +33 -0
  85. package/src/client/VZRight.tsx +113 -0
  86. package/src/client/VZSettings.tsx +275 -0
  87. package/src/client/VZSidebar/AIChat/ChatInput.tsx +243 -0
  88. package/src/client/VZSidebar/AIChat/DiffView.scss +136 -0
  89. package/src/client/VZSidebar/AIChat/DiffView.tsx +176 -0
  90. package/src/client/VZSidebar/AIChat/Message.tsx +79 -0
  91. package/src/client/VZSidebar/AIChat/MessageList.tsx +190 -0
  92. package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +35 -0
  93. package/src/client/VZSidebar/AIChat/ThinkingScratchpad.tsx +156 -0
  94. package/src/client/VZSidebar/AIChat/TypingIndicator.tsx +19 -0
  95. package/src/client/VZSidebar/AIChat/index.tsx +357 -0
  96. package/src/client/VZSidebar/AIChat/styles.scss +646 -0
  97. package/src/client/VZSidebar/AIChat/useSpeechRecognition.ts +117 -0
  98. package/src/client/VZSidebar/CreateDirModal.tsx +111 -0
  99. package/src/client/VZSidebar/CreateFileModal.tsx +111 -0
  100. package/src/client/VZSidebar/DeleteConfirmationModal.tsx +76 -0
  101. package/src/client/VZSidebar/DirectoryListing.tsx +89 -0
  102. package/src/client/VZSidebar/EmptyState.tsx +11 -0
  103. package/src/client/VZSidebar/FileListing.tsx +91 -0
  104. package/src/client/VZSidebar/FileTypeIcon.tsx +49 -0
  105. package/src/client/VZSidebar/Item.tsx +237 -0
  106. package/src/client/VZSidebar/Listing.tsx +47 -0
  107. package/src/client/VZSidebar/Search.tsx +527 -0
  108. package/src/client/VZSidebar/VisualEditor.tsx +282 -0
  109. package/src/client/VZSidebar/VoiceChatModal.tsx +121 -0
  110. package/src/client/VZSidebar/aiCopyPaste.ts +199 -0
  111. package/src/client/VZSidebar/index.tsx +728 -0
  112. package/src/client/VZSidebar/styles.scss +612 -0
  113. package/src/client/VZSidebar/useDragAndDrop.tsx +408 -0
  114. package/src/client/bootstrap.ts +26 -0
  115. package/src/client/buildWorker.ts +3 -0
  116. package/src/client/featureFlags.ts +8 -0
  117. package/src/client/generateRequestId.ts +6 -0
  118. package/src/client/getFileTree.ts +53 -0
  119. package/src/client/index.ts +19 -0
  120. package/src/client/main.jsx +10 -0
  121. package/src/client/presenceColor.ts +8 -0
  122. package/src/client/shouldTriggerRun.ts +31 -0
  123. package/src/client/sortFileTree.ts +21 -0
  124. package/src/client/tabsSearchParameters.test.ts +79 -0
  125. package/src/client/tabsSearchParameters.ts +137 -0
  126. package/src/client/themes/index.ts +7 -0
  127. package/src/client/themes/themes.ts +77 -0
  128. package/src/client/themes/uiwImports.ts +32 -0
  129. package/src/client/themes/useDynamicTheme.ts +29 -0
  130. package/src/client/themes/vizhubTheme/colors.ts +80 -0
  131. package/src/client/themes/vizhubTheme/index.ts +193 -0
  132. package/src/client/useActions.ts +288 -0
  133. package/src/client/useESLint/index.ts +72 -0
  134. package/src/client/useESLint/worker.ts +113 -0
  135. package/src/client/useEditorCache.ts +35 -0
  136. package/src/client/useFileCRUD.ts +228 -0
  137. package/src/client/useKeyboardShortcuts.ts +451 -0
  138. package/src/client/useOpenDirectories.ts +99 -0
  139. package/src/client/usePresenceAutoFollow.ts +85 -0
  140. package/src/client/usePrettier/index.ts +178 -0
  141. package/src/client/usePrettier/worker.ts +109 -0
  142. package/src/client/useRunCode.tsx +15 -0
  143. package/src/client/useRuntimeError.ts +32 -0
  144. package/src/client/useSubmitOperation.ts +15 -0
  145. package/src/client/useURLSync.ts +137 -0
  146. package/src/client/usernameLocalStorage.ts +43 -0
  147. package/src/client/utils/fileExtension.ts +10 -0
  148. package/src/client/utils/isImageFile.ts +12 -0
  149. package/src/client/vzReducer/aiChatReducer.ts +44 -0
  150. package/src/client/vzReducer/closeTabReducer.test.ts +205 -0
  151. package/src/client/vzReducer/closeTabsReducer.ts +96 -0
  152. package/src/client/vzReducer/createInitialState.ts +38 -0
  153. package/src/client/vzReducer/editorNoLongerWantsFocusReducer.ts +9 -0
  154. package/src/client/vzReducer/findPane.ts +19 -0
  155. package/src/client/vzReducer/index.ts +250 -0
  156. package/src/client/vzReducer/openTabReducer.test.ts +127 -0
  157. package/src/client/vzReducer/openTabReducer.ts +74 -0
  158. package/src/client/vzReducer/searchReducer.test.ts +292 -0
  159. package/src/client/vzReducer/searchReducer.ts +181 -0
  160. package/src/client/vzReducer/setActiveFileIdReducer.ts +28 -0
  161. package/src/client/vzReducer/setActiveFileLeftRightReducer.ts +76 -0
  162. package/src/client/vzReducer/setIsDocOpenReducer.ts +9 -0
  163. package/src/client/vzReducer/setIsSettingsOpenReducer.ts +9 -0
  164. package/src/client/vzReducer/setThemeReducer.ts +9 -0
  165. package/src/client/vzReducer/setUsernameReducer.ts +9 -0
  166. package/src/client/vzReducer/splitCurrentPaneReducer.ts +66 -0
  167. package/src/client/vzReducer/toggleAutoFollowReducer.ts +12 -0
  168. package/src/client/vzReducer/updatePane.ts +40 -0
  169. package/src/client/vzReducer/updatePresenceIndicatorReducer.ts +30 -0
  170. package/src/client/vzReducer/visualEditorReducer.ts +9 -0
  171. package/src/ot.ts +24 -0
  172. package/src/randomId.ts +15 -0
  173. package/src/reactRouterExports.ts +15 -0
  174. package/src/runCode.ts +27 -0
  175. package/src/server/aiChatHandler/aiEditing.ts +151 -0
  176. package/src/server/aiChatHandler/chatOperations.ts +476 -0
  177. package/src/server/aiChatHandler/errorHandling.ts +66 -0
  178. package/src/server/aiChatHandler/index.ts +199 -0
  179. package/src/server/aiChatHandler/llmStreaming.ts +419 -0
  180. package/src/server/aiChatHandler/validation.ts +24 -0
  181. package/src/server/computeInitialDocument.ts +220 -0
  182. package/src/server/config.ts +24 -0
  183. package/src/server/featureFlags.ts +8 -0
  184. package/src/server/generateAIResponse.ts +188 -0
  185. package/src/server/handleAIAssist.ts +53 -0
  186. package/src/server/handleAIChatMessage.ts +2 -0
  187. package/src/server/handleAICopilot.ts +97 -0
  188. package/src/server/index.ts +379 -0
  189. package/src/server/isDirectory.ts +1 -0
  190. package/src/server/livekit.ts +24 -0
  191. package/src/server/prettier.ts +115 -0
  192. package/src/server/setupEnv.ts +8 -0
  193. package/src/submitOperation.ts +25 -0
  194. package/src/types.ts +205 -0
  195. package/src/utils/fileDiff.ts +117 -0
  196. package/src/vite-env.d.ts +10 -0
@@ -0,0 +1,615 @@
1
+ import { createRoot } from 'react-dom/client';
2
+ import { EditorView } from 'codemirror';
3
+ import {
4
+ Compartment,
5
+ EditorState,
6
+ StateField,
7
+ } from '@codemirror/state';
8
+ import { javascript } from '@codemirror/lang-javascript';
9
+ import { svelte } from '@replit/codemirror-lang-svelte';
10
+ import { json } from '@codemirror/lang-json';
11
+ import { markdown } from '@codemirror/lang-markdown';
12
+ import { html } from '@codemirror/lang-html';
13
+ import { css } from '@codemirror/lang-css';
14
+ import { json1Sync } from 'codemirror-ot';
15
+
16
+ import { autocompletion } from '@codemirror/autocomplete';
17
+ import { indentationMarkers } from '@replit/codemirror-indentation-markers';
18
+ // import { showMinimap } from '@replit/codemirror-minimap';
19
+ import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
20
+ import {
21
+ Diagnostic,
22
+ linter,
23
+ lintGutter,
24
+ } from '@codemirror/lint';
25
+
26
+ import { json1Presence, textUnicode } from '../../ot';
27
+ import {
28
+ PaneId,
29
+ ShareDBDoc,
30
+ TabState,
31
+ Username,
32
+ } from '../../types';
33
+ import { VizFileId, VizContent } from '@vizhub/viz-types';
34
+ import { json1PresenceBroadcast } from './json1PresenceBroadcast';
35
+ import { json1PresenceDisplay } from './json1PresenceDisplay';
36
+ import {
37
+ colorsInTextPlugin,
38
+ rotationIndicator,
39
+ widgets,
40
+ } from './InteractiveWidgets';
41
+ import {
42
+ EditorCache,
43
+ editorCacheKey,
44
+ EditorCacheValue,
45
+ } from '../useEditorCache';
46
+ import { ThemeLabel, themeOptionsByLabel } from '../themes';
47
+ import {
48
+ keymap,
49
+ Decoration,
50
+ WidgetType,
51
+ ViewUpdate,
52
+ ViewPlugin,
53
+ DecorationSet,
54
+ } from '@codemirror/view';
55
+ import { Range } from '@codemirror/state';
56
+ import { syntaxTree } from '@codemirror/language';
57
+ import { RegExpCursor } from '@codemirror/search';
58
+ import { basicSetup } from './basicSetup';
59
+ import { InteractRule } from '@replit/codemirror-interact';
60
+ import rainbowBrackets from './rainbowBrackets';
61
+ import { cssLanguage } from '@codemirror/lang-css';
62
+ import { javascriptLanguage } from '@codemirror/lang-javascript';
63
+ import { copilot } from './Copilot';
64
+ import { type WorkerShape } from '@valtown/codemirror-ts/src/worker';
65
+ import * as Comlink from 'comlink';
66
+ import {
67
+ tsAutocompleteWorker,
68
+ tsFacetWorker,
69
+ tsHoverWorker,
70
+ tsLinterWorker,
71
+ tsSyncWorker,
72
+ } from '@valtown/codemirror-ts';
73
+ import { getFileExtension } from '../utils/fileExtension';
74
+ import { SparklesSVG } from '../Icons/SparklesSVG';
75
+ import { VZCodeContext } from '../VZCodeContext';
76
+ import { useContext, useMemo } from 'react';
77
+ import { handleAIChatMessage } from '../../server/aiChatHandler';
78
+
79
+ const DEBUG = false;
80
+
81
+ // Define a StateField to store the file name.
82
+ // This should be defined at the module level if it's to be imported by other modules.
83
+ export const fileNameStateField = StateField.define<string>(
84
+ {
85
+ create: () => '', // Default initial value
86
+ update: (value, tr) => value, // Typically set once at creation for a given editor instance
87
+ },
88
+ );
89
+
90
+ // Enables TypeScript +JSX support in CodeMirror.
91
+ const tsx = () =>
92
+ javascript({ jsx: true, typescript: true });
93
+
94
+ // Lazy initialization of worker
95
+ let worker: Comlink.Remote<WorkerShape> | null = null;
96
+ const initializeWorker = async () => {
97
+ if (worker === null) {
98
+ const innerWorker = new Worker(
99
+ new URL(
100
+ './typescriptExtension/worker.ts',
101
+ import.meta.url,
102
+ ),
103
+ {
104
+ type: 'module',
105
+ },
106
+ );
107
+ worker = Comlink.wrap<WorkerShape>(innerWorker);
108
+ await worker.initialize();
109
+ }
110
+ return worker;
111
+ };
112
+
113
+ const htmlConfig = {
114
+ matchClosingTags: true,
115
+ selfClosingTags: false,
116
+ autoCloseTags: true,
117
+ extraTags: {},
118
+ extraGlobalAttributes: {},
119
+ nestedLanguages: [
120
+ {
121
+ tag: 'script',
122
+ language: javascript,
123
+ parser: javascriptLanguage.parser,
124
+ },
125
+ {
126
+ tag: 'style',
127
+ language: css,
128
+ parser: cssLanguage.parser,
129
+ },
130
+ ],
131
+ nestedAttributes: [],
132
+ };
133
+
134
+ // Language extensions for CodeMirror.
135
+ // Keys are file extensions.
136
+ // Values are CodeMirror extensions.
137
+ // TODO consider moving this to a separate file.
138
+ const languageExtensions = {
139
+ json,
140
+ tsx,
141
+ js: tsx,
142
+ jsx: tsx,
143
+ ts: tsx,
144
+ html: () => html(htmlConfig),
145
+ css,
146
+ md: markdown,
147
+ svelte,
148
+ };
149
+
150
+ // Exported utility function to get language extension for a file extension
151
+ export const getLanguageExtension = (
152
+ fileExtension: string,
153
+ ) => {
154
+ const languageFunc = languageExtensions[fileExtension];
155
+ return languageFunc ? languageFunc() : undefined;
156
+ };
157
+
158
+ // Gets a value at a path in an object.
159
+ // e.g. getAtPath({a: {b: 1}}, ['a', 'b']) === 1
160
+ const getAtPath = (obj, path) => {
161
+ let current = obj;
162
+ for (const key of path) {
163
+ if (!current) return undefined;
164
+ current = current[key];
165
+ }
166
+ return current;
167
+ };
168
+
169
+ // Extend the EditorCacheValue type to include the compartments and the updateRainbowBrackets method
170
+ interface ExtendedEditorCacheValue
171
+ extends EditorCacheValue {
172
+ themeCompartment: Compartment;
173
+ languageCompartment: Compartment;
174
+ rainbowBracketsCompartment: Compartment;
175
+ updateRainbowBrackets: (enabled: boolean) => void;
176
+ }
177
+
178
+ // Gets or creates an `editorCache` entry for the given file id.
179
+ // Looks in `editorCache` first, and if not found, creates a new editor.
180
+ export const getOrCreateEditor = async ({
181
+ paneId = 'root',
182
+ fileId,
183
+ shareDBDoc,
184
+ filesPath,
185
+ localPresence,
186
+ docPresence,
187
+ theme,
188
+ onInteract,
189
+ editorCache,
190
+ usernameRef,
191
+ customInteractRules,
192
+ enableAutoFollowRef,
193
+ aiCopilotEndpoint,
194
+ esLintSource,
195
+ rainbowBracketsEnabled = true,
196
+ setIsAIChatOpen,
197
+ setAIChatMessage,
198
+ handleSendMessage,
199
+ }: {
200
+ // TODO pass this in from the outside
201
+ paneId?: PaneId;
202
+ fileId: VizFileId;
203
+
204
+ // The ShareDB document that contains the file.
205
+ // Used when the editor is created for:
206
+ // * Figuring out the initial text
207
+ // * Figuring out the initial language extension
208
+ // based on the file extension.
209
+ // It's also passed into the `json1Sync` extension,
210
+ // which is used for multiplayer editing.
211
+ // This can be `undefined` in the case where we are
212
+ // viewing files read-only, in which case multiplayer
213
+ // editing is not enabled.
214
+ shareDBDoc: ShareDBDoc<VizContent>;
215
+
216
+ filesPath: string[];
217
+ localPresence: any;
218
+ docPresence: any;
219
+ theme: ThemeLabel;
220
+ onInteract?: () => void;
221
+ editorCache: EditorCache;
222
+ usernameRef: React.MutableRefObject<Username>;
223
+ aiAssistEndpoint?: string;
224
+ customInteractRules?: Array<InteractRule>;
225
+
226
+ // Ref to a boolean that determines whether to
227
+ // enable auto-following the cursors of remote users.
228
+ enableAutoFollowRef: React.MutableRefObject<boolean>;
229
+ aiCopilotEndpoint?: string;
230
+ esLintSource: (
231
+ view: EditorView,
232
+ ) => Promise<readonly Diagnostic[]>;
233
+ rainbowBracketsEnabled?: boolean; // New parameter type
234
+ setIsAIChatOpen: (isAIChatOpen: boolean) => void;
235
+ setAIChatMessage: (message: string) => void;
236
+ handleSendMessage: any; // TODO fix types
237
+ }): Promise<ExtendedEditorCacheValue> => {
238
+ // Cache hit
239
+
240
+ const cacheKey = editorCacheKey(fileId, paneId);
241
+
242
+ if (editorCache.has(cacheKey)) {
243
+ return editorCache.get(
244
+ cacheKey,
245
+ ) as ExtendedEditorCacheValue;
246
+ }
247
+
248
+ // Cache miss
249
+
250
+ // Compute `text` and `fileExtension` from the ShareDB document.
251
+ const textPath = [...filesPath, fileId, 'text'];
252
+ const namePath = [...filesPath, fileId, 'name'];
253
+ const content = shareDBDoc.data;
254
+ const text = getAtPath(content, textPath);
255
+ const name = getAtPath(content, namePath);
256
+
257
+ const fileExtension = getFileExtension(name);
258
+
259
+ // Create a compartment for the theme so that it can be changed dynamically.
260
+ // Inspired by: https://github.com/craftzdog/cm6-themes/blob/main/example/index.ts
261
+ let themeCompartment = new Compartment();
262
+
263
+ // Create a compartment for rainbow brackets so that it can be enabled/disabled dynamically.
264
+ let rainbowBracketsCompartment = new Compartment();
265
+
266
+ // The CodeMirror extensions to use.
267
+ // const extensions = [autocompletion(), html(htmlConfig)]
268
+ const extensions = [];
269
+
270
+ // Initialize the fileNameStateField with the actual file name
271
+ extensions.push(fileNameStateField.init(() => name));
272
+
273
+ // This plugin implements multiplayer editing,
274
+ // real-time synchronozation of changes across clients.
275
+ // Does not deal with showing others' cursors.
276
+ if (shareDBDoc) {
277
+ extensions.push(
278
+ json1Sync({
279
+ shareDBDoc,
280
+ path: textPath,
281
+ json1: json1Presence,
282
+ textUnicode,
283
+ }),
284
+ );
285
+
286
+ // Deals with broadcasting changes in cursor location and selection.
287
+ if (localPresence) {
288
+ extensions.push(
289
+ json1PresenceBroadcast({
290
+ path: textPath,
291
+ localPresence,
292
+ usernameRef,
293
+ }),
294
+ );
295
+ }
296
+
297
+ // Deals with receiving the broadcast from other clients and displaying them.
298
+ if (docPresence) {
299
+ extensions.push(
300
+ json1PresenceDisplay({
301
+ path: textPath,
302
+ docPresence,
303
+ enableAutoFollowRef,
304
+ }),
305
+ );
306
+ }
307
+ } else {
308
+ // If the ShareDB document is not provided,
309
+ // then we do not allow editing.
310
+ extensions.push(EditorView.editable.of(false));
311
+ }
312
+
313
+ extensions.push(colorsInTextPlugin);
314
+
315
+ // This is the "basic setup" for CodeMirror,
316
+ // which actually adds a ton on functionality.
317
+ // TODO vet this functionality, and determine how much
318
+ // we want to replace with
319
+ // https://github.com/vizhub-core/vzcode/issues/134
320
+ extensions.push(basicSetup);
321
+
322
+ if (esLintSource) {
323
+ extensions.push(lintGutter()); // Show lint icons in the gutter
324
+ extensions.push(
325
+ linter(esLintSource, {
326
+ // You can configure linter options here, e.g., delay
327
+ delay: 750,
328
+ }),
329
+ );
330
+ }
331
+
332
+ // This supports dynamic changing of the theme.
333
+ extensions.push(
334
+ themeCompartment.of(themeOptionsByLabel[theme].value),
335
+ );
336
+
337
+ // Adds compartment for rainbow brackets with initial toggle state.
338
+ extensions.push(
339
+ rainbowBracketsCompartment.of(
340
+ rainbowBracketsEnabled ? rainbowBrackets() : [],
341
+ ),
342
+ );
343
+
344
+ // TODO handle dynamic changing of the file extension.
345
+ // TODO handle dynamic file extensions by making
346
+ // the CodeMirror language extension dynamic
347
+ // using a Compartment.
348
+ const languageCompartment = new Compartment();
349
+ // See https://github.com/vizhub-core/vzcode/issues/55
350
+
351
+ const languageExtension =
352
+ getLanguageExtension(fileExtension);
353
+ if (languageExtension) {
354
+ extensions.push(
355
+ languageCompartment.of(languageExtension),
356
+ );
357
+ } else {
358
+ // Not sure if this case even works.
359
+ // TODO manually test this case by creating a file
360
+ // that has no extension, opening it up,
361
+ // and then adding an extension.
362
+ // console.warn(
363
+ // `No language extension for file extension: ${fileExtension}`,
364
+ // );
365
+ // We still need to push the compartment,
366
+ // otherwise the compartment won't work when
367
+ // a file extension _is_ added later on.
368
+ extensions.push(languageCompartment.of([]));
369
+ }
370
+
371
+ // Enable line wrapping for Markdown files
372
+ if (fileExtension === 'md') {
373
+ extensions.push(EditorView.lineWrapping);
374
+ }
375
+
376
+ // Add interactive widgets.
377
+ // Includes the Alt+drag functionality for numbers.
378
+ // Calls `onInteract` when one of those widgets is interacted with.
379
+ // This can be used to trigger a transition to throttled mode
380
+ // for hot reloading.
381
+ // TODO consider leveraging the new `dragEnd` event handler.
382
+ // and removing the `onInteract` callback, replacing it with
383
+ // `onInteractStart` and `onInteractEnd`.
384
+ // That may be tricky for one-off interactions though, like
385
+ // the boolean checkboxes. The color pickers are also tricky,
386
+ // as they would also need to be able to handle `onInteractEnd`.
387
+ // See https://github.com/replit/codemirror-interact/issues/14
388
+ extensions.push(
389
+ widgets({ onInteract, customInteractRules }),
390
+ );
391
+
392
+ // TODO fix the bugginess in this one where
393
+ // the highlight persists after the mouse leaves.
394
+ // extensions.push(highlightWidgets);
395
+
396
+ extensions.push(rotationIndicator);
397
+
398
+ // extensions.push(
399
+ // AIAssistCodeMirrorKeyMap({
400
+ // shareDBDoc,
401
+ // fileId,
402
+ // tabList,
403
+ // aiAssistEndpoint,
404
+ // aiAssistOptions,
405
+ // }),
406
+ // );
407
+
408
+ // Add the extension that provides indentation markers.
409
+ extensions.push(
410
+ indentationMarkers({
411
+ // thickness: 2,
412
+ colors: {
413
+ light: '#4d586b',
414
+ dark: '#4d586b',
415
+ activeLight: '#8e949f',
416
+ activeDark: '#8e949f',
417
+ },
418
+ }),
419
+ );
420
+
421
+ if (name.endsWith('ts') || name.endsWith('tsx')) {
422
+ // Initialize worker if needed
423
+ const tsWorker = await initializeWorker();
424
+
425
+ extensions.push(
426
+ ...[
427
+ tsFacetWorker.of({ worker: tsWorker, path: name }),
428
+ tsSyncWorker(),
429
+ tsLinterWorker(),
430
+ autocompletion({
431
+ override: [tsAutocompleteWorker()],
432
+ }),
433
+ tsHoverWorker(),
434
+ ],
435
+ );
436
+ }
437
+
438
+ // Show the minimap
439
+ // See https://github.com/replit/codemirror-minimap#usage
440
+ // This extension has poor performance, so it's disabled for now.
441
+ // extensions.push(
442
+ // showMinimap.compute(['doc'], () => ({
443
+ // create: () => ({
444
+ // dom: document.createElement('div'),
445
+ // }),
446
+ // // Without this, performance is terrible.
447
+ // displayText: 'blocks',
448
+ // })),
449
+ // );
450
+
451
+ // VSCode keybindings
452
+ // See https://github.com/replit/codemirror-vscode-keymap#usage
453
+ // extensions.push(keymap.of(vscodeKeymap));
454
+ extensions.push(
455
+ keymap.of(
456
+ vscodeKeymap.map((binding) => {
457
+ // Here we override the Shift+Enter behavior specifically,
458
+ // as that can be used to trigger a manual save/Prettier,
459
+ // and the default behavior from the keymap interferes.
460
+ if (binding.key === 'Enter') {
461
+ delete binding.shift;
462
+ }
463
+ return binding;
464
+ }),
465
+ ),
466
+ );
467
+
468
+ // Adds copilot completions
469
+ DEBUG &&
470
+ console.log(
471
+ '[getOrCreateEditor] aiCopilotEndpoint: ',
472
+ aiCopilotEndpoint,
473
+ );
474
+ if (aiCopilotEndpoint) {
475
+ extensions.push(copilot({ aiCopilotEndpoint }));
476
+ }
477
+
478
+ // const { setIsAIChatOpen } = useContext(VZCodeContext);
479
+
480
+ // Widget appears after instances of "todo" in code editor, allowing AI to implement todo tasks when clicked
481
+ function createToDoPlugin() {
482
+ // setIsAIChatOpen: (open: boolean) => void,
483
+ class ToDoWidget extends WidgetType {
484
+ constructor() {
485
+ super();
486
+ }
487
+
488
+ eq(other: ToDoWidget) {
489
+ return false;
490
+ }
491
+
492
+ toDOM() {
493
+ const wrap = document.createElement('i');
494
+ wrap.style.display = 'inline-flex'; // prevent block expansion
495
+ wrap.style.alignItems = 'center';
496
+ wrap.style.justifyContent = 'center';
497
+ wrap.className = 'icon-button icon-button-dark';
498
+ const reactContainer =
499
+ document.createElement('div');
500
+ wrap.appendChild(reactContainer);
501
+
502
+ const root = createRoot(reactContainer);
503
+ root.render(
504
+ <div
505
+ onClick={() => {
506
+ setIsAIChatOpen(true);
507
+ // setAIChatMessage('Implement the TODO');
508
+ handleSendMessage('Implement the TODO');
509
+ }}
510
+ >
511
+ <SparklesSVG width={14} height={14} />
512
+ </div>,
513
+ );
514
+ return wrap;
515
+ }
516
+
517
+ ignoreEvent() {
518
+ return false;
519
+ }
520
+ }
521
+
522
+ function toDoWidgets(editor: EditorView) {
523
+ const { state } = editor;
524
+ const tree = syntaxTree(state);
525
+ const findToDo = new RegExpCursor(state.doc, 'todo', {
526
+ ignoreCase: true,
527
+ });
528
+ const widgets: Range<Decoration>[] = [];
529
+
530
+ while (!findToDo.next().done) {
531
+ const { from, to } = findToDo.value;
532
+ const node = tree.resolve(from);
533
+ let inComment = false;
534
+
535
+ // Walk up the parents until we hit the root, looking for a comment node
536
+ for (let cur: any = node; cur; cur = cur.parent) {
537
+ if (
538
+ cur.type.is('Comment') || // grammars that group comments
539
+ cur.type.is('comment') || // generic lowercase group
540
+ /Comment$/.test(cur.type.name) // fallback for e.g. LineComment
541
+ ) {
542
+ inComment = true;
543
+ break;
544
+ }
545
+ }
546
+ if (!inComment) continue; // skip non-comment TODOs
547
+
548
+ const deco = Decoration.widget({
549
+ widget: new ToDoWidget(),
550
+ side: 1,
551
+ }).range(to);
552
+ widgets.push(deco);
553
+ }
554
+ return Decoration.set(widgets);
555
+ }
556
+
557
+ const todoPlugin = ViewPlugin.fromClass(
558
+ class {
559
+ decorations: DecorationSet;
560
+
561
+ constructor(view: EditorView) {
562
+ this.decorations = toDoWidgets(view);
563
+ }
564
+
565
+ update(update: ViewUpdate) {
566
+ if (
567
+ update.docChanged ||
568
+ update.viewportChanged ||
569
+ syntaxTree(update.startState) !==
570
+ syntaxTree(update.state)
571
+ )
572
+ this.decorations = toDoWidgets(update.view);
573
+ }
574
+ },
575
+ {
576
+ decorations: (v) => v.decorations,
577
+ },
578
+ );
579
+
580
+ return todoPlugin;
581
+ }
582
+
583
+ extensions.push(createToDoPlugin());
584
+
585
+ const editor = new EditorView({
586
+ state: EditorState.create({
587
+ doc: text,
588
+ extensions,
589
+ }),
590
+ });
591
+
592
+ const editorCacheValue: ExtendedEditorCacheValue = {
593
+ editor,
594
+ themeCompartment,
595
+ languageCompartment,
596
+ rainbowBracketsCompartment,
597
+ updateRainbowBrackets: (enabled) => {
598
+ editor.dispatch({
599
+ effects: rainbowBracketsCompartment.reconfigure(
600
+ enabled ? rainbowBrackets() : [],
601
+ ),
602
+ });
603
+ },
604
+ };
605
+
606
+ // Populate the cache.
607
+ editorCache.set(cacheKey, editorCacheValue);
608
+
609
+ // Initialize rainbow brackets based on the toggle state
610
+ editorCacheValue.updateRainbowBrackets(
611
+ rainbowBracketsEnabled,
612
+ );
613
+
614
+ return editorCacheValue;
615
+ };