@synclineapi/editor 4.0.3 → 4.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.
@@ -1,3 +1,8 @@
1
+ declare interface BracketMatch {
2
+ open: CursorPosition;
3
+ close: CursorPosition;
4
+ }
5
+
1
6
  export declare const BUILT_IN_THEMES: ThemeDefinition[];
2
7
 
3
8
  /**
@@ -124,6 +129,34 @@ export declare interface CursorPosition {
124
129
  col: number;
125
130
  }
126
131
 
132
+ /**
133
+ * A single editing operation that can be applied forward (redo) or inverted (undo).
134
+ *
135
+ * - `insert` — `lines` were inserted starting at `row`. Undo removes them; redo re-inserts.
136
+ * - `delete` — `count` lines starting at `row` were removed; `removed` holds their content.
137
+ * Undo re-inserts; redo removes again.
138
+ * - `replace` — lines `[row, row + oldLines.length)` were replaced by `newLines`.
139
+ * Undo swaps `newLines` back to `oldLines`; redo swaps forward again.
140
+ *
141
+ * Only the affected lines are stored — never a full document copy — making this
142
+ * safe for documents with 1 M+ lines.
143
+ */
144
+ declare type EditOp = {
145
+ kind: 'insert';
146
+ row: number;
147
+ lines: string[];
148
+ } | {
149
+ kind: 'delete';
150
+ row: number;
151
+ count: number;
152
+ removed: string[];
153
+ } | {
154
+ kind: 'replace';
155
+ row: number;
156
+ oldLines: string[];
157
+ newLines: string[];
158
+ };
159
+
127
160
  /**
128
161
  * Public API surface returned by `SynclineEditor.create()`.
129
162
  * All methods are safe to call at any time after construction.
@@ -213,6 +246,132 @@ export declare interface EditorAPI {
213
246
  destroy(): void;
214
247
  }
215
248
 
249
+ declare abstract class EditorAutocomplete extends EditorFind {
250
+ protected _acVisible(): boolean;
251
+ protected _acHide(): void;
252
+ protected _acTrigger(): void;
253
+ protected _acTriggerNow(): void;
254
+ protected _renderAcPopup(): void;
255
+ protected _posAcPopup(): void;
256
+ protected _acAccept(): void;
257
+ protected _allCompletions(): CompletionItem[];
258
+ protected _emmetCheck(): void;
259
+ protected _emmetAccept(): boolean;
260
+ protected _snippetCheck(): void;
261
+ protected _snippetAccept(): boolean;
262
+ protected _expandBodyAt(body: string, triggerStart: number): void;
263
+ protected _ctrlD(): void;
264
+ }
265
+
266
+ declare abstract class EditorBase {
267
+ protected readonly _host: HTMLElement;
268
+ protected readonly _shadow: ShadowRoot;
269
+ protected readonly _editorEl: HTMLElement;
270
+ protected readonly _spacerEl: HTMLElement;
271
+ protected readonly _vpEl: HTMLElement;
272
+ protected readonly _inputEl: HTMLTextAreaElement;
273
+ protected readonly _minimapWrap: HTMLElement;
274
+ protected readonly _mmCanvas: HTMLCanvasElement;
275
+ protected readonly _mmSlider: HTMLElement;
276
+ protected readonly _statusBar: HTMLElement;
277
+ protected readonly _findBar: HTMLElement;
278
+ protected readonly _findInput: HTMLInputElement;
279
+ protected readonly _replaceInput: HTMLInputElement;
280
+ protected readonly _findCount: HTMLElement;
281
+ protected readonly _replaceRow: HTMLElement;
282
+ protected readonly _acPopup: HTMLElement;
283
+ protected readonly _emmetTip: HTMLElement;
284
+ protected readonly _snippetTip: HTMLElement;
285
+ protected readonly _hoverTip: HTMLElement;
286
+ protected readonly _themeOverlay: HTMLElement;
287
+ protected readonly _themePanel: HTMLElement;
288
+ protected readonly _placeholderEl: HTMLElement;
289
+ protected readonly _goToLineBar: HTMLElement;
290
+ protected readonly _goToLineInput: HTMLInputElement;
291
+ protected readonly _goToLineHint: HTMLElement;
292
+ protected _config: Required<EditorConfig>;
293
+ protected _tab: EditorTab;
294
+ protected _wm: WrapMap;
295
+ protected _foldedLines: Map<number, number>;
296
+ protected _tokenCache: TokenCache;
297
+ protected _themeManager: ThemeManager;
298
+ protected _findMatches: FindMatch[];
299
+ protected _findIdx: number;
300
+ protected _findCaseSensitive: boolean;
301
+ protected _findRegex: boolean;
302
+ protected _wordHighlights: FindMatch[];
303
+ protected _bracketMatch: BracketMatch | null;
304
+ protected _acItems: CompletionItem[];
305
+ protected _acSel: number;
306
+ protected _acPrefix: string;
307
+ protected _acStartCol: number;
308
+ protected _emmetAcStartCol: number;
309
+ protected _mc: MultiCursorState;
310
+ protected _extraCursors: ExtraCursor[];
311
+ protected _isDragging: boolean;
312
+ protected _dragAnchor: CursorPosition | null;
313
+ protected _hoverTimer: ReturnType<typeof setTimeout> | null;
314
+ protected _hoverPinned: boolean;
315
+ protected _mmDragMode: 'none' | 'slider' | 'jump';
316
+ protected _mmDragStartY: number;
317
+ protected _mmDragStartScroll: number;
318
+ protected _mmSliderOffset: number;
319
+ protected _mmColors: MinimapColors;
320
+ protected _lastInputTime: number;
321
+ protected _linewiseCopy: boolean;
322
+ protected _dynamicStyleEl: HTMLStyleElement;
323
+ protected _emmetExpanded: {
324
+ abbr: string;
325
+ result: string;
326
+ start: number;
327
+ } | null;
328
+ protected _snippetExpanded: {
329
+ snippet: CompletionItem;
330
+ start: number;
331
+ } | null;
332
+ protected _snippetSession: {
333
+ stops: Array<{
334
+ row: number;
335
+ col: number;
336
+ }>;
337
+ idx: number;
338
+ } | null;
339
+ protected _acDebounceTimer: ReturnType<typeof setTimeout> | null;
340
+ protected _rafId: number | null;
341
+ /** Number of visual rows currently rendered in the DOM viewport. */
342
+ protected _renderedRowCount: number;
343
+ /** Last scrollTop seen in _render(); used to gate wrap-map rebuilds. */
344
+ protected _lastRenderScroll: number;
345
+ protected _logicalScroll(domScrollTop: number): number;
346
+ protected _domScroll(logicalPx: number): number;
347
+ protected readonly _onWinMouseUp: () => void;
348
+ protected readonly _onWinResize: () => void;
349
+ protected readonly _onEditorScroll: () => void;
350
+ protected readonly _onWinMmMouseMove: (e: MouseEvent) => void;
351
+ protected readonly _onWinMmMouseUp: () => void;
352
+ protected _ro: ResizeObserver | null;
353
+ protected abstract _render(): void;
354
+ protected abstract _scheduleRender(): void;
355
+ protected abstract _scrollIntoView(): void;
356
+ protected abstract _applyDynamicStyles(): void;
357
+ protected abstract _rebuildWrapMap(): void;
358
+ protected abstract _bindEditorEvents(): void;
359
+ protected abstract _bindMinimapEvents(): void;
360
+ protected abstract _bindFindEvents(): void;
361
+ protected abstract _toggleWrap(): void;
362
+ protected abstract _openThemePicker(): void;
363
+ protected abstract _hideHover(): void;
364
+ protected abstract _updateMinimap(): void;
365
+ constructor(container: HTMLElement, config?: EditorConfig);
366
+ private _buildFindBar;
367
+ private _buildGoToLineBar;
368
+ protected _openGoToLine(): void;
369
+ protected _closeGoToLine(): void;
370
+ protected _focusInput(): void;
371
+ private _buildStatusBar;
372
+ protected _destroyBase(): void;
373
+ }
374
+
216
375
  /** ─── Editor Configuration ──────────────────────────────── */
