@nocturnium/svelte-ide 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/editor/CognitiveLoadMeter.svelte +27 -0
- package/dist/components/editor/ComplexityHeatLayer.svelte +157 -0
- package/dist/components/editor/ComplexityHeatLayer.svelte.d.ts +24 -0
- package/dist/components/editor/ComplexityLayer.svelte +345 -110
- package/dist/components/editor/ComplexityLayer.svelte.d.ts +20 -0
- package/dist/components/editor/ConflictZoneLayer.svelte +22 -15
- package/dist/components/editor/CustomEditor.svelte +81 -1
- package/dist/components/editor/CustomEditor.svelte.d.ts +3 -1
- package/dist/components/editor/EchoCursorLayer.svelte +60 -0
- package/dist/components/editor/PluginPreviewSandbox.svelte +43 -9
- package/dist/components/editor/PluginPreviewSandbox.svelte.d.ts +4 -4
- package/dist/components/editor/core/complexity-analyzer.d.ts +31 -0
- package/dist/components/editor/core/complexity-analyzer.js +479 -29
- package/dist/components/editor/core/conflict-predictor.d.ts +32 -0
- package/dist/components/editor/core/conflict-predictor.js +55 -0
- package/dist/components/editor/core/crdt-binding.d.ts +4 -0
- package/dist/components/editor/core/crdt-binding.js +34 -9
- package/dist/components/editor/core/echo-cursor.d.ts +18 -1
- package/dist/components/editor/core/echo-cursor.js +117 -6
- package/dist/components/editor/core/extract-function.d.ts +27 -0
- package/dist/components/editor/core/extract-function.js +865 -0
- package/dist/components/editor/core/index.d.ts +1 -0
- package/dist/components/editor/core/index.js +1 -0
- package/dist/components/editor/core/state.d.ts +38 -5
- package/dist/components/editor/core/state.js +175 -98
- package/dist/components/editor/core/timeline.js +6 -1
- package/dist/components/editor/editor-find.js +15 -3
- package/dist/components/editor/theme.d.ts +8 -0
- package/dist/components/editor/theme.js +52 -0
- package/dist/services/lsp-client.d.ts +3 -0
- package/dist/services/lsp-client.js +86 -14
- package/dist/styles/theme.css +4 -1
- package/package.json +1 -1
|
@@ -20,11 +20,16 @@
|
|
|
20
20
|
import { createEditorInput } from './editor-input';
|
|
21
21
|
import { createEditorScroll } from './editor-scroll';
|
|
22
22
|
import ComplexityLayer from './ComplexityLayer.svelte';
|
|
23
|
+
import ComplexityHeatLayer from './ComplexityHeatLayer.svelte';
|
|
23
24
|
import AIFocusLayer from './AIFocusLayer.svelte';
|
|
24
25
|
import EditorSelections from './EditorSelections.svelte';
|
|
25
26
|
import EditorLines from './EditorLines.svelte';
|
|
26
27
|
import CommandPalette from './CommandPalette.svelte';
|
|
27
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
getComplexityAnalyzer,
|
|
30
|
+
type ComplexityMetrics,
|
|
31
|
+
type ComplexityRegion
|
|
32
|
+
} from './core/complexity-analyzer';
|
|
28
33
|
import { registerSemanticFoldCommands } from './core/commands';
|
|
29
34
|
import { getSemanticAnalyzer, type FoldPreset } from './core/semantic-analyzer';
|
|
30
35
|
import type { AIAwareness } from './core/ai-awareness';
|
|
@@ -127,6 +132,8 @@
|
|
|
127
132
|
const complexityAnalyzer = getComplexityAnalyzer();
|
|
128
133
|
let complexityMetrics = $state<ComplexityMetrics | null>(null);
|
|
129
134
|
let complexityUpdateTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
135
|
+
let complexityFlashTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
136
|
+
let complexityFlashRegionKey = $state('');
|
|
130
137
|
|
|
131
138
|
// DOM refs
|
|
132
139
|
let container: HTMLDivElement;
|
|
@@ -148,6 +155,7 @@
|
|
|
148
155
|
// Reactive viewport height of the scrollable content area. Drives line
|
|
149
156
|
// virtualization (only rows within scrollTop +/- viewport + overscan render).
|
|
150
157
|
let viewportHeight = $state(FALLBACK_VIEWPORT_HEIGHT);
|
|
158
|
+
let viewportWidth = $state(0);
|
|
151
159
|
|
|
152
160
|
// Selection rendering
|
|
153
161
|
let cursorVisible = $state(true);
|
|
@@ -545,6 +553,43 @@
|
|
|
545
553
|
foldManager.expandAll();
|
|
546
554
|
}
|
|
547
555
|
|
|
556
|
+
function getComplexityRegionKey(region: ComplexityRegion): string {
|
|
557
|
+
return `${region.startLine}:${region.endLine}:${region.name ?? region.type}:${region.score}`;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export function flashComplexityRegion(region: ComplexityRegion) {
|
|
561
|
+
if (complexityFlashTimeout) {
|
|
562
|
+
clearTimeout(complexityFlashTimeout);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
complexityFlashRegionKey = '';
|
|
566
|
+
requestAnimationFrame(() => {
|
|
567
|
+
complexityFlashRegionKey = getComplexityRegionKey(region);
|
|
568
|
+
complexityFlashTimeout = setTimeout(() => {
|
|
569
|
+
complexityFlashRegionKey = '';
|
|
570
|
+
complexityFlashTimeout = null;
|
|
571
|
+
}, 1000);
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Imperative API (accessible via `bind:this`): scroll a raw document line into
|
|
577
|
+
* view and optionally flash the matching complexity region.
|
|
578
|
+
*/
|
|
579
|
+
export async function scrollToLine(line: number, region?: ComplexityRegion) {
|
|
580
|
+
await tick();
|
|
581
|
+
if (!editorContent) return;
|
|
582
|
+
|
|
583
|
+
const row = lineToVisualRow(line);
|
|
584
|
+
const targetTop = row * lineHeight;
|
|
585
|
+
editorContent.scrollTop = Math.max(0, targetTop - editorContent.clientHeight * 0.3);
|
|
586
|
+
|
|
587
|
+
if (region) {
|
|
588
|
+
flashComplexityRegion(region);
|
|
589
|
+
}
|
|
590
|
+
hiddenInput?.focus();
|
|
591
|
+
}
|
|
592
|
+
|
|
548
593
|
function handleFoldIndicatorClick(lineNumber: number, e: MouseEvent) {
|
|
549
594
|
e.preventDefault();
|
|
550
595
|
e.stopPropagation();
|
|
@@ -579,6 +624,16 @@
|
|
|
579
624
|
// Total scrollable content height (in px) for the full document, accounting
|
|
580
625
|
// for folded/hidden lines. Drives the spacer so the scrollbar stays correct.
|
|
581
626
|
let totalContentHeight = $derived(Math.max(visibleLines.length, 1) * lineHeight);
|
|
627
|
+
let estimatedContentWidth = $derived.by(() => {
|
|
628
|
+
let maxLineLength = 0;
|
|
629
|
+
for (const line of lines) {
|
|
630
|
+
maxLineLength = Math.max(maxLineLength, line?.text.length ?? 0);
|
|
631
|
+
}
|
|
632
|
+
return Math.max(
|
|
633
|
+
viewportWidth,
|
|
634
|
+
gutterWidth + CONTENT_PADDING + maxLineLength * charWidth + CONTENT_PADDING
|
|
635
|
+
);
|
|
636
|
+
});
|
|
582
637
|
|
|
583
638
|
// Windowed slice of visibleLines: only the rows intersecting the viewport
|
|
584
639
|
// (plus overscan) are rendered to the DOM. Each entry keeps its absolute
|
|
@@ -768,6 +823,9 @@
|
|
|
768
823
|
$effect(() => {
|
|
769
824
|
if (editorState && language !== editorState.language) {
|
|
770
825
|
editorState.setLanguage(language);
|
|
826
|
+
if (complexityHighlighting) {
|
|
827
|
+
updateComplexityMetrics();
|
|
828
|
+
}
|
|
771
829
|
}
|
|
772
830
|
});
|
|
773
831
|
|
|
@@ -813,6 +871,7 @@
|
|
|
813
871
|
// Measure the initial viewport height for line virtualization.
|
|
814
872
|
if (editorContent) {
|
|
815
873
|
viewportHeight = editorContent.clientHeight || FALLBACK_VIEWPORT_HEIGHT;
|
|
874
|
+
viewportWidth = editorContent.clientWidth || 0;
|
|
816
875
|
}
|
|
817
876
|
|
|
818
877
|
// Register semantic fold commands for the command palette
|
|
@@ -845,6 +904,7 @@
|
|
|
845
904
|
inputHandlers.measureCharacter();
|
|
846
905
|
if (editorContent) {
|
|
847
906
|
viewportHeight = editorContent.clientHeight || FALLBACK_VIEWPORT_HEIGHT;
|
|
907
|
+
viewportWidth = editorContent.clientWidth || 0;
|
|
848
908
|
}
|
|
849
909
|
});
|
|
850
910
|
if (container) {
|
|
@@ -892,6 +952,11 @@
|
|
|
892
952
|
complexityUpdateTimeout = null;
|
|
893
953
|
}
|
|
894
954
|
|
|
955
|
+
if (complexityFlashTimeout) {
|
|
956
|
+
clearTimeout(complexityFlashTimeout);
|
|
957
|
+
complexityFlashTimeout = null;
|
|
958
|
+
}
|
|
959
|
+
|
|
895
960
|
// Clean up semantic fold command registration
|
|
896
961
|
unregisterSemanticFoldCommands?.();
|
|
897
962
|
unregisterSemanticFoldCommands = null;
|
|
@@ -1001,12 +1066,27 @@
|
|
|
1001
1066
|
>
|
|
1002
1067
|
<!-- Complexity highlighting layer (bottommost) -->
|
|
1003
1068
|
{#if complexityHighlighting && complexityMetrics}
|
|
1069
|
+
<ComplexityHeatLayer
|
|
1070
|
+
metrics={complexityMetrics}
|
|
1071
|
+
{lineHeight}
|
|
1072
|
+
{gutterWidth}
|
|
1073
|
+
totalHeight={totalContentHeight}
|
|
1074
|
+
contentWidth={estimatedContentWidth}
|
|
1075
|
+
minScore={complexityThreshold}
|
|
1076
|
+
enabled={complexityHighlighting}
|
|
1077
|
+
flashRegionKey={complexityFlashRegionKey}
|
|
1078
|
+
lineToVisualRow={(line) => lineToVisualRow(line)}
|
|
1079
|
+
/>
|
|
1004
1080
|
<ComplexityLayer
|
|
1005
1081
|
metrics={complexityMetrics}
|
|
1006
1082
|
{lineHeight}
|
|
1007
1083
|
{gutterWidth}
|
|
1084
|
+
totalHeight={totalContentHeight}
|
|
1085
|
+
contentWidth={estimatedContentWidth}
|
|
1008
1086
|
minScore={complexityThreshold}
|
|
1009
1087
|
enabled={complexityHighlighting}
|
|
1088
|
+
flashRegionKey={complexityFlashRegionKey}
|
|
1089
|
+
lineToVisualRow={(line) => lineToVisualRow(line)}
|
|
1010
1090
|
/>
|
|
1011
1091
|
{/if}
|
|
1012
1092
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { EditorPreferences } from '../../types';
|
|
2
2
|
import { type Cursor } from './core';
|
|
3
|
-
import { type ComplexityMetrics } from './core/complexity-analyzer';
|
|
3
|
+
import { type ComplexityMetrics, type ComplexityRegion } from './core/complexity-analyzer';
|
|
4
4
|
import { type FoldPreset } from './core/semantic-analyzer';
|
|
5
5
|
import type { AIAwareness } from './core/ai-awareness';
|
|
6
6
|
interface Props {
|
|
@@ -36,6 +36,8 @@ interface Props {
|
|
|
36
36
|
declare const CustomEditor: import("svelte").Component<Props, {
|
|
37
37
|
applyFoldPreset: (preset: FoldPreset) => void;
|
|
38
38
|
unfoldAll: () => void;
|
|
39
|
+
flashComplexityRegion: (region: ComplexityRegion) => void;
|
|
40
|
+
scrollToLine: (line: number, region?: ComplexityRegion) => Promise<void>;
|
|
39
41
|
}, "content">;
|
|
40
42
|
type CustomEditor = ReturnType<typeof CustomEditor>;
|
|
41
43
|
export default CustomEditor;
|
|
@@ -37,19 +37,23 @@
|
|
|
37
37
|
let echoCursors = $state<EchoCursor[]>([]);
|
|
38
38
|
let replayingCursors = $state<Set<string>>(new Set());
|
|
39
39
|
let recentReplay = new SvelteMap<string, { text: string; opacity: number }>();
|
|
40
|
+
let echoContents = $state<Record<string, string>>({});
|
|
40
41
|
|
|
41
42
|
// Subscribe to echo cursor events
|
|
42
43
|
onMount(() => {
|
|
43
44
|
// Initialize with current cursors
|
|
44
45
|
echoCursors = manager.getEchoCursors();
|
|
46
|
+
echoContents = getEchoContents();
|
|
45
47
|
|
|
46
48
|
const unsubscribe = manager.subscribe((event: EchoCursorEvent) => {
|
|
47
49
|
switch (event.type) {
|
|
48
50
|
case 'echo-added':
|
|
49
51
|
echoCursors = manager.getEchoCursors();
|
|
52
|
+
echoContents = getEchoContents();
|
|
50
53
|
break;
|
|
51
54
|
case 'echo-removed':
|
|
52
55
|
echoCursors = manager.getEchoCursors();
|
|
56
|
+
echoContents = getEchoContents();
|
|
53
57
|
break;
|
|
54
58
|
case 'replay-started':
|
|
55
59
|
replayingCursors = new Set([...replayingCursors, event.cursorId]);
|
|
@@ -63,6 +67,8 @@
|
|
|
63
67
|
break;
|
|
64
68
|
case 'replay-completed':
|
|
65
69
|
replayingCursors = new Set([...replayingCursors].filter((id) => id !== event.cursorId));
|
|
70
|
+
echoCursors = manager.getEchoCursors();
|
|
71
|
+
echoContents = getEchoContents();
|
|
66
72
|
// Fade out replay text
|
|
67
73
|
setTimeout(() => {
|
|
68
74
|
const entry = recentReplay.get(event.cursorId);
|
|
@@ -78,6 +84,7 @@
|
|
|
78
84
|
if (!event.enabled) {
|
|
79
85
|
echoCursors = [];
|
|
80
86
|
replayingCursors = new Set();
|
|
87
|
+
echoContents = {};
|
|
81
88
|
recentReplay.clear();
|
|
82
89
|
}
|
|
83
90
|
break;
|
|
@@ -113,6 +120,12 @@
|
|
|
113
120
|
return tokens[Math.max(index, 0) % tokens.length];
|
|
114
121
|
}
|
|
115
122
|
|
|
123
|
+
function getEchoContents(): Record<string, string> {
|
|
124
|
+
return Object.fromEntries(
|
|
125
|
+
manager.getEchoCursors().map((cursor) => [cursor.id, manager.getEchoContent(cursor.id)])
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
116
129
|
/**
|
|
117
130
|
* Handle remove echo click
|
|
118
131
|
*/
|
|
@@ -127,6 +140,7 @@
|
|
|
127
140
|
{#each echoCursors as cursor (cursor.id)}
|
|
128
141
|
{@const isReplaying = replayingCursors.has(cursor.id)}
|
|
129
142
|
{@const replayText = recentReplay.get(cursor.id)}
|
|
143
|
+
{@const echoContent = echoContents[cursor.id] ?? ''}
|
|
130
144
|
|
|
131
145
|
<!-- Echo cursor marker -->
|
|
132
146
|
<div
|
|
@@ -171,6 +185,21 @@
|
|
|
171
185
|
<div class="echo-cursor__ripple"></div>
|
|
172
186
|
{/if}
|
|
173
187
|
</div>
|
|
188
|
+
|
|
189
|
+
<!-- Echo readout: the mirror's CURRENT line at the echo position, drawn on
|
|
190
|
+
an opaque lane so it never composites over the editor's own code
|
|
191
|
+
glyphs (a full-document transparent overlay read as doubled text). -->
|
|
192
|
+
<div
|
|
193
|
+
class="echo-cursor-buffer"
|
|
194
|
+
style="top: {cursor.position.line *
|
|
195
|
+
lineHeight}px; left: {gutterWidth}px; height: {lineHeight}px; --echo-color: var({getEchoColorToken(
|
|
196
|
+
cursor
|
|
197
|
+
)});"
|
|
198
|
+
>
|
|
199
|
+
<span class="echo-cursor-buffer__text"
|
|
200
|
+
>{echoContent.split('\n')[cursor.position.line] || ' '}</span
|
|
201
|
+
>
|
|
202
|
+
</div>
|
|
174
203
|
{/each}
|
|
175
204
|
|
|
176
205
|
<!-- Mode indicator -->
|
|
@@ -199,6 +228,8 @@
|
|
|
199
228
|
.echo-cursor {
|
|
200
229
|
position: absolute;
|
|
201
230
|
pointer-events: auto;
|
|
231
|
+
/* Keep the caret/label/replay above the echo readout lane. */
|
|
232
|
+
z-index: 1;
|
|
202
233
|
}
|
|
203
234
|
|
|
204
235
|
.echo-cursor__caret {
|
|
@@ -327,6 +358,35 @@
|
|
|
327
358
|
animation: echo-ripple 0.4s ease-out forwards;
|
|
328
359
|
}
|
|
329
360
|
|
|
361
|
+
.echo-cursor-buffer {
|
|
362
|
+
position: absolute;
|
|
363
|
+
right: 0;
|
|
364
|
+
display: flex;
|
|
365
|
+
align-items: center;
|
|
366
|
+
padding: 0 8px 0 10px;
|
|
367
|
+
font-family: monospace;
|
|
368
|
+
font-size: 13px;
|
|
369
|
+
line-height: 1;
|
|
370
|
+
white-space: pre;
|
|
371
|
+
overflow: hidden;
|
|
372
|
+
pointer-events: none;
|
|
373
|
+
/* Opaque lane: the echoed line must never show through onto the editor's
|
|
374
|
+
own glyphs — that reads as doubled, illegible code. */
|
|
375
|
+
background: linear-gradient(
|
|
376
|
+
90deg,
|
|
377
|
+
color-mix(in srgb, var(--echo-color) 24%, var(--ide-bg-secondary, #1a2744)) 0%,
|
|
378
|
+
var(--ide-bg-secondary, #1a2744) 72%
|
|
379
|
+
);
|
|
380
|
+
border-left: 2px solid var(--echo-color);
|
|
381
|
+
box-shadow: 0 1px 4px color-mix(in srgb, #000 35%, transparent);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.echo-cursor-buffer__text {
|
|
385
|
+
overflow: hidden;
|
|
386
|
+
text-overflow: ellipsis;
|
|
387
|
+
color: color-mix(in srgb, var(--echo-color) 60%, var(--ide-text-primary));
|
|
388
|
+
}
|
|
389
|
+
|
|
330
390
|
@keyframes echo-ripple {
|
|
331
391
|
0% {
|
|
332
392
|
transform: scale(0.5);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
/**
|
|
3
|
-
* Plugin Preview
|
|
3
|
+
* Plugin Preview
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Preview plugin effects before applying them to the main editor.
|
|
6
|
+
* Plugin transform functions are trusted caller-provided code and run
|
|
7
|
+
* in the host realm, not in an isolated sandbox.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
interface PluginTransform {
|
|
@@ -71,6 +71,9 @@
|
|
|
71
71
|
let isProcessing = $state(false);
|
|
72
72
|
let previewMode = $state<'split' | 'unified' | 'diff'>('split');
|
|
73
73
|
let sandboxContainer = $state<HTMLDivElement>(null!);
|
|
74
|
+
let transformRunId = 0;
|
|
75
|
+
|
|
76
|
+
const TRANSFORM_TIMEOUT_MS = 2_000;
|
|
74
77
|
|
|
75
78
|
// Demo plugins for showcase
|
|
76
79
|
const demoPlugins: PluginDefinition[] = [
|
|
@@ -164,18 +167,20 @@
|
|
|
164
167
|
const activePlugins = $derived(plugins.length > 0 ? plugins : demoPlugins);
|
|
165
168
|
|
|
166
169
|
/**
|
|
167
|
-
* Run plugin transform
|
|
170
|
+
* Run trusted plugin transform with an async watchdog.
|
|
168
171
|
*/
|
|
169
172
|
async function runTransform(plugin: PluginDefinition) {
|
|
173
|
+
const runId = ++transformRunId;
|
|
170
174
|
selectedPlugin = plugin;
|
|
171
175
|
isProcessing = true;
|
|
172
176
|
|
|
173
177
|
const startTime = performance.now();
|
|
174
178
|
|
|
175
179
|
try {
|
|
176
|
-
const result = await
|
|
180
|
+
const result = await runTrustedTransform(plugin, code);
|
|
177
181
|
const executionTime = performance.now() - startTime;
|
|
178
182
|
|
|
183
|
+
if (runId !== transformRunId) return;
|
|
179
184
|
currentTransform = {
|
|
180
185
|
id: `${plugin.id}-${Date.now()}`,
|
|
181
186
|
pluginName: plugin.name,
|
|
@@ -187,6 +192,7 @@
|
|
|
187
192
|
executionTime
|
|
188
193
|
};
|
|
189
194
|
} catch (error) {
|
|
195
|
+
if (runId !== transformRunId) return;
|
|
190
196
|
currentTransform = {
|
|
191
197
|
id: `${plugin.id}-${Date.now()}`,
|
|
192
198
|
pluginName: plugin.name,
|
|
@@ -198,9 +204,35 @@
|
|
|
198
204
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
199
205
|
executionTime: performance.now() - startTime
|
|
200
206
|
};
|
|
207
|
+
} finally {
|
|
208
|
+
if (runId === transformRunId) {
|
|
209
|
+
isProcessing = false;
|
|
210
|
+
}
|
|
201
211
|
}
|
|
212
|
+
}
|
|
202
213
|
|
|
203
|
-
|
|
214
|
+
function runTrustedTransform(plugin: PluginDefinition, input: string): Promise<string> {
|
|
215
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
216
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
217
|
+
timeoutId = setTimeout(() => {
|
|
218
|
+
reject(new Error(`Transform timed out after ${TRANSFORM_TIMEOUT_MS}ms`));
|
|
219
|
+
}, TRANSFORM_TIMEOUT_MS);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const transform = Promise.resolve()
|
|
223
|
+
.then(() => plugin.transform(input))
|
|
224
|
+
.then((result) => {
|
|
225
|
+
if (typeof result !== 'string') {
|
|
226
|
+
throw new Error('Transform returned a non-string result');
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return Promise.race([transform, timeout]).finally(() => {
|
|
232
|
+
if (timeoutId !== undefined) {
|
|
233
|
+
clearTimeout(timeoutId);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
204
236
|
}
|
|
205
237
|
|
|
206
238
|
/**
|
|
@@ -246,8 +278,10 @@
|
|
|
246
278
|
* Reset to original
|
|
247
279
|
*/
|
|
248
280
|
function resetTransform() {
|
|
281
|
+
transformRunId++;
|
|
249
282
|
currentTransform = null;
|
|
250
283
|
selectedPlugin = null;
|
|
284
|
+
isProcessing = false;
|
|
251
285
|
}
|
|
252
286
|
|
|
253
287
|
/**
|
|
@@ -273,7 +307,7 @@
|
|
|
273
307
|
{#if visible}
|
|
274
308
|
<div class="plugin-sandbox" bind:this={sandboxContainer}>
|
|
275
309
|
<div class="plugin-sandbox__header">
|
|
276
|
-
<h3 class="plugin-sandbox__title">Plugin Preview
|
|
310
|
+
<h3 class="plugin-sandbox__title">Plugin Preview</h3>
|
|
277
311
|
<div class="plugin-sandbox__actions">
|
|
278
312
|
<div class="preview-mode-toggle">
|
|
279
313
|
<button
|
|
@@ -387,7 +421,7 @@
|
|
|
387
421
|
<div class="preview-empty">
|
|
388
422
|
<p>Select a plugin to preview its transformation</p>
|
|
389
423
|
<p class="preview-hint">
|
|
390
|
-
|
|
424
|
+
Trusted plugin code runs in this editor before applying changes
|
|
391
425
|
</p>
|
|
392
426
|
</div>
|
|
393
427
|
{/if}
|
|
@@ -11,11 +11,11 @@ interface PluginDefinition {
|
|
|
11
11
|
transform: (code: string) => string | Promise<string>;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
|
-
* Plugin Preview
|
|
14
|
+
* Plugin Preview
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* Preview plugin effects before applying them to the main editor.
|
|
17
|
+
* Plugin transform functions are trusted caller-provided code and run
|
|
18
|
+
* in the host realm, not in an isolated sandbox.
|
|
19
19
|
*/
|
|
20
20
|
interface PluginTransform {
|
|
21
21
|
/** Transform ID */
|
|
@@ -22,6 +22,13 @@ export interface ComplexityFactors {
|
|
|
22
22
|
/** Number of function calls */
|
|
23
23
|
callCount: number;
|
|
24
24
|
}
|
|
25
|
+
export interface ComplexityContribution {
|
|
26
|
+
line: number;
|
|
27
|
+
kind: string;
|
|
28
|
+
reason: string;
|
|
29
|
+
increment: number;
|
|
30
|
+
nesting: number;
|
|
31
|
+
}
|
|
25
32
|
/**
|
|
26
33
|
* A region of code with its complexity analysis
|
|
27
34
|
*/
|
|
@@ -40,6 +47,10 @@ export interface ComplexityRegion {
|
|
|
40
47
|
type: 'function' | 'class' | 'block' | 'file';
|
|
41
48
|
/** Region name if identifiable */
|
|
42
49
|
name?: string;
|
|
50
|
+
/** Exact SonarSource Cognitive Complexity score */
|
|
51
|
+
cognitiveComplexity: number;
|
|
52
|
+
/** Per-increment Cognitive Complexity contribution breakdown */
|
|
53
|
+
contributions: ComplexityContribution[];
|
|
43
54
|
}
|
|
44
55
|
/**
|
|
45
56
|
* Overall complexity metrics for a document
|
|
@@ -53,6 +64,8 @@ export interface ComplexityMetrics {
|
|
|
53
64
|
regions: ComplexityRegion[];
|
|
54
65
|
/** Lines that exceed threshold */
|
|
55
66
|
hotspots: number[];
|
|
67
|
+
/** Sum of exact Cognitive Complexity across all regions */
|
|
68
|
+
totalCognitiveComplexity: number;
|
|
56
69
|
}
|
|
57
70
|
/**
|
|
58
71
|
* Complexity Analyzer class
|
|
@@ -76,6 +89,7 @@ export declare class ComplexityAnalyzer {
|
|
|
76
89
|
* Compute a cache key from all line content using a fast hash
|
|
77
90
|
*/
|
|
78
91
|
private computeCacheKey;
|
|
92
|
+
private getComplexityLanguage;
|
|
79
93
|
/**
|
|
80
94
|
* Identify code regions (functions, classes, blocks)
|
|
81
95
|
*
|
|
@@ -83,6 +97,9 @@ export declare class ComplexityAnalyzer {
|
|
|
83
97
|
* (object literals, destructuring) that don't represent new blocks.
|
|
84
98
|
*/
|
|
85
99
|
private identifyRegions;
|
|
100
|
+
private identifyPythonRegions;
|
|
101
|
+
private identifyGoRegions;
|
|
102
|
+
private getGoDeclaration;
|
|
86
103
|
/**
|
|
87
104
|
* Check if a `{` at position `ch` is the opening brace of a function/class definition
|
|
88
105
|
*/
|
|
@@ -99,6 +116,20 @@ export declare class ComplexityAnalyzer {
|
|
|
99
116
|
* Calculate complexity factors for a range of lines
|
|
100
117
|
*/
|
|
101
118
|
private calculateFactors;
|
|
119
|
+
private calculateCognitiveContributions;
|
|
120
|
+
private calculateBraceCognitiveContributions;
|
|
121
|
+
private calculatePythonCognitiveContributions;
|
|
122
|
+
private getCodeTokens;
|
|
123
|
+
private getIndent;
|
|
124
|
+
private addContribution;
|
|
125
|
+
private describeContributionKind;
|
|
126
|
+
private getBooleanSequenceContributions;
|
|
127
|
+
private nextNonTextToken;
|
|
128
|
+
private isGoTypeSwitch;
|
|
129
|
+
private isLabelledJump;
|
|
130
|
+
private isNestedFunctionToken;
|
|
131
|
+
private isDirectRecursiveCall;
|
|
132
|
+
private isPythonTernary;
|
|
102
133
|
/**
|
|
103
134
|
* Calculate complexity score from factors
|
|
104
135
|
*/
|