indusagi 0.12.16 → 0.12.18

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 (245) hide show
  1. package/README.md +0 -1
  2. package/dist/agent/agent.d.ts +2 -108
  3. package/dist/agent/agent.d.ts.map +1 -1
  4. package/dist/agent/agent.js +17 -210
  5. package/dist/agent/agent.js.map +1 -1
  6. package/dist/agent/event-bus.js +3 -1
  7. package/dist/agent/event-bus.js.map +1 -1
  8. package/dist/agent/index.d.ts +5 -11
  9. package/dist/agent/index.d.ts.map +1 -1
  10. package/dist/agent/index.js +8 -19
  11. package/dist/agent/index.js.map +1 -1
  12. package/dist/agent/messages.d.ts +48 -2
  13. package/dist/agent/messages.d.ts.map +1 -1
  14. package/dist/agent/messages.js.map +1 -1
  15. package/dist/agent/proxy.js.map +1 -1
  16. package/dist/agent/session-manager.d.ts +3 -2
  17. package/dist/agent/session-manager.d.ts.map +1 -1
  18. package/dist/agent/session-manager.js +6 -10
  19. package/dist/agent/session-manager.js.map +1 -1
  20. package/dist/agent/state-manager.js +0 -1
  21. package/dist/agent/state-manager.js.map +1 -1
  22. package/dist/agent/telemetry.js +3 -1
  23. package/dist/agent/telemetry.js.map +1 -1
  24. package/dist/agent/tools/bash.js +0 -1
  25. package/dist/agent/tools/bash.js.map +1 -1
  26. package/dist/agent/tools/index.d.ts +26 -26
  27. package/dist/agent/tools/registry.js +4 -2
  28. package/dist/agent/tools/registry.js.map +1 -1
  29. package/dist/agent/tools/todo-store.js +1 -2
  30. package/dist/agent/tools/todo-store.js.map +1 -1
  31. package/dist/agent/tools/utils/hook-runner.js +3 -1
  32. package/dist/agent/tools/utils/hook-runner.js.map +1 -1
  33. package/dist/agent/types.d.ts +1 -138
  34. package/dist/agent/types.d.ts.map +1 -1
  35. package/dist/agent/types.js.map +1 -1
  36. package/dist/ai/api-registry.js +3 -1
  37. package/dist/ai/api-registry.js.map +1 -1
  38. package/dist/ai/models.generated.d.ts +34 -0
  39. package/dist/ai/models.generated.d.ts.map +1 -1
  40. package/dist/ai/models.generated.js +34 -0
  41. package/dist/ai/models.generated.js.map +1 -1
  42. package/dist/ai/models.js +0 -1
  43. package/dist/ai/models.js.map +1 -1
  44. package/dist/ai/providers/amazon-bedrock.d.ts +0 -6
  45. package/dist/ai/providers/amazon-bedrock.d.ts.map +1 -1
  46. package/dist/ai/providers/amazon-bedrock.js +9 -47
  47. package/dist/ai/providers/amazon-bedrock.js.map +1 -1
  48. package/dist/ai/providers/anthropic.d.ts +0 -8
  49. package/dist/ai/providers/anthropic.d.ts.map +1 -1
  50. package/dist/ai/providers/anthropic.js +12 -28
  51. package/dist/ai/providers/anthropic.js.map +1 -1
  52. package/dist/ai/providers/azure-openai-responses.js +0 -1
  53. package/dist/ai/providers/azure-openai-responses.js.map +1 -1
  54. package/dist/ai/providers/google-shared.d.ts.map +1 -1
  55. package/dist/ai/providers/google-shared.js +22 -16
  56. package/dist/ai/providers/google-shared.js.map +1 -1
  57. package/dist/ai/providers/google-vertex.d.ts.map +1 -1
  58. package/dist/ai/providers/google-vertex.js +5 -28
  59. package/dist/ai/providers/google-vertex.js.map +1 -1
  60. package/dist/ai/providers/google.js +0 -1
  61. package/dist/ai/providers/google.js.map +1 -1
  62. package/dist/ai/providers/kimi.js +0 -1
  63. package/dist/ai/providers/kimi.js.map +1 -1
  64. package/dist/ai/providers/openai-codex-responses.js +0 -1
  65. package/dist/ai/providers/openai-codex-responses.js.map +1 -1
  66. package/dist/ai/providers/openai-completions.js +0 -2
  67. package/dist/ai/providers/openai-completions.js.map +1 -1
  68. package/dist/ai/providers/openai-responses.d.ts.map +1 -1
  69. package/dist/ai/providers/openai-responses.js +0 -30
  70. package/dist/ai/providers/openai-responses.js.map +1 -1
  71. package/dist/ai/providers/register-builtins.d.ts.map +1 -1
  72. package/dist/ai/providers/register-builtins.js +0 -6
  73. package/dist/ai/providers/register-builtins.js.map +1 -1
  74. package/dist/ai/providers/simple-options.js +0 -2
  75. package/dist/ai/providers/simple-options.js.map +1 -1
  76. package/dist/ai/providers/transform-messages.js +9 -3
  77. package/dist/ai/providers/transform-messages.js.map +1 -1
  78. package/dist/ai/stream.js +3 -1
  79. package/dist/ai/stream.js.map +1 -1
  80. package/dist/ai/types.d.ts +0 -34
  81. package/dist/ai/types.d.ts.map +1 -1
  82. package/dist/ai/types.js.map +1 -1
  83. package/dist/ai/utils/base-stream-handler.js +0 -1
  84. package/dist/ai/utils/base-stream-handler.js.map +1 -1
  85. package/dist/ai/utils/event-stream.js +4 -9
  86. package/dist/ai/utils/event-stream.js.map +1 -1
  87. package/dist/ai/utils/index.d.ts +0 -1
  88. package/dist/ai/utils/index.d.ts.map +1 -1
  89. package/dist/ai/utils/index.js +0 -1
  90. package/dist/ai/utils/index.js.map +1 -1
  91. package/dist/ai/utils/oauth/github-copilot.d.ts.map +1 -1
  92. package/dist/ai/utils/oauth/github-copilot.js +8 -19
  93. package/dist/ai/utils/oauth/github-copilot.js.map +1 -1
  94. package/dist/ai/utils/overflow.d.ts.map +1 -1
  95. package/dist/ai/utils/overflow.js +0 -4
  96. package/dist/ai/utils/overflow.js.map +1 -1
  97. package/dist/ai/utils/provider-adapter.js +0 -9
  98. package/dist/ai/utils/provider-adapter.js.map +1 -1
  99. package/dist/ai/utils/provider-client-builder.js +1 -1
  100. package/dist/ai/utils/provider-client-builder.js.map +1 -1
  101. package/dist/ai/utils/provider-errors.js +0 -2
  102. package/dist/ai/utils/provider-errors.js.map +1 -1
  103. package/dist/ai/utils/stream-event-helper.js +0 -3
  104. package/dist/ai/utils/stream-event-helper.js.map +1 -1
  105. package/dist/ai/utils/stream-handler-types.js +0 -5
  106. package/dist/ai/utils/stream-handler-types.js.map +1 -1
  107. package/dist/ai/utils/streaming-state-manager.js +2 -4
  108. package/dist/ai/utils/streaming-state-manager.js.map +1 -1
  109. package/dist/mcp/client-pool.js +2 -3
  110. package/dist/mcp/client-pool.js.map +1 -1
  111. package/dist/mcp/client.js +5 -18
  112. package/dist/mcp/client.js.map +1 -1
  113. package/dist/mcp/errors.js +0 -8
  114. package/dist/mcp/errors.js.map +1 -1
  115. package/dist/mcp/server.js +3 -8
  116. package/dist/mcp/server.js.map +1 -1
  117. package/dist/memory/embedder/openai.js +1 -7
  118. package/dist/memory/embedder/openai.js.map +1 -1
  119. package/dist/memory/memory.js +1 -6
  120. package/dist/memory/memory.js.map +1 -1
  121. package/dist/memory/processors/message-history.js +2 -4
  122. package/dist/memory/processors/message-history.js.map +1 -1
  123. package/dist/memory/processors/observational-memory/index.js +2 -7
  124. package/dist/memory/processors/observational-memory/index.js.map +1 -1
  125. package/dist/memory/processors/observational-memory/token-counter.js +3 -3
  126. package/dist/memory/processors/observational-memory/token-counter.js.map +1 -1
  127. package/dist/memory/processors/semantic-recall.js +2 -11
  128. package/dist/memory/processors/semantic-recall.js.map +1 -1
  129. package/dist/memory/processors/working-memory.js +2 -7
  130. package/dist/memory/processors/working-memory.js.map +1 -1
  131. package/dist/memory/storage/base.js +6 -4
  132. package/dist/memory/storage/base.js.map +1 -1
  133. package/dist/memory/storage/inmemory.js +2 -3
  134. package/dist/memory/storage/inmemory.js.map +1 -1
  135. package/dist/memory/vector/base.js +6 -4
  136. package/dist/memory/vector/base.js.map +1 -1
  137. package/dist/memory/vector/inmemory.js +5 -2
  138. package/dist/memory/vector/inmemory.js.map +1 -1
  139. package/dist/tui/autocomplete.js +1 -5
  140. package/dist/tui/autocomplete.js.map +1 -1
  141. package/dist/tui/components/box.js +1 -9
  142. package/dist/tui/components/box.js.map +1 -1
  143. package/dist/tui/components/cancellable-loader.js +4 -3
  144. package/dist/tui/components/cancellable-loader.js.map +1 -1
  145. package/dist/tui/components/editor.d.ts +3 -39
  146. package/dist/tui/components/editor.d.ts.map +1 -1
  147. package/dist/tui/components/editor.js +133 -162
  148. package/dist/tui/components/editor.js.map +1 -1
  149. package/dist/tui/components/image.js +0 -8
  150. package/dist/tui/components/image.js.map +1 -1
  151. package/dist/tui/components/input.js +11 -10
  152. package/dist/tui/components/input.js.map +1 -1
  153. package/dist/tui/components/loader.js +4 -7
  154. package/dist/tui/components/loader.js.map +1 -1
  155. package/dist/tui/components/markdown.js +0 -10
  156. package/dist/tui/components/markdown.js.map +1 -1
  157. package/dist/tui/components/select-list.js +4 -8
  158. package/dist/tui/components/select-list.js.map +1 -1
  159. package/dist/tui/components/settings-list.js +4 -12
  160. package/dist/tui/components/settings-list.js.map +1 -1
  161. package/dist/tui/components/spacer.js +0 -1
  162. package/dist/tui/components/spacer.js.map +1 -1
  163. package/dist/tui/components/text.js +0 -5
  164. package/dist/tui/components/text.js.map +1 -1
  165. package/dist/tui/components/truncated-text.js +0 -3
  166. package/dist/tui/components/truncated-text.js.map +1 -1
  167. package/dist/tui/fuzzy.js +1 -4
  168. package/dist/tui/fuzzy.js.map +1 -1
  169. package/dist/tui/keybindings.js +2 -3
  170. package/dist/tui/keybindings.js.map +1 -1
  171. package/dist/tui/keys.d.ts +1 -17
  172. package/dist/tui/keys.d.ts.map +1 -1
  173. package/dist/tui/keys.js +0 -56
  174. package/dist/tui/keys.js.map +1 -1
  175. package/dist/tui/stdin-buffer.js +5 -6
  176. package/dist/tui/stdin-buffer.js.map +1 -1
  177. package/dist/tui/terminal-image.js +3 -1
  178. package/dist/tui/terminal-image.js.map +1 -1
  179. package/dist/tui/terminal.d.ts +0 -13
  180. package/dist/tui/terminal.d.ts.map +1 -1
  181. package/dist/tui/terminal.js +7 -70
  182. package/dist/tui/terminal.js.map +1 -1
  183. package/dist/tui/tui.d.ts +0 -36
  184. package/dist/tui/tui.d.ts.map +1 -1
  185. package/dist/tui/tui.js +29 -101
  186. package/dist/tui/tui.js.map +1 -1
  187. package/dist/tui/utils.js +16 -12
  188. package/dist/tui/utils.js.map +1 -1
  189. package/package.json +1 -1
  190. package/dist/agent/agent-session.d.ts +0 -352
  191. package/dist/agent/agent-session.d.ts.map +0 -1
  192. package/dist/agent/agent-session.js +0 -870
  193. package/dist/agent/agent-session.js.map +0 -1
  194. package/dist/agent/agent-session.test.d.ts +0 -5
  195. package/dist/agent/agent-session.test.d.ts.map +0 -1
  196. package/dist/agent/agent-session.test.js +0 -129
  197. package/dist/agent/agent-session.test.js.map +0 -1
  198. package/dist/agent/bash-executor.d.ts +0 -33
  199. package/dist/agent/bash-executor.d.ts.map +0 -1
  200. package/dist/agent/bash-executor.js +0 -197
  201. package/dist/agent/bash-executor.js.map +0 -1
  202. package/dist/agent/compaction/compaction.d.ts +0 -87
  203. package/dist/agent/compaction/compaction.d.ts.map +0 -1
  204. package/dist/agent/compaction/compaction.js +0 -598
  205. package/dist/agent/compaction/compaction.js.map +0 -1
  206. package/dist/agent/compaction/index.d.ts +0 -6
  207. package/dist/agent/compaction/index.d.ts.map +0 -1
  208. package/dist/agent/compaction/index.js +0 -6
  209. package/dist/agent/compaction/index.js.map +0 -1
  210. package/dist/agent/compaction/utils.d.ts +0 -41
  211. package/dist/agent/compaction/utils.d.ts.map +0 -1
  212. package/dist/agent/compaction/utils.js +0 -240
  213. package/dist/agent/compaction/utils.js.map +0 -1
  214. package/dist/agent/settings-manager.d.ts +0 -204
  215. package/dist/agent/settings-manager.d.ts.map +0 -1
  216. package/dist/agent/settings-manager.js +0 -594
  217. package/dist/agent/settings-manager.js.map +0 -1
  218. package/dist/agent/tools/edit.test.d.ts +0 -6
  219. package/dist/agent/tools/edit.test.d.ts.map +0 -1
  220. package/dist/agent/tools/edit.test.js +0 -196
  221. package/dist/agent/tools/edit.test.js.map +0 -1
  222. package/dist/ai/providers/github-copilot-headers.d.ts +0 -33
  223. package/dist/ai/providers/github-copilot-headers.d.ts.map +0 -1
  224. package/dist/ai/providers/github-copilot-headers.js +0 -51
  225. package/dist/ai/providers/github-copilot-headers.js.map +0 -1
  226. package/dist/ai/providers/mistral.d.ts +0 -22
  227. package/dist/ai/providers/mistral.d.ts.map +0 -1
  228. package/dist/ai/providers/mistral.js +0 -497
  229. package/dist/ai/providers/mistral.js.map +0 -1
  230. package/dist/ai/types.test.d.ts +0 -8
  231. package/dist/ai/types.test.d.ts.map +0 -1
  232. package/dist/ai/types.test.js +0 -389
  233. package/dist/ai/types.test.js.map +0 -1
  234. package/dist/ai/utils/hash.d.ts +0 -6
  235. package/dist/ai/utils/hash.d.ts.map +0 -1
  236. package/dist/ai/utils/hash.js +0 -17
  237. package/dist/ai/utils/hash.js.map +0 -1
  238. package/dist/tui/kill-ring.d.ts +0 -28
  239. package/dist/tui/kill-ring.d.ts.map +0 -1
  240. package/dist/tui/kill-ring.js +0 -44
  241. package/dist/tui/kill-ring.js.map +0 -1
  242. package/dist/tui/undo-stack.d.ts +0 -17
  243. package/dist/tui/undo-stack.d.ts.map +0 -1
  244. package/dist/tui/undo-stack.js +0 -25
  245. package/dist/tui/undo-stack.js.map +0 -1