217
376
  /**
218
377
  * Full, deeply-customisable editor configuration.
@@ -466,6 +625,42 @@ export declare interface EditorConfig {
466
625
  * Default: `false`.
467
626
  */
468
627
  replaceBuiltins?: boolean;
628
+ /**
629
+ * Custom syntax tokeniser override.
630
+ *
631
+ * When provided, this function is called **instead of** the built-in
632
+ * regex-free tokeniser for **every line** before it is rendered.
633
+ * Return an array of `TokenSegment` objects that cover the half-open
634
+ * character ranges you want coloured. Ranges you don't cover are
635
+ * rendered as plain text.
636
+ *
637
+ * The result is still cached by the internal LRU token cache (keyed on
638
+ * the raw line text + language), so the function is only called once per
639
+ * unique `(text, language)` pair — not on every frame.
640
+ *
641
+ * Pass `null` or `undefined` to fall through to the built-in tokeniser.
642
+ *
643
+ * **Use this for custom languages, DSLs, or Markdown** where you need
644
+ * full control over which character ranges receive which token class.
645
+ *
646
+ * @example
647
+ * ```ts
648
+ * // Minimal Markdown tokeniser
649
+ * provideTokens: (line, _lang) => {
650
+ * const segs: TokenSegment[] = [];
651
+ * // ATX headings → keyword colour
652
+ * if (/^#{1,6}\s/.test(line))
653
+ * segs.push({ cls: 'kw', start: 0, end: line.length });
654
+ * // **bold** → string colour
655
+ * for (const m of line.matchAll(/\*\*(.+?)\*\*\/g))
656
+ * segs.push({ cls: 'str', start: m.index!, end: m.index! + m[0].length });
657
+ * return segs;
658
+ * }
659
+ * ```
660
+ *
661
+ * Default: `undefined` (use built-in tokeniser).
662
+ */
663
+ provideTokens?: (line: string, language: string) => TokenSegment[];
469
664
  /**
470
665
  * Dynamic completion provider called each time the autocomplete popup is
471
666
  * about to open. Receives a `CompletionContext` describing the current
@@ -491,14 +686,14 @@ export declare interface EditorConfig {
491
686
  * When the user types the opening character, the closing character is
492
687
  * inserted automatically and the cursor placed between them.
493
688
  *
494
- * Pass an empty object `{}` to disable all auto-closing.
689
+ * Pass an empty object `{ } ` to disable all auto-closing.
495
690
  *
496
691
  * @example
497
692
  * ```ts
498
693
  * // Only auto-close parentheses and curly braces
499
694
  * autoClosePairs: { '(': ')', '{': '}' }
500
695
  * ```
501
- * Default: `{ '(': ')', '[': ']', '{': '}', '"': '"', "'": "'", '`': '`' }`.
696
+ * Default: `{ '(': ')', '[': ']', '{': '}', '"': '"', "'": "'", '`': '`' } `.
502
697
  */
