vzcode 2.18.0 → 2.21.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.
@@ -67,8 +67,10 @@ import {
67
67
  } from '@valtown/codemirror-ts';
68
68
  import { getFileExtension } from '../utils/fileExtension';
69
69
  import { SparklesSVG } from '../Icons/SparklesSVG';
70
+ import { MINIMAL_EXTENSIONS } from '../featureFlags';
70
71
 
71
72
  const DEBUG = false;
73
+ const enableToDoPlugin = false;
72
74
 
73
75
  // Define a StateField to store the file name.
74
76
  // This should be defined at the module level if it's to be imported by other modules.
@@ -253,8 +255,10 @@ export const getOrCreateEditor = async ({
253
255
  // Create a compartment for rainbow brackets so that it can be enabled/disabled dynamically.
254
256
  const rainbowBracketsCompartment = new Compartment();
255
257
 
258
+ // Create a compartment for language so that it can be changed dynamically.
259
+ const languageCompartment = new Compartment();
260
+
256
261
  // The CodeMirror extensions to use.
257
- // const extensions = [autocompletion(), html(htmlConfig)]
258
262
  const extensions = [];
259
263
 
260
264
  // Initialize the fileNameStateField with the actual file name
@@ -273,26 +277,29 @@ export const getOrCreateEditor = async ({
273
277
  }),
274
278
  );
275
279
 
276
- // Deals with broadcasting changes in cursor location and selection.
277
- if (localPresence) {
278
- extensions.push(
279
- json1PresenceBroadcast({
280
- path: textPath,
281
- localPresence,
282
- usernameRef,
283
- }),
284
- );
285
- }
280
+ // Only add presence extensions if MINIMAL_EXTENSIONS is false
281
+ if (!MINIMAL_EXTENSIONS) {
282
+ // Deals with broadcasting changes in cursor location and selection.
283
+ if (localPresence) {
284
+ extensions.push(
285
+ json1PresenceBroadcast({
286
+ path: textPath,
287
+ localPresence,
288
+ usernameRef,
289
+ }),
290
+ );
291
+ }
286
292
 
287
- // Deals with receiving the broadcast from other clients and displaying them.
288
- if (docPresence) {
289
- extensions.push(
290
- json1PresenceDisplay({
291
- path: textPath,
292
- docPresence,
293
- enableAutoFollowRef,
294
- }),
295
- );
293
+ // Deals with receiving the broadcast from other clients and displaying them.
294
+ if (docPresence) {
295
+ extensions.push(
296
+ json1PresenceDisplay({
297
+ path: textPath,
298
+ docPresence,
299
+ enableAutoFollowRef,
300
+ }),
301
+ );
302
+ }
296
303
  }
297
304
  } else {
298
305
  // If the ShareDB document is not provided,
@@ -300,44 +307,7 @@ export const getOrCreateEditor = async ({
300
307
  extensions.push(EditorView.editable.of(false));
301
308
  }
302
309
 
303
- extensions.push(colorsInTextPlugin);
304
-
305
- // This is the "basic setup" for CodeMirror,
306
- // which actually adds a ton on functionality.
307
- // TODO vet this functionality, and determine how much
308
- // we want to replace with
309
- // https://github.com/vizhub-core/vzcode/issues/134
310
- extensions.push(basicSetup);
311
-
312
- if (esLintSource) {
313
- extensions.push(lintGutter()); // Show lint icons in the gutter
314
- extensions.push(
315
- linter(esLintSource, {
316
- // You can configure linter options here, e.g., delay
317
- delay: 750,
318
- }),
319
- );
320
- }
321
-
322
- // This supports dynamic changing of the theme.
323
- extensions.push(
324
- themeCompartment.of(themeOptionsByLabel[theme].value),
325
- );
326
-
327
- // Adds compartment for rainbow brackets with initial toggle state.
328
- extensions.push(
329
- rainbowBracketsCompartment.of(
330
- rainbowBracketsEnabled ? rainbowBrackets() : [],
331
- ),
332
- );
333
-
334
- // TODO handle dynamic changing of the file extension.
335
- // TODO handle dynamic file extensions by making
336
- // the CodeMirror language extension dynamic
337
- // using a Compartment.
338
- const languageCompartment = new Compartment();
339
- // See https://github.com/vizhub-core/vzcode/issues/55
340
-
310
+ // Add language extension (needed for both minimal and full modes)
341
311
  const languageExtension =
342
312
  getLanguageExtension(fileExtension);
343
313
  if (languageExtension) {
@@ -345,232 +315,269 @@ export const getOrCreateEditor = async ({
345
315
  languageCompartment.of(languageExtension),
346
316
  );
347
317
  } else {
348
- // Not sure if this case even works.
349
- // TODO manually test this case by creating a file
350
- // that has no extension, opening it up,
351
- // and then adding an extension.
352
- // console.warn(
353
- // `No language extension for file extension: ${fileExtension}`,
354
- // );
355
- // We still need to push the compartment,
356
- // otherwise the compartment won't work when
357
- // a file extension _is_ added later on.
358
318
  extensions.push(languageCompartment.of([]));
359
319
  }
360
320
 
361
- // Enable line wrapping for Markdown files
362
- if (fileExtension === 'md') {
363
- extensions.push(EditorView.lineWrapping);
364
- }
321
+ // If MINIMAL_EXTENSIONS is true, only include the JSON1 OT extension and essential functionality
322
+ if (MINIMAL_EXTENSIONS) {
323
+ // Only the most basic extensions are added
324
+ // The JSON1 OT extension and language extension are already added above
325
+ } else {
326
+ // Full feature set - include all extensions
327
+ extensions.push(colorsInTextPlugin);
328
+
329
+ // This is the "basic setup" for CodeMirror,
330
+ // which actually adds a ton on functionality.
331
+ // TODO vet this functionality, and determine how much
332
+ // we want to replace with
333
+ // https://github.com/vizhub-core/vzcode/issues/134
334
+ extensions.push(basicSetup);
335
+
336
+ if (esLintSource) {
337
+ extensions.push(lintGutter()); // Show lint icons in the gutter
338
+ extensions.push(
339
+ linter(esLintSource, {
340
+ // You can configure linter options here, e.g., delay
341
+ delay: 750,
342
+ }),
343
+ );
344
+ }
365
345
 
366
- // Add interactive widgets.
367
- // Includes the Alt+drag functionality for numbers.
368
- // Calls `onInteract` when one of those widgets is interacted with.
369
- // This can be used to trigger a transition to throttled mode
370
- // for hot reloading.
371
- // TODO consider leveraging the new `dragEnd` event handler.
372
- // and removing the `onInteract` callback, replacing it with
373
- // `onInteractStart` and `onInteractEnd`.
374
- // That may be tricky for one-off interactions though, like
375
- // the boolean checkboxes. The color pickers are also tricky,
376
- // as they would also need to be able to handle `onInteractEnd`.
377
- // See https://github.com/replit/codemirror-interact/issues/14
378
- extensions.push(
379
- widgets({ onInteract, customInteractRules }),
380
- );
346
+ // This supports dynamic changing of the theme.
347
+ extensions.push(
348
+ themeCompartment.of(themeOptionsByLabel[theme].value),
349
+ );
381
350
 
382
- // TODO fix the bugginess in this one where
383
- // the highlight persists after the mouse leaves.
384
- // extensions.push(highlightWidgets);
385
-
386
- extensions.push(rotationIndicator);
387
-
388
- // extensions.push(
389
- // AIAssistCodeMirrorKeyMap({
390
- // shareDBDoc,
391
- // fileId,
392
- // tabList,
393
- // aiAssistEndpoint,
394
- // aiAssistOptions,
395
- // }),
396
- // );
397
-
398
- // Add the extension that provides indentation markers.
399
- extensions.push(
400
- indentationMarkers({
401
- // thickness: 2,
402
- colors: {
403
- light: '#4d586b',
404
- dark: '#4d586b',
405
- activeLight: '#8e949f',
406
- activeDark: '#8e949f',
407
- },
408
- }),
409
- );
351
+ // Adds compartment for rainbow brackets with initial toggle state.
352
+ extensions.push(
353
+ rainbowBracketsCompartment.of(
354
+ rainbowBracketsEnabled ? rainbowBrackets() : [],
355
+ ),
356
+ );
410
357
 
411
- if (name.endsWith('ts') || name.endsWith('tsx')) {
412
- // Initialize worker if needed
413
- const tsWorker = await initializeWorker();
358
+ // Enable line wrapping for Markdown files
359
+ if (fileExtension === 'md') {
360
+ extensions.push(EditorView.lineWrapping);
361
+ }
414
362
 
363
+ // Add interactive widgets.
364
+ // Includes the Alt+drag functionality for numbers.
365
+ // Calls `onInteract` when one of those widgets is interacted with.
366
+ // This can be used to trigger a transition to throttled mode
367
+ // for hot reloading.
368
+ // TODO consider leveraging the new `dragEnd` event handler.
369
+ // and removing the `onInteract` callback, replacing it with
370
+ // `onInteractStart` and `onInteractEnd`.
371
+ // That may be tricky for one-off interactions though, like
372
+ // the boolean checkboxes. The color pickers are also tricky,
373
+ // as they would also need to be able to handle `onInteractEnd`.
374
+ // See https://github.com/replit/codemirror-interact/issues/14
415
375
  extensions.push(
416
- ...[
417
- tsFacetWorker.of({ worker: tsWorker, path: name }),
418
- tsSyncWorker(),
419
- tsLinterWorker(),
420
- autocompletion({
421
- override: [tsAutocompleteWorker()],
422
- }),
423
- tsHoverWorker(),
424
- ],
376
+ widgets({ onInteract, customInteractRules }),
425
377
  );
426
- }
427
378
 
428
- // Show the minimap
429
- // See https://github.com/replit/codemirror-minimap#usage
430
- // This extension has poor performance, so it's disabled for now.
431
- // extensions.push(
432
- // showMinimap.compute(['doc'], () => ({
433
- // create: () => ({
434
- // dom: document.createElement('div'),
435
- // }),
436
- // // Without this, performance is terrible.
437
- // displayText: 'blocks',
438
- // })),
439
- // );
440
-
441
- // VSCode keybindings
442
- // See https://github.com/replit/codemirror-vscode-keymap#usage
443
- // extensions.push(keymap.of(vscodeKeymap));
444
- extensions.push(
445
- keymap.of(
446
- vscodeKeymap.map((binding) => {
447
- // Here we override the Shift+Enter behavior specifically,
448
- // as that can be used to trigger a manual save/Prettier,
449
- // and the default behavior from the keymap interferes.
450
- if (binding.key === 'Enter') {
451
- delete binding.shift;
452
- }
453
- return binding;
454
- }),
455
- ),
456
- );
379
+ // TODO fix the bugginess in this one where
380
+ // the highlight persists after the mouse leaves.
381
+ // extensions.push(highlightWidgets);
457
382
 
458
- // Adds copilot completions
459
- DEBUG &&
460
- console.log(
461
- '[getOrCreateEditor] aiCopilotEndpoint: ',
462
- aiCopilotEndpoint,
383
+ extensions.push(rotationIndicator);
384
+
385
+ // extensions.push(
386
+ // AIAssistCodeMirrorKeyMap({
387
+ // shareDBDoc,
388
+ // fileId,
389
+ // tabList,
390
+ // aiAssistEndpoint,
391
+ // aiAssistOptions,
392
+ // }),
393
+ // );
394
+
395
+ // Add the extension that provides indentation markers.
396
+ extensions.push(
397
+ indentationMarkers({
398
+ // thickness: 2,
399
+ colors: {
400
+ light: '#4d586b',
401
+ dark: '#4d586b',
402
+ activeLight: '#8e949f',
403
+ activeDark: '#8e949f',
404
+ },
405
+ }),
463
406
  );
464
- if (aiCopilotEndpoint) {
465
- extensions.push(copilot({ aiCopilotEndpoint }));
466
- }
467
407
 
468
- // const { setIsAIChatOpen } = useContext(VZCodeContext);
408
+ if (name.endsWith('ts') || name.endsWith('tsx')) {
409
+ // Initialize worker if needed
410
+ const tsWorker = await initializeWorker();
469
411
 
470
- // Widget appears after instances of "todo" in code editor, allowing AI to implement todo tasks when clicked
471
- function createToDoPlugin() {
472
- // setIsAIChatOpen: (open: boolean) => void,
473
- class ToDoWidget extends WidgetType {
474
- constructor() {
475
- super();
476
- }
412
+ extensions.push(
413
+ ...[
414
+ tsFacetWorker.of({
415
+ worker: tsWorker,
416
+ path: name,
417
+ }),
418
+ tsSyncWorker(),
419
+ tsLinterWorker(),
420
+ autocompletion({
421
+ override: [tsAutocompleteWorker()],
422
+ }),
423
+ tsHoverWorker(),
424
+ ],
425
+ );
426
+ }
477
427
 
478
- eq(_other: ToDoWidget) {
479
- return false;
480
- }
428
+ // Show the minimap
429
+ // See https://github.com/replit/codemirror-minimap#usage
430
+ // This extension has poor performance, so it's disabled for now.
431
+ // extensions.push(
432
+ // showMinimap.compute(['doc'], () => ({
433
+ // create: () => ({
434
+ // dom: document.createElement('div'),
435
+ // }),
436
+ // // Without this, performance is terrible.
437
+ // displayText: 'blocks',
438
+ // })),
439
+ // );
481
440
 
482
- toDOM() {
483
- const wrap = document.createElement('i');
484
- wrap.style.display = 'inline-flex'; // prevent block expansion
485
- wrap.style.alignItems = 'center';
486
- wrap.style.justifyContent = 'center';
487
- wrap.className = 'icon-button icon-button-dark';
488
- const reactContainer =
489
- document.createElement('div');
490
- wrap.appendChild(reactContainer);
491
-
492
- const root = createRoot(reactContainer);
493
- root.render(
494
- <div
495
- onClick={() => {
496
- setIsAIChatOpen(true);
497
- // setAIChatMessage('Implement the TODO');
498
- handleSendMessage('Implement the TODO');
499
- }}
500
- >
501
- <SparklesSVG width={14} height={14} />
502
- </div>,
503
- );
504
- return wrap;
505
- }
441
+ // VSCode keybindings
442
+ // See https://github.com/replit/codemirror-vscode-keymap#usage
443
+ // extensions.push(keymap.of(vscodeKeymap));
444
+ extensions.push(
445
+ keymap.of(
446
+ vscodeKeymap.map((binding) => {
447
+ // Here we override the Shift+Enter behavior specifically,
448
+ // as that can be used to trigger a manual save/Prettier,
449
+ // and the default behavior from the keymap interferes.
450
+ if (binding.key === 'Enter') {
451
+ delete binding.shift;
452
+ }
453
+ return binding;
454
+ }),
455
+ ),
456
+ );
506
457
 
507
- ignoreEvent() {
508
- return false;
509
- }
458
+ // Adds copilot completions
459
+ DEBUG &&
460
+ console.log(
461
+ '[getOrCreateEditor] aiCopilotEndpoint: ',
462
+ aiCopilotEndpoint,
463
+ );
464
+ if (aiCopilotEndpoint) {
465
+ extensions.push(copilot({ aiCopilotEndpoint }));
510
466
  }
511
467
 
512
- function toDoWidgets(editor: EditorView) {
513
- const { state } = editor;
514
- const tree = syntaxTree(state);
515
- const findToDo = new RegExpCursor(state.doc, 'todo', {
516
- ignoreCase: true,
517
- });
518
- const widgets: Range<Decoration>[] = [];
519
-
520
- while (!findToDo.next().done) {
521
- const { from, to } = findToDo.value;
522
- const node = tree.resolve(from);
523
- let inComment = false;
524
-
525
- // Walk up the parents until we hit the root, looking for a comment node
526
- for (let cur: any = node; cur; cur = cur.parent) {
527
- if (
528
- cur.type.is('Comment') || // grammars that group comments
529
- cur.type.is('comment') || // generic lowercase group
530
- /Comment$/.test(cur.type.name) // fallback for e.g. LineComment
531
- ) {
532
- inComment = true;
533
- break;
534
- }
468
+ // const { setIsAIChatOpen } = useContext(VZCodeContext);
469
+
470
+ // Widget appears after instances of "todo" in code editor, allowing AI to implement todo tasks when clicked
471
+ function createToDoPlugin() {
472
+ // setIsAIChatOpen: (open: boolean) => void,
473
+ class ToDoWidget extends WidgetType {
474
+ constructor() {
475
+ super();
535
476
  }
536
- if (!inComment) continue; // skip non-comment TODOs
537
477
 
538
- const deco = Decoration.widget({
539
- widget: new ToDoWidget(),
540
- side: 1,
541
- }).range(to);
542
- widgets.push(deco);
543
- }
544
- return Decoration.set(widgets);
545
- }
478
+ eq(_other: ToDoWidget) {
479
+ return false;
480
+ }
546
481
 
547
- const todoPlugin = ViewPlugin.fromClass(
548
- class {
549
- decorations: DecorationSet;
482
+ toDOM() {
483
+ const wrap = document.createElement('i');
484
+ wrap.style.display = 'inline-flex'; // prevent block expansion
485
+ wrap.style.alignItems = 'center';
486
+ wrap.style.justifyContent = 'center';
487
+ wrap.className = 'icon-button icon-button-dark';
488
+ const reactContainer =
489
+ document.createElement('div');
490
+ wrap.appendChild(reactContainer);
491
+
492
+ const root = createRoot(reactContainer);
493
+ root.render(
494
+ <div
495
+ onClick={() => {
496
+ setIsAIChatOpen(true);
497
+ // setAIChatMessage('Implement the TODO');
498
+ handleSendMessage('Implement the TODO');
499
+ }}
500
+ >
501
+ <SparklesSVG width={14} height={14} />
502
+ </div>,
503
+ );
504
+ return wrap;
505
+ }
550
506
 
551
- constructor(view: EditorView) {
552
- this.decorations = toDoWidgets(view);
507
+ ignoreEvent() {
508
+ return false;
553
509
  }
510
+ }
511
+
512
+ function toDoWidgets(editor: EditorView) {
513
+ const { state } = editor;
514
+ const tree = syntaxTree(state);
515
+ const findToDo = new RegExpCursor(
516
+ state.doc,
517
+ 'todo',
518
+ {
519
+ ignoreCase: true,
520
+ },
521
+ );
522
+ const widgets: Range<Decoration>[] = [];
523
+
524
+ while (!findToDo.next().done) {
525
+ const { from, to } = findToDo.value;
526
+ const node = tree.resolve(from);
527
+ let inComment = false;
528
+
529
+ // Walk up the parents until we hit the root, looking for a comment node
530
+ for (let cur: any = node; cur; cur = cur.parent) {
531
+ if (
532
+ cur.type.is('Comment') || // grammars that group comments
533
+ cur.type.is('comment') || // generic lowercase group
534
+ /Comment$/.test(cur.type.name) // fallback for e.g. LineComment
535
+ ) {
536
+ inComment = true;
537
+ break;
538
+ }
539
+ }
540
+ if (!inComment) continue; // skip non-comment TODOs
554
541
 
555
- update(update: ViewUpdate) {
556
- if (
557
- update.docChanged ||
558
- update.viewportChanged ||
559
- syntaxTree(update.startState) !==
560
- syntaxTree(update.state)
561
- )
562
- this.decorations = toDoWidgets(update.view);
542
+ const deco = Decoration.widget({
543
+ widget: new ToDoWidget(),
544
+ side: 1,
545
+ }).range(to);
546
+ widgets.push(deco);
563
547
  }
564
- },
565
- {
566
- decorations: (v) => v.decorations,
567
- },
568
- );
548
+ return Decoration.set(widgets);
549
+ }
569
550
 
570
- return todoPlugin;
571
- }
551
+ const todoPlugin = ViewPlugin.fromClass(
552
+ class {
553
+ decorations: DecorationSet;
554
+
555
+ constructor(view: EditorView) {
556
+ this.decorations = toDoWidgets(view);
557
+ }
572
558
 
573
- extensions.push(createToDoPlugin());
559
+ update(update: ViewUpdate) {
560
+ if (
561
+ update.docChanged ||
562
+ update.viewportChanged ||
563
+ syntaxTree(update.startState) !==
564
+ syntaxTree(update.state)
565
+ )
566
+ this.decorations = toDoWidgets(update.view);
567
+ }
568
+ },
569
+ {
570
+ decorations: (v) => v.decorations,
571
+ },
572
+ );
573
+
574
+ return todoPlugin;
575
+ }
576
+
577
+ if (enableToDoPlugin) {
578
+ extensions.push(createToDoPlugin());
579
+ }
580
+ }
574
581
 
575
582
  const editor = new EditorView({
576
583
  state: EditorState.create({
@@ -92,6 +92,10 @@ export const VZRight = () => {
92
92
  enableHotReloading: isInteracting,
93
93
  enableSourcemap: true,
94
94
  vizId: 'example-viz',
95
+
96
+ // Don't clear the console here in VZCode, since
97
+ // we often want to see debug logs across multiple runs.
98
+ clearConsole: false,
95
99
  });
96
100
  isFirstRunRef.current = false;
97
101
  }
@@ -168,7 +168,7 @@ const ChatInputComponent = ({
168
168
  <Form.Group className="ai-chat-input-group">
169
169
  <Form.Control
170
170
  as="textarea"
171
- rows={5}
171
+ rows={10}
172
172
  value={aiChatMessage}
173
173
  onChange={handleChange}
174
174
  onKeyDown={handleKeyDown}
@@ -11,7 +11,7 @@
11
11
  display: flex;
12
12
  align-items: center;
13
13
  gap: 8px;
14
- font-size: 12px;
14
+ font-size: 14px;
15
15
  color: var(--bs-secondary-color);
16
16
 
17
17
  .files-changed {
@@ -134,4 +134,40 @@
134
134
  background-color: var(--d2h-bg-color);
135
135
  }
136
136
  }
137
+
138
+ .deleted-file {
139
+ margin-bottom: 8px;
140
+ border: 1px solid #30363d;
141
+ border-radius: 6px;
142
+ background-color: rgb(41, 44, 52);
143
+ overflow: hidden;
144
+
145
+ .deleted-file-header {
146
+ display: flex;
147
+ justify-content: space-between;
148
+ align-items: center;
149
+ padding: 12px 16px;
150
+ background-color: #161b22;
151
+ border-bottom: 1px solid #30363d;
152
+
153
+ .deleted-file-name {
154
+ color: #e6edf3;
155
+ font-weight: 600;
156
+ font-family:
157
+ 'SFMono-Regular', Consolas, 'Liberation Mono',
158
+ Menlo, monospace;
159
+ font-size: 14px;
160
+ }
161
+
162
+ .deleted-file-status {
163
+ color: #f85149;
164
+ font-weight: 600;
165
+ font-size: 14px;
166
+ background-color: rgba(248, 81, 73, 0.1);
167
+ padding: 4px 8px;
168
+ border-radius: 4px;
169
+ border: 1px solid rgba(248, 81, 73, 0.4);
170
+ }
171
+ }
172
+ }
137
173
  }