@@ -1,8 +1,6 @@
1
1
  import { getEditorKeybindings } from "../keybindings.js";
2
- import { decodeKittyPrintable, matchesKey } from "../keys.js";
3
- import { KillRing } from "../kill-ring.js";
2
+ import { matchesKey } from "../keys.js";
4
3
  import { ComponentBase, CURSOR_MARKER } from "../tui.js";
5
- import { UndoStack } from "../undo-stack.js";
6
4
  import { getSegmenter, isPunctuationChar, isWhitespaceChar, visibleWidth } from "../utils.js";
7
5
  import { SelectList } from "./select-list.js";
8
6
  const segmenter = getSegmenter();
@@ -68,60 +66,81 @@ export function wordWrapLine(line, maxWidth) {
68
66
  chunks.push({ text: line.slice(chunkStart), startIndex: chunkStart, endIndex: line.length });
69
67
  return chunks;
70
68
  }
69
+ // Kitty CSI-u sequences for printable keys, including optional shifted/base codepoints.
70
+ const KITTY_CSI_U_REGEX = /^\x1b\[(\d+)(?::(\d*))?(?::(\d+))?(?:;(\d+))?(?::(\d+))?u$/;
71
+ const KITTY_MOD_SHIFT = 1;
72
+ const KITTY_MOD_ALT = 2;
73
+ const KITTY_MOD_CTRL = 4;
74
+ // Decode a printable CSI-u sequence, preferring the shifted key when present.
75
+ function decodeKittyPrintable(data) {
76
+ const match = data.match(KITTY_CSI_U_REGEX);
77
+ if (!match)
78
+ return undefined;
79
+ // CSI-u groups: <codepoint>[:<shifted>[:<base>]];<mod>u
80
+ const codepoint = Number.parseInt(match[1] ?? "", 10);
81
+ if (!Number.isFinite(codepoint))
82
+ return undefined;
83
+ const shiftedKey = match[2] && match[2].length > 0 ? Number.parseInt(match[2], 10) : undefined;
84
+ const modValue = match[4] ? Number.parseInt(match[4], 10) : 1;
85
+ // Modifiers are 1-indexed in CSI-u; normalize to our bitmask.
86
+ const modifier = Number.isFinite(modValue) ? modValue - 1 : 0;
87
+ // Ignore CSI-u sequences used for Alt/Ctrl shortcuts.
88
+ if (modifier & (KITTY_MOD_ALT | KITTY_MOD_CTRL))
89
+ return undefined;
90
+ // Prefer the shifted keycode when Shift is held.
91
+ let effectiveCodepoint = codepoint;
92
+ if (modifier & KITTY_MOD_SHIFT && typeof shiftedKey === "number") {
93
+ effectiveCodepoint = shiftedKey;
94
+ }
95
+ // Drop control characters or invalid codepoints.
96
+ if (!Number.isFinite(effectiveCodepoint) || effectiveCodepoint < 32)
97
+ return undefined;
98
+ try {
99
+ return String.fromCodePoint(effectiveCodepoint);
100
+ }
101
+ catch {
102
+ return undefined;
103
+ }
104
+ }
71
105
  export class Editor extends ComponentBase {
72
- state = {
73
- lines: [""],
74
- cursorLine: 0,
75
- cursorCol: 0,
76
- };
77
- /** Focusable interface - set by TUI when focus changes */
78
- focused = false;
79
- tui;
80
- theme;
81
- paddingX = 0;
82
- // Store last render width for cursor navigation
83
- lastWidth = 80;
84
- // Vertical scrolling support
85
- scrollOffset = 0;
86
- // Border color (can be changed dynamically)
87
- borderColor;
88
- // Autocomplete support
89
- autocompleteProvider;
90
- autocompleteList;
91
- isAutocompleting = false;
92
- autocompletePrefix = "";
93
- autocompleteMaxVisible = 5;
94
- // Paste tracking for large pastes
95
- pastes = new Map();
96
- pasteCounter = 0;
97
- // Bracketed paste mode buffering
98
- pasteBuffer = "";
99
- isInPaste = false;
100
- pendingShiftEnter = false;
101
- // Prompt history for up/down navigation
102
- history = [];
103
- historyIndex = -1; // -1 = not browsing, 0 = most recent, 1 = older, etc.
104
- // Kill ring for Emacs-style kill/yank operations
105
- // Also tracks undo coalescing: "type-word" means we're mid-word (coalescing)
106
- killRing = new KillRing();
107
- lastAction = null;
108
- // Undo support
109
- undoStack = new UndoStack();
110
- // Sticky column navigation for wrapped lines
111
- // Maintains cursor column when moving up/down through lines of different lengths
112
- preferredVisualCol = null;
113
- onSubmit;
114
- onChange;
115
- disableSubmit = false;
116
106
  constructor(tui, theme, options = {}) {
117
107
  super();
108
+ this.state = {
109
+ lines: [""],
110
+ cursorLine: 0,
111
+ cursorCol: 0,
112
+ };
113
+ /** Focusable interface - set by TUI when focus changes */
114
+ this.focused = false;
115
+ this.paddingX = 0;
116
+ // Store last render width for cursor navigation
117
+ this.lastWidth = 80;
118
+ // Vertical scrolling support
119
+ this.scrollOffset = 0;
120
+ this.isAutocompleting = false;
121
+ this.autocompletePrefix = "";
122
+ // Paste tracking for large pastes
123
+ this.pastes = new Map();
124
+ this.pasteCounter = 0;
125
+ // Bracketed paste mode buffering
126
+ this.pasteBuffer = "";
127
+ this.isInPaste = false;
128
+ this.pendingShiftEnter = false;
129
+ // Prompt history for up/down navigation
130
+ this.history = [];
131
+ this.historyIndex = -1; // -1 = not browsing, 0 = most recent, 1 = older, etc.
132
+ // Kill ring for Emacs-style kill/yank operations
133
+ // Also tracks undo coalescing: "type-word" means we're mid-word (coalescing)
134
+ this.killRing = [];
135
+ this.lastAction = null;
136
+ // Undo support
137
+ this.undoStack = [];
138
+ this.disableSubmit = false;
118
139
  this.tui = tui;
119
140
  this.theme = theme;
120
141
  this.borderColor = theme.borderColor;
121
142
  const paddingX = options.paddingX ?? 0;
122
143
  this.paddingX = Number.isFinite(paddingX) ? Math.max(0, Math.floor(paddingX)) : 0;
123
- const maxVisible = options.autocompleteMaxVisible ?? 5;
124
- this.autocompleteMaxVisible = Number.isFinite(maxVisible) ? Math.max(3, Math.min(20, Math.floor(maxVisible))) : 5;
125
144
  }
126
145
  getPaddingX() {
127
146
  return this.paddingX;
@@ -133,16 +152,6 @@ export class Editor extends ComponentBase {
133
152
  this.tui.requestRender();
134
153
  }
135
154
  }
136
- getAutocompleteMaxVisible() {
137
- return this.autocompleteMaxVisible;
138
- }
139
- setAutocompleteMaxVisible(maxVisible) {
140
- const newMaxVisible = Number.isFinite(maxVisible) ? Math.max(3, Math.min(20, Math.floor(maxVisible))) : 5;
141
- if (this.autocompleteMaxVisible !== newMaxVisible) {
142
- this.autocompleteMaxVisible = newMaxVisible;
143
- this.tui.requestRender();
144
- }
145
- }
146
155
  setAutocompleteProvider(provider) {
147
156
  this.autocompleteProvider = provider;
148
157
  this.invalidate();
@@ -493,7 +502,7 @@ export class Editor extends ComponentBase {
493
502
  this.pasteCounter = 0;
494
503
  this.historyIndex = -1;
495
504
  this.scrollOffset = 0;
496
- this.undoStack.clear();
505
+ this.undoStack.length = 0;
497
506
  this.lastAction = null;
498
507
  if (this.onChange)
499
508
  this.onChange("");
@@ -890,93 +899,14 @@ export class Editor extends ComponentBase {
890
899
  }
891
900
  }
892
901
  }
893
- /**
894
- * Set cursor column and clear sticky column preference.
895
- * Call this for any direct column positioning or horizontal movement.
896
- */
897
- setCursorCol(col) {
898
- this.state.cursorCol = col;
899
- this.preferredVisualCol = null;
900
- }
901
- /**
902
- * Move cursor to a target visual line, applying sticky column logic.
903
- * Preserves the cursor column when moving between wrapped lines of different widths.
904
- */
905
- moveToVisualLine(visualLines, currentVisualLine, targetVisualLine) {
906
- const currentVL = visualLines[currentVisualLine];
907
- const targetVL = visualLines[targetVisualLine];
908
- if (currentVL && targetVL) {
909
- const currentVisualCol = this.state.cursorCol - currentVL.startCol;
910
- // For non-last segments, clamp to length-1 to stay within the segment
911
- const isLastSourceSegment = currentVisualLine === visualLines.length - 1 ||
912
- visualLines[currentVisualLine + 1]?.logicalLine !== currentVL.logicalLine;
913
- const sourceMaxVisualCol = isLastSourceSegment ? currentVL.length : Math.max(0, currentVL.length - 1);
914
- const isLastTargetSegment = targetVisualLine === visualLines.length - 1 ||
915
- visualLines[targetVisualLine + 1]?.logicalLine !== targetVL.logicalLine;
916
- const targetMaxVisualCol = isLastTargetSegment ? targetVL.length : Math.max(0, targetVL.length - 1);
917
- const moveToVisualCol = this.computeVerticalMoveColumn(currentVisualCol, sourceMaxVisualCol, targetMaxVisualCol);
918
- // Set cursor position
919
- this.state.cursorLine = targetVL.logicalLine;
920
- const targetCol = targetVL.startCol + moveToVisualCol;
921
- const logicalLine = this.state.lines[targetVL.logicalLine] || "";
922
- this.state.cursorCol = Math.min(targetCol, logicalLine.length);
923
- }
924
- }
925
- /**
926
- * Compute the target visual column for vertical cursor movement.
927
- * Implements sticky column logic: maintains the cursor column when moving between
928
- * wrapped lines of different lengths.
929
- *
930
- * Decision table:
931
- * | P | S | T | U | Scenario | Set Preferred | Move To |
932
- * |---|---|---|---| ---------------------------------------------------- |---------------|-------------|
933
- * | 0 | * | 0 | - | Start nav, target fits | null | current |
934
- * | 0 | * | 1 | - | Start nav, target shorter | current | target end |
935
- * | 1 | 0 | 0 | 0 | Clamped, target fits preferred | null | preferred |
936
- * | 1 | 0 | 0 | 1 | Clamped, target longer but still can't fit preferred | keep | target end |
937
- * | 1 | 0 | 1 | - | Clamped, target even shorter | keep | target end |
938
- * | 1 | 1 | 0 | - | Rewrapped, target fits current | null | current |
939
- * | 1 | 1 | 1 | - | Rewrapped, target shorter than current | current | target end |
940
- *
941
- * Where:
942
- * - P = preferred col is set
943
- * - S = cursor in middle of source line (not clamped to end)
944
- * - T = target line shorter than current visual col
945
- * - U = target line shorter than preferred col
946
- */
947
- computeVerticalMoveColumn(currentVisualCol, sourceMaxVisualCol, targetMaxVisualCol) {
948
- const hasPreferred = this.preferredVisualCol !== null; // P
949
- const cursorInMiddle = currentVisualCol < sourceMaxVisualCol; // S
950
- const targetTooShort = targetMaxVisualCol < currentVisualCol; // T
951
- if (!hasPreferred || cursorInMiddle) {
952
- if (targetTooShort) {
953
- // Cases 2 and 7: Start of navigation with target shorter than current
954
- // Save current column as preferred for later restoration
955
- this.preferredVisualCol = currentVisualCol;
956
- return targetMaxVisualCol;
957
- }
958
- // Cases 1 and 6: Target fits current column
959
- this.preferredVisualCol = null;
960
- return currentVisualCol;
961
- }
962
- const targetCantFitPreferred = targetMaxVisualCol < this.preferredVisualCol; // U
963
- if (targetTooShort || targetCantFitPreferred) {
964
- // Cases 4 and 5: Target can't fit preferred, clamp to target end
965
- return targetMaxVisualCol;
966
- }
967
- // Case 3: Target can fit preferred, restore it and clear preference
968
- const result = this.preferredVisualCol;
969
- this.preferredVisualCol = null;
970
- return result;
971
- }
972
902
  moveToLineStart() {
973
903
  this.lastAction = null;
974
- this.setCursorCol(0);
904
+ this.state.cursorCol = 0;
975
905
  }
976
906
  moveToLineEnd() {
977
907
  this.lastAction = null;
978
908
  const currentLine = this.state.lines[this.state.cursorLine] || "";
979
- this.setCursorCol(currentLine.length);
909
+ this.state.cursorCol = currentLine.length;
980
910
  }
981
911
  deleteToStartOfLine() {
982
912
  this.historyIndex = -1; // Exit history browsing mode
@@ -1208,10 +1138,20 @@ export class Editor extends ComponentBase {
1208
1138
  // Build visual line map for navigation
1209
1139
  const visualLines = this.buildVisualLineMap(width);
1210
1140
  const currentVisualLine = this.findCurrentVisualLine(visualLines);
1211
- // Move to target visual line with sticky column logic
1141
+ // Calculate column position within current visual line
1142
+ const currentVL = visualLines[currentVisualLine];
1143
+ const visualCol = currentVL ? this.state.cursorCol - currentVL.startCol : 0;
1144
+ // Move to target visual line
1212
1145
  const targetVisualLine = currentVisualLine + deltaLine;
1213
1146
  if (targetVisualLine >= 0 && targetVisualLine < visualLines.length) {
1214
- this.moveToVisualLine(visualLines, currentVisualLine, targetVisualLine);
1147
+ const targetVL = visualLines[targetVisualLine];
1148
+ if (targetVL) {
1149
+ this.state.cursorLine = targetVL.logicalLine;
1150
+ // Try to maintain visual column position, clamped to line length
1151
+ const targetCol = targetVL.startCol + Math.min(visualCol, targetVL.length);
1152
+ const logicalLine = this.state.lines[targetVL.logicalLine] || "";
1153
+ this.state.cursorCol = Math.min(targetCol, logicalLine.length);
1154
+ }
1215
1155
  }
1216
1156
  }
1217
1157
  if (deltaCol !== 0) {
@@ -1222,12 +1162,12 @@ export class Editor extends ComponentBase {
1222
1162
  const afterCursor = currentLine.slice(this.state.cursorCol);
1223
1163
  const graphemes = [...segmenter.segment(afterCursor)];
1224
1164
  const firstGrapheme = graphemes[0];
1225
- this.setCursorCol(this.state.cursorCol + (firstGrapheme ? firstGrapheme.segment.length : 1));
1165
+ this.state.cursorCol += firstGrapheme ? firstGrapheme.segment.length : 1;
1226
1166
  }
1227
1167
  else if (this.state.cursorLine < this.state.lines.length - 1) {
1228
1168
  // Wrap to start of next logical line
1229
1169
  this.state.cursorLine++;
1230
- this.setCursorCol(0);
1170
+ this.state.cursorCol = 0;
1231
1171
  }
1232
1172
  }
1233
1173
  else {
@@ -1236,13 +1176,13 @@ export class Editor extends ComponentBase {
1236
1176
  const beforeCursor = currentLine.slice(0, this.state.cursorCol);
1237
1177
  const graphemes = [...segmenter.segment(beforeCursor)];
1238
1178
  const lastGrapheme = graphemes[graphemes.length - 1];
1239
- this.setCursorCol(this.state.cursorCol - (lastGrapheme ? lastGrapheme.segment.length : 1));
1179
+ this.state.cursorCol -= lastGrapheme ? lastGrapheme.segment.length : 1;
1240
1180
  }
1241
1181
  else if (this.state.cursorLine > 0) {
1242
1182
  // Wrap to end of previous logical line
1243
1183
  this.state.cursorLine--;
1244
1184
  const prevLine = this.state.lines[this.state.cursorLine] || "";
1245
- this.setCursorCol(prevLine.length);
1185
+ this.state.cursorCol = prevLine.length;
1246
1186
  }
1247
1187
  }
1248
1188
  }
@@ -1261,8 +1201,17 @@ export class Editor extends ComponentBase {
1261
1201
  const currentVisualLine = this.findCurrentVisualLine(visualLines);
1262
1202
  // Calculate target visual line
1263
1203
  const targetVisualLine = Math.max(0, Math.min(visualLines.length - 1, currentVisualLine + direction * pageSize));
1264
- // Move cursor to target visual line with sticky column logic
1265
- this.moveToVisualLine(visualLines, currentVisualLine, targetVisualLine);
1204
+ // Move cursor to target visual line
1205
+ const targetVL = visualLines[targetVisualLine];
1206
+ if (targetVL) {
1207
+ // Preserve column position within the line
1208
+ const currentVL = visualLines[currentVisualLine];
1209
+ const visualCol = currentVL ? this.state.cursorCol - currentVL.startCol : 0;
1210
+ this.state.cursorLine = targetVL.logicalLine;
1211
+ const targetCol = targetVL.startCol + Math.min(visualCol, targetVL.length);
1212
+ const logicalLine = this.state.lines[targetVL.logicalLine] || "";
1213
+ this.state.cursorCol = Math.min(targetCol, logicalLine.length);
1214
+ }
1266
1215
  }
1267
1216
  moveWordBackwards() {
1268
1217
  this.lastAction = null;
@@ -1309,7 +1258,7 @@ export class Editor extends ComponentBase {
1309
1258
  if (this.killRing.length === 0)
1310
1259
  return;
1311
1260
  this.pushUndoSnapshot();
1312
- const text = this.killRing.peek();
1261
+ const text = this.killRing[this.killRing.length - 1] || "";
1313
1262
  this.insertYankedText(text);
1314
1263
  this.lastAction = "yank";
1315
1264
  }
@@ -1325,9 +1274,10 @@ export class Editor extends ComponentBase {
1325
1274
  // Delete the previously yanked text (still at end of ring before rotation)
1326
1275
  this.deleteYankedText();
1327
1276
  // Rotate the ring: move end to front
1328
- this.killRing.rotate();
1277
+ const lastEntry = this.killRing.pop();
1278
+ this.killRing.unshift(lastEntry);
1329
1279
  // Insert the new most recent entry (now at end after rotation)
1330
- const text = this.killRing.peek();
1280
+ const text = this.killRing[this.killRing.length - 1];
1331
1281
  this.insertYankedText(text);
1332
1282
  this.lastAction = "yank";
1333
1283
  }
@@ -1369,10 +1319,10 @@ export class Editor extends ComponentBase {
1369
1319
  }
1370
1320
  /**
1371
1321
  * Delete the previously yanked text (used by yank-pop).
1372
- * The yanked text is derived from killRing.peek() since it hasn't been rotated yet.
1322
+ * The yanked text is derived from killRing[end] since it hasn't been rotated yet.
1373
1323
  */
1374
1324
  deleteYankedText() {
1375
- const yankedText = this.killRing.peek();
1325
+ const yankedText = this.killRing[this.killRing.length - 1] || "";
1376
1326
  if (!yankedText)
1377
1327
  return;
1378
1328
  const yankLines = yankedText.split("\n");
@@ -1410,17 +1360,38 @@ export class Editor extends ComponentBase {
1410
1360
  * @param prepend - If accumulating, prepend (true) or append (false) to existing entry
1411
1361
  */
1412
1362
  addToKillRing(text, prepend) {
1413
- this.killRing.push(text, { prepend, accumulate: this.lastAction === "kill" });
1363
+ if (!text)
1364
+ return;
1365
+ if (this.lastAction === "kill" && this.killRing.length > 0) {
1366
+ // Accumulate with the most recent entry (at end of array)
1367
+ const lastEntry = this.killRing.pop();
1368
+ if (prepend) {
1369
+ this.killRing.push(text + lastEntry);
1370
+ }
1371
+ else {
1372
+ this.killRing.push(lastEntry + text);
1373
+ }
1374
+ }
1375
+ else {
1376
+ // Add new entry to end of ring
1377
+ this.killRing.push(text);
1378
+ }
1379
+ }
1380
+ captureUndoSnapshot() {
1381
+ return structuredClone(this.state);
1382
+ }
1383
+ restoreUndoSnapshot(snapshot) {
1384
+ Object.assign(this.state, structuredClone(snapshot));
1414
1385
  }
1415
1386
  pushUndoSnapshot() {
1416
- this.undoStack.push(this.state);
1387
+ this.undoStack.push(this.captureUndoSnapshot());
1417
1388
  }
1418
1389
  undo() {
1419
1390
  this.historyIndex = -1; // Exit history browsing mode
1420
- const snapshot = this.undoStack.pop();
1421
- if (!snapshot)
1391
+ if (this.undoStack.length === 0)
1422
1392
  return;
1423
- Object.assign(this.state, snapshot);
1393
+ const snapshot = this.undoStack.pop();
1394
+ this.restoreUndoSnapshot(snapshot);
1424
1395
  this.lastAction = null;
1425
1396
  if (this.onChange) {
1426
1397
  this.onChange(this.getText());
@@ -1501,7 +1472,7 @@ export class Editor extends ComponentBase {
1501
1472
  const suggestions = this.autocompleteProvider.getSuggestions(this.state.lines, this.state.cursorLine, this.state.cursorCol);
1502
1473
  if (suggestions && suggestions.items.length > 0) {
1503
1474
  this.autocompletePrefix = suggestions.prefix;
1504
- this.autocompleteList = new SelectList(suggestions.items, this.autocompleteMaxVisible, this.theme.selectList);
1475
+ this.autocompleteList = new SelectList(suggestions.items, 5, this.theme.selectList);
1505
1476
  this.isAutocompleting = true;
1506
1477
  }
1507
1478
  else {
@@ -1541,7 +1512,7 @@ https://github.com/EsotericSoftware/spine-runtimes/actions/runs/19536643416/job/
1541
1512
  const suggestions = provider.getForceFileSuggestions(this.state.lines, this.state.cursorLine, this.state.cursorCol);
1542
1513
  if (suggestions && suggestions.items.length > 0) {
1543
1514
  this.autocompletePrefix = suggestions.prefix;
1544
- this.autocompleteList = new SelectList(suggestions.items, this.autocompleteMaxVisible, this.theme.selectList);
1515
+ this.autocompleteList = new SelectList(suggestions.items, 5, this.theme.selectList);
1545
1516
  this.isAutocompleting = true;
1546
1517
  }
1547
1518
  else {
@@ -1563,7 +1534,7 @@ https://github.com/EsotericSoftware/spine-runtimes/actions/runs/19536643416/job/
1563
1534
  if (suggestions && suggestions.items.length > 0) {
1564
1535
  this.autocompletePrefix = suggestions.prefix;
1565
1536
  // Always create new SelectList to ensure update
1566
- this.autocompleteList = new SelectList(suggestions.items, this.autocompleteMaxVisible, this.theme.selectList);
1537
+ this.autocompleteList = new SelectList(suggestions.items, 5, this.theme.selectList);
1567
1538
  }
1568
1539
  else {
1569
1540
  this.cancelAutocomplete();