@travisennis/acai 0.0.5 → 0.0.6

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 (311) hide show
  1. package/README.md +4 -2
  2. package/dist/agent/index.d.ts +119 -0
  3. package/dist/agent/index.d.ts.map +1 -0
  4. package/dist/agent/index.js +406 -0
  5. package/dist/agent/manual-loop.d.ts +41 -0
  6. package/dist/agent/manual-loop.d.ts.map +1 -0
  7. package/dist/agent/manual-loop.js +278 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cli.js +27 -33
  11. package/dist/commands/add-directory-command.d.ts +3 -0
  12. package/dist/commands/add-directory-command.d.ts.map +1 -0
  13. package/dist/commands/add-directory-command.js +85 -0
  14. package/dist/commands/application-log-command.d.ts.map +1 -1
  15. package/dist/commands/application-log-command.js +34 -0
  16. package/dist/commands/clear-command.d.ts.map +1 -1
  17. package/dist/commands/clear-command.js +8 -0
  18. package/dist/commands/compact-command.d.ts.map +1 -1
  19. package/dist/commands/compact-command.js +15 -2
  20. package/dist/commands/context-command.d.ts +3 -0
  21. package/dist/commands/context-command.d.ts.map +1 -0
  22. package/dist/commands/context-command.js +183 -0
  23. package/dist/commands/copy-command.d.ts.map +1 -1
  24. package/dist/commands/copy-command.js +28 -0
  25. package/dist/commands/edit-command.d.ts.map +1 -1
  26. package/dist/commands/edit-command.js +33 -0
  27. package/dist/commands/edit-prompt-command.d.ts.map +1 -1
  28. package/dist/commands/edit-prompt-command.js +28 -0
  29. package/dist/commands/exit-command.d.ts.map +1 -1
  30. package/dist/commands/exit-command.js +20 -0
  31. package/dist/commands/files-command.d.ts.map +1 -1
  32. package/dist/commands/files-command.js +57 -0
  33. package/dist/commands/generate-rules-command.d.ts.map +1 -1
  34. package/dist/commands/generate-rules-command.js +311 -1
  35. package/dist/commands/handoff-command.d.ts +3 -0
  36. package/dist/commands/handoff-command.d.ts.map +1 -0
  37. package/dist/commands/handoff-command.js +202 -0
  38. package/dist/commands/health-command.d.ts.map +1 -1
  39. package/dist/commands/health-command.js +119 -2
  40. package/dist/commands/help-command.d.ts.map +1 -1
  41. package/dist/commands/help-command.js +28 -0
  42. package/dist/commands/history-command.d.ts +3 -0
  43. package/dist/commands/history-command.d.ts.map +1 -0
  44. package/dist/commands/history-command.js +534 -0
  45. package/dist/commands/init-command.d.ts +1 -1
  46. package/dist/commands/init-command.d.ts.map +1 -1
  47. package/dist/commands/init-command.js +55 -18
  48. package/dist/commands/last-log-command.d.ts.map +1 -1
  49. package/dist/commands/last-log-command.js +27 -0
  50. package/dist/commands/list-directories-command.d.ts +3 -0
  51. package/dist/commands/list-directories-command.d.ts.map +1 -0
  52. package/dist/commands/list-directories-command.js +48 -0
  53. package/dist/commands/list-tools-command.d.ts.map +1 -1
  54. package/dist/commands/list-tools-command.js +66 -3
  55. package/dist/commands/manager.d.ts +15 -3
  56. package/dist/commands/manager.d.ts.map +1 -1
  57. package/dist/commands/manager.js +86 -26
  58. package/dist/commands/model-command.d.ts +22 -0
  59. package/dist/commands/model-command.d.ts.map +1 -1
  60. package/dist/commands/model-command.js +256 -0
  61. package/dist/commands/paste-command.d.ts.map +1 -1
  62. package/dist/commands/paste-command.js +92 -0
  63. package/dist/commands/pickup-command.d.ts +3 -0
  64. package/dist/commands/pickup-command.d.ts.map +1 -0
  65. package/dist/commands/pickup-command.js +161 -0
  66. package/dist/commands/prompt-command.d.ts +1 -1
  67. package/dist/commands/prompt-command.d.ts.map +1 -1
  68. package/dist/commands/prompt-command.js +117 -2
  69. package/dist/commands/remove-directory-command.d.ts +3 -0
  70. package/dist/commands/remove-directory-command.d.ts.map +1 -0
  71. package/dist/commands/remove-directory-command.js +87 -0
  72. package/dist/commands/reset-command.d.ts +1 -1
  73. package/dist/commands/reset-command.d.ts.map +1 -1
  74. package/dist/commands/reset-command.js +13 -2
  75. package/dist/commands/rules-command.d.ts.map +1 -1
  76. package/dist/commands/rules-command.js +65 -0
  77. package/dist/commands/save-command.d.ts.map +1 -1
  78. package/dist/commands/save-command.js +12 -0
  79. package/dist/commands/shell-command.d.ts.map +1 -1
  80. package/dist/commands/shell-command.js +68 -0
  81. package/dist/commands/types.d.ts +9 -4
  82. package/dist/commands/types.d.ts.map +1 -1
  83. package/dist/commands/usage-command.d.ts.map +1 -1
  84. package/dist/commands/usage-command.js +22 -0
  85. package/dist/config.d.ts +6 -7
  86. package/dist/config.d.ts.map +1 -1
  87. package/dist/config.js +23 -29
  88. package/dist/formatting.d.ts +108 -0
  89. package/dist/formatting.d.ts.map +1 -1
  90. package/dist/formatting.js +147 -0
  91. package/dist/index.d.ts +7 -2
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +140 -38
  94. package/dist/logger.d.ts.map +1 -1
  95. package/dist/logger.js +47 -18
  96. package/dist/mentions.d.ts +2 -1
  97. package/dist/mentions.d.ts.map +1 -1
  98. package/dist/mentions.js +16 -1
  99. package/dist/messages.d.ts +8 -0
  100. package/dist/messages.d.ts.map +1 -1
  101. package/dist/messages.js +56 -19
  102. package/dist/middleware/cache.d.ts +3 -0
  103. package/dist/middleware/cache.d.ts.map +1 -0
  104. package/dist/middleware/cache.js +53 -0
  105. package/dist/middleware/index.d.ts +1 -0
  106. package/dist/middleware/index.d.ts.map +1 -1
  107. package/dist/middleware/index.js +1 -0
  108. package/dist/models/ai-config.d.ts +4 -2
  109. package/dist/models/ai-config.d.ts.map +1 -1
  110. package/dist/models/ai-config.js +12 -2
  111. package/dist/models/anthropic-provider.d.ts.map +1 -1
  112. package/dist/models/anthropic-provider.js +3 -60
  113. package/dist/models/manager.d.ts +2 -1
  114. package/dist/models/manager.d.ts.map +1 -1
  115. package/dist/models/manager.js +26 -2
  116. package/dist/models/openrouter-provider.d.ts +7 -14
  117. package/dist/models/openrouter-provider.d.ts.map +1 -1
  118. package/dist/models/openrouter-provider.js +114 -169
  119. package/dist/models/providers.d.ts +1 -1
  120. package/dist/models/providers.d.ts.map +1 -1
  121. package/dist/prompts.d.ts +1 -0
  122. package/dist/prompts.d.ts.map +1 -1
  123. package/dist/prompts.js +53 -4
  124. package/dist/repl/display-tool-messages.d.ts +1 -1
  125. package/dist/repl/display-tool-messages.d.ts.map +1 -1
  126. package/dist/repl/display-tool-messages.js +47 -44
  127. package/dist/repl/get-prompt-header.d.ts.map +1 -1
  128. package/dist/repl/get-prompt-header.js +1 -30
  129. package/dist/repl/project-status-line.d.ts +2 -0
  130. package/dist/repl/project-status-line.d.ts.map +1 -0
  131. package/dist/repl/project-status-line.js +31 -0
  132. package/dist/repl/prompt.d.ts +21 -0
  133. package/dist/repl/prompt.d.ts.map +1 -0
  134. package/dist/{repl-prompt.js → repl/prompt.js} +119 -22
  135. package/dist/repl/tool-call-repair.d.ts.map +1 -1
  136. package/dist/repl/tool-call-repair.js +8 -4
  137. package/dist/repl-new.d.ts +53 -0
  138. package/dist/repl-new.d.ts.map +1 -0
  139. package/dist/repl-new.js +374 -0
  140. package/dist/repl.d.ts +3 -5
  141. package/dist/repl.d.ts.map +1 -1
  142. package/dist/repl.js +74 -166
  143. package/dist/terminal/checkbox-prompt.d.ts.map +1 -1
  144. package/dist/terminal/checkbox-prompt.js +10 -4
  145. package/dist/terminal/index.d.ts +7 -0
  146. package/dist/terminal/index.d.ts.map +1 -1
  147. package/dist/terminal/index.js +94 -0
  148. package/dist/terminal/input-prompt.d.ts +2 -1
  149. package/dist/terminal/input-prompt.d.ts.map +1 -1
  150. package/dist/terminal/markdown.js +3 -0
  151. package/dist/terminal/search-prompt.d.ts.map +1 -1
  152. package/dist/terminal/search-prompt.js +11 -10
  153. package/dist/terminal/select-prompt.d.ts +2 -2
  154. package/dist/terminal/select-prompt.d.ts.map +1 -1
  155. package/dist/terminal/select-prompt.js +47 -39
  156. package/dist/tokens/threshold.d.ts +35 -0
  157. package/dist/tokens/threshold.d.ts.map +1 -0
  158. package/dist/tokens/threshold.js +85 -0
  159. package/dist/tools/advanced-edit-file.d.ts +69 -0
  160. package/dist/tools/advanced-edit-file.d.ts.map +1 -0
  161. package/dist/tools/advanced-edit-file.js +281 -0
  162. package/dist/tools/agent.d.ts +16 -5
  163. package/dist/tools/agent.d.ts.map +1 -1
  164. package/dist/tools/agent.js +71 -58
  165. package/dist/tools/bash-utils.d.ts +1 -1
  166. package/dist/tools/bash-utils.d.ts.map +1 -1
  167. package/dist/tools/bash-utils.js +14 -6
  168. package/dist/tools/bash.d.ts +21 -12
  169. package/dist/tools/bash.d.ts.map +1 -1
  170. package/dist/tools/bash.js +88 -135
  171. package/dist/tools/code-interpreter.d.ts +21 -9
  172. package/dist/tools/code-interpreter.d.ts.map +1 -1
  173. package/dist/tools/code-interpreter.js +138 -137
  174. package/dist/tools/delete-file.d.ts +17 -10
  175. package/dist/tools/delete-file.d.ts.map +1 -1
  176. package/dist/tools/delete-file.js +51 -95
  177. package/dist/tools/directory-tree.d.ts +17 -6
  178. package/dist/tools/directory-tree.d.ts.map +1 -1
  179. package/dist/tools/directory-tree.js +47 -49
  180. package/dist/tools/dynamic-tool-loader.d.ts +18 -8
  181. package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
  182. package/dist/tools/dynamic-tool-loader.js +121 -129
  183. package/dist/tools/dynamic-tool-parser.d.ts +1 -0
  184. package/dist/tools/dynamic-tool-parser.d.ts.map +1 -1
  185. package/dist/tools/dynamic-tool-parser.js +1 -0
  186. package/dist/tools/edit-file.d.ts +35 -15
  187. package/dist/tools/edit-file.d.ts.map +1 -1
  188. package/dist/tools/edit-file.js +112 -112
  189. package/dist/tools/filesystem-utils.d.ts +2 -1
  190. package/dist/tools/filesystem-utils.d.ts.map +1 -1
  191. package/dist/tools/filesystem-utils.js +31 -17
  192. package/dist/tools/glob.d.ts +36 -0
  193. package/dist/tools/glob.d.ts.map +1 -0
  194. package/dist/tools/glob.js +143 -0
  195. package/dist/tools/grep.d.ts +73 -12
  196. package/dist/tools/grep.d.ts.map +1 -1
  197. package/dist/tools/grep.js +413 -168
  198. package/dist/tools/index.d.ts +204 -124
  199. package/dist/tools/index.d.ts.map +1 -1
  200. package/dist/tools/index.js +242 -135
  201. package/dist/tools/llm-edit-fixer.d.ts +25 -0
  202. package/dist/tools/llm-edit-fixer.d.ts.map +1 -0
  203. package/dist/tools/llm-edit-fixer.js +150 -0
  204. package/dist/tools/move-file.d.ts +19 -7
  205. package/dist/tools/move-file.d.ts.map +1 -1
  206. package/dist/tools/move-file.js +40 -33
  207. package/dist/tools/read-file.d.ts +47 -9
  208. package/dist/tools/read-file.d.ts.map +1 -1
  209. package/dist/tools/read-file.js +74 -69
  210. package/dist/tools/read-multiple-files.d.ts +17 -6
  211. package/dist/tools/read-multiple-files.d.ts.map +1 -1
  212. package/dist/tools/read-multiple-files.js +76 -73
  213. package/dist/tools/save-file.d.ts +45 -12
  214. package/dist/tools/save-file.d.ts.map +1 -1
  215. package/dist/tools/save-file.js +58 -101
  216. package/dist/tools/think.d.ts +15 -7
  217. package/dist/tools/think.d.ts.map +1 -1
  218. package/dist/tools/think.js +30 -22
  219. package/dist/tools/types.d.ts +4 -10
  220. package/dist/tools/types.d.ts.map +1 -1
  221. package/dist/tools/types.js +9 -0
  222. package/dist/tools/utils.d.ts +14 -0
  223. package/dist/tools/utils.d.ts.map +1 -0
  224. package/dist/tools/utils.js +16 -0
  225. package/dist/tools/web-fetch.d.ts +11 -4
  226. package/dist/tools/web-fetch.d.ts.map +1 -1
  227. package/dist/tools/web-fetch.js +39 -38
  228. package/dist/tools/web-search.d.ts +15 -6
  229. package/dist/tools/web-search.d.ts.map +1 -1
  230. package/dist/tools/web-search.js +50 -32
  231. package/dist/tui/autocomplete.d.ts +44 -0
  232. package/dist/tui/autocomplete.d.ts.map +1 -0
  233. package/dist/tui/autocomplete.js +466 -0
  234. package/dist/tui/components/assistant-message.d.ts +18 -0
  235. package/dist/tui/components/assistant-message.d.ts.map +1 -0
  236. package/dist/tui/components/assistant-message.js +29 -0
  237. package/dist/tui/components/editor.d.ts +51 -0
  238. package/dist/tui/components/editor.d.ts.map +1 -0
  239. package/dist/tui/components/editor.js +758 -0
  240. package/dist/tui/components/footer.d.ts +24 -0
  241. package/dist/tui/components/footer.d.ts.map +1 -0
  242. package/dist/tui/components/footer.js +197 -0
  243. package/dist/tui/components/input.d.ts +14 -0
  244. package/dist/tui/components/input.d.ts.map +1 -0
  245. package/dist/tui/components/input.js +122 -0
  246. package/dist/tui/components/loader.d.ts +19 -0
  247. package/dist/tui/components/loader.d.ts.map +1 -0
  248. package/dist/tui/components/loader.js +45 -0
  249. package/dist/tui/components/markdown.d.ts +103 -0
  250. package/dist/tui/components/markdown.d.ts.map +1 -0
  251. package/dist/tui/components/markdown.js +533 -0
  252. package/dist/tui/components/modal.d.ts +40 -0
  253. package/dist/tui/components/modal.d.ts.map +1 -0
  254. package/dist/tui/components/modal.js +292 -0
  255. package/dist/tui/components/prompt-status.d.ts +16 -0
  256. package/dist/tui/components/prompt-status.d.ts.map +1 -0
  257. package/dist/tui/components/prompt-status.js +21 -0
  258. package/dist/tui/components/select-list.d.ts +22 -0
  259. package/dist/tui/components/select-list.d.ts.map +1 -0
  260. package/dist/tui/components/select-list.js +143 -0
  261. package/dist/tui/components/spacer.d.ts +16 -0
  262. package/dist/tui/components/spacer.d.ts.map +1 -0
  263. package/dist/tui/components/spacer.js +27 -0
  264. package/dist/tui/components/text.d.ts +26 -0
  265. package/dist/tui/components/text.d.ts.map +1 -0
  266. package/dist/tui/components/text.js +143 -0
  267. package/dist/tui/components/thinking-block.d.ts +14 -0
  268. package/dist/tui/components/thinking-block.d.ts.map +1 -0
  269. package/dist/tui/components/thinking-block.js +30 -0
  270. package/dist/tui/components/tool-execution.d.ts +17 -0
  271. package/dist/tui/components/tool-execution.d.ts.map +1 -0
  272. package/dist/tui/components/tool-execution.js +153 -0
  273. package/dist/tui/components/user-message.d.ts +9 -0
  274. package/dist/tui/components/user-message.d.ts.map +1 -0
  275. package/dist/tui/components/user-message.js +21 -0
  276. package/dist/tui/components/welcome.d.ts +6 -0
  277. package/dist/tui/components/welcome.d.ts.map +1 -0
  278. package/dist/tui/components/welcome.js +30 -0
  279. package/dist/tui/index.d.ts +14 -0
  280. package/dist/tui/index.d.ts.map +1 -0
  281. package/dist/tui/index.js +18 -0
  282. package/dist/tui/terminal.d.ts +37 -0
  283. package/dist/tui/terminal.d.ts.map +1 -0
  284. package/dist/tui/terminal.js +104 -0
  285. package/dist/tui/tui.d.ts +67 -0
  286. package/dist/tui/tui.d.ts.map +1 -0
  287. package/dist/tui/tui.js +184 -0
  288. package/dist/tui/utils.d.ts +19 -0
  289. package/dist/tui/utils.d.ts.map +1 -0
  290. package/dist/tui/utils.js +31 -0
  291. package/dist/utils/generators.d.ts +3 -0
  292. package/dist/utils/generators.d.ts.map +1 -0
  293. package/dist/utils/generators.js +25 -0
  294. package/dist/utils/iterables.d.ts +2 -0
  295. package/dist/utils/iterables.d.ts.map +1 -0
  296. package/dist/utils/iterables.js +6 -0
  297. package/package.json +16 -16
  298. package/dist/conversation-analyzer.d.ts +0 -11
  299. package/dist/conversation-analyzer.d.ts.map +0 -1
  300. package/dist/conversation-analyzer.js +0 -88
  301. package/dist/repl-prompt.d.ts +0 -15
  302. package/dist/repl-prompt.d.ts.map +0 -1
  303. package/dist/tokens/manage-output.d.ts +0 -34
  304. package/dist/tokens/manage-output.d.ts.map +0 -1
  305. package/dist/tokens/manage-output.js +0 -44
  306. package/dist/tool-executor.d.ts +0 -28
  307. package/dist/tool-executor.d.ts.map +0 -1
  308. package/dist/tool-executor.js +0 -74
  309. package/dist/tools/file-editing-utils.d.ts +0 -2
  310. package/dist/tools/file-editing-utils.d.ts.map +0 -1
  311. package/dist/tools/file-editing-utils.js +0 -135
