@nocturnium/svelte-ide 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ai/AIPanel.svelte +6 -1
- package/dist/components/ai/AIToolCallDisplay.svelte +1 -1
- package/dist/components/core/Avatar.svelte +14 -14
- package/dist/components/core/Badge.svelte +2 -2
- package/dist/components/core/Button.svelte +2 -2
- package/dist/components/editor/AIFocusLayer.svelte +3 -3
- package/dist/components/editor/CognitiveLoadMeter.svelte +15 -15
- package/dist/components/editor/CommandPalette.svelte +17 -17
- package/dist/components/editor/ComplexityLayer.svelte +11 -9
- package/dist/components/editor/ConflictZoneLayer.svelte +8 -8
- package/dist/components/editor/CustomEditor.svelte +34 -12
- package/dist/components/editor/CustomEditor.svelte.d.ts +5 -1
- package/dist/components/editor/FileIcon.svelte +2 -1
- package/dist/components/editor/GitBlameLayer.svelte +8 -8
- package/dist/components/editor/InlineDiffLayer.svelte +12 -12
- package/dist/components/editor/PluginPreviewSandbox.svelte +21 -21
- package/dist/components/editor/SnippetPalette.svelte +21 -21
- package/dist/components/editor/StructureMap.svelte +47 -30
- package/dist/components/editor/TimelineScrubber.svelte +11 -11
- package/dist/components/editor/core/complexity-analyzer.d.ts +4 -0
- package/dist/components/editor/core/complexity-analyzer.js +70 -12
- package/dist/components/editor/core/diagnostics.js +4 -4
- package/dist/components/editor/core/semantic-analyzer.js +13 -1
- package/dist/components/editor/core/structure-layout.d.ts +31 -0
- package/dist/components/editor/core/structure-layout.js +64 -0
- package/dist/styles/theme.css +24 -0
- package/package.json +1 -1
|
@@ -19,7 +19,11 @@ const THRESHOLDS = {
|
|
|
19
19
|
* Weights for different complexity factors
|
|
20
20
|
*/
|
|
21
21
|
const WEIGHTS = {
|
|
22
|
-
|
|
22
|
+
// Nesting is the dominant cognitive-load factor, but 15/level over-rated
|
|
23
|
+
// shallow code: a function with two nested `if`s landed at ~71 ("High").
|
|
24
|
+
// 12/level keeps deeply-nested code critical while letting genuinely medium
|
|
25
|
+
// code read as medium.
|
|
26
|
+
nestingDepth: 12,
|
|
23
27
|
branchingFactor: 8,
|
|
24
28
|
lineCount: 0.3,
|
|
25
29
|
identifierCount: 0.2,
|
|
@@ -77,12 +81,15 @@ export class ComplexityAnalyzer {
|
|
|
77
81
|
* Get complexity for a specific line
|
|
78
82
|
*/
|
|
79
83
|
getLineComplexity(metrics, line) {
|
|
84
|
+
// A line can sit inside several nested regions; report the highest score
|
|
85
|
+
// so an inner low-complexity region never masks the hot function around it.
|
|
86
|
+
let best = 0;
|
|
80
87
|
for (const region of metrics.regions) {
|
|
81
|
-
if (line >= region.startLine && line <= region.endLine) {
|
|
82
|
-
|
|
88
|
+
if (line >= region.startLine && line <= region.endLine && region.score > best) {
|
|
89
|
+
best = region.score;
|
|
83
90
|
}
|
|
84
91
|
}
|
|
85
|
-
return
|
|
92
|
+
return best;
|
|
86
93
|
}
|
|
87
94
|
/**
|
|
88
95
|
* Check if a line is a hotspot
|
|
@@ -117,10 +124,21 @@ export class ComplexityAnalyzer {
|
|
|
117
124
|
// Track blocks with their brace depth at push time
|
|
118
125
|
const blockStack = [];
|
|
119
126
|
let braceDepth = 0;
|
|
127
|
+
let pendingDef;
|
|
120
128
|
for (let i = 0; i < lines.length; i++) {
|
|
121
129
|
const text = lines[i].text;
|
|
122
130
|
// Check for function/class definition
|
|
123
131
|
const funcMatch = text.match(PATTERNS.functionDef);
|
|
132
|
+
const currentBlock = blockStack[blockStack.length - 1];
|
|
133
|
+
const defCandidate = this.getDefinitionCandidate(text, funcMatch, currentBlock?.type === 'class' && braceDepth === currentBlock.depth);
|
|
134
|
+
if (defCandidate) {
|
|
135
|
+
pendingDef = {
|
|
136
|
+
line: i,
|
|
137
|
+
type: defCandidate.type,
|
|
138
|
+
name: defCandidate.name,
|
|
139
|
+
depth: braceDepth
|
|
140
|
+
};
|
|
141
|
+
}
|
|
124
142
|
// Process character by character to properly match braces
|
|
125
143
|
// Skip braces inside strings and comments
|
|
126
144
|
let inString = null;
|
|
@@ -150,13 +168,16 @@ export class ComplexityAnalyzer {
|
|
|
150
168
|
if (c === '{') {
|
|
151
169
|
braceDepth++;
|
|
152
170
|
// If this is the opening brace of a function/class def, push it
|
|
153
|
-
if (
|
|
171
|
+
if (pendingDef &&
|
|
172
|
+
braceDepth === pendingDef.depth + 1 &&
|
|
173
|
+
this.isDefOpeningBrace(text, ch, pendingDef.type)) {
|
|
154
174
|
blockStack.push({
|
|
155
|
-
line:
|
|
156
|
-
type:
|
|
157
|
-
name:
|
|
175
|
+
line: pendingDef.line,
|
|
176
|
+
type: pendingDef.type,
|
|
177
|
+
name: pendingDef.name,
|
|
158
178
|
depth: braceDepth
|
|
159
179
|
});
|
|
180
|
+
pendingDef = undefined;
|
|
160
181
|
}
|
|
161
182
|
else if (!funcMatch ||
|
|
162
183
|
braceDepth > (blockStack.length > 0 ? blockStack[blockStack.length - 1].depth : 0) + 1) {
|
|
@@ -185,6 +206,9 @@ export class ComplexityAnalyzer {
|
|
|
185
206
|
}
|
|
186
207
|
}
|
|
187
208
|
braceDepth = Math.max(0, braceDepth - 1);
|
|
209
|
+
if (pendingDef && braceDepth < pendingDef.depth) {
|
|
210
|
+
pendingDef = undefined;
|
|
211
|
+
}
|
|
188
212
|
}
|
|
189
213
|
}
|
|
190
214
|
}
|
|
@@ -202,15 +226,49 @@ export class ComplexityAnalyzer {
|
|
|
202
226
|
/**
|
|
203
227
|
* Check if a `{` at position `ch` is the opening brace of a function/class definition
|
|
204
228
|
*/
|
|
205
|
-
isDefOpeningBrace(text, ch,
|
|
229
|
+
isDefOpeningBrace(text, ch, type) {
|
|
206
230
|
// For class: `class Foo {` — the `{` follows the class name
|
|
207
|
-
if (
|
|
231
|
+
if (type === 'class') {
|
|
208
232
|
return true; // First `{` on a class line is the class body
|
|
209
233
|
}
|
|
210
234
|
// For functions: the `{` should follow the parameter list closing `)`
|
|
211
|
-
// or follow `=>` for arrow functions
|
|
235
|
+
// with an optional TypeScript return type, or follow `=>` for arrow functions.
|
|
212
236
|
const before = text.slice(0, ch).trimEnd();
|
|
213
|
-
return
|
|
237
|
+
return /\)\s*(:\s*[^{};]+)?\s*$/.test(before) || before.endsWith('=>');
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Extract a definition candidate before its body brace is seen.
|
|
241
|
+
*/
|
|
242
|
+
getDefinitionCandidate(text, funcMatch, allowMethodStyle) {
|
|
243
|
+
if (funcMatch) {
|
|
244
|
+
// The arrow-assignment branch (`name = (`) also matches a parenthesised
|
|
245
|
+
// expression like `const x = (a - b) / c`, which is NOT a function.
|
|
246
|
+
// Only treat it as one when the line actually starts an arrow: it
|
|
247
|
+
// contains `=>`, or it has an unclosed `(` that opens a multi-line arrow
|
|
248
|
+
// signature. Otherwise the stale candidate gets attached to the next
|
|
249
|
+
// `if (...) {` block and a phantom region is reported.
|
|
250
|
+
const isArrowAssignment = !!funcMatch[3] && !funcMatch[2] && !funcMatch[4] && !funcMatch[5];
|
|
251
|
+
if (isArrowAssignment) {
|
|
252
|
+
const opens = (text.match(/\(/g) || []).length;
|
|
253
|
+
const closes = (text.match(/\)/g) || []).length;
|
|
254
|
+
if (!text.includes('=>') && opens <= closes) {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
type: funcMatch[5] ? 'class' : 'function',
|
|
260
|
+
name: funcMatch[2] || funcMatch[3] || funcMatch[4] || funcMatch[5]
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if (!allowMethodStyle) {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
const trimmed = text.trim();
|
|
267
|
+
const methodMatch = trimmed.match(/^(?:(?:public|private|protected|static|async|readonly|override)\s+)*(?!(?:if|else|for|while|do|switch|try|catch|finally|with|return|throw|new|typeof|void|delete|await|yield)\b)(\w+)\s*\(/);
|
|
268
|
+
if (methodMatch) {
|
|
269
|
+
return { type: 'function', name: methodMatch[1] };
|
|
270
|
+
}
|
|
271
|
+
return undefined;
|
|
214
272
|
}
|
|
215
273
|
/**
|
|
216
274
|
* Analyze a specific region
|
|
@@ -235,13 +235,13 @@ export function getSeverityIcon(severity) {
|
|
|
235
235
|
export function getSeverityColor(severity) {
|
|
236
236
|
switch (severity) {
|
|
237
237
|
case 'error':
|
|
238
|
-
return '
|
|
238
|
+
return 'var(--ide-error)';
|
|
239
239
|
case 'warning':
|
|
240
|
-
return '
|
|
240
|
+
return 'var(--ide-warning)';
|
|
241
241
|
case 'info':
|
|
242
|
-
return '
|
|
242
|
+
return 'var(--ide-info)';
|
|
243
243
|
case 'hint':
|
|
244
|
-
return '
|
|
244
|
+
return 'var(--ide-text-muted)';
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
/**
|
|
@@ -71,8 +71,9 @@ const JAVASCRIPT_PATTERNS = new Map([
|
|
|
71
71
|
[
|
|
72
72
|
'function',
|
|
73
73
|
{
|
|
74
|
-
startPattern: /^(export\s+)?(async\s+)?function\s+(\w+)|^const\s+(\w+)\s*=\s*(async\s*)?\([^)]*\)\s
|
|
74
|
+
startPattern: /^(export\s+)?(async\s+)?function\s+(\w+)|^(?:export\s+)?const\s+(\w+)\s*=\s*(async\s*)?\([^)]*\)\s*(?::[^=]+)?=>/,
|
|
75
75
|
blockBased: true,
|
|
76
|
+
expressionBody: true,
|
|
76
77
|
getLabel: (m) => `Function: ${m[3] || m[4] || 'anonymous'}`
|
|
77
78
|
}
|
|
78
79
|
],
|
|
@@ -298,6 +299,17 @@ export class SemanticAnalyzer {
|
|
|
298
299
|
label: config.getLabel?.(match)
|
|
299
300
|
});
|
|
300
301
|
}
|
|
302
|
+
else if (match && config.expressionBody && text.includes('=>')) {
|
|
303
|
+
// Brace-less expression-bodied arrow (e.g. `export const f = () => x`):
|
|
304
|
+
// no block to track, so record it as a single-line region.
|
|
305
|
+
regions.push({
|
|
306
|
+
category,
|
|
307
|
+
startLine: i,
|
|
308
|
+
endLine: i,
|
|
309
|
+
confidence: 0.85,
|
|
310
|
+
label: config.getLabel?.(match)
|
|
311
|
+
});
|
|
312
|
+
}
|
|
301
313
|
}
|
|
302
314
|
}
|
|
303
315
|
// Update depth
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structure Map row layout
|
|
3
|
+
*
|
|
4
|
+
* The Structure Map places one labeled row per semantic region. Rows want to sit
|
|
5
|
+
* at a position proportional to their start line (so the map reads like a
|
|
6
|
+
* minimap), but every label needs a minimum pixel height to stay legible. With
|
|
7
|
+
* pure proportional placement, regions that start on the same line (e.g. the
|
|
8
|
+
* `exports` and `class` regions of `export class Foo`) or that cluster densely
|
|
9
|
+
* (a run of test hooks) overprint each other into illegible mush.
|
|
10
|
+
*
|
|
11
|
+
* `layoutStructureRows` resolves non-overlapping vertical offsets:
|
|
12
|
+
* - pass 1 (top-down): no row sits closer than `rowHeight` below the previous
|
|
13
|
+
* one, preserving proportional placement wherever there is room;
|
|
14
|
+
* - pass 2 (bottom-up): if the de-collided stack overruns the available height,
|
|
15
|
+
* pull rows back up so the last one ends exactly at `height`, keeping the
|
|
16
|
+
* minimum spacing intact.
|
|
17
|
+
*
|
|
18
|
+
* When there are genuinely more rows than can fit (`rows * rowHeight > height`)
|
|
19
|
+
* the stack is floored at the top and rows are placed sequentially; callers are
|
|
20
|
+
* expected to clip the overflow (`overflow: hidden`).
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Resolve non-overlapping top offsets (in px) for structure-map rows.
|
|
24
|
+
*
|
|
25
|
+
* @param startLines 0-based start line of each row, in render order.
|
|
26
|
+
* @param totalLines Total line count the map represents.
|
|
27
|
+
* @param height Available height of the rendering area, in px.
|
|
28
|
+
* @param rowHeight Minimum legible height of a single row, in px.
|
|
29
|
+
* @returns Top offset in px for each input row, in the same order.
|
|
30
|
+
*/
|
|
31
|
+
export declare function layoutStructureRows(startLines: readonly number[], totalLines: number, height: number, rowHeight: number): number[];
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structure Map row layout
|
|
3
|
+
*
|
|
4
|
+
* The Structure Map places one labeled row per semantic region. Rows want to sit
|
|
5
|
+
* at a position proportional to their start line (so the map reads like a
|
|
6
|
+
* minimap), but every label needs a minimum pixel height to stay legible. With
|
|
7
|
+
* pure proportional placement, regions that start on the same line (e.g. the
|
|
8
|
+
* `exports` and `class` regions of `export class Foo`) or that cluster densely
|
|
9
|
+
* (a run of test hooks) overprint each other into illegible mush.
|
|
10
|
+
*
|
|
11
|
+
* `layoutStructureRows` resolves non-overlapping vertical offsets:
|
|
12
|
+
* - pass 1 (top-down): no row sits closer than `rowHeight` below the previous
|
|
13
|
+
* one, preserving proportional placement wherever there is room;
|
|
14
|
+
* - pass 2 (bottom-up): if the de-collided stack overruns the available height,
|
|
15
|
+
* pull rows back up so the last one ends exactly at `height`, keeping the
|
|
16
|
+
* minimum spacing intact.
|
|
17
|
+
*
|
|
18
|
+
* When there are genuinely more rows than can fit (`rows * rowHeight > height`)
|
|
19
|
+
* the stack is floored at the top and rows are placed sequentially; callers are
|
|
20
|
+
* expected to clip the overflow (`overflow: hidden`).
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Resolve non-overlapping top offsets (in px) for structure-map rows.
|
|
24
|
+
*
|
|
25
|
+
* @param startLines 0-based start line of each row, in render order.
|
|
26
|
+
* @param totalLines Total line count the map represents.
|
|
27
|
+
* @param height Available height of the rendering area, in px.
|
|
28
|
+
* @param rowHeight Minimum legible height of a single row, in px.
|
|
29
|
+
* @returns Top offset in px for each input row, in the same order.
|
|
30
|
+
*/
|
|
31
|
+
export function layoutStructureRows(startLines, totalLines, height, rowHeight) {
|
|
32
|
+
const n = startLines.length;
|
|
33
|
+
if (n === 0)
|
|
34
|
+
return [];
|
|
35
|
+
const span = Math.max(1, totalLines);
|
|
36
|
+
const tops = startLines.map((line) => (Math.max(0, line) / span) * height);
|
|
37
|
+
// Pass 1 — top-down: enforce the minimum gap, keeping proportional order.
|
|
38
|
+
for (let i = 1; i < n; i++) {
|
|
39
|
+
const min = tops[i - 1] + rowHeight;
|
|
40
|
+
if (tops[i] < min)
|
|
41
|
+
tops[i] = min;
|
|
42
|
+
}
|
|
43
|
+
// Pass 2 — bottom-up: if the stack overflows, slide it back into bounds.
|
|
44
|
+
if (height > 0 && tops[n - 1] + rowHeight > height) {
|
|
45
|
+
tops[n - 1] = height - rowHeight;
|
|
46
|
+
for (let i = n - 2; i >= 0; i--) {
|
|
47
|
+
const max = tops[i + 1] - rowHeight;
|
|
48
|
+
if (tops[i] > max)
|
|
49
|
+
tops[i] = max;
|
|
50
|
+
}
|
|
51
|
+
// More rows than fit: floor at the top and place sequentially. Some
|
|
52
|
+
// overlap is unavoidable here, but the result is deterministic and the
|
|
53
|
+
// caller clips it.
|
|
54
|
+
if (tops[0] < 0) {
|
|
55
|
+
tops[0] = 0;
|
|
56
|
+
for (let i = 1; i < n; i++) {
|
|
57
|
+
const min = tops[i - 1] + rowHeight;
|
|
58
|
+
if (tops[i] < min)
|
|
59
|
+
tops[i] = min;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return tops;
|
|
64
|
+
}
|
package/dist/styles/theme.css
CHANGED
|
@@ -65,6 +65,12 @@
|
|
|
65
65
|
--ide-interactive-muted: color-mix(in srgb, var(--ide-interactive) 70%, transparent);
|
|
66
66
|
--ide-interactive-rgb: 74, 141, 183;
|
|
67
67
|
|
|
68
|
+
/* Primary action — the signature warm ember accent, shared as a single
|
|
69
|
+
source of truth by primary buttons and badges so "primary" reads the
|
|
70
|
+
same everywhere. */
|
|
71
|
+
--ide-primary: var(--ide-interactive-active);
|
|
72
|
+
--ide-primary-hover: var(--ide-interactive-hover);
|
|
73
|
+
|
|
68
74
|
/* IDE Accent Colors */
|
|
69
75
|
--ide-accent: var(--color-nocturnium-wave);
|
|
70
76
|
--ide-accent-hover: var(--color-nocturnium-flame);
|
|
@@ -85,11 +91,14 @@
|
|
|
85
91
|
|
|
86
92
|
/* Plugin Status Colors */
|
|
87
93
|
--ide-plugin-draft: var(--ide-text-muted);
|
|
94
|
+
--ide-plugin-submitted: var(--ide-info);
|
|
88
95
|
--ide-plugin-reviewing: var(--color-nocturnium-aurora-yellow);
|
|
89
96
|
--ide-plugin-approved: var(--color-nocturnium-aurora-green);
|
|
97
|
+
--ide-plugin-testing: var(--color-nocturnium-teal);
|
|
90
98
|
--ide-plugin-rejected: var(--ide-error);
|
|
91
99
|
--ide-plugin-deploying: var(--color-nocturnium-aurora-blue);
|
|
92
100
|
--ide-plugin-deployed: var(--color-nocturnium-aurora-green);
|
|
101
|
+
--ide-plugin-rolled_back: var(--color-nocturnium-flame);
|
|
93
102
|
|
|
94
103
|
/* CRDT Collaboration Colors */
|
|
95
104
|
--ide-collab-cursor-1: var(--color-nocturnium-aurora-blue);
|
|
@@ -397,6 +406,11 @@
|
|
|
397
406
|
color: var(--ide-plugin-draft);
|
|
398
407
|
}
|
|
399
408
|
|
|
409
|
+
.ide-plugin-status--submitted {
|
|
410
|
+
background-color: color-mix(in srgb, var(--ide-plugin-submitted) 20%, transparent);
|
|
411
|
+
color: var(--ide-plugin-submitted);
|
|
412
|
+
}
|
|
413
|
+
|
|
400
414
|
.ide-plugin-status--reviewing {
|
|
401
415
|
background-color: color-mix(in srgb, var(--ide-plugin-reviewing) 20%, transparent);
|
|
402
416
|
color: var(--ide-plugin-reviewing);
|
|
@@ -407,6 +421,11 @@
|
|
|
407
421
|
color: var(--ide-plugin-approved);
|
|
408
422
|
}
|
|
409
423
|
|
|
424
|
+
.ide-plugin-status--testing {
|
|
425
|
+
background-color: color-mix(in srgb, var(--ide-plugin-testing) 20%, transparent);
|
|
426
|
+
color: var(--ide-plugin-testing);
|
|
427
|
+
}
|
|
428
|
+
|
|
410
429
|
.ide-plugin-status--rejected {
|
|
411
430
|
background-color: color-mix(in srgb, var(--ide-plugin-rejected) 20%, transparent);
|
|
412
431
|
color: var(--ide-plugin-rejected);
|
|
@@ -422,6 +441,11 @@
|
|
|
422
441
|
color: var(--ide-plugin-deployed);
|
|
423
442
|
}
|
|
424
443
|
|
|
444
|
+
.ide-plugin-status--rolled_back {
|
|
445
|
+
background-color: color-mix(in srgb, var(--ide-plugin-rolled_back) 20%, transparent);
|
|
446
|
+
color: var(--ide-plugin-rolled_back);
|
|
447
|
+
}
|
|
448
|
+
|
|
425
449
|
/* Agent Status Animations */
|
|
426
450
|
@keyframes ide-agent-pulse {
|
|
427
451
|
0%,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocturnium/svelte-ide",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Svelte 5 code editor and IDE building blocks — custom editor, syntax highlighting, code folding, multi-cursor, LSP client, and optional realtime collaboration.",
|
|
5
5
|
"author": "Nocturnium & Jordan Dziat <hello@nocturnium.ai> (https://nocturnium.ai)",
|
|
6
6
|
"license": "MIT",
|