@nocturnium/svelte-ide 1.0.0 → 1.0.2

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.
Files changed (96) hide show
  1. package/README.md +43 -47
  2. package/dist/components/agents/AgentActivityPanel.svelte +3 -1
  3. package/dist/components/agents/AgentAvatar.svelte +127 -35
  4. package/dist/components/agents/AgentCursor.svelte +15 -5
  5. package/dist/components/agents/AgentPresenceBar.svelte +7 -2
  6. package/dist/components/ai/AIConversationList.svelte +39 -34
  7. package/dist/components/ai/AIInlineEdit.svelte +1 -3
  8. package/dist/components/ai/AIMessage.svelte +5 -5
  9. package/dist/components/ai/AIMessageActions.svelte +18 -3
  10. package/dist/components/ai/AIMessageContent.svelte +22 -20
  11. package/dist/components/ai/AIPanel.svelte +17 -9
  12. package/dist/components/ai/AISuggestionWidget.svelte +1 -3
  13. package/dist/components/ai/AIToolCallDisplay.svelte +10 -14
  14. package/dist/components/core/Badge.svelte +9 -1
  15. package/dist/components/core/ConnectionStatus.svelte +73 -68
  16. package/dist/components/core/ErrorBoundary.svelte +56 -56
  17. package/dist/components/core/ErrorBoundary.svelte.d.ts +5 -5
  18. package/dist/components/core/Icon.svelte +22 -11
  19. package/dist/components/core/ResizeHandle.svelte +1 -1
  20. package/dist/components/core/Tooltip.svelte +1 -7
  21. package/dist/components/editor/AIFocusLayer.svelte +15 -7
  22. package/dist/components/editor/Breadcrumbs.svelte +18 -6
  23. package/dist/components/editor/BreakpointLayer.svelte +51 -60
  24. package/dist/components/editor/CognitiveLoadMeter.svelte +4 -2
  25. package/dist/components/editor/CollaborativeEditor.svelte +1 -5
  26. package/dist/components/editor/CommandPalette.svelte +1 -4
  27. package/dist/components/editor/ComplexityLayer.svelte +8 -6
  28. package/dist/components/editor/ConflictZoneLayer.svelte +2 -8
  29. package/dist/components/editor/ContextLens.svelte +1 -4
  30. package/dist/components/editor/CustomEditor.svelte +85 -41
  31. package/dist/components/editor/DebugConsole.svelte +8 -17
  32. package/dist/components/editor/EchoCursorLayer.svelte +3 -1
  33. package/dist/components/editor/EditorGutter.svelte +8 -2
  34. package/dist/components/editor/EditorLines.svelte +6 -3
  35. package/dist/components/editor/EditorPane.svelte +1 -6
  36. package/dist/components/editor/EditorSelections.svelte +29 -11
  37. package/dist/components/editor/FileExplorer.svelte +26 -4
  38. package/dist/components/editor/FileIcon.svelte +3 -1
  39. package/dist/components/editor/FindReplace.svelte +16 -4
  40. package/dist/components/editor/GhostBracketLayer.svelte +2 -2
  41. package/dist/components/editor/GitBlameLayer.svelte +2 -1
  42. package/dist/components/editor/InlineDiagnosticsLayer.svelte +4 -13
  43. package/dist/components/editor/InlineDiffLayer.svelte +18 -9
  44. package/dist/components/editor/Minimap.svelte +16 -9
  45. package/dist/components/editor/PluginPreviewSandbox.svelte +3 -2
  46. package/dist/components/editor/ProblemsPanel.svelte +11 -35
  47. package/dist/components/editor/QuickActionsMenu.svelte +5 -14
  48. package/dist/components/editor/SnippetPalette.svelte +11 -12
  49. package/dist/components/editor/StructureMap.svelte +2 -1
  50. package/dist/components/editor/SymbolOutline.svelte +14 -19
  51. package/dist/components/editor/TimelineScrubber.svelte +7 -6
  52. package/dist/components/editor/core/complexity-analyzer.js +42 -12
  53. package/dist/components/editor/core/conflict-predictor.js +2 -4
  54. package/dist/components/editor/core/folding.d.ts +9 -0
  55. package/dist/components/editor/core/folding.js +40 -5
  56. package/dist/components/editor/core/multi-cursor.js +4 -8
  57. package/dist/components/editor/core/navigation.js +2 -6
  58. package/dist/components/editor/core/quick-actions.js +22 -17
  59. package/dist/components/editor/core/search.js +2 -6
  60. package/dist/components/editor/core/semantic-analyzer.js +1 -3
  61. package/dist/components/editor/core/snippet-manager.js +4 -3
  62. package/dist/components/editor/core/state.js +2 -2
  63. package/dist/components/editor/core/timeline.js +1 -3
  64. package/dist/components/editor/editor-input.js +9 -6
  65. package/dist/components/editor/editor-multicursor.js +2 -2
  66. package/dist/components/editor/tokenizer/languages/css.js +146 -24
  67. package/dist/components/editor/tokenizer/languages/go.js +76 -13
  68. package/dist/components/editor/tokenizer/languages/javascript.d.ts +13 -2
  69. package/dist/components/editor/tokenizer/languages/javascript.js +278 -84
  70. package/dist/components/editor/tokenizer/languages/python.js +116 -19
  71. package/dist/components/editor/tokenizer/languages/svelte.js +20 -7
  72. package/dist/components/layout/IDELayout.svelte +6 -2
  73. package/dist/components/layout/StatusBar.svelte +32 -20
  74. package/dist/components/lsp/AutocompleteWidget.svelte +19 -19
  75. package/dist/components/lsp/DiagnosticMarker.svelte +61 -52
  76. package/dist/components/lsp/DiagnosticsPanel.svelte +45 -27
  77. package/dist/components/lsp/HoverTooltip.svelte +56 -61
  78. package/dist/components/lsp/LSPEditor.svelte +7 -18
  79. package/dist/components/lsp/SignatureHelpWidget.svelte +12 -9
  80. package/dist/components/plugins/PluginCard.svelte +3 -13
  81. package/dist/components/plugins/PluginProposalForm.svelte +19 -31
  82. package/dist/components/vfs/LockConflictDialog.svelte +112 -45
  83. package/dist/components/vfs/LockIndicator.svelte +0 -1
  84. package/dist/components/vfs/LockOverlay.svelte +53 -53
  85. package/dist/components/vfs/LockOverlay.svelte.d.ts +5 -5
  86. package/dist/components/vfs/VersionConflictDialog.svelte +107 -77
  87. package/dist/services/error-handling.js +1 -7
  88. package/dist/services/mock-ai.js +9 -7
  89. package/dist/stores/agents.svelte.js +50 -10
  90. package/dist/stores/ai.svelte.js +66 -18
  91. package/dist/stores/collaboration.svelte.js +70 -14
  92. package/dist/stores/editor.svelte.js +50 -10
  93. package/dist/stores/plugin.svelte.js +60 -12
  94. package/dist/stores/vfs.svelte.js +77 -19
  95. package/dist/styles/theme.css +16 -7
  96. package/package.json +186 -1
