@nocturnium/svelte-ide 1.0.2 → 1.0.4

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.
Files changed (78) hide show
  1. package/README.md +5 -3
  2. package/dist/components/ai/AIMessageContent.svelte +24 -14
  3. package/dist/components/ai/AIPanel.svelte +22 -0
  4. package/dist/components/editor/CollaborativeEditor.svelte +68 -5
  5. package/dist/components/editor/CollaborativeEditor.svelte.d.ts +14 -0
  6. package/dist/components/editor/ContextLens.svelte +16 -10
  7. package/dist/components/editor/CustomEditor.svelte +52 -33
  8. package/dist/components/editor/CustomEditor.svelte.d.ts +2 -2
  9. package/dist/components/editor/EchoCursorLayer.svelte +43 -11
  10. package/dist/components/editor/Editor.svelte +17 -0
  11. package/dist/components/editor/Editor.svelte.d.ts +9 -0
  12. package/dist/components/editor/EditorPane.svelte +18 -1
  13. package/dist/components/editor/EditorPane.svelte.d.ts +5 -0
  14. package/dist/components/editor/EditorSelections.svelte +27 -11
  15. package/dist/components/editor/EditorSelections.svelte.d.ts +1 -0
  16. package/dist/components/editor/GhostBracketLayer.svelte +38 -25
  17. package/dist/components/editor/core/folding.d.ts +11 -0
  18. package/dist/components/editor/core/folding.js +41 -0
  19. package/dist/components/editor/core/index.d.ts +0 -5
  20. package/dist/components/editor/core/index.js +4 -5
  21. package/dist/components/editor/core/state.d.ts +5 -0
  22. package/dist/components/editor/core/state.js +131 -12
  23. package/dist/components/editor/editor-find.d.ts +1 -0
  24. package/dist/components/editor/editor-find.js +6 -5
  25. package/dist/components/editor/editor-input.d.ts +1 -0
  26. package/dist/components/editor/editor-input.js +4 -1
  27. package/dist/components/editor/editor-scroll.d.ts +1 -0
  28. package/dist/components/editor/editor-scroll.js +2 -1
  29. package/dist/components/editor/index.d.ts +19 -3
  30. package/dist/components/editor/index.js +18 -4
  31. package/dist/components/editor/tokenizer/base.d.ts +1 -25
  32. package/dist/components/editor/tokenizer/base.js +0 -172
  33. package/dist/components/editor/tokenizer/index.d.ts +4 -0
  34. package/dist/components/editor/tokenizer/index.js +1 -1
  35. package/dist/components/editor/tokenizer/languages/html.d.ts +3 -2
  36. package/dist/components/editor/tokenizer/languages/html.js +64 -6
  37. package/dist/components/editor/tokenizer/languages/javascript.d.ts +0 -3
  38. package/dist/components/editor/tokenizer/languages/javascript.js +1 -2
  39. package/dist/components/editor/tokenizer/languages/svelte.d.ts +1 -1
  40. package/dist/components/editor/tokenizer/languages/svelte.js +6 -1
  41. package/dist/components/editor/tokenizer/types.d.ts +0 -28
  42. package/dist/crdt/awareness.d.ts +8 -2
  43. package/dist/crdt/awareness.js +11 -4
  44. package/dist/crdt/document.d.ts +10 -1
  45. package/dist/crdt/document.js +15 -7
  46. package/dist/crdt/index.d.ts +8 -2
  47. package/dist/crdt/index.js +5 -2
  48. package/dist/crdt/undo.d.ts +2 -7
  49. package/dist/crdt/undo.js +1 -8
  50. package/dist/index.d.ts +7 -9
  51. package/dist/index.js +7 -9
  52. package/dist/services/error-handling.d.ts +2 -11
  53. package/dist/services/error-handling.js +15 -4
  54. package/dist/services/lsp-client.d.ts +3 -0
  55. package/dist/services/lsp-client.js +55 -10
  56. package/dist/services/mock-ai.js +1 -1
  57. package/dist/services/optimistic.d.ts +8 -5
  58. package/dist/services/optimistic.js +36 -10
  59. package/dist/services/vfs-client.js +11 -3
  60. package/dist/stores/agents.svelte.js +3 -2
  61. package/dist/stores/ai-persistence.svelte.js +7 -2
  62. package/dist/stores/ai.svelte.js +3 -2
  63. package/dist/stores/collaboration.svelte.d.ts +1 -1
  64. package/dist/stores/collaboration.svelte.js +3 -2
  65. package/dist/stores/editor.svelte.js +29 -5
  66. package/dist/stores/layout.svelte.js +3 -0
  67. package/dist/stores/plugin.svelte.js +9 -3
  68. package/dist/stores/vfs.svelte.js +26 -9
  69. package/dist/styles/theme.css +43 -0
  70. package/dist/types/vfs.d.ts +15 -1
  71. package/dist/types/vfs.js +9 -0
  72. package/dist/utils/language.d.ts +4 -3
  73. package/dist/utils/language.js +8 -18
  74. package/package.json +1 -1
  75. package/dist/components/editor/MinimalEditor.svelte +0 -75
  76. package/dist/components/editor/MinimalEditor.svelte.d.ts +0 -6
  77. package/dist/components/editor/MinimalEditor2.svelte +0 -84
  78. package/dist/components/editor/MinimalEditor2.svelte.d.ts +0 -6
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { getTokenizer } from '../tokenizer';
9
9
  import { HISTORY_GROUP_TIMEOUT_MS, MAX_HISTORY_SIZE } from '../constants';
