rovecode 0.3.3 → 0.4.0-beta.3

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 (495) hide show
  1. package/CHANGELOG.md +527 -0
  2. package/README.md +46 -48
  3. package/bin/rovecode.js +24 -0
  4. package/dist/cli/app-j6gn14w3.js +2 -0
  5. package/dist/cli/ask-user-cwstt8fz.js +2 -0
  6. package/dist/cli/auth-login-9bbp9915.js +2 -0
  7. package/dist/cli/auth-m8p9grty.js +2 -0
  8. package/dist/cli/bench-16zqdms5.js +9 -0
  9. package/dist/cli/catalog-1xchffa4.js +2 -0
  10. package/dist/cli/cli-1n1zb64f.js +2 -0
  11. package/dist/cli/client-2t9gjkck.js +2 -0
  12. package/dist/cli/commands-exafvm2b.js +2 -0
  13. package/dist/cli/connect-6zde0kn3.js +2 -0
  14. package/dist/cli/context-cmd-5t43wgqt.js +2 -0
  15. package/dist/cli/context-report-kt01pw8y.js +2 -0
  16. package/dist/cli/count-remote-ap7x3vh6.js +2 -0
  17. package/dist/cli/design-ne5zszyh.js +2 -0
  18. package/dist/cli/dispatch-2r5myxye.js +2 -0
  19. package/dist/cli/doctor-ws4fh4tn.js +3 -0
  20. package/dist/cli/executor-bdrjn634.js +2 -0
  21. package/dist/cli/export-1mxb9g5p.js +2 -0
  22. package/dist/cli/files-g104xghh.js +2 -0
  23. package/dist/cli/gauntlet-07xrjpj7.js +2 -0
  24. package/dist/cli/gauntlet-runner-xvy64436.js +10 -0
  25. package/dist/cli/gauntlet-wave3-jm91yt5w.js +5 -0
  26. package/dist/cli/gauntlet-wave4-r13py7p1.js +14 -0
  27. package/dist/cli/hashline-znvrat11.js +2 -0
  28. package/dist/cli/http-xafw6fsh.js +143 -0
  29. package/dist/cli/index-1sgjm25y.js +2 -0
  30. package/dist/cli/init-g2m0tn4m.js +51 -0
  31. package/dist/cli/install-avaqjjqq.js +2 -0
  32. package/dist/cli/loop-mmpfft01.js +2 -0
  33. package/dist/cli/main-0904f6ps.js +5 -0
  34. package/dist/cli/main-0ab9fc26.js +9 -0
  35. package/dist/cli/main-0jys2ccn.js +3 -0
  36. package/dist/cli/main-0mtcdbs7.js +3 -0
  37. package/dist/cli/main-0z1w2zsg.js +3 -0
  38. package/dist/cli/main-1dchs7xv.js +18 -0
  39. package/dist/cli/main-1ereejm1.js +3 -0
  40. package/dist/cli/main-1k1kw6b5.js +3 -0
  41. package/dist/cli/main-27y4sm2k.js +38 -0
  42. package/dist/cli/main-2wwjex5j.js +58 -0
  43. package/dist/cli/main-2yeveeve.js +6 -0
  44. package/dist/cli/main-2yfck9b5.js +3 -0
  45. package/dist/cli/main-2zmzgkwh.js +3 -0
  46. package/dist/cli/main-351pz3z7.js +7 -0
  47. package/dist/cli/main-3gjqfh7a.js +6 -0
  48. package/dist/cli/main-3nf3kgve.js +3 -0
  49. package/dist/cli/main-3pjrb2hd.js +3 -0
  50. package/dist/cli/main-3rxcvgna.js +19 -0
  51. package/dist/cli/main-4b3jgy66.js +19 -0
  52. package/dist/cli/main-4wndhjdc.js +7 -0
  53. package/dist/cli/main-4xcmvxnk.js +3 -0
  54. package/dist/cli/main-5tbz0wbz.js +4 -0
  55. package/dist/cli/main-5ywnwthm.js +3 -0
  56. package/dist/cli/main-6b62vkz0.js +14 -0
  57. package/dist/cli/main-6dnk69vp.js +3 -0
  58. package/dist/cli/main-6genrmhs.js +136 -0
  59. package/dist/cli/main-73g7eff4.js +15 -0
  60. package/dist/cli/main-7c5thhjd.js +5 -0
  61. package/dist/cli/main-7rn6bqje.js +3 -0
  62. package/dist/cli/main-80haw7qk.js +4 -0
  63. package/dist/cli/main-875s60s2.js +4 -0
  64. package/dist/cli/main-8kjxbpw4.js +8 -0
  65. package/dist/cli/main-90ds1z4e.js +10 -0
  66. package/dist/cli/main-9etavkew.js +3 -0
  67. package/dist/cli/main-a9njrkk1.js +3 -0
  68. package/dist/cli/main-aecrjq2d.js +12 -0
  69. package/dist/cli/main-ck9asesq.js +9 -0
  70. package/dist/cli/main-cta9racd.js +4 -0
  71. package/dist/cli/main-ddv7j2ag.js +3 -0
  72. package/dist/cli/main-dfreez27.js +10 -0
  73. package/dist/cli/main-f7rw7des.js +3 -0
  74. package/dist/cli/main-ggcn7rd7.js +5 -0
  75. package/dist/cli/main-gzkmycnv.js +3 -0
  76. package/dist/cli/main-hq51jg8v.js +18 -0
  77. package/dist/cli/main-jft389w9.js +8 -0
  78. package/dist/cli/main-k1eqkg83.js +3 -0
  79. package/dist/cli/main-k2y8a2aw.js +9 -0
  80. package/dist/cli/main-kcpbykxz.js +4 -0
  81. package/dist/cli/main-kd488vje.js +22 -0
  82. package/dist/cli/main-kh32yvgk.js +5 -0
  83. package/dist/cli/main-kqxnqjnv.js +25 -0
  84. package/dist/cli/main-kyn0xnsg.js +3 -0
  85. package/dist/cli/main-m1kk6fp5.js +21 -0
  86. package/dist/cli/main-mv40pcr2.js +4 -0
  87. package/dist/cli/main-n0t3973w.js +3 -0
  88. package/dist/cli/main-nqveez48.js +4 -0
  89. package/dist/cli/main-pknhvrmj.js +3 -0
  90. package/dist/cli/main-pn1w7a7j.js +3 -0
  91. package/dist/cli/main-prxxs70n.js +4 -0
  92. package/dist/cli/main-q3vsesf9.js +3 -0
  93. package/dist/cli/main-qsevpgsv.js +3 -0
  94. package/dist/cli/main-rdgdw24b.js +25 -0
  95. package/dist/cli/main-rfth4tbm.js +16 -0
  96. package/dist/cli/main-rg0wn0xf.js +5 -0
  97. package/dist/cli/main-sdmxhtv8.js +4 -0
  98. package/dist/cli/main-skbp13js.js +18 -0
  99. package/dist/cli/main-t4xnd213.js +7 -0
  100. package/dist/cli/main-vqak588n.js +4 -0
  101. package/dist/cli/main-w2n1303f.js +9 -0
  102. package/dist/cli/main-wbrdspr2.js +5 -0
  103. package/dist/cli/main-wsrg79c1.js +7 -0
  104. package/dist/cli/main-x4r0fne4.js +5 -0
  105. package/dist/cli/main-xea2f3tn.js +6 -0
  106. package/dist/cli/main-xg704a3c.js +3 -0
  107. package/dist/cli/main-xvnrabfp.js +16 -0
  108. package/dist/cli/main-xy53xf0r.js +4 -0
  109. package/dist/cli/main-y1fqy60y.js +3 -0
  110. package/dist/cli/main-yn8cd281.js +34 -0
  111. package/dist/cli/main-yr0ksc0h.js +4 -0
  112. package/dist/cli/main-z2ex2vyf.js +4 -0
  113. package/dist/cli/main-z3aayzvq.js +3 -0
  114. package/dist/cli/main-zaqh35jg.js +3 -0
  115. package/dist/cli/main-zc2e8e46.js +4 -0
  116. package/dist/cli/main-zzrfw6cf.js +13 -0
  117. package/dist/cli/main.js +280 -0
  118. package/dist/cli/market-cmd-e14kmx9n.js +5 -0
  119. package/dist/cli/mcp-login-wq7ktdek.js +2 -0
  120. package/dist/cli/mcp-market-cmd-9mg3jecy.js +2 -0
  121. package/dist/cli/notify-b7qc0cjb.js +2 -0
  122. package/dist/cli/oauth-z8whcgfx.js +2 -0
  123. package/dist/cli/output-b3ewj3ps.js +16 -0
  124. package/dist/cli/profiles-6mr5he5e.js +2 -0
  125. package/dist/cli/provider-config-g7j42q8x.js +2 -0
  126. package/dist/cli/provider-jr1y8vvm.js +2 -0
  127. package/dist/cli/registry-s8yk86g0.js +2 -0
  128. package/dist/cli/registry-t6p8d4mn.js +2 -0
  129. package/dist/cli/repl-bajwe1mh.js +11 -0
  130. package/dist/cli/resume-rwn9nz7y.js +2 -0
  131. package/dist/cli/run-flags-nah7ndpt.js +2 -0
  132. package/dist/cli/runtime-n7gafzhb.js +2 -0
  133. package/dist/cli/sandbox-config-emdy18x4.js +2 -0
  134. package/dist/cli/server-b0nvs2bn.js +5 -0
  135. package/dist/cli/session-arg-y75wd4kj.js +2 -0
  136. package/dist/cli/session-j62evmjq.js +2 -0
  137. package/dist/cli/sessions-cmd-tsnwz0ns.js +7 -0
  138. package/dist/cli/settings-df10wfez.js +2 -0
  139. package/dist/cli/setup-jzvv72fg.js +2 -0
  140. package/dist/cli/sextant-smoke-37m81ke6.js +5 -0
  141. package/dist/cli/skills-cmd-gjxnxnhx.js +2 -0
  142. package/dist/cli/smoke-p7748apt.js +8 -0
  143. package/dist/cli/start-chat-s4st3mm0.js +12 -0
  144. package/dist/cli/stream-gmeyewds.js +2 -0
  145. package/dist/cli/task-gh0kkp3n.js +2 -0
  146. package/dist/cli/tasks-z1kfpe8e.js +2 -0
  147. package/dist/cli/thinking-0eqkrz6t.js +2 -0
  148. package/dist/cli/todo-5brcrt9m.js +2 -0
  149. package/dist/cli/tools-7pzm0vj9.js +2 -0
  150. package/dist/cli/tools-s635p6s8.js +2 -0
  151. package/dist/cli/trust-cmd-cjav8zgm.js +2 -0
  152. package/dist/cli/update-check-pt31bm2f.js +2 -0
  153. package/dist/cli/update-cmd-tk131s9t.js +2 -0
  154. package/dist/cli/voice-56nabd8d.js +2 -0
  155. package/dist/cli/webfetch-xd8q596m.js +2 -0
  156. package/dist/cli/websearch-5hkf98k1.js +2 -0
  157. package/dist/cli/workflow-cmd-cy3cvzjp.js +4 -0
  158. package/dist/cli/workspace-q10g5z3e.js +2 -0
  159. package/dist/lib/index.js +62 -0
  160. package/dist/lib/models-index.json +1 -0
  161. package/dist/lib/plugins.js +55 -0
  162. package/dist/lib/providers.js +17 -0
  163. package/dist/lib/public-api.js +20 -0
  164. package/dist/lib/sdk.js +360 -0
  165. package/package.json +84 -72
  166. package/bin/rovecode.ts +0 -21
  167. package/src/acp/server.ts +0 -374
  168. package/src/cli/auth-login.ts +0 -122
  169. package/src/cli/connect.ts +0 -244
  170. package/src/cli/context-cmd.ts +0 -199
  171. package/src/cli/dispatch.ts +0 -82
  172. package/src/cli/doctor.ts +0 -362
  173. package/src/cli/export.ts +0 -276
  174. package/src/cli/help.ts +0 -293
  175. package/src/cli/is-tui-invocation.ts +0 -8
  176. package/src/cli/main.ts +0 -583
  177. package/src/cli/market-cmd.ts +0 -658
  178. package/src/cli/mcp-login.ts +0 -141
  179. package/src/cli/mcp-market-cmd.ts +0 -302
  180. package/src/cli/output.ts +0 -382
  181. package/src/cli/repl.ts +0 -250
  182. package/src/cli/repomap-root.ts +0 -14
  183. package/src/cli/resume.ts +0 -57
  184. package/src/cli/run-flags.ts +0 -43
  185. package/src/cli/run-limits.ts +0 -78
  186. package/src/cli/runtime.ts +0 -931
  187. package/src/cli/session-arg.ts +0 -30
  188. package/src/cli/sessions-cmd.ts +0 -145
  189. package/src/cli/setup.ts +0 -153
  190. package/src/cli/skills-cmd.ts +0 -194
  191. package/src/cli/start-chat.ts +0 -65
  192. package/src/cli/trust-cmd.ts +0 -52
  193. package/src/coding/bash.ts +0 -148
  194. package/src/coding/checkpoints.ts +0 -327
  195. package/src/coding/diff.ts +0 -138
  196. package/src/coding/files.ts +0 -341
  197. package/src/coding/hashline.ts +0 -274
  198. package/src/coding/lsp-gate.ts +0 -254
  199. package/src/coding/lsp-servers.ts +0 -147
  200. package/src/coding/lsp.ts +0 -283
  201. package/src/coding/repomap-cache.ts +0 -99
  202. package/src/coding/repomap-files.ts +0 -192
  203. package/src/coding/repomap.ts +0 -481
  204. package/src/core/agents.ts +0 -255
  205. package/src/core/compaction.ts +0 -259
  206. package/src/core/config.ts +0 -289
  207. package/src/core/context-report.ts +0 -228
  208. package/src/core/context.ts +0 -60
  209. package/src/core/count-remote.ts +0 -107
  210. package/src/core/execpolicy-rules.ts +0 -196
  211. package/src/core/execpolicy.ts +0 -385
  212. package/src/core/executor.ts +0 -454
  213. package/src/core/guardrails.ts +0 -400
  214. package/src/core/hooks.ts +0 -411
  215. package/src/core/images.ts +0 -230
  216. package/src/core/intro.ts +0 -266
  217. package/src/core/loop.ts +0 -567
  218. package/src/core/modes.ts +0 -372
  219. package/src/core/orchestrator.ts +0 -245
  220. package/src/core/proc-group.ts +0 -48
  221. package/src/core/project-trust.ts +0 -98
  222. package/src/core/reflection.ts +0 -165
  223. package/src/core/sandbox-config.ts +0 -186
  224. package/src/core/session-id.ts +0 -24
  225. package/src/core/session-images.ts +0 -73
  226. package/src/core/session-ops.ts +0 -183
  227. package/src/core/session-text.ts +0 -29
  228. package/src/core/session.ts +0 -469
  229. package/src/core/settings.ts +0 -170
  230. package/src/core/tasks.ts +0 -646
  231. package/src/core/token-scale.ts +0 -108
  232. package/src/core/tools.ts +0 -309
  233. package/src/core/trust.ts +0 -104
  234. package/src/core/types.ts +0 -330
  235. package/src/core/update-check.ts +0 -171
  236. package/src/core/usage.ts +0 -204
  237. package/src/core/validate.ts +0 -121
  238. package/src/core/verify-gate.ts +0 -159
  239. package/src/core/verify.ts +0 -236
  240. package/src/core/voice.ts +0 -158
  241. package/src/core/win-job.ts +0 -183
  242. package/src/core/workspace.ts +0 -184
  243. package/src/design/audit.ts +0 -797
  244. package/src/design/direction.ts +0 -190
  245. package/src/design/rules.ts +0 -157
  246. package/src/eval/bench.ts +0 -150
  247. package/src/eval/gauntlet-runner.ts +0 -215
  248. package/src/eval/gauntlet-support.ts +0 -84
  249. package/src/eval/gauntlet-wave3.ts +0 -269
  250. package/src/eval/gauntlet-wave4.ts +0 -217
  251. package/src/eval/gauntlet.ts +0 -253
  252. package/src/index.ts +0 -17
  253. package/src/lanes/agy.ts +0 -95
  254. package/src/lanes/approval.ts +0 -24
  255. package/src/lanes/claude.ts +0 -129
  256. package/src/lanes/codex.ts +0 -127
  257. package/src/lanes/events.ts +0 -130
  258. package/src/lanes/job.ts +0 -142
  259. package/src/lanes/opencode.ts +0 -122
  260. package/src/lanes/process.ts +0 -184
  261. package/src/lanes/progress.ts +0 -183
  262. package/src/lanes/registry.ts +0 -178
  263. package/src/lanes/runner.ts +0 -124
  264. package/src/lanes/types.ts +0 -112
  265. package/src/market/catalogs/mcp-docs.json +0 -111
  266. package/src/market/catalogs/plugins.json +0 -111
  267. package/src/market/catalogs/skills.json +0 -478
  268. package/src/market/clone.ts +0 -72
  269. package/src/market/context-cost.ts +0 -121
  270. package/src/market/digest.ts +0 -106
  271. package/src/market/index.ts +0 -22
  272. package/src/market/install.ts +0 -578
  273. package/src/market/manifest.ts +0 -187
  274. package/src/market/prereq.ts +0 -145
  275. package/src/market/registry.ts +0 -363
  276. package/src/market/resolve.ts +0 -111
  277. package/src/market/types.ts +0 -236
  278. package/src/market/validate.ts +0 -227
  279. package/src/mcp/client.ts +0 -449
  280. package/src/mcp/config.ts +0 -252
  281. package/src/mcp/local-package.ts +0 -211
  282. package/src/mcp/market-catalog.ts +0 -84
  283. package/src/mcp/market-install.ts +0 -289
  284. package/src/mcp/market.ts +0 -362
  285. package/src/mcp/oauth.ts +0 -251
  286. package/src/mcp/prompts-resources.ts +0 -249
  287. package/src/mcp/shared.ts +0 -149
  288. package/src/mcp/status.ts +0 -67
  289. package/src/mcp/tools.ts +0 -275
  290. package/src/mcp/transport.ts +0 -122
  291. package/src/mcp/trust.ts +0 -25
  292. package/src/memory/blocks.ts +0 -278
  293. package/src/memory/recall.ts +0 -355
  294. package/src/memory/scope.ts +0 -182
  295. package/src/memory/store.ts +0 -105
  296. package/src/memory/tools.ts +0 -99
  297. package/src/plugins/cli.ts +0 -119
  298. package/src/plugins/discover.ts +0 -108
  299. package/src/plugins/index.ts +0 -50
  300. package/src/plugins/install.ts +0 -184
  301. package/src/plugins/load.ts +0 -124
  302. package/src/plugins/manifest.ts +0 -92
  303. package/src/plugins/state.ts +0 -83
  304. package/src/providers/auth.ts +0 -408
  305. package/src/providers/cache.ts +0 -223
  306. package/src/providers/catalog-local.ts +0 -160
  307. package/src/providers/catalog.ts +0 -421
  308. package/src/providers/middleware-context.ts +0 -86
  309. package/src/providers/middleware.ts +0 -373
  310. package/src/providers/model-list.ts +0 -23
  311. package/src/providers/oauth/common.ts +0 -105
  312. package/src/providers/oauth/device-code.ts +0 -107
  313. package/src/providers/oauth/github-copilot.ts +0 -146
  314. package/src/providers/oauth/loopback.ts +0 -158
  315. package/src/providers/oauth/openai.ts +0 -163
  316. package/src/providers/oauth/openrouter.ts +0 -89
  317. package/src/providers/oauth/pkce.ts +0 -45
  318. package/src/providers/oauth/registry.ts +0 -39
  319. package/src/providers/oauth/seam.ts +0 -89
  320. package/src/providers/profile-glm53.ts +0 -111
  321. package/src/providers/profile-sonnet5-persona.ts +0 -65
  322. package/src/providers/profile-sonnet5-voice.ts +0 -23
  323. package/src/providers/profiles.ts +0 -156
  324. package/src/providers/provider-config.ts +0 -311
  325. package/src/providers/registry.ts +0 -333
  326. package/src/providers/responses.ts +0 -209
  327. package/src/providers/retry.ts +0 -234
  328. package/src/providers/router.ts +0 -294
  329. package/src/providers/sse.ts +0 -26
  330. package/src/providers/stream-errors.ts +0 -117
  331. package/src/providers/stream.ts +0 -566
  332. package/src/providers/thinking.ts +0 -189
  333. package/src/providers/wire-messages.ts +0 -129
  334. package/src/providers/wire-responses.ts +0 -79
  335. package/src/providers/wire-select.ts +0 -53
  336. package/src/server/http.ts +0 -291
  337. package/src/server/openapi.ts +0 -246
  338. package/src/sextant/card-hits.ts +0 -102
  339. package/src/sextant/card-keys.ts +0 -55
  340. package/src/sextant/context-source.ts +0 -157
  341. package/src/sextant/crew-cards.ts +0 -350
  342. package/src/sextant/draw-agents.ts +0 -273
  343. package/src/sextant/draw-code.ts +0 -388
  344. package/src/sextant/draw-context.ts +0 -222
  345. package/src/sextant/draw-frame.ts +0 -164
  346. package/src/sextant/draw-market.ts +0 -573
  347. package/src/sextant/draw-messages.ts +0 -386
  348. package/src/sextant/draw-pet.ts +0 -230
  349. package/src/sextant/draw-plan.ts +0 -187
  350. package/src/sextant/draw-tabs.ts +0 -85
  351. package/src/sextant/draw-util.ts +0 -65
  352. package/src/sextant/draw-wizard.ts +0 -378
  353. package/src/sextant/engine.ts +0 -230
  354. package/src/sextant/frame-hits.ts +0 -25
  355. package/src/sextant/frame.ts +0 -101
  356. package/src/sextant/git-status.ts +0 -197
  357. package/src/sextant/grid.ts +0 -59
  358. package/src/sextant/input.ts +0 -119
  359. package/src/sextant/keys.ts +0 -521
  360. package/src/sextant/layout.ts +0 -86
  361. package/src/sextant/local-commands.ts +0 -169
  362. package/src/sextant/market-source.ts +0 -287
  363. package/src/sextant/mentions.ts +0 -200
  364. package/src/sextant/message-hits.ts +0 -26
  365. package/src/sextant/model.ts +0 -387
  366. package/src/sextant/overlays.ts +0 -456
  367. package/src/sextant/panel-hits.ts +0 -38
  368. package/src/sextant/pet.ts +0 -399
  369. package/src/sextant/screen.ts +0 -324
  370. package/src/sextant/scroll-hits.ts +0 -66
  371. package/src/sextant/scrollbar.ts +0 -82
  372. package/src/sextant/sextant-bridge.ts +0 -174
  373. package/src/sextant/sextant-cards.ts +0 -142
  374. package/src/sextant/sextant-diff-base.ts +0 -63
  375. package/src/sextant/sextant-files.ts +0 -154
  376. package/src/sextant/sextant-frame-loop.ts +0 -335
  377. package/src/sextant/sextant-renderer.ts +0 -574
  378. package/src/sextant/sextant-repo.ts +0 -140
  379. package/src/sextant/theme.ts +0 -66
  380. package/src/sextant/tool-rows.ts +0 -189
  381. package/src/sextant/types.ts +0 -493
  382. package/src/skills/index.ts +0 -387
  383. package/src/skills/pack.ts +0 -220
  384. package/src/skills/spec.ts +0 -162
  385. package/src/skills/tools.ts +0 -69
  386. package/src/skills/versioned.ts +0 -227
  387. package/src/telemetry/otel-export.ts +0 -122
  388. package/src/telemetry/otel-lanes.ts +0 -89
  389. package/src/telemetry/otel-logs.ts +0 -131
  390. package/src/telemetry/otel-metrics.ts +0 -136
  391. package/src/telemetry/otel.ts +0 -397
  392. package/src/telemetry/otlp.ts +0 -76
  393. package/src/tools/ask-user.ts +0 -156
  394. package/src/tools/bash-bg.ts +0 -94
  395. package/src/tools/bash-jobs.ts +0 -237
  396. package/src/tools/design.ts +0 -151
  397. package/src/tools/evalcell.ts +0 -338
  398. package/src/tools/html-text.ts +0 -139
  399. package/src/tools/provider.ts +0 -149
  400. package/src/tools/task.ts +0 -250
  401. package/src/tools/todo.ts +0 -320
  402. package/src/tools/webfetch.ts +0 -332
  403. package/src/tools/websearch.ts +0 -359
  404. package/src/tui/agents-cmd.ts +0 -41
  405. package/src/tui/app.ts +0 -749
  406. package/src/tui/attach.ts +0 -127
  407. package/src/tui/boot-notes.ts +0 -41
  408. package/src/tui/builtin-prompts.ts +0 -59
  409. package/src/tui/checkpoints-cmd.ts +0 -70
  410. package/src/tui/clipboard-image.ts +0 -81
  411. package/src/tui/clipboard.ts +0 -78
  412. package/src/tui/commands.ts +0 -283
  413. package/src/tui/config-view.ts +0 -53
  414. package/src/tui/context-cmds.ts +0 -282
  415. package/src/tui/cost.ts +0 -108
  416. package/src/tui/crash-guard.ts +0 -173
  417. package/src/tui/focus-terminal.ts +0 -34
  418. package/src/tui/git-cmds.ts +0 -273
  419. package/src/tui/git-plain.ts +0 -58
  420. package/src/tui/info-cmd.ts +0 -150
  421. package/src/tui/input-plain.ts +0 -76
  422. package/src/tui/mcp-cmd.ts +0 -128
  423. package/src/tui/memory-note.ts +0 -77
  424. package/src/tui/modes-cmd.ts +0 -45
  425. package/src/tui/notify-seq.ts +0 -100
  426. package/src/tui/notify.ts +0 -318
  427. package/src/tui/overlays.ts +0 -97
  428. package/src/tui/pi-renderer.ts +0 -428
  429. package/src/tui/providers-cmd.ts +0 -377
  430. package/src/tui/reasoning-view.ts +0 -56
  431. package/src/tui/renderer.ts +0 -128
  432. package/src/tui/replay-marker.ts +0 -29
  433. package/src/tui/session-cmd.ts +0 -148
  434. package/src/tui/session-manage.ts +0 -95
  435. package/src/tui/sextant-attach.ts +0 -102
  436. package/src/tui/sextant-io.ts +0 -202
  437. package/src/tui/sextant-smoke.ts +0 -110
  438. package/src/tui/shell-cmd.ts +0 -158
  439. package/src/tui/smoke.ts +0 -72
  440. package/src/tui/staged-terminal.ts +0 -50
  441. package/src/tui/startup.ts +0 -12
  442. package/src/tui/theme.ts +0 -59
  443. package/src/tui/todo-label.ts +0 -7
  444. package/src/tui/trust-card.ts +0 -107
  445. package/src/tui/tui-commands.ts +0 -87
  446. package/tsconfig.json +0 -30
  447. package/vendor/pi-tui/LICENSE +0 -21
  448. package/vendor/pi-tui/PATCHES.md +0 -12
  449. package/vendor/pi-tui/PROVENANCE.md +0 -12
  450. package/vendor/pi-tui/README.upstream.md +0 -854
  451. package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  452. package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  453. package/vendor/pi-tui/src/alt-screen-search.ts +0 -158
  454. package/vendor/pi-tui/src/autocomplete.ts +0 -827
  455. package/vendor/pi-tui/src/components/alt-screen-flash.ts +0 -52
  456. package/vendor/pi-tui/src/components/box.ts +0 -138
  457. package/vendor/pi-tui/src/components/cancellable-loader.ts +0 -41
  458. package/vendor/pi-tui/src/components/editor.ts +0 -2364
  459. package/vendor/pi-tui/src/components/h-stack.ts +0 -45
  460. package/vendor/pi-tui/src/components/image.ts +0 -128
  461. package/vendor/pi-tui/src/components/input.ts +0 -448
  462. package/vendor/pi-tui/src/components/loader.ts +0 -93
  463. package/vendor/pi-tui/src/components/markdown.ts +0 -1016
  464. package/vendor/pi-tui/src/components/scroll-view.ts +0 -217
  465. package/vendor/pi-tui/src/components/select-list.ts +0 -230
  466. package/vendor/pi-tui/src/components/settings-list.ts +0 -277
  467. package/vendor/pi-tui/src/components/spacer.ts +0 -29
  468. package/vendor/pi-tui/src/components/stack.ts +0 -155
  469. package/vendor/pi-tui/src/components/text.ts +0 -108
  470. package/vendor/pi-tui/src/components/truncated-text.ts +0 -66
  471. package/vendor/pi-tui/src/components/v-stack.ts +0 -34
  472. package/vendor/pi-tui/src/editor-component.ts +0 -75
  473. package/vendor/pi-tui/src/fuzzy.ts +0 -138
  474. package/vendor/pi-tui/src/index.ts +0 -149
  475. package/vendor/pi-tui/src/keybindings.ts +0 -321
  476. package/vendor/pi-tui/src/keys.ts +0 -1402
  477. package/vendor/pi-tui/src/kill-ring.ts +0 -47
  478. package/vendor/pi-tui/src/latex.ts +0 -1381
  479. package/vendor/pi-tui/src/layout-node.ts +0 -52
  480. package/vendor/pi-tui/src/layout.ts +0 -411
  481. package/vendor/pi-tui/src/native-modifiers.ts +0 -60
  482. package/vendor/pi-tui/src/native-module-path.ts +0 -32
  483. package/vendor/pi-tui/src/stdin-buffer.ts +0 -445
  484. package/vendor/pi-tui/src/terminal-colors.ts +0 -74
  485. package/vendor/pi-tui/src/terminal-image.ts +0 -701
  486. package/vendor/pi-tui/src/terminal.ts +0 -554
  487. package/vendor/pi-tui/src/tui-alt-screen.ts +0 -1379
  488. package/vendor/pi-tui/src/tui-main-screen.ts +0 -655
  489. package/vendor/pi-tui/src/tui.ts +0 -1264
  490. package/vendor/pi-tui/src/undo-stack.ts +0 -29
  491. package/vendor/pi-tui/src/utils.ts +0 -1327
  492. package/vendor/pi-tui/src/word-navigation.ts +0 -118
  493. package/vendor/pi-tui/test/test-themes.ts +0 -39
  494. package/vendor/pi-tui/test/virtual-terminal.ts +0 -219
  495. /package/{src/providers → dist/cli}/models-index.json +0 -0