503
698
  autoClosePairs?: Record<string, string>;
504
699
  /**
@@ -598,15 +793,15 @@ export declare interface EditorConfig {
598
793
  * // Dracula-inspired keywords + string colours on any base theme
599
794
  * editor.updateConfig({
600
795
  * tokenColors: {
601
- * keyword: '#ff79c6',
602
- * string: '#f1fa8c',
796
+ * keyword: '#ff79c6',
797
+ * string: '#f1fa8c',
603
798
  * function: '#50fa7b',
604
- * type: '#8be9fd',
605
- * comment: '#6272a4',
799
+ * type: '#8be9fd',
800
+ * comment: '#6272a4',
606
801
  * }
607
802
  * });
608
803
  * ```
609
- * Default: `{}` (no overrides — all colours come from the active theme).
804
+ * Default: `{ } ` (no overrides — all colours come from the active theme).
610
805
  */
611
806
  tokenColors?: TokenColors;
612
807
  /**
@@ -674,6 +869,98 @@ export declare interface EditorConfig {
674
869
  goToLine?: boolean;
675
870
  }
676
871
 
872
+ declare abstract class EditorEditing extends EditorRenderer {
873
+ protected _insertStr(text: string, groupable: boolean): void;
874
+ protected _doBackspace(): void;
875
+ protected _doEnter(): void;
876
+ protected _doDelete(): void;
877
+ protected _selectAll(): void;
878
+ protected _doCopy(): void;
879
+ protected _doCut(): void;
880
+ /**
881
+ * Cmd+Enter: insert a blank (auto-indented) line below the current line and
882
+ * move the cursor there — regardless of where the cursor is on the line.
883
+ * Matches VS Code's "editor.action.insertLineAfter".
884
+ */
885
+ protected _doInsertLineBelow(): void;
886
+ /**
887
+ * Cmd+Shift+Enter: insert a blank (auto-indented) line above the current line
888
+ * and move the cursor there.
889
+ * Matches VS Code's "editor.action.insertLineBefore".
890
+ */
891
+ protected _doInsertLineAbove(): void;
892
+ protected _toggleComment(): void;
893
+ protected _duplicateLine(): void;
894
+ protected _deleteLine(): void;
895
+ protected _indentSel(): void;
896
+ protected _unindentSel(): void;
897
+ protected _toggleWrap(): void;
898
+ protected _getSelRows(): number[];
899
+ protected _duplicateLines(down: boolean): void;
900
+ protected _moveLines(up: boolean): void;
901
+ /** Skip forward past one word boundary (Option/Ctrl+Right, Option/Ctrl+Delete). */
902
+ protected _wordSkipRight(line: string, col: number): number;
903
+ /** Skip backward past one word boundary (Option/Ctrl+Left, Option/Ctrl+Backspace). */
904
+ protected _wordSkipLeft(line: string, col: number): number;
905
+ /** Cmd+Backspace: delete from cursor back to column 0 on the same line. */
906
+ protected _deleteToLineStart(): void;
907
+ /** Option/Ctrl+Backspace: delete one word to the left of cursor. */
908
+ protected _deleteWordLeft(): void;
909
+ /** Cmd+Delete: delete from cursor forward to end of line. */
910
+ protected _deleteToLineEnd(): void;
911
+ /** Option/Ctrl+Delete: delete one word to the right of cursor. */
912
+ protected _deleteWordRight(): void;
913
+ protected _doUndo(): void;
914
+ protected _doRedo(): void;
915
+ }
916
+
917
+ declare abstract class EditorEvents extends EditorHoverAndTheme {
918
+ protected _posFromMouse(e: MouseEvent): CursorPosition;
919
+ protected _bindEditorEvents(): void;
920
+ protected _onKeyDown(e: KeyboardEvent): void;
921
+ protected _handleArrowKeys(e: KeyboardEvent, _ctrl: boolean, shift: boolean): void;
922
+ protected _isWordChar(ch: string): boolean;
923
+ private _wordStart;
924
+ private _wordEnd;
925
+ protected _onInput(e: InputEvent): void;
926
+ protected _bindMinimapEvents(): void;
927
+ }
928
+
929
+ declare abstract class EditorFind extends EditorEditing {
930
+ protected _openFind(withReplace: boolean): void;
931
+ protected _closeFind(): void;
932
+ protected _runFind(q: string): void;
933
+ protected _navFind(dir: 1 | -1): void;
934
+ protected _doReplaceOne(): void;
935
+ protected _doReplaceAll(): void;
936
+ protected _bindFindEvents(): void;
937
+ }
938
+
939
+ declare abstract class EditorHoverAndTheme extends EditorAutocomplete {
940
+ protected _scheduleHover(e: MouseEvent): void;
941
+ protected _doHover(clientX: number, clientY: number): void;
942
+ protected _showHoverTip(doc: HoverDoc, clientX: number, clientY: number): void;
943
+ protected _hideHover(): void;
944
+ protected _openThemePicker(): void;
945
+ protected _applyTheme(theme: string | ThemeDefinition): void;
946
+ }
947
+
948
+ declare abstract class EditorRenderer extends EditorStyles {
949
+ protected _scheduleRender(): void;
950
+ protected _render(): void;
951
+ protected readonly _onFoldBtnClick: (e: MouseEvent) => void;
952
+ protected _updateStatusBar(): void;
953
+ protected _updateMinimap(): void;
954
+ protected _rebuildWrapMap(): void;
955
+ protected _scrollIntoView(): void;
956
+ }
957
+
958
+ declare abstract class EditorStyles extends EditorBase {
959
+ protected _applyDynamicStyles(): void;
960
+ protected _applyTokenOverrides(): void;
961
+ protected _refreshMinimapColors(): void;
962
+ }
963
+
677
964
  /**
678
965
  * The full state of an open editor buffer (tab).
679
966
  * Contains the document text, cursor, selection, scroll offset, and history stacks.
@@ -725,14 +1012,31 @@ export declare interface FindMatch {
725
1012
 
726
1013
  /**
727
1014
  * A point-in-time snapshot of document state stored in the undo/redo stacks.
1015
+ * Stores a delta `EditOp` rather than a full document copy.
728
1016
  */