10
- import { CursorManager, createCursorManager, getSelectionStart, getSelectionEnd, isSelectionEmpty } from './multi-cursor';
10
+ import { CursorManager, createCursorManager, comparePositions, getSelectionStart, getSelectionEnd, isSelectionEmpty } from './multi-cursor';
11
11
  /**
12
12
  * Core editor state class
13
13
  */
@@ -361,10 +361,12 @@ export class EditorState {
361
361
  const start = getSelectionStart(cursor.selection);
362
362
  const end = getSelectionEnd(cursor.selection);
363
363
  this.deleteRangeInternal(start, end);
364
+ this.transformCursorUpdatesForDelete(updates, start, end);
364
365
  currentPos = start;
365
366
  }
366
367
  // Insert text
367
368
  const newPos = this.insertAtInternal(currentPos, text);
369
+ this.transformCursorUpdatesForInsert(updates, currentPos, text);
368
370
  updates.push({ id: cursor.id, position: newPos });
369
371
  }
370
372
  // Apply all cursor updates using batch update to avoid multiple merge/emit calls
@@ -453,6 +455,7 @@ export class EditorState {
453
455
  const start = getSelectionStart(cursor.selection);
454
456
  const end = getSelectionEnd(cursor.selection);
455
457
  this.deleteRangeInternal(start, end);
458
+ this.transformCursorUpdatesForDelete(updates, start, end);
456
459
  updates.push({ id: cursor.id, position: start });
457
460
  }
458
461
  }
