@visual-z/xeno 0.2.1 → 0.4.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 (282) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +126 -34
  3. package/docs/app-server.md +84 -0
  4. package/docs/install.md +51 -13
  5. package/docs/providers.md +24 -3
  6. package/package.json +7 -5
  7. package/src/apps/app-server.ts +28 -0
  8. package/src/apps/approve.ts +3 -3
  9. package/src/apps/cli.ts +25 -10
  10. package/src/apps/commands.ts +45 -8
  11. package/src/apps/completions.ts +78 -22
  12. package/src/apps/control-transport.ts +89 -0
  13. package/src/apps/image-draft.ts +46 -0
  14. package/src/apps/model-catalog.ts +2 -1
  15. package/src/apps/project-trust.ts +2 -42
  16. package/src/apps/provider-connect.ts +346 -0
  17. package/src/apps/render.ts +22 -47
  18. package/src/apps/resources.ts +5 -2
  19. package/src/apps/shell.ts +333 -1542
  20. package/src/apps/startup-input.ts +45 -9
  21. package/src/apps/startup.ts +149 -94
  22. package/src/apps/ui/app.ts +1553 -0
  23. package/src/apps/ui/control-model.ts +26 -0
  24. package/src/apps/ui/extension-host.ts +194 -0
  25. package/src/apps/ui/image-cache.ts +78 -0
  26. package/src/apps/ui/palette.ts +88 -0
  27. package/src/apps/ui/project.ts +127 -0
  28. package/src/apps/ui/standalone.ts +146 -0
  29. package/src/apps/ui/status-model.ts +68 -0
  30. package/src/apps/ui/surface.ts +86 -0
  31. package/src/apps/ui/theme.ts +86 -0
  32. package/src/core/acceptance.ts +107 -0
  33. package/src/core/action-defaults.ts +47 -295
  34. package/src/core/benchmark.ts +2 -2
  35. package/src/core/branch-receipt.ts +24 -0
  36. package/src/core/checkpoints.ts +64 -0
  37. package/src/core/churn.ts +2 -0
  38. package/src/core/command-artifacts.ts +94 -0
  39. package/src/core/command-contract.ts +35 -0
  40. package/src/core/completion-action.ts +20 -0
  41. package/src/core/completion-review.ts +37 -21
  42. package/src/core/context-detail.ts +37 -0
  43. package/src/core/context-policy.ts +74 -1
  44. package/src/core/continuation.ts +17 -2
  45. package/src/core/control-protocol.ts +62 -0
  46. package/src/core/control-view.ts +42 -0
  47. package/src/core/derive.ts +29 -3
  48. package/src/core/draft-images.ts +56 -0
  49. package/src/core/environment.ts +145 -0
  50. package/src/core/evaluation-coverage.ts +74 -0
  51. package/src/core/event-validation.ts +81 -6
  52. package/src/core/events.ts +99 -3
  53. package/src/core/evidence-links.ts +7 -0
  54. package/src/core/evidence.ts +13 -3
  55. package/src/core/execution.ts +8 -2
  56. package/src/core/experiment.ts +133 -0
  57. package/src/core/export.ts +9 -0
  58. package/src/core/failure-recovery.ts +173 -0
  59. package/src/core/file-precondition.ts +72 -0
  60. package/src/core/handoff.ts +65 -4
  61. package/src/core/harness-diagnostics.ts +206 -0
  62. package/src/core/history-query.ts +38 -2
  63. package/src/core/history.ts +38 -1
  64. package/src/core/integration-health.ts +34 -0
  65. package/src/core/keymap.ts +8 -20
  66. package/src/core/model-picker.ts +64 -0
  67. package/src/core/native-eval.ts +25 -0
  68. package/src/core/note-guidance.ts +34 -0
  69. package/src/core/oauth-fields.ts +44 -0
  70. package/src/core/observation-view.ts +49 -8
  71. package/src/core/permissions.ts +24 -2
  72. package/src/core/process-receipt.ts +105 -0
  73. package/src/core/process-state.ts +37 -0
  74. package/src/core/program-status.ts +31 -0
  75. package/src/core/project.ts +70 -19
  76. package/src/core/prompt-policy.ts +37 -0
  77. package/src/core/query-eval-contract.ts +14 -0
  78. package/src/core/read-observation.ts +82 -0
  79. package/src/core/recovery-corpus.ts +17 -0
  80. package/src/core/recovery-evaluation.ts +60 -0
  81. package/src/core/recovery-sources.ts +74 -0
  82. package/src/core/request-progress.ts +87 -0
  83. package/src/core/request-state.ts +114 -0
  84. package/src/core/runtime-target.ts +94 -0
  85. package/src/core/search-observation.ts +97 -0
  86. package/src/core/session-tree.ts +147 -0
  87. package/src/core/settings.ts +33 -10
  88. package/src/core/skills.ts +44 -0
  89. package/src/core/sql-query.ts +211 -0
  90. package/src/core/theme.ts +36 -58
  91. package/src/core/tool-contract.ts +51 -2
  92. package/src/core/tool-discovery.ts +87 -0
  93. package/src/core/tool-failure.ts +60 -0
  94. package/src/core/tool-input.ts +6 -0
  95. package/src/core/tool-mode-protocol.ts +25 -0
  96. package/src/core/transcript.ts +19 -4
  97. package/src/core/trial-lifecycle.ts +14 -0
  98. package/src/core/tui-mode.ts +29 -0
  99. package/src/core/update-check.ts +97 -0
  100. package/src/core/version.ts +55 -0
  101. package/src/core/work-state.ts +49 -3
  102. package/src/core/working-set.ts +91 -0
  103. package/src/runtime/auth.ts +11 -4
  104. package/src/runtime/branch-jobs.ts +36 -0
  105. package/src/runtime/catalog.ts +13 -3
  106. package/src/runtime/clipboard.ts +15 -4
  107. package/src/runtime/command-artifacts.ts +64 -0
  108. package/src/runtime/control-connection.ts +104 -0
  109. package/src/runtime/environment-tools.ts +60 -0
  110. package/src/runtime/evidence-notes.ts +4 -2
  111. package/src/runtime/exec-sessions.ts +206 -61
  112. package/src/runtime/execution-control.ts +1 -0
  113. package/src/runtime/extension-ui.ts +22 -30
  114. package/src/runtime/extensions.ts +95 -16
  115. package/src/runtime/harness-comparison.ts +27 -7
  116. package/src/runtime/harness-eval.ts +462 -0
  117. package/src/runtime/history-cache.ts +57 -0
  118. package/src/runtime/history-tools.ts +62 -12
  119. package/src/runtime/integrations.ts +210 -0
  120. package/src/runtime/loop.ts +1088 -282
  121. package/src/runtime/model-discovery.ts +208 -0
  122. package/src/runtime/model.ts +9 -0
  123. package/src/runtime/native-eval-fixtures.ts +33 -0
  124. package/src/runtime/native-eval.ts +146 -0
  125. package/src/runtime/oauth.ts +80 -3
  126. package/src/runtime/pi-provider.ts +169 -12
  127. package/src/runtime/program-status.ts +38 -0
  128. package/src/runtime/provider-connect.ts +109 -0
  129. package/src/runtime/provider-presets.ts +87 -0
  130. package/src/runtime/provider-request.ts +13 -2
  131. package/src/runtime/provider.ts +23 -9
  132. package/src/runtime/query-eval-verifier.ts +28 -0
  133. package/src/runtime/recovery-coverage.ts +71 -0
  134. package/src/runtime/recovery-evidence.ts +76 -0
  135. package/src/runtime/recovery-fixtures.ts +116 -0
  136. package/src/runtime/runtime-target.ts +149 -0
  137. package/src/runtime/session-control.ts +198 -0
  138. package/src/runtime/tool-broker.ts +137 -0
  139. package/src/runtime/tool-discovery.ts +57 -0
  140. package/src/runtime/tool-mode-eval.ts +146 -0
  141. package/src/runtime/tool-queue.ts +77 -0
  142. package/src/runtime/tools.ts +628 -183
  143. package/src/runtime/trial-control.ts +45 -0
  144. package/src/runtime/update-check.ts +47 -0
  145. package/src/runtime/window-trial.ts +2 -1
  146. package/src/runtime/working-notes.ts +33 -3
  147. package/src/runtime/workspace-lifecycle.ts +97 -0
  148. package/src/store/acceptance.ts +15 -0
  149. package/src/store/checkpoint-history.ts +43 -0
  150. package/src/store/command-artifacts.ts +96 -0
  151. package/src/store/command.ts +65 -37
  152. package/src/store/file-checkpoints.ts +104 -0
  153. package/src/store/file-index.ts +13 -1
  154. package/src/store/git-checkpoints.ts +67 -0
  155. package/src/store/observations.ts +61 -20
  156. package/src/store/process-receipts.ts +34 -0
  157. package/src/store/query-source.ts +13 -8
  158. package/src/store/recovery.ts +2 -3
  159. package/src/store/replay.ts +16 -4
  160. package/src/store/runtime-target.ts +64 -0
  161. package/src/store/search.ts +171 -132
  162. package/src/store/session-archive.ts +4 -0
  163. package/src/store/source-cancellation.ts +6 -0
  164. package/src/store/update-cache.ts +36 -0
  165. package/src/store/workspace-observation.ts +113 -0
  166. package/src/tui/ansi-spans.ts +126 -0
  167. package/src/tui/style.ts +19 -16
  168. package/src/tui/ui/atomic-draft.ts +121 -0
  169. package/src/tui/ui/canvas.ts +96 -0
  170. package/src/tui/ui/colors.ts +70 -0
  171. package/src/tui/ui/composer.ts +200 -0
  172. package/src/tui/ui/control-panel.ts +23 -0
  173. package/src/tui/ui/dialog.ts +379 -0
  174. package/src/tui/ui/draft.ts +23 -0
  175. package/src/tui/ui/edge.ts +186 -0
  176. package/src/tui/ui/image-size.ts +40 -0
  177. package/src/tui/ui/ink.ts +210 -0
  178. package/src/tui/ui/items.ts +40 -0
  179. package/src/tui/ui/keys.ts +40 -0
  180. package/src/tui/ui/layout.ts +74 -0
  181. package/src/tui/ui/list-model.ts +86 -0
  182. package/src/tui/ui/list-view.ts +172 -0
  183. package/src/tui/ui/mascot.ts +67 -0
  184. package/src/tui/ui/messages.ts +518 -0
  185. package/src/tui/ui/motion.ts +28 -0
  186. package/src/tui/ui/paths.ts +10 -0
  187. package/src/tui/ui/pictures.ts +78 -0
  188. package/src/tui/ui/screen.ts +421 -0
  189. package/src/tui/ui/status.ts +279 -0
  190. package/src/tui/ui/styled.ts +85 -0
  191. package/src/tui/ui/testing.ts +5 -0
  192. package/src/tui/ui/tool-card.ts +150 -0
  193. package/src/apps/extension-ui.ts +0 -330
  194. package/src/apps/resource-browser.ts +0 -75
  195. package/src/apps/session-browser.ts +0 -113
  196. package/src/apps/session-picker.ts +0 -55
  197. package/src/tui/ansi.ts +0 -67
  198. package/src/tui/autocomplete.ts +0 -29
  199. package/src/tui/chat-viewport.ts +0 -47
  200. package/src/tui/complete.ts +0 -87
  201. package/src/tui/editor.ts +0 -940
  202. package/src/tui/host.ts +0 -264
  203. package/src/tui/input.ts +0 -75
  204. package/src/tui/keys.ts +0 -378
  205. package/src/tui/markdown.ts +0 -17
  206. package/src/tui/render.ts +0 -153
  207. package/src/tui/select.ts +0 -258
  208. package/src/tui/settings.ts +0 -44
  209. package/src/tui/statusline.ts +0 -162
  210. package/src/tui/system-theme.ts +0 -700
  211. package/src/tui/terminal.ts +0 -30
  212. package/src/tui/text.ts +0 -52
  213. package/src/tui/toolview.ts +0 -386
  214. package/src/tui/transcript.ts +0 -123
  215. package/src/tui/tree.ts +0 -471
  216. package/src/vendor/pi-tui/LICENSE.upstream +0 -21
  217. package/src/vendor/pi-tui/UPSTREAM.json +0 -50
  218. package/src/vendor/pi-tui/VENDOR.md +0 -25
  219. package/src/vendor/pi-tui/alt-screen-search.ts +0 -327
  220. package/src/vendor/pi-tui/autocomplete.ts +0 -860
  221. package/src/vendor/pi-tui/colors.ts +0 -367
  222. package/src/vendor/pi-tui/components/alt-screen-flash.ts +0 -51
  223. package/src/vendor/pi-tui/components/box.ts +0 -166
  224. package/src/vendor/pi-tui/components/cancellable-loader.ts +0 -40
  225. package/src/vendor/pi-tui/components/editor.ts +0 -2472
  226. package/src/vendor/pi-tui/components/h-stack.ts +0 -44
  227. package/src/vendor/pi-tui/components/image.ts +0 -127
  228. package/src/vendor/pi-tui/components/input.ts +0 -494
  229. package/src/vendor/pi-tui/components/loader.ts +0 -101
  230. package/src/vendor/pi-tui/components/markdown.ts +0 -1021
  231. package/src/vendor/pi-tui/components/mouse-region.ts +0 -33
  232. package/src/vendor/pi-tui/components/scroll-view.ts +0 -224
  233. package/src/vendor/pi-tui/components/select-list.ts +0 -273
  234. package/src/vendor/pi-tui/components/settings-list.ts +0 -328
  235. package/src/vendor/pi-tui/components/spacer.ts +0 -28
  236. package/src/vendor/pi-tui/components/stack.ts +0 -154
  237. package/src/vendor/pi-tui/components/text.ts +0 -107
  238. package/src/vendor/pi-tui/components/truncated-text.ts +0 -65
  239. package/src/vendor/pi-tui/components/v-stack.ts +0 -33
  240. package/src/vendor/pi-tui/editor-component.ts +0 -74
  241. package/src/vendor/pi-tui/fuzzy.ts +0 -138
  242. package/src/vendor/pi-tui/index.ts +0 -186
  243. package/src/vendor/pi-tui/keybindings.ts +0 -320
  244. package/src/vendor/pi-tui/keys.ts +0 -1401
  245. package/src/vendor/pi-tui/kill-ring.ts +0 -46
  246. package/src/vendor/pi-tui/latex.ts +0 -1506
  247. package/src/vendor/pi-tui/layout-node.ts +0 -51
  248. package/src/vendor/pi-tui/layout.ts +0 -449
  249. package/src/vendor/pi-tui/native/UPSTREAM.json +0 -23
  250. package/src/vendor/pi-tui/native/clipboard.h +0 -208
  251. package/src/vendor/pi-tui/native/darwin/README.md +0 -22
  252. package/src/vendor/pi-tui/native/darwin/build.sh +0 -69
  253. package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
  254. package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
  255. package/src/vendor/pi-tui/native/darwin/src/darwin-platform.m +0 -132
  256. package/src/vendor/pi-tui/native/linux/README.md +0 -23
  257. package/src/vendor/pi-tui/native/linux/build.sh +0 -65
  258. package/src/vendor/pi-tui/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
  259. package/src/vendor/pi-tui/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
  260. package/src/vendor/pi-tui/native/linux/src/clipboard-worker.h +0 -70
  261. package/src/vendor/pi-tui/native/linux/src/linux-platform-x11.c +0 -274
  262. package/src/vendor/pi-tui/native/napi.h +0 -91
  263. package/src/vendor/pi-tui/native/win32/README.md +0 -31
  264. package/src/vendor/pi-tui/native/win32/build.mjs +0 -232
  265. package/src/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
  266. package/src/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
  267. package/src/vendor/pi-tui/native/win32/src/win32-platform.c +0 -241
  268. package/src/vendor/pi-tui/native-modifiers.ts +0 -13
  269. package/src/vendor/pi-tui/native-module-path.ts +0 -31
  270. package/src/vendor/pi-tui/native-platform.ts +0 -65
  271. package/src/vendor/pi-tui/oklab.ts +0 -233
  272. package/src/vendor/pi-tui/stdin-buffer.ts +0 -444
  273. package/src/vendor/pi-tui/terminal-colors.ts +0 -91
  274. package/src/vendor/pi-tui/terminal-image.ts +0 -731
  275. package/src/vendor/pi-tui/terminal.ts +0 -554
  276. package/src/vendor/pi-tui/tui-alt-screen.ts +0 -1750
  277. package/src/vendor/pi-tui/tui-main-screen.ts +0 -655
  278. package/src/vendor/pi-tui/tui.ts +0 -1493
  279. package/src/vendor/pi-tui/undo-stack.ts +0 -28
  280. package/src/vendor/pi-tui/utils.ts +0 -1385
  281. package/src/vendor/pi-tui/wheel-scroll.ts +0 -82
  282. package/src/vendor/pi-tui/word-navigation.ts +0 -117
