vzcode 1.2.0 → 1.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/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-Blj_0vZF.js"></script>
24
- <link rel="stylesheet" crossorigin href="/assets/index-CWMOj-be.css">
23
+ <script type="module" crossorigin src="/assets/index-wJsFLYuD.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.2.0",
3
+ "version": "1.4.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",
@@ -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.27.0",
110
+ "@codemirror/view": "^6.28.1",
99
111
  "@lezer/highlight": "^1.2.0",
100
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.2",
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,13 @@
124
136
  "json0-ot-diff": "^1.1.2",
125
137
  "ngrok": "^4.3.3",
126
138
  "open": "^10.1.0",
127
- "openai": "^4.48.2",
128
- "prettier-plugin-svelte": "^3.2.3",
139
+ "openai": "^4.51.0",
140
+ "prettier-plugin-svelte": "^3.2.4",
129
141
  "react": "^18.3.1",
130
142
  "react-bootstrap": "^2.10.2",
131
143
  "react-dom": "^18.3.1",
132
144
  "react-router-dom": "^6.23.1",
133
- "sharedb": "^5.0.1",
145
+ "sharedb": "^5.0.2",
134
146
  "sharedb-client-browser": "^5.0.1",
135
147
  "vizhub-ui": "^3.23.0",
136
148
  "ws": "^8.17.0"
@@ -138,16 +150,16 @@
138
150
  "devDependencies": {
139
151
  "@types/react": "^18.3.3",
140
152
  "@types/react-dom": "^18.3.0",
141
- "@vitejs/plugin-react": "^4.3.0",
153
+ "@vitejs/plugin-react": "^4.3.1",
142
154
  "concurrently": "^8.2.2",
143
- "prettier": "^3.3.1",
144
- "sass": "^1.77.4",
155
+ "prettier": "^3.3.2",
156
+ "sass": "^1.77.5",
145
157
  "typescript": "^5.4.5",
146
- "vite": "^5.2.12",
158
+ "vite": "^5.3.1",
147
159
  "vitest": "^1.6.0"
148
160
  },
149
161
  "optionalDependencies": {
150
- "@rollup/rollup-win32-x64-msvc": "^4.18.0",
151
- "@rollup/rollup-darwin-arm64": "^4.18.0"
162
+ "@rollup/rollup-darwin-arm64": "^4.18.0",
163
+ "@rollup/rollup-win32-x64-msvc": "^4.18.0"
152
164
  }
153
165
  }
@@ -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 '../CodeEditor/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({
@@ -35,9 +35,10 @@ export const CodeEditor = ({
35
35
  editorNoLongerWantsFocus,
36
36
  typeScriptWorker,
37
37
  theme,
38
+ codeEditorRef,
38
39
  } = useContext(VZCodeContext);
39
40
 
40
- const ref = useRef<HTMLDivElement>(null);
41
+ const ref = codeEditorRef;
41
42
 
42
43
  // Set `doc.data.isInteracting` to `true` when the user is interacting
43
44
  // via interactive code widgets (e.g. Alt+drag), and `false` when they are not.
@@ -138,5 +139,11 @@ export const CodeEditor = ({
138
139
  editorNoLongerWantsFocus,
139
140
  ]);
140
141
 
141
- return <div className="vz-code-editor" ref={ref}></div>;
142
+ return (
143
+ <div
144
+ className="vz-code-editor"
145
+ ref={ref}
146
+ tabIndex={-1}
147
+ ></div>
148
+ );
142
149
  };
