vzcode 1.1.0 → 1.3.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/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-BPHulZpL.js"></script>
24
- <link rel="stylesheet" crossorigin href="/assets/index-CWMOj-be.css">
23
+ <script type="module" crossorigin src="/assets/index-BnoPNwcv.js"></script>
24
+ <link rel="stylesheet" crossorigin href="/assets/index-ClPo_hzs.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.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -14,7 +14,7 @@
14
14
  "test-interactive": "cd test/sampleDirectories/kitchenSink && node ../../../src/server/index.js",
15
15
  "prettier": "prettier {*.*,**/*.*} --write",
16
16
  "tsc": "tsc --noEmit",
17
- "dev": "concurrently \"npm run test-interactive\" \"vite\"",
17
+ "dev": "EDITOR_PORT=5173 concurrently \"npm run test-interactive\" \"vite\"",
18
18
  "build": "vite build",
19
19
  "build-release": "vite build --mode release",
20
20
  "preview": "concurrently \"npm run test-interactive\" \"vite preview\"",
@@ -79,6 +79,18 @@
79
79
  {
80
80
  "name": "Nghia Minh Huynh",
81
81
  "url": "https://github.com/NghiaMinhHuynh"
82
+ },
83
+ {
84
+ "name": "Michael Halpern",
85
+ "url": "https://github.com/michaelhelper"
86
+ },
87
+ {
88
+ "name": "Pratheet Joshi",
89
+ "url": "https://github.com/DaCatDude"
90
+ },
91
+ {
92
+ "name": "Philip Park",
93
+ "url": "https://github.com/philippark"
82
94
  }
83
95
  ],
84
96
  "license": "MIT",
@@ -87,7 +99,7 @@
87
99
  },
88
100
  "homepage": "https://github.com/vizhub-core/vzcode#readme",