@@ -0,0 +1,758 @@
1
+ import style from "../../terminal/style.js";
2
+ import { SelectList } from "./select-list.js";
3
+ export class Editor {
4
+ state = {
5
+ lines: [""],
6
+ cursorLine: 0,
7
+ cursorCol: 0,
8
+ };
9
+ config = {};
10
+ // Autocomplete support
11
+ autocompleteProvider;
12
+ autocompleteList;
13
+ isAutocompleting = false;
14
+ autocompletePrefix = "";
15
+ autocompleteDebounceTimer;
16
+ // Paste tracking for large pastes
17
+ pastes = new Map();
18
+ pasteCounter = 0;
19
+ // Bracketed paste mode buffering
20
+ pasteBuffer = "";
21
+ isInPaste = false;
22
+ onSubmit;
23
+ onChange;
24
+ disableSubmit = false;
25
+ // Custom key handlers for coding-agent
26
+ onEscape;
27
+ onCtrlC;
28
+ onRenderRequested;
29
+ constructor(config) {
30
+ if (config) {
31
+ this.config = { ...this.config, ...config };
32
+ }
33
+ }
34
+ configure(config) {
35
+ this.config = { ...this.config, ...config };
36
+ }
37
+ setAutocompleteProvider(provider) {
38
+ this.autocompleteProvider = provider;
39
+ }
40
+ render(width) {
41
+ const horizontal = style.gray("─");
42
+ // Layout the text - use full width
43
+ const layoutLines = this.layoutText(width);
44
+ const result = [];
45
+ // Render top border
46
+ result.push(horizontal.repeat(width));
47
+ // Render each layout line
48
+ for (const layoutLine of layoutLines) {
49
+ let displayText = layoutLine.text;
50
+ let visibleLength = layoutLine.text.length;
51
+ // Add cursor if this line has it
52
+ if (layoutLine.hasCursor && layoutLine.cursorPos !== undefined) {
53
+ const before = displayText.slice(0, layoutLine.cursorPos);
54
+ const after = displayText.slice(layoutLine.cursorPos);
55
+ if (after.length > 0) {
56
+ // Cursor is on a character - replace it with highlighted version
57
+ const cursor = `\x1b[7m${after[0]}\x1b[0m`;
58
+ const restAfter = after.slice(1);
59
+ displayText = before + cursor + restAfter;
60
+ // visibleLength stays the same - we're replacing, not adding
61
+ }
62
+ else {
63
+ // Cursor is at the end - check if we have room for the space
64
+ if (layoutLine.text.length < width) {
65
+ // We have room - add highlighted space
66
+ const cursor = "\x1b[7m \x1b[0m";
67
+ displayText = before + cursor;
68
+ // visibleLength increases by 1 - we're adding a space
69
+ visibleLength = layoutLine.text.length + 1;
70
+ }
71
+ else {
72
+ // Line is at full width - use reverse video on last character if possible
73
+ // or just show cursor at the end without adding space
74
+ if (before.length > 0) {
75
+ const lastChar = before[before.length - 1];
76
+ const cursor = `\x1b[7m${lastChar}\x1b[0m`;
77
+ displayText = before.slice(0, -1) + cursor;
78
+ }
79
+ // visibleLength stays the same
80
+ }
81
+ }
82
+ }
83
+ // Calculate padding based on actual visible length
84
+ const padding = " ".repeat(Math.max(0, width - visibleLength));
85
+ // Render the line (no side borders, just horizontal lines above and below)
86
+ result.push(displayText + padding);
87
+ }
88
+ // Render bottom border
89
+ result.push(horizontal.repeat(width));
90
+ // Add autocomplete list if active
91
+ if (this.isAutocompleting && this.autocompleteList) {
92
+ const autocompleteResult = this.autocompleteList.render(width);
93
+ result.push(...autocompleteResult);
94
+ }
95
+ return result;
96
+ }
97
+ handleInput(data) {
98
+ // Handle bracketed paste mode
99
+ // Start of paste: \x1b[200~
100
+ // End of paste: \x1b[201~
101
+ // Check if we're starting a bracketed paste
102
+ if (data.includes("\x1b[200~")) {
103
+ this.isInPaste = true;
104
+ this.pasteBuffer = "";
105
+ // Remove the start marker and keep the rest
106
+ const cleanedData = data.replace("\x1b[200~", "");
107
+ // Process the remaining data
108
+ this.processInputData(cleanedData);
109
+ return;
110
+ }
111
+ // If we're in a paste, buffer the data
112
+ if (this.isInPaste) {
113
+ // Append data to buffer first (end marker could be split across chunks)
114
+ this.pasteBuffer += data;
115
+ // Check if the accumulated buffer contains the end marker
116
+ const endIndex = this.pasteBuffer.indexOf("\x1b[201~");
117
+ if (endIndex !== -1) {
118
+ // Extract content before the end marker
119
+ const pasteContent = this.pasteBuffer.substring(0, endIndex);
120
+ // Process the complete paste
121
+ this.handlePaste(pasteContent);
122
+ // Reset paste state
123
+ this.isInPaste = false;
124
+ // Process any remaining data after the end marker
125
+ const remaining = this.pasteBuffer.substring(endIndex + 6); // 6 = length of \x1b[201~
126
+ this.pasteBuffer = "";
127
+ if (remaining.length > 0) {
128
+ this.handleInput(remaining);
129
+ }
130
+ return;
131
+ }
132
+ // Still accumulating, wait for more data
133
+ return;
134
+ }
135
+ // Intercept Escape key - but only if autocomplete is NOT active
136
+ // (let parent handle escape for autocomplete cancellation)
137
+ if (data === "\x1b" && this.onEscape && !this.isShowingAutocomplete()) {
138
+ this.onEscape();
139
+ return;
140
+ }
141
+ // Intercept Ctrl+C
142
+ if (data === "\x03" && this.onCtrlC) {
143
+ this.onCtrlC();
144
+ return;
145
+ }
146
+ // Process regular input data
147
+ this.processInputData(data);
148
+ }
149
+ processInputData(data) {
150
+ // Handle special key combinations first
151
+ // Ctrl+C - Exit (let parent handle this)
152
+ if (data.charCodeAt(0) === 3) {
153
+ return;
154
+ }
155
+ // Handle autocomplete special keys first (but don't block other input)
156
+ if (this.isAutocompleting && this.autocompleteList) {
157
+ // Escape - cancel autocomplete
158
+ if (data === "\x1b") {
159
+ this.cancelAutocomplete();
160
+ return;
161
+ }
162
+ // Let the autocomplete list handle navigation and selection
163
+ if (data === "\x1b[A" ||
164
+ data === "\x1b[B" ||
165
+ data === "\r" ||
166
+ data === "\t") {
167
+ // Pass arrow keys and Tab to the list for navigation
168
+ if (data === "\x1b[A" || data === "\x1b[B" || data === "\t") {
169
+ this.autocompleteList.handleInput(data);
170
+ }
171
+ // Only Enter applies the selection
172
+ if (data === "\r") {
173
+ const selected = this.autocompleteList.getSelectedItem();
174
+ if (selected && this.autocompleteProvider) {
175
+ const result = this.autocompleteProvider.applyCompletion(this.state.lines, this.state.cursorLine, this.state.cursorCol, selected, this.autocompletePrefix);
176
+ this.state.lines = result.lines;
177
+ this.state.cursorLine = result.cursorLine;
178
+ this.state.cursorCol = result.cursorCol;
179
+ this.cancelAutocomplete();
180
+ if (this.onChange) {
181
+ this.onChange(this.getText());
182
+ }
183
+ }
184
+ return;
185
+ }
186
+ // For other keys, handle normally within autocomplete
187
+ return;
188
+ }
189
+ // For other keys (like regular typing), DON'T return here
190
+ // Let them fall through to normal character handling
191
+ }
192
+ // Tab key - context-aware completion (but not when already autocompleting)
193
+ if (data === "\t" && !this.isAutocompleting) {
194
+ void this.handleTabCompletion();
195
+ return;
196
+ }
197
+ // Continue with rest of input handling
198
+ // Ctrl+K - Delete current line
199
+ if (data.charCodeAt(0) === 11) {
200
+ this.deleteCurrentLine();
201
+ }
202
+ // Ctrl+A - Move to start of line
203
+ else if (data.charCodeAt(0) === 1) {
204
+ this.moveToLineStart();
205
+ }
206
+ // Ctrl+E - Move to end of line
207
+ else if (data.charCodeAt(0) === 5) {
208
+ this.moveToLineEnd();
209
+ }
210
+ // Modified Enter keys (Shift+Enter, Ctrl+Enter, etc.) - create new line
211
+ else if (this.isModifiedEnter(data)) {
212
+ // Modifier + Enter = new line
213
+ this.addNewLine();
214
+ }
215
+ // Plain Enter (char code 13 for CR) - only CR submits, LF adds new line
216
+ else if (data.charCodeAt(0) === 13 && data.length === 1) {
217
+ // If submit is disabled, do nothing
218
+ if (this.disableSubmit) {
219
+ return;
220
+ }
221
+ // Get text and substitute paste markers with actual content
222
+ let result = this.state.lines.join("\n").trim();
223
+ // Replace all [paste #N +xxx lines] or [paste #N xxx chars] markers with actual paste content
224
+ for (const [pasteId, pasteContent] of this.pastes) {
225
+ // Match formats: [paste #N], [paste #N +xxx lines], or [paste #N xxx chars]
226
+ const markerRegex = new RegExp(`\\[paste #${pasteId}( (\\+\\d+ lines|\\d+ chars))?\\]`, "g");
227
+ result = result.replace(markerRegex, pasteContent);
228
+ }
229
+ // Reset editor and clear pastes
230
+ this.state = {
231
+ lines: [""],
232
+ cursorLine: 0,
233
+ cursorCol: 0,
234
+ };
235
+ this.pastes.clear();
236
+ this.pasteCounter = 0;
237
+ // Notify that editor is now empty
238
+ if (this.onChange) {
239
+ this.onChange("");
240
+ }
241
+ if (this.onSubmit) {
242
+ this.onSubmit(result);
243
+ }
244
+ }
245
+ // Backspace
246
+ else if (data.charCodeAt(0) === 127 || data.charCodeAt(0) === 8) {
247
+ this.handleBackspace();
248
+ }
249
+ // Line navigation shortcuts (Home/End keys)
250
+ else if (data === "\x1b[H" || data === "\x1b[1~" || data === "\x1b[7~") {
251
+ // Home key
252
+ this.moveToLineStart();
253
+ }
254
+ else if (data === "\x1b[F" || data === "\x1b[4~" || data === "\x1b[8~") {
255
+ // End key
256
+ this.moveToLineEnd();
257
+ }
258
+ // Forward delete (Fn+Backspace or Delete key)
259
+ else if (data === "\x1b[3~") {
260
+ // Delete key
261
+ this.handleForwardDelete();
262
+ }
263
+ // Arrow keys
264
+ else if (data === "\x1b[A") {
265
+ // Up
266
+ this.moveCursor(-1, 0);
267
+ }
268
+ else if (data === "\x1b[B") {
269
+ // Down
270
+ this.moveCursor(1, 0);
271
+ }
272
+ else if (data === "\x1b[C") {
273
+ // Right
274
+ this.moveCursor(0, 1);
275
+ }
276
+ else if (data === "\x1b[D") {
277
+ // Left
278
+ this.moveCursor(0, -1);
279
+ }
280
+ // Regular characters (printable ASCII)
281
+ else if (data.charCodeAt(0) >= 32 && data.charCodeAt(0) <= 126) {
282
+ this.insertCharacter(data);
283
+ }
284
+ }
285
+ layoutText(contentWidth) {
286
+ const layoutLines = [];
287
+ if (this.state.lines.length === 0 ||
288
+ (this.state.lines.length === 1 && this.state.lines[0] === "")) {
289
+ // Empty editor
290
+ layoutLines.push({
291
+ text: "",
292
+ hasCursor: true,
293
+ cursorPos: 0,
294
+ });
295
+ return layoutLines;
296
+ }
297
+ // Process each logical line
298
+ for (let i = 0; i < this.state.lines.length; i++) {
299
+ const line = this.state.lines[i] || "";
300
+ const isCurrentLine = i === this.state.cursorLine;
301
+ const maxLineLength = contentWidth;
302
+ if (line.length <= maxLineLength) {
303
+ // Line fits in one layout line
304
+ if (isCurrentLine) {
305
+ layoutLines.push({
306
+ text: line,
307
+ hasCursor: true,
308
+ cursorPos: this.state.cursorCol,
309
+ });
310
+ }
311
+ else {
312
+ layoutLines.push({
313
+ text: line,
314
+ hasCursor: false,
315
+ });
316
+ }
317
+ }
318
+ else {
319
+ // Line needs wrapping
320
+ const chunks = [];
321
+ for (let pos = 0; pos < line.length; pos += maxLineLength) {
322
+ chunks.push(line.slice(pos, pos + maxLineLength));
323
+ }
324
+ for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
325
+ const chunk = chunks[chunkIndex];
326
+ if (!chunk)
327
+ continue;
328
+ const chunkStart = chunkIndex * maxLineLength;
329
+ const chunkEnd = chunkStart + chunk.length;
330
+ const cursorPos = this.state.cursorCol;
331
+ const hasCursorInChunk = isCurrentLine && cursorPos >= chunkStart && cursorPos <= chunkEnd;
332
+ if (hasCursorInChunk) {
333
+ layoutLines.push({
334
+ text: chunk,
335
+ hasCursor: true,
336
+ cursorPos: cursorPos - chunkStart,
337
+ });
338
+ }
339
+ else {
340
+ layoutLines.push({
341
+ text: chunk,
342
+ hasCursor: false,
343
+ });
344
+ }
345
+ }
346
+ }
347
+ }
348
+ return layoutLines;
349
+ }
350
+ getText() {
351
+ return this.state.lines.join("\n");
352
+ }
353
+ setText(text) {
354
+ // Split text into lines, handling different line endings
355
+ const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
356
+ // Ensure at least one empty line
357
+ this.state.lines = lines.length === 0 ? [""] : lines;
358
+ // Reset cursor to end of text
359
+ this.state.cursorLine = this.state.lines.length - 1;
360
+ this.state.cursorCol = this.state.lines[this.state.cursorLine]?.length || 0;
361
+ // Notify of change
362
+ if (this.onChange) {
363
+ this.onChange(this.getText());
364
+ }
365
+ }
366
+ // All the editor methods from before...
367
+ insertCharacter(char) {
368
+ const line = this.state.lines[this.state.cursorLine] || "";
369
+ const before = line.slice(0, this.state.cursorCol);
370
+ const after = line.slice(this.state.cursorCol);
371
+ this.state.lines[this.state.cursorLine] = before + char + after;
372
+ this.state.cursorCol += char.length; // Fix: increment by the length of the inserted string
373
+ if (this.onChange) {
374
+ this.onChange(this.getText());
375
+ }
376
+ // Check if we should trigger or update autocomplete
377
+ if (!this.isAutocompleting) {
378
+ // Auto-trigger for "/" at the start of a line (slash commands)
379
+ if (char === "/" && this.isAtStartOfMessage()) {
380
+ void this.tryTriggerAutocomplete();
381
+ }
382
+ // Also auto-trigger when typing letters in a slash command context
383
+ else if (/[a-zA-Z0-9]/.test(char)) {
384
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
385
+ const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
386
+ // Check if we're in a slash command with a space (i.e., typing arguments)
387
+ if (textBeforeCursor.startsWith("/") &&
388
+ textBeforeCursor.includes(" ")) {
389
+ void this.tryTriggerAutocomplete();
390
+ }
391
+ }
392
+ }
393
+ else {
394
+ void this.updateAutocomplete();
395
+ }
396
+ }
397
+ handlePaste(pastedText) {
398
+ // Clean the pasted text
399
+ const cleanText = pastedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
400
+ // Convert tabs to spaces (4 spaces per tab)
401
+ const tabExpandedText = cleanText.replace(/\t/g, " ");
402
+ // Filter out non-printable characters except newlines
403
+ const filteredText = tabExpandedText
404
+ .split("")
405
+ .filter((char) => char === "\n" || (char >= " " && char <= "~"))
406
+ .join("");
407
+ // Split into lines
408
+ const pastedLines = filteredText.split("\n");
409
+ // Check if this is a large paste (> 10 lines or > 1000 characters)
410
+ const totalChars = filteredText.length;
411
+ if (pastedLines.length > 10 || totalChars > 1000) {
412
+ // Store the paste and insert a marker
413
+ this.pasteCounter++;
414
+ const pasteId = this.pasteCounter;
415
+ this.pastes.set(pasteId, filteredText);
416
+ // Insert marker like "[paste #1 +123 lines]" or "[paste #1 1234 chars]"
417
+ const marker = pastedLines.length > 10
418
+ ? `[paste #${pasteId} +${pastedLines.length} lines]`
419
+ : `[paste #${pasteId} ${totalChars} chars]`;
420
+ for (const char of marker) {
421
+ this.insertCharacter(char);
422
+ }
423
+ return;
424
+ }
425
+ if (pastedLines.length === 1) {
426
+ // Single line - just insert each character
427
+ const text = pastedLines[0] || "";
428
+ for (const char of text) {
429
+ this.insertCharacter(char);
430
+ }
431
+ return;
432
+ }
433
+ // Multi-line paste - be very careful with array manipulation
434
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
435
+ const beforeCursor = currentLine.slice(0, this.state.cursorCol);
436
+ const afterCursor = currentLine.slice(this.state.cursorCol);
437
+ // Build the new lines array step by step
438
+ const newLines = [];
439
+ // Add all lines before current line
440
+ for (let i = 0; i < this.state.cursorLine; i++) {
441
+ newLines.push(this.state.lines[i] || "");
442
+ }
443
+ // Add the first pasted line merged with before cursor text
444
+ newLines.push(beforeCursor + (pastedLines[0] || ""));
445
+ // Add all middle pasted lines
446
+ for (let i = 1; i < pastedLines.length - 1; i++) {
447
+ newLines.push(pastedLines[i] || "");
448
+ }
449
+ // Add the last pasted line with after cursor text
450
+ newLines.push((pastedLines[pastedLines.length - 1] || "") + afterCursor);
451
+ // Add all lines after current line
452
+ for (let i = this.state.cursorLine + 1; i < this.state.lines.length; i++) {
453
+ newLines.push(this.state.lines[i] || "");
454
+ }
455
+ // Replace the entire lines array
456
+ this.state.lines = newLines;
457
+ // Update cursor position to end of pasted content
458
+ this.state.cursorLine += pastedLines.length - 1;
459
+ this.state.cursorCol = (pastedLines[pastedLines.length - 1] || "").length;
460
+ // Notify of change
461
+ if (this.onChange) {
462
+ this.onChange(this.getText());
463
+ }
464
+ }
465
+ addNewLine() {
466
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
467
+ const before = currentLine.slice(0, this.state.cursorCol);
468
+ const after = currentLine.slice(this.state.cursorCol);
469
+ // Split current line
470
+ this.state.lines[this.state.cursorLine] = before;
471
+ this.state.lines.splice(this.state.cursorLine + 1, 0, after);
472
+ // Move cursor to start of new line
473
+ this.state.cursorLine++;
474
+ this.state.cursorCol = 0;
475
+ if (this.onChange) {
476
+ this.onChange(this.getText());
477
+ }
478
+ }
479
+ handleBackspace() {
480
+ if (this.state.cursorCol > 0) {
481
+ // Delete character in current line
482
+ const line = this.state.lines[this.state.cursorLine] || "";
483
+ const before = line.slice(0, this.state.cursorCol - 1);
484
+ const after = line.slice(this.state.cursorCol);
485
+ this.state.lines[this.state.cursorLine] = before + after;
486
+ this.state.cursorCol--;
487
+ }
488
+ else if (this.state.cursorLine > 0) {
489
+ // Merge with previous line
490
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
491
+ const previousLine = this.state.lines[this.state.cursorLine - 1] || "";
492
+ this.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;
493
+ this.state.lines.splice(this.state.cursorLine, 1);
494
+ this.state.cursorLine--;
495
+ this.state.cursorCol = previousLine.length;
496
+ }
497
+ if (this.onChange) {
498
+ this.onChange(this.getText());
499
+ }
500
+ // Update autocomplete after backspace
501
+ if (this.isAutocompleting) {
502
+ void this.updateAutocomplete();
503
+ }
504
+ }
505
+ moveToLineStart() {
506
+ this.state.cursorCol = 0;
507
+ }
508
+ moveToLineEnd() {
509
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
510
+ this.state.cursorCol = currentLine.length;
511
+ }
512
+ handleForwardDelete() {
513
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
514
+ if (this.state.cursorCol < currentLine.length) {
515
+ // Delete character at cursor position (forward delete)
516
+ const before = currentLine.slice(0, this.state.cursorCol);
517
+ const after = currentLine.slice(this.state.cursorCol + 1);
518
+ this.state.lines[this.state.cursorLine] = before + after;
519
+ }
520
+ else if (this.state.cursorLine < this.state.lines.length - 1) {
521
+ // At end of line - merge with next line
522
+ const nextLine = this.state.lines[this.state.cursorLine + 1] || "";
523
+ this.state.lines[this.state.cursorLine] = currentLine + nextLine;
524
+ this.state.lines.splice(this.state.cursorLine + 1, 1);
525
+ }
526
+ if (this.onChange) {
527
+ this.onChange(this.getText());
528
+ }
529
+ }
530
+ deleteCurrentLine() {
531
+ if (this.state.lines.length === 1) {
532
+ // Only one line - just clear it
533
+ this.state.lines[0] = "";
534
+ this.state.cursorCol = 0;
535
+ }
536
+ else {
537
+ // Multiple lines - remove current line
538
+ this.state.lines.splice(this.state.cursorLine, 1);
539
+ // Adjust cursor position
540
+ if (this.state.cursorLine >= this.state.lines.length) {
541
+ // Was on last line, move to new last line
542
+ this.state.cursorLine = this.state.lines.length - 1;
543
+ }
544
+ // Clamp cursor column to new line length
545
+ const newLine = this.state.lines[this.state.cursorLine] || "";
546
+ this.state.cursorCol = Math.min(this.state.cursorCol, newLine.length);
547
+ }
548
+ if (this.onChange) {
549
+ this.onChange(this.getText());
550
+ }
551
+ }
552
+ moveCursor(deltaLine, deltaCol) {
553
+ if (deltaLine !== 0) {
554
+ const newLine = this.state.cursorLine + deltaLine;
555
+ if (newLine >= 0 && newLine < this.state.lines.length) {
556
+ this.state.cursorLine = newLine;
557
+ // Clamp cursor column to new line length
558
+ const line = this.state.lines[this.state.cursorLine] || "";
559
+ this.state.cursorCol = Math.min(this.state.cursorCol, line.length);
560
+ }
561
+ }
562
+ if (deltaCol !== 0) {
563
+ // Move column
564
+ const newCol = this.state.cursorCol + deltaCol;
565
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
566
+ const maxCol = currentLine.length;
567
+ this.state.cursorCol = Math.max(0, Math.min(maxCol, newCol));
568
+ }
569
+ }
570
+ // Helper method to check if cursor is at start of message (for slash command detection)
571
+ isAtStartOfMessage() {
572
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
573
+ const beforeCursor = currentLine.slice(0, this.state.cursorCol);
574
+ // At start if line is empty, only contains whitespace, or is just "/"
575
+ return beforeCursor.trim() === "" || beforeCursor.trim() === "/";
576
+ }
577
+ // Autocomplete methods
578
+ async tryTriggerAutocomplete(explicitTab = false) {
579
+ if (!this.autocompleteProvider)
580
+ return;
581
+ // Check if we should trigger file completion on Tab
582
+ if (explicitTab) {
583
+ const provider = this
584
+ .autocompleteProvider;
585
+ // Only check file completion triggering if the provider has the method
586
+ // For slash commands, we always want to show autocomplete
587
+ if (provider.shouldTriggerFileCompletion &&
588
+ !provider.shouldTriggerFileCompletion(this.state.lines, this.state.cursorLine, this.state.cursorCol)) {
589
+ return;
590
+ }
591
+ }
592
+ const suggestions = await this.autocompleteProvider.getSuggestions(this.state.lines, this.state.cursorLine, this.state.cursorCol);
593
+ if (suggestions && suggestions.items.length > 0) {
594
+ this.autocompletePrefix = suggestions.prefix;
595
+ this.isAutocompleting = true;
596
+ if (this.autocompleteList) {
597
+ this.autocompleteList.updateItems(suggestions.items);
598
+ }
599
+ else {
600
+ this.autocompleteList = new SelectList(suggestions.items, 5);
601
+ }
602
+ // Request re-render to show autocomplete list
603
+ this.onRenderRequested?.();
604
+ }
605
+ else {
606
+ this.cancelAutocomplete();
607
+ }
608
+ }
609
+ async handleTabCompletion() {
610
+ if (!this.autocompleteProvider)
611
+ return;
612
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
613
+ const beforeCursor = currentLine.slice(0, this.state.cursorCol);
614
+ // Check if we're in a slash command context
615
+ if (beforeCursor.trimStart().startsWith("/")) {
616
+ await this.handleSlashCommandCompletion();
617
+ }
618
+ else {
619
+ await this.forceFileAutocomplete();
620
+ }
621
+ }
622
+ async handleSlashCommandCompletion() {
623
+ // For now, fall back to regular autocomplete (slash commands)
624
+ // This can be extended later to handle command-specific argument completion
625
+ await this.tryTriggerAutocomplete(true);
626
+ }
627
+ async forceFileAutocomplete() {
628
+ if (!this.autocompleteProvider)
629
+ return;
630
+ // Check if provider has the force method
631
+ const provider = this.autocompleteProvider;
632
+ if (!provider.getForceFileSuggestions) {
633
+ await this.tryTriggerAutocomplete(true);
634
+ return;
635
+ }
636
+ const suggestions = await provider.getForceFileSuggestions(this.state.lines, this.state.cursorLine, this.state.cursorCol);
637
+ if (suggestions && suggestions.items.length > 0) {
638
+ this.autocompletePrefix = suggestions.prefix;
639
+ if (this.autocompleteList) {
640
+ this.autocompleteList.updateItems(suggestions.items);
641
+ }
642
+ else {
643
+ this.autocompleteList = new SelectList(suggestions.items, 5);
644
+ }
645
+ this.isAutocompleting = true;
646
+ // Request re-render to show autocomplete list
647
+ this.onRenderRequested?.();
648
+ }
649
+ else {
650
+ this.cancelAutocomplete();
651
+ }
652
+ }
653
+ cancelAutocomplete() {
654
+ this.isAutocompleting = false;
655
+ this.autocompleteList = undefined;
656
+ this.autocompletePrefix = "";
657
+ if (this.autocompleteDebounceTimer) {
658
+ clearTimeout(this.autocompleteDebounceTimer);
659
+ this.autocompleteDebounceTimer = undefined;
660
+ }
661
+ }
662
+ isShowingAutocomplete() {
663
+ return this.isAutocompleting;
664
+ }
665
+ async updateAutocomplete() {
666
+ if (!this.isAutocompleting || !this.autocompleteProvider)
667
+ return;
668
+ // Check if the current text still matches our autocomplete context
669
+ // This prevents unnecessary updates when typing unrelated text
670
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
671
+ const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
672
+ // If we're no longer in the context that triggered autocomplete, cancel it
673
+ // For slash commands, check if we're still in slash command context
674
+ // For file paths, check if we're still in the same path context
675
+ if (textBeforeCursor.startsWith("/")) {
676
+ // For slash commands, we should continue autocomplete as long as we're in slash command context
677
+ // Don't cancel based on prefix matching for progressive typing
678
+ }
679
+ else {
680
+ // For file paths, check if we're still in the same path context
681
+ if (!textBeforeCursor.endsWith(this.autocompletePrefix)) {
682
+ this.cancelAutocomplete();
683
+ return;
684
+ }
685
+ }
686
+ // Clear any existing debounce timer
687
+ if (this.autocompleteDebounceTimer) {
688
+ clearTimeout(this.autocompleteDebounceTimer);
689
+ }
690
+ // Debounce autocomplete updates to prevent rapid-fire file system operations
691
+ this.autocompleteDebounceTimer = setTimeout(async () => {
692
+ const suggestions = await this.autocompleteProvider?.getSuggestions(this.state.lines, this.state.cursorLine, this.state.cursorCol);
693
+ if (suggestions && suggestions.items.length > 0) {
694
+ this.autocompletePrefix = suggestions.prefix;
695
+ if (this.autocompleteList) {
696
+ // Update the existing list with new items
697
+ this.autocompleteList.updateItems(suggestions.items);
698
+ }
699
+ else {
700
+ this.autocompleteList = new SelectList(suggestions.items, 5);
701
+ }
702
+ this.isAutocompleting = true;
703
+ // Request re-render to show updated autocomplete list
704
+ this.onRenderRequested?.();
705
+ }
706
+ else {
707
+ // No more matches, cancel autocomplete
708
+ this.cancelAutocomplete();
709
+ // Request re-render to hide autocomplete
710
+ this.onRenderRequested?.();
711
+ }
712
+ }, 50); // 50ms debounce delay
713
+ }
714
+ isModifiedEnter(data) {
715
+ // Common modified Enter sequences across terminals
716
+ const sequences = [
717
+ // Shift+Enter sequences
718
+ "\x1b[13;2~", // Some terminals
719
+ "\x1bOM", // Some terminals
720
+ "\\\r", // VS Code terminal
721
+ "\x1b\r", // Option+Enter (macOS)
722
+ // Ctrl+Enter sequences
723
+ "\x1b[13;5~", // Some terminals
724
+ ];
725
+ // Check for known sequences
726
+ if (sequences.includes(data)) {
727
+ return true;
728
+ }
729
+ // Check for Enter with escape sequences (general case)
730
+ if (data.length > 1 &&
731
+ data.includes("\x1b") &&
732
+ (data.includes("\r") || data.includes("\n"))) {
733
+ return true;
734
+ }
735
+ // Check for Ctrl+Enter (Ctrl + CR)
736
+ if (data.charCodeAt(0) === 13 && data.length > 1) {
737
+ return true;
738
+ }
739
+ return false;
740
+ }
741
+ getCursorPosition() {
742
+ // Return cursor position relative to the editor component
743
+ // The editor has a top border line, then content lines, then a bottom border line
744
+ // So cursor position within editor is: row = layoutLineIndex + 1
745
+ const width = 80; // Use a reasonable default width for calculation
746
+ const layoutLines = this.layoutText(width);
747
+ // Find which layout line contains the cursor
748
+ for (let i = 0; i < layoutLines.length; i++) {
749
+ const layoutLine = layoutLines[i];
750
+ if (layoutLine.hasCursor && layoutLine.cursorPos !== undefined) {
751
+ // Add 1 to account for the top border line
752
+ return [i + 1, layoutLine.cursorPos];
753
+ }
754
+ }
755
+ // If no cursor found, return position at start of first content line (after top border)
756
+ return [1, 0];
757
+ }
758
+ }