@@ -27,6 +27,9 @@ export const json1PresenceDisplay = ({
27
27
  // See https://codemirror.net/6/docs/ref/#view.Decoration
28
28
  decorations: RangeSet<Decoration>;
29
29
 
30
+ //Added variable for cursor position
31
+ cursorPosition = {};
32
+
30
33
  constructor(view: EditorView) {
31
34
  // Initialize decorations to empty array so CodeMirror doesn't crash.
32
35
  this.decorations = RangeSet.of([]);
@@ -36,8 +39,18 @@ export const json1PresenceDisplay = ({
36
39
  // * Values are presence objects as defined by ot-json1-presence
37
40
  const presenceState = {};
38
41
 
42
+ // Add the scroll event listener
43
+ //This runs for the arrow key scrolling, it should result in the users scrolling to eachother's location.
44
+ view.dom.addEventListener('scroll', () => {
45
+ this.scrollToCursor(view);
46
+ });
39
47
  // Receive remote presence changes.
40
48
  docPresence.on('receive', (id, presence) => {
49
+ if (debug)
50
+ console.log(
51
+ `Received presence for id ${id}`,
52
+ presence,
53
+ ); // Debug statement
41
54
  // If presence === null, the user has disconnected / exited
42
55
  // We also check if the presence is for the current file or not.
43
56
  if (presence && pathMatches(path, presence)) {
@@ -97,6 +110,13 @@ export const json1PresenceDisplay = ({
97
110
  }),
98
111
  });
99
112
  }
113
+ if (view.state.doc.length >= from) {
114
+ // Ensure position is valid
115
+ this.cursorPosition[id] = from; // Store the cursor position, important to run if we cant get the regular scroll to work
116
+ // console.log(`Stored cursor position for id ${id}: ${from}`); // Debug statement
117
+ } else {
118
+ // console.warn(`Invalid cursor position for id ${id}: ${from}`); // Debug statement
119
+ }
100
120
  }
101
121
 
102
122
  this.decorations = Decoration.set(
@@ -116,8 +136,21 @@ export const json1PresenceDisplay = ({
116
136
  annotations: [presenceAnnotation.of(true)],
117
137
  });
118
138
  }, 0);
139
+ //call to scroll to the cursor of the other user
140
+ this.scrollToCursor(view);
119
141
  });
120
142
  }
143
+ // Method to scroll the view to keep the cursor in view
144
+ scrollToCursor(view) {
145
+ for (const id in this.cursorPosition) {
146
+ //getting the cursor position of the other cursor
147
+ const cursorPos = this.cursorPosition[id];
148
+ view.dispatch({
149
+ //if the other person's cursor has jumped off screen, we will follow it by scrolling there directly.
150
+ effects: EditorView.scrollIntoView(cursorPos),
151
+ });
152
+ }
153
+ }
121
154
  },
