@nocturnium/svelte-ide 1.0.0 → 1.0.1

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 (95) 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.js +210 -29
  69. package/dist/components/editor/tokenizer/languages/python.js +116 -19
  70. package/dist/components/editor/tokenizer/languages/svelte.js +20 -7
  71. package/dist/components/layout/IDELayout.svelte +6 -2
  72. package/dist/components/layout/StatusBar.svelte +32 -20
  73. package/dist/components/lsp/AutocompleteWidget.svelte +19 -19
  74. package/dist/components/lsp/DiagnosticMarker.svelte +61 -52
  75. package/dist/components/lsp/DiagnosticsPanel.svelte +45 -27
  76. package/dist/components/lsp/HoverTooltip.svelte +56 -61
  77. package/dist/components/lsp/LSPEditor.svelte +7 -18
  78. package/dist/components/lsp/SignatureHelpWidget.svelte +12 -9
  79. package/dist/components/plugins/PluginCard.svelte +3 -13
  80. package/dist/components/plugins/PluginProposalForm.svelte +19 -31
  81. package/dist/components/vfs/LockConflictDialog.svelte +112 -45
  82. package/dist/components/vfs/LockIndicator.svelte +0 -1
  83. package/dist/components/vfs/LockOverlay.svelte +53 -53
  84. package/dist/components/vfs/LockOverlay.svelte.d.ts +5 -5
  85. package/dist/components/vfs/VersionConflictDialog.svelte +107 -77
  86. package/dist/services/error-handling.js +1 -7
  87. package/dist/services/mock-ai.js +9 -7
  88. package/dist/stores/agents.svelte.js +50 -10
  89. package/dist/stores/ai.svelte.js +66 -18
  90. package/dist/stores/collaboration.svelte.js +70 -14
  91. package/dist/stores/editor.svelte.js +50 -10
  92. package/dist/stores/plugin.svelte.js +60 -12
  93. package/dist/stores/vfs.svelte.js +77 -19
  94. package/dist/styles/theme.css +16 -7
  95. package/package.json +186 -1
@@ -272,12 +272,8 @@
272
272
  <div class="outline-header">
273
273
  <h3 class="outline-title">Outline</h3>
274
274
  <div class="outline-actions">
275
- <button class="action-btn" onclick={expandAll} title="Expand all">
276
-
277
- </button>
278
- <button class="action-btn" onclick={collapseAll} title="Collapse all">
279
-
280
- </button>
275
+ <button class="action-btn" onclick={expandAll} title="Expand all"> ⊞ </button>
276
+ <button class="action-btn" onclick={collapseAll} title="Collapse all"> ⊟ </button>
281
277
  </div>
282
278
  </div>
283
279
 
@@ -290,9 +286,7 @@
290
286
  bind:value={searchQuery}
291
287
  />
