@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.
- package/README.md +43 -47
- package/dist/components/agents/AgentActivityPanel.svelte +3 -1
- package/dist/components/agents/AgentAvatar.svelte +127 -35
- package/dist/components/agents/AgentCursor.svelte +15 -5
- package/dist/components/agents/AgentPresenceBar.svelte +7 -2
- package/dist/components/ai/AIConversationList.svelte +39 -34
- package/dist/components/ai/AIInlineEdit.svelte +1 -3
- package/dist/components/ai/AIMessage.svelte +5 -5
- package/dist/components/ai/AIMessageActions.svelte +18 -3
- package/dist/components/ai/AIMessageContent.svelte +22 -20
- package/dist/components/ai/AIPanel.svelte +17 -9
- package/dist/components/ai/AISuggestionWidget.svelte +1 -3
- package/dist/components/ai/AIToolCallDisplay.svelte +10 -14
- package/dist/components/core/Badge.svelte +9 -1
- package/dist/components/core/ConnectionStatus.svelte +73 -68
- package/dist/components/core/ErrorBoundary.svelte +56 -56
- package/dist/components/core/ErrorBoundary.svelte.d.ts +5 -5
- package/dist/components/core/Icon.svelte +22 -11
- package/dist/components/core/ResizeHandle.svelte +1 -1
- package/dist/components/core/Tooltip.svelte +1 -7
- package/dist/components/editor/AIFocusLayer.svelte +15 -7
- package/dist/components/editor/Breadcrumbs.svelte +18 -6
- package/dist/components/editor/BreakpointLayer.svelte +51 -60
- package/dist/components/editor/CognitiveLoadMeter.svelte +4 -2
- package/dist/components/editor/CollaborativeEditor.svelte +1 -5
- package/dist/components/editor/CommandPalette.svelte +1 -4
- package/dist/components/editor/ComplexityLayer.svelte +8 -6
- package/dist/components/editor/ConflictZoneLayer.svelte +2 -8
- package/dist/components/editor/ContextLens.svelte +1 -4
- package/dist/components/editor/CustomEditor.svelte +85 -41
- package/dist/components/editor/DebugConsole.svelte +8 -17
- package/dist/components/editor/EchoCursorLayer.svelte +3 -1
- package/dist/components/editor/EditorGutter.svelte +8 -2
- package/dist/components/editor/EditorLines.svelte +6 -3
- package/dist/components/editor/EditorPane.svelte +1 -6
- package/dist/components/editor/EditorSelections.svelte +29 -11
- package/dist/components/editor/FileExplorer.svelte +26 -4
- package/dist/components/editor/FileIcon.svelte +3 -1
- package/dist/components/editor/FindReplace.svelte +16 -4
- package/dist/components/editor/GhostBracketLayer.svelte +2 -2
- package/dist/components/editor/GitBlameLayer.svelte +2 -1
- package/dist/components/editor/InlineDiagnosticsLayer.svelte +4 -13
- package/dist/components/editor/InlineDiffLayer.svelte +18 -9
- package/dist/components/editor/Minimap.svelte +16 -9
- package/dist/components/editor/PluginPreviewSandbox.svelte +3 -2
- package/dist/components/editor/ProblemsPanel.svelte +11 -35
- package/dist/components/editor/QuickActionsMenu.svelte +5 -14
- package/dist/components/editor/SnippetPalette.svelte +11 -12
- package/dist/components/editor/StructureMap.svelte +2 -1
- package/dist/components/editor/SymbolOutline.svelte +14 -19
- package/dist/components/editor/TimelineScrubber.svelte +7 -6
- package/dist/components/editor/core/complexity-analyzer.js +42 -12
- package/dist/components/editor/core/conflict-predictor.js +2 -4
- package/dist/components/editor/core/folding.d.ts +9 -0
- package/dist/components/editor/core/folding.js +40 -5
- package/dist/components/editor/core/multi-cursor.js +4 -8
- package/dist/components/editor/core/navigation.js +2 -6
- package/dist/components/editor/core/quick-actions.js +22 -17
- package/dist/components/editor/core/search.js +2 -6
- package/dist/components/editor/core/semantic-analyzer.js +1 -3
- package/dist/components/editor/core/snippet-manager.js +4 -3
- package/dist/components/editor/core/state.js +2 -2
- package/dist/components/editor/core/timeline.js +1 -3
- package/dist/components/editor/editor-input.js +9 -6
- package/dist/components/editor/editor-multicursor.js +2 -2
- package/dist/components/editor/tokenizer/languages/css.js +146 -24
- package/dist/components/editor/tokenizer/languages/go.js +76 -13
- package/dist/components/editor/tokenizer/languages/javascript.js +210 -29
- package/dist/components/editor/tokenizer/languages/python.js +116 -19
- package/dist/components/editor/tokenizer/languages/svelte.js +20 -7
- package/dist/components/layout/IDELayout.svelte +6 -2
- package/dist/components/layout/StatusBar.svelte +32 -20
- package/dist/components/lsp/AutocompleteWidget.svelte +19 -19
- package/dist/components/lsp/DiagnosticMarker.svelte +61 -52
- package/dist/components/lsp/DiagnosticsPanel.svelte +45 -27
- package/dist/components/lsp/HoverTooltip.svelte +56 -61
- package/dist/components/lsp/LSPEditor.svelte +7 -18
- package/dist/components/lsp/SignatureHelpWidget.svelte +12 -9
- package/dist/components/plugins/PluginCard.svelte +3 -13
- package/dist/components/plugins/PluginProposalForm.svelte +19 -31
- package/dist/components/vfs/LockConflictDialog.svelte +112 -45
- package/dist/components/vfs/LockIndicator.svelte +0 -1
- package/dist/components/vfs/LockOverlay.svelte +53 -53
- package/dist/components/vfs/LockOverlay.svelte.d.ts +5 -5
- package/dist/components/vfs/VersionConflictDialog.svelte +107 -77
- package/dist/services/error-handling.js +1 -7
- package/dist/services/mock-ai.js +9 -7
- package/dist/stores/agents.svelte.js +50 -10
- package/dist/stores/ai.svelte.js +66 -18
- package/dist/stores/collaboration.svelte.js +70 -14
- package/dist/stores/editor.svelte.js +50 -10
- package/dist/stores/plugin.svelte.js +60 -12
- package/dist/stores/vfs.svelte.js +77 -19
- package/dist/styles/theme.css +16 -7
- 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) => {
|
|
389
|
-
|
|
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">
|
|
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:
|
|
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({
|
|
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 ||
|
|
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) ||
|
|
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([
|
|
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
|
|
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',
|
|
32
|
-
'
|
|
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
|
|
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(' + ') &&
|
|
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
|
-
|
|
253
|
+
quickfix: 0,
|
|
249
254
|
'refactor.extract': 1,
|
|
250
255
|
'refactor.inline': 2,
|
|
251
256
|
'refactor.rename': 3,
|
|
252
|
-
|
|
257
|
+
refactor: 4,
|
|
253
258
|
'source.organizeImports': 5,
|
|
254
259
|
'source.fixAll': 6,
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
417
|
-
|
|
421
|
+
quickfix: '🔧',
|
|
422
|
+
refactor: '✨',
|
|
418
423
|
'refactor.extract': '📤',
|
|
419
424
|
'refactor.inline': '📥',
|
|
420
425
|
'refactor.rename': '✏️',
|
|
421
|
-
|
|
426
|
+
source: '📋',
|
|
422
427
|
'source.organizeImports': '📦',
|
|
423
428
|
'source.fixAll': '✅',
|
|
424
|
-
|
|
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
|
|
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 =
|
|
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 &&
|
|
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
|
});
|