@linxiraos/pi-tui 1.1.26 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (423) hide show
  1. package/CHANGELOG.md +51 -491
  2. package/README.md +13 -7
  3. package/dist/types/active-terminal.d.ts +30 -0
  4. package/dist/types/app-keybindings.d.ts +7 -2
  5. package/dist/types/apps/cleanse-board.d.ts +1 -1
  6. package/dist/types/apps/debug/log-viewer.d.ts +4 -0
  7. package/dist/types/apps/debug/raw-sse-buffer.d.ts +2 -0
  8. package/dist/types/apps/git/git-tui.d.ts +5 -4
  9. package/dist/types/apps/git/help.d.ts +3 -1
  10. package/dist/types/apps/if-bench-board.d.ts +2 -2
  11. package/dist/types/apps/setup-model-picker.d.ts +1 -1
  12. package/dist/types/autocomplete.d.ts +5 -2
  13. package/dist/types/charts/chart-plan.d.ts +674 -0
  14. package/dist/types/charts/chart-svg.d.ts +59 -0
  15. package/dist/types/charts/table-data.d.ts +175 -0
  16. package/dist/types/chat/assistant-message.d.ts +11 -1
  17. package/dist/types/chat/chat-transcript-builder.d.ts +3 -0
  18. package/dist/types/chat/compaction-summary-message.d.ts +1 -0
  19. package/dist/types/chat/execution-shared.d.ts +6 -5
  20. package/dist/types/chat/fence-figure.d.ts +42 -0
  21. package/dist/types/chat/figure-markdown.d.ts +42 -0
  22. package/dist/types/chat/messages.d.ts +1 -1
  23. package/dist/types/chat/svg-figure.d.ts +33 -0
  24. package/dist/types/chat/svg-source.d.ts +49 -0
  25. package/dist/types/chat/table-chart.d.ts +89 -0
  26. package/dist/types/chat/tool-execution.d.ts +2 -0
  27. package/dist/types/chat/user-message.d.ts +17 -4
  28. package/dist/types/chrome/shared.d.ts +9 -0
  29. package/dist/types/chrome/status-notice.d.ts +15 -3
  30. package/dist/types/chrome/transcript-container.d.ts +6 -0
  31. package/dist/types/components/box.d.ts +1 -0
  32. package/dist/types/components/disclosure.d.ts +1 -0
  33. package/dist/types/components/editor.d.ts +23 -5
  34. package/dist/types/components/form.d.ts +1 -0
  35. package/dist/types/components/image.d.ts +26 -44
  36. package/dist/types/components/input.d.ts +2 -1
  37. package/dist/types/components/key-value-list.d.ts +1 -0
  38. package/dist/types/components/layout/row.d.ts +1 -0
  39. package/dist/types/components/layout/stack.d.ts +1 -0
  40. package/dist/types/components/loader.d.ts +5 -6
  41. package/dist/types/components/markdown.d.ts +47 -5
  42. package/dist/types/components/scroll-view.d.ts +1 -0
  43. package/dist/types/components/section.d.ts +1 -0
  44. package/dist/types/components/table.d.ts +1 -0
  45. package/dist/types/components/text.d.ts +1 -0
  46. package/dist/types/components/tree-view.d.ts +7 -0
  47. package/dist/types/components/truncated-text.d.ts +1 -0
  48. package/dist/types/fuzzy.d.ts +29 -1
  49. package/dist/types/key-hint-format.d.ts +9 -1
  50. package/dist/types/keybinding-matchers.d.ts +3 -0
  51. package/dist/types/keybindings.d.ts +1 -1
  52. package/dist/types/kitty-graphics.d.ts +2 -2
  53. package/dist/types/loop-watchdog.d.ts +8 -7
  54. package/dist/types/multiplexers/cmux.d.ts +16 -0
  55. package/dist/types/multiplexers/herdr.d.ts +26 -0
  56. package/dist/types/multiplexers/notify.d.ts +13 -0
  57. package/dist/types/multiplexers/orca.d.ts +12 -0
  58. package/dist/types/multiplexers/screen.d.ts +9 -0
  59. package/dist/types/multiplexers/tmux.d.ts +29 -0
  60. package/dist/types/multiplexers/types.d.ts +100 -0
  61. package/dist/types/multiplexers/wmux.d.ts +12 -0
  62. package/dist/types/multiplexers/zellij.d.ts +12 -0
  63. package/dist/types/native/apply.d.ts +4 -3
  64. package/dist/types/native/backend.d.ts +5 -4
  65. package/dist/types/native/blobs.d.ts +7 -3
  66. package/dist/types/native/encode.d.ts +8 -6
  67. package/dist/types/native/node.d.ts +36 -5
  68. package/dist/types/native/overlay.d.ts +8 -3
  69. package/dist/types/native/settle.d.ts +4 -3
  70. package/dist/types/overlays/agent-hub-projection.d.ts +6 -5
  71. package/dist/types/overlays/agent-hub-types.d.ts +9 -0
  72. package/dist/types/overlays/agent-hub.d.ts +3 -0
  73. package/dist/types/overlays/agent-transcript-viewer.d.ts +12 -2
  74. package/dist/types/overlays/agents-hub.d.ts +9 -0
  75. package/dist/types/overlays/annotation-overlay.d.ts +19 -2
  76. package/dist/types/overlays/annotation-types.d.ts +2 -0
  77. package/dist/types/overlays/ask-dialog.d.ts +5 -2
  78. package/dist/types/overlays/btw-history-panel.d.ts +13 -1
  79. package/dist/types/overlays/copy-selector.d.ts +1 -0
  80. package/dist/types/overlays/history-search.d.ts +10 -0
  81. package/dist/types/overlays/hook-selector.d.ts +9 -4
  82. package/dist/types/overlays/hub-frame.d.ts +4 -1
  83. package/dist/types/overlays/jobs-panel.d.ts +1 -7
  84. package/dist/types/overlays/model-browser.d.ts +54 -7
  85. package/dist/types/overlays/model-hub.d.ts +37 -0
  86. package/dist/types/overlays/oauth-selector.d.ts +0 -1
  87. package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
  88. package/dist/types/overlays/report-panel.d.ts +68 -0
  89. package/dist/types/overlays/rewind-selector.d.ts +1 -0
  90. package/dist/types/overlays/session-observer-registry.d.ts +2 -0
  91. package/dist/types/overlays/session-selector.d.ts +6 -2
  92. package/dist/types/overlays/settings-defs.d.ts +1 -0
  93. package/dist/types/overlays/thinking-selector.d.ts +2 -2
  94. package/dist/types/overlays/usage-dashboard.d.ts +12 -8
  95. package/dist/types/overlays/usage-display.d.ts +3 -1
  96. package/dist/types/prompt/composer-cache.d.ts +0 -16
  97. package/dist/types/prompt/composer.d.ts +18 -3
  98. package/dist/types/prompt/custom-editor.d.ts +25 -13
  99. package/dist/types/prompt/image-source.d.ts +2 -1
  100. package/dist/types/prompt/interactive-context-helpers.d.ts +5 -1
  101. package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
  102. package/dist/types/prompt/welcome.d.ts +7 -2
  103. package/dist/types/render/composite.d.ts +2 -0
  104. package/dist/types/render/output-pane.d.ts +1 -0
  105. package/dist/types/render/render-utils.d.ts +1 -0
  106. package/dist/types/render/tool-card.d.ts +6 -0
  107. package/dist/types/render/width-aware-text.d.ts +2 -0
  108. package/dist/types/setup/scenes/types.d.ts +1 -0
  109. package/dist/types/space-hold.d.ts +40 -25
  110. package/dist/types/status-line/component.d.ts +3 -0
  111. package/dist/types/status-line/context-usage.d.ts +7 -5
  112. package/dist/types/status-line/host.d.ts +4 -1
  113. package/dist/types/status-line/segments.d.ts +2 -0
  114. package/dist/types/status-line/types.d.ts +14 -3
  115. package/dist/types/terminal-capabilities.d.ts +41 -36
  116. package/dist/types/terminal-multiplexer.d.ts +127 -14
  117. package/dist/types/terminal.d.ts +23 -16
  118. package/dist/types/theme/active-symbols.d.ts +1 -1
  119. package/dist/types/theme/schema.d.ts +11 -2
  120. package/dist/types/theme/symbols.d.ts +1 -1
  121. package/dist/types/theme/theme-class.d.ts +7 -0
  122. package/dist/types/theme/theme.d.ts +1 -1
  123. package/dist/types/theme/tui-adapters.d.ts +3 -0
  124. package/dist/types/thinking.d.ts +1 -1
  125. package/dist/types/tmux.d.ts +11 -4
  126. package/dist/types/tools/bash.d.ts +7 -2
  127. package/dist/types/tools/daemon.d.ts +1 -1
  128. package/dist/types/tools/eval.d.ts +22 -3
  129. package/dist/types/tools/glob.d.ts +4 -0
  130. package/dist/types/tools/native-view.d.ts +47 -0
  131. package/dist/types/tools/output-meta.d.ts +11 -1
  132. package/dist/types/tools/renderer.d.ts +37 -2
  133. package/dist/types/tools/streaming-output.d.ts +20 -1
  134. package/dist/types/tools/task.d.ts +2 -3
  135. package/dist/types/tools/write.d.ts +9 -2
  136. package/dist/types/tools/yield.d.ts +22 -0
  137. package/dist/types/tui.d.ts +36 -0
  138. package/dist/types/utils.d.ts +6 -0
  139. package/dist/types/vim.d.ts +19 -5
  140. package/dist/types/windows-input-mode.d.ts +33 -1
  141. package/package.json +13 -9
  142. package/src/active-terminal.d.ts +30 -0
  143. package/src/active-terminal.ts +80 -0
  144. package/src/app-keybindings.d.ts +7 -2
  145. package/src/app-keybindings.ts +7 -2
  146. package/src/apps/autoresearch-dashboard.ts +7 -3
  147. package/src/apps/cleanse-board.ts +2 -2
  148. package/src/apps/cleanse-picker.ts +1 -1
  149. package/src/apps/debug/log-viewer.ts +188 -72
  150. package/src/apps/debug/raw-sse-buffer.ts +8 -0
  151. package/src/apps/debug/raw-sse.ts +39 -5
  152. package/src/apps/git/diff-pane.ts +96 -23
  153. package/src/apps/git/git-tui.ts +23 -16
  154. package/src/apps/git/help.ts +29 -20
  155. package/src/apps/git/sidebar.ts +8 -3
  156. package/src/apps/if-bench-board.ts +2 -2
  157. package/src/apps/ps-top.ts +2 -2
  158. package/src/apps/setup-model-picker.ts +1 -1
  159. package/src/autocomplete.d.ts +5 -2
  160. package/src/autocomplete.ts +67 -43
  161. package/src/bracketed-paste.ts +10 -3
  162. package/src/charts/chart-plan.d.ts +674 -0
  163. package/src/charts/chart-plan.ts +5081 -0
  164. package/src/charts/chart-svg.d.ts +59 -0
  165. package/src/charts/chart-svg.ts +4657 -0
  166. package/src/charts/table-data.d.ts +175 -0
  167. package/src/charts/table-data.ts +1478 -0
  168. package/src/chat/advisor-message.ts +11 -0
  169. package/src/chat/assistant-message.d.ts +11 -1
  170. package/src/chat/assistant-message.ts +245 -54
  171. package/src/chat/chat-transcript-builder.d.ts +3 -0
  172. package/src/chat/chat-transcript-builder.ts +9 -3
  173. package/src/chat/compaction-summary-message.d.ts +1 -0
  174. package/src/chat/compaction-summary-message.ts +8 -0
  175. package/src/chat/execution-shared.d.ts +6 -5
  176. package/src/chat/execution-shared.ts +42 -25
  177. package/src/chat/fence-figure.d.ts +42 -0
  178. package/src/chat/fence-figure.ts +80 -0
  179. package/src/chat/figure-markdown.d.ts +42 -0
  180. package/src/chat/figure-markdown.ts +174 -0
  181. package/src/chat/messages.d.ts +1 -1
  182. package/src/chat/messages.ts +1 -1
  183. package/src/chat/read-tool-group.ts +6 -4
  184. package/src/chat/skill-message.ts +6 -0
  185. package/src/chat/svg-figure.d.ts +33 -0
  186. package/src/chat/svg-figure.ts +399 -0
  187. package/src/chat/svg-source.d.ts +49 -0
  188. package/src/chat/svg-source.ts +188 -0
  189. package/src/chat/table-chart.d.ts +89 -0
  190. package/src/chat/table-chart.ts +294 -0
  191. package/src/chat/tool-execution.d.ts +2 -0
  192. package/src/chat/tool-execution.ts +86 -7
  193. package/src/chat/user-message.d.ts +17 -4
  194. package/src/chat/user-message.ts +71 -8
  195. package/src/chrome/shared.ts +53 -0
  196. package/src/chrome/status-notice.ts +25 -7
  197. package/src/chrome/transcript-container.d.ts +6 -0
  198. package/src/chrome/transcript-container.ts +162 -19
  199. package/src/components/box.d.ts +1 -0
  200. package/src/components/box.ts +5 -0
  201. package/src/components/disclosure.d.ts +1 -0
  202. package/src/components/disclosure.ts +8 -0
  203. package/src/components/editor.d.ts +23 -5
  204. package/src/components/editor.ts +316 -47
  205. package/src/components/form.d.ts +1 -0
  206. package/src/components/form.ts +8 -0
  207. package/src/components/image.d.ts +26 -44
  208. package/src/components/image.ts +115 -152
  209. package/src/components/input.d.ts +2 -1
  210. package/src/components/input.ts +17 -7
  211. package/src/components/key-value-list.d.ts +1 -0
  212. package/src/components/key-value-list.ts +4 -0
  213. package/src/components/layout/row.d.ts +1 -0
  214. package/src/components/layout/row.ts +7 -0
  215. package/src/components/layout/stack.d.ts +1 -0
  216. package/src/components/layout/stack.ts +7 -0
  217. package/src/components/loader.d.ts +5 -6
  218. package/src/components/loader.ts +10 -17
  219. package/src/components/markdown.d.ts +47 -5
  220. package/src/components/markdown.ts +786 -303
  221. package/src/components/scroll-view.d.ts +1 -0
  222. package/src/components/scroll-view.ts +5 -0
  223. package/src/components/section.d.ts +1 -0
  224. package/src/components/section.ts +5 -0
  225. package/src/components/settings-list.ts +2 -0
  226. package/src/components/table.d.ts +1 -0
  227. package/src/components/table.ts +4 -0
  228. package/src/components/text.d.ts +1 -0
  229. package/src/components/text.ts +7 -0
  230. package/src/components/tree-view.d.ts +7 -0
  231. package/src/components/tree-view.ts +38 -8
  232. package/src/components/truncated-text.d.ts +1 -0
  233. package/src/components/truncated-text.ts +5 -0
  234. package/src/desktop-notify.ts +3 -2
  235. package/src/fuzzy.d.ts +29 -1
  236. package/src/fuzzy.ts +115 -30
  237. package/src/hotkeys-markdown.ts +9 -2
  238. package/src/key-hint-format.d.ts +9 -1
  239. package/src/key-hint-format.ts +26 -11
  240. package/src/keybinding-matchers.d.ts +3 -0
  241. package/src/keybinding-matchers.ts +6 -2
  242. package/src/keybindings.d.ts +1 -1
  243. package/src/keybindings.ts +1 -1
  244. package/src/kitty-graphics.d.ts +2 -2
  245. package/src/kitty-graphics.ts +11 -5
  246. package/src/loop-watchdog.d.ts +8 -7
  247. package/src/loop-watchdog.ts +18 -13
  248. package/src/multiplexers/cmux.d.ts +16 -0
  249. package/src/multiplexers/cmux.ts +29 -0
  250. package/src/multiplexers/herdr.d.ts +26 -0
  251. package/src/multiplexers/herdr.ts +64 -0
  252. package/src/multiplexers/notify.d.ts +13 -0
  253. package/src/multiplexers/notify.ts +24 -0
  254. package/src/multiplexers/orca.d.ts +12 -0
  255. package/src/multiplexers/orca.ts +16 -0
  256. package/src/multiplexers/screen.d.ts +9 -0
  257. package/src/multiplexers/screen.ts +15 -0
  258. package/src/multiplexers/tmux.d.ts +29 -0
  259. package/src/multiplexers/tmux.ts +40 -0
  260. package/src/multiplexers/types.d.ts +100 -0
  261. package/src/multiplexers/types.ts +110 -0
  262. package/src/multiplexers/wmux.d.ts +12 -0
  263. package/src/multiplexers/wmux.ts +16 -0
  264. package/src/multiplexers/zellij.d.ts +12 -0
  265. package/src/multiplexers/zellij.ts +24 -0
  266. package/src/native/apply.d.ts +4 -3
  267. package/src/native/apply.ts +4 -3
  268. package/src/native/backend.d.ts +5 -4
  269. package/src/native/backend.ts +199 -22
  270. package/src/native/blobs.d.ts +7 -3
  271. package/src/native/blobs.ts +47 -12
  272. package/src/native/encode.d.ts +8 -6
  273. package/src/native/encode.ts +76 -34
  274. package/src/native/node.d.ts +36 -5
  275. package/src/native/node.ts +31 -5
  276. package/src/native/overlay.d.ts +8 -3
  277. package/src/native/overlay.ts +17 -4
  278. package/src/native/reconcile.ts +16 -4
  279. package/src/native/settle.d.ts +4 -3
  280. package/src/native/settle.ts +4 -3
  281. package/src/overlays/agent-hub-projection.d.ts +6 -5
  282. package/src/overlays/agent-hub-projection.ts +124 -13
  283. package/src/overlays/agent-hub-types.d.ts +9 -0
  284. package/src/overlays/agent-hub-types.ts +6 -0
  285. package/src/overlays/agent-hub.ts +48 -29
  286. package/src/overlays/agent-transcript-viewer.ts +93 -23
  287. package/src/overlays/agents-hub.ts +28 -8
  288. package/src/overlays/annotation-overlay.ts +293 -90
  289. package/src/overlays/annotation-types.ts +2 -0
  290. package/src/overlays/ask-dialog.ts +10 -17
  291. package/src/overlays/btw-history-panel.ts +136 -39
  292. package/src/overlays/copy-selector.d.ts +1 -0
  293. package/src/overlays/copy-selector.ts +32 -8
  294. package/src/overlays/extensions/extension-dashboard.ts +4 -4
  295. package/src/overlays/extensions/state-manager.ts +52 -12
  296. package/src/overlays/history-search.ts +100 -7
  297. package/src/overlays/hook-selector.ts +12 -5
  298. package/src/overlays/hub-frame.ts +19 -1
  299. package/src/overlays/jobs-panel.ts +33 -52
  300. package/src/overlays/model-browser.ts +367 -122
  301. package/src/overlays/model-hub.ts +435 -59
  302. package/src/overlays/move-overlay.ts +28 -14
  303. package/src/overlays/oauth-selector.ts +54 -70
  304. package/src/overlays/pause-screen.ts +2 -2
  305. package/src/overlays/plan-review-overlay.ts +208 -70
  306. package/src/overlays/plugin-settings.ts +3 -3
  307. package/src/overlays/report-panel.ts +191 -0
  308. package/src/overlays/rewind-selector.ts +88 -17
  309. package/src/overlays/session-observer-registry.d.ts +2 -0
  310. package/src/overlays/session-observer-registry.ts +10 -2
  311. package/src/overlays/session-selector.ts +64 -26
  312. package/src/overlays/settings-defs.ts +5 -4
  313. package/src/overlays/settings-selector.ts +33 -8
  314. package/src/overlays/snapcompact-shape-preview.ts +2 -3
  315. package/src/overlays/stats-notice.ts +1 -1
  316. package/src/overlays/thinking-selector.ts +11 -10
  317. package/src/overlays/tree-selector.ts +41 -20
  318. package/src/overlays/usage-dashboard.ts +117 -58
  319. package/src/overlays/usage-display.ts +10 -4
  320. package/src/overlays/usage-row.ts +22 -8
  321. package/src/prompt/attachment-chips.ts +15 -11
  322. package/src/prompt/composer-attachments.ts +6 -4
  323. package/src/prompt/composer-cache.ts +7 -86
  324. package/src/prompt/composer.ts +37 -26
  325. package/src/prompt/custom-editor.ts +282 -105
  326. package/src/prompt/image-source.ts +2 -1
  327. package/src/prompt/interactive-context-helpers.ts +32 -18
  328. package/src/prompt/macos-spelling.ts +7 -0
  329. package/src/prompt/magic-keywords.ts +11 -2
  330. package/src/prompt/model-mention-autocomplete.ts +14 -14
  331. package/src/prompt/queued-messages.ts +6 -4
  332. package/src/prompt/tips.txt +13 -9
  333. package/src/prompt/welcome.ts +64 -46
  334. package/src/prompt/word-completion.ts +38 -5
  335. package/src/render/composite.d.ts +2 -0
  336. package/src/render/composite.ts +64 -0
  337. package/src/render/output-pane.d.ts +1 -0
  338. package/src/render/output-pane.ts +6 -0
  339. package/src/render/render-utils.d.ts +1 -0
  340. package/src/render/render-utils.ts +16 -1
  341. package/src/render/tool-card.d.ts +6 -0
  342. package/src/render/tool-card.ts +15 -0
  343. package/src/render/width-aware-text.d.ts +2 -0
  344. package/src/render/width-aware-text.ts +5 -0
  345. package/src/setup/scenes/model.ts +2 -1
  346. package/src/setup/scenes/types.ts +1 -0
  347. package/src/space-hold.d.ts +40 -25
  348. package/src/space-hold.ts +156 -52
  349. package/src/status-line/component.ts +292 -78
  350. package/src/status-line/context-usage.ts +21 -46
  351. package/src/status-line/host.ts +4 -1
  352. package/src/status-line/segments.ts +40 -22
  353. package/src/status-line/types.ts +15 -3
  354. package/src/terminal-capabilities.d.ts +41 -36
  355. package/src/terminal-capabilities.ts +137 -213
  356. package/src/terminal-multiplexer.d.ts +127 -14
  357. package/src/terminal-multiplexer.ts +112 -33
  358. package/src/terminal.d.ts +23 -16
  359. package/src/terminal.ts +203 -99
  360. package/src/theme/active-symbols.d.ts +1 -1
  361. package/src/theme/active-symbols.ts +1 -1
  362. package/src/theme/schema-validation.ts +1 -0
  363. package/src/theme/schema.d.ts +11 -2
  364. package/src/theme/schema.ts +14 -1
  365. package/src/theme/session-color.ts +1 -7
  366. package/src/theme/symbols.d.ts +1 -1
  367. package/src/theme/symbols.ts +11 -4
  368. package/src/theme/theme-class.d.ts +7 -0
  369. package/src/theme/theme-class.ts +26 -1
  370. package/src/theme/theme-schema.json +5 -1
  371. package/src/theme/theme.d.ts +1 -1
  372. package/src/theme/theme.ts +8 -4
  373. package/src/theme/tui-adapters.d.ts +3 -0
  374. package/src/theme/tui-adapters.ts +68 -0
  375. package/src/thinking.d.ts +1 -1
  376. package/src/thinking.ts +1 -1
  377. package/src/tmux.d.ts +11 -4
  378. package/src/tmux.ts +47 -11
  379. package/src/tools/ast-edit.ts +59 -35
  380. package/src/tools/ast-grep.ts +35 -16
  381. package/src/tools/bash.d.ts +7 -2
  382. package/src/tools/bash.ts +116 -47
  383. package/src/tools/daemon.d.ts +1 -1
  384. package/src/tools/daemon.ts +1 -1
  385. package/src/tools/default-renderer.ts +35 -19
  386. package/src/tools/edit.ts +142 -120
  387. package/src/tools/eval-format/index.ts +28 -2
  388. package/src/tools/eval-format/javascript.ts +7 -5
  389. package/src/tools/eval.d.ts +22 -3
  390. package/src/tools/eval.ts +364 -208
  391. package/src/tools/fetch.ts +5 -6
  392. package/src/tools/glob.d.ts +4 -0
  393. package/src/tools/glob.ts +33 -65
  394. package/src/tools/grep.ts +81 -48
  395. package/src/tools/index.ts +2 -0
  396. package/src/tools/lsp.ts +81 -59
  397. package/src/tools/mcp.ts +111 -68
  398. package/src/tools/native-view.d.ts +47 -0
  399. package/src/tools/native-view.ts +102 -1
  400. package/src/tools/output-meta.d.ts +11 -1
  401. package/src/tools/output-meta.ts +23 -3
  402. package/src/tools/read.ts +7 -5
  403. package/src/tools/renderer.d.ts +37 -2
  404. package/src/tools/renderer.ts +42 -2
  405. package/src/tools/streaming-output.d.ts +20 -1
  406. package/src/tools/streaming-output.ts +154 -50
  407. package/src/tools/task.d.ts +2 -3
  408. package/src/tools/task.ts +115 -62
  409. package/src/tools/todo.ts +119 -30
  410. package/src/tools/wait.ts +34 -24
  411. package/src/tools/web-search.ts +1 -1
  412. package/src/tools/write.d.ts +9 -2
  413. package/src/tools/write.ts +147 -95
  414. package/src/tools/yield.d.ts +22 -0
  415. package/src/tools/yield.ts +225 -0
  416. package/src/tui.d.ts +36 -0
  417. package/src/tui.ts +666 -217
  418. package/src/utils.d.ts +6 -0
  419. package/src/utils.ts +19 -0
  420. package/src/vim.d.ts +19 -5
  421. package/src/vim.ts +333 -18
  422. package/src/windows-input-mode.d.ts +33 -1
  423. package/src/windows-input-mode.ts +212 -6