@@ -519,25 +522,32 @@ export class EditorState {
519
522
  for (const cursor of cursors) {
520
523
  const { line, column } = cursor.selection.head;
521
524
  if (column > 0) {
525
+ const from = { line, column: column - 1 };
526
+ const to = { line, column };
522
527
  // Delete within line
523
528
  const currentLine = this._lines[line];
524
- currentLine.text = currentLine.text.slice(0, column - 1) + currentLine.text.slice(column);
529
+ currentLine.text =
530
+ currentLine.text.slice(0, from.column) + currentLine.text.slice(to.column);
525
531
  // Re-tokenize to end of document so deleting a multi-line construct
526
532
  // delimiter re-propagates state to lines below.
527
533
  this.retokenize(line, this._lines.length);
528
- updates.push({ id: cursor.id, position: { line, column: column - 1 } });
534
+ this.transformCursorUpdatesForDelete(updates, from, to);
535
+ updates.push({ id: cursor.id, position: from });
529
536
  }
530
537
  else if (line > 0) {
531
538
  // Join with previous line
532
539
  const prevLine = this._lines[line - 1];
533
540
  const currentLine = this._lines[line];
534
541
  const newColumn = prevLine.text.length;
542
+ const from = { line: line - 1, column: newColumn };
543
+ const to = { line, column: 0 };
535
544
  prevLine.text += currentLine.text;
536
545
  this._lines.splice(line, 1);
537
546
  this._tokenizerStates.splice(line, 1);
538
547
  this.renumberLines(line);
539
548
  this.retokenize(line - 1, this._lines.length);
540
- updates.push({ id: cursor.id, position: { line: line - 1, column: newColumn } });
549
+ this.transformCursorUpdatesForDelete(updates, from, to);
550
+ updates.push({ id: cursor.id, position: from });
541
551
  }
542
552
  }
543
553
  // Apply all cursor updates using batch update
@@ -564,28 +574,41 @@ export class EditorState {
564
574
  return;
565
575
  }
566
576
  this.saveHistory('delete');
577
+ const updates = [];
567
578
  for (const cursor of cursors) {
568
579
  const { line, column } = cursor.selection.head;
569
580
  const currentLine = this._lines[line];
570
581
  if (column < currentLine.text.length) {
582
+ const from = { line, column };
583
+ const to = { line, column: column + 1 };
571
584
  // Delete within line
572
- currentLine.text = currentLine.text.slice(0, column) + currentLine.text.slice(column + 1);
585
+ currentLine.text =
586
+ currentLine.text.slice(0, from.column) + currentLine.text.slice(to.column);
573
587
  // Re-tokenize to end of document so deleting a multi-line construct
574
588
  // delimiter re-propagates state to lines below.
575
589
  this.retokenize(line, this._lines.length);
590
+ this.transformCursorUpdatesForDelete(updates, from, to);
591
+ updates.push({ id: cursor.id, position: from });
576
592
  }
577
593
  else if (line < this._lines.length - 1) {
578
594
  // Join with next line
579
595
  const nextLine = this._lines[line + 1];
596
+ const from = { line, column };
597
+ const to = { line: line + 1, column: 0 };
580
598
  currentLine.text += nextLine.text;
581
599
  this._lines.splice(line + 1, 1);
582
600
  this._tokenizerStates.splice(line + 1, 1);
583
601
  this.renumberLines(line + 1);
584
602
  this.retokenize(line, this._lines.length);
603
+ this.transformCursorUpdatesForDelete(updates, from, to);
604
+ updates.push({ id: cursor.id, position: from });
585
605
  }
586
606
  }
607
+ // Apply all cursor updates using batch update
608
+ this._cursorManager.batchUpdateCursors(updates);
587
609
  this.emitChange({ type: 'delete', from: this.cursor, to: this.cursor });
588
610
  this.emitSelectionChange();
611
+ this.emitCursorChange();
589
612
  }
590
613
  /**
591
614
  * Insert a new line (enter key)
@@ -624,8 +647,8 @@ export class EditorState {
624
647
  timestamp: now
625
648
  };
626
649
  if (shouldMerge) {
627
- // Update the last entry instead of creating a new one
628
- this._undoStack[this._undoStack.length - 1] = entry;
650
+ // Keep the original restore snapshot; only extend the group's recency.
651
+ this._undoStack[this._undoStack.length - 1].timestamp = now;
629
652
  }
630
653
  else {
631
654
  this._undoStack.push(entry);
@@ -715,6 +738,57 @@ export class EditorState {
715
738
  head: { line: sel.head.line, column: sel.head.column }
716
739
  };
717
740
  }
741
+ transformCursorUpdatesForInsert(updates, at, text) {
742
+ for (const update of updates) {
743
+ update.position = this.transformPositionForInsert(update.position, at, text);
744
+ }
745
+ }
746
+ transformPositionForInsert(position, at, text) {
747
+ if (comparePositions(position, at) < 0) {
748
+ return position;
749
+ }
750
+ const textLines = text.split('\n');
751
+ const insertedLineCount = textLines.length - 1;
752
+ if (insertedLineCount === 0) {
753
+ if (position.line !== at.line) {
754
+ return position;
755
+ }
756
+ return { line: position.line, column: position.column + text.length };
757
+ }
758
+ if (position.line === at.line) {
759
+ return {
760
+ line: position.line + insertedLineCount,
761
+ column: textLines[textLines.length - 1].length + position.column - at.column
762
+ };
763
+ }
764
+ return { line: position.line + insertedLineCount, column: position.column };
765
+ }
766
+ transformCursorUpdatesForDelete(updates, from, to) {
767
+ for (const update of updates) {
768
+ update.position = this.transformPositionForDelete(update.position, from, to);
769
+ }
770
+ }
771
+ transformPositionForDelete(position, from, to) {
772
+ if (comparePositions(position, from) <= 0) {
773
+ return position;
774
+ }
775
+ if (comparePositions(position, to) <= 0) {
776
+ return from;
777
+ }
778
+ if (from.line === to.line) {
779
+ if (position.line !== from.line) {
780
+ return position;
781
+ }
782
+ return { line: position.line, column: position.column - (to.column - from.column) };
783
+ }
784
+ if (position.line === to.line) {
785
+ return {
786
+ line: from.line,
787
+ column: from.column + position.column - to.column
788
+ };
789
+ }
790
+ return { line: position.line - (to.line - from.line), column: position.column };
791
+ }
718
792
  /**
719
793
  * Check if undo is available
720
794
  */
