@nocturnium/svelte-ide 1.1.1 → 1.2.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/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 +325 -109
- package/dist/components/editor/ComplexityLayer.svelte.d.ts +13 -0
- package/dist/components/editor/ConflictZoneLayer.svelte +22 -15
- package/dist/components/editor/CustomEditor.svelte +80 -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 {
|
|
@@ -24,10 +23,16 @@
|
|
|
24
23
|
* that sat past the initial view.
|
|
25
24
|
*/
|
|
26
25
|
totalHeight?: number;
|
|
26
|
+
/** Estimated full scrollable content width in pixels */
|
|
27
|
+
contentWidth?: number;
|
|
27
28
|
/** Minimum score to show indicators (default: 50) */
|
|
28
29
|
minScore?: number;
|
|
29
30
|
/** Whether indicators are enabled */
|
|
30
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;
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
let {
|
|
@@ -35,8 +40,11 @@
|
|
|
35
40
|
lineHeight,
|
|
36
41
|
gutterWidth = 50,
|
|
37
42
|
totalHeight = 0,
|
|
43
|
+
contentWidth = 0,
|
|
38
44
|
minScore = 50,
|
|
39
|
-
enabled = true
|
|
45
|
+
enabled = true,
|
|
46
|
+
flashRegionKey = '',
|
|
47
|
+
lineToVisualRow = (line: number) => line
|
|
40
48
|
}: Props = $props();
|
|
41
49
|
|
|
42
50
|
// Filter regions that exceed the threshold
|
|
@@ -44,55 +52,6 @@
|
|
|
44
52
|
enabled && metrics ? metrics.regions.filter((r) => r.score >= minScore) : []
|
|
45
53
|
);
|
|
46
54
|
|
|
47
|
-
// Expand regions to individual line indicators for gutter
|
|
48
|
-
let lineIndicators = $derived.by(() => {
|
|
49
|
-
if (!enabled || !metrics) return [];
|
|
50
|
-
|
|
51
|
-
const indicators: Array<{
|
|
52
|
-
line: number;
|
|
53
|
-
score: number;
|
|
54
|
-
region: ComplexityRegion;
|
|
55
|
-
isStart: boolean;
|
|
56
|
-
isEnd: boolean;
|
|
57
|
-
}> = [];
|
|
58
|
-
|
|
59
|
-
for (const region of highlightedRegions) {
|
|
60
|
-
for (let line = region.startLine; line <= region.endLine; line++) {
|
|
61
|
-
indicators.push({
|
|
62
|
-
line,
|
|
63
|
-
score: region.score,
|
|
64
|
-
region,
|
|
65
|
-
isStart: line === region.startLine,
|
|
66
|
-
isEnd: line === region.endLine
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return indicators;
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// Group by line (in case of overlapping regions, take highest score)
|
|
75
|
-
let lineScores = $derived.by(() => {
|
|
76
|
-
const scores = new SvelteMap<
|
|
77
|
-
number,
|
|
78
|
-
{ score: number; region: ComplexityRegion; isStart: boolean; isEnd: boolean }
|
|
79
|
-
>();
|
|
80
|
-
|
|
81
|
-
for (const indicator of lineIndicators) {
|
|
82
|
-
const existing = scores.get(indicator.line);
|
|
83
|
-
if (!existing || indicator.score > existing.score) {
|
|
84
|
-
scores.set(indicator.line, {
|
|
85
|
-
score: indicator.score,
|
|
86
|
-
region: indicator.region,
|
|
87
|
-
isStart: indicator.isStart,
|
|
88
|
-
isEnd: indicator.isEnd
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return scores;
|
|
94
|
-
});
|
|
95
|
-
|
|
96
55
|
/**
|
|
97
56
|
* Get the color based on score level
|
|
98
57
|
*/
|
|
@@ -101,7 +60,8 @@
|
|
|
101
60
|
// medium otherwise (lines below the minScore threshold aren't shown).
|
|
102
61
|
if (score >= 85) return 'var(--ide-error)'; // Critical
|
|
103
62
|
if (score >= 70) return 'var(--ide-warning)'; // High
|
|
104
|
-
return 'var(--ide-info)'; // Medium
|
|
63
|
+
if (score >= 50) return 'var(--ide-info)'; // Medium
|
|
64
|
+
return 'var(--ide-success)'; // Low
|
|
105
65
|
}
|
|
106
66
|
|
|
107
67
|
/**
|
|
@@ -109,19 +69,64 @@
|
|
|
109
69
|
*/
|
|
110
70
|
function getOpacity(score: number): number {
|
|
111
71
|
const normalized = Math.min(1, Math.max(0, (score - minScore) / (100 - minScore)));
|
|
112
|
-
return 0.
|
|
72
|
+
return 0.65 + normalized * 0.35;
|
|
113
73
|
}
|
|
114
74
|
|
|
115
75
|
let hoveredRegion = $state<ComplexityRegion | null>(null);
|
|
116
|
-
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
|
+
}
|
|
117
99
|
|
|
118
|
-
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
|
+
) {
|
|
119
119
|
hoveredRegion = region;
|
|
120
|
-
const rect = (event.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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 };
|
|
125
130
|
}
|
|
126
131
|
|
|
127
132
|
function handleMouseLeave() {
|
|
@@ -129,31 +134,45 @@
|
|
|
129
134
|
}
|
|
130
135
|
</script>
|
|
131
136
|
|
|
132
|
-
{#if enabled &&
|
|
137
|
+
{#if enabled && highlightedRegions.length > 0}
|
|
133
138
|
<div
|
|
134
139
|
class="complexity-gutter"
|
|
135
140
|
aria-hidden="true"
|
|
136
|
-
style="width: {
|
|
141
|
+
style="width: {contentWidth ? `${contentWidth}px` : '100%'};{totalHeight
|
|
142
|
+
? ` height: ${totalHeight}px;`
|
|
143
|
+
: ''} --editor-gutter-width: {gutterWidth}px;"
|
|
137
144
|
>
|
|
138
|
-
{#each
|
|
145
|
+
{#each highlightedRegions as region (getRegionKey(region))}
|
|
146
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
139
147
|
<div
|
|
140
|
-
class="complexity-
|
|
141
|
-
class:complexity-
|
|
142
|
-
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)}
|
|
143
151
|
style="
|
|
144
|
-
top: {
|
|
145
|
-
height: {
|
|
146
|
-
--indicator-color: {getColor(
|
|
147
|
-
--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;
|
|
148
157
|
"
|
|
149
|
-
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')}
|
|
150
173
|
onmouseleave={handleMouseLeave}
|
|
151
|
-
role="button"
|
|
152
|
-
tabindex="-1"
|
|
153
174
|
>
|
|
154
|
-
{
|
|
155
|
-
<span class="complexity-gutter__score">{data.score}</span>
|
|
156
|
-
{/if}
|
|
175
|
+
{region.score}
|
|
157
176
|
</div>
|
|
158
177
|
{/each}
|
|
159
178
|
</div>
|
|
@@ -162,19 +181,40 @@
|
|
|
162
181
|
{#if hoveredRegion}
|
|
163
182
|
<div
|
|
164
183
|
class="complexity-tooltip"
|
|
165
|
-
style=
|
|
184
|
+
style={tooltipAnchor === 'left'
|
|
185
|
+
? `top: ${tooltipPosition.top}px; right: ${tooltipPosition.right}px;`
|
|
186
|
+
: `top: ${tooltipPosition.top}px; left: ${tooltipPosition.left}px;`}
|
|
166
187
|
>
|
|
167
188
|
<div class="complexity-tooltip__header">
|
|
168
|
-
<span class="complexity-tooltip__badge" style="
|
|
169
|
-
{hoveredRegion.
|
|
170
|
-
</span>
|
|
171
|
-
<span class="complexity-tooltip__title">
|
|
172
|
-
{hoveredRegion.name || hoveredRegion.type}
|
|
189
|
+
<span class="complexity-tooltip__badge" style="color: {getColor(hoveredRegion.score)}">
|
|
190
|
+
{hoveredRegion.cognitiveComplexity}
|
|
173
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>
|
|
174
198
|
</div>
|
|
175
199
|
<div class="complexity-tooltip__lines">
|
|
176
|
-
Lines {hoveredRegion.startLine + 1} - {hoveredRegion.endLine +
|
|
200
|
+
{hoveredRegion.name || hoveredRegion.type} · Lines {hoveredRegion.startLine + 1} - {hoveredRegion.endLine +
|
|
201
|
+
1}
|
|
202
|
+
· Score {hoveredRegion.score}
|
|
177
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}
|
|
178
218
|
{#if hoveredRegion.suggestion}
|
|
179
219
|
<div class="complexity-tooltip__suggestion">
|
|
180
220
|
{hoveredRegion.suggestion}
|
|
@@ -199,53 +239,190 @@
|
|
|
199
239
|
bottom: 0;
|
|
200
240
|
pointer-events: none;
|
|
201
241
|
z-index: 5;
|
|
242
|
+
overflow: visible;
|
|
202
243
|
}
|
|
203
244
|
|
|
204
|
-
.complexity-
|
|
245
|
+
.complexity-gutter__spine {
|
|
205
246
|
position: absolute;
|
|
206
|
-
|
|
247
|
+
left: calc(var(--editor-gutter-width, 50px) - 3px);
|
|
207
248
|
width: 4px;
|
|
208
249
|
pointer-events: auto;
|
|
209
250
|
cursor: help;
|
|
210
251
|
background: var(--indicator-color);
|
|
211
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;
|
|
212
256
|
transition:
|
|
213
257
|
opacity 0.15s ease,
|
|
214
|
-
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;
|
|
215
274
|
}
|
|
216
275
|
|
|
217
|
-
.complexity-
|
|
276
|
+
.complexity-gutter__spine:hover {
|
|
218
277
|
opacity: 1;
|
|
219
278
|
width: 6px;
|
|
279
|
+
transform: translateX(-1px);
|
|
220
280
|
}
|
|
221
281
|
|
|
222
|
-
.complexity-
|
|
223
|
-
|
|
282
|
+
.complexity-gutter__spine--critical {
|
|
283
|
+
animation: complexity-spine-pulse 2.4s ease-in-out infinite;
|
|
224
284
|
}
|
|
225
285
|
|
|
226
|
-
.complexity-
|
|
227
|
-
|
|
286
|
+
.complexity-gutter__spine--critical::before {
|
|
287
|
+
animation: complexity-spine-bloom-pulse 2.4s ease-in-out infinite;
|
|
228
288
|
}
|
|
229
289
|
|
|
230
|
-
.complexity-
|
|
231
|
-
|
|
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;
|
|
232
296
|
}
|
|
233
297
|
|
|
234
298
|
.complexity-gutter__score {
|
|
235
299
|
position: absolute;
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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);
|
|
241
312
|
color: var(--indicator-color);
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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;
|
|
245
326
|
}
|
|
246
327
|
|
|
247
|
-
.complexity-
|
|
248
|
-
|
|
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
|
+
}
|
|
249
426
|
}
|
|
250
427
|
|
|
251
428
|
/* Tooltip */
|
|
@@ -253,7 +430,7 @@
|
|
|
253
430
|
position: fixed;
|
|
254
431
|
z-index: 1000;
|
|
255
432
|
min-width: 200px;
|
|
256
|
-
max-width:
|
|
433
|
+
max-width: 360px;
|
|
257
434
|
padding: 12px;
|
|
258
435
|
background: var(--ide-bg-secondary, #1a2744);
|
|
259
436
|
border: 1px solid var(--ide-border, #a8c5d9);
|
|
@@ -274,12 +451,18 @@
|
|
|
274
451
|
display: flex;
|
|
275
452
|
align-items: center;
|
|
276
453
|
justify-content: center;
|
|
277
|
-
width:
|
|
278
|
-
height:
|
|
279
|
-
|
|
280
|
-
font-size: 11px;
|
|
454
|
+
min-width: 38px;
|
|
455
|
+
height: 38px;
|
|
456
|
+
font-size: 24px;
|
|
281
457
|
font-weight: 700;
|
|
282
|
-
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;
|
|
283
466
|
}
|
|
284
467
|
|
|
285
468
|
.complexity-tooltip__title {
|
|
@@ -287,12 +470,45 @@
|
|
|
287
470
|
color: var(--ide-text-primary, #f4f1e0);
|
|
288
471
|
}
|
|
289
472
|
|
|
473
|
+
.complexity-tooltip__source {
|
|
474
|
+
font-size: 10px;
|
|
475
|
+
color: var(--ide-text-muted, #a8c5d9);
|
|
476
|
+
}
|
|
477
|
+
|
|
290
478
|
.complexity-tooltip__lines {
|
|
291
479
|
color: var(--ide-text-muted, #a8c5d9);
|
|
292
480
|
font-size: 11px;
|
|
293
481
|
margin-bottom: 8px;
|
|
294
482
|
}
|
|
295
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
|
+
|
|
296
512
|
.complexity-tooltip__suggestion {
|
|
297
513
|
padding: 8px;
|
|
298
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 */
|
|
@@ -13,10 +20,16 @@ interface Props {
|
|
|
13
20
|
* that sat past the initial view.
|
|
14
21
|
*/
|
|
15
22
|
totalHeight?: number;
|
|
23
|
+
/** Estimated full scrollable content width in pixels */
|
|
24
|
+
contentWidth?: number;
|
|
16
25
|
/** Minimum score to show indicators (default: 50) */
|
|
17
26
|
minScore?: number;
|
|
18
27
|
/** Whether indicators are enabled */
|
|
19
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;
|
|
20
33
|
}
|
|
21
34
|
declare const ComplexityLayer: import("svelte").Component<Props, {}, "">;
|
|
22
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;
|