89
101
  "dependencies": {
90
- "@codemirror/autocomplete": "^6.16.0",
102
+ "@codemirror/autocomplete": "^6.16.2",
91
103
  "@codemirror/lang-css": "^6.2.1",
92
104
  "@codemirror/lang-html": "^6.4.9",
93
105
  "@codemirror/lang-javascript": "^6.2.2",
@@ -95,23 +107,23 @@
95
107
  "@codemirror/lang-markdown": "^6.2.5",
96
108
  "@codemirror/state": "^6.4.1",
97
109
  "@codemirror/theme-one-dark": "^6.1.2",
98
- "@codemirror/view": "^6.26.3",
110
+ "@codemirror/view": "^6.28.0",
99
111
  "@lezer/highlight": "^1.2.0",
100
- "@replit/codemirror-indentation-markers": "^6.5.1",
112
+ "@replit/codemirror-indentation-markers": "^6.5.2",
101
113
  "@replit/codemirror-interact": "^6.3.1",
102
114
  "@replit/codemirror-lang-svelte": "^6.0.0",
103
115
  "@replit/codemirror-vscode-keymap": "^6.0.2",
104
116
  "@teamwork/websocket-json-stream": "^2.0.0",
105
- "@typescript/vfs": "^1.5.0",
106
- "@uiw/codemirror-theme-abcdef": "^4.22.1",
107
- "@uiw/codemirror-theme-dracula": "^4.22.1",
108
- "@uiw/codemirror-theme-eclipse": "^4.22.1",
109
- "@uiw/codemirror-theme-github": "^4.22.1",
110
- "@uiw/codemirror-theme-material": "^4.22.1",
111
- "@uiw/codemirror-theme-nord": "^4.22.1",
112
- "@uiw/codemirror-theme-okaidia": "^4.22.1",
113
- "@uiw/codemirror-theme-xcode": "^4.22.1",
114
- "@uiw/codemirror-themes": "^4.22.1",
117
+ "@typescript/vfs": "^1.5.3",
118
+ "@uiw/codemirror-theme-abcdef": "^4.22.2",
119
+ "@uiw/codemirror-theme-dracula": "^4.22.2",
120
+ "@uiw/codemirror-theme-eclipse": "^4.22.2",
121
+ "@uiw/codemirror-theme-github": "^4.22.2",
122
+ "@uiw/codemirror-theme-material": "^4.22.2",
123
+ "@uiw/codemirror-theme-nord": "^4.22.2",
124
+ "@uiw/codemirror-theme-okaidia": "^4.22.2",
125
+ "@uiw/codemirror-theme-xcode": "^4.22.2",
126
+ "@uiw/codemirror-themes": "^4.22.2",
115
127
  "body-parser": "^1.20.2",
116
128
  "codemirror": "^6.0.1",
117
129
  "codemirror-ot": "^4.4.0",
@@ -124,13 +136,14 @@
124
136
  "json0-ot-diff": "^1.1.2",
125
137
  "ngrok": "^4.3.3",
126
138
  "open": "^10.1.0",
127
- "openai": "^4.47.1",
128
- "prettier-plugin-svelte": "^3.2.3",
139
+ "openai": "^4.50.0",
140
+ "prettier-plugin-svelte": "^3.2.4",
141
+ "rainbowbrackets": "^2.0.2",
129
142
  "react": "^18.3.1",
130
143
  "react-bootstrap": "^2.10.2",
131
144
  "react-dom": "^18.3.1",
132
145
  "react-router-dom": "^6.23.1",
133
- "sharedb": "^5.0.0",
146
+ "sharedb": "^5.0.2",
134
147
  "sharedb-client-browser": "^5.0.1",
135
148
  "vizhub-ui": "^3.23.0",
136
149
  "ws": "^8.17.0"
@@ -138,12 +151,16 @@
138
151
  "devDependencies": {
139
152
  "@types/react": "^18.3.3",
140
153
  "@types/react-dom": "^18.3.0",
141
- "@vitejs/plugin-react": "^4.3.0",
154
+ "@vitejs/plugin-react": "^4.3.1",
142
155
  "concurrently": "^8.2.2",
143
- "prettier": "^3.2.5",
144
- "sass": "^1.77.2",
156
+ "prettier": "^3.3.2",
157
+ "sass": "^1.77.4",
145
158
  "typescript": "^5.4.5",
146
- "vite": "^5.2.11",
159
+ "vite": "^5.2.13",
147
160
  "vitest": "^1.6.0"
161
+ },
162
+ "optionalDependencies": {
163
+ "@rollup/rollup-darwin-arm64": "^4.18.0",
164
+ "@rollup/rollup-win32-x64-msvc": "^4.18.0"
148
165
  }
149
166
  }
@@ -39,6 +39,7 @@ import { typeScriptLinter } from './typeScriptLinter';
39
39
  import { keymap } from '@codemirror/view';
40
40
  import { basicSetup } from './basicSetup';
41
41
  import { InteractRule } from '@replit/codemirror-interact';
42
+ import rainbowBrackets from 'rainbowbrackets';
42
43
 
43
44
  // Feature flag to enable TypeScript completions & TypeScript Linter.
44
45
  const enableTypeScriptCompletions = true;
@@ -166,7 +167,7 @@ export const getOrCreateEditor = ({
166
167
  );
167
168
  }
168
169
 