@@ -738,11 +812,56 @@ export class EditorState {
738
812
  return true;
739
813
  if (!a || !b)
740
814
  return a === b;
741
- return (a.inBlockComment === b.inBlockComment &&
742
- a.inTemplateLiteral === b.inTemplateLiteral &&
743
- a.inMultilineString === b.inMultilineString &&
744
- a.stringDelimiter === b.stringDelimiter &&
745
- a.templateDepth === b.templateDepth);
815
+ const aKeys = Object.keys(a)
816
+ .filter((key) => a[key] !== undefined)
817
+ .sort();
818
+ const bKeys = Object.keys(b)
819
+ .filter((key) => b[key] !== undefined)
820
+ .sort();
821
+ if (aKeys.length !== bKeys.length)
822
+ return false;
823
+ for (let i = 0; i < aKeys.length; i++) {
824
+ const key = aKeys[i];
825
+ if (key !== bKeys[i])
826
+ return false;
827
+ if (!this.tokenizerStateValuesEqual(a[key], b[key])) {
828
+ return false;
829
+ }
830
+ }
831
+ return true;
832
+ }
833
+ tokenizerStateValuesEqual(a, b) {
834
+ if (a === b)
835
+ return true;
836
+ if (typeof a !== typeof b)
837
+ return false;
838
+ if (a === null || b === null)
839
+ return a === b;
840
+ if (typeof a !== 'object' || typeof b !== 'object')
841
+ return false;
842
+ if (Array.isArray(a) || Array.isArray(b)) {
843
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
844
+ return false;
845
+ return a.every((value, index) => this.tokenizerStateValuesEqual(value, b[index]));
846
+ }
847
+ const aRecord = a;
848
+ const bRecord = b;
849
+ const aKeys = Object.keys(aRecord)
850
+ .filter((key) => aRecord[key] !== undefined)
851
+ .sort();
852
+ const bKeys = Object.keys(bRecord)
853
+ .filter((key) => bRecord[key] !== undefined)
854
+ .sort();
855
+ if (aKeys.length !== bKeys.length)
856
+ return false;
857
+ for (let i = 0; i < aKeys.length; i++) {
858
+ const key = aKeys[i];
859
+ if (key !== bKeys[i])
860
+ return false;
861
+ if (!this.tokenizerStateValuesEqual(aRecord[key], bRecord[key]))
862
+ return false;
863
+ }
864
+ return true;
746
865
  }
