@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
@@ -13,13 +13,15 @@
13
13
  * Focus regions (`toc`/`body`/`actions`) cycle with Tab/Shift+Tab; arrows move
14
14
  * within the focused region and step left into the sidebar. The default focus is
15
15
  * `actions`, so the muscle memory of the old single-target overlay carries over:
16
- * ↑/↓ select options, Enter confirms, ←/→ drives the slider when there is no
17
- * sidebar, g/G + PgUp/PgDn scroll, and the external-editor key opens the plan.
16
+ * ↑/↓ select options, Enter confirms, ←/→ drives the slider, g/G + PgUp/PgDn
17
+ * scroll, and the external-editor key opens the plan. Under Tern the options
18
+ * are a horizontal bar, so ←/→ select options and Shift+←/→ drive the slider.
18
19
  */
19
20
  import {
20
21
  type Component,
21
22
  Ellipsis,
22
23
  Editor,
24
+ getWidthConfigEpoch,
23
25
  Markdown,
24
26
  type MarkdownTheme,
25
27
  matchesKey,
@@ -31,7 +33,7 @@ import {
31
33
  } from "../index";
32
34
  import { sanitizeText } from "@linxiraos/pi-utils";
33
35
  import { sanitizeStatusText } from "../chrome/shared";
34
- import { getEditorTheme, getMarkdownTheme, theme } from "../theme/theme";
36
+ import { getEditorTheme, getMarkdownTheme, getThemeEpoch, theme } from "../theme/theme";
35
37
  import {
36
38
  matchesAppExternalEditor,
37
39
  matchesSelectCancel,
@@ -58,6 +60,7 @@ import type { KeyName } from "../key-hint-format";
58
60
  import { col, item, keyed, md, node, row as rowNode, span, text } from "../native/describe";
59
61
  import { leafKey, type NativeChild, type NativeNode, type NativeUiEvent } from "../native/node";
60
62
  import { actionButton, actionHint, hintsRow, itemIndex, type NativeHint, selectList } from "../native/overlay";
63
+ import { isNativeRendering } from "../native/state";
61
64
  import { getKeybindings } from "../keybindings";
62
65
 
63
66
  /** Title shown in the overlay's top border. */
@@ -74,6 +77,8 @@ const SIDEBAR_MIN_TOTAL_WIDTH = 64;
74
77
  const SIDEBAR_MIN_BODY_WIDTH = 40;
75
78
  /** Persisted line-context cap; render-time captions clamp again to the viewport. */
76
79
  const MAX_ANNOTATION_CONTEXT_WIDTH = 120;
80
+ /** Oldest delete/annotate undo snapshots are dropped past this depth (each holds the full plan text). */
81
+ const MAX_UNDO_ENTRIES = 100;
77
82
 
78
83
  type Focus = "toc" | "body" | "actions";
79
84
 
@@ -146,6 +151,78 @@ interface UndoEntry {
146
151
  deleted: string[];
147
152
  }
148
153
 
154
+ /** Concatenated plan body rows and their anchors, reused while every render input is unchanged. */
155
+ interface BodyLayout {
156
+ width: number;
157
+ annotationRev: number;
158
+ themeEpoch: number;
159
+ widthEpoch: number;
160
+ /** Each section's `Markdown.render` result the layout was built from (compared by identity). */
161
+ rendered: readonly (readonly string[])[];
162
+ lines: readonly string[];
163
+ anchors: BodyRowAnchor[];
164
+ offsets: number[];
165
+ }
166
+
167
+ /** Line contexts per rendered-lines array; `Markdown.render` returns a stable array per (text, width). */
168
+ const lineContextCache = new WeakMap<readonly string[], readonly LineAnchorContext[]>();
169
+ /** Match keys per context list, so resolving several annotations normalizes each row once. */
170
+ const normalizedContextCache = new WeakMap<readonly LineAnchorContext[], readonly string[]>();
171
+
172
+ function lineContexts(rendered: readonly string[]): readonly LineAnchorContext[] {
173
+ let contexts = lineContextCache.get(rendered);
174
+ if (!contexts) {
175
+ contexts = rendered.map(line => {
176
+ const sanitized = sanitizeStatusText(line);
177
+ const truncated = visibleWidth(sanitized) > MAX_ANNOTATION_CONTEXT_WIDTH;
178
+ const text = truncateToWidth(sanitized, MAX_ANNOTATION_CONTEXT_WIDTH, Ellipsis.Unicode);
179
+ return { text: text || "(blank line)", truncated };
180
+ });
181
+ lineContextCache.set(rendered, contexts);
182
+ }
183
+ return contexts;
184
+ }
185
+
186
+ function normalizeLineContext(context: LineAnchorContext): string {
187
+ const normalized = sanitizeStatusText(context.text).replace(/\s+/g, " ").trim();
188
+ return context.truncated && normalized.endsWith("…") ? normalized.slice(0, -1) : normalized;
189
+ }
190
+
191
+ /** Row in `contexts` nearest `storedRow` whose text matches the stored context, or -1. */
192
+ function resolveLineRow(
193
+ storedRow: number,
194
+ storedContext: LineAnchorContext,
195
+ contexts: readonly LineAnchorContext[],
196
+ ): number {
197
+ if (contexts.length === 0) return -1;
198
+ const targetRow = Math.max(0, Math.floor(storedRow));
199
+ const normalizedStoredContext = normalizeLineContext(storedContext);
200
+ if (!normalizedStoredContext) return -1;
201
+ let normalizedContexts = normalizedContextCache.get(contexts);
202
+ if (!normalizedContexts) {
203
+ normalizedContexts = contexts.map(normalizeLineContext);
204
+ normalizedContextCache.set(contexts, normalizedContexts);
205
+ }
206
+ let best = -1;
207
+ let bestDistance = Number.POSITIVE_INFINITY;
208
+ for (let row = 0; row < normalizedContexts.length; row++) {
209
+ const normalizedContext = normalizedContexts[row]!;
210
+ if (
211
+ normalizedContext !== normalizedStoredContext &&
212
+ !normalizedContext.includes(normalizedStoredContext) &&
213
+ !normalizedStoredContext.includes(normalizedContext)
214
+ ) {
215
+ continue;
216
+ }
217
+ const distance = Math.abs(row - targetRow);
218
+ if (distance < bestDistance) {
219
+ best = row;
220
+ bestDistance = distance;
221
+ }
222
+ }
223
+ return best;
224
+ }
225
+
149
226
  export interface PlanReviewOverlayCallbacks {
150
227
  /** Invoked with the chosen option label (never a disabled one). */
151
228
  onPick: (label: string) => void;
@@ -197,6 +274,10 @@ export class PlanReviewOverlay implements Component {
197
274
  #sectionOffsets: number[] = [];
198
275
  /** Rendered body row to underlying plan row; callouts retain their owner's anchor. */
199
276
  #bodyRowAnchors: BodyRowAnchor[] = [];
277
+ /** Memoized {@link #buildBody} result. */
278
+ #bodyLayout: BodyLayout | undefined;
279
+ /** Lines last handed to the scroll view; unchanged layouts skip the copy and keep its render cache. */
280
+ #scrollLines: readonly string[] | undefined;
200
281
  #undo: UndoEntry[] = [];
201
282
  /** Titles of sections deleted in the overlay, surfaced as Refine feedback. */
202
283
  #deleted: string[] = [];
@@ -249,6 +330,11 @@ export class PlanReviewOverlay implements Component {
249
330
  #nativeRoot: NativeNode | undefined;
250
331
  /** Content the memoized root was built from. */
251
332
  #nativeRootContent: NativePlanContent | undefined;
333
+ /**
334
+ * The section the last Contents jump targets under a native surface, which
335
+ * scrolls the body itself: `n` counts the jumps, so each one reveals it again.
336
+ */
337
+ #jump: { section: number; n: number } | undefined;
252
338
 
253
339
  constructor(
254
340
  planContent: string,
@@ -437,8 +523,8 @@ export class PlanReviewOverlay implements Component {
437
523
  ) {
438
524
  continue;
439
525
  }
440
- const contexts = section.md.render(MAX_ANNOTATION_CONTEXT_WIDTH).map(line => this.#lineContext(line));
441
- const row = this.#resolveLineRow(
526
+ const contexts = lineContexts(section.md.render(MAX_ANNOTATION_CONTEXT_WIDTH));
527
+ const row = resolveLineRow(
442
528
  entry.target.row,
443
529
  { text: entry.target.context, truncated: entry.target.contextTruncated === true },
444
530
  contexts,
@@ -661,6 +747,10 @@ export class PlanReviewOverlay implements Component {
661
747
  }
662
748
 
663
749
  #handleActions(data: string): void {
750
+ if (isNativeRendering()) {
751
+ this.#handleNativeActions(data);
752
+ return;
753
+ }
664
754
  // Left/right always drive the slider. The sidebar sits beside the body
665
755
  // (above this row), not the slider, so stealing left for it would strand
666
756
  // the operator unable to step the model tier back — reach the ToC via Tab.
@@ -690,6 +780,37 @@ export class PlanReviewOverlay implements Component {
690
780
  this.#handleBodyScroll(data);
691
781
  }
692
782
 
783
+ /** Tern lays the options out as a horizontal decision bar: ←/→ step through
784
+ * it, ↑ returns to the body above, and the model slider moves to Shift+←/→. */
785
+ #handleNativeActions(data: string): void {
786
+ if (matchesKey(data, "shift+left")) {
787
+ this.#moveSlider(-1);
788
+ return;
789
+ }
790
+ if (matchesKey(data, "shift+right")) {
791
+ this.#moveSlider(1);
792
+ return;
793
+ }
794
+ if (matchesKey(data, "left") || matchesKey(data, "h")) {
795
+ this.#moveSelection(-1);
796
+ return;
797
+ }
798
+ if (matchesKey(data, "right") || matchesKey(data, "l")) {
799
+ this.#moveSelection(1);
800
+ return;
801
+ }
802
+ if (matchesSelectUp(data) || matchesKey(data, "k")) {
803
+ this.#setFocus("body");
804
+ return;
805
+ }
806
+ if (matchesSelectDown(data) || matchesKey(data, "j")) return;
807
+ if (matchesKey(data, "enter") || matchesKey(data, "return") || data === "\n") {
808
+ this.#confirmSelection();
809
+ return;
810
+ }
811
+ this.#handleBodyScroll(data);
812
+ }
813
+
693
814
  #handleBody(data: string): void {
694
815
  if (data === "a") {
695
816
  this.#startBodyAnnotate();
@@ -798,6 +919,7 @@ export class PlanReviewOverlay implements Component {
798
919
  #scrubBodyToToc(): void {
799
920
  const sectionIndex = this.#toc[this.#tocCursor];
800
921
  if (sectionIndex === undefined) return;
922
+ this.#jump = { section: sectionIndex, n: (this.#jump?.n ?? 0) + 1 };
801
923
  const offset = this.#sectionOffsets[sectionIndex];
802
924
  if (offset !== undefined) {
803
925
  this.#scrollView.setScrollOffset(offset);
@@ -823,6 +945,7 @@ export class PlanReviewOverlay implements Component {
823
945
  }
824
946
 
825
947
  #pushUndo(): void {
948
+ if (this.#undo.length >= MAX_UNDO_ENTRIES) this.#undo.shift();
826
949
  this.#undo.push({
827
950
  text: joinPlanSections(this.#sections),
828
951
  annotations: this.#sections.map(section =>
@@ -916,7 +1039,7 @@ export class PlanReviewOverlay implements Component {
916
1039
  seenRows.add(candidate.row);
917
1040
  sectionAnchors.push({ text: candidate.context, truncated: candidate.contextTruncated });
918
1041
  }
919
- const resolvedRow = this.#resolveLineRow(
1042
+ const resolvedRow = resolveLineRow(
920
1043
  annotation.target.row,
921
1044
  { text: annotation.target.context, truncated: annotation.target.contextTruncated },
922
1045
  sectionAnchors,
@@ -1144,23 +1267,64 @@ export class PlanReviewOverlay implements Component {
1144
1267
 
1145
1268
  #layoutBody(lines: readonly string[], height: number): void {
1146
1269
  this.#captureScrollProgress();
1147
- this.#scrollView.setLines(lines);
1270
+ if (lines !== this.#scrollLines) {
1271
+ this.#scrollView.setLines(lines);
1272
+ this.#scrollLines = lines;
1273
+ }
1148
1274
  this.#scrollView.setHeight(height);
1149
1275
  const maxOffset = this.#scrollView.getMaxScrollOffset();
1150
1276
  if (maxOffset > 0) this.#scrollView.setScrollOffset(Math.round(this.#scrollProgress * maxOffset));
1151
1277
  }
1152
1278
 
1153
- /** Build the concatenated body lines and map each rendered row back to a plan anchor. */
1154
- #buildBody(bodyContentWidth: number): string[] {
1279
+ /**
1280
+ * Build the concatenated body lines and map each rendered row back to a plan
1281
+ * anchor. Memoized on width, annotation revision, theme/width epochs and each
1282
+ * section's rendered-lines identity, so steady frames and scrolling reuse it.
1283
+ */
1284
+ #buildBody(bodyContentWidth: number): readonly string[] {
1285
+ const themeEpoch = getThemeEpoch();
1286
+ const widthEpoch = getWidthConfigEpoch();
1287
+ const cached = this.#bodyLayout;
1288
+ if (
1289
+ cached?.width === bodyContentWidth &&
1290
+ cached.annotationRev === this.#annotationRev &&
1291
+ cached.themeEpoch === themeEpoch &&
1292
+ cached.widthEpoch === widthEpoch &&
1293
+ cached.rendered.length === this.#sections.length &&
1294
+ this.#sections.every((section, i) => section.md.render(bodyContentWidth) === cached.rendered[i])
1295
+ ) {
1296
+ this.#sectionOffsets = cached.offsets;
1297
+ this.#bodyRowAnchors = cached.anchors;
1298
+ return cached.lines;
1299
+ }
1155
1300
  const lines: string[] = [];
1156
1301
  const anchors: BodyRowAnchor[] = [];
1302
+ const renderedSections: (readonly string[])[] = [];
1157
1303
  // oxlint-disable-next-line unicorn/no-new-array -- length preallocation
1158
1304
  const offsets: number[] = new Array(this.#sections.length);
1159
1305
  for (let sectionIndex = 0; sectionIndex < this.#sections.length; sectionIndex++) {
1160
1306
  const section = this.#sections[sectionIndex]!;
1161
1307
  offsets[sectionIndex] = lines.length;
1162
1308
  const rendered = section.md.render(bodyContentWidth);
1163
- const contexts = rendered.map(line => this.#lineContext(line));
1309
+ renderedSections.push(rendered);
1310
+ const contexts = lineContexts(rendered);
1311
+ // Resolve each note's row once; notes on one row keep their array order.
1312
+ let notesByRow: Map<number, string[]> | undefined;
1313
+ for (const annotation of section.annotations) {
1314
+ const annotationRow =
1315
+ annotation.target.kind === "section"
1316
+ ? 0
1317
+ : resolveLineRow(
1318
+ annotation.target.row,
1319
+ { text: annotation.target.context, truncated: annotation.target.contextTruncated },
1320
+ contexts,
1321
+ );
1322
+ if (annotationRow < 0) continue;
1323
+ notesByRow ??= new Map();
1324
+ const notes = notesByRow.get(annotationRow);
1325
+ if (notes) notes.push(annotation.note);
1326
+ else notesByRow.set(annotationRow, [annotation.note]);
1327
+ }
1164
1328
  for (let row = 0; row < rendered.length; row++) {
1165
1329
  const context = contexts[row]!;
1166
1330
  const anchor = {
@@ -1171,67 +1335,24 @@ export class PlanReviewOverlay implements Component {
1171
1335
  };
1172
1336
  lines.push(rendered[row]!);
1173
1337
  anchors.push(anchor);
1174
- for (const annotation of section.annotations) {
1175
- const annotationRow =
1176
- annotation.target.kind === "section"
1177
- ? 0
1178
- : this.#resolveLineRow(
1179
- annotation.target.row,
1180
- {
1181
- text: annotation.target.context,
1182
- truncated: annotation.target.contextTruncated,
1183
- },
1184
- contexts,
1185
- );
1186
- if (annotationRow === row) {
1187
- this.#appendAnnotationCallout(lines, anchors, annotation.note, anchor, bodyContentWidth);
1188
- }
1189
- }
1338
+ const notes = notesByRow?.get(row);
1339
+ if (!notes) continue;
1340
+ for (const note of notes) this.#appendAnnotationCallout(lines, anchors, note, anchor, bodyContentWidth);
1190
1341
  }
1191
1342
  }
1192
1343
  this.#sectionOffsets = offsets;
1193
1344
  this.#bodyRowAnchors = anchors;
1194
- return lines;
1195
- }
1196
-
1197
- #lineContext(line: string): LineAnchorContext {
1198
- const sanitized = sanitizeStatusText(line);
1199
- const truncated = visibleWidth(sanitized) > MAX_ANNOTATION_CONTEXT_WIDTH;
1200
- const text = truncateToWidth(sanitized, MAX_ANNOTATION_CONTEXT_WIDTH, Ellipsis.Unicode);
1201
- return { text: text || "(blank line)", truncated };
1202
- }
1203
-
1204
- #resolveLineRow(
1205
- storedRow: number,
1206
- storedContext: LineAnchorContext,
1207
- contexts: readonly LineAnchorContext[],
1208
- ): number {
1209
- if (contexts.length === 0) return -1;
1210
- const targetRow = Math.max(0, Math.floor(storedRow));
1211
- const normalize = (context: LineAnchorContext): string => {
1212
- const normalized = sanitizeStatusText(context.text).replace(/\s+/g, " ").trim();
1213
- return context.truncated && normalized.endsWith("…") ? normalized.slice(0, -1) : normalized;
1345
+ this.#bodyLayout = {
1346
+ width: bodyContentWidth,
1347
+ annotationRev: this.#annotationRev,
1348
+ themeEpoch,
1349
+ widthEpoch,
1350
+ rendered: renderedSections,
1351
+ lines,
1352
+ anchors,
1353
+ offsets,
1214
1354
  };
1215
- const normalizedStoredContext = normalize(storedContext);
1216
- if (!normalizedStoredContext) return -1;
1217
- let best = -1;
1218
- let bestDistance = Number.POSITIVE_INFINITY;
1219
- for (let row = 0; row < contexts.length; row++) {
1220
- const normalizedContext = normalize(contexts[row]!);
1221
- if (
1222
- normalizedContext !== normalizedStoredContext &&
1223
- !normalizedContext.includes(normalizedStoredContext) &&
1224
- !normalizedStoredContext.includes(normalizedContext)
1225
- ) {
1226
- continue;
1227
- }
1228
- const distance = Math.abs(row - targetRow);
1229
- if (distance < bestDistance) {
1230
- best = row;
1231
- bestDistance = distance;
1232
- }
1233
- }
1234
- return best;
1355
+ return lines;
1235
1356
  }
1236
1357
 
1237
1358
  #appendAnnotationCallout(
@@ -1404,6 +1525,7 @@ export class PlanReviewOverlay implements Component {
1404
1525
  const sig = [
1405
1526
  this.#focus,
1406
1527
  this.#tocCursor,
1528
+ this.#jump?.n ?? 0,
1407
1529
  this.#selectedIndex,
1408
1530
  this.#sliderIndex,
1409
1531
  this.#committed ? `committed:${this.#committedLabel ?? ""}` : "",
@@ -1419,6 +1541,13 @@ export class PlanReviewOverlay implements Component {
1419
1541
  const children: NativeChild[] = [];
1420
1542
  const tools = this.#describeTools();
1421
1543
  if (tools) children.push(tools);
1544
+ // The section a Contents jump targets scrolls to the body's top.
1545
+ const jump = this.#jump;
1546
+ const body = jump
1547
+ ? content.body.map((section): NativeNode =>
1548
+ section.key === `s${jump.section}` ? { ...section, reveal: { at: "start", n: jump.n } } : section,
1549
+ )
1550
+ : content.body;
1422
1551
  const bodyCol = node(
1423
1552
  "col",
1424
1553
  { role: "zeta.plan.body", grow: 1, gap: "md", tone: this.#focus === "body" ? "accent" : undefined },
@@ -1478,7 +1607,7 @@ export class PlanReviewOverlay implements Component {
1478
1607
  return this.#nativeRoot;
1479
1608
  }
1480
1609
 
1481
- /** Head tools: Copy `c` and the external editor, mirroring their keys. */
1610
+ /** Head tools: Copy `c`, the external editor and Cancel, mirroring their keys. */
1482
1611
  #describeTools(): NativeNode | undefined {
1483
1612
  if (this.#committed) return undefined;
1484
1613
  const buttons: NativeNode[] = [];
@@ -1489,6 +1618,12 @@ export class PlanReviewOverlay implements Component {
1489
1618
  if (this.callbacks.onExternalEditor) {
1490
1619
  buttons.push(actionButton("Edit in $EDITOR", "externalEditor", editorKeyId ? { keys: editorKeyId } : {}));
1491
1620
  }
1621
+ // Cancel the review the way the cancel key does; while annotating that key
1622
+ // only leaves the draft/chooser, so the button is hidden there.
1623
+ if (!this.#annotating && !this.#annotationChooser) {
1624
+ const cancelKeyId = getKeybindings().getKeys("tui.select.cancel")[0];
1625
+ buttons.push(actionButton("Cancel", "cancel", cancelKeyId ? { keys: cancelKeyId } : {}));
1626
+ }
1492
1627
  if (buttons.length === 0) return undefined;
1493
1628
  return node("row", { role: "zeta.plan.tools", gap: "sm", align: "center", justify: "end" }, buttons, "tools");
1494
1629
  }
@@ -1496,12 +1631,15 @@ export class PlanReviewOverlay implements Component {
1496
1631
  handleNativeEvent(event: NativeUiEvent): void {
1497
1632
  if (this.#committed) return;
1498
1633
  if (event.type === "action") {
1499
- // Head tools run exactly what `c` and the external-editor key run.
1634
+ // Head tools run exactly what `c`, the external-editor key and Esc run.
1500
1635
  if (this.#annotating || this.#annotationChooser) return;
1501
1636
  if (event.act === "copyPlan" && this.callbacks.onCopyPlan) {
1502
1637
  void this.callbacks.onCopyPlan(joinPlanSections(this.#sections));
1503
1638
  } else if (event.act === "externalEditor") {
1504
1639
  this.callbacks.onExternalEditor?.();
1640
+ } else if (event.act === "cancel") {
1641
+ this.#committed = true;
1642
+ this.callbacks.onCancel();
1505
1643
  }
1506
1644
  return;
1507
1645
  }
@@ -1672,8 +1810,8 @@ export class PlanReviewOverlay implements Component {
1672
1810
  const hints: (NativeHint | undefined)[] = [];
1673
1811
  switch (this.#focus) {
1674
1812
  case "actions":
1675
- hints.push(upDown("select"), key("enter", "confirm"));
1676
- if (this.#slider) hints.push(key(["left", "right"], "model"));
1813
+ hints.push(key(["left", "right"], "select"), key("enter", "confirm"));
1814
+ if (this.#slider) hints.push(key(["shift+left", "shift+right"], "model"));
1677
1815
  break;
1678
1816
  case "toc":
1679
1817
  hints.push(
@@ -378,7 +378,7 @@ export class PluginListComponent extends OverlayPanel {
378
378
  lead:
379
379
  rows.length > 0
380
380
  ? "Plugins installed for you and this project. Configure one to turn it or its features on and off."
381
- : "No plugins installed. Install one with omp plugin install <package>, or <name>@<marketplace>.",
381
+ : "No plugins installed. Install one with zetacode plugin install <package>, or <name>@<marketplace>.",
382
382
  sections: rows.length > 0 ? [{ id: "installed", title: "Installed", rows }] : [],
383
383
  focus: this.#selectList.getSelectedItem()?.value ?? null,
384
384
  editing: null,
@@ -403,10 +403,10 @@ export class PluginListComponent extends OverlayPanel {
403
403
  text([span("No plugins installed", "muted")]),
404
404
  node("kv", {
405
405
  items: [
406
- { k: "Install npm plugins", v: [span("omp plugin install <package>", "code")] },
406
+ { k: "Install npm plugins", v: [span("zetacode plugin install <package>", "code")] },
407
407
  {
408
408
  k: "Install marketplace plugins",
409
- v: [span("omp plugin install <name>@<marketplace>", "code")],
409
+ v: [span("zetacode plugin install <name>@<marketplace>", "code")],
410
410
  },
411
411
  ],
412
412
  }),
@@ -0,0 +1,191 @@
1
+ import type { TspScrollBy, TspText } from "@linxiraos/pi-wire";
2
+ import { OverlayPanel } from "../chrome/overlay-box";
3
+ import { editorKey } from "../chrome/keybinding-hints";
4
+ import { Ellipsis } from "../index";
5
+ import { ScrollView } from "../components/scroll-view";
6
+ import { Spacer } from "../components/spacer";
7
+ import { Text } from "../components/text";
8
+ import {
9
+ matchesSelectCancel,
10
+ matchesSelectDown,
11
+ matchesSelectPageDown,
12
+ matchesSelectPageUp,
13
+ matchesSelectUp,
14
+ } from "../keybinding-matchers";
15
+ import { matchesKey } from "../keys";
16
+ import { routeSgrMouseInput } from "../mouse";
17
+ import { actionBar, actionButton } from "../native/overlay";
18
+ import { col } from "../native/describe";
19
+ import type { NativeNode, NativeScroll, NativeUiEvent } from "../native/node";
20
+ import { theme } from "../theme/theme";
21
+ import type { Component } from "../tui";
22
+
23
+ /** Options of a {@link ReportPanel}. */
24
+ export interface ReportPanelOptions {
25
+ /** Plain title: the text-mode border title, and the native sheet head unless {@link head} is given. */
26
+ readonly title: string;
27
+ /** Styled native sheet head. */
28
+ readonly head?: TspText;
29
+ /** The read-only report; it draws no frame or title of its own. */
30
+ readonly body: Component;
31
+ /**
32
+ * Display form of the key that dismisses the report while it sits above the
33
+ * editor (the editor's interrupt key, which routes to it). Once the panel
34
+ * holds focus ({@link ReportPanel.holdFocus}) the hint shows the key its own
35
+ * input closes on instead.
36
+ */
37
+ readonly closeKey: string;
38
+ /** The focused panel (native sheet, text-mode full-screen page) closes on Esc / its Close button: this runs. */
39
+ readonly onClose: () => void;
40
+ /**
41
+ * Most rows the text-mode panel may take, borders included (the screen rows
42
+ * left above the editor); a taller report scrolls inside it. `undefined`
43
+ * leaves it uncapped.
44
+ */
45
+ readonly maxRows?: () => number | undefined;
46
+ }
47
+
48
+ /** Text-mode rows around the scrolled body: the borders, two spacers and the footer. */
49
+ const TEXT_CHROME_ROWS = 5;
50
+
51
+ /**
52
+ * A read-only command report (`/changelog`, `/context`, `/tools`, …).
53
+ *
54
+ * Text mode draws a titled box with the body and an Esc hint: above the editor
55
+ * like the `/btw` panel while it fits there (the editor keeps focus and its
56
+ * Esc takes it away), or as a full-screen page on the alternate screen when it
57
+ * is taller, whose body scrolls on the arrow/page/Home/End keys and the wheel.
58
+ * Natively it is a sheet like `/usage`: a centred `lg` glass sheet titled by
59
+ * the report whose body the terminal scrolls (wheel, and the same keys
60
+ * forwarded as scroll requests), then a Close button; Esc closes it.
61
+ */
62
+ export class ReportPanel extends OverlayPanel {
63
+ readonly nativeOverlay: NonNullable<Component["nativeOverlay"]>;
64
+ readonly #body: Component;
65
+ readonly #onClose: () => void;
66
+ readonly #maxRows: (() => number | undefined) | undefined;
67
+ readonly #view: ScrollView;
68
+ readonly #footer: Text;
69
+ #closeHint: string;
70
+ #bodyRows = 0;
71
+ #scroll: NativeScroll | undefined;
72
+ #native: { scroll: NativeScroll | undefined; node: NativeNode } | undefined;
73
+
74
+ constructor(options: ReportPanelOptions) {
75
+ super(options.title, "zeta.overlay.report");
76
+ this.nativeOverlay = {
77
+ role: "zeta.overlay.report",
78
+ size: "lg",
79
+ anchor: "center",
80
+ head: options.head ?? options.title,
81
+ };
82
+ this.#body = options.body;
83
+ this.#onClose = options.onClose;
84
+ this.#maxRows = options.maxRows;
85
+ this.#closeHint = `${options.closeKey} to close`;
86
+ this.#view = new ScrollView(options.body, {
87
+ height: 0,
88
+ scrollbar: "auto",
89
+ ellipsis: Ellipsis.Omit,
90
+ theme: { track: text => theme.fg("dim", text), thumb: text => theme.fg("accent", text) },
91
+ });
92
+ this.#footer = new Text(theme.fg("muted", this.#closeHint), 0, 0);
93
+ this.addChild(new Spacer(1));
94
+ this.addChild(this.#view);
95
+ this.addChild(new Spacer(1));
96
+ this.addChild(this.#footer);
97
+ }
98
+
99
+ /**
100
+ * The panel is shown with focus (native sheet, text-mode full-screen page):
101
+ * its own {@link handleInput} closes it, so the hint names that key.
102
+ */
103
+ holdFocus(): void {
104
+ this.#closeHint = `${editorKey("tui.select.cancel")} to close`;
105
+ }
106
+
107
+ /**
108
+ * Text mode: cap the box to {@link ReportPanelOptions.maxRows}, scrolling a
109
+ * taller body. The cap is a hard limit: when a resize leaves fewer rows than
110
+ * the box's chrome needs, its bottom (the hint and closing border) is cut
111
+ * rather than spilling over the editor.
112
+ */
113
+ override render(width: number): readonly string[] {
114
+ const innerWidth = Math.max(1, width - 4);
115
+ const rows = this.#body.render(innerWidth).length;
116
+ const cap = this.#maxRows?.();
117
+ const bodyMax = cap === undefined ? rows : Math.max(1, cap - TEXT_CHROME_ROWS);
118
+ const overflows = rows > bodyMax;
119
+ this.#bodyRows = Math.min(rows, bodyMax);
120
+ this.#view.setHeight(this.#bodyRows);
121
+ const scrollHint = `${editorKey("tui.select.pageUp")}/${editorKey("tui.select.pageDown")} scroll · `;
122
+ this.#footer.setText(theme.fg("muted", overflows ? scrollHint + this.#closeHint : this.#closeHint));
123
+ const lines = super.render(width);
124
+ return cap !== undefined && lines.length > cap ? lines.slice(0, Math.max(0, cap)) : lines;
125
+ }
126
+
127
+ /** Text mode: rows the whole box takes at `width` when nothing caps it. */
128
+ heightAt(width: number): number {
129
+ return this.#body.render(Math.max(1, width - 4)).length + TEXT_CHROME_ROWS;
130
+ }
131
+
132
+ /** The sheet body (the overlay is the frame, and its body the scroller): the report, then Close (Esc). */
133
+ override describe(): NativeNode {
134
+ const scroll = this.#scroll;
135
+ if (this.#native && this.#native.scroll === scroll) return this.#native.node;
136
+ const body: NativeNode = { ...col([this.#body]), key: "body", scroll };
137
+ const node = col([body, actionBar([null, actionButton("Close", "close", { keys: "escape" })])], { gap: "md" });
138
+ this.#native = { scroll, node };
139
+ return node;
140
+ }
141
+
142
+ override invalidate(): void {
143
+ this.#native = undefined;
144
+ super.invalidate();
145
+ }
146
+
147
+ /**
148
+ * Focused (the native sheet, or the text-mode full-screen page): Esc closes;
149
+ * the arrow, page, Home and End keys and the wheel scroll the body.
150
+ */
151
+ handleInput(data: string): void {
152
+ if (
153
+ routeSgrMouseInput(data, event => {
154
+ if (event.wheel === null) return false;
155
+ this.#view.scroll(event.wheel * 3);
156
+ return true;
157
+ })
158
+ ) {
159
+ return;
160
+ }
161
+ if (matchesSelectCancel(data)) {
162
+ this.#onClose();
163
+ return;
164
+ }
165
+ const by: TspScrollBy | undefined = matchesSelectUp(data)
166
+ ? "line-up"
167
+ : matchesSelectDown(data)
168
+ ? "line-down"
169
+ : matchesSelectPageUp(data)
170
+ ? "page-up"
171
+ : matchesSelectPageDown(data)
172
+ ? "page-down"
173
+ : matchesKey(data, "home")
174
+ ? "start"
175
+ : matchesKey(data, "end")
176
+ ? "end"
177
+ : undefined;
178
+ if (by === undefined) return;
179
+ // Natively the terminal scrolls the sheet body; text mode scrolls the box.
180
+ this.#scroll = { by, n: (this.#scroll?.n ?? 0) + 1 };
181
+ const page = Math.max(1, this.#bodyRows - 1);
182
+ if (by === "start") this.#view.scrollToTop();
183
+ else if (by === "end") this.#view.scrollToBottom();
184
+ else this.#view.scroll(by === "line-up" ? -1 : by === "line-down" ? 1 : by === "page-up" ? -page : page);
185
+ }
186
+
187
+ /** Close runs what Esc runs. */
188
+ handleNativeEvent(event: NativeUiEvent): void {
189
+ if (event.type === "action" && event.act === "close") this.#onClose();
190
+ }
191
+ }