package/src/tui/keys.ts DELETED
@@ -1,378 +0,0 @@
1
- /**
2
- * Key decoding as a pure function: bytes in, events plus an unconsumed
3
- * prefix out.
4
- *
5
- * Escape sequences are split by pty and TCP buffering at arbitrary
6
- * boundaries -- `\x1b[A` (up-arrow) can arrive as `\x1b[` then `A`. A
7
- * callback state machine can only be shown to survive that by luck; a
8
- * function that returns the leftover prefix can be shown by a test.
9
- */
10
-
11
- import { assertNever } from "./exhaustive.ts";
12
-
13
- /**
14
- * xterm ctlseqs modifiers, attached only when a recognised bit is set.
15
- * Absent means unmodified -- `{ type: "left" }` stays that shape so
16
- * existing matches keep working. Meta (bit 3) is not modelled.
17
- */
18
- export interface KeyMods {
19
- readonly shift?: boolean;
20
- readonly alt?: boolean;
21
- readonly ctrl?: boolean;
22
- }
23
-
24
- export type Key =
25
- | { type: "char"; grapheme: string }
26
- | { type: "enter"; shift: boolean }
27
- | { type: "backspace" }
28
- | { type: "delete"; mods?: KeyMods }
29
- | { type: "tab"; shift?: boolean }
30
- | { type: "escape" }
31
- | { type: "up"; mods?: KeyMods }
32
- | { type: "down"; mods?: KeyMods }
33
- | { type: "left"; mods?: KeyMods }
34
- | { type: "right"; mods?: KeyMods }
35
- | { type: "home"; mods?: KeyMods }
36
- | { type: "end"; mods?: KeyMods }
37
- | { type: "pageup"; mods?: KeyMods }
38
- | { type: "pagedown"; mods?: KeyMods }
39
- | { type: "ctrl"; key: string; shift?: boolean }
40
- | { type: "alt"; key: string };
41
-
42
- export interface DecodeResult {
43
- events: Key[];
44
- rest: Uint8Array;
45
- }
46
-
47
- /**
48
- * 50ms. Measured convention, not a standard: xterm, libuv and readline all
49
- * pick a few tens of milliseconds to decide that a lone ESC is the Escape
50
- * key rather than the start of a CSI. Too short and a slow CSI is split into
51
- * Escape plus junk; too long and Escape-to-interrupt feels sticky. The
52
- * decoder itself is still a pure function -- the caller passes `escTimedOut`
53
- * after it has waited, because a timeout is a clock and a clock is not pure.
54
- */
55
- export const ESCAPE_TIMEOUT_MS = 50;
56
-
57
- type Seq =
58
- | { kind: "csi"; params: number[]; final: string }
59
- | { kind: "ss3"; final: string }
60
- | { kind: "alt"; grapheme: string };
61
-
62
- type Taken = { key: Key | null; next: number };
63
-
64
- /**
65
- * Stable chord spelling for the keymap (`ctrl+shift+x`). Alt+Enter arrives
66
- * as `{ type: "alt", key: "\\r" }` on some terminals; that must still be
67
- * `alt+enter` or a rebound follow-up never matches.
68
- */
69
- export function formatTuiKey(key: Key): string {
70
- switch (key.type) {
71
- case "char":
72
- return key.grapheme.length === 1 ? key.grapheme.toLowerCase() : key.grapheme;
73
- case "enter":
74
- return key.shift ? "shift+enter" : "enter";
75
- case "backspace":
76
- return "backspace";
77
- case "delete":
78
- return withMods(key.mods, "delete");
79
- case "tab":
80
- return key.shift === true ? "shift+tab" : "tab";
81
- case "escape":
82
- return "escape";
83
- case "up":
84
- case "down":
85
- case "left":
86
- case "right":
87
- case "home":
88
- case "end":
89
- return withMods(key.mods, key.type);
90
- case "pageup":
91
- return withMods(key.mods, "pageup");
92
- case "pagedown":
93
- return withMods(key.mods, "pagedown");
94
- case "ctrl":
95
- return key.shift === true
96
- ? `ctrl+shift+${key.key.toLowerCase()}`
97
- : `ctrl+${key.key.toLowerCase()}`;
98
- case "alt": {
99
- const k = key.key === "\r" || key.key === "\n" ? "enter" : key.key.toLowerCase();
100
- return `alt+${k}`;
101
- }
102
- default:
103
- return assertNever(key, "formatTuiKey");
104
- }
105
- }
106
-
107
- function withMods(mods: KeyMods | undefined, key: string): string {
108
- const parts: string[] = [];
109
- if (mods?.ctrl === true) parts.push("ctrl");
110
- if (mods?.alt === true) parts.push("alt");
111
- if (mods?.shift === true) parts.push("shift");
112
- parts.push(key);
113
- return parts.join("+");
114
- }
115
-
116
- export function decodeKeys(bytes: Uint8Array, opts: { escTimedOut?: boolean } = {}): DecodeResult {
117
- const events: Key[] = [];
118
- let i = 0;
119
- const timedOut = opts.escTimedOut === true;
120
- while (i < bytes.length) {
121
- const taken = take(bytes, i, timedOut);
122
- if (taken === null) break;
123
- if (taken.key !== null) events.push(taken.key);
124
- i = taken.next;
125
- }
126
- return { events, rest: bytes.slice(i) };
127
- }
128
-
129
- function take(bytes: Uint8Array, i: number, timedOut: boolean): Taken | null {
130
- const b = bytes[i]!;
131
-
132
- if (b === 0x1b) {
133
- if (i + 1 >= bytes.length) {
134
- return timedOut ? { key: { type: "escape" }, next: i + 1 } : null;
135
- }
136
- const next = bytes[i + 1]!;
137
- if (next === 0x5b) return takeCsi(bytes, i);
138
- if (next === 0x4f) return takeSs3(bytes, i);
139
- const utf = takeUtf8(bytes, i + 1);
140
- if (utf === null) return null;
141
- return seqToTaken({ kind: "alt", grapheme: utf.grapheme }, utf.next);
142
- }
143
-
144
- if (b === 0x0d || b === 0x0a) return { key: { type: "enter", shift: false }, next: i + 1 };
145
- if (b === 0x09) return { key: { type: "tab" }, next: i + 1 };
146
- if (b === 0x7f) return { key: { type: "backspace" }, next: i + 1 };
147
- if (b < 0x20) {
148
- // C0 minus ESC/CR/LF/TAB, already handled. 0x01..0x1A are Ctrl+a..z
149
- // (Ctrl+H is 0x08, distinct from DEL-as-Backspace).
150
- if (b >= 0x01 && b <= 0x1a) {
151
- return { key: { type: "ctrl", key: String.fromCharCode(b + 96) }, next: i + 1 };
152
- }
153
- // Ctrl+_ is US (0x1f), not in a-z. Without this the advertised undo
154
- // chord never arrives from a real tty — applyKey would only see it
155
- // from tests constructing the event by hand.
156
- if (b === 0x1f) return { key: { type: "ctrl", key: "_" }, next: i + 1 };
157
- return { key: null, next: i + 1 };
158
- }
159
-
160
- const utf = takeUtf8(bytes, i);
161
- if (utf === null) return null;
162
- return { key: { type: "char", grapheme: utf.grapheme }, next: utf.next };
163
- }
164
-
165
- /**
166
- * CSI: ESC [ ... final. ECMA-48 §5.4. Parameter bytes 0x30-0x3F,
167
- * intermediate 0x20-0x2F, final 0x40-0x7E. Running out of bytes before a
168
- * final is the split-read case and must leave the prefix in `rest`.
169
- */
170
- function takeCsi(bytes: Uint8Array, i: number): Taken | null {
171
- let j = i + 2;
172
- while (j < bytes.length) {
173
- const c = bytes[j]!;
174
- if (c >= 0x40 && c <= 0x7e) {
175
- const params = decodeParams(bytes.subarray(i + 2, j));
176
- const final = String.fromCharCode(c);
177
- return seqToTaken({ kind: "csi", params, final }, j + 1);
178
- }
179
- if (c >= 0x20 && c <= 0x3f) {
180
- j++;
181
- continue;
182
- }
183
- // Not a CSI parameter. Consume the ESC alone as Escape so we do not
184
- // stall on garbage; the rest of the buffer is decoded on the next loop.
185
- return { key: { type: "escape" }, next: i + 1 };
186
- }
187
- return null;
188
- }
189
-
190
- /**
191
- * SS3: ESC O X. ECMA-48 SS3 / xterm DECCKM application-cursor mode.
192
- * Without this, arrow keys in a terminal that has sent SM ?1 become
193
- * unrecognised (ESC O A rather than ESC [ A).
194
- */
195
- function takeSs3(bytes: Uint8Array, i: number): Taken | null {
196
- if (i + 2 >= bytes.length) return null;
197
- const final = String.fromCharCode(bytes[i + 2]!);
198
- return seqToTaken({ kind: "ss3", final }, i + 3);
199
- }
200
-
201
- function seqToTaken(seq: Seq, next: number): Taken {
202
- return { key: seqToKey(seq), next };
203
- }
204
-
205
- function seqToKey(seq: Seq): Key | null {
206
- switch (seq.kind) {
207
- case "csi":
208
- return csiToKey(seq.params, seq.final);
209
- case "ss3":
210
- return ss3ToKey(seq.final);
211
- case "alt":
212
- return { type: "alt", key: seq.grapheme };
213
- default:
214
- return assertNever(seq, "seqToKey");
215
- }
216
- }
217
-
218
- function ss3ToKey(final: string): Key | null {
219
- switch (final) {
220
- case "A": return { type: "up" };
221
- case "B": return { type: "down" };
222
- case "C": return { type: "right" };
223
- case "D": return { type: "left" };
224
- case "H": return { type: "home" };
225
- case "F": return { type: "end" };
226
- default: return null;
227
- }
228
- }
229
-
230
- function csiToKey(params: number[], final: string): Key | null {
231
- const mods = xtermMods(params[1]);
232
- switch (final) {
233
- case "A": return navKey("up", mods);
234
- case "B": return navKey("down", mods);
235
- case "C": return navKey("right", mods);
236
- case "D": return navKey("left", mods);
237
- case "H": return navKey("home", mods);
238
- case "F": return navKey("end", mods);
239
- case "Z": return { type: "tab", shift: true };
240
- case "u": return kittyKey(params);
241
- case "~": return tildeKey(params);
242
- default: return null;
243
- }
244
- }
245
-
246
- /**
247
- * Kitty keyboard protocol CSI unicode ; modifier u.
248
- * https://sw.kovidgoyal.net/kitty/keyboard-protocol/
249
- * Shift+Enter is not in ECMA-48; terminals that can report it use this
250
- * (or xterm modifyOtherKeys below). Without it, Shift+Enter is just Enter
251
- * and a multi-line editor cannot exist.
252
- */
253
- function kittyKey(params: number[]): Key | null {
254
- const cp = params[0] ?? 0;
255
- const shift = hasShift(params[1]);
256
- if (cp === 13) return { type: "enter", shift };
257
- if (cp === 27) return { type: "escape" };
258
- if (cp === 127) return { type: "backspace" };
259
- if (cp === 9) return hasShift(params[1]) ? { type: "tab", shift: true } : { type: "tab" };
260
- if (cp >= 32) return { type: "char", grapheme: String.fromCodePoint(cp) };
261
- return null;
262
- }
263
-
264
- /**
265
- * xterm CSI n~ (vt sequences) and CSI 27 ; mod ; key ~ (modifyOtherKeys).
266
- * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
267
- */
268
- function tildeKey(params: number[]): Key | null {
269
- const a = params[0] ?? 0;
270
- if (a === 27) {
271
- const key = params[2] ?? 0;
272
- if (key === 13) return { type: "enter", shift: hasShift(params[1]) };
273
- return null;
274
- }
275
- const mods = xtermMods(params[1]);
276
- switch (a) {
277
- case 1:
278
- case 7: return navKey("home", mods);
279
- case 3: return navKey("delete", mods);
280
- case 4:
281
- case 8: return navKey("end", mods);
282
- case 5: return navKey("pageup", mods);
283
- case 6: return navKey("pagedown", mods);
284
- case 13: return { type: "enter", shift: hasShift(params[1]) };
285
- default: return null;
286
- }
287
- }
288
-
289
- type NavType =
290
- | "up"
291
- | "down"
292
- | "left"
293
- | "right"
294
- | "home"
295
- | "end"
296
- | "pageup"
297
- | "pagedown"
298
- | "delete";
299
-
300
- function navKey(type: NavType, mods: KeyMods | undefined): Key {
301
- return mods === undefined ? { type } : { type, mods };
302
- }
303
-
304
- /**
305
- * xterm ctlseqs (modified cursor keys / modifyOtherKeys): the CSI
306
- * modifier parameter is `1 + bitmask`. Bit 0 is Shift, bit 1 Alt, bit
307
- * 2 Ctrl; they combine (CSI 1;7D is Ctrl+Alt+Left).
308
- * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
309
- * Without these, CSI 1;3D (Alt+Left) and CSI 1;5D (Ctrl+Left) collapse
310
- * onto CSI D, and the editor's word-motion bindings can never fire.
311
- */
312
- const XTERM_MOD_SHIFT = 1;
313
- const XTERM_MOD_ALT = 2;
314
- const XTERM_MOD_CTRL = 4;
315
-
316
- /**
317
- * Decode the xterm modifier parameter. Omitted or 1 means unmodified.
318
- * Values below 1 are not in the encoding -- treating them as a bitmask
319
- * would sign-extend (`0 - 1` is -1, every bit set). Unrecognised bits
320
- * (Meta = 8, CSI 1;9D) are ignored rather than dropping the key: a
321
- * dead key looks like a stuck terminal, an unbound shortcut does not.
322
- */
323
- function xtermMods(mod: number | undefined): KeyMods | undefined {
324
- if (mod === undefined || mod < 2) return undefined;
325
- const bits = mod - 1;
326
- const shift = (bits & XTERM_MOD_SHIFT) !== 0;
327
- const alt = (bits & XTERM_MOD_ALT) !== 0;
328
- const ctrl = (bits & XTERM_MOD_CTRL) !== 0;
329
- if (!shift && !alt && !ctrl) return undefined;
330
- return {
331
- ...(shift ? { shift: true } : {}),
332
- ...(alt ? { alt: true } : {}),
333
- ...(ctrl ? { ctrl: true } : {}),
334
- };
335
- }
336
-
337
- /** xterm/Kitty modifier encoding: 1 + bitfield, bit 0 is Shift. */
338
- function hasShift(mod: number | undefined): boolean {
339
- return xtermMods(mod)?.shift === true;
340
- }
341
-
342
- function decodeParams(buf: Uint8Array): number[] {
343
- if (buf.length === 0) return [];
344
- let s = new TextDecoder().decode(buf);
345
- if (s.startsWith("?") || s.startsWith(">")) s = s.slice(1);
346
- if (s.length === 0) return [];
347
- const out: number[] = [];
348
- for (const part of s.split(";")) {
349
- const n = part === "" ? 0 : Number.parseInt(part, 10);
350
- out.push(Number.isFinite(n) ? n : 0);
351
- }
352
- return out;
353
- }
354
-
355
- function utf8Need(lead: number): number {
356
- if (lead < 0x80) return 1;
357
- if ((lead & 0xe0) === 0xc0) return 2;
358
- if ((lead & 0xf0) === 0xe0) return 3;
359
- if ((lead & 0xf8) === 0xf0) return 4;
360
- return 1;
361
- }
362
-
363
- function takeUtf8(bytes: Uint8Array, i: number): { grapheme: string; next: number } | null {
364
- const lead = bytes[i]!;
365
- const need = utf8Need(lead);
366
- if (i + need > bytes.length) return null;
367
- if (need > 1) {
368
- for (let k = 1; k < need; k++) {
369
- if ((bytes[i + k]! & 0xc0) !== 0x80) {
370
- return { grapheme: "\uFFFD", next: i + 1 };
371
- }
372
- }
373
- } else if (lead >= 0x80) {
374
- return { grapheme: "\uFFFD", next: i + 1 };
375
- }
376
- const grapheme = new TextDecoder().decode(bytes.subarray(i, i + need));
377
- return { grapheme, next: i + need };
378
- }
@@ -1,17 +0,0 @@
1
- /**
2
- * Markdown → terminal lines, via the vendored pi Markdown component.
3
- *
4
- * D19: the live UI is pi's renderer, which is built on `marked`. Our
5
- * previous subset parser lived here; the function signature stayed so
6
- * apps/render.ts and theming tests still call renderMarkdown(text, width,
7
- * style). Style is mapped onto pi's MarkdownTheme in host.ts.
8
- */
9
- import { renderMarkdownLines } from "./host.ts";
10
- import { IDENTITY, type Style } from "./style.ts";
11
-
12
- export { ANSI, IDENTITY } from "./style.ts";
13
- export type { Style } from "./style.ts";
14
-
15
- export function renderMarkdown(text: string, width: number, style: Style = IDENTITY): string[] {
16
- return renderMarkdownLines(text, width, style);
17
- }
package/src/tui/render.ts DELETED
@@ -1,153 +0,0 @@
1
- /**
2
- * Line-level differential renderer.
3
- *
4
- * Cell-level diffing would need a full screen buffer of character-plus-attrs
5
- * per cell. Line-level diffing compares strings, and a terminal already
6
- * paints in rows. Unchanged neighbours are skipped even when they sit
7
- * between two changed rows -- the test that pins this would accept a
8
- * contiguous first-changed..last-changed rewrite, and that rewrite is how
9
- * a one-line spinner in a tall transcript becomes a full-region repaint.
10
- *
11
- * Main screen only. The live region sits at the bottom of the viewport;
12
- * history stays in the terminal's own scrollback. No alt-screen, so we
13
- * never own (and never have to reimplement) native scroll or search.
14
- */
15
-
16
- import { usableColumns, usableRows, type Terminal } from "./terminal.ts";
17
- import type { Component } from "./text.ts";
18
- import {
19
- CR, LF, SYNC_BEGIN, SYNC_END, ERASE_DOWN, ERASE_LINE_TAIL,
20
- cursorUp, cursorDown,
21
- } from "./ansi.ts";
22
-
23
- export class Renderer {
24
- private readonly terminal: Terminal;
25
- private previous: string[] = [];
26
- /** Row within the live region where the hardware cursor sits, 0-based. */
27
- private cursorRow = 0;
28
- /**
29
- * Overflow lines already written as scrollback. A count, not an identity:
30
- * this matches paintStream, which only commits growth. Reset when the
31
- * frame fits again so a later tall frame is not silently dropped.
32
- */
33
- private flushed = 0;
34
-
35
- constructor(terminal: Terminal) {
36
- this.terminal = terminal;
37
- }
38
-
39
- /** Rows currently occupying the live region, after overflow was committed. */
40
- get liveRowCount(): number {
41
- return this.previous.length;
42
- }
43
-
44
- render(root: Component): void {
45
- const fitted = fitLive(
46
- root.render(usableColumns(this.terminal.columns)),
47
- usableRows(this.terminal.rows),
48
- );
49
- if (fitted.overflow.length > this.flushed) {
50
- const fresh = fitted.overflow.slice(this.flushed);
51
- this.eraseLive();
52
- // Outside the sync pair, matching paintStream: these lines become
53
- // real scrollback. Dropping them here is how L0–L2 vanished from a
54
- // 5-row viewport even though fitLive had already split them off.
55
- this.terminal.write(fresh.join("\n") + "\n");
56
- this.flushed = fitted.overflow.length;
57
- } else if (fitted.overflow.length === 0) {
58
- this.flushed = 0;
59
- }
60
- const next = fitted.live;
61
- const body = this.diff(next);
62
- if (body === null) return;
63
- // One write per frame. Two writes would let tmux flush the CSI 2026
64
- // begin without the matching end, and the user would see the tear
65
- // synchronized output exists to hide.
66
- this.terminal.write(SYNC_BEGIN + body + SYNC_END);
67
- this.previous = next;
68
- }
69
-
70
- /**
71
- * Clear the live region so committed overflow is not painted on top of
72
- * it. Same motion as Tui.dropLive; kept here so the generic render path
73
- * (editor, select list) cannot forget it the way the first streaming
74
- * pass did.
75
- */
76
- private eraseLive(): void {
77
- if (this.previous.length === 0) return;
78
- let out = CR;
79
- if (this.cursorRow > 0) out += cursorUp(this.cursorRow);
80
- out += ERASE_DOWN;
81
- this.terminal.write(out);
82
- this.previous = [];
83
- this.cursorRow = 0;
84
- }
85
-
86
- private diff(next: string[]): string | null {
87
- const prev = this.previous;
88
- if (prev.length === next.length && prev.every((line, i) => line === next[i])) {
89
- return null;
90
- }
91
-
92
- if (prev.length === 0) {
93
- this.cursorRow = Math.max(0, next.length - 1);
94
- return next.join(LF);
95
- }
96
-
97
- let out = "";
98
- let row = this.cursorRow;
99
-
100
- const go = (target: number): void => {
101
- out += CR;
102
- if (target < row) out += cursorUp(row - target);
103
- else if (target > row) out += cursorDown(target - row);
104
- row = target;
105
- };
106
-
107
- const shared = Math.min(prev.length, next.length);
108
- for (let i = 0; i < shared; i++) {
109
- if (prev[i] === next[i]) continue;
110
- go(i);
111
- out += next[i]!;
112
- out += ERASE_LINE_TAIL;
113
- }
114
-
115
- if (next.length > prev.length) {
116
- go(prev.length - 1);
117
- for (let i = prev.length; i < next.length; i++) {
118
- out += LF;
119
- out += next[i]!;
120
- row = i;
121
- }
122
- } else if (next.length < prev.length) {
123
- go(next.length === 0 ? 0 : next.length);
124
- out += ERASE_DOWN;
125
- }
126
-
127
- if (next.length === 0) {
128
- this.cursorRow = 0;
129
- } else {
130
- if (row !== next.length - 1) go(next.length - 1);
131
- this.cursorRow = next.length - 1;
132
- }
133
- return out;
134
- }
135
- }
136
-
137
- /**
138
- * Split a frame so the live tail never exceeds the viewport.
139
- *
140
- * `diff` locates rows with cursorUp(row - target). A previous frame taller
141
- * than the screen has its cursor clamped at row 0 by the terminal, and every
142
- * later move is then off by the overflow. The head goes to the caller to
143
- * commit as scrollback; we only keep the tail.
144
- */
145
- export function fitLive(
146
- lines: readonly string[],
147
- maxRows: number,
148
- ): { overflow: string[]; live: string[] } {
149
- if (maxRows <= 0) return { overflow: [...lines], live: [] };
150
- if (lines.length <= maxRows) return { overflow: [], live: [...lines] };
151
- const cut = lines.length - maxRows;
152
- return { overflow: lines.slice(0, cut), live: lines.slice(cut) };
153
- }