747
866
  /**
748
867
  * Re-tokenize a range of lines with early-exit optimization
@@ -17,6 +17,7 @@ export interface EditorFindDeps {
17
17
  scrollCursorIntoView: () => void;
18
18
  focusEditor: () => void;
19
19
  isReadonly: () => boolean;
20
+ lineToVisualRow?: (line: number) => number;
20
21
  }
21
22
  export interface MatchRect {
22
23
  top: number;
@@ -92,19 +92,20 @@ export function createEditorFind(deps) {
92
92
  return [];
93
93
  const { scrollTop, viewportHeight } = viewport;
94
94
  const { lineHeight, charWidth, gutterWidth, contentPadding } = measurements;
95
- const firstVisibleLine = Math.max(0, Math.floor(scrollTop / lineHeight) - 1);
96
- const lineCount = deps.getLineCount();
97
- const lastVisibleLine = Math.min(lineCount - 1, Math.ceil((scrollTop + viewportHeight) / lineHeight) + 1);
95
+ const lineToVisualRow = deps.lineToVisualRow ?? ((line) => line);
96
+ const firstVisibleRow = Math.max(0, Math.floor(scrollTop / lineHeight) - 1);
97
+ const lastVisibleRow = Math.max(0, Math.ceil((scrollTop + viewportHeight) / lineHeight) + 1);
98
98
  const rects = [];
99
99
  for (let i = 0; i < matches.length; i++) {
100
100
  const match = matches[i];
101
- if (match.line < firstVisibleLine || match.line > lastVisibleLine)
101
+ const visualRow = lineToVisualRow(match.line);
102
+ if (visualRow < firstVisibleRow || visualRow > lastVisibleRow)
102
103
  continue;
103
104
  const width = (match.endColumn - match.startColumn) * charWidth;
104
105
  if (width <= 0)
105
106
  continue;
106
107
  rects.push({
107
- top: match.line * lineHeight,
108
+ top: visualRow * lineHeight,
108
109
  left: gutterWidth + contentPadding + match.startColumn * charWidth,
109
110
  width,
110
111
  height: lineHeight,
@@ -30,6 +30,7 @@ export interface EditorInputDeps {
30
30
  lineHeight: number;
31
31
  gutterWidth: number;
32
32
  };
33
+ visualRowToLine?: (visualRow: number) => number;
33
34
  /**
34
35
  * Editor tab size (columns per tab stop). Optional for backwards
35
36
  * compatibility; when omitted, DEFAULT_TAB_SIZE is used. Needed so that
@@ -82,7 +82,10 @@ export function createEditorInput(deps) {
82
82
  const x = e.clientX - rect.left - gutterWidth - CONTENT_PADDING;
83
83
  const y = e.clientY - rect.top;
84
84
  const editorState = deps.getEditorState();
85
- const line = Math.max(0, Math.min(Math.floor((y + scrollTop) / lineHeight), editorState.lineCount - 1));
85
+ const visualRow = Math.max(0, Math.min(Math.floor((y + scrollTop) / lineHeight), editorState.lineCount - 1));
86
+ const line = deps.visualRowToLine
87
+ ? deps.visualRowToLine(visualRow)
88
+ : Math.max(0, Math.min(visualRow, editorState.lineCount - 1));
86
89
  const lineContent = editorState.getLine(line);
87
90
  const lineText = lineContent?.text ?? '';
88
91
  const tabSize = deps.getTabSize?.() ?? DEFAULT_TAB_SIZE;
@@ -2,6 +2,7 @@ import type { Selection } from './core';
2
2
  export interface ScrollConfig {
3
3
  getEditorContent: () => HTMLDivElement | null;
4
4
  getSelection: () => Selection;
5
+ lineToVisualRow?: (line: number) => number;
5
6
  getMeasurements: () => {
6
7
  lineHeight: number;
7
8
  charWidth: number;
@@ -8,7 +8,8 @@ export function createEditorScroll(config) {
8
8
  return;
9
9
  const selection = config.getSelection();
10
10
  const { lineHeight, charWidth, gutterWidth } = config.getMeasurements();
11
- const cursorTop = selection.head.line * lineHeight;
11
+ const lineToVisualRow = config.lineToVisualRow ?? ((line) => line);
12
+ const cursorTop = lineToVisualRow(selection.head.line) * lineHeight;
12
13
  const cursorLeft = gutterWidth + CONTENT_PADDING + selection.head.column * charWidth;
13
14
  const viewportHeight = editorContent.clientHeight;
14
15
  const viewportWidth = editorContent.clientWidth;
@@ -3,13 +3,29 @@
3
3
  */
4
4
  export { default as Editor } from './Editor.svelte';
5
5
  export { default as CustomEditor } from './CustomEditor.svelte';
6
- export { default as CollaborativeEditor } from './CollaborativeEditor.svelte';
7
6
  export { default as EditorTabs } from './EditorTabs.svelte';
8
7
  export { default as EditorPane } from './EditorPane.svelte';
9
8
  export { default as FileIcon } from './FileIcon.svelte';
10
9
  export { default as FileExplorer } from './FileExplorer.svelte';
