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/assets/{index-BmJyUPq5.js → index-Bx5B6Xgl.js} +45 -45
- package/dist/assets/{index-CWMOj-be.css → index-ClPo_hzs.css} +1 -1
- package/dist/assets/index-wJsFLYuD.js +156 -0
- package/dist/assets/{worker-DtIkBAuN.js → worker-DqNV0IYq.js} +206 -197
- package/dist/index.html +2 -2
- package/package.json +34 -22
- package/src/client/CodeEditor/getOrCreateEditor.ts +4 -1
- package/src/client/CodeEditor/index.tsx +9 -2
- package/src/client/CodeEditor/json1PresenceDisplay.ts +33 -0
- package/src/client/CodeEditor/rainbowBrackets.ts +110 -0
- package/src/client/VZCodeContext.tsx +14 -0
- package/src/client/VZSidebar/index.tsx +2 -1
- package/src/client/VZSidebar/styles.scss +1 -0
- package/src/client/themes/themes.ts +19 -1
- package/src/client/useKeyboardShortcuts.ts +297 -2
- package/src/client/useTypeScript/worker/constants.ts +14 -0
- package/src/client/useTypeScript/worker/index.ts +1 -12
- package/src/server/index.js +5 -2
- package/dist/assets/index-Blj_0vZF.js +0 -155
|
@@ -1,6 +1,154 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
2
|
import { shouldTriggerRun } from './shouldTriggerRun';
|
|
3
|
-
import {
|
|
3
|
+
import { syntaxTree } from '@codemirror/language';
|
|
4
|
+
import { SyntaxNode, SyntaxNodeRef } from '@lezer/common';
|
|
5
|
+
import { EditorView } from '@codemirror/view';
|
|
6
|
+
import { EditorState } from '@codemirror/state';
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
The following is a helpful resource for the following Code Mirror Syntax Tree methods below
|
|
10
|
+
https://lezer.codemirror.net/docs/ref/#common
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Store the element in the current editor DOM to highlight, indicating a potential jump to definition is possible
|
|
14
|
+
let activeJumpingElement: HTMLSpanElement = null;
|
|
15
|
+
|
|
16
|
+
// Store the syntax node representing the destination within the syntax tree
|
|
17
|
+
let definingNode: SyntaxNode = null;
|
|
18
|
+
|
|
19
|
+
// Example nesting types for the specific language to find level in the syntax tree
|
|
20
|
+
const nestingTypes = new Set<string>([
|
|
21
|
+
'FunctionDeclaration',
|
|
22
|
+
'ClassDeclaration',
|
|
23
|
+
'MethodDeclaration',
|
|
24
|
+
'Block',
|
|
25
|
+
'IfStatement',
|
|
26
|
+
'ForStatement',
|
|
27
|
+
'WhileStatement',
|
|
28
|
+
'SwitchStatement',
|
|
29
|
+
'TryStatement',
|
|
30
|
+
'CatchClause',
|
|
31
|
+
'WithStatement',
|
|
32
|
+
'ArrowFunction',
|
|
33
|
+
'ImportGroup',
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
// Example declaration types for the specific language to find definitions in the syntax tree
|
|
37
|
+
const declarationTypes = new Set<string>([
|
|
38
|
+
'VariableDeclaration',
|
|
39
|
+
'FunctionDeclaration',
|
|
40
|
+
'ClassDeclaration',
|
|
41
|
+
'MethodDeclaration',
|
|
42
|
+
'PropertyDeclaration',
|
|
43
|
+
'ImportDeclaration',
|
|
44
|
+
'ExportDeclaration',
|
|
45
|
+
'CallExpression',
|
|
46
|
+
'ArrayPattern',
|
|
47
|
+
'ObjectPattern',
|
|
48
|
+
'PatternProperty',
|
|
49
|
+
'ArgList',
|
|
50
|
+
'TypeArgList',
|
|
51
|
+
'ParamList',
|
|
52
|
+
'ForOfSpec',
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
function getIdentifierContext(
|
|
56
|
+
identifier: SyntaxNode,
|
|
57
|
+
): number {
|
|
58
|
+
let current: SyntaxNode = identifier;
|
|
59
|
+
let levels: number = 0;
|
|
60
|
+
|
|
61
|
+
// Traverse up the tree to find the total depth, only counting valid nesting types
|
|
62
|
+
while (current && current.type) {
|
|
63
|
+
const parentType: string = current.type.name;
|
|
64
|
+
|
|
65
|
+
if (nestingTypes.has(parentType)) {
|
|
66
|
+
levels++;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
current = current.parent;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return levels;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function jumpToDefinition(
|
|
76
|
+
editor: EditorView,
|
|
77
|
+
node: SyntaxNode,
|
|
78
|
+
): SyntaxNode {
|
|
79
|
+
const state: EditorState = editor.state;
|
|
80
|
+
const definitions: Array<{
|
|
81
|
+
identifier: SyntaxNode;
|
|
82
|
+
context: number;
|
|
83
|
+
}> = [];
|
|
84
|
+
|
|
85
|
+
// From an identifier in the syntax tree, fetch the name and context to find closest defining syntax node
|
|
86
|
+
const identifier: SyntaxNode = node;
|
|
87
|
+
const identifierName: string = state.doc.sliceString(
|
|
88
|
+
identifier.from,
|
|
89
|
+
identifier.to,
|
|
90
|
+
);
|
|
91
|
+
const context: number = getIdentifierContext(identifier);
|
|
92
|
+
|
|
93
|
+
if (identifier) {
|
|
94
|
+
syntaxTree(state).iterate({
|
|
95
|
+
enter(tree: SyntaxNodeRef) {
|
|
96
|
+
// Traverse syntax tree to find positions of respective identifier definitions within context
|
|
97
|
+
if (
|
|
98
|
+
declarationTypes.has(tree.name) ||
|
|
99
|
+
nestingTypes.has(tree.name)
|
|
100
|
+
) {
|
|
101
|
+
const parent: SyntaxNode = tree.node;
|
|
102
|
+
|
|
103
|
+
// Fetch a host of potential identifiers in an attempt to find the defining syntax node
|
|
104
|
+
const children: Array<SyntaxNode> = [
|
|
105
|
+
...parent.getChildren('VariableDefinition'),
|
|
106
|
+
...parent.getChildren('PropertyDefinition'),
|
|
107
|
+
...parent.getChildren('PropertyName'),
|
|
108
|
+
...parent.getChildren('Identifier'),
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
children.forEach((child: SyntaxNode) => {
|
|
112
|
+
const name: string = state.doc.sliceString(
|
|
113
|
+
child.from,
|
|
114
|
+
child.to,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// Ensure no jump to self by comparing syntax node id's
|
|
118
|
+
if (
|
|
119
|
+
name === identifierName &&
|
|
120
|
+
child.type.id !== identifier.type.id
|
|
121
|
+
) {
|
|
122
|
+
definitions.push({
|
|
123
|
+
identifier: child,
|
|
124
|
+
context: getIdentifierContext(child),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (definitions.length > 0) {
|
|
133
|
+
// Sort definitions by their context in the syntax tree
|
|
134
|
+
definitions.sort((a, b) => a.context - b.context);
|
|
135
|
+
|
|
136
|
+
let closestDefinition: SyntaxNode =
|
|
137
|
+
definitions[0].identifier;
|
|
138
|
+
|
|
139
|
+
for (let i = definitions.length - 1; i >= 0; i--) {
|
|
140
|
+
if (definitions[i].context <= context) {
|
|
141
|
+
closestDefinition = definitions[i].identifier;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return closestDefinition;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
4
152
|
|
|
5
153
|
// This module implements the keyboard shortcuts
|
|
6
154
|
// for the VZCode editor.
|
|
@@ -10,6 +158,7 @@ import { VZCodeContext } from './VZCodeContext';
|
|
|
10
158
|
// * Alt-PageUp: Change the active tab to the previous one
|
|
11
159
|
// * Alt-PageDown: Change the active tab to the next one
|
|
12
160
|
// * Ctrl-s or Shift-Enter: Run the code and format it with Prettier
|
|
161
|
+
// * Ctrl-Click: Jump to closest definition for a potential identifier
|
|
13
162
|
export const useKeyboardShortcuts = ({
|
|
14
163
|
closeTabs,
|
|
15
164
|
activeFileId,
|
|
@@ -18,6 +167,9 @@ export const useKeyboardShortcuts = ({
|
|
|
18
167
|
setActiveFileRight,
|
|
19
168
|
runPrettierRef,
|
|
20
169
|
runCodeRef,
|
|
170
|
+
sidebarRef,
|
|
171
|
+
editorCache,
|
|
172
|
+
codeEditorRef,
|
|
21
173
|
}) => {
|
|
22
174
|
useEffect(() => {
|
|
23
175
|
const handleKeyPress = (event: KeyboardEvent) => {
|
|
@@ -32,12 +184,21 @@ export const useKeyboardShortcuts = ({
|
|
|
32
184
|
|
|
33
185
|
// Run the code
|
|
34
186
|
const runCode = runCodeRef.current;
|
|
187
|
+
|
|
35
188
|
if (runCode !== null) {
|
|
36
189
|
runCode();
|
|
37
190
|
}
|
|
38
191
|
return;
|
|
39
192
|
}
|
|
40
193
|
|
|
194
|
+
if (event.ctrlKey === true) {
|
|
195
|
+
// On holding CTRL key, search for a potential definition jump using mouse location
|
|
196
|
+
document.addEventListener(
|
|
197
|
+
'mouseover',
|
|
198
|
+
handleMouseOver,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
41
202
|
if (event.altKey === true) {
|
|
42
203
|
// Alt-w: Close the current tab
|
|
43
204
|
if (event.key === 'w') {
|
|
@@ -65,15 +226,149 @@ export const useKeyboardShortcuts = ({
|
|
|
65
226
|
setActiveFileRight();
|
|
66
227
|
return;
|
|
67
228
|
}
|
|
229
|
+
|
|
230
|
+
if (event.key === '1') {
|
|
231
|
+
if (sidebarRef.current) {
|
|
232
|
+
sidebarRef.current.focus();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (event.key === '2') {
|
|
237
|
+
if (codeEditorRef.current) {
|
|
238
|
+
codeEditorRef.current.focus();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const resetActiveJumpingElement = (): void => {
|
|
245
|
+
if (activeJumpingElement) {
|
|
246
|
+
activeJumpingElement.style.cursor = 'initial';
|
|
247
|
+
activeJumpingElement.style.textDecoration = 'none';
|
|
248
|
+
activeJumpingElement = definingNode = null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
document.removeEventListener(
|
|
252
|
+
'mouseover',
|
|
253
|
+
handleMouseOver,
|
|
254
|
+
);
|
|
255
|
+
document.removeEventListener(
|
|
256
|
+
'mousedown',
|
|
257
|
+
jumpToDefinitionHandler,
|
|
258
|
+
);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const jumpToDefinitionHandler = (
|
|
262
|
+
event: MouseEvent,
|
|
263
|
+
): void => {
|
|
264
|
+
// Ensure the current destination node is defined and current cursor position matches highlighted element
|
|
265
|
+
if (
|
|
266
|
+
!definingNode ||
|
|
267
|
+
(event.target as HTMLSpanElement) !==
|
|
268
|
+
activeJumpingElement
|
|
269
|
+
) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Move current cursor and center view in the editor to destination node
|
|
274
|
+
const editor: EditorView =
|
|
275
|
+
editorCache.get(activeFileId).editor;
|
|
276
|
+
const closestDefinition: SyntaxNode = definingNode;
|
|
277
|
+
|
|
278
|
+
editor.dispatch({
|
|
279
|
+
selection: {
|
|
280
|
+
anchor: closestDefinition.from,
|
|
281
|
+
head: closestDefinition.to,
|
|
282
|
+
},
|
|
283
|
+
scrollIntoView: true,
|
|
284
|
+
effects: EditorView.scrollIntoView(
|
|
285
|
+
closestDefinition.from,
|
|
286
|
+
{
|
|
287
|
+
y: 'center',
|
|
288
|
+
},
|
|
289
|
+
),
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
resetActiveJumpingElement();
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const handleKeyRelease = (event: KeyboardEvent) => {
|
|
296
|
+
// On releasing CTRL key, reset all active definition jumping elements and listeners
|
|
297
|
+
if (!event.ctrlKey) {
|
|
298
|
+
resetActiveJumpingElement();
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const handleMouseOver = (event: MouseEvent) => {
|
|
303
|
+
const editor: EditorView =
|
|
304
|
+
editorCache.get(activeFileId).editor;
|
|
305
|
+
const tree = syntaxTree(editor.state);
|
|
306
|
+
const element = event.target as HTMLSpanElement;
|
|
307
|
+
|
|
308
|
+
// Ensure the identifier element can be found and is within the current editor DOM
|
|
309
|
+
if (
|
|
310
|
+
element == null ||
|
|
311
|
+
!editor.dom.contains(element)
|
|
312
|
+
) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const position: number = editor.posAtDOM(element);
|
|
317
|
+
const identifier: SyntaxNode = tree.resolveInner(
|
|
318
|
+
position,
|
|
319
|
+
1,
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
// All valid identifiers must be span elements to find a potential defining jump in editor
|
|
323
|
+
if (
|
|
324
|
+
identifier &&
|
|
325
|
+
element instanceof HTMLSpanElement
|
|
326
|
+
) {
|
|
327
|
+
const potentialJump: SyntaxNode = jumpToDefinition(
|
|
328
|
+
editor,
|
|
329
|
+
identifier,
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
// Only allowing to jump to other definition nodes
|
|
333
|
+
if (
|
|
334
|
+
potentialJump &&
|
|
335
|
+
identifier.type.id !== potentialJump.type.id
|
|
336
|
+
) {
|
|
337
|
+
if (activeJumpingElement) {
|
|
338
|
+
activeJumpingElement.style.cursor = 'initial';
|
|
339
|
+
activeJumpingElement.style.textDecoration =
|
|
340
|
+
'none';
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
activeJumpingElement = element;
|
|
344
|
+
definingNode = potentialJump;
|
|
345
|
+
|
|
346
|
+
activeJumpingElement.style.cursor = 'pointer';
|
|
347
|
+
activeJumpingElement.style.textDecoration =
|
|
348
|
+
'underline';
|
|
349
|
+
|
|
350
|
+
// CTRL + Click: Jump to relative definition, which is removed on CTRL key release
|
|
351
|
+
document.addEventListener(
|
|
352
|
+
'mousedown',
|
|
353
|
+
jumpToDefinitionHandler,
|
|
354
|
+
{ once: true },
|
|
355
|
+
);
|
|
356
|
+
}
|
|
68
357
|
}
|
|
69
358
|
};
|
|
70
359
|
|
|
71
360
|
document.addEventListener('keydown', handleKeyPress);
|
|
361
|
+
document.addEventListener('keyup', handleKeyRelease);
|
|
362
|
+
|
|
72
363
|
return () => {
|
|
73
364
|
document.removeEventListener(
|
|
74
365
|
'keydown',
|
|
75
366
|
handleKeyPress,
|
|
76
367
|
);
|
|
368
|
+
document.removeEventListener(
|
|
369
|
+
'keyup',
|
|
370
|
+
handleKeyRelease,
|
|
371
|
+
);
|
|
77
372
|
};
|
|
78
373
|
}, [
|
|
79
374
|
handleOpenCreateFileModal,
|
|
@@ -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 {
|
|
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';
|
package/src/server/index.js
CHANGED
|
@@ -310,9 +310,12 @@ server.listen(port, async () => {
|
|
|
310
310
|
open(url);
|
|
311
311
|
})();
|
|
312
312
|
} else {
|
|
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;
|
|
313
316
|
console.log(
|
|
314
|
-
`Editor is live at http://localhost:${
|
|
317
|
+
`Editor is live at http://localhost:${livePort}`,
|
|
315
318
|
);
|
|
316
|
-
open(`http://localhost:${
|
|
319
|
+
open(`http://localhost:${livePort}`);
|
|
317
320
|
}
|
|
318
321
|
});
|