122
155
  {
123
156
  decorations: (v) => v.decorations,
@@ -0,0 +1,110 @@
1
+ import {
2
+ EditorView,
3
+ Decoration,
4
+ ViewPlugin,
5
+ } from '@codemirror/view';
6
+
7
+ function generateColors() {
8
+ return ['red', 'orange', 'yellow', 'green'];
9
+ }
10
+
11
+ const rainbowBracketsPlugin = ViewPlugin.fromClass(
12
+ class {
13
+ decorations;
14
+
15
+ constructor(view) {
16
+ this.decorations = this.getBracketDecorations(view);
17
+ }
18
+
19
+ update(update) {
20
+ if (
21
+ update.docChanged ||
22
+ update.selectionSet ||
23
+ update.viewportChanged
24
+ ) {
25
+ this.decorations = this.getBracketDecorations(
26
+ update.view,
27
+ );
28
+ }
29
+ }
30
+
31
+ getBracketDecorations(view) {
32
+ const { doc } = view.state;
33
+ const decorations = [];
34
+ const stack = [];
35
+ const colors = generateColors();
36
+
37
+ for (let pos = 0; pos < doc.length; pos += 1) {
38
+ const char = doc.sliceString(pos, pos + 1);
39
+ if (char === '(' || char === '[' || char === '{') {
40
+ stack.push({ type: char, from: pos });
41
+ } else if (
42
+ char === ')' ||
43
+ char === ']' ||
44
+ char === '}'
45
+ ) {
46
+ const open = stack.pop();
47
+ if (
48
+ open &&
49
+ open.type === this.getMatchingBracket(char)
50
+ ) {
51
+ const color =
52
+ colors[stack.length % colors.length];
53
+ decorations.push(
54
+ Decoration.mark({
55
+ class: `rainbow-bracket-${color}`,
56
+ }).range(open.from, open.from + 1),
57
+ Decoration.mark({
58
+ class: `rainbow-bracket-${color}`,
59
+ }).range(pos, pos + 1),
60
+ );
61
+ }
62
+ }
63
+ }
64
+
65
+ decorations.sort(
66
+ (a, b) =>
67
+ a.from - b.from || a.startSide - b.startSide,
68
+ );
69
+
70
+ return Decoration.set(decorations);
71
+ }
72
+
73
+ // eslint-disable-next-line class-methods-use-this
74
+ getMatchingBracket(closingBracket) {
75
+ switch (closingBracket) {
76
+ case ')':
77
+ return '(';
78
+ case ']':
79
+ return '[';
80
+ case '}':
81
+ return '{';
82
+ default:
83
+ return null;
84
+ }
85
+ }
86
+ },
87
+ {
88
+ decorations: (v) => v.decorations,
89
+ },
90
+ );
91
+
92
+ export default function rainbowBrackets() {
93
+ return [
94
+ rainbowBracketsPlugin,
95
+ EditorView.baseTheme({
96
+ '.rainbow-bracket-red': { color: 'white' },
97
+ '.rainbow-bracket-red > span': { color: 'white' },
98
+ '.rainbow-bracket-orange': { color: '#FFFFB2' },
99
+ '.rainbow-bracket-orange > span': {
100
+ color: '#FFFFB2',
101
+ },
102
+ '.rainbow-bracket-yellow': { color: '#FFB2DA' },
103
+ '.rainbow-bracket-yellow > span': {
104
+ color: '#FFB2DA',
105
+ },
106
+ '.rainbow-bracket-green': { color: '#B2FFB2' },
107
+ '.rainbow-bracket-green > span': { color: '#B2FFB2' },
108
+ }),
109
+ ];
110
+ }
@@ -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,10 @@ export type VZCodeContextValue = {
126
127
  >;
127
128
  runCodeRef: React.MutableRefObject<null | (() => void)>;
128
129
 
130
+ sidebarRef: React.RefObject<HTMLDivElement>;
131
+
132
+ codeEditorRef: React.RefObject<HTMLDivElement>;
133
+
129
134
  connected: boolean;
130
135
 
131
136
  hoveredItemId: ItemId | null;
@@ -174,6 +179,10 @@ export const VZCodeProvider = ({
174
179
 
175
180
  const runCodeRef = useRunCode(submitOperation);
176
181
 
182
+ const sidebarRef = useRef(null);
183
+
184
+ const codeEditorRef = useRef(null);
185
+
177
186
  // The error message shows either:
178
187
  // * `prettierError` - errors from Prettier, client-side only
179
188
  // * `codeError` - errors from an external source, such as
@@ -316,6 +325,9 @@ export const VZCodeProvider = ({
316
325
  setActiveFileRight,
317
326
  runPrettierRef,
318
327
  runCodeRef,
328
+ sidebarRef,
329
+ editorCache,
330
+ codeEditorRef,
319
331
  });
320
332
 
321
333
  // Track the currently hovered file id.
@@ -382,6 +394,8 @@ export const VZCodeProvider = ({
382
394
  handleCreateDirClick,
383
395
  runPrettierRef,
384
396
  runCodeRef,
397
+ sidebarRef,
398
+ codeEditorRef,
385
399
 
386
400
  connected,
387
401
 
@@ -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 {
@@ -26,7 +26,25 @@ export type ThemeOption = {
26
26
  value: any;
27
27
  label: ThemeLabel;
28
28
  };
29
-
29
+ // add light theme brackets to eclipse theme
30
+ // const modifiedEclipse = {
31
+ // ...eclipse,
32
+ // '.rainbow-bracket-red > span': {
33
+ // color: 'white',
34
+ // },
35
+ // '.rainbow-bracket-orange > span': {
36
+ // //yellow
37
+ // color: '#FFFFB2',
38
+ // },
39
+ // '.rainbow-bracket-yellow > span': {
40
+ // // orange
41
+ // color: '#FFB2DA',
42
+ // },
43
+ // '.rainbow-bracket-green > span': {
44
+ // // green
45
+ // color: '#B2FFB2',
46
+ // }
47
+ // };
30
48
  export const themes: Array<ThemeOption> = [
31
49
  { value: abcdef, label: 'abcdef' },
32
50
  { value: dracula, label: 'dracula' },