11
- export * from './core';
10
+ export * from './core/state';
11
+ export * from './core/navigation';
12
+ export * from './core/keybindings';
13
+ export * from './core/search';
14
+ export * from './core/folding';
15
+ export * from './core/multi-cursor';
16
+ export * from './core/complexity-analyzer';
17
+ export * from './core/ai-awareness';
18
+ export * from './core/semantic-analyzer';
19
+ export * from './core/commands';
20
+ export * from './core/bracket-healer';
21
+ export * from './core/git-blame';
22
+ export * from './core/snippet-manager';
23
+ export * from './core/quick-actions';
24
+ export * from './core/diagnostics';
25
+ export * from './core/breakpoints';
26
+ export type { Position } from './core/state';
27
+ export type { Diagnostic, Range } from './core/quick-actions';
12
28
  export * from './theme';
13
29
  export { getLanguageExtension, getLanguageConfig, getLanguageFromExtension, getLanguageFromFilename, getLanguageFromMimeType, getAllLanguageConfigs, resolveLanguage, type LanguageConfig } from './languages';
14
30
  export { getSupportedLanguages, isLanguageSupported } from './languages';
15
- export { getTokenizer, tokenize, getTokenClass, tokensToHTML, PlaintextTokenizer, SimpleTokenizer, GrammarTokenizer, createToken, type Token, type TokenizedLine, type TokenizerState, type TokenType, type LanguageTokenizer, type TokenRule, type LanguageGrammar } from './tokenizer';
31
+ export { getTokenizer, tokenize, getTokenClass, tokensToHTML, PlaintextTokenizer, createToken, type Token, type TokenizedLine, type TokenizerState, type TokenType, type LanguageTokenizer } from './tokenizer';
@@ -4,13 +4,27 @@
4
4
  // Components
5
5
  export { default as Editor } from './Editor.svelte';
6
6
  export { default as CustomEditor } from './CustomEditor.svelte';
7
- export { default as CollaborativeEditor } from './CollaborativeEditor.svelte';
8
7
  export { default as EditorTabs } from './EditorTabs.svelte';
9
8
  export { default as EditorPane } from './EditorPane.svelte';
10
9
  export { default as FileIcon } from './FileIcon.svelte';
11
10
  export { default as FileExplorer } from './FileExplorer.svelte';
12
- // Core utilities
13
- export * from './core';
11
+ // Core utilities (explicitly excluding CRDT binding; use @nocturnium/svelte-ide/crdt)
12
+ export * from './core/state';
13
+ export * from './core/navigation';
14
+ export * from './core/keybindings';
15
+ export * from './core/search';
16
+ export * from './core/folding';
17
+ export * from './core/multi-cursor';
18
+ export * from './core/complexity-analyzer';
19
+ export * from './core/ai-awareness';
20
+ export * from './core/semantic-analyzer';
21
+ export * from './core/commands';
22
+ export * from './core/bracket-healer';
23
+ export * from './core/git-blame';
24
+ export * from './core/snippet-manager';
25
+ export * from './core/quick-actions';
26
+ export * from './core/diagnostics';
27
+ export * from './core/breakpoints';
14
28
  // Theme
15
29
  export * from './theme';
16
30
  // Languages (explicit exports to avoid conflicts with tokenizer)