package/src/utils.d.ts CHANGED
@@ -26,6 +26,12 @@ export interface TextSizingOptions {
26
26
  */
27
27
  export declare function encodeTextSized(text: string, options?: TextSizingOptions): string;
28
28
  export declare function sliceWithWidth(line: string, startCol: number, length: number, strict?: boolean | null): SliceResult;
29
+ /**
30
+ * Hard-wrap one literal terminal row without trimming spaces or splitting graphemes.
31
+ * An oversized grapheme is emitted intact even when it exceeds the requested width.
32
+ * Zero-cell rows remain one empty row, matching restored-terminal reflow.
33
+ */
34
+ export declare function wrapLiteralLine(line: string, width: number): string[];
29
35
  export declare function truncateToWidth(text: string, maxWidth: number, ellipsisKind?: Ellipsis | null | "", pad?: boolean | null): string;
30
36
  export declare function wrapTextWithAnsi(text: string, width: number): string[];
31
37
  export declare function extractSegments(line: string, beforeEnd: number, afterStart: number, afterLen: number, strictAfter: boolean): ExtractSegmentsResult;
package/src/utils.ts CHANGED
@@ -154,6 +154,25 @@ export function sliceWithWidth(line: string, startCol: number, length: number, s
154
154
  return nativeSliceWithWidth(line, startCol, length, strict ?? null, DEFAULT_TAB_WIDTH);
155
155
  }
156
156
 
157
+ /**
158
+ * Hard-wrap one literal terminal row without trimming spaces or splitting graphemes.
159
+ * An oversized grapheme is emitted intact even when it exceeds the requested width.
160
+ * Zero-cell rows remain one empty row, matching restored-terminal reflow.
161
+ */
162
+ export function wrapLiteralLine(line: string, width: number): string[] {
163
+ const columns = Math.max(1, width);
164
+ const lineWidth = visibleWidth(line);
165
+ if (lineWidth === 0) return [""];
166
+ const rows: string[] = [];
167
+ for (let column = 0; column < lineWidth;) {
168
+ let slice = sliceWithWidth(line, column, columns, true);
169
+ if (slice.width === 0) slice = sliceWithWidth(line, column, columns);
170
+ rows.push(slice.text);
171
+ column += Math.max(1, slice.width);
172
+ }
173
+ return rows;
174
+ }
175
+
157
176
  export function truncateToWidth(
158
177
  text: string,
159
178
  maxWidth: number,
package/src/vim.d.ts CHANGED
@@ -8,8 +8,8 @@
8
8
  * This is a usable subset of Vim, not a reimplementation of it (see issue #3299): Normal and Visual
9
9
  * modes, the common motions, and operators built from those motions.
10
10
  */
11
- export type VimMode = "insert" | "normal" | "visual" | "visual-line";
12
- export type VimOperator = "d" | "y" | "c";
11
+ export type VimMode = "insert" | "normal" | "visual" | "visual-line" | "replace";
12
+ export type VimOperator = "d" | "y" | "c" | ">" | "<";
13
13
  export interface VimPosition {
14
14
  line: number;
15
15
  col: number;
@@ -50,6 +50,20 @@ export type VimCommand = {
50
50
  count: number;
51
51
  } | {
52
52
  kind: "undo";
53
+ } | {
54
+ kind: "replace";
55
+ from: VimPosition;
56
+ to: VimPosition;
57
+ text: string;
58
+ } | {
59
+ kind: "join";
60
+ fromLine: number;
61
+ lines: number;
62
+ } | {
63
+ kind: "indent";
64
+ fromLine: number;
65
+ toLine: number;
66
+ out: boolean;
53
67
  };
54
68
  /** Start offset of the grapheme after `col`, clamped to `text.length`. */
55
69
  export declare function nextGraphemeStart(text: string, col: number): number;
@@ -62,11 +76,11 @@ export declare class VimState {
62
76
  mode: VimMode;
63
77
  /** Fixed end of a Visual selection; the cursor is the moving end. */
64
78
  anchor: VimPosition | null;
65
- /** True while a count, operator, `g`, or text-object prefix is half-typed — Escape cancels it. */
79
+ /** True while a count, operator, `g`, text-object, find, or `r` prefix is half-typed — Escape cancels it. */
66
80
  get pending(): boolean;
67
81
  /**
68
- * The half-typed command as Vim would echo it (`"2"`, `"d"`, `"2d"`, `"di"`) — empty when
69
- * nothing is pending. Hosts render this next to the mode so a partially entered operator is
82
+ * The half-typed command as Vim would echo it (`"2"`, `"d"`, `"2d"`, `"di"`, `"df"`, `"r"`) — empty
83
+ * when nothing is pending. Hosts render this next to the mode so a partially entered operator is
70
84
  * visible instead of silently swallowing the next keystroke.
71
85
  */
72
86
  get pendingText(): string;
package/src/vim.ts CHANGED
@@ -11,9 +11,9 @@ import { getSegmenter, moveWordLeft, moveWordRight } from "./utils";
11
11
  * modes, the common motions, and operators built from those motions.
12
12
  */
13
13
 
14
- export type VimMode = "insert" | "normal" | "visual" | "visual-line";
14
+ export type VimMode = "insert" | "normal" | "visual" | "visual-line" | "replace";
15
15
 
16
- export type VimOperator = "d" | "y" | "c";
16
+ export type VimOperator = "d" | "y" | "c" | ">" | "<";
17
17
 
18
18
  export interface VimPosition {
19
19
  line: number;
@@ -38,7 +38,10 @@ export type VimCommand =
38
38
  | { kind: "delete"; from: VimPosition; to: VimPosition; linewise: boolean; insert: boolean }
39
39
  | { kind: "openLine"; below: boolean }
40
40
  | { kind: "paste"; after: boolean; count: number }
41
- | { kind: "undo" };
41
+ | { kind: "undo" }
42
+ | { kind: "replace"; from: VimPosition; to: VimPosition; text: string }
43
+ | { kind: "join"; fromLine: number; lines: number }
44
+ | { kind: "indent"; fromLine: number; toLine: number; out: boolean };
42
45
 
43
46
  const segmenter = getSegmenter();
44
47
 
@@ -75,6 +78,35 @@ function firstNonBlank(text: string): number {
75
78
  }
76
79
  return 0;
77
80
  }
81
+ function findPendingKey(pending: { forward: boolean; till: boolean } | null): string {
82
+ if (!pending) return "";
83
+ return pending.till ? (pending.forward ? "t" : "T") : pending.forward ? "f" : "F";
84
+ }
85
+
86
+ /**
87
+ * Column of the `count`th grapheme equal to `target`, searching strictly after `fromCol` when
88
+ * `forward` and strictly before it otherwise. Line-local, like Vim's `f`/`F`.
89
+ */
90
+ function findGrapheme(text: string, fromCol: number, target: string, forward: boolean, count: number): number | null {
91
+ if (count < 1 || target.length === 0) return null;
92
+ const segs = [...segmenter.segment(text)];
93
+ let here = -1;
94
+ for (let i = 0; i < segs.length; i++) {
95
+ if (segs[i]!.index <= fromCol) here = i;
96
+ else break;
97
+ }
98
+ let seen = 0;
99
+ if (forward) {
100
+ for (let i = here + 1; i < segs.length; i++) {
101
+ if (segs[i]!.segment === target && ++seen === count) return segs[i]!.index;
102
+ }
103
+ } else {
104
+ for (let i = here - 1; i >= 0; i--) {
105
+ if (segs[i]!.segment === target && ++seen === count) return segs[i]!.index;
106
+ }
107
+ }
108
+ return null;
109
+ }
78
110
 
79
111
  /** Vim's `w`: `moveWordRight` stops at the end of the current word, so skip the gap that follows. */
80
112
  function wordForward(text: string, col: number): number {
@@ -157,11 +189,7 @@ function wordObject(text: string, col: number, around: boolean, big: boolean, co
157
189
  * leading run when there is none.
158
190
  */
159
191
  function quoteObject(text: string, col: number, quote: string, around: boolean): [number, number] | null {
160
- const marks: number[] = [];
161
- for (let i = 0; i < text.length; i++) {
162
- if (text.charAt(i) === "\\") i++;
163
- else if (text.charAt(i) === quote) marks.push(i);
164
- }
192
+ const marks = quoteMarks(text, quote);
165
193
  for (let p = 0; p + 1 < marks.length; p += 2) {
166
194
  const open = marks[p]!;
167
195
  const close = marks[p + 1]!;
@@ -254,6 +282,113 @@ function paragraphObject(buf: VimBuffer, around: boolean): TextObjectRange {
254
282
  return { from: { line: first, col: 0 }, to: { line: end, col: (buf.lines[end] ?? "").length }, linewise: true };
255
283
  }
256
284
 
285
+ const BRACKET_MATCH: Record<string, { mate: string; forward: boolean }> = {
286
+ "(": { mate: ")", forward: true },
287
+ ")": { mate: "(", forward: false },
288
+ "[": { mate: "]", forward: true },
289
+ "]": { mate: "[", forward: false },
290
+ "{": { mate: "}", forward: true },
291
+ "}": { mate: "{", forward: false },
292
+ };
293
+
294
+ /** Quote columns, skipping a backslash and the character after it, same as `i"`/`a"`. */
295
+ function quoteMarks(text: string, quote: string): number[] {
296
+ const marks: number[] = [];
297
+ for (let i = 0; i < text.length; i++) {
298
+ if (text.charAt(i) === "\\") i++;
299
+ else if (text.charAt(i) === quote) marks.push(i);
300
+ }
301
+ return marks;
302
+ }
303
+
304
+ function quoteMate(marks: readonly number[], col: number): number | null {
305
+ for (let p = 0; p + 1 < marks.length; p += 2) {
306
+ const open = marks[p]!;
307
+ const close = marks[p + 1]!;
308
+ if (col === open) return close;
309
+ if (col === close) return open;
310
+ }
311
+ return null;
312
+ }
313
+
314
+ /** Column of the match for `%`: the next bracket or quote on this line, then its pair. */
315
+ function matchDelimiter(buf: VimBuffer): VimPosition | null {
316
+ const line = buf.lines[buf.cursorLine] ?? "";
317
+ const quotes = {
318
+ '"': quoteMarks(line, '"'),
319
+ "'": quoteMarks(line, "'"),
320
+ };
321
+ const quoteAt = (col: number): '"' | "'" | null =>
322
+ quotes['"'].includes(col) ? '"' : quotes["'"].includes(col) ? "'" : null;
323
+ let col = buf.cursorCol;
324
+ while (col < line.length && BRACKET_MATCH[line.charAt(col)] === undefined && quoteAt(col) === null) col++;
325
+ const quote = quoteAt(col);
326
+ if (quote) {
327
+ const mate = quoteMate(quotes[quote], col);
328
+ return mate === null ? null : { line: buf.cursorLine, col: mate };
329
+ }
330
+ const spec = BRACKET_MATCH[line.charAt(col)];
331
+ if (!spec) return null;
332
+ const { text, cursor } = flatten({ lines: buf.lines, cursorLine: buf.cursorLine, cursorCol: col });
333
+ const open = spec.forward ? line.charAt(col) : spec.mate;
334
+ const close = spec.forward ? spec.mate : line.charAt(col);
335
+ let depth = 0;
336
+ let match = -1;
337
+ if (spec.forward) {
338
+ for (let i = cursor + 1; i < text.length; i++) {
339
+ const ch = text.charAt(i);
340
+ if (ch === open) depth++;
341
+ else if (ch === close) {
342
+ if (depth === 0) {
343
+ match = i;
344
+ break;
345
+ }
346
+ depth--;
347
+ }
348
+ }
349
+ } else {
350
+ for (let i = cursor - 1; i >= 0; i--) {
351
+ const ch = text.charAt(i);
352
+ if (ch === close) depth++;
353
+ else if (ch === open) {
354
+ if (depth === 0) {
355
+ match = i;
356
+ break;
357
+ }
358
+ depth--;
359
+ }
360
+ }
361
+ }
362
+ return match < 0 ? null : offsetToPos(buf.lines, match);
363
+ }
364
+
365
+ function isParagraphBlank(lines: readonly string[], line: number): boolean {
366
+ return (lines[line] ?? "").trim() === "";
367
+ }
368
+
369
+ /** Line `{`/`}` land on. A missing boundary stays on the first or last line. */
370
+ function paragraphBoundary(lines: readonly string[], line: number, forward: boolean): number {
371
+ const last = lines.length - 1;
372
+ if (last < 0) return 0;
373
+ let i = Math.max(0, Math.min(line, last));
374
+ if (forward) {
375
+ if (i < last && !isParagraphBlank(lines, i)) {
376
+ while (i < last && !isParagraphBlank(lines, i + 1)) i++;
377
+ return i < last ? i + 1 : last;
378
+ }
379
+ while (i < last && isParagraphBlank(lines, i)) i++;
380
+ while (i < last && !isParagraphBlank(lines, i)) i++;
381
+ return i;
382
+ }
383
+ if (i > 0 && !isParagraphBlank(lines, i)) {
384
+ while (i > 0 && !isParagraphBlank(lines, i - 1)) i--;
385
+ return i > 0 ? i - 1 : 0;
386
+ }
387
+ while (i > 0 && isParagraphBlank(lines, i)) i--;
388
+ while (i > 0 && !isParagraphBlank(lines, i - 1)) i--;
389
+ return i > 0 ? i - 1 : 0;
390
+ }
391
+
257
392
  interface Motion {
258
393
  to: VimPosition;
259
394
  /** Inclusive motions cover the grapheme under `to` when used with an operator. */
@@ -271,6 +406,13 @@ export class VimState {
271
406
  #pendingG = false;
272
407
  /** `i` or `a` typed after an operator or in Visual mode — waiting for the object key. */
273
408
  #textObject: "i" | "a" | null = null;
409
+ /** `f`/`F`/`t`/`T` waiting for the target grapheme. */
410
+ #findPending: { forward: boolean; till: boolean } | null = null;
411
+ /** Last find attempt, so `;` repeats it and `,` reverses it. */
412
+ #lastFind: { forward: boolean; till: boolean; target: string } | null = null;
413
+ /** `r` waiting for the replacement grapheme. */
414
+ #replaceChar = false;
415
+
274
416
  /**
275
417
  * Vim's "desired column": `j`/`k` remember the column you started from, so descending through a
276
418
  * short line and back out returns to it instead of collapsing permanently. `null` means the
@@ -279,18 +421,25 @@ export class VimState {
279
421
  */
280
422
  #desiredCol: number | null = null;
281
423
 
282
- /** True while a count, operator, `g`, or text-object prefix is half-typed — Escape cancels it. */
424
+ /** True while a count, operator, `g`, text-object, find, or `r` prefix is half-typed — Escape cancels it. */
283
425
  get pending(): boolean {
284
- return this.#count.length > 0 || this.#operator !== null || this.#pendingG || this.#textObject !== null;
426
+ return (
427
+ this.#count.length > 0 ||
428
+ this.#operator !== null ||
429
+ this.#pendingG ||
430
+ this.#textObject !== null ||
431
+ this.#findPending !== null ||
432
+ this.#replaceChar
433
+ );
285
434
  }
286
435
 
287
436
  /**
288
- * The half-typed command as Vim would echo it (`"2"`, `"d"`, `"2d"`, `"di"`) — empty when
289
- * nothing is pending. Hosts render this next to the mode so a partially entered operator is
437
+ * The half-typed command as Vim would echo it (`"2"`, `"d"`, `"2d"`, `"di"`, `"df"`, `"r"`) — empty
438
+ * when nothing is pending. Hosts render this next to the mode so a partially entered operator is
290
439
  * visible instead of silently swallowing the next keystroke.
291
440
  */
292
441
  get pendingText(): string {
293
- return `${this.#count}${this.#operator ?? ""}${this.#pendingG ? "g" : ""}${this.#textObject ?? ""}`;
442
+ return `${this.#count}${this.#operator ?? ""}${this.#pendingG ? "g" : ""}${this.#textObject ?? ""}${findPendingKey(this.#findPending)}${this.#replaceChar ? "r" : ""}`;
294
443
  }
295
444
 
296
445
  get visual(): boolean {
@@ -302,6 +451,7 @@ export class VimState {
302
451
  this.anchor = null;
303
452
  this.#desiredCol = null;
304
453
  this.#clearPending();
454
+ this.#lastFind = null;
305
455
  }
306
456
 
307
457
  #clearPending(): void {
@@ -309,6 +459,8 @@ export class VimState {
309
459
  this.#operator = null;
310
460
  this.#pendingG = false;
311
461
  this.#textObject = null;
462
+ this.#findPending = null;
463
+ this.#replaceChar = false;
312
464
  }
313
465
 
314
466
  #takeCount(): number {
@@ -334,6 +486,46 @@ export class VimState {
334
486
  handleKey(key: string, buf: VimBuffer): VimCommand[] | null {
335
487
  if (key === "escape") return this.#handleEscape(buf);
336
488
  if (this.mode === "insert") return null;
489
+ // The character after `f`/`t` is the target, including digits. Resolve it before the count prefix.
490
+ if (this.#findPending) {
491
+ const spec = this.#findPending;
492
+ this.#findPending = null;
493
+ this.#desiredCol = null;
494
+ this.#lastFind = { ...spec, target: key };
495
+ const count = this.#count.length > 0 ? Math.max(1, Number.parseInt(this.#count, 10)) : 1;
496
+ const motion = this.#seek(key, buf, spec, count);
497
+ if (!motion) {
498
+ this.#operator = null;
499
+ this.#count = "";
500
+ return [];
501
+ }
502
+ return this.#applyMotion(buf, motion);
503
+ }
504
+ if (this.#replaceChar) {
505
+ this.#replaceChar = false;
506
+ const count = this.#count.length > 0 ? Math.max(1, Number.parseInt(this.#count, 10)) : 1;
507
+ const line = buf.lines[buf.cursorLine] ?? "";
508
+ // One segmenter pass: stepping with `nextGraphemeStart` would rescan the line per grapheme.
509
+ let end = -1;
510
+ let taken = 0;
511
+ for (const seg of segmenter.segment(line)) {
512
+ if (seg.index < buf.cursorCol) continue;
513
+ if (++taken === count) {
514
+ end = seg.index + seg.segment.length;
515
+ break;
516
+ }
517
+ }
518
+ this.#count = "";
519
+ if (end < 0) return [];
520
+ return [
521
+ {
522
+ kind: "replace",
523
+ from: { line: buf.cursorLine, col: buf.cursorCol },
524
+ to: { line: buf.cursorLine, col: end },
525
+ text: key.repeat(count),
526
+ },
527
+ ];
528
+ }
337
529
 
338
530
  // Count prefix. `0` is the line-start motion unless it extends a count already being typed.
339
531
  if ((key >= "1" && key <= "9") || (key === "0" && this.#count.length > 0)) {
@@ -364,6 +556,21 @@ export class VimState {
364
556
  this.#textObject = key;
365
557
  return [];
366
558
  }
559
+ if (key === "f" || key === "F" || key === "t" || key === "T") {
560
+ this.#desiredCol = null;
561
+ this.#findPending = { forward: key === "f" || key === "t", till: key === "t" || key === "T" };
562
+ return [];
563
+ }
564
+ if ((key === "r" || key === "R") && this.#operator === null && !this.visual) {
565
+ this.#desiredCol = null;
566
+ if (key === "R") {
567
+ this.#takeCount();
568
+ this.mode = "replace";
569
+ return [{ kind: "mode", mode: "replace" }];
570
+ }
571
+ this.#replaceChar = true;
572
+ return [];
573
+ }
367
574
 
368
575
  // Only consecutive `j`/`k` carry the desired column; anything else re-anchors it. Counts and
369
576
  // the `g` prefix returned above, so `2j` still continues an established column.
@@ -388,7 +595,7 @@ export class VimState {
388
595
  { kind: "move", to: this.#clampNormal(buf, cursorOf(buf)) },
389
596
  ];
390
597
  }
391
- if (this.mode === "insert") {
598
+ if (this.mode === "insert" || this.mode === "replace") {
392
599
  this.mode = "normal";
393
600
  const text = buf.lines[buf.cursorLine] ?? "";
394
601
  return [
@@ -454,6 +661,31 @@ export class VimState {
454
661
  for (let i = 0; i < count; i++) col = wordEnd(line, col);
455
662
  return { to: at(col), inclusive: true, linewise: false };
456
663
  }
664
+ case "%": {
665
+ // `{count}%` jumps to that percentage of the buffer, linewise, like Vim.
666
+ if (this.#count.length > 0) {
667
+ if (count > 100) return null;
668
+ const target = Math.max(0, Math.floor((count * buf.lines.length + 99) / 100) - 1);
669
+ const text = buf.lines[target] ?? "";
670
+ return { to: { line: target, col: firstNonBlank(text) }, inclusive: false, linewise: true };
671
+ }
672
+ const to = matchDelimiter(buf);
673
+ return to === null ? null : { to, inclusive: true, linewise: false };
674
+ }
675
+ case "{":
676
+ case "}": {
677
+ let line = buf.cursorLine;
678
+ for (let n = 0; n < count; n++) {
679
+ const next = paragraphBoundary(buf.lines, line, key === "}");
680
+ if (next === line) break;
681
+ line = next;
682
+ }
683
+ const text = buf.lines[line] ?? "";
684
+ // No blank line ahead: Vim parks at the end of the buffer, not column 0. A bare move
685
+ // clamps back onto the last grapheme; an operator still covers the rest of the line.
686
+ const atBufferEnd = key === "}" && line === buf.lines.length - 1 && text.trim() !== "";
687
+ return { to: { line, col: atBufferEnd ? text.length : 0 }, inclusive: false, linewise: false };
688
+ }
457
689
  case "G": {
458
690
  const target = this.#count.length > 0 ? count - 1 : buf.lines.length - 1;
459
691
  return {
@@ -462,23 +694,66 @@ export class VimState {
462
694
  linewise: true,
463
695
  };
464
696
  }
697
+ case ";":
698
+ case ",":
699
+ return this.#repeatFind(key === ";", buf, count);
465
700
  default:
466
701
  return null;
467
702
  }
468
703
  }
469
704
 
705
+ #repeatFind(sameDirection: boolean, buf: VimBuffer, count: number): Motion | null {
706
+ const last = this.#lastFind;
707
+ if (!last) return null;
708
+ const forward = sameDirection ? last.forward : !last.forward;
709
+ const text = buf.lines[buf.cursorLine] ?? "";
710
+ // A till lands beside its target, so repeating from the cursor rediscovers that same target.
711
+ const fromCol = last.till
712
+ ? forward
713
+ ? nextGraphemeStart(text, buf.cursorCol)
714
+ : prevGraphemeStart(text, buf.cursorCol)
715
+ : buf.cursorCol;
716
+ return this.#seek(last.target, buf, { forward, till: last.till }, count, fromCol);
717
+ }
718
+
719
+ /**
720
+ * `f`/`F` land on the match and include it. `t`/`T` land on the adjacent grapheme and include
721
+ * that grapheme, so the operator stops short of the target. A till that would not move fails.
722
+ */
723
+ #seek(
724
+ target: string,
725
+ buf: VimBuffer,
726
+ spec: { forward: boolean; till: boolean },
727
+ count: number,
728
+ fromCol = buf.cursorCol,
729
+ ): Motion | null {
730
+ const text = buf.lines[buf.cursorLine] ?? "";
731
+ const match = findGrapheme(text, fromCol, target, spec.forward, count);
732
+ if (match === null) return null;
733
+ const land = spec.till ? (spec.forward ? prevGraphemeStart(text, match) : nextGraphemeStart(text, match)) : match;
734
+ if (land === buf.cursorCol) return null;
735
+ return { to: { line: buf.cursorLine, col: land }, inclusive: true, linewise: false };
736
+ }
737
+
470
738
  /** Turn a resolved motion into either a cursor move, a selection extension, or an operator range. */
471
739
  #applyMotion(buf: VimBuffer, motion: Motion): VimCommand[] {
472
740
  const operator = this.#operator;
473
741
  this.#operator = null;
474
742
  this.#takeCount();
743
+ if (operator === ">" || operator === "<") {
744
+ const fromLine = Math.min(buf.cursorLine, motion.to.line);
745
+ const toLine = Math.max(buf.cursorLine, motion.to.line);
746
+ return [{ kind: "indent", fromLine, toLine, out: operator === "<" }];
747
+ }
475
748
 
476
749
  if (operator === null) {
477
750
  // `$` parks on the last grapheme in Normal mode but must still be able to select the
478
- // final character in Visual mode, where the cursor is allowed one past it.
479
- const to = this.visual
480
- ? { line: motion.to.line, col: Math.min(motion.to.col, (buf.lines[motion.to.line] ?? "").length) }
481
- : this.#clampNormal(buf, motion.to);
751
+ // final character in Visual mode, where the cursor is allowed one past it. Replace mode
752
+ // may also sit past the end, so typing there appends instead of overwriting the last grapheme.
753
+ const to =
754
+ this.visual || this.mode === "replace"
755
+ ? { line: motion.to.line, col: Math.min(motion.to.col, (buf.lines[motion.to.line] ?? "").length) }
756
+ : this.#clampNormal(buf, motion.to);
482
757
  return [{ kind: "move", to }];
483
758
  }
484
759
 
@@ -494,6 +769,11 @@ export class VimState {
494
769
  }
495
770
 
496
771
  #operate(operator: VimOperator, from: VimPosition, to: VimPosition, linewise: boolean): VimCommand[] {
772
+ if (operator === ">" || operator === "<") {
773
+ const fromLine = Math.min(from.line, to.line);
774
+ const toLine = Math.max(from.line, to.line);
775
+ return [{ kind: "indent", fromLine, toLine, out: operator === "<" }];
776
+ }
497
777
  if (operator === "y") {
498
778
  return [
499
779
  { kind: "yank", from, to, linewise },
@@ -683,6 +963,28 @@ export class VimState {
683
963
  case "u":
684
964
  this.#takeCount();
685
965
  return [{ kind: "undo" }];
966
+ case "J": {
967
+ if (this.#operator !== null) {
968
+ this.#clearPending();
969
+ return [];
970
+ }
971
+ return [{ kind: "join", fromLine: buf.cursorLine, lines: Math.max(2, this.#takeCount()) }];
972
+ }
973
+ case ">":
974
+ case "<": {
975
+ if (this.#operator === key) {
976
+ const span = this.#takeCount();
977
+ const last = Math.min(buf.cursorLine + span - 1, buf.lines.length - 1);
978
+ this.#operator = null;
979
+ return [{ kind: "indent", fromLine: buf.cursorLine, toLine: last, out: key === "<" }];
980
+ }
981
+ if (this.#operator !== null) {
982
+ this.#clearPending();
983
+ return [];
984
+ }
985
+ this.#operator = key;
986
+ return [];
987
+ }
686
988
  default:
687
989
  // Normal mode swallows unknown printable keys rather than typing them into the buffer.
688
990
  this.#clearPending();
@@ -727,6 +1029,19 @@ export class VimState {
727
1029
  const commands = this.#operate(operator, from, to, linewise);
728
1030
  return operator === "c" ? commands : [...commands, { kind: "mode", mode: "normal" }];
729
1031
  }
1032
+ case ">":
1033
+ case "<":
1034
+ case "J": {
1035
+ const { from, to } = visualRange(buf, anchor, linewise);
1036
+ this.#clearPending();
1037
+ this.anchor = null;
1038
+ this.mode = "normal";
1039
+ const edit: VimCommand =
1040
+ key === "J"
1041
+ ? { kind: "join", fromLine: from.line, lines: Math.max(2, to.line - from.line + 1) }
1042
+ : { kind: "indent", fromLine: from.line, toLine: to.line, out: key === "<" };
1043
+ return [edit, { kind: "mode", mode: "normal" }];
1044
+ }
730
1045
  default:
731
1046
  this.#clearPending();
732
1047
  return [];
@@ -16,10 +16,26 @@
16
16
  * terminal sends; chords legacy encoding loses (modified Enter/Tab/Escape,
17
17
  * Ctrl+Shift+letter, …) translate to kitty CSI-u, which key matching already
18
18
  * parses.
19
+ *
20
+ * A host terminal that writes VT instead of key records (any ConPTY host
21
+ * besides Windows Terminal's native path) reaches us as one `Vk = 0` text
22
+ * record per byte: Alt+Up arrives as `ESC`, `[`, `1`, `;`, `3`, `A`. Those
23
+ * bytes are reassembled into the escape sequence they spell; delivering them
24
+ * one at a time turns every such key into a bare Escape plus typed garbage.
25
+ */
26
+ /**
27
+ * Stateful decoder: joins UTF-16 surrogate halves that arrive as separate
28
+ * records, and reassembles VT escape sequences relayed as text records.
19
29
  */
20
- /** Stateful decoder: joins UTF-16 surrogate halves that arrive as separate records. */
21
30
  export declare class Win32InputModeDecoder {
22
31
  #private;
32
+ /** Whether relayed VT text is held waiting for the rest of its sequence. */
33
+ get hasPendingSequence(): boolean;
34
+ /**
35
+ * Release held VT text as-is. The caller invokes this once no further bytes
36
+ * arrived in time, so a lone relayed ESC still acts as the Escape key.
37
+ */
38
+ flushPendingSequence(): string[];
23
39
  /**
24
40
  * Translate one stdin sequence. Returns `undefined` when `data` is not a
25
41
  * win32-input-mode record (pass it through unchanged), otherwise the key
@@ -35,3 +51,19 @@ export declare class Win32InputModeDecoder {
35
51
  */
36
52
  decodePaste(content: string): string;
37
53
  }
54
+ /**
55
+ * Restores paste delimiters that a Windows console host emitted as individual
56
+ * win32-input-mode key records before StdinBuffer searches for the delimiters.
57
+ * Non-matching records remain untouched for normal key and paste decoding.
58
+ */
59
+ export declare class Win32PasteMarkerNormalizer {
60
+ #private;
61
+ /**
62
+ * `onExpired` runs after held records are released because their wait timed
63
+ * out, so a downstream holder can treat the same wait as already elapsed.
64
+ */
65
+ constructor(onInput: (data: string) => void, onExpired?: () => void);
66
+ process(data: string): void;
67
+ /** Release an incomplete marker as its original key records. */
68
+ flush(): void;
69
+ }