@@ -1,1379 +0,0 @@
1
- // @ts-nocheck -- vendored by rovecode: upstream-checked at pinned SHA 853a80d2; see ../PATCHES.md
2
- import {
3
- AltScreenSearchComponent,
4
- type AltScreenSearchMatch,
5
- findAltScreenSearchMatches,
6
- getAltScreenSearchMatchKey,
7
- } from "./alt-screen-search.ts";
8
- import { AltScreenFlashContainer } from "./components/alt-screen-flash.ts";
9
- import { ScrollView } from "./components/scroll-view.ts";
10
- import { getKeybindings } from "./keybindings.ts";
11
- import { isKeyRelease } from "./keys.ts";
12
- import {
13
- getScrollbarGeometry,
14
- getScrollViewBox,
15
- getScrollViewsAt,
16
- type LayoutFrame,
17
- renderLayoutFrame,
18
- type ScrollbarGeometry,
19
- } from "./layout.ts";
20
- import type { Terminal } from "./terminal.ts";
21
- import {
22
- deleteAllKittyImages,
23
- deleteAllKittyPlacements,
24
- deleteKittyImage,
25
- getCapabilities,
26
- getKittyImagePlacement,
27
- type ImageProtocol,
28
- isImageLine,
29
- setCapabilities,
30
- type TerminalCapabilities,
31
- } from "./terminal-image.ts";
32
- import {
33
- type Component,
34
- CURSOR_MARKER,
35
- compositeTuiLine,
36
- type OverlayHandle,
37
- TuiBase,
38
- type TuiStopOptions,
39
- VIEWPORT_TUI,
40
- type ViewportTUI,
41
- } from "./tui.ts";
42
- import {
43
- extractAnsiCode,
44
- getGraphemeCellRange,
45
- getOsc8LinkAtColumn,
46
- getWordSegmenter,
47
- sliceByColumn,
48
- stripTerminalSequences,
49
- visibleWidth,
50
- } from "./utils.ts";
51
-
52
- const ENTER_ALT_SCREEN = "\x1b[?1049h";
53
- const EXIT_ALT_SCREEN = "\x1b[?1049l";
54
- const DISABLE_AUTOWRAP = "\x1b[?7l";
55
- const ENABLE_AUTOWRAP = "\x1b[?7h";
56
- const ENABLE_BUTTON_MOTION_MOUSE = "\x1b[?1000h\x1b[?1002h\x1b[?1004h\x1b[?1006h";
57
- const ENABLE_ALL_MOTION_MOUSE = "\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1004h\x1b[?1006h";
58
- const DISABLE_MOUSE = "\x1b[?1006l\x1b[?1004l\x1b[?1003l\x1b[?1002l\x1b[?1000l";
59
- const FOCUS_IN = "\x1b[I";
60
- const FOCUS_OUT = "\x1b[O";
61
- const BEGIN_SYNCHRONIZED_OUTPUT = "\x1b[?2026h";
62
- const END_SYNCHRONIZED_OUTPUT = "\x1b[?2026l";
63
- const OSC133_ZONE_PREFIX = /^(?:\x1b\]133;[ABC](?:\x07|\x1b\\))+/;
64
- const OSC133_PROMPT_START = /^\x1b\]133;A(?:\x07|\x1b\\)/;
65
- const PAGE_SCROLL_OVERLAP = 4;
66
- const MAX_CACHED_OFFSCREEN_KITTY_IMAGES = 16;
67
- const MAX_CACHED_OFFSCREEN_KITTY_TRANSMISSION_BYTES = 32 * 1024 * 1024;
68
- const MAX_CACHED_OFFSCREEN_KITTY_DECODED_BYTES = 64 * 1024 * 1024;
69
- const DOUBLE_CLICK_INTERVAL_MS = 500;
70
- // Regular mode delegates double-click selection to the terminal emulator. Fullscreen owns mouse selection,
71
- // so mirror common terminal word-selection behavior by keeping paths and kebab-case tokens whole.
72
- const TERMINAL_WORD_SELECTION_JOINERS = new Set(["/", "-"]);
73
- const wordSegmenter = getWordSegmenter();
74
-
75
- interface CachedKittyImage {
76
- transmissionGeneration: number;
77
- transmissionBytes: number;
78
- estimatedDecodedBytes: number;
79
- }
80
-
81
- interface SelectionPoint {
82
- row: number;
83
- col: number;
84
- scrollView?: ScrollView;
85
- /** Whether this point lies between terminal cells rather than on a cell. */
86
- boundary?: boolean;
87
- }
88
-
89
- interface SelectionRange {
90
- start: SelectionPoint;
91
- end: SelectionPoint;
92
- }
93
-
94
- type SelectionGranularity = "character" | "word" | "line";
95
-
96
- interface ClickTarget {
97
- timestamp: number;
98
- count: number;
99
- row: number;
100
- scrollView?: ScrollView;
101
- wordStart: number;
102
- wordEnd: number;
103
- }
104
-
105
- interface SgrMouseEvent {
106
- button: number;
107
- x: number;
108
- y: number;
109
- release: boolean;
110
- }
111
-
112
- interface WheelEvent {
113
- direction: -1 | 1;
114
- x: number;
115
- y: number;
116
- }
117
-
118
- interface ScrollbarDrag {
119
- scrollView: ScrollView;
120
- grabOffset: number;
121
- }
122
-
123
- interface ScrollbarTarget {
124
- scrollView: ScrollView;
125
- geometry: ScrollbarGeometry;
126
- }
127
-
128
- type SearchSelectionMode = "query" | "retain" | "next" | "previous";
129
-
130
- interface ActiveSearch {
131
- component: AltScreenSearchComponent;
132
- overlay?: OverlayHandle;
133
- query: string;
134
- matches: AltScreenSearchMatch[];
135
- selectedIndex: number;
136
- selectedKey?: string;
137
- anchorRow: number;
138
- selectionMode: SearchSelectionMode;
139
- }
140
-
141
- interface SearchHighlightRange {
142
- startCol: number;
143
- endCol: number;
144
- current: boolean;
145
- }
146
-
147
- export interface TuiAltScreenOptions {
148
- /** Number of logical lines moved for each mouse-wheel event. */
149
- wheelScrollLines?: number;
150
- /** Capture mouse events for viewport scrolling and application-owned text selection. */
151
- mouse?: boolean;
152
- /** Style a non-current transcript search match. */
153
- searchMatchStyle?: (text: string) => string;
154
- /** Style the current transcript search match. */
155
- searchCurrentMatchStyle?: (text: string) => string;
156
- /** Open an OSC 8 hyperlink activated with a primary-button click. */
157
- openUrl?: (url: string) => void;
158
- /** Handle an unmodified secondary-button press for clipboard paste. Currently enabled on Windows only. */
159
- onRightClickPaste?: () => void;
160
- /** Automatically copy selected text to the clipboard on mouse release (default: true). */
161
- copyOnSelect?: boolean;
162
- /**
163
- * Copy selected text to the system clipboard. Return `true` on success; the caller flashes
164
- * an error otherwise. When omitted, the selection is copied via an OSC 52 write.
165
- */
166
- copySelection?: (text: string) => Promise<boolean>;
167
- }
168
-
169
- /** Alternate-screen TUI with a scrollable, application-owned viewport. */
170
- export class TuiAltScreen extends TuiBase implements ViewportTUI {
171
- readonly mode = "fullscreen" as const;
172
- readonly [VIEWPORT_TUI] = true as const;
173
- private previousScreen: string[] = [];
174
- private lastDocument: string[] = [];
175
- private previousScreenWidth = 0;
176
- private previousScreenHeight = 0;
177
- private layoutRoot: Component | undefined;
178
- private currentLayout: LayoutFrame | undefined;
179
- private readonly implicitDocument: Component;
180
- private readonly implicitScrollView: ScrollView;
181
- private readonly flashes: AltScreenFlashContainer;
182
- private altScreenActive = false;
183
- private imageProtocol: ImageProtocol = null;
184
- private savedCapabilities?: TerminalCapabilities;
185
- private readonly uploadedKittyImages = new Map<number, CachedKittyImage>();
186
- private selectionAnchor?: SelectionPoint;
187
- private selectionFocus?: SelectionPoint;
188
- private selectionGranularity: SelectionGranularity = "character";
189
- private selectionInitialRange?: SelectionRange;
190
- private lastClick?: ClickTarget;
191
- private selectionDragPointer?: { x: number; y: number };
192
- private selectionAutoScrollDirection: -1 | 0 | 1 = 0;
193
- private selectionAutoScrollTimer?: NodeJS.Timeout;
194
- private selectionPressActive = false;
195
- private scrollbarDrag?: ScrollbarDrag;
196
- private scrollbarHover?: ScrollView;
197
- private activeSearch?: ActiveSearch;
198
- private pressedUrl?: string;
199
- private selectionDragged = false;
200
- private readonly wheelScrollLines: number;
201
- private readonly mouseEnabled: boolean;
202
- private readonly searchMatchStyle: (text: string) => string;
203
- private readonly searchCurrentMatchStyle: (text: string) => string;
204
- private readonly openUrl?: (url: string) => void;
205
- private readonly onRightClickPaste?: () => void;
206
- private copyOnSelect: boolean;
207
- private readonly copySelection?: (text: string) => Promise<boolean>;
208
-
209
- constructor(
210
- terminal: Terminal,
211
- showHardwareCursor?: boolean,
212
- logDirectory?: string,
213
- options: TuiAltScreenOptions = {},
214
- ) {
215
- super(terminal, showHardwareCursor, logDirectory);
216
- this.implicitDocument = {
217
- render: (width) => super.render(width),
218
- invalidate: () => {
219
- for (const child of this.children) child.invalidate();
220
- },
221
- };
222
- this.implicitScrollView = new ScrollView(this.implicitDocument, { follow: "end", primary: true });
223
- this.flashes = new AltScreenFlashContainer(() => this.requestRender());
224
- this.wheelScrollLines = Math.max(1, Math.floor(options.wheelScrollLines ?? 1));
225
- this.mouseEnabled = options.mouse ?? true;
226
- this.searchMatchStyle = options.searchMatchStyle ?? ((text) => `\x1b[4m${text}\x1b[24m`);
227
- this.searchCurrentMatchStyle = options.searchCurrentMatchStyle ?? ((text) => `\x1b[1;7m${text}\x1b[22;27m`);
228
- this.openUrl = options.openUrl;
229
- this.onRightClickPaste = options.onRightClickPaste;
230
- this.copyOnSelect = options.copyOnSelect ?? true;
231
- this.copySelection = options.copySelection;
232
- this.addInputListener((data) => this.handleViewportInput(data));
233
- }
234
-
235
- get viewportTop(): number {
236
- return this.getPrimaryScrollView().scrollTop;
237
- }
238
-
239
- get isFollowingOutput(): boolean {
240
- return this.getPrimaryScrollView().isFollowingEnd;
241
- }
242
-
243
- getCopyOnSelect(): boolean {
244
- return this.copyOnSelect;
245
- }
246
-
247
- setCopyOnSelect(enabled: boolean): void {
248
- this.copyOnSelect = enabled;
249
- }
250
-
251
- /** Whether the fullscreen viewport has a non-empty active text selection. */
252
- hasActiveSelection(): boolean {
253
- return this.getActiveSelectionText() !== undefined;
254
- }
255
-
256
- /** Copy the active fullscreen text selection, if any, using the configured selection clipboard path. */
257
- async copyActiveSelectionToClipboard(): Promise<boolean> {
258
- const text = this.getActiveSelectionText();
259
- if (!text) return false;
260
- return this.copyTextToClipboard(text);
261
- }
262
-
263
- setLayoutRoot(component: Component | undefined): void {
264
- if (this.layoutRoot === component) return;
265
- this.layoutRoot = component;
266
- this.currentLayout = undefined;
267
- this.requestRender();
268
- }
269
-
270
- override render(width: number): string[] {
271
- return this.layoutRoot?.render(width) ?? super.render(width);
272
- }
273
-
274
- protected override getMountedRoots(): readonly Component[] {
275
- return this.layoutRoot ? [this.layoutRoot] : this.children;
276
- }
277
-
278
- private getPrimaryScrollView(): ScrollView {
279
- return this.currentLayout?.primaryScrollView ?? this.implicitScrollView;
280
- }
281
-
282
- protected override beforeTerminalStart(): void {
283
- this.stopSelectionAutoScroll();
284
- this.selectionPressActive = false;
285
- this.stopScrollbarHover();
286
- this.stopScrollbarDrag();
287
- this.flashes.dispose();
288
- this.altScreenActive = true;
289
- const capabilities = getCapabilities();
290
- this.imageProtocol = capabilities.images;
291
- this.uploadedKittyImages.clear();
292
- if (capabilities.images === "iterm2") {
293
- this.savedCapabilities = capabilities;
294
- setCapabilities({ ...capabilities, images: null });
295
- this.invalidate();
296
- }
297
- this.lastDocument = [];
298
- this.selectionAnchor = undefined;
299
- this.selectionFocus = undefined;
300
- this.selectionGranularity = "character";
301
- this.selectionInitialRange = undefined;
302
- this.lastClick = undefined;
303
- this.pressedUrl = undefined;
304
- this.selectionDragged = false;
305
- this.resetRenderState();
306
- const term = process.env.TERM?.toLowerCase() ?? "";
307
- // Multiplexers can lag when every pointer movement is forwarded. Button-motion
308
- // tracking preserves clicks, wheel events, selections, and scrollbar dragging.
309
- const mouseSequence =
310
- process.env.TMUX !== undefined ||
311
- process.env.ZELLIJ !== undefined ||
312
- process.env.STY !== undefined ||
313
- term.startsWith("tmux") ||
314
- term.startsWith("screen")
315
- ? ENABLE_BUTTON_MOTION_MOUSE
316
- : ENABLE_ALL_MOTION_MOUSE;
317
- this.terminal.write(
318
- `${ENTER_ALT_SCREEN}${DISABLE_AUTOWRAP}${this.mouseEnabled ? mouseSequence : ""}\x1b[2J\x1b[H\x1b[?25l`,
319
- );
320
- }
321
-
322
- protected override beforeTerminalStop(_options: TuiStopOptions): void {
323
- this.closeSearch();
324
- this.stopSelectionAutoScroll();
325
- this.selectionPressActive = false;
326
- this.stopScrollbarHover();
327
- this.stopScrollbarDrag();
328
- this.flashes.dispose();
329
- if (!this.altScreenActive) return;
330
- this.terminal.write(
331
- `${BEGIN_SYNCHRONIZED_OUTPUT}${this.deleteKittyImages()}${this.mouseEnabled ? DISABLE_MOUSE : ""}${ENABLE_AUTOWRAP}${END_SYNCHRONIZED_OUTPUT}`,
332
- );
333
- this.uploadedKittyImages.clear();
334
- }
335
-
336
- protected override afterTerminalStop(options: TuiStopOptions): void {
337
- if (!this.altScreenActive) return;
338
- this.altScreenActive = false;
339
- if (options.preserveScreen) {
340
- this.terminal.write(`${BEGIN_SYNCHRONIZED_OUTPUT}${EXIT_ALT_SCREEN}\x1b[?25h${END_SYNCHRONIZED_OUTPUT}`);
341
- } else {
342
- const width = Math.max(1, this.terminal.columns);
343
- const documentLines = this.render(width).map((line) => line.replace(OSC133_ZONE_PREFIX, ""));
344
- this.lastDocument = this.applyLineResets(documentLines.map((line) => line.replaceAll(CURSOR_MARKER, ""))).map(
345
- (line) => (isImageLine(line) || visibleWidth(line) <= width ? line : sliceByColumn(line, 0, width, true)),
346
- );
347
- let buffer = `${BEGIN_SYNCHRONIZED_OUTPUT}${EXIT_ALT_SCREEN}${DISABLE_AUTOWRAP}`;
348
- for (let row = 0; row < this.lastDocument.length; row++) {
349
- if (row > 0) buffer += "\r\n";
350
- buffer += `\r\x1b[2K${this.lastDocument[row] ?? ""}`;
351
- }
352
- buffer += `\x1b[0m${ENABLE_AUTOWRAP}\r\n\x1b[?25h${END_SYNCHRONIZED_OUTPUT}`;
353
- this.terminal.write(buffer);
354
- }
355
- if (this.savedCapabilities) {
356
- setCapabilities(this.savedCapabilities);
357
- this.savedCapabilities = undefined;
358
- }
359
- }
360
-
361
- private deleteKittyImages(): string {
362
- return this.imageProtocol === "kitty" ? deleteAllKittyImages() : "";
363
- }
364
-
365
- private prepareKittyScreen(screen: string[]): { lines: string[]; evictedImageDeletion: string } {
366
- const visibleImageIds = new Set<number>();
367
- const lines = screen.map((line) => {
368
- const placement = getKittyImagePlacement(line);
369
- if (!placement) return line;
370
- visibleImageIds.add(placement.imageId);
371
-
372
- const cachedImage = this.uploadedKittyImages.get(placement.imageId);
373
- const nextCachedImage = {
374
- transmissionGeneration: placement.transmissionGeneration,
375
- transmissionBytes: placement.transmissionBytes,
376
- estimatedDecodedBytes: placement.estimatedDecodedBytes,
377
- };
378
- if (cachedImage) this.uploadedKittyImages.delete(placement.imageId);
379
- this.uploadedKittyImages.set(placement.imageId, nextCachedImage);
380
-
381
- return cachedImage?.transmissionGeneration === placement.transmissionGeneration
382
- ? placement.replacementLine
383
- : line;
384
- });
385
-
386
- let cachedOffscreenImageCount = 0;
387
- let cachedOffscreenTransmissionBytes = 0;
388
- let cachedOffscreenDecodedBytes = 0;
389
- for (const [imageId, cachedImage] of this.uploadedKittyImages) {
390
- if (visibleImageIds.has(imageId)) continue;
391
- cachedOffscreenImageCount += 1;
392
- cachedOffscreenTransmissionBytes += cachedImage.transmissionBytes;
393
- cachedOffscreenDecodedBytes += cachedImage.estimatedDecodedBytes;
394
- }
395
-
396
- let evictedImageDeletion = "";
397
- for (const [imageId, cachedImage] of this.uploadedKittyImages) {
398
- if (
399
- cachedOffscreenImageCount <= MAX_CACHED_OFFSCREEN_KITTY_IMAGES &&
400
- cachedOffscreenTransmissionBytes <= MAX_CACHED_OFFSCREEN_KITTY_TRANSMISSION_BYTES &&
401
- cachedOffscreenDecodedBytes <= MAX_CACHED_OFFSCREEN_KITTY_DECODED_BYTES
402
- ) {
403
- break;
404
- }
405
- if (visibleImageIds.has(imageId)) continue;
406
- evictedImageDeletion += deleteKittyImage(imageId);
407
- this.uploadedKittyImages.delete(imageId);
408
- cachedOffscreenImageCount -= 1;
409
- cachedOffscreenTransmissionBytes -= cachedImage.transmissionBytes;
410
- cachedOffscreenDecodedBytes -= cachedImage.estimatedDecodedBytes;
411
- }
412
- return { lines, evictedImageDeletion };
413
- }
414
-
415
- protected override resetRenderState(): void {
416
- this.previousScreen = [];
417
- this.previousScreenWidth = 0;
418
- this.previousScreenHeight = 0;
419
- this.currentLayout = undefined;
420
- }
421
-
422
- scrollBy(lines: number): void {
423
- this.getPrimaryScrollView().scrollBy(lines);
424
- this.requestRender();
425
- }
426
-
427
- scrollToTop(): void {
428
- this.getPrimaryScrollView().scrollToStart();
429
- this.requestRender();
430
- }
431
-
432
- scrollToBottom(): void {
433
- this.getPrimaryScrollView().scrollToEnd();
434
- this.requestRender();
435
- }
436
-
437
- private scrollToPrompt(direction: -1 | 1): void {
438
- if (!this.currentLayout) return;
439
- const scrollView = this.getPrimaryScrollView();
440
- const lines = getScrollViewBox(this.currentLayout, scrollView)?.scrollContentLines;
441
- if (!lines) return;
442
-
443
- for (let row = scrollView.scrollTop + direction; row >= 0 && row < lines.length; row += direction) {
444
- if (!OSC133_PROMPT_START.test(lines[row] ?? "")) continue;
445
- scrollView.scrollTo(row);
446
- this.requestRender();
447
- return;
448
- }
449
- }
450
-
451
- private openSearch(): void {
452
- if (this.activeSearch) {
453
- this.activeSearch.overlay?.focus();
454
- return;
455
- }
456
- const component = new AltScreenSearchComponent((query) => this.updateSearchQuery(query));
457
- const search: ActiveSearch = {
458
- component,
459
- query: "",
460
- matches: [],
461
- selectedIndex: -1,
462
- anchorRow: this.getPrimaryScrollView().scrollTop,
463
- selectionMode: "query",
464
- };
465
- this.activeSearch = search;
466
- search.overlay = this.showOverlay(component, {
467
- anchor: "top-right",
468
- width: "40%",
469
- minWidth: 24,
470
- margin: 1,
471
- });
472
- }
473
-
474
- private closeSearch(): void {
475
- const search = this.activeSearch;
476
- if (!search) return;
477
- this.activeSearch = undefined;
478
- search.overlay?.hide();
479
- this.requestRender();
480
- }
481
-
482
- private updateSearchQuery(query: string): void {
483
- const search = this.activeSearch;
484
- if (!search || query === search.query) return;
485
- const selected = search.matches[search.selectedIndex];
486
- search.anchorRow = selected?.segments[0]?.row ?? this.getPrimaryScrollView().scrollTop;
487
- search.query = query;
488
- search.selectionMode = "query";
489
- search.component.setResult(-1, 0);
490
- this.requestRender();
491
- }
492
-
493
- private navigateSearch(direction: -1 | 1): void {
494
- const search = this.activeSearch;
495
- if (!search?.query) return;
496
- search.selectionMode = direction < 0 ? "previous" : "next";
497
- this.requestRender();
498
- }
499
-
500
- private refreshSearch(layout: LayoutFrame): boolean {
501
- const search = this.activeSearch;
502
- if (!search) return false;
503
- const scrollView = layout.primaryScrollView ?? this.implicitScrollView;
504
- const box = getScrollViewBox(layout, scrollView);
505
- const lines = box?.scrollContentLines;
506
- if (!lines || !search.query.trim()) {
507
- search.matches = [];
508
- search.selectedIndex = -1;
509
- search.selectedKey = undefined;
510
- search.selectionMode = "retain";
511
- search.component.setResult(-1, 0);
512
- return false;
513
- }
514
-
515
- const shouldRevealSelection = search.selectionMode !== "retain";
516
- const matches = findAltScreenSearchMatches(lines, search.query);
517
- const exactIndex = search.selectedKey
518
- ? matches.findIndex((match) => getAltScreenSearchMatchKey(match) === search.selectedKey)
519
- : -1;
520
- let selectedIndex = -1;
521
- if (matches.length > 0) {
522
- if (search.selectionMode === "query") {
523
- selectedIndex = matches.findIndex((match) => (match.segments[0]?.row ?? 0) >= search.anchorRow);
524
- if (selectedIndex < 0) selectedIndex = 0;
525
- } else if (search.selectionMode === "next") {
526
- const baseIndex = exactIndex >= 0 ? exactIndex : Math.min(search.selectedIndex, matches.length - 1);
527
- selectedIndex = baseIndex < 0 ? 0 : (baseIndex + 1) % matches.length;
528
- } else if (search.selectionMode === "previous") {
529
- const baseIndex = exactIndex >= 0 ? exactIndex : Math.min(search.selectedIndex, matches.length - 1);
530
- selectedIndex = baseIndex < 0 ? matches.length - 1 : (baseIndex - 1 + matches.length) % matches.length;
531
- } else {
532
- selectedIndex =
533
- exactIndex >= 0 ? exactIndex : Math.min(Math.max(0, search.selectedIndex), matches.length - 1);
534
- }
535
- }
536
-
537
- search.matches = matches;
538
- search.selectedIndex = selectedIndex;
539
- search.selectedKey = selectedIndex >= 0 ? getAltScreenSearchMatchKey(matches[selectedIndex]!) : undefined;
540
- search.selectionMode = "retain";
541
- search.component.setResult(selectedIndex, matches.length);
542
- if (!shouldRevealSelection) return false;
543
-
544
- const selected = matches[selectedIndex];
545
- const firstSegment = selected?.segments[0];
546
- const lastSegment = selected?.segments[selected.segments.length - 1];
547
- if (!box || !firstSegment || !lastSegment || scrollView.viewportHeight <= 0) return false;
548
- const before = scrollView.scrollTop;
549
- const visibleBottom = before + scrollView.viewportHeight - 1;
550
- let target = before;
551
- if (firstSegment.row < before || lastSegment.row > visibleBottom) {
552
- target = firstSegment.row - Math.floor(scrollView.viewportHeight / 3);
553
- }
554
- scrollView.scrollTo(target, { disableFollow: true });
555
- return scrollView.scrollTop !== before;
556
- }
557
-
558
- /** Show a transient message in the alternate-screen flash stack. */
559
- flash(message: string, durationMs?: number): void {
560
- this.flashes.flash(message, durationMs);
561
- }
562
-
563
- private shouldDeferViewportInputToOverlay(): boolean {
564
- return this.isOverlayFocused() && this.activeSearch?.overlay?.isFocused() !== true;
565
- }
566
-
567
- private handleViewportInput(data: string): { consume?: boolean } | undefined {
568
- if (data === FOCUS_OUT) {
569
- const hadActiveSelection = this.selectionPressActive;
570
- const hadNonEmptyActiveSelection = hadActiveSelection && this.getSelectionBounds() !== undefined;
571
- this.selectionPressActive = false;
572
- this.stopSelectionAutoScroll();
573
- this.stopScrollbarHover();
574
- this.stopScrollbarDrag();
575
- this.pressedUrl = undefined;
576
- this.selectionDragged = false;
577
- if (hadActiveSelection) {
578
- this.selectionAnchor = undefined;
579
- this.selectionFocus = undefined;
580
- this.selectionGranularity = "character";
581
- this.selectionInitialRange = undefined;
582
- if (hadNonEmptyActiveSelection) this.requestRender();
583
- }
584
- this.lastClick = undefined;
585
- return { consume: true };
586
- }
587
- if (data === FOCUS_IN) return { consume: true };
588
-
589
- const wheelEvent = this.parseWheelEvent(data);
590
- if (wheelEvent) {
591
- if (this.shouldDeferViewportInputToOverlay()) return undefined;
592
- this.routeWheel(wheelEvent);
593
- return { consume: true };
594
- }
595
- const mouseEvent = this.parseSgrMouseEvent(data);
596
- if (mouseEvent) {
597
- if (this.handleRightClickPaste(mouseEvent)) return { consume: true };
598
- const handled = this.handleScrollbarMouseEvent(mouseEvent);
599
- if (!this.scrollbarDrag) this.updateScrollbarHover(mouseEvent.x, mouseEvent.y);
600
- if (!handled) this.handleSelectionMouseEvent(mouseEvent);
601
- return { consume: true };
602
- }
603
- if (this.isMouseSequence(data)) return { consume: true };
604
-
605
- const keybindings = getKeybindings();
606
- const isRelease = isKeyRelease(data);
607
- if (keybindings.matches(data, "tui.altScreen.search")) {
608
- if (!isRelease) this.openSearch();
609
- return { consume: true };
610
- }
611
- if (this.activeSearch?.overlay?.isFocused()) {
612
- if (keybindings.matches(data, "tui.altScreen.searchNext")) {
613
- if (!isRelease) this.navigateSearch(1);
614
- return { consume: true };
615
- }
616
- if (keybindings.matches(data, "tui.altScreen.searchPrevious")) {
617
- if (!isRelease) this.navigateSearch(-1);
618
- return { consume: true };
619
- }
620
- if (keybindings.matches(data, "tui.altScreen.searchClose")) {
621
- if (!isRelease) this.closeSearch();
622
- return { consume: true };
623
- }
624
- }
625
- if (this.shouldDeferViewportInputToOverlay()) return undefined;
626
- if (keybindings.matches(data, "tui.altScreen.pageUp")) {
627
- if (!isRelease) {
628
- this.scrollBy(-Math.max(1, this.getPrimaryScrollView().viewportHeight - PAGE_SCROLL_OVERLAP));
629
- }
630
- return { consume: true };
631
- }
632
- if (keybindings.matches(data, "tui.altScreen.pageDown")) {
633
- if (!isRelease) {
634
- this.scrollBy(Math.max(1, this.getPrimaryScrollView().viewportHeight - PAGE_SCROLL_OVERLAP));
635
- }
636
- return { consume: true };
637
- }
638
- if (keybindings.matches(data, "tui.altScreen.halfPageUp")) {
639
- if (!isRelease) this.scrollBy(-Math.max(1, Math.floor(this.getPrimaryScrollView().viewportHeight / 2)));
640
- return { consume: true };
641
- }
642
- if (keybindings.matches(data, "tui.altScreen.halfPageDown")) {
643
- if (!isRelease) this.scrollBy(Math.max(1, Math.floor(this.getPrimaryScrollView().viewportHeight / 2)));
644
- return { consume: true };
645
- }
646
- if (keybindings.matches(data, "tui.altScreen.lineUp")) {
647
- if (!isRelease) this.scrollBy(-1);
648
- return { consume: true };
649
- }
650
- if (keybindings.matches(data, "tui.altScreen.lineDown")) {
651
- if (!isRelease) this.scrollBy(1);
652
- return { consume: true };
653
- }
654
- if (keybindings.matches(data, "tui.altScreen.previousPrompt")) {
655
- if (!isRelease) this.scrollToPrompt(-1);
656
- return { consume: true };
657
- }
658
- if (keybindings.matches(data, "tui.altScreen.nextPrompt")) {
659
- if (!isRelease) this.scrollToPrompt(1);
660
- return { consume: true };
661
- }
662
- if (keybindings.matches(data, "tui.altScreen.top")) {
663
- if (!isRelease) this.scrollToTop();
664
- return { consume: true };
665
- }
666
- if (keybindings.matches(data, "tui.altScreen.bottom")) {
667
- if (!isRelease) this.scrollToBottom();
668
- return { consume: true };
669
- }
670
- return undefined;
671
- }
672
-
673
- private parseWheelEvent(data: string): WheelEvent | undefined {
674
- const sgr = /^\x1b\[<(\d+);(\d+);(\d+)[Mm]$/.exec(data);
675
- if (sgr) {
676
- const button = Number.parseInt(sgr[1], 10);
677
- if ((button & 64) === 0) return undefined;
678
- const direction = button & 3;
679
- if (direction !== 0 && direction !== 1) return undefined;
680
- return {
681
- direction: direction === 0 ? -1 : 1,
682
- x: Number.parseInt(sgr[2], 10) - 1,
683
- y: Number.parseInt(sgr[3], 10) - 1,
684
- };
685
- }
686
- if (data.length === 6 && data.startsWith("\x1b[M")) {
687
- const button = data.charCodeAt(3) - 32;
688
- if ((button & 64) === 0) return undefined;
689
- const direction = button & 3;
690
- if (direction !== 0 && direction !== 1) return undefined;
691
- return {
692
- direction: direction === 0 ? -1 : 1,
693
- x: data.charCodeAt(4) - 33,
694
- y: data.charCodeAt(5) - 33,
695
- };
696
- }
697
- return undefined;
698
- }
699
-
700
- private routeWheel(event: WheelEvent): void {
701
- let remaining = event.direction * this.wheelScrollLines;
702
- const seen = new Set<ScrollView>();
703
- for (const scrollView of this.currentLayout ? getScrollViewsAt(this.currentLayout, event.x, event.y) : []) {
704
- seen.add(scrollView);
705
- remaining = scrollView.scrollBy(remaining);
706
- if (remaining === 0 || scrollView.overscroll === "contain") break;
707
- }
708
- const primary = this.getPrimaryScrollView();
709
- if (remaining !== 0 && !seen.has(primary)) primary.scrollBy(remaining);
710
- this.updateScrollbarHover(event.x, event.y);
711
- this.requestRender();
712
- }
713
-
714
- private parseSgrMouseEvent(data: string): SgrMouseEvent | undefined {
715
- const match = /^\x1b\[<(\d+);(\d+);(\d+)([Mm])$/.exec(data);
716
- if (!match) return undefined;
717
- return {
718
- button: Number.parseInt(match[1], 10),
719
- x: Number.parseInt(match[2], 10) - 1,
720
- y: Number.parseInt(match[3], 10) - 1,
721
- release: match[4] === "m",
722
- };
723
- }
724
-
725
- private handleRightClickPaste(event: SgrMouseEvent): boolean {
726
- if (
727
- !this.onRightClickPaste ||
728
- process.platform !== "win32" ||
729
- process.env.TERM_PROGRAM?.toLowerCase() === "vscode" ||
730
- event.release ||
731
- event.button !== 2
732
- ) {
733
- return false;
734
- }
735
- try {
736
- this.onRightClickPaste();
737
- } catch {
738
- // Clipboard paste is best-effort.
739
- }
740
- return true;
741
- }
742
-
743
- private getScrollbarTargetAt(x: number, y: number): ScrollbarTarget | undefined {
744
- if (this.hasOverlay() || !this.currentLayout) return undefined;
745
- for (const scrollView of getScrollViewsAt(this.currentLayout, x, y)) {
746
- const box = getScrollViewBox(this.currentLayout, scrollView);
747
- const geometry = box ? getScrollbarGeometry(box) : undefined;
748
- if (
749
- geometry &&
750
- x === geometry.column &&
751
- y >= geometry.thumbTop &&
752
- y < geometry.thumbTop + geometry.thumbHeight
753
- ) {
754
- return { scrollView, geometry };
755
- }
756
- }
757
- return undefined;
758
- }
759
-
760
- private setScrollbarHover(scrollView: ScrollView | undefined): void {
761
- if (scrollView === this.scrollbarHover) return;
762
- this.scrollbarHover?.setScrollbarActive(false);
763
- this.scrollbarHover = scrollView;
764
- this.scrollbarHover?.setScrollbarActive(true);
765
- }
766
-
767
- private updateScrollbarHover(x: number, y: number): void {
768
- this.setScrollbarHover(this.getScrollbarTargetAt(x, y)?.scrollView);
769
- }
770
-
771
- private stopScrollbarHover(): void {
772
- this.setScrollbarHover(undefined);
773
- }
774
-
775
- private handleScrollbarMouseEvent(event: SgrMouseEvent): boolean {
776
- if (this.scrollbarDrag) {
777
- if (event.release) {
778
- this.stopScrollbarDrag();
779
- return true;
780
- }
781
- const box = this.currentLayout
782
- ? getScrollViewBox(this.currentLayout, this.scrollbarDrag.scrollView)
783
- : undefined;
784
- const geometry = box ? getScrollbarGeometry(box) : undefined;
785
- if (geometry) {
786
- const maxThumbOffset = geometry.trackHeight - geometry.thumbHeight;
787
- const thumbOffset = Math.max(
788
- 0,
789
- Math.min(maxThumbOffset, event.y - geometry.trackTop - this.scrollbarDrag.grabOffset),
790
- );
791
- const scrollTop =
792
- maxThumbOffset === 0 ? 0 : Math.round((thumbOffset / maxThumbOffset) * geometry.maxScrollTop);
793
- this.scrollbarDrag.scrollView.scrollTo(scrollTop);
794
- }
795
- return true;
796
- }
797
-
798
- if (event.release || (event.button & 32) !== 0 || (event.button & 3) !== 0) return false;
799
- const target = this.getScrollbarTargetAt(event.x, event.y);
800
- if (!target) return false;
801
- this.stopSelectionAutoScroll();
802
- this.selectionPressActive = false;
803
- this.selectionAnchor = undefined;
804
- this.selectionFocus = undefined;
805
- this.selectionGranularity = "character";
806
- this.selectionInitialRange = undefined;
807
- this.lastClick = undefined;
808
- this.pressedUrl = undefined;
809
- this.selectionDragged = false;
810
- this.setScrollbarHover(target.scrollView);
811
- this.scrollbarDrag = {
812
- scrollView: target.scrollView,
813
- grabOffset: event.y - target.geometry.thumbTop,
814
- };
815
- return true;
816
- }
817
-
818
- private stopScrollbarDrag(): void {
819
- this.scrollbarDrag = undefined;
820
- }
821
-
822
- private getScrollSelectionPoint(scrollView: ScrollView, x: number, y: number): SelectionPoint | undefined {
823
- if (!this.currentLayout) return undefined;
824
- const box = getScrollViewBox(this.currentLayout, scrollView);
825
- if (!box || box.rect.height <= 0 || box.clip.height <= 0) return undefined;
826
- const visibleTop = Math.max(0, box.rect.y, box.clip.y);
827
- const visibleBottom = Math.min(
828
- this.terminal.rows - 1,
829
- box.rect.y + box.rect.height - 1,
830
- box.clip.y + box.clip.height - 1,
831
- );
832
- if (visibleBottom < visibleTop) return undefined;
833
- const pointerRow = Math.max(visibleTop, Math.min(visibleBottom, y));
834
- const maxContentRow = Math.max(0, (box.scrollContentLines?.length ?? 1) - 1);
835
- return {
836
- row: Math.max(0, Math.min(maxContentRow, scrollView.scrollTop + pointerRow - box.rect.y)),
837
- col: Math.max(0, Math.min(box.rect.width - 1, x - box.rect.x)),
838
- scrollView,
839
- };
840
- }
841
-
842
- private getSelectionPoint(event: SgrMouseEvent, scrollView?: ScrollView): SelectionPoint {
843
- if (scrollView) {
844
- const point = this.getScrollSelectionPoint(scrollView, event.x, event.y);
845
- if (point) return point;
846
- }
847
- return {
848
- row: Math.max(0, Math.min(this.terminal.rows - 1, event.y)),
849
- col: Math.max(0, Math.min(this.terminal.columns - 1, event.x)),
850
- };
851
- }
852
-
853
- private getSelectionSourceLine(point: SelectionPoint): string {
854
- if (point.scrollView && this.currentLayout) {
855
- const lines = getScrollViewBox(this.currentLayout, point.scrollView)?.scrollContentLines;
856
- if (lines) return lines[point.row] ?? "";
857
- }
858
- return this.previousScreen[point.row] ?? "";
859
- }
860
-
861
- private getWordSelection(point: SelectionPoint): SelectionRange | undefined {
862
- const line = stripTerminalSequences(this.getSelectionSourceLine(point));
863
- const segments: Array<{ start: number; end: number; selectable: boolean; joiner: boolean }> = [];
864
- let start = 0;
865
- for (const segment of wordSegmenter.segment(line)) {
866
- const end = start + visibleWidth(segment.segment);
867
- const joiner = TERMINAL_WORD_SELECTION_JOINERS.has(segment.segment);
868
- segments.push({ start, end, selectable: segment.isWordLike === true || joiner, joiner });
869
- start = end;
870
- }
871
- const clickedSegmentIndex = segments.findIndex(
872
- (segment) => point.col >= segment.start && point.col < segment.end,
873
- );
874
- if (clickedSegmentIndex < 0) return undefined;
875
-
876
- const canJoin = (
877
- left: { selectable: boolean; joiner: boolean },
878
- right: { selectable: boolean; joiner: boolean },
879
- ): boolean => left.selectable && right.selectable && (left.joiner || right.joiner);
880
- let selectionStart = segments[clickedSegmentIndex].start;
881
- let selectionEnd = segments[clickedSegmentIndex].end;
882
- for (let index = clickedSegmentIndex; index > 0 && canJoin(segments[index - 1], segments[index]); index--) {
883
- selectionStart = segments[index - 1].start;
884
- }
885
- for (
886
- let index = clickedSegmentIndex;
887
- index < segments.length - 1 && canJoin(segments[index], segments[index + 1]);
888
- index++
889
- ) {
890
- selectionEnd = segments[index + 1].end;
891
- }
892
- return {
893
- start: { ...point, col: selectionStart },
894
- end: { ...point, col: selectionEnd, boundary: true },
895
- };
896
- }
897
-
898
- private getLineSelection(point: SelectionPoint): SelectionRange {
899
- return {
900
- start: { ...point, col: 0 },
901
- end: { ...point, col: visibleWidth(this.getSelectionSourceLine(point)), boundary: true },
902
- };
903
- }
904
-
905
- private updateSelectionFocus(point: SelectionPoint): void {
906
- if (this.selectionGranularity === "character" || !this.selectionInitialRange) {
907
- this.selectionFocus = point;
908
- return;
909
- }
910
- const range = this.selectionGranularity === "word" ? this.getWordSelection(point) : this.getLineSelection(point);
911
- if (!range) return;
912
- const initial = this.selectionInitialRange;
913
- const targetBeforeInitial =
914
- range.start.row < initial.start.row ||
915
- (range.start.row === initial.start.row && range.start.col < initial.start.col);
916
- if (targetBeforeInitial) {
917
- this.selectionAnchor = initial.end;
918
- this.selectionFocus = range.start;
919
- } else {
920
- this.selectionAnchor = initial.start;
921
- this.selectionFocus = range.end;
922
- }
923
- }
924
-
925
- private getClickCount(point: SelectionPoint, word: SelectionRange | undefined): number {
926
- const now = Date.now();
927
- const previous = this.lastClick;
928
- const count =
929
- word &&
930
- previous &&
931
- now - previous.timestamp <= DOUBLE_CLICK_INTERVAL_MS &&
932
- previous.row === point.row &&
933
- previous.scrollView === point.scrollView &&
934
- previous.wordStart === word.start.col &&
935
- previous.wordEnd === word.end.col
936
- ? (previous.count % 3) + 1
937
- : 1;
938
- this.lastClick = word
939
- ? {
940
- timestamp: now,
941
- count,
942
- row: point.row,
943
- scrollView: point.scrollView,
944
- wordStart: word.start.col,
945
- wordEnd: word.end.col,
946
- }
947
- : undefined;
948
- return count;
949
- }
950
-
951
- private updateSelectionAutoScroll(event: SgrMouseEvent): void {
952
- const scrollView = this.selectionAnchor?.scrollView;
953
- if (!scrollView || !this.currentLayout) {
954
- this.stopSelectionAutoScroll();
955
- return;
956
- }
957
- const box = getScrollViewBox(this.currentLayout, scrollView);
958
- if (!box || box.rect.height <= 0 || box.clip.height <= 0) {
959
- this.stopSelectionAutoScroll();
960
- return;
961
- }
962
- const visibleTop = Math.max(0, box.rect.y, box.clip.y);
963
- const visibleBottom = Math.min(
964
- this.terminal.rows - 1,
965
- box.rect.y + box.rect.height - 1,
966
- box.clip.y + box.clip.height - 1,
967
- );
968
- this.selectionDragPointer = { x: event.x, y: event.y };
969
- this.selectionAutoScrollDirection = event.y <= visibleTop ? -1 : event.y >= visibleBottom ? 1 : 0;
970
- if (this.selectionAutoScrollDirection === 0) {
971
- this.stopSelectionAutoScroll();
972
- return;
973
- }
974
- if (this.selectionAutoScrollTimer) return;
975
- this.selectionAutoScrollTimer = setInterval(() => this.autoScrollSelection(), 50);
976
- this.selectionAutoScrollTimer.unref();
977
- }
978
-
979
- private autoScrollSelection(): void {
980
- const scrollView = this.selectionAnchor?.scrollView;
981
- const pointer = this.selectionDragPointer;
982
- const direction = this.selectionAutoScrollDirection;
983
- if (!scrollView || !pointer || direction === 0) {
984
- this.stopSelectionAutoScroll();
985
- return;
986
- }
987
- const remaining = scrollView.scrollBy(direction);
988
- if (remaining === direction) {
989
- this.stopSelectionAutoScroll();
990
- return;
991
- }
992
- const point = this.getScrollSelectionPoint(scrollView, pointer.x, pointer.y);
993
- if (point) this.updateSelectionFocus(point);
994
- this.requestRender();
995
- }
996
-
997
- private stopSelectionAutoScroll(): void {
998
- if (this.selectionAutoScrollTimer) {
999
- clearInterval(this.selectionAutoScrollTimer);
1000
- this.selectionAutoScrollTimer = undefined;
1001
- }
1002
- this.selectionAutoScrollDirection = 0;
1003
- this.selectionDragPointer = undefined;
1004
- }
1005
-
1006
- private handleSelectionMouseEvent(event: SgrMouseEvent): void {
1007
- const button = event.button & 3;
1008
- if (button !== 0 && !(event.release && button === 3)) return;
1009
- const anchorScrollView = this.selectionAnchor?.scrollView;
1010
- const point = this.getSelectionPoint(event, anchorScrollView);
1011
- if (event.release) {
1012
- if (!this.selectionPressActive) return;
1013
- this.selectionPressActive = false;
1014
- this.stopSelectionAutoScroll();
1015
- if (!this.selectionAnchor) return;
1016
- this.updateSelectionFocus(point);
1017
- const clickedUrl =
1018
- !this.selectionDragged &&
1019
- this.selectionAnchor.scrollView === point.scrollView &&
1020
- this.selectionAnchor.row === point.row &&
1021
- this.selectionAnchor.col === point.col
1022
- ? this.pressedUrl
1023
- : undefined;
1024
- this.pressedUrl = undefined;
1025
- if (clickedUrl && this.openUrl) {
1026
- this.selectionAnchor = undefined;
1027
- this.selectionFocus = undefined;
1028
- try {
1029
- this.openUrl(clickedUrl);
1030
- } catch {
1031
- // URL activation is best-effort.
1032
- }
1033
- this.requestRender();
1034
- return;
1035
- }
1036
- if (this.copyOnSelect) void this.copySelectionToClipboard();
1037
- this.requestRender();
1038
- return;
1039
- }
1040
- if ((event.button & 32) !== 0) {
1041
- if (!this.selectionPressActive || !this.selectionAnchor) return;
1042
- this.selectionDragged = true;
1043
- this.lastClick = undefined;
1044
- this.pressedUrl = undefined;
1045
- this.updateSelectionFocus(point);
1046
- this.updateSelectionAutoScroll(event);
1047
- this.requestRender();
1048
- return;
1049
- }
1050
- this.stopSelectionAutoScroll();
1051
- this.selectionPressActive = true;
1052
- const scrollView =
1053
- !this.hasOverlay() && this.currentLayout
1054
- ? getScrollViewsAt(this.currentLayout, event.x, event.y)[0]
1055
- : undefined;
1056
- const anchor = this.getSelectionPoint(event, scrollView);
1057
- const word = this.getWordSelection(anchor);
1058
- const clickCount = this.getClickCount(anchor, word);
1059
- const range = clickCount === 2 ? word : clickCount === 3 ? this.getLineSelection(anchor) : undefined;
1060
- this.selectionGranularity = range ? (clickCount === 2 ? "word" : "line") : "character";
1061
- this.selectionInitialRange = range;
1062
- this.selectionAnchor = range?.start ?? anchor;
1063
- this.selectionFocus = range?.end ?? anchor;
1064
- this.selectionDragged = false;
1065
- this.pressedUrl = range
1066
- ? undefined
1067
- : getOsc8LinkAtColumn(
1068
- this.previousScreen[Math.max(0, Math.min(this.terminal.rows - 1, event.y))] ?? "",
1069
- Math.max(0, Math.min(this.terminal.columns - 1, event.x)),
1070
- );
1071
- this.requestRender();
1072
- }
1073
-
1074
- private getSelectionBounds(): { start: SelectionPoint; end: SelectionPoint } | undefined {
1075
- if (!this.selectionAnchor || !this.selectionFocus) return undefined;
1076
- if (this.selectionAnchor.scrollView !== this.selectionFocus.scrollView) return undefined;
1077
- const anchorBeforeFocus =
1078
- this.selectionAnchor.row < this.selectionFocus.row ||
1079
- (this.selectionAnchor.row === this.selectionFocus.row && this.selectionAnchor.col < this.selectionFocus.col);
1080
- if (
1081
- this.selectionAnchor.row === this.selectionFocus.row &&
1082
- this.selectionAnchor.col === this.selectionFocus.col
1083
- ) {
1084
- return undefined;
1085
- }
1086
- return anchorBeforeFocus
1087
- ? { start: this.selectionAnchor, end: this.selectionFocus }
1088
- : { start: this.selectionFocus, end: this.selectionAnchor };
1089
- }
1090
-
1091
- private getSelectionColumns(
1092
- line: string,
1093
- row: number,
1094
- selection: { start: SelectionPoint; end: SelectionPoint },
1095
- minColumn = 0,
1096
- maxColumn = visibleWidth(line),
1097
- ): { start: number; end: number } {
1098
- const lineWidth = visibleWidth(line);
1099
- let start = Math.max(0, minColumn);
1100
- let end = Math.min(lineWidth, maxColumn);
1101
- if (row === selection.start.row) {
1102
- start = getGraphemeCellRange(line, selection.start.col)?.start ?? Math.min(selection.start.col, lineWidth);
1103
- }
1104
- if (row === selection.end.row) {
1105
- end = selection.end.boundary
1106
- ? Math.min(selection.end.col, lineWidth)
1107
- : (getGraphemeCellRange(line, selection.end.col)?.end ?? Math.min(selection.end.col + 1, lineWidth));
1108
- }
1109
- return { start: Math.max(minColumn, start), end: Math.min(maxColumn, end) };
1110
- }
1111
-
1112
- private getActiveSelectionText(): string | undefined {
1113
- const selection = this.getSelectionBounds();
1114
- if (!selection) return undefined;
1115
- let sourceLines: readonly string[] = this.previousScreen;
1116
- if (selection.start.scrollView) {
1117
- if (!this.currentLayout) return undefined;
1118
- const box = getScrollViewBox(this.currentLayout, selection.start.scrollView);
1119
- if (!box?.scrollContentLines) return undefined;
1120
- sourceLines = box.scrollContentLines;
1121
- }
1122
- const lines: string[] = [];
1123
- for (let row = selection.start.row; row <= selection.end.row; row++) {
1124
- const line = sourceLines[row] ?? "";
1125
- const columns = this.getSelectionColumns(line, row, selection);
1126
- lines.push(
1127
- stripTerminalSequences(
1128
- sliceByColumn(line, columns.start, Math.max(0, columns.end - columns.start), true),
1129
- ).trimEnd(),
1130
- );
1131
- }
1132
- const text = lines.join("\n");
1133
- return text.length === 0 ? undefined : text;
1134
- }
1135
-
1136
- private async copySelectionToClipboard(): Promise<boolean> {
1137
- const text = this.getActiveSelectionText();
1138
- if (!text) return false;
1139
- return this.copyTextToClipboard(text);
1140
- }
1141
-
1142
- private async copyTextToClipboard(text: string): Promise<boolean> {
1143
- // Prefer an injected clipboard implementation (native clipboard + platform tools with a
1144
- // verified success path) when the host app provides one. A bare OSC 52 write can show
1145
- // "Copied!" while leaving the system clipboard untouched (e.g. macOS Terminal.app, tmux
1146
- // without OSC 52 clipboard passthrough), so only report success when it actually copies.
1147
- if (this.copySelection) {
1148
- const ok = await this.copySelection(text);
1149
- this.flash(ok ? "Copied!" : "Copy failed");
1150
- return ok;
1151
- }
1152
- this.terminal.write(`\x1b]52;c;${Buffer.from(text).toString("base64")}\x07`);
1153
- this.flash("Copied!");
1154
- return true;
1155
- }
1156
-
1157
- private applySearchTextHighlight(text: string, current: boolean): string {
1158
- const style = current ? this.searchCurrentMatchStyle : this.searchMatchStyle;
1159
- let result = "";
1160
- let plainStart = 0;
1161
- let index = 0;
1162
- while (index < text.length) {
1163
- const ansi = extractAnsiCode(text, index);
1164
- if (!ansi) {
1165
- index += 1;
1166
- continue;
1167
- }
1168
- if (index > plainStart) result += style(text.slice(plainStart, index));
1169
- result += ansi.code;
1170
- index += ansi.length;
1171
- plainStart = index;
1172
- }
1173
- if (plainStart < text.length) result += style(text.slice(plainStart));
1174
- return result;
1175
- }
1176
-
1177
- private applySearchHighlights(screen: string[], layout: LayoutFrame): string[] {
1178
- const search = this.activeSearch;
1179
- if (!search || search.selectedIndex < 0 || search.matches.length === 0) return screen;
1180
- const scrollView = layout.primaryScrollView ?? this.implicitScrollView;
1181
- const box = getScrollViewBox(layout, scrollView);
1182
- if (!box) return screen;
1183
-
1184
- const rangesByRow = new Map<number, SearchHighlightRange[]>();
1185
- const scrollbarColumn = getScrollbarGeometry(box)?.column;
1186
- const minRow = Math.max(0, box.rect.y, box.clip.y);
1187
- const maxRow = Math.min(screen.length, box.rect.y + box.rect.height, box.clip.y + box.clip.height);
1188
- const minColumn = Math.max(0, box.rect.x, box.clip.x);
1189
- const maxColumn = Math.min(
1190
- this.terminal.columns,
1191
- box.rect.x + box.rect.width,
1192
- box.clip.x + box.clip.width,
1193
- scrollbarColumn ?? Number.POSITIVE_INFINITY,
1194
- );
1195
- for (let matchIndex = 0; matchIndex < search.matches.length; matchIndex++) {
1196
- for (const segment of search.matches[matchIndex]!.segments) {
1197
- const row = box.rect.y + segment.row - scrollView.scrollTop;
1198
- if (row < minRow || row >= maxRow) continue;
1199
- const startCol = Math.max(minColumn, box.rect.x + segment.startCol);
1200
- const endCol = Math.min(maxColumn, box.rect.x + segment.endCol);
1201
- if (endCol <= startCol) continue;
1202
- const ranges = rangesByRow.get(row) ?? [];
1203
- ranges.push({ startCol, endCol, current: matchIndex === search.selectedIndex });
1204
- rangesByRow.set(row, ranges);
1205
- }
1206
- }
1207
-
1208
- const result = [...screen];
1209
- for (const [row, ranges] of rangesByRow) {
1210
- let line = result[row] ?? "";
1211
- if (isImageLine(line)) continue;
1212
- const lineWidth = visibleWidth(line);
1213
- for (const range of ranges.sort((a, b) => b.startCol - a.startCol)) {
1214
- const startCol = Math.min(range.startCol, lineWidth);
1215
- const endCol = Math.min(range.endCol, lineWidth);
1216
- if (endCol <= startCol) continue;
1217
- const before = sliceByColumn(line, 0, startCol, true);
1218
- const highlighted = sliceByColumn(line, startCol, endCol - startCol, true);
1219
- const after = sliceByColumn(line, endCol, Math.max(0, lineWidth - endCol), true);
1220
- line = `${before}${this.applySearchTextHighlight(highlighted, range.current)}${after}`;
1221
- }
1222
- result[row] = line;
1223
- }
1224
- return result;
1225
- }
1226
-
1227
- private applySelectionHighlight(text: string): string {
1228
- let result = "\x1b[7m";
1229
- let index = 0;
1230
- while (index < text.length) {
1231
- const ansi = extractAnsiCode(text, index);
1232
- if (!ansi) {
1233
- result += text[index];
1234
- index += 1;
1235
- continue;
1236
- }
1237
- result += ansi.code;
1238
- if (ansi.code.endsWith("m")) result += "\x1b[7m";
1239
- index += ansi.length;
1240
- }
1241
- return `${result}\x1b[27m`;
1242
- }
1243
-
1244
- private applySelection(screen: string[], layout = this.currentLayout): string[] {
1245
- const selection = this.getSelectionBounds();
1246
- if (!selection) return screen;
1247
- let screenSelection = selection;
1248
- let minRow = 0;
1249
- let maxRow = screen.length - 1;
1250
- let minColumn = 0;
1251
- let maxColumn = this.terminal.columns;
1252
- if (selection.start.scrollView) {
1253
- if (!layout) return screen;
1254
- const box = getScrollViewBox(layout, selection.start.scrollView);
1255
- if (!box) return screen;
1256
- minRow = Math.max(0, box.rect.y, box.clip.y);
1257
- maxRow = Math.min(screen.length - 1, box.rect.y + box.rect.height - 1, box.clip.y + box.clip.height - 1);
1258
- minColumn = Math.max(0, box.rect.x, box.clip.x);
1259
- maxColumn = Math.min(this.terminal.columns, box.rect.x + box.rect.width, box.clip.x + box.clip.width);
1260
- screenSelection = {
1261
- start: {
1262
- ...selection.start,
1263
- row: box.rect.y + selection.start.row - selection.start.scrollView.scrollTop,
1264
- col: box.rect.x + selection.start.col,
1265
- },
1266
- end: {
1267
- ...selection.end,
1268
- row: box.rect.y + selection.end.row - selection.start.scrollView.scrollTop,
1269
- col: box.rect.x + selection.end.col,
1270
- },
1271
- };
1272
- }
1273
- return screen.map((line, row) => {
1274
- if (
1275
- row < minRow ||
1276
- row > maxRow ||
1277
- row < screenSelection.start.row ||
1278
- row > screenSelection.end.row ||
1279
- isImageLine(line)
1280
- ) {
1281
- return line;
1282
- }
1283
- const lineWidth = visibleWidth(line);
1284
- const columns = this.getSelectionColumns(line, row, screenSelection, minColumn, maxColumn);
1285
- if (columns.end <= columns.start) return line;
1286
- const before = sliceByColumn(line, 0, columns.start, true);
1287
- const selected = sliceByColumn(line, columns.start, columns.end - columns.start, true);
1288
- const after = sliceByColumn(line, columns.end, Math.max(0, lineWidth - columns.end), true);
1289
- return `${before}${this.applySelectionHighlight(selected)}${after}`;
1290
- });
1291
- }
1292
-
1293
- private isMouseSequence(data: string): boolean {
1294
- return /^\x1b\[<\d+;\d+;\d+[Mm]$/.test(data) || (data.length === 6 && data.startsWith("\x1b[M"));
1295
- }
1296
-
1297
- private compositeFlashes(screen: string[], width: number, height: number): string[] {
1298
- const flashLines = this.flashes.render(width).slice(-height);
1299
- if (flashLines.length === 0) return screen;
1300
- const result = [...screen];
1301
- while (result.length < height) result.push("");
1302
- for (let row = 0; row < flashLines.length; row++) {
1303
- const line = flashLines[row]!;
1304
- const flashWidth = visibleWidth(line);
1305
- if (flashWidth === 0) continue;
1306
- result[row] = compositeTuiLine(result[row] ?? "", line, width - flashWidth, flashWidth, width);
1307
- }
1308
- return result;
1309
- }
1310
-
1311
- protected override doRender(): void {
1312
- if (this.stopped || !this.altScreenActive) return;
1313
- const width = Math.max(1, this.terminal.columns);
1314
- const height = Math.max(1, this.terminal.rows);
1315
- const root = this.layoutRoot ?? this.implicitScrollView;
1316
- let nextLayout = renderLayoutFrame(root, width, height, () => this.requestRender());
1317
- if (this.refreshSearch(nextLayout)) {
1318
- nextLayout = renderLayoutFrame(root, width, height, () => this.requestRender());
1319
- }
1320
- let screen = nextLayout.lines.map((line) => line.replace(OSC133_ZONE_PREFIX, ""));
1321
- screen = this.applySearchHighlights(screen, nextLayout);
1322
- screen = this.compositeOverlays(screen, width, height);
1323
- if (screen.length > height) screen = screen.slice(screen.length - height);
1324
- screen = this.applySelection(screen, nextLayout);
1325
- screen = this.compositeFlashes(screen, width, height);
1326
-
1327
- const cursorPos = this.extractCursorPosition(screen, height);
1328
- screen = this.applyLineResets(screen).map((line) => {
1329
- if (isImageLine(line) || visibleWidth(line) <= width) return line;
1330
- return sliceByColumn(line, 0, width, true);
1331
- });
1332
-
1333
- const fullRedraw =
1334
- this.previousScreen.length === 0 || this.previousScreenWidth !== width || this.previousScreenHeight !== height;
1335
- const imagesNeedRedraw = screen.some(
1336
- (line, row) =>
1337
- line !== this.previousScreen[row] && (isImageLine(line) || isImageLine(this.previousScreen[row] ?? "")),
1338
- );
1339
- const redrawImages = fullRedraw || imagesNeedRedraw;
1340
- const hadUploadedKittyImages = this.uploadedKittyImages.size > 0;
1341
- const preparedKittyScreen =
1342
- redrawImages && this.imageProtocol === "kitty"
1343
- ? this.prepareKittyScreen(screen)
1344
- : { lines: screen, evictedImageDeletion: "" };
1345
-
1346
- let buffer = BEGIN_SYNCHRONIZED_OUTPUT;
1347
- if (fullRedraw) {
1348
- this.fullRedrawCount += 1;
1349
- const clearImages =
1350
- this.imageProtocol === "kitty" && hadUploadedKittyImages
1351
- ? deleteAllKittyPlacements()
1352
- : this.deleteKittyImages();
1353
- buffer += `${clearImages}\x1b[2J`;
1354
- } else if (imagesNeedRedraw) {
1355
- if (this.imageProtocol === "iterm2") buffer += "\x1b[2J";
1356
- else if (this.imageProtocol === "kitty") buffer += deleteAllKittyPlacements();
1357
- }
1358
- buffer += preparedKittyScreen.evictedImageDeletion;
1359
-
1360
- for (let row = 0; row < height; row++) {
1361
- if (!fullRedraw && !imagesNeedRedraw && screen[row] === this.previousScreen[row]) continue;
1362
- buffer += `\x1b[${row + 1};1H\x1b[2K${preparedKittyScreen.lines[row] ?? ""}`;
1363
- }
1364
-
1365
- if (cursorPos) {
1366
- buffer += `\x1b[${cursorPos.row + 1};${Math.min(width, cursorPos.col) + 1}H`;
1367
- buffer += this.getShowHardwareCursor() ? "\x1b[?25h" : "\x1b[?25l";
1368
- } else {
1369
- buffer += "\x1b[?25l";
1370
- }
1371
- buffer += END_SYNCHRONIZED_OUTPUT;
1372
- this.terminal.write(buffer);
1373
-
1374
- this.previousScreen = screen;
1375
- this.previousScreenWidth = width;
1376
- this.previousScreenHeight = height;
1377
- this.currentLayout = nextLayout;
1378
- }
1379
- }