@@ -18,4 +32,4 @@ export { getLanguageExtension, getLanguageConfig, getLanguageFromExtension, getL
18
32
  // Re-export from languages (these exist in both but we prefer languages versions)
19
33
  export { getSupportedLanguages, isLanguageSupported } from './languages';
20
34
  // Tokenizer (explicit exports to avoid conflicts)
21
- export { getTokenizer, tokenize, getTokenClass, tokensToHTML, PlaintextTokenizer, SimpleTokenizer, GrammarTokenizer, createToken } from './tokenizer';
35
+ export { getTokenizer, tokenize, getTokenClass, tokensToHTML, PlaintextTokenizer, createToken } from './tokenizer';
@@ -1,35 +1,11 @@
1
1
  /**
2
2
  * Base tokenizer with common functionality
3
3
  */
4
- import type { Token, TokenizedLine, TokenizerState, TokenType, LanguageGrammar, LanguageTokenizer } from './types';
4
+ import type { Token, TokenizedLine, TokenizerState, TokenType, LanguageTokenizer } from './types';
5
5
  /**
6
6
  * Create a token
7
7
  */
8
8
  export declare function createToken(type: TokenType, text: string, start: number): Token;
9
- /**
10
- * Base tokenizer class using grammar rules
11
- */
12
- export declare class GrammarTokenizer implements LanguageTokenizer {
13
- language: string;
14
- private grammar;
15
- constructor(grammar: LanguageGrammar);
16
- getInitialState(): TokenizerState;
17
- tokenizeLine(line: string, lineNumber: number, prevState?: TokenizerState): TokenizedLine;
18
- private updateState;
19
- }
20
- /**
21
- * Simple regex-based tokenizer for basic languages
22
- */
23
- export declare class SimpleTokenizer implements LanguageTokenizer {
24
- language: string;
25
- private patterns;
26
- constructor(language: string, patterns: Array<{
27
- type: TokenType;
28
- regex: RegExp;
29
- }>);
30
- getInitialState(): TokenizerState;
31
- tokenizeLine(line: string, lineNumber: number, prevState?: TokenizerState): TokenizedLine;
32
- }
33
9
  /**
34
10
  * Plaintext tokenizer - no highlighting
35
11
  */
@@ -12,178 +12,6 @@ export function createToken(type, text, start) {
12
12
  end: start + text.length
13
13
  };
14
14
  }
15
- /**
16
- * Base tokenizer class using grammar rules
17
- */
18
- export class GrammarTokenizer {
19
- language;
20
- grammar;
21
- constructor(grammar) {
22
- this.language = grammar.language;
23
- this.grammar = grammar;
24
- }
25
- getInitialState() {
26
- return {};
27
- }
28
- tokenizeLine(line, lineNumber, prevState) {
29
- const tokens = [];
30
- let pos = 0;
31
- const state = { ...prevState };
32
- let currentRuleSet = 'root';
33
- // Determine starting rule set based on state
34
- if (state.inBlockComment) {
35
- currentRuleSet = 'blockComment';
36
- }
37
- else if (state.inTemplateLiteral) {
38
- currentRuleSet = 'templateLiteral';
39
- }
40
- else if (state.inMultilineString) {
41
- currentRuleSet = 'multilineString';
42
- }
43
- while (pos < line.length) {
44
- const remaining = line.slice(pos);
45
- let matched = false;
46
- // Try to match rules in current rule set
47
- const rules = this.grammar.rules[currentRuleSet] || this.grammar.rules['root'] || [];
48
- for (const rule of rules) {
49
- const match = remaining.match(rule.pattern);
50
- if (match && match.index === 0) {
51
- const text = match[0];
52
- tokens.push(createToken(rule.type, text, pos));
53
- pos += text.length;
54
- matched = true;
55
- // Handle state transitions
56
- if (rule.nextState) {
57
- currentRuleSet = rule.nextState;
58
- this.updateState(state, rule.nextState, true);
59
- }
60
- if (rule.popState) {
61
- currentRuleSet = 'root';
62
- this.updateState(state, currentRuleSet, false);
63
- }
64
- break;
65
- }
66
- }
67
- // No match - consume single character as text
68
- if (!matched) {
69
- const char = line[pos];
70
- // Try to merge with previous text token
71
- const lastToken = tokens[tokens.length - 1];
72
- if (lastToken && lastToken.type === 'text' && lastToken.end === pos) {
73
- lastToken.text += char;
74
- lastToken.end += 1;
75
- }
76
- else {
77
- tokens.push(createToken('text', char, pos));
78
- }
79
- pos += 1;
80
- }
81
- }
82
- // Handle empty lines
83
- if (tokens.length === 0) {
84
- tokens.push(createToken('text', '', 0));
85
- }
86
- return {
87
- lineNumber,
88
- tokens,
89
- text: line,
90
- state: { ...state }
91
- };
92
- }
93
- updateState(state, ruleSet, entering) {
94
- switch (ruleSet) {
95
- case 'blockComment':
96
- state.inBlockComment = entering;
97
- break;
98
- case 'templateLiteral':
99
- state.inTemplateLiteral = entering;
100
- break;
101
- case 'multilineString':
102
- state.inMultilineString = entering;
103
- break;
104
- }
105
- }
106
- }
107
- /**
108
- * Simple regex-based tokenizer for basic languages
109
- */
110
- export class SimpleTokenizer {
111
- language;
112
- patterns;
113
- constructor(language, patterns) {
114
- this.language = language;
115
- this.patterns = patterns;
116
- }
117
- getInitialState() {
118
- return {};
119
- }
120
- tokenizeLine(line, lineNumber, prevState) {
121
- const tokens = [];
122
- let pos = 0;
123
- const state = { ...prevState };
124
- // Handle block comment continuation
125
- if (state.inBlockComment) {
126
- const endMatch = line.indexOf('*/');
127
- if (endMatch !== -1) {
128
- tokens.push(createToken('comment.block', line.slice(0, endMatch + 2), 0));
129
- pos = endMatch + 2;
130
- state.inBlockComment = false;
131
- }
132
- else {
133
- tokens.push(createToken('comment.block', line, 0));
134
- return { lineNumber, tokens, text: line, state };
135
- }
136
- }
137
- while (pos < line.length) {
138
- const remaining = line.slice(pos);
139
- let matched = false;
140
- // Check for block comment start
141
- if (remaining.startsWith('/*')) {
142
- const endMatch = remaining.indexOf('*/', 2);
143
- if (endMatch !== -1) {
144
- const text = remaining.slice(0, endMatch + 2);
145
- tokens.push(createToken('comment.block', text, pos));
146
- pos += text.length;
147
- }
148
- else {
149
- tokens.push(createToken('comment.block', remaining, pos));
150
- state.inBlockComment = true;
151
- pos = line.length;
152
- }
153
- matched = true;
154
- continue;
155
- }
156
- // Try each pattern
157
- for (const { type, regex } of this.patterns) {
158
- const match = remaining.match(regex);
159
- if (match && match.index === 0) {
160
- const text = match[0];
161
- tokens.push(createToken(type, text, pos));
162
- pos += text.length;
163
- matched = true;
164
- break;
165
- }
166
- }
167
- // No match - consume character
168
- if (!matched) {
169
- const char = remaining[0];
170
- const lastToken = tokens[tokens.length - 1];
171
- if (lastToken && lastToken.type === 'text' && lastToken.end === pos) {
172
- lastToken.text += char;
173
- lastToken.end += 1;
174
- }
175
- else {
176
- tokens.push(createToken('text', char, pos));
177
- }
178
- pos += 1;
179
- }
180
- }
181
- if (tokens.length === 0) {
182
- tokens.push(createToken('text', '', 0));
183
- }
184
- return { lineNumber, tokens, text: line, state };
185
- }
186
- }
187
15
  /**
188
16
  * Plaintext tokenizer - no highlighting
189
17
  */