729
1017
  export declare interface HistorySnapshot {
730
- /** Document lines at the time of the snapshot. */
731
- doc: string[];
732
- /** Cursor position at the time of the snapshot. */
733
- cur: CursorPosition;
734
- /** Selection at the time of the snapshot, or `null` if none. */
735
- sel: Selection_2 | null;
1018
+ /** The editing operation that was performed to reach the post-edit state. */
1019
+ op: EditOp;
1020
+ /** Cursor position before the operation (restored on undo). */
1021
+ beforeCur: CursorPosition;
1022
+ /** Selection before the operation (restored on undo). */
1023
+ beforeSel: Selection_2 | null;
1024
+ /** Cursor position after the operation (restored on redo). */
1025
+ afterCur: CursorPosition;
1026
+ /** Selection after the operation (restored on redo). */
1027
+ afterSel: Selection_2 | null;
1028
+ /**
1029
+ * Extra cursor positions immediately BEFORE the operation.
1030
+ * Restored when the operation is undone, so multi-cursor state
1031
+ * (including per-cursor selections from Ctrl+D) is preserved
1032
+ * across undo — identical to VS Code behaviour.
1033
+ */
1034
+ beforeExtra: ExtraCursor[];
1035
+ /**
1036
+ * Extra cursor positions immediately AFTER the operation.
1037
+ * Restored when the operation is redone.
1038
+ */
1039
+ afterExtra: ExtraCursor[];
736
1040
  }