292
288
  {#if searchQuery}
293
- <button class="search-clear" onclick={() => (searchQuery = '')}>
294
- ×
295
- </button>
289
+ <button class="search-clear" onclick={() => (searchQuery = '')}> × </button>
296
290
  {/if}
297
291
  </div>
298
292
 
@@ -333,12 +327,7 @@
333
327
  title="{symbol.kind}: {symbol.name}{symbol.detail ? ` - ${symbol.detail}` : ''}"
334
328
  >
335
329
  {#if hasChildren}
336
- <span
337
- class="symbol-chevron"
338
- class:symbol-chevron--collapsed={collapsed}
339
- >
340
-
341
- </span>
330
+ <span class="symbol-chevron" class:symbol-chevron--collapsed={collapsed}> ▾ </span>
342
331
  {:else}
343
332
  <span class="symbol-spacer"></span>
344
333
  {/if}
@@ -385,8 +374,16 @@
385
374
  <span
386
375
  class="symbol-chevron"
387
376
  class:symbol-chevron--collapsed={childCollapsed}
388
- onclick={(e) => { e.stopPropagation(); toggleCollapse(child); }}
389
- onkeydown={(e) => { if (e.key === 'Enter') { e.stopPropagation(); toggleCollapse(child); } }}
377
+ onclick={(e) => {
378
+ e.stopPropagation();
379
+ toggleCollapse(child);
380
+ }}
381
+ onkeydown={(e) => {
382
+ if (e.key === 'Enter') {
383
+ e.stopPropagation();
384
+ toggleCollapse(child);
385
+ }
386
+ }}
390
387
  role="button"
391
388
  tabindex={-1}
392
389
  >
@@ -580,7 +577,6 @@
580
577
  font-size: 12px;
581
578
  }
582
579
 
583
-
584
580
  .symbol-item {
585
581
  display: flex;
586
582
  align-items: center;
@@ -674,7 +670,6 @@
674
670
  margin-left: auto;
675
671
  }
676
672
 
677
-
678
673
  /* Hover preview */
679
674
  .outline-preview {
680
675
  padding: 8px 12px;
@@ -7,10 +7,7 @@
7
7
  * Supports playback mode with smooth transitions.
8
8
  */
9
9
 
10
- import {
11
- type SnapshotMetadata,
12
- formatTimestamp
13
- } from './core/timeline';
10
+ import { type SnapshotMetadata, formatTimestamp } from './core/timeline';
14
11
 
15
12
  interface TimelineMarker {
16
13
  position: number;
@@ -265,7 +262,9 @@
265
262
  </div>
266
263
  <div class="timeline-scrubber__tooltip-content">
267
264
  <div class="timeline-scrubber__tooltip-label">{hoveredMarker.label}</div>
268
- <div class="timeline-scrubber__tooltip-time">{formatTimestamp(hoveredMarker.timestamp)}</div>
265
+ <div class="timeline-scrubber__tooltip-time">
266
+ {formatTimestamp(hoveredMarker.timestamp)}
267
+ </div>
269
268
  </div>
270
269
  </div>
271
270
  {/if}
@@ -306,7 +305,9 @@
306
305
  border-radius: 4px;
307
306
  color: var(--color-text-muted, #888);
308
307
  cursor: pointer;
309
- transition: background 0.15s ease, color 0.15s ease;
308
+ transition:
309
+ background 0.15s ease,
310
+ color 0.15s ease;
310
311
  }
311
312
 
312
313
  .timeline-scrubber__btn:hover {
@@ -13,7 +13,7 @@ const THRESHOLDS = {
13
13
  low: 30,
14
14
  medium: 50,
15
15
  high: 70,
16
- critical: 85,
16
+ critical: 85
17
17
  };
18
18
  /**
19
19
  * Weights for different complexity factors
@@ -23,7 +23,7 @@ const WEIGHTS = {
23
23
  branchingFactor: 8,
24
24
  lineCount: 0.3,
25
25
  identifierCount: 0.2,
26
- callCount: 0.5,
26
+ callCount: 0.5
27
27
  };
28
28
  /**
29
29
  * Patterns for detecting code constructs
@@ -42,7 +42,7 @@ const PATTERNS = {
42
42
  // Block openers
43
43
  blockOpen: /\{/g,
44
44
  // Block closers
45
- blockClose: /\}/g,
45
+ blockClose: /\}/g
46
46
  };
47
47
  /**
48
48
  * Complexity Analyzer class
@@ -67,7 +67,7 @@ export class ComplexityAnalyzer {
67
67
  overall,
68
68
  level: this.getLevel(overall),
69
69
  regions: analyzedRegions,
70
- hotspots,
70
+ hotspots
71
71
  };
72
72
  this.cacheKey = key;
73
73
  this.cache.set(key, metrics);
@@ -151,13 +151,21 @@ export class ComplexityAnalyzer {
151
151
  braceDepth++;
152
152
  // If this is the opening brace of a function/class def, push it
153
153
  if (funcMatch && this.isDefOpeningBrace(text, ch, funcMatch)) {
154
- blockStack.push({ line: i, type: funcMatch[5] ? 'class' : 'function', name: funcMatch[2] || funcMatch[3] || funcMatch[4] || funcMatch[5], depth: braceDepth });
154
+ blockStack.push({
155
+ line: i,
156
+ type: funcMatch[5] ? 'class' : 'function',
157
+ name: funcMatch[2] || funcMatch[3] || funcMatch[4] || funcMatch[5],
158
+ depth: braceDepth
159
+ });
155
160
  }
156
- else if (!funcMatch || braceDepth > (blockStack.length > 0 ? blockStack[blockStack.length - 1].depth : 0) + 1) {
161
+ else if (!funcMatch ||
162
+ braceDepth > (blockStack.length > 0 ? blockStack[blockStack.length - 1].depth : 0) + 1) {
157
163
  // Anonymous/inline brace — only push as block if it's a
158
164
  // statement-level block (i.e., after control flow keyword on this line)
159
165
  const prefix = text.slice(0, ch).trim();
160
- if (/\b(if|else|for|while|do|switch|try|catch|finally)\b/.test(prefix) || prefix.endsWith('=>') || prefix.endsWith(')')) {
166
+ if (/\b(if|else|for|while|do|switch|try|catch|finally)\b/.test(prefix) ||
167
+ prefix.endsWith('=>') ||
168
+ prefix.endsWith(')')) {
161
169
  blockStack.push({ line: i, type: 'block', depth: braceDepth });
162
170
  }
163
171
  // Otherwise it's an expression brace (object literal, destructuring) — ignore
@@ -172,7 +180,7 @@ export class ComplexityAnalyzer {
172
180
  startLine: block.line,
173
181
  endLine: i,
174
182
  type: block.type,
175
- name: block.name,
183
+ name: block.name
176
184
  });
177
185
  }
178
186
  }
@@ -186,7 +194,7 @@ export class ComplexityAnalyzer {
186
194
  regions.push({
187
195
  startLine: 0,
188
196
  endLine: lines.length - 1,
189
- type: 'file',
197
+ type: 'file'
190
198
  });
191
199
  }
192
200
  return regions;
@@ -215,7 +223,7 @@ export class ComplexityAnalyzer {
215
223
  ...region,
216
224
  score,
217
225
  factors,
218
- suggestion,
226
+ suggestion
219
227
  };
220
228
  }
221
229
  /**
@@ -230,7 +238,29 @@ export class ComplexityAnalyzer {
230
238
  // Use a global version of the nesting pattern so we can count all matches per line
231
239
  const nestingStartGlobal = /\b(if|for|while|switch|try|catch|with)\s*\(|=>\s*\{|\bdo\s*\{/g;
232
240
  // Keywords that should NOT be counted as function calls
233
- const controlKeywords = new Set(['if', 'for', 'while', 'switch', 'catch', 'function', 'return', 'typeof', 'new', 'throw', 'await', 'yield', 'import', 'export', 'class', 'super', 'this', 'void', 'delete', 'in', 'of']);
241
+ const controlKeywords = new Set([
242
+ 'if',
243
+ 'for',
244
+ 'while',
245
+ 'switch',
246
+ 'catch',
247
+ 'function',
248
+ 'return',
249
+ 'typeof',
250
+ 'new',
251
+ 'throw',
252
+ 'await',
253
+ 'yield',
254
+ 'import',
255
+ 'export',
256
+ 'class',
257
+ 'super',
258
+ 'this',
259
+ 'void',
260
+ 'delete',
261
+ 'in',
262
+ 'of'
263
+ ]);
234
264
  for (let i = startLine; i <= endLine && i < lines.length; i++) {
235
265
  const text = lines[i].text;
236
266
  // Track nesting — count ALL nesting openers per line
@@ -272,7 +302,7 @@ export class ComplexityAnalyzer {
272
302
  branchingFactor,
273
303
  lineCount: endLine - startLine + 1,
274
304
  identifierCount: identifiers.size,
275
- callCount,
305
+ callCount
276
306
  };
277
307
  }
278
308
  /**
@@ -41,13 +41,11 @@ export class ConflictPredictor {
41
41
  }
42
42
  // Check each semantic region for multiple users
43
43
  for (const region of semanticRegions) {
44
- const usersInRegion = activeUsers.filter((user) => user.cursorLine >= region.startLine &&
45
- user.cursorLine <= region.endLine);
44
+ const usersInRegion = activeUsers.filter((user) => user.cursorLine >= region.startLine && user.cursorLine <= region.endLine);
46
45
  // Also check for users near the region
47
46
  const usersNearRegion = activeUsers.filter((user) => {
48
47
  const distanceToRegion = Math.min(Math.abs(user.cursorLine - region.startLine), Math.abs(user.cursorLine - region.endLine));
49
- return (distanceToRegion <= this.config.proximityThreshold &&
50
- !usersInRegion.includes(user));
48
+ return distanceToRegion <= this.config.proximityThreshold && !usersInRegion.includes(user);
51
49
  });
52
50
  const allRelevantUsers = [...usersInRegion, ...usersNearRegion];
53
51
  if (allRelevantUsers.length >= 2) {
@@ -60,6 +60,15 @@ export declare class FoldManager {
60
60
  * Get fold region at a specific line (if it starts there)
61
61
  */
62
62
  getRegionAtLine(line: number): FoldRegion | undefined;
63
+ /**
64
+ * Get the innermost (smallest) fold region whose range contains `line`
65
+ * (startLine <= line <= endLine). Unlike {@link getRegionAtLine}, this matches
66
+ * when the cursor is anywhere inside the block, not only on the fold header —
67
+ * so "fold current" works from within a function body. Optionally restrict to
68
+ * collapsed or uncollapsed regions (e.g. fold skips already-collapsed regions
69
+ * so repeated presses fold progressively outward).
70
+ */
71
+ getInnermostRegionContaining(line: number, filter?: 'collapsed' | 'uncollapsed'): FoldRegion | undefined;
63
72
  /**
64
73
  * Check if a line is hidden (inside a collapsed fold)
65
74
  */
@@ -28,8 +28,20 @@ const BRACKET_PAIRS = {
28
28
  * HTML5 void elements that don't need closing tags
29
29
  */
30
30
  const HTML_VOID_ELEMENTS = new Set([
31
- 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
32
- 'link', 'meta', 'param', 'source', 'track', 'wbr'
31
+ 'area',
32
+ 'base',
33
+ 'br',
34
+ 'col',
35
+ 'embed',
36
+ 'hr',
37
+ 'img',
38
+ 'input',
39
+ 'link',
40
+ 'meta',
41
+ 'param',
42
+ 'source',
43
+ 'track',
44
+ 'wbr'
33
45
  ]);
34
46
  /**
35
47
  * Extract tag name from an opening or closing tag
@@ -452,7 +464,7 @@ export function detectFoldRegions(lines, language = 'plaintext', config = {}) {
452
464
  if (a.startLine !== b.startLine) {
453
465
  return a.startLine - b.startLine;
454
466
  }
455
- return (b.endLine - b.startLine) - (a.endLine - a.startLine);
467
+ return b.endLine - b.startLine - (a.endLine - a.startLine);
456
468
  });
457
469
  // Remove overlapping regions (keep the first/largest one)
458
470
  const filteredRegions = [];
@@ -505,7 +517,30 @@ export class FoldManager {
505
517
  * Get fold region at a specific line (if it starts there)
506
518
  */
507
519
  getRegionAtLine(line) {
508
- return this.regions.find(r => r.startLine === line);
520
+ return this.regions.find((r) => r.startLine === line);
521
+ }
522
+ /**
523
+ * Get the innermost (smallest) fold region whose range contains `line`
524
+ * (startLine <= line <= endLine). Unlike {@link getRegionAtLine}, this matches
525
+ * when the cursor is anywhere inside the block, not only on the fold header —
526
+ * so "fold current" works from within a function body. Optionally restrict to
527
+ * collapsed or uncollapsed regions (e.g. fold skips already-collapsed regions
528
+ * so repeated presses fold progressively outward).
529
+ */
530
+ getInnermostRegionContaining(line, filter) {
531
+ let best;
532
+ for (const r of this.regions) {
533
+ if (line < r.startLine || line > r.endLine)
534
+ continue;
535
+ if (filter === 'collapsed' && !r.collapsed)
536
+ continue;
537
+ if (filter === 'uncollapsed' && r.collapsed)
538
+ continue;
539
+ if (!best || r.endLine - r.startLine < best.endLine - best.startLine) {
540
+ best = r;
541
+ }
542
+ }
543
+ return best;
509
544
  }
510
545
  /**
511
546
  * Check if a line is hidden (inside a collapsed fold)
@@ -517,7 +552,7 @@ export class FoldManager {
517
552
  * Check if a line has a fold indicator
518
553
  */
519
554
  hasFoldIndicator(line) {
520
- return this.regions.some(r => r.startLine === line);
555
+ return this.regions.some((r) => r.startLine === line);
521
556
  }
522
557
  /**
523
558
  * Check if the fold at this line is collapsed
@@ -37,17 +37,13 @@ export function positionsEqual(a, b) {
37
37
  * Get the start position of a selection (min of anchor and head)
38
38
  */
39
39
  export function getSelectionStart(selection) {
40
- return isPositionBefore(selection.anchor, selection.head)
41
- ? selection.anchor
42
- : selection.head;
40
+ return isPositionBefore(selection.anchor, selection.head) ? selection.anchor : selection.head;
43
41
  }
44
42
  /**
45
43
  * Get the end position of a selection (max of anchor and head)
46
44
  */
47
45
  export function getSelectionEnd(selection) {
48
- return isPositionBefore(selection.anchor, selection.head)
49
- ? selection.head
50
- : selection.anchor;
46
+ return isPositionBefore(selection.anchor, selection.head) ? selection.head : selection.anchor;
51
47
  }
52
48
  /**
53
49
  * Check if a selection is empty (anchor equals head)
@@ -220,7 +216,7 @@ export class CursorManager {
220
216
  }
221
217
  // Get all non-primary cursors sorted by ID (most recent last)
222
218
  const secondaries = [...this.cursors.values()]
223
- .filter(c => !c.isPrimary)
219
+ .filter((c) => !c.isPrimary)
224
220
  .sort((a, b) => {
225
221
  // Extract numeric part of ID for comparison
226
222
  const idA = parseInt(a.id.replace('cursor-', ''), 10);
@@ -501,7 +497,7 @@ export class CursorManager {
501
497
  */
502
498
  clone() {
503
499
  return {
504
- cursors: [...this.cursors.values()].map(c => ({
500
+ cursors: [...this.cursors.values()].map((c) => ({
505
501
  id: c.id,
506
502
  selection: {
507
503
  anchor: { ...c.selection.anchor },
@@ -300,9 +300,7 @@ export class Navigation {
300
300
  const { line, column } = this.state.cursor;
301
301
  const targetLine = Math.max(0, line - pageSize);
302
302
  const targetLineContent = this.state.getLine(targetLine);
303
- const newColumn = targetLineContent
304
- ? Math.min(column, targetLineContent.text.length)
305
- : 0;
303
+ const newColumn = targetLineContent ? Math.min(column, targetLineContent.text.length) : 0;
306
304
  this.moveTo({ line: targetLine, column: newColumn }, extend);
307
305
  }
308
306
  /**
@@ -312,9 +310,7 @@ export class Navigation {
312
310
  const { line, column } = this.state.cursor;
313
311
  const targetLine = Math.min(this.state.lineCount - 1, line + pageSize);
314
312
  const targetLineContent = this.state.getLine(targetLine);
315
- const newColumn = targetLineContent
316
- ? Math.min(column, targetLineContent.text.length)
317
- : 0;
313
+ const newColumn = targetLineContent ? Math.min(column, targetLineContent.text.length) : 0;
318
314
  this.moveTo({ line: targetLine, column: newColumn }, extend);
319
315
  }
320
316
  // ============================================
@@ -43,13 +43,15 @@ export class QuickActionsManager {
43
43
  kind: 'quickfix',
44
44
  description: 'Remove the declaration of unused variable',
45
45
  edit: {
46
- changes: [{
46
+ changes: [
47
+ {
47
48
  range: {
48
49
  start: { line: ctx.position.line, column: 0 },
49
50
  end: { line: ctx.position.line + 1, column: 0 }
50
51
  },
51
52
  newText: ''
52
- }]
53
+ }
54
+ ]
53
55
  }
54
56
  });
55
57
  }
@@ -66,13 +68,15 @@ export class QuickActionsManager {
66
68
  kind: 'quickfix',
67
69
  isPreferred: true,
68
70
  edit: {
69
- changes: [{
71
+ changes: [
72
+ {
70
73
  range: {
71
74
  start: { line: ctx.position.line, column: ctx.lineContent.trimEnd().length },
72
75
  end: { line: ctx.position.line, column: ctx.lineContent.trimEnd().length }
73
76
  },
74
77
  newText: ';'
75
- }]
78
+ }
79
+ ]
76
80
  }
77
81
  });
78
82
  }
@@ -134,7 +138,8 @@ export class QuickActionsManager {
134
138
  });
135
139
  }
136
140
  // Convert to template literal
137
- if (ctx.lineContent.includes(' + ') && (ctx.lineContent.includes('"') || ctx.lineContent.includes("'"))) {
141
+ if (ctx.lineContent.includes(' + ') &&
142
+ (ctx.lineContent.includes('"') || ctx.lineContent.includes("'"))) {
138
143
  actions.push({
139
144
  id: 'convert-to-template',
140
145
  title: 'Convert to template literal',
@@ -245,15 +250,15 @@ export class QuickActionsManager {
245
250
  return 1;
246
251
  // Then by kind priority
247
252
  const kindPriority = {
248
- 'quickfix': 0,
253
+ quickfix: 0,
249
254
  'refactor.extract': 1,
250
255
  'refactor.inline': 2,
251
256
  'refactor.rename': 3,
252
- 'refactor': 4,
257
+ refactor: 4,
253
258
  'source.organizeImports': 5,
254
259
  'source.fixAll': 6,
255
- 'source': 7,
256
- 'generate': 8
260
+ source: 7,
261
+ generate: 8
257
262
  };
258
263
  return (kindPriority[a.kind] ?? 99) - (kindPriority[b.kind] ?? 99);
259
264
  });
@@ -400,10 +405,10 @@ export function groupActionsByKind(actions) {
400
405
  */
401
406
  export function getKindLabel(kind) {
402
407
  const labels = {
403
- 'quickfix': 'Quick Fix',
404
- 'refactor': 'Refactor',
405
- 'source': 'Source Action',
406
- 'generate': 'Generate'
408
+ quickfix: 'Quick Fix',
409
+ refactor: 'Refactor',
410
+ source: 'Source Action',
411
+ generate: 'Generate'
407
412
  };
408
413
  const baseKind = kind.split('.')[0];
409
414
  return labels[baseKind] || kind;
@@ -413,15 +418,15 @@ export function getKindLabel(kind) {
413
418
  */
414
419
  export function getKindIcon(kind) {
415
420
  const icons = {
416
- 'quickfix': '🔧',
417
- 'refactor': '✨',
421
+ quickfix: '🔧',
422
+ refactor: '✨',
418
423
  'refactor.extract': '📤',
419
424
  'refactor.inline': '📥',
420
425
  'refactor.rename': '✏️',
421
- 'source': '📋',
426
+ source: '📋',
422
427
  'source.organizeImports': '📦',
423
428
  'source.fixAll': '✅',
424
- 'generate': '⚡'
429
+ generate: '⚡'
425
430
  };
426
431
  return icons[kind] || icons[kind.split('.')[0]] || '💡';
427
432
  }
@@ -135,9 +135,7 @@ export function replaceMatch(lines, match, replacement) {
135
135
  const line = newLines[match.line];
136
136
  // Replace the match
137
137
  newLines[match.line] =
138
- line.substring(0, match.startColumn) +
139
- replacement +
140
- line.substring(match.endColumn);
138
+ line.substring(0, match.startColumn) + replacement + line.substring(match.endColumn);
141
139
  const lengthDiff = replacement.length - match.text.length;
142
140
  return {
143
141
  newLines,
@@ -165,9 +163,7 @@ export function replaceAllMatches(lines, matches, replacement) {
165
163
  for (const match of sortedMatches) {
166
164
  const line = newLines[match.line];
167
165
  newLines[match.line] =
168
- line.substring(0, match.startColumn) +
169
- replacement +
170
- line.substring(match.endColumn);
166
+ line.substring(0, match.startColumn) + replacement + line.substring(match.endColumn);
171
167
  }
172
168
  return newLines;
173
169
  }
@@ -229,9 +229,7 @@ export class SemanticAnalyzer {
229
229
  if (config.linePattern) {
230
230
  if (config.linePattern.test(text)) {
231
231
  // Find or create a contiguous region
232
- const lastRegion = regions.find((r) => r.category === category &&
233
- r.endLine === i - 1 &&
234
- !config.blockBased);
232
+ const lastRegion = regions.find((r) => r.category === category && r.endLine === i - 1 && !config.blockBased);
235
233
  if (lastRegion) {
236
234
  lastRegion.endLine = i;
237
235
  }
@@ -324,8 +324,7 @@ export class SnippetManager {
324
324
  findByPrefix(prefix, language) {
325
325
  const snippets = this.getSnippetsForLanguage(language);
326
326
  const lowerPrefix = prefix.toLowerCase();
327
- return snippets.filter((s) => s.prefix.toLowerCase().startsWith(lowerPrefix) ||
328
- s.name.toLowerCase().includes(lowerPrefix));
327
+ return snippets.filter((s) => s.prefix.toLowerCase().startsWith(lowerPrefix) || s.name.toLowerCase().includes(lowerPrefix));
329
328
  }
330
329
  /**
331
330
  * Get snippet by ID
@@ -507,7 +506,9 @@ export class SnippetManager {
507
506
  */
508
507
  getCategories(language) {
509
508
  const categories = new Set();
510
- const snippets = language ? this.getSnippetsForLanguage(language) : [...this.snippets.values(), ...this.userSnippets.values()];
509
+ const snippets = language
510
+ ? this.getSnippetsForLanguage(language)
511
+ : [...this.snippets.values(), ...this.userSnippets.values()];
511
512
  for (const snippet of snippets) {
512
513
  categories.add(snippet.category);
513
514
  }
@@ -610,10 +610,10 @@ export class EditorState {
610
610
  saveHistory(changeType) {
611
611
  const now = Date.now();
612
612
  // Check if we should merge with the last history entry
613
- const shouldMerge = (changeType &&
613
+ const shouldMerge = changeType &&
614
614
  changeType === this.lastHistoryType &&
615
615
  this._undoStack.length > 0 &&
616
- now - this.lastHistoryTimestamp < this.historyGroupTimeout);
616
+ now - this.lastHistoryTimestamp < this.historyGroupTimeout;
617
617
  // Save cursor state
618
618
  const cursorState = this._cursorManager.clone();
619
619
  const entry = {
@@ -104,9 +104,7 @@ export class TimelineManager {
104
104
  */
105
105
  captureOnEdit(content, author) {
106
106
  const lineCount = content.split('\n').length;
107
- const lastLineCount = this.snapshots.length > 0
108
- ? this.snapshots[this.snapshots.length - 1].lineCount
109
- : 0;
107
+ const lastLineCount = this.snapshots.length > 0 ? this.snapshots[this.snapshots.length - 1].lineCount : 0;
110
108
  const linesDiff = Math.abs(lineCount - lastLineCount);
111
109
  const timeSinceLastSnapshot = Date.now() - this.lastSnapshotTime;
112
110
  // Capture if significant change or enough time passed
@@ -36,9 +36,7 @@ export function createEditorInput(deps) {
36
36
  return 0;
37
37
  let visualCol = 0; // visual column in "cells" (each cell == charWidth px)
38
38
  for (let i = 0; i < lineText.length; i++) {
39
- const cellsForChar = lineText[i] === '\t'
40
- ? tabSize - (visualCol % tabSize)
41
- : 1;
39
+ const cellsForChar = lineText[i] === '\t' ? tabSize - (visualCol % tabSize) : 1;
42
40
  const startPx = visualCol * charWidth;
43
41
  const widthPx = cellsForChar * charWidth;
44
42
  // If x falls within the first half of this character's width, the
@@ -289,7 +287,12 @@ export function createEditorInput(deps) {
289
287
  }
290
288
  // Let keyboard handler process it
291
289
  const handled = deps.getKeyboardHandler().handleKeyDown(e, deps.isReadonly());
292
- if (!handled && !deps.isReadonly() && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
290
+ if (!handled &&
291
+ !deps.isReadonly() &&
292
+ e.key.length === 1 &&
293
+ !e.ctrlKey &&
294
+ !e.metaKey &&
295
+ !e.altKey) {
293
296
  // Regular character input - handled by input event
294
297
  }
295
298
  }
@@ -351,7 +354,7 @@ export function createEditorInput(deps) {
351
354
  }
352
355
  else if (deps.hasMultipleCursors()) {
353
356
  // Multiple cursors with no selection: copy line at each cursor
354
- const lines = cursors.map(c => editorState.getLine(c.selection.head.line)?.text ?? '');
357
+ const lines = cursors.map((c) => editorState.getLine(c.selection.head.line)?.text ?? '');
355
358
  e.clipboardData?.setData('text/plain', lines.join('\n') + '\n');
356
359
  }
357
360
  else {
@@ -376,7 +379,7 @@ export function createEditorInput(deps) {
376
379
  }
377
380
  else if (deps.hasMultipleCursors()) {
378
381
  // Multiple cursors with no selection: cut line at each cursor
379
- const lines = cursors.map(c => editorState.getLine(c.selection.head.line)?.text ?? '');
382
+ const lines = cursors.map((c) => editorState.getLine(c.selection.head.line)?.text ?? '');
380
383
  e.clipboardData?.setData('text/plain', lines.join('\n') + '\n');
381
384
  // Select and delete each line (in reverse order to maintain positions)
382
385
  const sortedCursors = [...cursors].sort((a, b) => b.selection.head.line - a.selection.head.line);
@@ -123,7 +123,7 @@ export function selectNextOccurrence(state) {
123
123
  if (matchStart.line > lastCursorEnd.line ||
124
124
  (matchStart.line === lastCursorEnd.line && matchStart.column >= lastCursorEnd.column)) {
125
125
  // Check if this match is already covered by a cursor
126
- const alreadySelected = currentCursors.some(cursor => {
126
+ const alreadySelected = currentCursors.some((cursor) => {
127
127
  const start = getSelectionStart(cursor.selection);
128
128
  return start.line === matchStart.line && start.column === matchStart.column;
129
129
  });
@@ -137,7 +137,7 @@ export function selectNextOccurrence(state) {
137
137
  if (!foundMatch) {
138
138
  for (const match of matches) {
139
139
  const matchStart = match.start;
140
- const alreadySelected = currentCursors.some(cursor => {
140
+ const alreadySelected = currentCursors.some((cursor) => {
141
141
  const start = getSelectionStart(cursor.selection);
142
142
  return start.line === matchStart.line && start.column === matchStart.column;
143
143
  });