@@ -5,7 +5,19 @@
5
5
  /** Display name */
6
6
  name: string;
7
7
  /** Symbol type */
8
- type: 'file' | 'module' | 'namespace' | 'class' | 'interface' | 'function' | 'method' | 'property' | 'variable' | 'constant' | 'enum' | 'block';
8
+ type:
9
+ | 'file'
10
+ | 'module'
11
+ | 'namespace'
12
+ | 'class'
13
+ | 'interface'
14
+ | 'function'
15
+ | 'method'
16
+ | 'property'
17
+ | 'variable'
18
+ | 'constant'
19
+ | 'enum'
20
+ | 'block';
9
21
  /** Line number (0-based) */
10
22
  line: number;
11
23
  /** Column number */
@@ -211,7 +223,10 @@
211
223
  </span>
212
224
  <span class="breadcrumb-label">{symbol.name}</span>
213
225
  {#if symbol.siblings && symbol.siblings.length > 0}
214
- <span class="breadcrumb-chevron" class:breadcrumb-chevron--open={activeDropdown === symbol.id}>
226
+ <span
227
+ class="breadcrumb-chevron"
228
+ class:breadcrumb-chevron--open={activeDropdown === symbol.id}
229
+ >
215
230
 
216
231
  </span>
217
232
  {/if}
@@ -233,10 +248,7 @@
233
248
  class:dropdown-item--current={sibling.id === symbol.id}
234
249
  onclick={() => handleSiblingSelect(sibling)}
235
250
  >
236
- <span
237
- class="dropdown-icon"
238
- style="color: {typeColors[sibling.type]};"
239
- >
251
+ <span class="dropdown-icon" style="color: {typeColors[sibling.type]};">
240
252
  {typeIcons[sibling.type]}
241
253
  </span>
242
254
  <span class="dropdown-label">{sibling.name}</span>
@@ -1,3 +1,28 @@
1
+ <script module lang="ts">
2
+ function getBreakpointTitle(bp: Breakpoint): string {
3
+ let title = `Breakpoint at line ${bp.line + 1}`;
4
+ if (bp.condition) {
5
+ title += `\nCondition: ${bp.condition}`;
6
+ }
7
+ if (bp.logMessage) {
8
+ title += `\nLog: ${bp.logMessage}`;
9
+ }
10
+ if (bp.hitCondition) {
11
+ title += `\nHit condition: ${bp.hitCondition}`;
12
+ }
13
+ if (bp.hitCount > 0) {
14
+ title += `\nHit count: ${bp.hitCount}`;
15
+ }
16
+ if (bp.state === 'disabled') {
17
+ title += '\n(Disabled)';
18
+ }
19
+ if (bp.errorMessage) {
20
+ title += `\nError: ${bp.errorMessage}`;
21
+ }
22
+ return title;
23
+ }
24
+ </script>
25
+
1
26
  <script lang="ts">
2
27
  /**
3
28
  * Breakpoint Layer
@@ -41,7 +66,10 @@
41
66
  }: Props = $props();
42
67
 
43
68
  let breakpoints = $state<Breakpoint[]>([]);
44
- let contextMenu = $state<{ breakpoint: Breakpoint; position: { top: number; left: number } } | null>(null);
69
+ let contextMenu = $state<{
70
+ breakpoint: Breakpoint;
71
+ position: { top: number; left: number };
72
+ } | null>(null);
45
73
  let editingBreakpoint = $state<Breakpoint | null>(null);
46
74
  let conditionInput = $state('');
47
75
  let logMessageInput = $state('');
@@ -264,35 +292,20 @@
264
292
  class="breakpoint-context-menu"
265
293
  style="top: {contextMenu.position.top}px; left: {contextMenu.position.left}px;"
266
294
  >
267
- <button
268
- class="menu-item"
269
- onclick={() => handleContextAction('toggle')}
270
- >
295
+ <button class="menu-item" onclick={() => handleContextAction('toggle')}>
271
296
  {contextMenu.breakpoint.state === 'disabled' ? 'Enable' : 'Disable'} Breakpoint
272
297
  </button>
273
- <button
274
- class="menu-item"
275
- onclick={() => handleContextAction('remove')}
276
- >
298
+ <button class="menu-item" onclick={() => handleContextAction('remove')}>
277
299
  Remove Breakpoint
278
300
  </button>
279
301
  <div class="menu-divider"></div>
280
- <button
281
- class="menu-item"
282
- onclick={() => handleContextAction('condition')}
283
- >
302
+ <button class="menu-item" onclick={() => handleContextAction('condition')}>
284
303
  Edit Condition...
285
304
  </button>
286
- <button
287
- class="menu-item"
288
- onclick={() => handleContextAction('logpoint')}
289
- >
305
+ <button class="menu-item" onclick={() => handleContextAction('logpoint')}>
290
306
  Edit Log Message...
291
307
  </button>
292
- <button
293
- class="menu-item"
294
- onclick={() => handleContextAction('hitCondition')}
295
- >
308
+ <button class="menu-item" onclick={() => handleContextAction('hitCondition')}>
296
309
  Edit Hit Count...
297
310
  </button>
298
311
  </div>
@@ -300,8 +313,19 @@
300
313
 
301
314
  <!-- Edit dialog -->
302
315
  {#if editingBreakpoint}
303
- <div class="breakpoint-edit-overlay" role="presentation" onclick={cancelEdit} onkeydown={(e) => e.key === 'Escape' && cancelEdit()}>
304
- <div class="breakpoint-edit-dialog" role="dialog" tabindex={-1} onclick={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()}>
316
+ <div
317
+ class="breakpoint-edit-overlay"
318
+ role="presentation"
319
+ onclick={cancelEdit}
320
+ onkeydown={(e) => e.key === 'Escape' && cancelEdit()}
321
+ >
322
+ <div
323
+ class="breakpoint-edit-dialog"
324
+ role="dialog"
325
+ tabindex={-1}
326
+ onclick={(e) => e.stopPropagation()}
327
+ onkeydown={(e) => e.stopPropagation()}
328
+ >
305
329
  <div class="dialog-header">
306
330
  {#if editMode === 'condition'}
307
331
  Edit Condition
@@ -325,9 +349,7 @@
325
349
  autofocus
326
350
  />
327
351
  </label>
328
- <p class="dialog-hint">
329
- Expression will be evaluated in the current scope.
330
- </p>
352
+ <p class="dialog-hint">Expression will be evaluated in the current scope.</p>
331
353
  {:else if editMode === 'logpoint'}
332
354
  <label class="dialog-label">
333
355
  Log message (use {'{expression}'} for values):
@@ -341,9 +363,7 @@
341
363
  autofocus
342
364
  />
343
365
  </label>
344
- <p class="dialog-hint">
345
- Logpoints log a message without stopping execution.
346
- </p>
366
+ <p class="dialog-hint">Logpoints log a message without stopping execution.</p>
347
367
  {:else}
348
368
  <label class="dialog-label">
349
369
  Break when hit count:
@@ -363,43 +383,14 @@
363
383
  {/if}
364
384
  </div>
365
385
  <div class="dialog-actions">
366
- <button class="dialog-btn dialog-btn--cancel" onclick={cancelEdit}>
367
- Cancel
368
- </button>
369
- <button class="dialog-btn dialog-btn--save" onclick={saveEdit}>
370
- Save
371
- </button>
386
+ <button class="dialog-btn dialog-btn--cancel" onclick={cancelEdit}> Cancel </button>
387
+ <button class="dialog-btn dialog-btn--save" onclick={saveEdit}> Save </button>
372
388
  </div>
373
389
  </div>
374
390
  </div>
375
391
  {/if}
376
392
  {/if}
377
393
 
378
- <script module lang="ts">
379
- function getBreakpointTitle(bp: Breakpoint): string {
380
- let title = `Breakpoint at line ${bp.line + 1}`;
381
- if (bp.condition) {
382
- title += `\nCondition: ${bp.condition}`;
383
- }
384
- if (bp.logMessage) {
385
- title += `\nLog: ${bp.logMessage}`;
386
- }
387
- if (bp.hitCondition) {
388
- title += `\nHit condition: ${bp.hitCondition}`;
389
- }
390
- if (bp.hitCount > 0) {
391
- title += `\nHit count: ${bp.hitCount}`;
392
- }
393
- if (bp.state === 'disabled') {
394
- title += '\n(Disabled)';
395
- }
396
- if (bp.errorMessage) {
397
- title += `\nError: ${bp.errorMessage}`;
398
- }
399
- return title;
400
- }
401
- </script>
402
-
403
394
  <style>
404
395
  .breakpoint-layer {
405
396
  position: absolute;
@@ -68,7 +68,7 @@
68
68
  aria-label="Code complexity: {score} out of 100, {levelLabel} complexity"
69
69
  onmouseenter={handleMouseEnter}
70
70
  onmouseleave={handleMouseLeave}
71
- onclick={onclick}
71
+ {onclick}
72
72
  onkeydown={(e) => e.key === 'Enter' && onclick?.()}
73
73
  tabindex={onclick ? 0 : -1}
74
74
  >
@@ -143,7 +143,9 @@
143
143
  {#if metrics.regions.some((r) => r.suggestion)}
144
144
  <div class="cognitive-meter__tooltip-section">
145
145
  <span class="cognitive-meter__tooltip-label">Suggestions:</span>
146
- {#each metrics.regions.filter((r) => r.suggestion).slice(0, 2) as region (region.startLine)}
146
+ {#each metrics.regions
147
+ .filter((r) => r.suggestion)
148
+ .slice(0, 2) as region (region.startLine)}
147
149
  <p class="cognitive-meter__tooltip-suggestion">
148
150
  {region.suggestion}
149
151
  </p>
@@ -9,11 +9,7 @@
9
9
  import { onMount, onDestroy } from 'svelte';
10
10
  import * as Y from 'yjs';
11
11
  import CustomEditor from './CustomEditor.svelte';
12
- import {
13
- createEditorState,
14
- createCRDTBinding,
15
- type CRDTBinding
16
- } from './core';
12
+ import { createEditorState, createCRDTBinding, type CRDTBinding } from './core';
17
13
  import type { EditorPreferences } from '../../types';
18
14
  import type { CollaborationUser } from '../../types/crdt';
19
15
 
@@ -134,10 +134,7 @@
134
134
  }
135
135
 
136
136
  function escapeHtml(text: string): string {
137
- return text
138
- .replace(/&/g, '&amp;')
139
- .replace(/</g, '&lt;')
140
- .replace(/>/g, '&gt;');
137
+ return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
141
138
  }
142
139
 
143
140
  function escapeRegex(text: string): string {
@@ -59,7 +59,10 @@
59
59
 
60
60
  // Group by line (in case of overlapping regions, take highest score)
61
61
  let lineScores = $derived.by(() => {
62
- const scores = new SvelteMap<number, { score: number; region: ComplexityRegion; isStart: boolean; isEnd: boolean }>();
62
+ const scores = new SvelteMap<
63
+ number,
64
+ { score: number; region: ComplexityRegion; isStart: boolean; isEnd: boolean }
65
+ >();
63
66
 
64
67
  for (const indicator of lineIndicators) {
65
68
  const existing = scores.get(indicator.line);
@@ -142,10 +145,7 @@
142
145
  style="top: {tooltipPosition.top}px; left: {tooltipPosition.left}px;"
143
146
  >
144
147
  <div class="complexity-tooltip__header">
145
- <span
146
- class="complexity-tooltip__badge"
147
- style="background: {getColor(hoveredRegion.score)}"
148
- >
148
+ <span class="complexity-tooltip__badge" style="background: {getColor(hoveredRegion.score)}">
149
149
  {hoveredRegion.score}
150
150
  </span>
151
151
  <span class="complexity-tooltip__title">
@@ -188,7 +188,9 @@
188
188
  cursor: help;
189
189
  background: var(--indicator-color);
190
190
  opacity: var(--indicator-opacity);
191
- transition: opacity 0.15s ease, width 0.15s ease;
191
+ transition:
192
+ opacity 0.15s ease,
193
+ width 0.15s ease;
192
194
  }
193
195
 
194
196
  .complexity-gutter__indicator:hover {
@@ -139,10 +139,7 @@
139
139
  tabindex={-1}
140
140
  >
141
141
  <!-- Gutter indicator -->
142
- <div
143
- class="conflict-zone__gutter"
144
- style="left: -{gutterWidth}px; width: {gutterWidth}px;"
145
- >
142
+ <div class="conflict-zone__gutter" style="left: -{gutterWidth}px; width: {gutterWidth}px;">
146
143
  <span class="conflict-zone__icon">{getSeverityIcon(zone.severity)}</span>
147
144
  </div>
148
145
 
@@ -211,10 +208,7 @@
211
208
  <div class="conflict-tooltip__participants">
212
209
  {#each hoveredZone.participants as participant (participant.userId)}
213
210
  <div class="conflict-tooltip__participant">
214
- <span
215
- class="conflict-tooltip__dot"
216
- style="background: {participant.color}"
217
- ></span>
211
+ <span class="conflict-tooltip__dot" style="background: {participant.color}"></span>
218
212
  <span class="conflict-tooltip__name">
219
213
  {participant.userName}
220
214
  {#if participant.isAI}
@@ -174,10 +174,7 @@
174
174
  {#if enabled && visibleItems.length > 0}
175
175
  <div class="context-lens" aria-hidden="true">
176
176
  {#each visibleItems as item (`${item.line}-${item.type}`)}
177
- <div
178
- class="context-lens__item context-lens__item--{item.type}"
179
- style={getItemStyle(item)}
180
- >
177
+ <div class="context-lens__item context-lens__item--{item.type}" style={getItemStyle(item)}>
181
178
  {#if item.icon}
182
179
  <span class="context-lens__icon">{item.icon}</span>
183
180
  {/if}
@@ -192,7 +192,10 @@
192
192
 
193
193
  // Reactive state from editor
194
194
  let lines = $state<readonly ReturnType<typeof editorState.getLine>[]>([]);
195
- let selection = $state<Selection>({ anchor: { line: 0, column: 0 }, head: { line: 0, column: 0 } });
195
+ let selection = $state<Selection>({
196
+ anchor: { line: 0, column: 0 },
197
+ head: { line: 0, column: 0 }
198
+ });
196
199
  let hasSelection = $state(false);
197
200
  let cursors = $state<readonly Cursor[]>([]);
198
201
  let hasMultipleCursors = $state(false);
@@ -386,7 +389,9 @@
386
389
  onUnfoldAll: () => foldManager.expandAll(),
387
390
  onSelectNextOccurrence: () => selectNextOccurrence(editorState),
388
391
  onSelectAllOccurrences: () => selectAllOccurrences(editorState),
389
- onShowCommandPalette: () => { showCommandPalette = true; },
392
+ onShowCommandPalette: () => {
393
+ showCommandPalette = true;
394
+ },
390
395
  onSave,
391
396
  announce,
392
397
  resetCursorBlink,
@@ -396,15 +401,25 @@
396
401
  }
397
402
  },
398
403
 
399
- setCharWidth: (w) => { charWidth = w; },
400
- setLineHeight: (h) => { lineHeight = h; },
401
- setScrollTop: (v) => { scrollTop = v; },
402
- setScrollLeft: (v) => { scrollLeft = v; },
403
- setCursorVisible: (v) => { cursorVisible = v; },
404
+ setCharWidth: (w) => {
405
+ charWidth = w;
406
+ },
407
+ setLineHeight: (h) => {
408
+ lineHeight = h;
409
+ },
410
+ setScrollTop: (v) => {
411
+ scrollTop = v;
412
+ },
413
+ setScrollLeft: (v) => {
414
+ scrollLeft = v;
415
+ },
416
+ setCursorVisible: (v) => {
417
+ cursorVisible = v;
418
+ },
404
419
 
405
420
  isShowFindReplace: () => showFindReplace,
406
421
  getSearchQuery: () => searchQuery,
407
- getSearchMatchCount: () => searchMatches.length,
422
+ getSearchMatchCount: () => searchMatches.length
408
423
  });
409
424
  }
410
425
 
@@ -453,17 +468,23 @@
453
468
  function handleFoldCurrent() {
454
469
  if (!folding || !editorState) return;
455
470
  const currentLine = selection.head.line;
456
- // Find fold region at or containing current line
457
- const region = foldManager.getRegionAtLine(currentLine);
471
+ // Fold the innermost block the cursor is *inside*, not only when the cursor
472
+ // sits on the fold header. Skip already-collapsed regions so repeated
473
+ // presses fold progressively outward (VS Code behaviour).
474
+ const region = foldManager.getInnermostRegionContaining(currentLine, 'uncollapsed');
458
475
  if (region) {
459
- foldManager.collapse(currentLine);
476
+ foldManager.collapse(region.startLine);
460
477
  }
461
478
  }
462
479
 
463
480
  function handleUnfoldCurrent() {
464
481
  if (!folding || !editorState) return;
465
482
  const currentLine = selection.head.line;
466
- foldManager.expand(currentLine);
483
+ // Expand the innermost collapsed block containing the cursor (mirrors fold).
484
+ const region = foldManager.getInnermostRegionContaining(currentLine, 'collapsed');
485
+ if (region) {
486
+ foldManager.expand(region.startLine);
487
+ }
467
488
  }
468
489
 
469
490
  function handleFoldIndicatorClick(lineNumber: number, e: MouseEvent) {
@@ -486,7 +507,7 @@
486
507
  if (!folding || visibleLineIndices.length === 0) {
487
508
  return lines.map((line, index) => ({ line, index }));
488
509
  }
489
- return visibleLineIndices.map(index => ({
510
+ return visibleLineIndices.map((index) => ({
490
511
  line: lines[index],
491
512
  index
492
513
  }));
@@ -512,7 +533,11 @@
512
533
  const firstRow = Math.max(0, Math.floor(scrollTop / lineHeight) - ROW_OVERSCAN);
513
534
  const lastRow = Math.min(rowCount - 1, firstRow + rowsPerViewport + ROW_OVERSCAN * 2);
514
535
 
515
- const slice: Array<{ line: (typeof visibleLines)[number]['line']; index: number; visualRow: number }> = [];
536
+ const slice: Array<{
537
+ line: (typeof visibleLines)[number]['line'];
538
+ index: number;
539
+ visualRow: number;
540
+ }> = [];
516
541
  for (let visualRow = firstRow; visualRow <= lastRow; visualRow++) {
517
542
  const entry = visibleLines[visualRow];
518
543
  slice.push({ line: entry.line, index: entry.index, visualRow });
@@ -629,7 +654,6 @@
629
654
  );
630
655
  });
631
656
 
632
-
633
657
  // Watch for content prop changes (external changes only, not user typing)
634
658
  $effect(() => {
635
659
  // Only react to a GENUINE external change to the `content` prop (e.g. a file
@@ -662,8 +686,14 @@
662
686
  // Use requestAnimationFrame to ensure DOM has updated
663
687
  const rafId = requestAnimationFrame(() => {
664
688
  if (contentEl) {
665
- contentEl.scrollTop = Math.min(savedScrollTop, contentEl.scrollHeight - contentEl.clientHeight);
666
- contentEl.scrollLeft = Math.min(savedScrollLeft, contentEl.scrollWidth - contentEl.clientWidth);
689
+ contentEl.scrollTop = Math.min(
690
+ savedScrollTop,
691
+ contentEl.scrollHeight - contentEl.clientHeight
692
+ );
693
+ contentEl.scrollLeft = Math.min(
694
+ savedScrollLeft,
695
+ contentEl.scrollWidth - contentEl.clientWidth
696
+ );
667
697
  }
668
698
  });
669
699
  // Cleanup: cancel pending RAF if effect re-runs
@@ -687,15 +717,16 @@
687
717
  }
688
718
  });
689
719
 
690
-
691
720
  // Ensure cursor is on a visible line after fold changes
692
721
  $effect(() => {
693
722
  if (folding && editorState && foldManager.isLineHidden(selection.head.line)) {
694
723
  // Find the fold region containing the cursor and move to its start
695
724
  for (const region of foldRegions) {
696
- if (region.collapsed &&
725
+ if (
726
+ region.collapsed &&
697
727
  selection.head.line > region.startLine &&
698
- selection.head.line <= region.endLine) {
728
+ selection.head.line <= region.endLine
729
+ ) {
699
730
  editorState.setCursor({ line: region.startLine, column: 0 });
700
731
  break;
701
732
  }
@@ -838,9 +869,18 @@
838
869
  matchCount={searchMatches.length}
839
870
  currentMatch={currentMatchIndex >= 0 ? currentMatchIndex + 1 : 0}
840
871
  onQueryChange={handleQueryChange}
841
- onReplaceTextChange={(text) => { editorFind.setReplaceText(text); syncFindState(); }}
842
- onCaseSensitiveChange={(value) => { editorFind.setCaseSensitive(value); syncFindState(); }}
843
- onUseRegexChange={(value) => { editorFind.setUseRegex(value); syncFindState(); }}
872
+ onReplaceTextChange={(text) => {
873
+ editorFind.setReplaceText(text);
874
+ syncFindState();
875
+ }}
876
+ onCaseSensitiveChange={(value) => {
877
+ editorFind.setCaseSensitive(value);
878
+ syncFindState();
879
+ }}
880
+ onUseRegexChange={(value) => {
881
+ editorFind.setUseRegex(value);
882
+ syncFindState();
883
+ }}
844
884
  onFindNext={handleFindNext}
845
885
  onFindPrev={handleFindPrev}
846
886
  onReplace={handleReplace}
@@ -872,7 +912,12 @@
872
912
 
873
913
  <!-- Screen reader status (announces cursor position changes) -->
874
914
  <div class="custom-editor__sr-status" aria-live="polite" aria-atomic="true">
875
- {readonly ? 'Read-only. ' : ''}Line {selection.head.line + 1}, Column {selection.head.column + 1}{hasSelection ? `, ${Math.abs(selection.head.line - selection.anchor.line) + 1} lines selected` : ''}{hasMultipleCursors ? `, ${cursors.length} cursors active` : ''}{searchMatches.length > 0 ? `, ${searchMatches.length} search matches, match ${currentMatchIndex + 1} of ${searchMatches.length}` : ''}
915
+ {readonly ? 'Read-only. ' : ''}Line {selection.head.line + 1}, Column {selection.head.column +
916
+ 1}{hasSelection
917
+ ? `, ${Math.abs(selection.head.line - selection.anchor.line) + 1} lines selected`
918
+ : ''}{hasMultipleCursors ? `, ${cursors.length} cursors active` : ''}{searchMatches.length > 0
919
+ ? `, ${searchMatches.length} search matches, match ${currentMatchIndex + 1} of ${searchMatches.length}`
920
+ : ''}
876
921
  </div>
877
922
 
878
923
  <!-- Dynamic screen reader announcements for actions -->
@@ -882,7 +927,10 @@
882
927
 
883
928
  <!-- Hidden keyboard shortcuts help for screen readers -->
884
929
  <div id="editor-help" class="custom-editor__sr-status">
885
- Keyboard shortcuts: Ctrl+F to find, Ctrl+H to replace, Ctrl+D to add cursor at next occurrence, Ctrl+Shift+L to select all occurrences, Escape to clear.{folding ? ' Ctrl+Shift+[ to fold, Ctrl+Shift+] to unfold.' : ''}
930
+ Keyboard shortcuts: Ctrl+F to find, Ctrl+H to replace, Ctrl+D to add cursor at next occurrence,
931
+ Ctrl+Shift+L to select all occurrences, Escape to clear.{folding
932
+ ? ' Ctrl+Shift+[ to fold, Ctrl+Shift+] to unfold.'
933
+ : ''}
886
934
  </div>
887
935
 
888
936
  <!-- Main editor content -->
@@ -903,8 +951,8 @@
903
951
  {#if complexityHighlighting && complexityMetrics}
904
952
  <ComplexityLayer
905
953
  metrics={complexityMetrics}
906
- lineHeight={lineHeight}
907
- gutterWidth={gutterWidth}
954
+ {lineHeight}
955
+ {gutterWidth}
908
956
  minScore={complexityThreshold}
909
957
  enabled={complexityHighlighting}
910
958
  />
@@ -914,9 +962,9 @@
914
962
  {#if aiAgents.length > 0}
915
963
  <AIFocusLayer
916
964
  agents={aiAgents}
917
- lineHeight={lineHeight}
918
- charWidth={charWidth}
919
- gutterWidth={gutterWidth}
965
+ {lineHeight}
966
+ {charWidth}
967
+ {gutterWidth}
920
968
  contentPadding={CONTENT_PADDING}
921
969
  showLabels={showAILabels}
922
970
  showFocusRegions={showAIFocusRegions}
@@ -926,7 +974,10 @@
926
974
 
927
975
  <!-- Gutter background (extends full height of content, matching the virtualized spacer) -->
928
976
  {#if mergedPrefs.lineNumbers !== 'off'}
929
- <div class="custom-editor__gutter-bg" style="width: {gutterWidth}px; height: {totalContentHeight}px;"></div>
977
+ <div
978
+ class="custom-editor__gutter-bg"
979
+ style="width: {gutterWidth}px; height: {totalContentHeight}px;"
980
+ ></div>
930
981
  {/if}
931
982
 
932
983
  <!-- Search match highlights (below selection) -->
@@ -946,7 +997,7 @@
946
997
  <EditorSelections
947
998
  {cursors}
948
999
  {cursorVisible}
949
- readonly={readonly}
1000
+ {readonly}
950
1001
  {lineHeight}
951
1002
  {charWidth}
952
1003
  {gutterWidth}
@@ -959,7 +1010,7 @@
959
1010
 
960
1011
  <!-- Lines (virtualized: only the windowed slice is rendered) -->
961
1012
  <EditorLines
962
- windowedLines={windowedLines}
1013
+ {windowedLines}
963
1014
  totalHeight={totalContentHeight}
964
1015
  {lineHeight}
965
1016
  activeLine={selection.head.line}
@@ -974,15 +1025,11 @@
974
1025
  onFoldIndicatorClick={handleFoldIndicatorClick}
975
1026
  onExpandFold={(index) => foldManager.expand(index)}
976
1027
  />
977
-
978
1028
  </div>
979
1029
  </div>
980
1030
 
981
1031
  <!-- Command Palette -->
982
- <CommandPalette
983
- bind:open={showCommandPalette}
984
- onClose={() => editorContent?.focus()}
985
- />
1032
+ <CommandPalette bind:open={showCommandPalette} onClose={() => editorContent?.focus()} />
986
1033
 
987
1034
  <style>
988
1035
  .custom-editor {
@@ -1070,8 +1117,6 @@
1070
1117
  border-color: rgba(255, 213, 0, 0.8);
1071
1118
  }
1072
1119
 
1073
-
1074
-
1075
1120
  /* Gutter background - extends full height of content */
1076
1121
  .custom-editor__gutter-bg {
1077
1122
  position: absolute;
@@ -1084,7 +1129,6 @@
1084
1129
  pointer-events: none;
1085
1130
  }
1086
1131
 
1087
-
1088
1132
  /* Token styles */
1089
1133
  :global(.token-comment),
1090
1134
  :global(.token-comment-line),
@@ -260,7 +260,7 @@
260
260
  </div>
261
261
 
262
262
  <div class="header-filters">
263
- {#each (['all', 'error', 'warning', 'info', 'log'] as const) as filterType (filterType)}
263
+ {#each ['all', 'error', 'warning', 'info', 'log'] as const as filterType (filterType)}
264
264
  {@const count = entryCounts()[filterType]}
265
265
  <button
266
266
  class="filter-btn"
@@ -273,7 +273,9 @@
273
273
  <span class="filter-icon">{getTypeIcon(filterType)}</span>
274
274
  {/if}
275
275
  <span class="filter-label">
276
- {filterType === 'all' ? 'All' : filterType.charAt(0).toUpperCase() + filterType.slice(1)}
276
+ {filterType === 'all'
277
+ ? 'All'
278
+ : filterType.charAt(0).toUpperCase() + filterType.slice(1)}
277
279
  </span>
278
280
  {#if count > 0}
279
281
  <span class="filter-count">{count}</span>
@@ -283,13 +285,9 @@
283
285
  </div>
284
286
 
285
287
  <div class="header-actions">
286
- <button class="action-btn" onclick={onClear} title="Clear console (Ctrl+L)">
287
- 🗑
288
- </button>
288
+ <button class="action-btn" onclick={onClear} title="Clear console (Ctrl+L)"> 🗑 </button>
289
289
  {#if onClose}
290
- <button class="action-btn" onclick={onClose} title="Close">
291
- ×
292
- </button>
290
+ <button class="action-btn" onclick={onClose} title="Close"> × </button>
293
291
  {/if}
294
292
  </div>
295
293
  </div>
@@ -317,10 +315,7 @@
317
315
  </span>
318
316
 
319
317
  {#if entry.expandable}
320
- <button
321
- class="entry-expand"
322
- onclick={() => toggleExpand(entry.id)}
323
- >
318
+ <button class="entry-expand" onclick={() => toggleExpand(entry.id)}>
324
319
  {isExpanded ? '▾' : '▸'}
325
320
  </button>
326
321
  {/if}
@@ -362,11 +357,7 @@
362
357
  onkeydown={handleKeyDown}
363
358
  placeholder={inputPlaceholder}
364
359
  />
365
- <button
366
- class="input-submit"
367
- onclick={handleSubmit}
368
- disabled={!inputValue.trim()}
369
- >
360
+ <button class="input-submit" onclick={handleSubmit} disabled={!inputValue.trim()}>
370
361
 
371
362
  </button>
372
363
  </div>