@nocturnium/svelte-ide 1.0.5 → 1.1.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/ComplexityLayer.svelte +5 -3
- package/dist/components/editor/CustomEditor.svelte +46 -12
- package/dist/components/editor/CustomEditor.svelte.d.ts +5 -1
- package/dist/components/editor/core/complexity-analyzer.js +25 -4
- package/dist/components/editor/core/folding.d.ts +3 -4
- package/dist/components/editor/core/folding.js +36 -0
- package/package.json +1 -1
|
@@ -83,9 +83,11 @@
|
|
|
83
83
|
* Get the color based on score level
|
|
84
84
|
*/
|
|
85
85
|
function getColor(score: number): string {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return '
|
|
86
|
+
// Bands match the analyzer's levels: critical >= 85, high >= 70,
|
|
87
|
+
// medium otherwise (lines below the minScore threshold aren't shown).
|
|
88
|
+
if (score >= 85) return 'var(--ide-error)'; // Critical
|
|
89
|
+
if (score >= 70) return 'var(--ide-warning)'; // High
|
|
90
|
+
return 'var(--ide-info)'; // Medium
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
/**
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
import CommandPalette from './CommandPalette.svelte';
|
|
27
27
|
import { getComplexityAnalyzer, type ComplexityMetrics } from './core/complexity-analyzer';
|
|
28
28
|
import { registerSemanticFoldCommands } from './core/commands';
|
|
29
|
-
import { getSemanticAnalyzer } from './core/semantic-analyzer';
|
|
29
|
+
import { getSemanticAnalyzer, type FoldPreset } from './core/semantic-analyzer';
|
|
30
30
|
import type { AIAwareness } from './core/ai-awareness';
|
|
31
31
|
import {
|
|
32
32
|
CURSOR_BLINK_MS,
|
|
@@ -501,6 +501,50 @@
|
|
|
501
501
|
}
|
|
502
502
|
}
|
|
503
503
|
|
|
504
|
+
/**
|
|
505
|
+
* Collapse every semantic region whose category is listed in `preset.hide`,
|
|
506
|
+
* after first expanding everything so presets compose predictably. Shared by
|
|
507
|
+
* the command-palette preset commands and the exported imperative API.
|
|
508
|
+
*/
|
|
509
|
+
function applyFoldPresetInternal(preset: FoldPreset) {
|
|
510
|
+
if (!folding || !editorState) return;
|
|
511
|
+
foldManager.expandAll();
|
|
512
|
+
const analyzer = getSemanticAnalyzer();
|
|
513
|
+
const semanticRegions = analyzer.analyze(editorState.lines, language);
|
|
514
|
+
const foldRegions = foldManager.getRegions();
|
|
515
|
+
|
|
516
|
+
// Semantic region boundaries don't always line up exactly with bracket-fold
|
|
517
|
+
// headers — a function's foldable `{` can sit a line below its doc-commented
|
|
518
|
+
// header, and a hidden category (e.g. `private`, `error-handling`) may cover
|
|
519
|
+
// several nested blocks. So collapse every foldable block whose header falls
|
|
520
|
+
// within a hidden semantic region's range, rather than requiring an exact
|
|
521
|
+
// header-line match (which silently folded almost nothing). collapse() is
|
|
522
|
+
// idempotent, so collapsing the same header twice is harmless.
|
|
523
|
+
for (const region of semanticRegions) {
|
|
524
|
+
if (!preset.hide.includes(region.category)) continue;
|
|
525
|
+
for (const fold of foldRegions) {
|
|
526
|
+
if (fold.startLine >= region.startLine && fold.startLine <= region.endLine) {
|
|
527
|
+
foldManager.collapse(fold.startLine);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Imperative API (accessible via `bind:this`): apply a semantic fold preset.
|
|
535
|
+
*/
|
|
536
|
+
export function applyFoldPreset(preset: FoldPreset) {
|
|
537
|
+
applyFoldPresetInternal(preset);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Imperative API (accessible via `bind:this`): expand all folded regions.
|
|
542
|
+
*/
|
|
543
|
+
export function unfoldAll() {
|
|
544
|
+
if (!folding || !editorState) return;
|
|
545
|
+
foldManager.expandAll();
|
|
546
|
+
}
|
|
547
|
+
|
|
504
548
|
function handleFoldIndicatorClick(lineNumber: number, e: MouseEvent) {
|
|
505
549
|
e.preventDefault();
|
|
506
550
|
e.stopPropagation();
|
|
@@ -787,17 +831,7 @@
|
|
|
787
831
|
foldManager?.expandAll();
|
|
788
832
|
},
|
|
789
833
|
onApplyPreset: (preset) => {
|
|
790
|
-
|
|
791
|
-
foldManager?.expandAll();
|
|
792
|
-
// Then collapse categories that should be hidden
|
|
793
|
-
const analyzer = getSemanticAnalyzer();
|
|
794
|
-
const regions = analyzer.analyze(editorState.lines, language);
|
|
795
|
-
for (const category of preset.hide) {
|
|
796
|
-
const categoryRegions = analyzer.getByCategory(regions, category);
|
|
797
|
-
for (const region of categoryRegions) {
|
|
798
|
-
foldManager?.collapse(region.startLine);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
834
|
+
applyFoldPresetInternal(preset);
|
|
801
835
|
},
|
|
802
836
|
getLanguage: () => language
|
|
803
837
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { EditorPreferences } from '../../types';
|
|
2
2
|
import { type Cursor } from './core';
|
|
3
3
|
import { type ComplexityMetrics } from './core/complexity-analyzer';
|
|
4
|
+
import { type FoldPreset } from './core/semantic-analyzer';
|
|
4
5
|
import type { AIAwareness } from './core/ai-awareness';
|
|
5
6
|
interface Props {
|
|
6
7
|
content: string;
|
|
@@ -32,6 +33,9 @@ interface Props {
|
|
|
32
33
|
onComplexityChange?: (metrics: ComplexityMetrics | null) => void;
|
|
33
34
|
onSave?: () => void;
|
|
34
35
|
}
|
|
35
|
-
declare const CustomEditor: import("svelte").Component<Props, {
|
|
36
|
+
declare const CustomEditor: import("svelte").Component<Props, {
|
|
37
|
+
applyFoldPreset: (preset: FoldPreset) => void;
|
|
38
|
+
unfoldAll: () => void;
|
|
39
|
+
}, "content">;
|
|
36
40
|
type CustomEditor = ReturnType<typeof CustomEditor>;
|
|
37
41
|
export default CustomEditor;
|
|
@@ -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
|
|
@@ -234,6 +241,20 @@ export class ComplexityAnalyzer {
|
|
|
234
241
|
*/
|
|
235
242
|
getDefinitionCandidate(text, funcMatch, allowMethodStyle) {
|
|
236
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
|
+
}
|
|
237
258
|
return {
|
|
238
259
|
type: funcMatch[5] ? 'class' : 'function',
|
|
239
260
|
name: funcMatch[2] || funcMatch[3] || funcMatch[4] || funcMatch[5]
|
|
@@ -18,7 +18,7 @@ export interface FoldRegion {
|
|
|
18
18
|
/** Indentation level (for nested folds) */
|
|
19
19
|
level: number;
|
|
20
20
|
/** Type of fold region */
|
|
21
|
-
type: 'bracket' | 'indentation' | 'region' | 'comment';
|
|
21
|
+
type: 'bracket' | 'indentation' | 'region' | 'comment' | 'import';
|
|
22
22
|
/** Whether this region is currently collapsed */
|
|
23
23
|
collapsed: boolean;
|
|
24
24
|
}
|
|
@@ -34,12 +34,11 @@ export interface FoldingConfig {
|
|
|
34
34
|
regions: boolean;
|
|
35
35
|
/** Enable multi-line comment folding */
|
|
36
36
|
comments: boolean;
|
|
37
|
+
/** Enable folding a run of consecutive import statements */
|
|
38
|
+
imports: boolean;
|
|
37
39
|
/** Minimum lines for a foldable region */
|
|
38
40
|
minLines: number;
|
|
39
41
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Detect all fold regions in a document
|
|
42
|
-
*/
|
|
43
42
|
export declare function detectFoldRegions(lines: readonly Line[], language?: string, config?: Partial<FoldingConfig>): FoldRegion[];
|
|
44
43
|
/**
|
|
45
44
|
* Fold state manager
|
|
@@ -11,6 +11,7 @@ const DEFAULT_CONFIG = {
|
|
|
11
11
|
indentation: true,
|
|
12
12
|
regions: true,
|
|
13
13
|
comments: true,
|
|
14
|
+
imports: true,
|
|
14
15
|
minLines: 2
|
|
15
16
|
};
|
|
16
17
|
/**
|
|
@@ -424,6 +425,38 @@ function detectCommentFolds(lines, minLines = 2) {
|
|
|
424
425
|
/**
|
|
425
426
|
* Detect all fold regions in a document
|
|
426
427
|
*/
|
|
428
|
+
/**
|
|
429
|
+
* Detect a fold region for each run of consecutive import statements. Import
|
|
430
|
+
* groups have no braces of their own, so without this an editor (and the
|
|
431
|
+
* "fold imports" semantic preset) can't collapse them.
|
|
432
|
+
*/
|
|
433
|
+
function detectImportFolds(lines, minLines = 2) {
|
|
434
|
+
const regions = [];
|
|
435
|
+
// ES `import ... from`, side-effect `import '...'`, and Python `from x import`.
|
|
436
|
+
const isImportLine = (text) => /^\s*import\b/.test(text) || /^\s*from\s+\S+\s+import\b/.test(text);
|
|
437
|
+
let start = -1;
|
|
438
|
+
for (let i = 0; i <= lines.length; i++) {
|
|
439
|
+
const isImport = i < lines.length && isImportLine(lines[i].text);
|
|
440
|
+
if (isImport) {
|
|
441
|
+
if (start === -1)
|
|
442
|
+
start = i;
|
|
443
|
+
}
|
|
444
|
+
else if (start !== -1) {
|
|
445
|
+
const end = i - 1;
|
|
446
|
+
if (end - start + 1 >= minLines) {
|
|
447
|
+
regions.push({
|
|
448
|
+
startLine: start,
|
|
449
|
+
endLine: end,
|
|
450
|
+
level: 0,
|
|
451
|
+
type: 'import',
|
|
452
|
+
collapsed: false
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
start = -1;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return regions;
|
|
459
|
+
}
|
|
427
460
|
export function detectFoldRegions(lines, language = 'plaintext', config = {}) {
|
|
428
461
|
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
429
462
|
const allRegions = [];
|
|
@@ -459,6 +492,9 @@ export function detectFoldRegions(lines, language = 'plaintext', config = {}) {
|
|
|
459
492
|
if (cfg.comments) {
|
|
460
493
|
allRegions.push(...detectCommentFolds(lines, cfg.minLines));
|
|
461
494
|
}
|
|
495
|
+
if (cfg.imports) {
|
|
496
|
+
allRegions.push(...detectImportFolds(lines, cfg.minLines));
|
|
497
|
+
}
|
|
462
498
|
// Sort by start line, then by length (longer regions first for nesting)
|
|
463
499
|
allRegions.sort((a, b) => {
|
|
464
500
|
if (a.startLine !== b.startLine) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocturnium/svelte-ide",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
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",
|