@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
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* Hover over gutter indicators for details.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { SvelteMap } from 'svelte/reactivity';
|
|
11
10
|
import type { ComplexityMetrics, ComplexityRegion } from './core/complexity-analyzer';
|
|
12
11
|
|
|
13
12
|
interface Props {
|
|
@@ -17,68 +16,42 @@
|
|
|
17
16
|
lineHeight: number;
|
|
18
17
|
/** Gutter width in pixels */
|
|
19
18
|
gutterWidth?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Full height of the scrollable content in pixels. The layer must span the
|
|
21
|
+
* whole document (not just one viewport) or indicators below the first
|
|
22
|
+
* screenful get clipped — which previously hid every high-complexity region
|
|
23
|
+
* that sat past the initial view.
|
|
24
|
+
*/
|
|
25
|
+
totalHeight?: number;
|
|
26
|
+
/** Estimated full scrollable content width in pixels */
|
|
27
|
+
contentWidth?: number;
|
|
20
28
|
/** Minimum score to show indicators (default: 50) */
|
|
21
29
|
minScore?: number;
|
|
22
30
|
/** Whether indicators are enabled */
|
|
23
31
|
enabled?: boolean;
|
|
32
|
+
/** Region key to briefly emphasize after jump-to-hottest */
|
|
33
|
+
flashRegionKey?: string;
|
|
34
|
+
/** Maps a raw document line to its rendered visual row */
|
|
35
|
+
lineToVisualRow?: (line: number) => number;
|
|
24
36
|
}
|
|
25
37
|
|
|
26
|
-
let {
|
|
38
|
+
let {
|
|
39
|
+
metrics,
|
|
40
|
+
lineHeight,
|
|
41
|
+
gutterWidth = 50,
|
|
42
|
+
totalHeight = 0,
|
|
43
|
+
contentWidth = 0,
|
|
44
|
+
minScore = 50,
|
|
45
|
+
enabled = true,
|
|
46
|
+
flashRegionKey = '',
|
|
47
|
+
lineToVisualRow = (line: number) => line
|
|
48
|
+
}: Props = $props();
|
|
27
49
|
|
|
28
50
|
// Filter regions that exceed the threshold
|
|
29
51
|
let highlightedRegions = $derived(
|
|
30
52
|
enabled && metrics ? metrics.regions.filter((r) => r.score >= minScore) : []
|
|
31
53
|
);
|
|
32
54
|
|
|
33
|
-
// Expand regions to individual line indicators for gutter
|
|
34
|
-
let lineIndicators = $derived.by(() => {
|
|
35
|
-
if (!enabled || !metrics) return [];
|
|
36
|
-
|
|
37
|
-
const indicators: Array<{
|
|
38
|
-
line: number;
|
|
39
|
-
score: number;
|
|
40
|
-
region: ComplexityRegion;
|
|
41
|
-
isStart: boolean;
|
|
42
|
-
isEnd: boolean;
|
|
43
|
-
}> = [];
|
|
44
|
-
|
|
45
|
-
for (const region of highlightedRegions) {
|
|
46
|
-
for (let line = region.startLine; line <= region.endLine; line++) {
|
|
47
|
-
indicators.push({
|
|
48
|
-
line,
|
|
49
|
-
score: region.score,
|
|
50
|
-
region,
|
|
51
|
-
isStart: line === region.startLine,
|
|
52
|
-
isEnd: line === region.endLine
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return indicators;
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// Group by line (in case of overlapping regions, take highest score)
|
|
61
|
-
let lineScores = $derived.by(() => {
|
|
62
|
-
const scores = new SvelteMap<
|
|
63
|
-
number,
|
|
64
|
-
{ score: number; region: ComplexityRegion; isStart: boolean; isEnd: boolean }
|
|
65
|
-
>();
|
|
66
|
-
|
|
67
|
-
for (const indicator of lineIndicators) {
|
|
68
|
-
const existing = scores.get(indicator.line);
|
|
69
|
-
if (!existing || indicator.score > existing.score) {
|
|
70
|
-
scores.set(indicator.line, {
|
|
71
|
-
score: indicator.score,
|
|
72
|
-
region: indicator.region,
|
|
73
|
-
isStart: indicator.isStart,
|
|
74
|
-
isEnd: indicator.isEnd
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return scores;
|
|
80
|
-
});
|
|
81
|
-
|
|
82
55
|
/**
|
|
83
56
|
* Get the color based on score level
|
|
84
57
|
*/
|
|
@@ -87,7 +60,8 @@
|
|
|
87
60
|
// medium otherwise (lines below the minScore threshold aren't shown).
|
|
88
61
|
if (score >= 85) return 'var(--ide-error)'; // Critical
|
|
89
62
|
if (score >= 70) return 'var(--ide-warning)'; // High
|
|
90
|
-
return 'var(--ide-info)'; // Medium
|
|
63
|
+
if (score >= 50) return 'var(--ide-info)'; // Medium
|
|
64
|
+
return 'var(--ide-success)'; // Low
|
|
91
65
|
}
|
|
92
66
|
|
|
93
67
|
/**
|
|
@@ -95,19 +69,64 @@
|
|
|
95
69
|
*/
|
|
96
70
|
function getOpacity(score: number): number {
|
|
97
71
|
const normalized = Math.min(1, Math.max(0, (score - minScore) / (100 - minScore)));
|
|
98
|
-
return 0.
|
|
72
|
+
return 0.65 + normalized * 0.35;
|
|
99
73
|
}
|
|
100
74
|
|
|
101
75
|
let hoveredRegion = $state<ComplexityRegion | null>(null);
|
|
102
|
-
let tooltipPosition = $state({ top: 0, left: 0 });
|
|
76
|
+
let tooltipPosition = $state({ top: 0, left: 0, right: 0 });
|
|
77
|
+
let tooltipAnchor = $state<'left' | 'right'>('right');
|
|
78
|
+
let visibleContributions = $derived(
|
|
79
|
+
hoveredRegion
|
|
80
|
+
? [...hoveredRegion.contributions].sort((a, b) => a.line - b.line).slice(0, 8)
|
|
81
|
+
: []
|
|
82
|
+
);
|
|
83
|
+
let hiddenContributionCount = $derived(
|
|
84
|
+
hoveredRegion
|
|
85
|
+
? Math.max(0, hoveredRegion.contributions.length - visibleContributions.length)
|
|
86
|
+
: 0
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
function getRegionKey(region: ComplexityRegion): string {
|
|
90
|
+
return `${region.startLine}:${region.endLine}:${region.name ?? region.type}:${region.score}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getLevelLabel(score: number): string {
|
|
94
|
+
if (score >= 85) return 'Critical';
|
|
95
|
+
if (score >= 70) return 'High';
|
|
96
|
+
if (score >= 50) return 'Medium';
|
|
97
|
+
return 'Low';
|
|
98
|
+
}
|
|
103
99
|
|
|
104
|
-
function
|
|
100
|
+
function getRegionTop(region: ComplexityRegion): number {
|
|
101
|
+
return lineToVisualRow(region.startLine) * lineHeight;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function getRegionHeight(region: ComplexityRegion): number {
|
|
105
|
+
const startRow = lineToVisualRow(region.startLine);
|
|
106
|
+
const endRow = Math.max(startRow, lineToVisualRow(region.endLine));
|
|
107
|
+
return Math.max(lineHeight, (endRow - startRow + 1) * lineHeight);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getContributionLabel(contribution: ComplexityRegion['contributions'][number]): string {
|
|
111
|
+
return contribution.kind || contribution.reason;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function handleMouseEnter(
|
|
115
|
+
region: ComplexityRegion,
|
|
116
|
+
event: MouseEvent,
|
|
117
|
+
anchor: 'left' | 'right' = 'right'
|
|
118
|
+
) {
|
|
105
119
|
hoveredRegion = region;
|
|
106
|
-
const rect = (event.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
120
|
+
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
|
121
|
+
tooltipAnchor = anchor;
|
|
122
|
+
// The gutter spine opens the tooltip to its right (over the code); the score
|
|
123
|
+
// badge sits at the right edge, so it opens to its left. Anchor the left case
|
|
124
|
+
// by `right` (not a translateX) so the box grows leftward cleanly — translateX
|
|
125
|
+
// could subpixel-clip the first glyph of the region name.
|
|
126
|
+
tooltipPosition =
|
|
127
|
+
anchor === 'right'
|
|
128
|
+
? { top: rect.top, left: rect.right + 8, right: 0 }
|
|
129
|
+
: { top: rect.top, left: 0, right: window.innerWidth - rect.left + 8 };
|
|
111
130
|
}
|
|
112
131
|
|
|
113
132
|
function handleMouseLeave() {
|
|
@@ -115,27 +134,45 @@
|
|
|
115
134
|
}
|
|
116
135
|
</script>
|
|
117
136
|
|
|
118
|
-
{#if enabled &&
|
|
119
|
-
<div
|
|
120
|
-
|
|
137
|
+
{#if enabled && highlightedRegions.length > 0}
|
|
138
|
+
<div
|
|
139
|
+
class="complexity-gutter"
|
|
140
|
+
aria-hidden="true"
|
|
141
|
+
style="width: {contentWidth ? `${contentWidth}px` : '100%'};{totalHeight
|
|
142
|
+
? ` height: ${totalHeight}px;`
|
|
143
|
+
: ''} --editor-gutter-width: {gutterWidth}px;"
|
|
144
|
+
>
|
|
145
|
+
{#each highlightedRegions as region (getRegionKey(region))}
|
|
146
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
121
147
|
<div
|
|
122
|
-
class="complexity-
|
|
123
|
-
class:complexity-
|
|
124
|
-
class:complexity-
|
|
148
|
+
class="complexity-gutter__spine"
|
|
149
|
+
class:complexity-gutter__spine--critical={region.score >= 85}
|
|
150
|
+
class:complexity-gutter__spine--flash={flashRegionKey === getRegionKey(region)}
|
|
125
151
|
style="
|
|
126
|
-
top: {
|
|
127
|
-
height: {
|
|
128
|
-
--indicator-color: {getColor(
|
|
129
|
-
--indicator-opacity: {getOpacity(
|
|
152
|
+
top: {getRegionTop(region)}px;
|
|
153
|
+
height: {getRegionHeight(region)}px;
|
|
154
|
+
--indicator-color: {getColor(region.score)};
|
|
155
|
+
--indicator-opacity: {getOpacity(region.score)};
|
|
156
|
+
--indicator-glow: {region.score >= 85 ? 18 : region.score >= 70 ? 14 : 9}px;
|
|
130
157
|
"
|
|
131
|
-
onmouseenter={(e) => handleMouseEnter(
|
|
158
|
+
onmouseenter={(e) => handleMouseEnter(region, e)}
|
|
159
|
+
onmouseleave={handleMouseLeave}
|
|
160
|
+
></div>
|
|
161
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
162
|
+
<div
|
|
163
|
+
class="complexity-gutter__score"
|
|
164
|
+
class:complexity-gutter__score--critical={region.score >= 85}
|
|
165
|
+
class:complexity-gutter__score--flash={flashRegionKey === getRegionKey(region)}
|
|
166
|
+
style="
|
|
167
|
+
top: {getRegionTop(region) + 3}px;
|
|
168
|
+
right: 18px;
|
|
169
|
+
--indicator-color: {getColor(region.score)};
|
|
170
|
+
--indicator-opacity: {getOpacity(region.score)};
|
|
171
|
+
"
|
|
172
|
+
onmouseenter={(e) => handleMouseEnter(region, e, 'left')}
|
|
132
173
|
onmouseleave={handleMouseLeave}
|
|
133
|
-
role="button"
|
|
134
|
-
tabindex="-1"
|
|
135
174
|
>
|
|
136
|
-
{
|
|
137
|
-
<span class="complexity-gutter__score">{data.score}</span>
|
|
138
|
-
{/if}
|
|
175
|
+
{region.score}
|
|
139
176
|
</div>
|
|
140
177
|
{/each}
|
|
141
178
|
</div>
|
|
@@ -144,19 +181,40 @@
|
|
|
144
181
|
{#if hoveredRegion}
|
|
145
182
|
<div
|
|
146
183
|
class="complexity-tooltip"
|
|
147
|
-
style=
|
|
184
|
+
style={tooltipAnchor === 'left'
|
|
185
|
+
? `top: ${tooltipPosition.top}px; right: ${tooltipPosition.right}px;`
|
|
186
|
+
: `top: ${tooltipPosition.top}px; left: ${tooltipPosition.left}px;`}
|
|
148
187
|
>
|
|
149
188
|
<div class="complexity-tooltip__header">
|
|
150
|
-
<span class="complexity-tooltip__badge" style="
|
|
151
|
-
{hoveredRegion.
|
|
152
|
-
</span>
|
|
153
|
-
<span class="complexity-tooltip__title">
|
|
154
|
-
{hoveredRegion.name || hoveredRegion.type}
|
|
189
|
+
<span class="complexity-tooltip__badge" style="color: {getColor(hoveredRegion.score)}">
|
|
190
|
+
{hoveredRegion.cognitiveComplexity}
|
|
155
191
|
</span>
|
|
192
|
+
<div class="complexity-tooltip__heading">
|
|
193
|
+
<span class="complexity-tooltip__title">
|
|
194
|
+
{getLevelLabel(hoveredRegion.score)} Cognitive Complexity
|
|
195
|
+
</span>
|
|
196
|
+
<span class="complexity-tooltip__source">SonarSource Cognitive Complexity metric</span>
|
|
197
|
+
</div>
|
|
156
198
|
</div>
|
|
157
199
|
<div class="complexity-tooltip__lines">
|
|
158
|
-
Lines {hoveredRegion.startLine + 1} - {hoveredRegion.endLine +
|
|
200
|
+
{hoveredRegion.name || hoveredRegion.type} · Lines {hoveredRegion.startLine + 1} - {hoveredRegion.endLine +
|
|
201
|
+
1}
|
|
202
|
+
· Score {hoveredRegion.score}
|
|
159
203
|
</div>
|
|
204
|
+
{#if visibleContributions.length > 0}
|
|
205
|
+
<ul class="complexity-tooltip__contributions">
|
|
206
|
+
{#each visibleContributions as contribution, index (`${contribution.line}:${contribution.kind}:${index}`)}
|
|
207
|
+
<li>
|
|
208
|
+
<span>line {contribution.line + 1}</span>
|
|
209
|
+
<strong>+{contribution.increment}</strong>
|
|
210
|
+
<span>{getContributionLabel(contribution)} (nesting {contribution.nesting})</span>
|
|
211
|
+
</li>
|
|
212
|
+
{/each}
|
|
213
|
+
{#if hiddenContributionCount > 0}
|
|
214
|
+
<li class="complexity-tooltip__more">+{hiddenContributionCount} more</li>
|
|
215
|
+
{/if}
|
|
216
|
+
</ul>
|
|
217
|
+
{/if}
|
|
160
218
|
{#if hoveredRegion.suggestion}
|
|
161
219
|
<div class="complexity-tooltip__suggestion">
|
|
162
220
|
{hoveredRegion.suggestion}
|
|
@@ -176,57 +234,195 @@
|
|
|
176
234
|
position: absolute;
|
|
177
235
|
top: 0;
|
|
178
236
|
left: 0;
|
|
237
|
+
/* Height is set inline to the full content height so indicators below the
|
|
238
|
+
first viewport are not clipped. Fall back to the viewport when unknown. */
|
|
179
239
|
bottom: 0;
|
|
180
240
|
pointer-events: none;
|
|
181
241
|
z-index: 5;
|
|
182
|
-
overflow:
|
|
242
|
+
overflow: visible;
|
|
183
243
|
}
|
|
184
244
|
|
|
185
|
-
.complexity-
|
|
245
|
+
.complexity-gutter__spine {
|
|
186
246
|
position: absolute;
|
|
187
|
-
|
|
247
|
+
left: calc(var(--editor-gutter-width, 50px) - 3px);
|
|
188
248
|
width: 4px;
|
|
189
249
|
pointer-events: auto;
|
|
190
250
|
cursor: help;
|
|
191
251
|
background: var(--indicator-color);
|
|
192
252
|
opacity: var(--indicator-opacity);
|
|
253
|
+
border-radius: 999px;
|
|
254
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--indicator-color) 35%, transparent);
|
|
255
|
+
isolation: isolate;
|
|
193
256
|
transition:
|
|
194
257
|
opacity 0.15s ease,
|
|
195
|
-
width 0.15s ease
|
|
258
|
+
width 0.15s ease,
|
|
259
|
+
box-shadow 0.15s ease,
|
|
260
|
+
transform 0.15s ease;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.complexity-gutter__spine::before {
|
|
264
|
+
content: '';
|
|
265
|
+
position: absolute;
|
|
266
|
+
inset: -2px -1px;
|
|
267
|
+
border-radius: inherit;
|
|
268
|
+
background: var(--indicator-color);
|
|
269
|
+
filter: blur(6px);
|
|
270
|
+
opacity: 0.55;
|
|
271
|
+
transform: scaleX(1);
|
|
272
|
+
transform-origin: center;
|
|
273
|
+
z-index: -1;
|
|
196
274
|
}
|
|
197
275
|
|
|
198
|
-
.complexity-
|
|
276
|
+
.complexity-gutter__spine:hover {
|
|
199
277
|
opacity: 1;
|
|
200
278
|
width: 6px;
|
|
279
|
+
transform: translateX(-1px);
|
|
201
280
|
}
|
|
202
281
|
|
|
203
|
-
.complexity-
|
|
204
|
-
|
|
282
|
+
.complexity-gutter__spine--critical {
|
|
283
|
+
animation: complexity-spine-pulse 2.4s ease-in-out infinite;
|
|
205
284
|
}
|
|
206
285
|
|
|
207
|
-
.complexity-
|
|
208
|
-
|
|
286
|
+
.complexity-gutter__spine--critical::before {
|
|
287
|
+
animation: complexity-spine-bloom-pulse 2.4s ease-in-out infinite;
|
|
209
288
|
}
|
|
210
289
|
|
|
211
|
-
.complexity-
|
|
212
|
-
|
|
290
|
+
.complexity-gutter__spine--flash {
|
|
291
|
+
animation: complexity-spine-flash 0.9s ease-out 1;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.complexity-gutter__spine--flash::before {
|
|
295
|
+
animation: complexity-spine-bloom-flash 0.9s ease-out 1;
|
|
213
296
|
}
|
|
214
297
|
|
|
215
298
|
.complexity-gutter__score {
|
|
216
299
|
position: absolute;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
300
|
+
display: inline-flex;
|
|
301
|
+
align-items: center;
|
|
302
|
+
justify-content: center;
|
|
303
|
+
min-width: 44px;
|
|
304
|
+
height: 30px;
|
|
305
|
+
padding: 0 10px;
|
|
306
|
+
border: 1px solid color-mix(in srgb, var(--indicator-color) 65%, transparent);
|
|
307
|
+
border-radius: 999px;
|
|
308
|
+
background: color-mix(in srgb, var(--indicator-color) 18%, var(--ide-bg-primary));
|
|
309
|
+
box-shadow:
|
|
310
|
+
0 0 18px color-mix(in srgb, var(--indicator-color) 32%, transparent),
|
|
311
|
+
inset 0 1px 0 color-mix(in srgb, #fff 12%, transparent);
|
|
222
312
|
color: var(--indicator-color);
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
313
|
+
font-size: 22px;
|
|
314
|
+
font-weight: 800;
|
|
315
|
+
font-variant-numeric: tabular-nums;
|
|
316
|
+
line-height: 1;
|
|
317
|
+
text-shadow: 0 0 12px color-mix(in srgb, var(--indicator-color) 48%, transparent);
|
|
318
|
+
opacity: calc(0.76 + (var(--indicator-opacity) - 0.65) * 0.7);
|
|
319
|
+
transform: scale(1);
|
|
320
|
+
transform-origin: center;
|
|
321
|
+
/* The badge is a primary hover target for the explain-on-hover tooltip,
|
|
322
|
+
not only the 4px spine. */
|
|
323
|
+
pointer-events: auto;
|
|
324
|
+
cursor: help;
|
|
325
|
+
box-sizing: border-box;
|
|
226
326
|
}
|
|
227
327
|
|
|
228
|
-
.complexity-
|
|
229
|
-
|
|
328
|
+
.complexity-gutter__score--critical {
|
|
329
|
+
animation: complexity-score-glow-pulse 2.4s ease-in-out infinite;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.complexity-gutter__score--flash {
|
|
333
|
+
animation: complexity-score-arrival-pop 0.35s ease-out 1;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
@keyframes complexity-spine-pulse {
|
|
337
|
+
0%,
|
|
338
|
+
100% {
|
|
339
|
+
transform: scaleX(1);
|
|
340
|
+
}
|
|
341
|
+
50% {
|
|
342
|
+
transform: scaleX(1.12);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
@keyframes complexity-spine-bloom-pulse {
|
|
347
|
+
0%,
|
|
348
|
+
100% {
|
|
349
|
+
filter: blur(6px);
|
|
350
|
+
opacity: 0.55;
|
|
351
|
+
transform: scaleX(1);
|
|
352
|
+
}
|
|
353
|
+
50% {
|
|
354
|
+
filter: blur(10px);
|
|
355
|
+
opacity: 0.78;
|
|
356
|
+
transform: scaleX(1.35);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
@keyframes complexity-score-glow-pulse {
|
|
361
|
+
0%,
|
|
362
|
+
100% {
|
|
363
|
+
box-shadow:
|
|
364
|
+
0 0 18px color-mix(in srgb, var(--indicator-color) 32%, transparent),
|
|
365
|
+
inset 0 1px 0 color-mix(in srgb, #fff 12%, transparent);
|
|
366
|
+
transform: scale(1);
|
|
367
|
+
}
|
|
368
|
+
50% {
|
|
369
|
+
box-shadow:
|
|
370
|
+
0 0 28px color-mix(in srgb, var(--indicator-color) 52%, transparent),
|
|
371
|
+
0 0 44px color-mix(in srgb, var(--indicator-color) 28%, transparent),
|
|
372
|
+
inset 0 1px 0 color-mix(in srgb, #fff 16%, transparent);
|
|
373
|
+
transform: scale(1.04);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
@keyframes complexity-score-arrival-pop {
|
|
378
|
+
0% {
|
|
379
|
+
transform: scale(1.15);
|
|
380
|
+
box-shadow:
|
|
381
|
+
0 0 30px color-mix(in srgb, var(--indicator-color) 62%, transparent),
|
|
382
|
+
0 0 58px color-mix(in srgb, var(--indicator-color) 36%, transparent),
|
|
383
|
+
inset 0 1px 0 color-mix(in srgb, #fff 18%, transparent);
|
|
384
|
+
}
|
|
385
|
+
100% {
|
|
386
|
+
transform: scale(1);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
@keyframes complexity-spine-flash {
|
|
391
|
+
0% {
|
|
392
|
+
opacity: 1;
|
|
393
|
+
width: 8px;
|
|
394
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--indicator-color) 70%, transparent);
|
|
395
|
+
transform: translateX(-2px);
|
|
396
|
+
}
|
|
397
|
+
100% {
|
|
398
|
+
opacity: var(--indicator-opacity);
|
|
399
|
+
width: 4px;
|
|
400
|
+
transform: translateX(0);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
@keyframes complexity-spine-bloom-flash {
|
|
405
|
+
0% {
|
|
406
|
+
filter: blur(12px);
|
|
407
|
+
opacity: 0.9;
|
|
408
|
+
transform: scaleX(1.45);
|
|
409
|
+
}
|
|
410
|
+
100% {
|
|
411
|
+
filter: blur(6px);
|
|
412
|
+
opacity: 0.55;
|
|
413
|
+
transform: scaleX(1);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
@media (prefers-reduced-motion: reduce) {
|
|
418
|
+
.complexity-gutter__spine--critical,
|
|
419
|
+
.complexity-gutter__spine--critical::before,
|
|
420
|
+
.complexity-gutter__spine--flash,
|
|
421
|
+
.complexity-gutter__spine--flash::before,
|
|
422
|
+
.complexity-gutter__score--critical,
|
|
423
|
+
.complexity-gutter__score--flash {
|
|
424
|
+
animation: none;
|
|
425
|
+
}
|
|
230
426
|
}
|
|
231
427
|
|
|
232
428
|
/* Tooltip */
|
|
@@ -234,7 +430,7 @@
|
|
|
234
430
|
position: fixed;
|
|
235
431
|
z-index: 1000;
|
|
236
432
|
min-width: 200px;
|
|
237
|
-
max-width:
|
|
433
|
+
max-width: 360px;
|
|
238
434
|
padding: 12px;
|
|
239
435
|
background: var(--ide-bg-secondary, #1a2744);
|
|
240
436
|
border: 1px solid var(--ide-border, #a8c5d9);
|
|
@@ -255,12 +451,18 @@
|
|
|
255
451
|
display: flex;
|
|
256
452
|
align-items: center;
|
|
257
453
|
justify-content: center;
|
|
258
|
-
width:
|
|
259
|
-
height:
|
|
260
|
-
|
|
261
|
-
font-size: 11px;
|
|
454
|
+
min-width: 38px;
|
|
455
|
+
height: 38px;
|
|
456
|
+
font-size: 24px;
|
|
262
457
|
font-weight: 700;
|
|
263
|
-
color:
|
|
458
|
+
color: var(--indicator-color);
|
|
459
|
+
font-variant-numeric: tabular-nums;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.complexity-tooltip__heading {
|
|
463
|
+
display: flex;
|
|
464
|
+
flex-direction: column;
|
|
465
|
+
gap: 2px;
|
|
264
466
|
}
|
|
265
467
|
|
|
266
468
|
.complexity-tooltip__title {
|
|
@@ -268,12 +470,45 @@
|
|
|
268
470
|
color: var(--ide-text-primary, #f4f1e0);
|
|
269
471
|
}
|
|
270
472
|
|
|
473
|
+
.complexity-tooltip__source {
|
|
474
|
+
font-size: 10px;
|
|
475
|
+
color: var(--ide-text-muted, #a8c5d9);
|
|
476
|
+
}
|
|
477
|
+
|
|
271
478
|
.complexity-tooltip__lines {
|
|
272
479
|
color: var(--ide-text-muted, #a8c5d9);
|
|
273
480
|
font-size: 11px;
|
|
274
481
|
margin-bottom: 8px;
|
|
275
482
|
}
|
|
276
483
|
|
|
484
|
+
.complexity-tooltip__contributions {
|
|
485
|
+
list-style: none;
|
|
486
|
+
margin: 0 0 8px;
|
|
487
|
+
padding: 0;
|
|
488
|
+
display: grid;
|
|
489
|
+
gap: 3px;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.complexity-tooltip__contributions li {
|
|
493
|
+
display: grid;
|
|
494
|
+
grid-template-columns: 52px 34px minmax(0, 1fr);
|
|
495
|
+
gap: 6px;
|
|
496
|
+
align-items: baseline;
|
|
497
|
+
font-size: 11px;
|
|
498
|
+
color: var(--ide-text-secondary, #a8c5d9);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.complexity-tooltip__contributions strong {
|
|
502
|
+
color: var(--ide-text-primary, #f4f1e0);
|
|
503
|
+
font-variant-numeric: tabular-nums;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.complexity-tooltip__contributions .complexity-tooltip__more {
|
|
507
|
+
display: block;
|
|
508
|
+
color: var(--ide-text-muted, #a8c5d9);
|
|
509
|
+
font-style: italic;
|
|
510
|
+
}
|
|
511
|
+
|
|
277
512
|
.complexity-tooltip__suggestion {
|
|
278
513
|
padding: 8px;
|
|
279
514
|
background: rgba(255, 255, 255, 0.05);
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Complexity Layer
|
|
3
|
+
*
|
|
4
|
+
* Shows complexity indicators in the gutter area only.
|
|
5
|
+
* No background highlighting - keeps editing area clean.
|
|
6
|
+
* Hover over gutter indicators for details.
|
|
7
|
+
*/
|
|
1
8
|
import type { ComplexityMetrics } from './core/complexity-analyzer';
|
|
2
9
|
interface Props {
|
|
3
10
|
/** Complexity metrics for the current document */
|
|
@@ -6,10 +13,23 @@ interface Props {
|
|
|
6
13
|
lineHeight: number;
|
|
7
14
|
/** Gutter width in pixels */
|
|
8
15
|
gutterWidth?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Full height of the scrollable content in pixels. The layer must span the
|
|
18
|
+
* whole document (not just one viewport) or indicators below the first
|
|
19
|
+
* screenful get clipped — which previously hid every high-complexity region
|
|
20
|
+
* that sat past the initial view.
|
|
21
|
+
*/
|
|
22
|
+
totalHeight?: number;
|
|
23
|
+
/** Estimated full scrollable content width in pixels */
|
|
24
|
+
contentWidth?: number;
|
|
9
25
|
/** Minimum score to show indicators (default: 50) */
|
|
10
26
|
minScore?: number;
|
|
11
27
|
/** Whether indicators are enabled */
|
|
12
28
|
enabled?: boolean;
|
|
29
|
+
/** Region key to briefly emphasize after jump-to-hottest */
|
|
30
|
+
flashRegionKey?: string;
|
|
31
|
+
/** Maps a raw document line to its rendered visual row */
|
|
32
|
+
lineToVisualRow?: (line: number) => number;
|
|
13
33
|
}
|
|
14
34
|
declare const ComplexityLayer: import("svelte").Component<Props, {}, "">;
|
|
15
35
|
type ComplexityLayer = ReturnType<typeof ComplexityLayer>;
|
|
@@ -40,17 +40,20 @@
|
|
|
40
40
|
* Get color for severity level
|
|
41
41
|
*/
|
|
42
42
|
function getSeverityColor(severity: ConflictZone['severity']): string {
|
|
43
|
+
// Route through the shared --ide-* band tokens so conflict severity stays
|
|
44
|
+
// in lockstep with the complexity band system (one source of truth) and
|
|
45
|
+
// avoids the repo's phantom hardcoded-hex footgun.
|
|
43
46
|
switch (severity) {
|
|
44
47
|
case 'critical':
|
|
45
|
-
return '
|
|
48
|
+
return 'var(--ide-error)';
|
|
46
49
|
case 'high':
|
|
47
|
-
return '
|
|
50
|
+
return 'var(--ide-warning)';
|
|
48
51
|
case 'medium':
|
|
49
|
-
return '
|
|
52
|
+
return 'var(--ide-info)';
|
|
50
53
|
case 'low':
|
|
51
|
-
return '
|
|
54
|
+
return 'var(--ide-success)';
|
|
52
55
|
default:
|
|
53
|
-
return '
|
|
56
|
+
return 'var(--ide-text-muted)';
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
|
|
@@ -243,21 +246,25 @@
|
|
|
243
246
|
.conflict-zone {
|
|
244
247
|
position: absolute;
|
|
245
248
|
right: 0;
|
|
249
|
+
/* Tint by the zone's own severity color (was hardcoded red for every
|
|
250
|
+
severity, so low/medium zones still washed red). */
|
|
246
251
|
background: linear-gradient(
|
|
247
252
|
90deg,
|
|
248
|
-
|
|
253
|
+
color-mix(in srgb, var(--zone-color) calc(var(--zone-opacity) * 100%), transparent),
|
|
249
254
|
transparent
|
|
250
255
|
);
|
|
251
|
-
|
|
252
|
-
border-left: 3px solid rgba(239, 68, 68, var(--border-opacity));
|
|
253
|
-
border-left-color: var(--zone-color);
|
|
256
|
+
border-left: 3px solid var(--zone-color);
|
|
254
257
|
pointer-events: auto;
|
|
255
258
|
cursor: pointer;
|
|
256
|
-
transition: background
|
|
259
|
+
transition: background 0.2s ease;
|
|
257
260
|
}
|
|
258
261
|
|
|
259
262
|
.conflict-zone:hover {
|
|
260
|
-
background
|
|
263
|
+
background: linear-gradient(
|
|
264
|
+
90deg,
|
|
265
|
+
color-mix(in srgb, var(--zone-color) calc((var(--zone-opacity) + 0.1) * 100%), transparent),
|
|
266
|
+
transparent
|
|
267
|
+
);
|
|
261
268
|
}
|
|
262
269
|
|
|
263
270
|
.conflict-zone--critical {
|
|
@@ -417,17 +424,17 @@
|
|
|
417
424
|
display: inline-block;
|
|
418
425
|
padding: 1px 4px;
|
|
419
426
|
margin-left: 4px;
|
|
420
|
-
background:
|
|
427
|
+
background: color-mix(in srgb, var(--ide-ai-assistant) 30%, transparent);
|
|
421
428
|
border-radius: 3px;
|
|
422
429
|
font-size: 9px;
|
|
423
|
-
color:
|
|
430
|
+
color: var(--ide-ai-assistant);
|
|
424
431
|
}
|
|
425
432
|
|
|
426
433
|
.conflict-tooltip__suggestion {
|
|
427
434
|
padding: 8px;
|
|
428
|
-
background:
|
|
435
|
+
background: color-mix(in srgb, var(--ide-warning) 12%, transparent);
|
|
429
436
|
border-radius: 4px;
|
|
430
|
-
border-left: 2px solid
|
|
437
|
+
border-left: 2px solid var(--ide-warning);
|
|
431
438
|
color: var(--ide-text-secondary, #a8c5d9);
|
|
432
439
|
font-size: 11px;
|
|
433
440
|
line-height: 1.4;
|