169
- // Deals with receiving the broadcas from other clients and displaying them.
170
+ // Deals with receiving the broadcast from other clients and displaying them.
170
171
  if (docPresence) {
171
172
  extensions.push(
172
173
  json1PresenceDisplay({
@@ -328,6 +329,8 @@ export const getOrCreateEditor = ({
328
329
  }),
329
330
  ),
330
331
  );
332
+ // adds rainbow brackets
333
+ extensions.push(rainbowBrackets());
331
334
 
332
335
  const editor = new EditorView({
333
336
  state: EditorState.create({
@@ -111,12 +111,17 @@ export const CodeEditor = ({
111
111
  if (!ref.current) return;
112
112
  if (!content) return;
113
113
 
114
- // Add the editor to the DOM.
114
+ // Add the editor and apply the prior scroll position to the DOM.
115
115
  ref.current.appendChild(editorCacheValue.editor.dom);
116
+ editorCacheValue.editor.scrollDOM.scrollTo({
117
+ top: editorCacheValue.scrollPosition ?? 0,
118
+ });
116
119
 
117
120
  return () => {
118
- // Remove the old editor from the DOM.
121
+ // Remove the old editor from the DOM and store the current scroll position.
119
122
  // This happens every time `activeFileId` changes.
123
+ editorCacheValue.scrollPosition =
124
+ editorCacheValue.editor.scrollDOM.scrollTop;
120
125
  ref.current.removeChild(editorCacheValue.editor.dom);
121
126
  };
122
127
  }, [shareDBDoc, editorCacheValue]);
@@ -56,7 +56,9 @@ export const json1PresenceDisplay = ({
56
56
  const { start, end } = presence;
57
57
  const from = start[start.length - 1];
58
58
  const to = end[end.length - 1];
59
- const userColor = new ColorHash().rgb(id);
59
+ const userColor = new ColorHash({
60
+ lightness: 0.75,
61
+ }).rgb(id);
60
62
  const { username } = presence;
61
63
 
62
64
  presenceDecorations.push({
@@ -3,6 +3,7 @@ import {
3
3
  useCallback,
4
4
  useReducer,
5
5
  useState,
6
+ useRef,
6
7
  } from 'react';
7
8
  import {
8
9
  FileId,
@@ -126,6 +127,8 @@ export type VZCodeContextValue = {
126
127
  >;
127
128
  runCodeRef: React.MutableRefObject<null | (() => void)>;
128
129
 
130
+ sidebarRef: React.RefObject<HTMLDivElement>;
131
+
129
132
  connected: boolean;
130
133
 
131
134
  hoveredItemId: ItemId | null;
@@ -174,6 +177,8 @@ export const VZCodeProvider = ({
174
177
 
175
178
  const runCodeRef = useRunCode(submitOperation);
176
179
 
180
+ const sidebarRef = useRef(null);
181
+
177
182
  // The error message shows either:
178
183
  // * `prettierError` - errors from Prettier, client-side only
179
184
  // * `codeError` - errors from an external source, such as
@@ -316,6 +321,8 @@ export const VZCodeProvider = ({
316
321
  setActiveFileRight,
317
322
  runPrettierRef,
318
323
  runCodeRef,
324
+ sidebarRef,
325
+ editorCache,
319
326
  });
320
327
 
321
328
  // Track the currently hovered file id.
@@ -382,6 +389,7 @@ export const VZCodeProvider = ({
382
389
  handleCreateDirClick,
383
390
  runPrettierRef,
384
391
  runCodeRef,
392
+ sidebarRef,
385
393
 
386
394
  connected,
387
395
 
@@ -46,6 +46,7 @@ export const VZSidebar = ({
46
46
  handleOpenCreateFileModal,
47
47
  handleOpenCreateDirModal,
48
48
  connected,
49
+ sidebarRef,
49
50
  } = useContext(VZCodeContext);
50
51
 
51
52
  const fileTree = useMemo(
@@ -102,7 +103,7 @@ export const VZSidebar = ({
102
103
  onDragLeave={handleDragLeave}
103
104
  onDrop={handleDrop}
104
105
  >
105
- <div className="files">
106
+ <div className="files" ref={sidebarRef} tabIndex={-1}>
106
107
  <div className="full-box">
107
108
  <div className="sidebar-section-hint">Files</div>
108
109
  <div className="sidebar-section-buttons">
@@ -20,6 +20,7 @@
20
20
  display: flex;
21
21
  flex-direction: column;
22
22
  flex: 1;
23
+ outline: none;
23
24
  }
24
25
 
25
26
  .file-or-directory {
@@ -190,6 +190,33 @@ function vizhubThemeInit(
190
190
  backgroundColor: 'transparent',
191
191
  outline: '1px solid #ff2222',
192
192
  },
193
+ '.rainbow-bracket-red > span': {
194
+ color: 'white',
195
+ },
196
+ '.rainbow-bracket-orange > span': {
197
+ // red
198
+ color: '#FFB2B2',
199
+ },
200
+ '.rainbow-bracket-yellow > span': {
201
+ // orange
202
+ color: '#FFB2DA',
203
+ },
204
+ '.rainbow-bracket-green > span': {
205
+ // yellow
206
+ color: '#FFFFB2',
207
+ },
208
+ '.rainbow-bracket-blue > span': {
209
+ // green
210
+ color: '#B2FFB2',
211
+ },
212
+ '.rainbow-bracket-indigo > span': {
213
+ // blue
214
+ color: '#B2FFFF',
215
+ },
216
+ '.rainbow-bracket-violet > span': {
217
+ // purple
218
+ color: '#DAB2FF',
219
+ }
193
220
  },
194
221
  { dark: true },
195
222
  ),
@@ -5,6 +5,7 @@ import { EditorView } from 'codemirror';
5
5
  export type EditorCacheValue = {
6
6
  editor: EditorView;
7
7
  themeCompartment: any;
8
+ scrollPosition?: number;
8
9
  };
9
10
 
10
11
  export type EditorCache = Map<FileId, EditorCacheValue>;
@@ -1,6 +1,154 @@
1
- import { useContext, useEffect } from 'react';
1
+ import { useEffect } from 'react';
2
2
  import { shouldTriggerRun } from './shouldTriggerRun';
3
- import { VZCodeContext } from './VZCodeContext';
3
+ import { EditorView } from 'codemirror';
4
+ import { syntaxTree } from '@codemirror/language';
5
+ import { SyntaxNode } from '@lezer/common';
6
+ import {
7
+ EditorState,
8
+ SelectionRange,
9
+ } from '@codemirror/state';
10
+
11
+ // Nesting types for the specific language
12
+ const nestingTypes = new Set<string>([
13
+ 'FunctionDeclaration',
14
+ 'ClassDeclaration',
15
+ 'MethodDeclaration',
16
+ 'Block',
17
+ 'IfStatement',
18
+ 'ForStatement',
19
+ 'WhileStatement',
20
+ 'SwitchStatement',
21
+ 'TryStatement',
22
+ 'CatchClause',
23
+ 'WithStatement',
24
+ 'ArrowFunction',
25
+ ]);
26
+
27
+ // Declaration types for the specific language
28
+ const declarationTypes = new Set<string>([
29
+ 'VariableDeclaration',
30
+ 'FunctionDeclaration',
31
+ 'ClassDeclaration',
32
+ 'MethodDeclaration',
33
+ 'PropertyDeclaration',
34
+ 'ImportDeclaration',
35
+ 'ExportDeclaration',
36
+ 'CallExpression',
37
+ 'ArrayPattern',
38
+ 'ObjectPattern',
39
+ 'PatternProperty',
40
+ 'ArgList',
41
+ 'TypeArgList',
42
+ 'ParamList',
43
+ 'ForOfSpec',
44
+ ]);
45
+
46
+ function getIdentifierContext(
47
+ identifier: SyntaxNode,
48
+ ): number {
49
+ let levels = 0;
50
+ let current: SyntaxNode = identifier;
51
+
52
+ // Traverse up the tree to find the total depth
53
+ while (current && current.type) {
54
+ const parentType: string = current.type.name;
55
+
56
+ if (nestingTypes.has(parentType)) {
57
+ levels++;
58
+ }
59
+
60
+ current = current.parent;
61
+ }
62
+
63
+ return levels;
64
+ }
65
+
66
+ function jumpToDefinition(editor: EditorView) {
67
+ // Use current editor state and selection range to find a potential definition
68
+ const state: EditorState = editor.state;
69
+ const location: SelectionRange = state.selection.main;
70
+ const definitions: Array<{
71
+ identifier: SyntaxNode;
72
+ context: number;
73
+ }> = [];
74
+
75
+ // Fetch the current syntax node information using cursor position
76
+ const identifier: SyntaxNode = syntaxTree(
77
+ state,
78
+ ).resolveInner(location.from, -1);
79
+ const identifierName: string = state.doc.sliceString(
80
+ identifier.from,
81
+ identifier.to,
82
+ );
83
+ const context: number = getIdentifierContext(identifier);
84
+
85
+ if (identifier) {
86
+ syntaxTree(state).iterate({
87
+ enter(tree) {
88
+ // Traverse syntax tree to find positions of respective identifier definitions within context
89
+ if (
90
+ declarationTypes.has(tree.name) ||
91
+ nestingTypes.has(tree.name)
92
+ ) {
93
+ const parent: SyntaxNode = tree.node;
94
+ const children: Array<SyntaxNode> = [
95
+ ...parent.getChildren('VariableDefinition'),
96
+ ...parent.getChildren('PropertyDefinition'),
97
+ ...parent.getChildren('PropertyName'),
98
+ ...parent.getChildren('Identifier'),
99
+ ];
100
+
101
+ children.forEach((child: SyntaxNode) => {
102
+ const name: string = state.doc.sliceString(
103
+ child.from,
104
+ child.to,
105
+ );
106
+
107
+ if (
108
+ name === identifierName &&
109
+ child !== identifier
110
+ ) {
111
+ definitions.push({
112
+ identifier: child,
113
+ context: getIdentifierContext(child),
114
+ });
115
+ }
116
+ });
117
+ }
118
+ },
119
+ });
120
+
121
+ if (definitions.length > 0) {
122
+ // Sort definitions by their context in the syntax tree
123
+ definitions.sort((a, b) => a.context - b.context);
124
+
125
+ let closestDefinition: SyntaxNode =
126
+ definitions[0].identifier;
127
+
128
+ for (let i = definitions.length - 1; i >= 0; i--) {
129
+ if (definitions[i].context <= context) {
130
+ closestDefinition = definitions[i].identifier;
131
+ break;
132
+ }
133
+ }
134
+
135
+ // Set the cursor position to the valid declaration in current scope
136
+ editor.dispatch({
137
+ selection: {
138
+ anchor: closestDefinition.from,
139
+ head: closestDefinition.to,
140
+ },
141
+ scrollIntoView: true,
142
+ effects: EditorView.scrollIntoView(
143
+ closestDefinition.from,
144
+ {
145
+ y: 'center',
146
+ },
147
+ ),
148
+ });
149
+ }
150
+ }
151
+ }
4
152
 
5
153
  // This module implements the keyboard shortcuts
6
154
  // for the VZCode editor.
@@ -18,6 +166,8 @@ export const useKeyboardShortcuts = ({
18
166
  setActiveFileRight,
19
167
  runPrettierRef,
20
168
  runCodeRef,
169
+ sidebarRef,
170
+ editorCache,
21
171
  }) => {
22
172
  useEffect(() => {
23
173
  const handleKeyPress = (event: KeyboardEvent) => {
@@ -32,12 +182,28 @@ export const useKeyboardShortcuts = ({
32
182
 
33
183
  // Run the code
34
184
  const runCode = runCodeRef.current;
185
+
35
186
  if (runCode !== null) {
36
187
  runCode();
37
188
  }
38
189
  return;
39
190
  }
40
191
 
192
+ if (event.ctrlKey) {
193
+ const editor: EditorView =
194
+ editorCache.get(activeFileId).editor;
195
+ const jumpToDefinitionHandler = () => {
196
+ jumpToDefinition(editor);
197
+ };
198
+
199
+ // CTRL + Click: Jump to relative definition
200
+ document.addEventListener(
201
+ 'mousedown',
202
+ jumpToDefinitionHandler,
203
+ { once: true },
204
+ );
205
+ }
206
+
41
207
  if (event.altKey === true) {
42
208
  // Alt-w: Close the current tab
43
209
  if (event.key === 'w') {
@@ -66,6 +232,14 @@ export const useKeyboardShortcuts = ({
66
232
  return;
67
233
  }
68
234
  }
235
+
236
+ if (event.ctrlKey == true) {
237
+ if (event.key === '0') {
238
+ if (sidebarRef.current) {
239
+ sidebarRef.current.focus();
240
+ }
241
+ }
242
+ }
69
243
  };
70
244
 
71
245
  document.addEventListener('keydown', handleKeyPress);
@@ -61,6 +61,17 @@ const LINT_ERROR_CODE_ASSIGNABLE_TO_NEVER = 2345;
61
61
  // Cannot find name 'd3'.
62
62
  export const LINT_ERROR_CODE_CANNOT_FIND_NAME = 2304;
63
63
 
64
+ // Type '{}' is missing the following properties from type '{ indx:
65
+ //Ignore specific TypeScript warning on object reassignment
66
+ const LINT_ERROR_CODE_OBJ_REASSINGMENT = 2739;
67
+
68
+ // Type 'any' is not assignable to type 'never'.
69
+ const LINT_ERROR_CODE_ANY_NOT_ASSIGNABLE_TO_NEVER = 2322;
70
+
71
+ // This code is for errors like:
72
+ // "Object is of type 'unknown'."
73
+ const LINT_ERROR_CODE_UNKNOWN = 18046;
74
+
64
75
  export const excludedErrorCodes = new Set([
65
76
  LINT_ERROR_CODE_ANY,
66
77
  LINT_ERROR_CODE_IMPORT,
@@ -70,4 +81,7 @@ export const excludedErrorCodes = new Set([
70
81
  LINT_ERROR_CODE_NON_EXISTENT_PROPERTY,
71
82
  LINT_ERROR_CODE_ITERATED_THROUGH,
72
83
  LINT_ERROR_CODE_ASSIGNABLE_TO_NEVER,
84
+ LINT_ERROR_CODE_OBJ_REASSINGMENT,
85
+ LINT_ERROR_CODE_ANY_NOT_ASSIGNABLE_TO_NEVER,
86
+ LINT_ERROR_CODE_UNKNOWN,
73
87
  ]);
@@ -1,17 +1,6 @@
1
1
  import * as tsvfs from '@typescript/vfs';
2
2
  import ts from 'typescript';
3
- import { File, Files, VZCodeContent } from '../../../types';
4
- import {
5
- AutocompleteRequest,
6
- AutocompleteResponse,
7
- LinterRequest,
8
- LinterResponse,
9
- } from './requestTypes';
10
- import {
11
- LINT_ERROR_CODE_CANNOT_FIND_NAME,
12
- compilerOptions,
13
- excludedErrorCodes,
14
- } from './constants';
3
+ import { compilerOptions } from './constants';
15
4
  import { handleMessageUpdateContent } from './handleMessageUpdateContent';
16
5
  import { handleMessageAutocompleteRequest } from './handleMessageAutocompleteRequest';
17
6
  import { handleMessageLintRequest } from './handleMessageLintRequest';
@@ -310,9 +310,12 @@ server.listen(port, async () => {
310
310
  open(url);
311
311
  })();
312
312
  } else {
313
- // console.log(
314
- // `Editor is live at http://localhost:${port}`,
315
- // );
316
- // open(`http://localhost:${port}`);
313
+ // Sets the port to the one specified in the environment
314
+ // variable (for development) or the default port.
315
+ let livePort = process.env.EDITOR_PORT || port;
316
+ console.log(
317
+ `Editor is live at http://localhost:${livePort}`,
318
+ );
319
+ open(`http://localhost:${livePort}`);
317
320
  }
318
321
  });