@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.
@@ -83,9 +83,11 @@
83
83
  * Get the color based on score level
84
84
  */
85
85
  function getColor(score: number): string {
86
- if (score >= 85) return '#ef4444'; // Critical - red
87
- if (score >= 70) return '#f59e0b'; // High - orange
88
- return '#3b82f6'; // Medium - blue
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
- // First expand all
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, {}, "content">;
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
- nestingDepth: 15,
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
- return region.score;
88
+ if (line >= region.startLine && line <= region.endLine && region.score > best) {
89
+ best = region.score;
83
90
  }
84
91
  }
85
- return 0;
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.5",
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",