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.
- package/package.json +3 -2
- package/src/cli.ts +208 -0
- package/src/client/AIAssist/AIAssistCodeMirrorKeyMap.ts +42 -0
- package/src/client/AIAssist/AIAssistWidget/index.tsx +135 -0
- package/src/client/AIAssist/AIAssistWidget/style.scss +47 -0
- package/src/client/AIAssist/Spinner.jsx +76 -0
- package/src/client/AIAssist/formatFile.ts +27 -0
- package/src/client/AIAssist/generateFilesContext.ts +25 -0
- package/src/client/AIAssist/index.ts +2 -0
- package/src/client/AIAssist/startAIAssist.ts +95 -0
- package/src/client/App/index.tsx +152 -0
- package/src/client/App/style.scss +82 -0
- package/src/client/App/usePending.ts +51 -0
- package/src/client/App/useShareDB.ts +161 -0
- package/src/client/CodeEditor/Copilot/index.ts +74 -0
- package/src/client/CodeEditor/InteractiveWidgets/boolToggler.ts +12 -0
- package/src/client/CodeEditor/InteractiveWidgets/colorPicker.ts +68 -0
- package/src/client/CodeEditor/InteractiveWidgets/colorsInTextPlugin.ts +174 -0
- package/src/client/CodeEditor/InteractiveWidgets/highlightWidgets.ts +79 -0
- package/src/client/CodeEditor/InteractiveWidgets/index.ts +44 -0
- package/src/client/CodeEditor/InteractiveWidgets/markdownCheckboxToggler.ts +12 -0
- package/src/client/CodeEditor/InteractiveWidgets/numberDragger.ts +20 -0
- package/src/client/CodeEditor/InteractiveWidgets/rotateWidget.ts +143 -0
- package/src/client/CodeEditor/InteractiveWidgets/urlClicker.test.ts +48 -0
- package/src/client/CodeEditor/InteractiveWidgets/urlClicker.ts +12 -0
- package/src/client/CodeEditor/InteractiveWidgets/vec2Slider.ts +25 -0
- package/src/client/CodeEditor/basicSetup.ts +73 -0
- package/src/client/CodeEditor/getOrCreateEditor.tsx +615 -0
- package/src/client/CodeEditor/index.tsx +202 -0
- package/src/client/CodeEditor/json1PresenceBroadcast.ts +41 -0
- package/src/client/CodeEditor/json1PresenceDisplay.ts +350 -0
- package/src/client/CodeEditor/rainbowBrackets.ts +122 -0
- package/src/client/CodeEditor/style.scss +156 -0
- package/src/client/CodeEditor/typescriptExtension/worker.ts +32 -0
- package/src/client/CodeErrorOverlay/index.tsx +77 -0
- package/src/client/CodeErrorOverlay/style.scss +33 -0
- package/src/client/Fonts/fonts.ts +8 -0
- package/src/client/Icons/AdjustmentSVG.tsx +19 -0
- package/src/client/Icons/BugSVG.tsx +20 -0
- package/src/client/Icons/CloseSVG.tsx +19 -0
- package/src/client/Icons/CssSVG.tsx +16 -0
- package/src/client/Icons/DirectoryArrowSVG.tsx +19 -0
- package/src/client/Icons/EditSVG.tsx +19 -0
- package/src/client/Icons/FileSVG.tsx +19 -0
- package/src/client/Icons/FolderSVG.tsx +20 -0
- package/src/client/Icons/GearSVG.tsx +16 -0
- package/src/client/Icons/HtmlSVG.tsx +16 -0
- package/src/client/Icons/JavaScriptSVG.tsx +16 -0
- package/src/client/Icons/JsonSVG.tsx +16 -0
- package/src/client/Icons/MarkdownSVG.tsx +16 -0
- package/src/client/Icons/MicOffSVG.tsx +35 -0
- package/src/client/Icons/MicSVG.tsx +25 -0
- package/src/client/Icons/NewSVG.tsx +19 -0
- package/src/client/Icons/PinSVG.tsx +14 -0
- package/src/client/Icons/PlaySVG.tsx +16 -0
- package/src/client/Icons/QuestionMarkSVG.tsx +18 -0
- package/src/client/Icons/ReactSVG.tsx +16 -0
- package/src/client/Icons/SearchSVG.tsx +20 -0
- package/src/client/Icons/SparklesSVG.tsx +23 -0
- package/src/client/Icons/SplitEditorSVG.tsx +34 -0
- package/src/client/Icons/StopSVG.tsx +15 -0
- package/src/client/Icons/SvelteSVG.tsx +16 -0
- package/src/client/Icons/TrashSVG.tsx +19 -0
- package/src/client/Icons/TypeScriptSVG.tsx +16 -0
- package/src/client/Icons/index.tsx +26 -0
- package/src/client/ImageViewer/index.tsx +91 -0
- package/src/client/ImageViewer/style.scss +41 -0
- package/src/client/PresenceNotifications/PresenceNotifications.test.ts +134 -0
- package/src/client/PresenceNotifications/index.tsx +158 -0
- package/src/client/PresenceNotifications/style.scss +17 -0
- package/src/client/RunCodeWidget/index.tsx +117 -0
- package/src/client/RunCodeWidget/style.scss +24 -0
- package/src/client/SplitPaneResizeContext.tsx +159 -0
- package/src/client/TabList/Tab.tsx +73 -0
- package/src/client/TabList/index.tsx +35 -0
- package/src/client/TabList/style.scss +51 -0
- package/src/client/VZCodeContext/index.tsx +29 -0
- package/src/client/VZCodeContext/types.ts +248 -0
- package/src/client/VZCodeContext/useVZCodeState.ts +602 -0
- package/src/client/VZKeyboardShortcutsDoc.tsx +148 -0
- package/src/client/VZLeft.tsx +28 -0
- package/src/client/VZMiddle.tsx +172 -0
- package/src/client/VZResizer/index.tsx +128 -0
- package/src/client/VZResizer/styles.scss +33 -0
- package/src/client/VZRight.tsx +113 -0
- package/src/client/VZSettings.tsx +275 -0
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +243 -0
- package/src/client/VZSidebar/AIChat/DiffView.scss +136 -0
- package/src/client/VZSidebar/AIChat/DiffView.tsx +176 -0
- package/src/client/VZSidebar/AIChat/Message.tsx +79 -0
- package/src/client/VZSidebar/AIChat/MessageList.tsx +190 -0
- package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +35 -0
- package/src/client/VZSidebar/AIChat/ThinkingScratchpad.tsx +156 -0
- package/src/client/VZSidebar/AIChat/TypingIndicator.tsx +19 -0
- package/src/client/VZSidebar/AIChat/index.tsx +357 -0
- package/src/client/VZSidebar/AIChat/styles.scss +646 -0
- package/src/client/VZSidebar/AIChat/useSpeechRecognition.ts +117 -0
- package/src/client/VZSidebar/CreateDirModal.tsx +111 -0
- package/src/client/VZSidebar/CreateFileModal.tsx +111 -0
- package/src/client/VZSidebar/DeleteConfirmationModal.tsx +76 -0
- package/src/client/VZSidebar/DirectoryListing.tsx +89 -0
- package/src/client/VZSidebar/EmptyState.tsx +11 -0
- package/src/client/VZSidebar/FileListing.tsx +91 -0
- package/src/client/VZSidebar/FileTypeIcon.tsx +49 -0
- package/src/client/VZSidebar/Item.tsx +237 -0
- package/src/client/VZSidebar/Listing.tsx +47 -0
- package/src/client/VZSidebar/Search.tsx +527 -0
- package/src/client/VZSidebar/VisualEditor.tsx +282 -0
- package/src/client/VZSidebar/VoiceChatModal.tsx +121 -0
- package/src/client/VZSidebar/aiCopyPaste.ts +199 -0
- package/src/client/VZSidebar/index.tsx +728 -0
- package/src/client/VZSidebar/styles.scss +612 -0
- package/src/client/VZSidebar/useDragAndDrop.tsx +408 -0
- package/src/client/bootstrap.ts +26 -0
- package/src/client/buildWorker.ts +3 -0
- package/src/client/featureFlags.ts +8 -0
- package/src/client/generateRequestId.ts +6 -0
- package/src/client/getFileTree.ts +53 -0
- package/src/client/index.ts +19 -0
- package/src/client/main.jsx +10 -0
- package/src/client/presenceColor.ts +8 -0
- package/src/client/shouldTriggerRun.ts +31 -0
- package/src/client/sortFileTree.ts +21 -0
- package/src/client/tabsSearchParameters.test.ts +79 -0
- package/src/client/tabsSearchParameters.ts +137 -0
- package/src/client/themes/index.ts +7 -0
- package/src/client/themes/themes.ts +77 -0
- package/src/client/themes/uiwImports.ts +32 -0
- package/src/client/themes/useDynamicTheme.ts +29 -0
- package/src/client/themes/vizhubTheme/colors.ts +80 -0
- package/src/client/themes/vizhubTheme/index.ts +193 -0
- package/src/client/useActions.ts +288 -0
- package/src/client/useESLint/index.ts +72 -0
- package/src/client/useESLint/worker.ts +113 -0
- package/src/client/useEditorCache.ts +35 -0
- package/src/client/useFileCRUD.ts +228 -0
- package/src/client/useKeyboardShortcuts.ts +451 -0
- package/src/client/useOpenDirectories.ts +99 -0
- package/src/client/usePresenceAutoFollow.ts +85 -0
- package/src/client/usePrettier/index.ts +178 -0
- package/src/client/usePrettier/worker.ts +109 -0
- package/src/client/useRunCode.tsx +15 -0
- package/src/client/useRuntimeError.ts +32 -0
- package/src/client/useSubmitOperation.ts +15 -0
- package/src/client/useURLSync.ts +137 -0
- package/src/client/usernameLocalStorage.ts +43 -0
- package/src/client/utils/fileExtension.ts +10 -0
- package/src/client/utils/isImageFile.ts +12 -0
- package/src/client/vzReducer/aiChatReducer.ts +44 -0
- package/src/client/vzReducer/closeTabReducer.test.ts +205 -0
- package/src/client/vzReducer/closeTabsReducer.ts +96 -0
- package/src/client/vzReducer/createInitialState.ts +38 -0
- package/src/client/vzReducer/editorNoLongerWantsFocusReducer.ts +9 -0
- package/src/client/vzReducer/findPane.ts +19 -0
- package/src/client/vzReducer/index.ts +250 -0
- package/src/client/vzReducer/openTabReducer.test.ts +127 -0
- package/src/client/vzReducer/openTabReducer.ts +74 -0
- package/src/client/vzReducer/searchReducer.test.ts +292 -0
- package/src/client/vzReducer/searchReducer.ts +181 -0
- package/src/client/vzReducer/setActiveFileIdReducer.ts +28 -0
- package/src/client/vzReducer/setActiveFileLeftRightReducer.ts +76 -0
- package/src/client/vzReducer/setIsDocOpenReducer.ts +9 -0
- package/src/client/vzReducer/setIsSettingsOpenReducer.ts +9 -0
- package/src/client/vzReducer/setThemeReducer.ts +9 -0
- package/src/client/vzReducer/setUsernameReducer.ts +9 -0
- package/src/client/vzReducer/splitCurrentPaneReducer.ts +66 -0
- package/src/client/vzReducer/toggleAutoFollowReducer.ts +12 -0
- package/src/client/vzReducer/updatePane.ts +40 -0
- package/src/client/vzReducer/updatePresenceIndicatorReducer.ts +30 -0
- package/src/client/vzReducer/visualEditorReducer.ts +9 -0
- package/src/ot.ts +24 -0
- package/src/randomId.ts +15 -0
- package/src/reactRouterExports.ts +15 -0
- package/src/runCode.ts +27 -0
- package/src/server/aiChatHandler/aiEditing.ts +151 -0
- package/src/server/aiChatHandler/chatOperations.ts +476 -0
- package/src/server/aiChatHandler/errorHandling.ts +66 -0
- package/src/server/aiChatHandler/index.ts +199 -0
- package/src/server/aiChatHandler/llmStreaming.ts +419 -0
- package/src/server/aiChatHandler/validation.ts +24 -0
- package/src/server/computeInitialDocument.ts +220 -0
- package/src/server/config.ts +24 -0
- package/src/server/featureFlags.ts +8 -0
- package/src/server/generateAIResponse.ts +188 -0
- package/src/server/handleAIAssist.ts +53 -0
- package/src/server/handleAIChatMessage.ts +2 -0
- package/src/server/handleAICopilot.ts +97 -0
- package/src/server/index.ts +379 -0
- package/src/server/isDirectory.ts +1 -0
- package/src/server/livekit.ts +24 -0
- package/src/server/prettier.ts +115 -0
- package/src/server/setupEnv.ts +8 -0
- package/src/submitOperation.ts +25 -0
- package/src/types.ts +205 -0
- package/src/utils/fileDiff.ts +117 -0
- 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
|
+
};
|