737
1041
 
738
1042
  /**
@@ -814,6 +1118,13 @@ export declare interface MinimapColors {
814
1118
  [key: string]: string;
815
1119
  }
816
1120
 
1121
+ declare interface MultiCursorState {
1122
+ cursors: ExtraCursor[];
1123
+ searchWord: string;
1124
+ lastMatchRow: number;
1125
+ lastMatchCol: number;
1126
+ }
1127
+
817
1128
  /**
818
1129
  * A selection range defined by an anchor (where selection started)
819
1130
  * and a focus (where the caret currently sits). Either end can be
@@ -831,77 +1142,7 @@ declare interface Selection_2 {
831
1142
  }
832
1143
  export { Selection_2 as Selection }
833
1144
 
834
- export declare class SynclineEditor implements EditorAPI {
835
- private readonly _host;
836
- private readonly _shadow;
837
- private readonly _editorEl;
838
- private readonly _spacerEl;
839
- private readonly _vpEl;
840
- private readonly _inputEl;
841
- private readonly _minimapWrap;
842
- private readonly _mmCanvas;
843
- private readonly _mmSlider;
844
- private readonly _statusBar;
845
- private readonly _findBar;
846
- private readonly _findInput;
847
- private readonly _replaceInput;
848
- private readonly _findCount;
849
- private readonly _replaceRow;
850
- private readonly _acPopup;
851
- private readonly _emmetTip;
852
- private readonly _snippetTip;
853
- private readonly _hoverTip;
854
- private readonly _themeOverlay;
855
- private readonly _themePanel;
856
- private readonly _placeholderEl;
857
- private readonly _goToLineBar;
858
- private readonly _goToLineInput;
859
- private readonly _goToLineHint;
860
- private _config;
861
- private _tab;
862
- private _wm;
863
- private _foldedLines;
864
- private _tokenCache;
865
- private _themeManager;
866
- private _findMatches;
867
- private _findIdx;
868
- private _findCaseSensitive;
869
- private _findRegex;
870
- private _wordHighlights;
871
- private _bracketMatch;
872
- private _acItems;
873
- private _acSel;
874
- private _acPrefix;
875
- private _acStartCol;
876
- private _emmetAcStartCol;
877
- private _mc;
878
- private _extraCursors;
879
- private _isDragging;
880
- private _dragAnchor;
881
- private _hoverTimer;
882
- private _hoverPinned;
883
- private _mmDragMode;
884
- private _mmDragStartY;
885
- private _mmDragStartScroll;
886
- private _mmSliderOffset;
887
- private _mmColors;
888
- private _lastInputTime;
889
- private _dynamicStyleEl;
890
- private _emmetExpanded;
891
- private _snippetExpanded;
892
- /** Active snippet tab-stop session. Cleared on click, Escape, or structural edit. */
893
- private _snippetSession;
894
- /** Pending debounce timer ID for autocomplete re-computation. */
895
- private _acDebounceTimer;
896
- /** Pending requestAnimationFrame ID for coalesced renders. */
897
- private _rafId;
898
- private readonly _onWinMouseUp;
899
- private readonly _onWinResize;
900
- private readonly _onEditorScroll;
901
- private readonly _onWinMmMouseMove;
902
- private readonly _onWinMmMouseUp;
903
- private _ro;
904
- constructor(container: HTMLElement, config?: EditorConfig);
1145
+ export declare class SynclineEditor extends EditorEvents implements EditorAPI {
905
1146
  getValue(): string;
906
1147
  setValue(value: string): void;
907
1148
  getCursor(): CursorPosition;
@@ -918,101 +1159,6 @@ export declare class SynclineEditor implements EditorAPI {
918
1159
  redo(): void;
919
1160
  executeCommand(command: string): void;
920
1161
  destroy(): void;
921
- private _buildFindBar;
922
- private _buildGoToLineBar;
923
- private _openGoToLine;
924
- private _closeGoToLine;
925
- private _buildStatusBar;
926
- private _applyDynamicStyles;
927
- /**
928
- * Apply tokenColors overrides as inline styles on the host element.
929
- * Must be called AFTER the theme has applied its own inline styles so
930
- * our values win (last writer wins for element.style.setProperty).
931
- * When a token field is empty/absent, restore the active theme's value.
932
- */
933
- private _applyTokenOverrides;
934
- /**
935
- * Schedule a render on the next animation frame.
936
- * Multiple calls within a single frame are coalesced into one render —
937
- * ideal for scroll and resize handlers which can fire at 60+ Hz.
938
- */
939
- private _scheduleRender;
940
- private _render;
941
- private readonly _onFoldBtnClick;
942
- private _updateStatusBar;
943
- private _updateMinimap;
944
- private _rebuildWrapMap;
945
- private _scrollIntoView;
946
- private _focusInput;
947
- private _insertStr;
948
- private _doBackspace;
949
- private _doEnter;
950
- private _doDelete;
951
- private _selectAll;
952
- private _doCopy;
953
- private _doCut;
954
- private _toggleComment;
955
- private _duplicateLine;
956
- private _deleteLine;
957
- private _indentSel;
958
- private _unindentSel;
959
- private _toggleWrap;
960
- private _getSelRows;
961
- private _posFromMouse;
962
- private _openFind;
963
- private _closeFind;
964
- private _runFind;
965
- private _navFind;
966
- private _acVisible;
967
- private _acHide;
968
- /**
969
- * Schedule an autocomplete refresh after a brief debounce pause.
970
- * Prevents the suggestion engine running on every single keystroke —
971
- * only fires when the user pauses for `AC_DEBOUNCE_MS` milliseconds.
972
- * Call `_acTriggerNow()` directly when an immediate refresh is required
973
- * (e.g., on explicit Ctrl+Space or arrow-key navigation).
974
- */
975
- private _acTrigger;
976
- /** Run an autocomplete refresh immediately (no debounce). */
977
- private _acTriggerNow;
978
- private _renderAcPopup;
979
- private _posAcPopup;
980
- private _acAccept;
981
- private _emmetCheck;
982
- private _emmetAccept;
983
- /** Returns built-in snippets for the current language merged with user completions.
984
- * When `replaceBuiltins` is true the language built-ins are suppressed so only
985
- * the caller-supplied completions (e.g. MDX_SNIPPETS) are active. */
986
- private _allCompletions;
987
- private _snippetCheck;
988
- private _snippetAccept;
989
- /**
990
- * Expand a snippet/emmet body template at the current cursor position.
991
- * `triggerStart` is the column where the trigger word begins.
992
- * `cur.col` must be at the end of the trigger (emmet) or at `triggerStart`
993
- * when the prefix was already stripped (snippet accepted from popup).
994
- * Everything between `triggerStart` and `cur.col` is replaced by `body`.
995
- */
996
- private _expandBodyAt;
997
- private _openThemePicker;
998
- private _bindEditorEvents;
999
- private _onKeyDown;
1000
- private _handleArrowKeys;
1001
- private _isWordChar;
1002
- private _wordSkipRight;
1003
- private _wordSkipLeft;
1004
- private _duplicateLines;
1005
- private _moveLines;
1006
- private _scheduleHover;
1007
- private _doHover;
1008
- private _showHoverTip;
1009
- private _hideHover;
1010
- private _onInput;
1011
- private _ctrlD;
1012
- private _bindFindEvents;
1013
- private _doReplaceOne;
1014
- private _doReplaceAll;
1015
- private _bindMinimapEvents;
1016
1162
  }
1017
1163
 
1018
1164
  export declare const THEME_DRACULA: ThemeDefinition;
@@ -1049,6 +1195,22 @@ export declare interface ThemeDefinition {
1049
1195
  tokens: ThemeTokens;
1050
1196
  }
1051
1197
 
1198
+ declare class ThemeManager {
1199
+ private readonly _registry;
1200
+ private _activeId;
1201
+ private readonly _root;
1202
+ constructor(root: HTMLElement);
1203
+ /** Register a custom or override theme. */
1204
+ register(theme: ThemeDefinition): void;
1205
+ /** Apply a theme by id or by definition. */
1206
+ apply(idOrDef: string | ThemeDefinition): void;
1207
+ get activeId(): string;
1208
+ get activeTheme(): ThemeDefinition | undefined;
1209
+ get allIds(): string[];
1210
+ get all(): ThemeDefinition[];
1211
+ private _applyTokens;
1212
+ }
1213
+
1052
1214
  /** ─── Theme ─────────────────────────────────────────────── */
1053
1215
  /**
1054
1216
  * The complete set of CSS custom-property values that define a theme's
@@ -1166,6 +1328,25 @@ export declare interface ThemeTokens {
1166
1328
  tokDec: string;
1167
1329
  }
1168
1330
 
1331
+ /** Simple LRU token cache — keyed by `"docLine:segIdx"`. */
1332
+ declare class TokenCache {
1333
+ private readonly _cache;
1334
+ private readonly _maxSize;
1335
+ private _hits;
1336
+ private _misses;
1337
+ private _evictions;
1338
+ constructor(maxSize?: number);
1339
+ /** Live snapshot of cache performance. Useful for observability. */
1340
+ get metrics(): CacheMetrics;
1341
+ get(key: string, text: string): TokenSegment[] | null;
1342
+ set(key: string, text: string, segs: TokenSegment[]): void;
1343
+ invalidateLine(docLine: number, segCount: number): void;
1344
+ /** Clear all cached entries and reset performance counters. */
1345
+ clear(): void;
1346
+ /** Reset performance counters without clearing cached entries. */
1347
+ resetMetrics(): void;
1348
+ }
1349
+
1169
1350
  /**
1170
1351
  * Syntax token class identifiers produced by the tokeniser.
1171
1352
  * Each value maps to a CSS class that controls token colour.
@@ -1259,4 +1440,35 @@ export declare interface VisualRow {
1259
1440
  text: string;
1260
1441
  }
1261
1442
 
1443
+ declare interface WrapMap {
1444
+ /** wrapMap[docLine] = array of segment strings (empty array for outside-window lines). */
1445
+ segments: string[][];
1446
+ /**
1447
+ * Compact array of fully-computed visual rows covering only the window
1448
+ * [windowStart, windowEnd]. Index 0 here corresponds to absolute visual
1449
+ * row `windowVisStart`, so callers must offset: `visualRows[v - windowVisStart]`.
1450
+ *
1451
+ * For non-windowed mode `windowVisStart === 0` and the array covers the
1452
+ * whole document — the offset is a no-op (0).
1453
+ */
1454
+ visualRows: VisualRow[];
1455
+ /**
1456
+ * docToVisArr[docLine] = first visual row index for that doc line.
1457
+ * `null` signals the **identity mapping** (visRow === docLine), used by the
1458
+ * large-file fast path when word-wrap is off and there are no folds.
1459
+ * Callers should use `docToVisualPos()` rather than indexing this directly.
1460
+ */
1461
+ docToVisArr: number[] | null;
1462
+ /**
1463
+ * Total estimated visual rows in the document.
1464
+ * Use this for spacer-height calculations instead of `visualRows.length`.
1465
+ */
1466
+ totalVisualRows: number;
1467
+ /**
1468
+ * Absolute visual-row index of `visualRows[0]`.
1469
+ * Always 0 for non-windowed (full) builds.
1470
+ */
1471
+ windowVisStart: number;
1472
+ }
1473
+
1262
1474
  export { }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "4.0.3",
6
+ "version": "4.0.4",
7
7
  "description": "A zero-dependency, pixel-perfect, fully customisable browser-based code editor",
8
8
  "type": "module",
9
9
  "main": "./dist/syncline-editor.umd.js",