@@ -15,6 +15,10 @@ export { GoTokenizer, createGoTokenizer } from './languages/go';
15
15
  export { MarkdownTokenizer, createMarkdownTokenizer } from './languages/markdown';
16
16
  export { SvelteTokenizer, createSvelteTokenizer } from './languages/svelte';
17
17
  import type { LanguageTokenizer, Token, TokenizedLine, TokenizerState, TokenType } from './types';
18
+ /**
19
+ * Tokenizer factory functions by language
20
+ */
21
+ export declare const tokenizerFactories: Record<string, () => LanguageTokenizer>;
18
22
  /**
19
23
  * Get the canonical language name from an alias or extension
20
24
  */
@@ -78,7 +78,7 @@ const languageAliases = {
78
78
  /**
79
79
  * Tokenizer factory functions by language
80
80
  */
81
- const tokenizerFactories = {
81
+ export const tokenizerFactories = {
82
82
  javascript: createJavaScriptTokenizer,
83
83
  typescript: createTypeScriptTokenizer,
84
84
  jsx: createJSXTokenizer,
@@ -3,14 +3,15 @@
3
3
  */
4
4
  import type { TokenizedLine, TokenizerState } from '../types';
5
5
  interface HTMLTokenizerState extends TokenizerState {
6
- inTag?: boolean;
7
6
  inScript?: boolean;
8
7
  inStyle?: boolean;
9
- tagName?: string;
8
+ innerState?: TokenizerState;
10
9
  }
11
10
  export declare class HTMLTokenizer {
12
11
  language: string;
13
12
  private isXML;
13
+ private jsTokenizer;
14
+ private cssTokenizer;
14
15
  constructor(options?: {
15
16
  xml?: boolean;
16
17
  });