vzcode 0.28.0 → 0.30.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-836bdce7.css +1 -0
- package/dist/assets/index-c3a6c525.js +115 -0
- package/dist/assets/worker-ed0e2778.js +366 -0
- package/dist/index.html +2 -2
- package/package.json +10 -6
- package/src/client/AIAssist.ts +110 -19
- package/src/client/AIAssistWidget/index.tsx +101 -0
- package/src/client/AIAssistWidget/style.scss +10 -0
- package/src/client/App/VZCodeContext.tsx +249 -0
- package/src/client/App/VZLeft.tsx +56 -0
- package/src/client/App/VZMiddle.tsx +91 -0
- package/src/client/App/VZRight.tsx +15 -0
- package/src/client/App/index.tsx +106 -0
- package/src/client/{style.scss → App/style.scss} +14 -3
- package/src/client/App/useShareDB.ts +110 -0
- package/src/client/CodeEditor/getOrCreateEditor.ts +54 -6
- package/src/client/CodeEditor/index.tsx +9 -0
- package/src/client/CodeEditor/json1PresenceDisplay.ts +38 -9
- package/src/client/CodeEditor/typeScriptCompletions.ts +108 -0
- package/src/client/CodeEditor/typeScriptLinter.ts +45 -0
- package/src/client/CodeErrorOverlay/index.tsx +27 -1
- package/src/client/CodeErrorOverlay/style.scss +11 -11
- package/src/client/SplitPaneResizeContext.tsx +198 -39
- package/src/client/TabList/index.tsx +1 -27
- package/src/client/{Resizer → VZResizer}/index.tsx +37 -13
- package/src/client/VZSettings.tsx +1 -1
- package/src/client/VZSidebar/CreateFileModal.tsx +93 -0
- package/src/client/VZSidebar/DeleteConfirmationModal.tsx +42 -24
- package/src/client/VZSidebar/index.tsx +69 -9
- package/src/client/useFileCRUD.ts +19 -19
- package/src/client/useOpenDirectories.ts +12 -8
- package/src/client/usePrettier/index.ts +13 -1
- package/src/client/useSubmitOperation.ts +16 -19
- package/src/client/useTypeScript/index.ts +44 -0
- package/src/client/useTypeScript/requestTypes.ts +29 -0
- package/src/client/useTypeScript/worker.ts +242 -0
- package/src/client/usernameLocalStorage.ts +1 -1
- package/src/client/vzReducer/createInitialState.ts +4 -5
- package/src/index.ts +5 -1
- package/src/server/handleAIAssist.js +76 -12
- package/src/server/index.js +55 -2
- package/src/types.ts +78 -1
- package/dist/assets/index-a9585cbc.js +0 -111
- package/dist/assets/index-d30c9f1b.css +0 -1
- package/src/client/App.tsx +0 -286
- /package/src/client/{Resizer → VZResizer}/styles.scss +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import { SplitPaneResizeContext } from '../SplitPaneResizeContext';
|
|
3
|
+
import { TabList } from '../TabList';
|
|
4
|
+
import { CodeEditor } from '../CodeEditor';
|
|
5
|
+
import { CodeErrorOverlay } from '../CodeErrorOverlay';
|
|
6
|
+
import { PresenceNotifications } from '../PresenceNotifications';
|
|
7
|
+
import { AIAssistWidget } from '../AIAssistWidget';
|
|
8
|
+
import { VZCodeContext } from './VZCodeContext';
|
|
9
|
+
|
|
10
|
+
// The middle portion of the VZCode environment, containing:
|
|
11
|
+
// * The list of tabs at the top
|
|
12
|
+
// * The code editor itself
|
|
13
|
+
// * The code error overlay
|
|
14
|
+
// * The presence notifications
|
|
15
|
+
// * The UI for AI Assist
|
|
16
|
+
export const VZMiddle = () => {
|
|
17
|
+
const { codeEditorWidth } = useContext(
|
|
18
|
+
SplitPaneResizeContext,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
// TODO leverage this context in deeper levels of the component tree.
|
|
22
|
+
const {
|
|
23
|
+
content,
|
|
24
|
+
shareDBDoc,
|
|
25
|
+
submitOperation,
|
|
26
|
+
localPresence,
|
|
27
|
+
docPresence,
|
|
28
|
+
files,
|
|
29
|
+
createFile,
|
|
30
|
+
activeFileId,
|
|
31
|
+
setActiveFileId,
|
|
32
|
+
tabList,
|
|
33
|
+
openTab,
|
|
34
|
+
closeTabs,
|
|
35
|
+
theme,
|
|
36
|
+
username,
|
|
37
|
+
editorCache,
|
|
38
|
+
editorWantsFocus,
|
|
39
|
+
editorNoLongerWantsFocus,
|
|
40
|
+
errorMessage,
|
|
41
|
+
typeScriptWorker,
|
|
42
|
+
} = useContext(VZCodeContext);
|
|
43
|
+
|
|
44
|
+
return activeFileId !== null ? (
|
|
45
|
+
<div
|
|
46
|
+
className="middle"
|
|
47
|
+
style={{ width: codeEditorWidth + 'px' }}
|
|
48
|
+
>
|
|
49
|
+
<TabList
|
|
50
|
+
files={files}
|
|
51
|
+
tabList={tabList}
|
|
52
|
+
activeFileId={activeFileId}
|
|
53
|
+
setActiveFileId={setActiveFileId}
|
|
54
|
+
openTab={openTab}
|
|
55
|
+
closeTabs={closeTabs}
|
|
56
|
+
createFile={createFile}
|
|
57
|
+
/>
|
|
58
|
+
{content && activeFileId ? (
|
|
59
|
+
<CodeEditor
|
|
60
|
+
shareDBDoc={shareDBDoc}
|
|
61
|
+
submitOperation={submitOperation}
|
|
62
|
+
localPresence={localPresence}
|
|
63
|
+
docPresence={docPresence}
|
|
64
|
+
activeFileId={activeFileId}
|
|
65
|
+
theme={theme}
|
|
66
|
+
editorCache={editorCache}
|
|
67
|
+
editorWantsFocus={editorWantsFocus}
|
|
68
|
+
editorNoLongerWantsFocus={
|
|
69
|
+
editorNoLongerWantsFocus
|
|
70
|
+
}
|
|
71
|
+
username={username}
|
|
72
|
+
typeScriptWorker={typeScriptWorker}
|
|
73
|
+
tabList={tabList}
|
|
74
|
+
/>
|
|
75
|
+
) : null}
|
|
76
|
+
<CodeErrorOverlay errorMessage={errorMessage} />
|
|
77
|
+
<PresenceNotifications
|
|
78
|
+
docPresence={docPresence}
|
|
79
|
+
localPresence={localPresence}
|
|
80
|
+
/>
|
|
81
|
+
{content && activeFileId ? (
|
|
82
|
+
<AIAssistWidget
|
|
83
|
+
activeFileId={activeFileId}
|
|
84
|
+
shareDBDoc={shareDBDoc}
|
|
85
|
+
editorCache={editorCache}
|
|
86
|
+
tabList={tabList}
|
|
87
|
+
/>
|
|
88
|
+
) : null}
|
|
89
|
+
</div>
|
|
90
|
+
) : null;
|
|
91
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Displays the Vite devserver iframe.
|
|
2
|
+
// TODO test this out, think this through.
|
|
3
|
+
// Purpose: support folks using VZCode as the editor
|
|
4
|
+
// for their Vite-based projects.
|
|
5
|
+
const enableIframe = false;
|
|
6
|
+
|
|
7
|
+
export const VZRight = () => {
|
|
8
|
+
return (
|
|
9
|
+
<div className="right">
|
|
10
|
+
{enableIframe ? (
|
|
11
|
+
<iframe src="http://localhost:5173/"></iframe>
|
|
12
|
+
) : null}
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import ShareDBClient from 'sharedb-client-browser/dist/sharedb-client-umd.cjs';
|
|
3
|
+
import { json1Presence } from '../../ot';
|
|
4
|
+
import { Username } from '../../types';
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
import PrettierWorker from '../usePrettier/worker?worker';
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
import TypeScriptWorker from '../useTypeScript/worker?worker';
|
|
9
|
+
import { SplitPaneResizeProvider } from '../SplitPaneResizeContext';
|
|
10
|
+
import { VZResizer } from '../VZResizer';
|
|
11
|
+
import {
|
|
12
|
+
useInitialUsername,
|
|
13
|
+
usePersistUsername,
|
|
14
|
+
} from '../usernameLocalStorage';
|
|
15
|
+
import { useShareDB } from './useShareDB';
|
|
16
|
+
import { VZLeft } from './VZLeft';
|
|
17
|
+
import { VZMiddle } from './VZMiddle';
|
|
18
|
+
import { VZRight } from './VZRight';
|
|
19
|
+
import {
|
|
20
|
+
VZCodeContext,
|
|
21
|
+
VZCodeProvider,
|
|
22
|
+
} from './VZCodeContext';
|
|
23
|
+
import './style.scss';
|
|
24
|
+
|
|
25
|
+
// Instantiate the Prettier worker.
|
|
26
|
+
const prettierWorker = new PrettierWorker();
|
|
27
|
+
|
|
28
|
+
// Instantiate the TypeScript worker.
|
|
29
|
+
const typeScriptWorker = new TypeScriptWorker();
|
|
30
|
+
|
|
31
|
+
// Register our custom JSON1 OT type that supports presence.
|
|
32
|
+
// See https://github.com/vizhub-core/json1-presence
|
|
33
|
+
ShareDBClient.types.register(json1Presence.type);
|
|
34
|
+
|
|
35
|
+
// Establish the singleton ShareDB connection over WebSockets.
|
|
36
|
+
// TODO consider using reconnecting WebSocket
|
|
37
|
+
const { Connection } = ShareDBClient;
|
|
38
|
+
const wsProtocol =
|
|
39
|
+
window.location.protocol === 'https:'
|
|
40
|
+
? 'wss://'
|
|
41
|
+
: 'ws://';
|
|
42
|
+
const socket = new WebSocket(
|
|
43
|
+
wsProtocol + window.location.host + '/ws',
|
|
44
|
+
);
|
|
45
|
+
const connection = new Connection(socket);
|
|
46
|
+
|
|
47
|
+
// Stores the username to local storage.
|
|
48
|
+
// TODO consider if there's a cleaner pattern for this.
|
|
49
|
+
// This makes sense in VZCode itself, but for an app with
|
|
50
|
+
// authentication that wraps VZCode, it is not required.
|
|
51
|
+
const PersistUsername = () => {
|
|
52
|
+
const { username } = useContext(VZCodeContext);
|
|
53
|
+
usePersistUsername(username);
|
|
54
|
+
return null;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function App() {
|
|
58
|
+
const {
|
|
59
|
+
shareDBDoc,
|
|
60
|
+
content,
|
|
61
|
+
localPresence,
|
|
62
|
+
docPresence,
|
|
63
|
+
submitOperation,
|
|
64
|
+
} = useShareDB({
|
|
65
|
+
connection,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Get the initial username from localStorage.
|
|
69
|
+
const initialUsername: Username = useInitialUsername();
|
|
70
|
+
|
|
71
|
+
// Feature flag for enabling the right-side panel.
|
|
72
|
+
// Useful for debugging dual split pane functionality.
|
|
73
|
+
// We may want to add this as an actual VZCode feature,
|
|
74
|
+
// for running the code that the VZCode user is developing
|
|
75
|
+
// in the same browser window as the VZCode editor,
|
|
76
|
+
// so that multiple browser windows are not required.
|
|
77
|
+
const enableRightPanel = true;
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<SplitPaneResizeProvider>
|
|
81
|
+
<VZCodeProvider
|
|
82
|
+
content={content}
|
|
83
|
+
shareDBDoc={shareDBDoc}
|
|
84
|
+
submitOperation={submitOperation}
|
|
85
|
+
localPresence={localPresence}
|
|
86
|
+
docPresence={docPresence}
|
|
87
|
+
prettierWorker={prettierWorker}
|
|
88
|
+
typeScriptWorker={typeScriptWorker}
|
|
89
|
+
initialUsername={initialUsername}
|
|
90
|
+
>
|
|
91
|
+
<div className="app">
|
|
92
|
+
<VZLeft />
|
|
93
|
+
<VZMiddle />
|
|
94
|
+
{enableRightPanel ? <VZRight /> : null}
|
|
95
|
+
<VZResizer side="left" />
|
|
96
|
+
{enableRightPanel ? (
|
|
97
|
+
<VZResizer side="right" />
|
|
98
|
+
) : null}
|
|
99
|
+
</div>
|
|
100
|
+
<PersistUsername />
|
|
101
|
+
</VZCodeProvider>
|
|
102
|
+
</SplitPaneResizeProvider>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export default App;
|
|
@@ -49,16 +49,27 @@ body,
|
|
|
49
49
|
position: relative;
|
|
50
50
|
|
|
51
51
|
.left,
|
|
52
|
-
.
|
|
52
|
+
.middle {
|
|
53
53
|
display: flex;
|
|
54
54
|
|
|
55
55
|
/* To suport PrettierErrorOverlay, which is position: absolute */
|
|
56
56
|
position: relative;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
.
|
|
60
|
-
flex: 1;
|
|
59
|
+
.middle {
|
|
60
|
+
// flex: 1;
|
|
61
61
|
flex-direction: column;
|
|
62
62
|
min-width: 0;
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
.right {
|
|
66
|
+
flex: 1;
|
|
67
|
+
background-color: white;
|
|
68
|
+
|
|
69
|
+
iframe {
|
|
70
|
+
width: 100%;
|
|
71
|
+
height: 100%;
|
|
72
|
+
border: none;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
64
75
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { randomId } from '../../randomId';
|
|
3
|
+
import { ShareDBDoc, VZCodeContent } from '../../types';
|
|
4
|
+
import { useSubmitOperation } from '../useSubmitOperation';
|
|
5
|
+
|
|
6
|
+
// Set up the connection to ShareDB.
|
|
7
|
+
export const useShareDB = ({
|
|
8
|
+
connection,
|
|
9
|
+
}: {
|
|
10
|
+
connection: any;
|
|
11
|
+
}) => {
|
|
12
|
+
// The ShareDB document.
|
|
13
|
+
const [shareDBDoc, setShareDBDoc] =
|
|
14
|
+
useState<ShareDBDoc<VZCodeContent> | null>(null);
|
|
15
|
+
|
|
16
|
+
// Local ShareDB presence, for broadcasting our cursor position
|
|
17
|
+
// so other clients can see it.
|
|
18
|
+
// See https://share.github.io/sharedb/api/local-presence
|
|
19
|
+
const [localPresence, setLocalPresence] = useState(null);
|
|
20
|
+
|
|
21
|
+
// The document-level presence object, which emits
|
|
22
|
+
// changes in remote presence.
|
|
23
|
+
const [docPresence, setDocPresence] = useState(null);
|
|
24
|
+
|
|
25
|
+
// The `doc.data` part of the ShareDB document,
|
|
26
|
+
// updated on each change to decouple rendering from ShareDB.
|
|
27
|
+
// Starts out as `null` until the document is loaded.
|
|
28
|
+
const [content, setContent] =
|
|
29
|
+
useState<VZCodeContent | null>(null);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
// Since there is only ever a single document,
|
|
33
|
+
// these things are pretty arbitrary.
|
|
34
|
+
// * `collection` - the ShareDB collection to use
|
|
35
|
+
// * `id` - the id of the ShareDB document to use
|
|
36
|
+
const collection = 'documents';
|
|
37
|
+
const id = '1';
|
|
38
|
+
|
|
39
|
+
// Initialize the ShareDB document.
|
|
40
|
+
const shareDBDoc = connection.get(collection, id);
|
|
41
|
+
|
|
42
|
+
// Subscribe to the document to get updates.
|
|
43
|
+
// This callback gets called once only.
|
|
44
|
+
shareDBDoc.subscribe(() => {
|
|
45
|
+
// Expose ShareDB doc to downstream logic.
|
|
46
|
+
setShareDBDoc(shareDBDoc);
|
|
47
|
+
|
|
48
|
+
// Set initial data.
|
|
49
|
+
setContent(shareDBDoc.data);
|
|
50
|
+
|
|
51
|
+
// Listen for all changes and update `data`.
|
|
52
|
+
// This decouples rendering logic from ShareDB.
|
|
53
|
+
// This callback gets called on each change.
|
|
54
|
+
shareDBDoc.on('op', () => {
|
|
55
|
+
// TODO consider excluding file contents from this,
|
|
56
|
+
// because currently we are re-rendering the entire
|
|
57
|
+
// document on each file change (each keystroke while editing).
|
|
58
|
+
// This is not required, because no part of the app outside
|
|
59
|
+
// of the CodeMirror integration uses those file contents.
|
|
60
|
+
// The Sidebar only needs to know file names, not contents.
|
|
61
|
+
setContent(shareDBDoc.data);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Set up presence.
|
|
65
|
+
// See https://github.com/share/sharedb/blob/master/examples/rich-text-presence/client.js#L53
|
|
66
|
+
const docPresence = connection.getDocPresence(
|
|
67
|
+
collection,
|
|
68
|
+
id,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Subscribe to receive remote presence updates.
|
|
72
|
+
docPresence.subscribe(function (error) {
|
|
73
|
+
if (error) throw error;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Set up our local presence for broadcasting this client's presence.
|
|
77
|
+
const generateTimestampedId = () => {
|
|
78
|
+
const timestamp = Date.now().toString(36);
|
|
79
|
+
const randomPart = randomId();
|
|
80
|
+
return `${timestamp}-${randomPart}`;
|
|
81
|
+
};
|
|
82
|
+
setLocalPresence(
|
|
83
|
+
docPresence.create(generateTimestampedId()),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// Store docPresence so child components can listen for changes.
|
|
87
|
+
setDocPresence(docPresence);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// TODO unsubscribe from presence
|
|
91
|
+
// TODO unsubscribe from doc
|
|
92
|
+
return () => {
|
|
93
|
+
// shareDBDoc.destroy();
|
|
94
|
+
// docPresence.destroy();
|
|
95
|
+
};
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
// A convenience function for mutating the ShareDB document
|
|
99
|
+
// based submitting OT ops generated by diffing the JSON.
|
|
100
|
+
const submitOperation =
|
|
101
|
+
useSubmitOperation<VZCodeContent>(shareDBDoc);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
shareDBDoc,
|
|
105
|
+
content,
|
|
106
|
+
localPresence,
|
|
107
|
+
docPresence,
|
|
108
|
+
submitOperation,
|
|
109
|
+
};
|
|
110
|
+
};
|
|
@@ -4,10 +4,13 @@ import {
|
|
|
4
4
|
EditorState,
|
|
5
5
|
} from '@codemirror/state';
|
|
6
6
|
import { javascript } from '@codemirror/lang-javascript';
|
|
7
|
+
import { json } from '@codemirror/lang-json';
|
|
7
8
|
import { markdown } from '@codemirror/lang-markdown';
|
|
8
9
|
import { html } from '@codemirror/lang-html';
|
|
9
10
|
import { css } from '@codemirror/lang-css';
|
|
10
11
|
import { json1Sync } from 'codemirror-ot';
|
|
12
|
+
import { autocompletion } from '@codemirror/autocomplete';
|
|
13
|
+
import { Diagnostic, linter } from '@codemirror/lint';
|
|
11
14
|
import { json1Presence, textUnicode } from '../../ot';
|
|
12
15
|
import {
|
|
13
16
|
FileId,
|
|
@@ -29,18 +32,30 @@ import {
|
|
|
29
32
|
} from '../useEditorCache';
|
|
30
33
|
import { ThemeLabel, themeOptionsByLabel } from '../themes';
|
|
31
34
|
import { AIAssist } from '../AIAssist';
|
|
35
|
+
import { typeScriptCompletions } from './typeScriptCompletions';
|
|
36
|
+
import { typeScriptLinter } from './typeScriptLinter';
|
|
37
|
+
import { TabState } from '../vzReducer';
|
|
38
|
+
|
|
39
|
+
// Feature flag to enable TypeScript completions & TypeScript Linter.
|
|
40
|
+
const enableTypeScriptCompletions = true;
|
|
41
|
+
const enableTypeScriptLinter = true;
|
|
42
|
+
|
|
43
|
+
// Enables TypeScript +JSX support in CodeMirror.
|
|
44
|
+
const tsx = () =>
|
|
45
|
+
javascript({ jsx: true, typescript: true });
|
|
32
46
|
|
|
33
47
|
// Language extensions for CodeMirror.
|
|
34
48
|
// Keys are file extensions.
|
|
35
49
|
// Values are CodeMirror extensions.
|
|
36
50
|
// TODO consider moving this to a separate file.
|
|
37
51
|
const languageExtensions = {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
json,
|
|
53
|
+
tsx,
|
|
54
|
+
js: tsx,
|
|
55
|
+
jsx: tsx,
|
|
56
|
+
ts: tsx,
|
|
57
|
+
html,
|
|
58
|
+
css,
|
|
44
59
|
md: markdown,
|
|
45
60
|
};
|
|
46
61
|
|
|
@@ -69,6 +84,8 @@ export const getOrCreateEditor = ({
|
|
|
69
84
|
usernameRef,
|
|
70
85
|
aiAssistEndpoint,
|
|
71
86
|
aiAssistOptions,
|
|
87
|
+
typeScriptWorker,
|
|
88
|
+
tabList,
|
|
72
89
|
}: {
|
|
73
90
|
fileId: FileId;
|
|
74
91
|
|
|
@@ -92,6 +109,8 @@ export const getOrCreateEditor = ({
|
|
|
92
109
|
aiAssistOptions?: {
|
|
93
110
|
[key: string]: any;
|
|
94
111
|
};
|
|
112
|
+
typeScriptWorker: Worker;
|
|
113
|
+
tabList: Array<TabState>;
|
|
95
114
|
}): EditorCacheValue => {
|
|
96
115
|
// Cache hit
|
|
97
116
|
if (editorCache.has(fileId)) {
|
|
@@ -209,12 +228,41 @@ export const getOrCreateEditor = ({
|
|
|
209
228
|
|
|
210
229
|
extensions.push(
|
|
211
230
|
AIAssist({
|
|
231
|
+
shareDBDoc,
|
|
212
232
|
fileId,
|
|
233
|
+
tabList,
|
|
213
234
|
aiAssistEndpoint,
|
|
214
235
|
aiAssistOptions,
|
|
215
236
|
}),
|
|
216
237
|
);
|
|
217
238
|
|
|
239
|
+
// Add the extension that provides TypeScript completions.
|
|
240
|
+
if (enableTypeScriptCompletions) {
|
|
241
|
+
extensions.push(
|
|
242
|
+
autocompletion({
|
|
243
|
+
override: [
|
|
244
|
+
typeScriptCompletions({
|
|
245
|
+
typeScriptWorker,
|
|
246
|
+
fileName: name,
|
|
247
|
+
}),
|
|
248
|
+
],
|
|
249
|
+
}),
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
if (enableTypeScriptLinter) {
|
|
253
|
+
extensions.push(
|
|
254
|
+
linter(
|
|
255
|
+
typeScriptLinter({
|
|
256
|
+
typeScriptWorker,
|
|
257
|
+
fileName: name,
|
|
258
|
+
shareDBDoc,
|
|
259
|
+
fileId,
|
|
260
|
+
}) as unknown as () => Diagnostic[],
|
|
261
|
+
//Needs the unknown because we are returning a Promise<Diagnostic>
|
|
262
|
+
),
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
218
266
|
const editor = new EditorView({
|
|
219
267
|
state: EditorState.create({
|
|
220
268
|
doc: text,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from '../useEditorCache';
|
|
18
18
|
import { getOrCreateEditor } from './getOrCreateEditor';
|
|
19
19
|
import './style.scss';
|
|
20
|
+
import { TabState } from '../vzReducer';
|
|
20
21
|
|
|
21
22
|
export const CodeEditor = ({
|
|
22
23
|
activeFileId,
|
|
@@ -32,6 +33,8 @@ export const CodeEditor = ({
|
|
|
32
33
|
username,
|
|
33
34
|
aiAssistEndpoint,
|
|
34
35
|
aiAssistOptions,
|
|
36
|
+
typeScriptWorker,
|
|
37
|
+
tabList,
|
|
35
38
|
}: {
|
|
36
39
|
activeFileId: FileId;
|
|
37
40
|
shareDBDoc: ShareDBDoc<VZCodeContent> | null;
|
|
@@ -62,6 +65,9 @@ export const CodeEditor = ({
|
|
|
62
65
|
aiAssistOptions?: {
|
|
63
66
|
[key: string]: string;
|
|
64
67
|
};
|
|
68
|
+
typeScriptWorker: Worker;
|
|
69
|
+
|
|
70
|
+
tabList: Array<TabState>;
|
|
65
71
|
}) => {
|
|
66
72
|
const ref = useRef<HTMLDivElement>(null);
|
|
67
73
|
|
|
@@ -111,6 +117,8 @@ export const CodeEditor = ({
|
|
|
111
117
|
usernameRef,
|
|
112
118
|
aiAssistEndpoint,
|
|
113
119
|
aiAssistOptions,
|
|
120
|
+
typeScriptWorker,
|
|
121
|
+
tabList,
|
|
114
122
|
}),
|
|
115
123
|
[
|
|
116
124
|
activeFileId,
|
|
@@ -124,6 +132,7 @@ export const CodeEditor = ({
|
|
|
124
132
|
usernameRef,
|
|
125
133
|
aiAssistEndpoint,
|
|
126
134
|
aiAssistOptions,
|
|
135
|
+
typeScriptWorker,
|
|
127
136
|
],
|
|
128
137
|
);
|
|
129
138
|
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
Decoration,
|
|
6
6
|
} from '@codemirror/view';
|
|
7
7
|
import { Annotation, RangeSet } from '@codemirror/state';
|
|
8
|
+
import ColorHash from 'color-hash';
|
|
9
|
+
import { Username } from '../../types';
|
|
8
10
|
|
|
9
11
|
// Deals with receiving the broadcasted presence cursor locations
|
|
10
12
|
// from other clients and displaying them.
|
|
@@ -50,8 +52,9 @@ export const json1PresenceDisplay = ({
|
|
|
50
52
|
const presence = presenceState[id];
|
|
51
53
|
const { start, end } = presence;
|
|
52
54
|
const from = start[start.length - 1];
|
|
53
|
-
// TODO support selection ranges (first attempt introduced layout errors)
|
|
54
55
|
const to = end[end.length - 1];
|
|
56
|
+
const userColor = new ColorHash().rgb(id);
|
|
57
|
+
const { username } = presence;
|
|
55
58
|
if (from === to) {
|
|
56
59
|
return {
|
|
57
60
|
from,
|
|
@@ -59,7 +62,11 @@ export const json1PresenceDisplay = ({
|
|
|
59
62
|
value: Decoration.widget({
|
|
60
63
|
side: -1,
|
|
61
64
|
block: false,
|
|
62
|
-
widget: new PresenceWidget(
|
|
65
|
+
widget: new PresenceWidget(
|
|
66
|
+
id,
|
|
67
|
+
userColor,
|
|
68
|
+
username,
|
|
69
|
+
),
|
|
63
70
|
}),
|
|
64
71
|
};
|
|
65
72
|
} else {
|
|
@@ -70,8 +77,8 @@ export const json1PresenceDisplay = ({
|
|
|
70
77
|
class: 'cm-json1-presence',
|
|
71
78
|
attributes: {
|
|
72
79
|
style: `
|
|
73
|
-
background-color: rgba(
|
|
74
|
-
|
|
80
|
+
background-color: rgba(${userColor}, 0.75);
|
|
81
|
+
mix-blend-mode: luminosity;
|
|
75
82
|
`,
|
|
76
83
|
},
|
|
77
84
|
}),
|
|
@@ -122,9 +129,17 @@ const pathMatches = (path, presence) => {
|
|
|
122
129
|
// Displays a single remote presence cursor.
|
|
123
130
|
class PresenceWidget extends WidgetType {
|
|
124
131
|
id: string;
|
|
125
|
-
|
|
132
|
+
color: string;
|
|
133
|
+
username: Username;
|
|
134
|
+
constructor(
|
|
135
|
+
id: string,
|
|
136
|
+
color: string,
|
|
137
|
+
username: Username,
|
|
138
|
+
) {
|
|
126
139
|
super();
|
|
127
140
|
this.id = id;
|
|
141
|
+
this.color = color;
|
|
142
|
+
this.username = username;
|
|
128
143
|
}
|
|
129
144
|
|
|
130
145
|
eq(other: PresenceWidget) {
|
|
@@ -135,14 +150,30 @@ class PresenceWidget extends WidgetType {
|
|
|
135
150
|
const span = document.createElement('span');
|
|
136
151
|
span.setAttribute('aria-hidden', 'true');
|
|
137
152
|
span.className = 'cm-json1-presence';
|
|
138
|
-
|
|
139
153
|
// This child is what actually displays the presence.
|
|
140
154
|
// Nested so that the layout is not impacted.
|
|
141
155
|
//
|
|
142
156
|
// The initial attempt using the top level span to render
|
|
143
157
|
// the cursor caused a wonky layout with adjacent characters shifting
|
|
144
158
|
// left and right by 1 pixel or so.
|
|
145
|
-
|
|
159
|
+
const div = document.createElement('div');
|
|
160
|
+
span.appendChild(div);
|
|
161
|
+
div.style.borderLeft = `1px solid rgba(${this.color})`;
|
|
162
|
+
|
|
163
|
+
// background color behind username
|
|
164
|
+
const userDiv = document.createElement('div');
|
|
165
|
+
userDiv.className = 'remote-cursor-username';
|
|
166
|
+
userDiv.style.top = `-20px`;
|
|
167
|
+
userDiv.style.height = `20px`;
|
|
168
|
+
userDiv.style.width = `${this.username.length * 11}px`;
|
|
169
|
+
userDiv.style.backgroundColor = `rgba(${this.color})`;
|
|
170
|
+
userDiv.style.color = `black`;
|
|
171
|
+
userDiv.style.textAlign = `center`;
|
|
172
|
+
userDiv.appendChild(
|
|
173
|
+
document.createTextNode(this.username),
|
|
174
|
+
);
|
|
175
|
+
span.appendChild(userDiv);
|
|
176
|
+
|
|
146
177
|
return span;
|
|
147
178
|
}
|
|
148
179
|
|
|
@@ -150,7 +181,6 @@ class PresenceWidget extends WidgetType {
|
|
|
150
181
|
return false;
|
|
151
182
|
}
|
|
152
183
|
}
|
|
153
|
-
|
|
154
184
|
const presenceTheme = EditorView.baseTheme({
|
|
155
185
|
'.cm-json1-presence': {
|
|
156
186
|
position: 'relative',
|
|
@@ -161,6 +191,5 @@ const presenceTheme = EditorView.baseTheme({
|
|
|
161
191
|
bottom: '0',
|
|
162
192
|
left: '0',
|
|
163
193
|
right: '0',
|
|
164
|
-
borderLeft: '1px solid yellow',
|
|
165
194
|
},
|
|
166
195
|
});
|