@sagmans/dsh-tui 0.3.0 → 0.5.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 (253) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +407 -53
  3. package/cordis.patch.yml +14 -0
  4. package/lib/agent/present.js +1 -1
  5. package/lib/agent/present.js.map +1 -1
  6. package/lib/agent/projections.d.ts +22 -6
  7. package/lib/agent/projections.d.ts.map +1 -1
  8. package/lib/agent/projections.js +21 -10
  9. package/lib/agent/projections.js.map +1 -1
  10. package/lib/agent/prompt-history.d.ts +89 -0
  11. package/lib/agent/prompt-history.d.ts.map +1 -0
  12. package/lib/agent/prompt-history.js +382 -0
  13. package/lib/agent/prompt-history.js.map +1 -0
  14. package/lib/agent/status.d.ts +6 -0
  15. package/lib/agent/status.d.ts.map +1 -1
  16. package/lib/agent/status.js +1 -0
  17. package/lib/agent/status.js.map +1 -1
  18. package/lib/cards.d.ts +83 -13
  19. package/lib/cards.d.ts.map +1 -1
  20. package/lib/cards.js +96 -28
  21. package/lib/cards.js.map +1 -1
  22. package/lib/export.d.ts +3 -3
  23. package/lib/export.d.ts.map +1 -1
  24. package/lib/export.js +23 -13
  25. package/lib/export.js.map +1 -1
  26. package/lib/fold-cursor.d.ts +12 -5
  27. package/lib/fold-cursor.d.ts.map +1 -1
  28. package/lib/fold-cursor.js +16 -5
  29. package/lib/fold-cursor.js.map +1 -1
  30. package/lib/gates.d.ts.map +1 -1
  31. package/lib/gates.js +42 -9
  32. package/lib/gates.js.map +1 -1
  33. package/lib/herdr/client.d.ts +57 -0
  34. package/lib/herdr/client.d.ts.map +1 -0
  35. package/lib/herdr/client.js +218 -0
  36. package/lib/herdr/client.js.map +1 -0
  37. package/lib/herdr/constants.d.ts +93 -0
  38. package/lib/herdr/constants.d.ts.map +1 -0
  39. package/lib/herdr/constants.js +79 -0
  40. package/lib/herdr/constants.js.map +1 -0
  41. package/lib/herdr/reporter.d.ts +60 -0
  42. package/lib/herdr/reporter.d.ts.map +1 -0
  43. package/lib/herdr/reporter.js +217 -0
  44. package/lib/herdr/reporter.js.map +1 -0
  45. package/lib/herdr/state.d.ts +57 -0
  46. package/lib/herdr/state.d.ts.map +1 -0
  47. package/lib/herdr/state.js +62 -0
  48. package/lib/herdr/state.js.map +1 -0
  49. package/lib/index.d.ts.map +1 -1
  50. package/lib/index.js +700 -81
  51. package/lib/index.js.map +1 -1
  52. package/lib/injection.d.ts +9 -0
  53. package/lib/injection.d.ts.map +1 -0
  54. package/lib/injection.js +128 -0
  55. package/lib/injection.js.map +1 -0
  56. package/lib/input/actions.d.ts +19 -2
  57. package/lib/input/actions.d.ts.map +1 -1
  58. package/lib/input/actions.js +60 -11
  59. package/lib/input/actions.js.map +1 -1
  60. package/lib/input/cancel.d.ts +55 -0
  61. package/lib/input/cancel.d.ts.map +1 -0
  62. package/lib/input/cancel.js +47 -0
  63. package/lib/input/cancel.js.map +1 -0
  64. package/lib/input/completion.d.ts +5 -2
  65. package/lib/input/completion.d.ts.map +1 -1
  66. package/lib/input/completion.js +79 -4
  67. package/lib/input/completion.js.map +1 -1
  68. package/lib/input/file-search.d.ts +105 -0
  69. package/lib/input/file-search.d.ts.map +1 -0
  70. package/lib/input/file-search.js +665 -0
  71. package/lib/input/file-search.js.map +1 -0
  72. package/lib/input/fuzzy.d.ts +42 -0
  73. package/lib/input/fuzzy.d.ts.map +1 -0
  74. package/lib/input/fuzzy.js +289 -0
  75. package/lib/input/fuzzy.js.map +1 -0
  76. package/lib/input/ghost.d.ts +46 -0
  77. package/lib/input/ghost.d.ts.map +1 -0
  78. package/lib/input/ghost.js +58 -0
  79. package/lib/input/ghost.js.map +1 -0
  80. package/lib/input/keymap.d.ts +3 -1
  81. package/lib/input/keymap.d.ts.map +1 -1
  82. package/lib/input/keymap.js +16 -3
  83. package/lib/input/keymap.js.map +1 -1
  84. package/lib/input/match.d.ts +12 -4
  85. package/lib/input/match.d.ts.map +1 -1
  86. package/lib/input/match.js +50 -51
  87. package/lib/input/match.js.map +1 -1
  88. package/lib/input/submission.d.ts +34 -1
  89. package/lib/input/submission.d.ts.map +1 -1
  90. package/lib/input/submission.js +33 -5
  91. package/lib/input/submission.js.map +1 -1
  92. package/lib/keys-command.d.ts +19 -3
  93. package/lib/keys-command.d.ts.map +1 -1
  94. package/lib/keys-command.js +34 -31
  95. package/lib/keys-command.js.map +1 -1
  96. package/lib/settings-notice.d.ts +5 -0
  97. package/lib/settings-notice.d.ts.map +1 -1
  98. package/lib/settings-notice.js +9 -7
  99. package/lib/settings-notice.js.map +1 -1
  100. package/lib/stash/lock.d.ts +72 -0
  101. package/lib/stash/lock.d.ts.map +1 -0
  102. package/lib/stash/lock.js +374 -0
  103. package/lib/stash/lock.js.map +1 -0
  104. package/lib/stash/paths.d.ts +22 -0
  105. package/lib/stash/paths.d.ts.map +1 -0
  106. package/lib/stash/paths.js +84 -0
  107. package/lib/stash/paths.js.map +1 -0
  108. package/lib/stash/private-fs.d.ts +61 -0
  109. package/lib/stash/private-fs.d.ts.map +1 -0
  110. package/lib/stash/private-fs.js +379 -0
  111. package/lib/stash/private-fs.js.map +1 -0
  112. package/lib/stash/schema.d.ts +56 -0
  113. package/lib/stash/schema.d.ts.map +1 -0
  114. package/lib/stash/schema.js +108 -0
  115. package/lib/stash/schema.js.map +1 -0
  116. package/lib/stash/store.d.ts +92 -0
  117. package/lib/stash/store.d.ts.map +1 -0
  118. package/lib/stash/store.js +262 -0
  119. package/lib/stash/store.js.map +1 -0
  120. package/lib/stash.d.ts +122 -0
  121. package/lib/stash.d.ts.map +1 -0
  122. package/lib/stash.js +353 -0
  123. package/lib/stash.js.map +1 -0
  124. package/lib/terminal/external-editor.d.ts +93 -0
  125. package/lib/terminal/external-editor.d.ts.map +1 -0
  126. package/lib/terminal/external-editor.js +263 -0
  127. package/lib/terminal/external-editor.js.map +1 -0
  128. package/lib/terminal/host-writes.d.ts +39 -0
  129. package/lib/terminal/host-writes.d.ts.map +1 -0
  130. package/lib/terminal/host-writes.js +136 -0
  131. package/lib/terminal/host-writes.js.map +1 -0
  132. package/lib/terminal/signals.d.ts +25 -0
  133. package/lib/terminal/signals.d.ts.map +1 -0
  134. package/lib/terminal/signals.js +39 -0
  135. package/lib/terminal/signals.js.map +1 -0
  136. package/lib/terminal/warning-screen.d.ts +19 -1
  137. package/lib/terminal/warning-screen.d.ts.map +1 -1
  138. package/lib/terminal/warning-screen.js +37 -1
  139. package/lib/terminal/warning-screen.js.map +1 -1
  140. package/lib/terminal-text.d.ts +72 -0
  141. package/lib/terminal-text.d.ts.map +1 -0
  142. package/lib/terminal-text.js +603 -0
  143. package/lib/terminal-text.js.map +1 -0
  144. package/lib/text.d.ts +45 -6
  145. package/lib/text.d.ts.map +1 -1
  146. package/lib/text.js +106 -46
  147. package/lib/text.js.map +1 -1
  148. package/lib/theme-command.d.ts +7 -4
  149. package/lib/theme-command.d.ts.map +1 -1
  150. package/lib/theme-command.js +32 -12
  151. package/lib/theme-command.js.map +1 -1
  152. package/lib/theme-files.d.ts +110 -0
  153. package/lib/theme-files.d.ts.map +1 -0
  154. package/lib/theme-files.js +352 -0
  155. package/lib/theme-files.js.map +1 -0
  156. package/lib/theme-schema.d.ts +121 -0
  157. package/lib/theme-schema.d.ts.map +1 -0
  158. package/lib/theme-schema.js +87 -0
  159. package/lib/theme-schema.js.map +1 -0
  160. package/lib/theme-settings.d.ts +72 -10
  161. package/lib/theme-settings.d.ts.map +1 -1
  162. package/lib/theme-settings.js +162 -45
  163. package/lib/theme-settings.js.map +1 -1
  164. package/lib/theme-tokens.d.ts +39 -7
  165. package/lib/theme-tokens.d.ts.map +1 -1
  166. package/lib/theme-tokens.js +104 -13
  167. package/lib/theme-tokens.js.map +1 -1
  168. package/lib/theme.d.ts +24 -2
  169. package/lib/theme.d.ts.map +1 -1
  170. package/lib/theme.js +49 -6
  171. package/lib/theme.js.map +1 -1
  172. package/lib/todo-guard.d.ts +116 -0
  173. package/lib/todo-guard.d.ts.map +1 -0
  174. package/lib/todo-guard.js +259 -0
  175. package/lib/todo-guard.js.map +1 -0
  176. package/lib/tool-display.d.ts +57 -0
  177. package/lib/tool-display.d.ts.map +1 -0
  178. package/lib/tool-display.js +51 -0
  179. package/lib/tool-display.js.map +1 -0
  180. package/lib/transcript.d.ts +28 -0
  181. package/lib/transcript.d.ts.map +1 -1
  182. package/lib/transcript.js +126 -37
  183. package/lib/transcript.js.map +1 -1
  184. package/lib/ui/diff.d.ts +49 -0
  185. package/lib/ui/diff.d.ts.map +1 -0
  186. package/lib/ui/diff.js +208 -0
  187. package/lib/ui/diff.js.map +1 -0
  188. package/lib/ui/dock.d.ts.map +1 -1
  189. package/lib/ui/dock.js +9 -6
  190. package/lib/ui/dock.js.map +1 -1
  191. package/lib/ui/editor.d.ts +41 -1
  192. package/lib/ui/editor.d.ts.map +1 -1
  193. package/lib/ui/editor.js +100 -4
  194. package/lib/ui/editor.js.map +1 -1
  195. package/lib/ui/frame.d.ts +16 -5
  196. package/lib/ui/frame.d.ts.map +1 -1
  197. package/lib/ui/frame.js +32 -13
  198. package/lib/ui/frame.js.map +1 -1
  199. package/lib/ui/history-picker.d.ts +15 -0
  200. package/lib/ui/history-picker.d.ts.map +1 -0
  201. package/lib/ui/history-picker.js +26 -0
  202. package/lib/ui/history-picker.js.map +1 -0
  203. package/lib/ui/keymap-picker.d.ts +16 -0
  204. package/lib/ui/keymap-picker.d.ts.map +1 -0
  205. package/lib/ui/keymap-picker.js +41 -0
  206. package/lib/ui/keymap-picker.js.map +1 -0
  207. package/lib/ui/layout.d.ts +33 -0
  208. package/lib/ui/layout.d.ts.map +1 -0
  209. package/lib/ui/layout.js +33 -0
  210. package/lib/ui/layout.js.map +1 -0
  211. package/lib/ui/markdown.d.ts +25 -3
  212. package/lib/ui/markdown.d.ts.map +1 -1
  213. package/lib/ui/markdown.js +12 -8
  214. package/lib/ui/markdown.js.map +1 -1
  215. package/lib/ui/picker-card.d.ts +42 -0
  216. package/lib/ui/picker-card.d.ts.map +1 -0
  217. package/lib/ui/picker-card.js +148 -0
  218. package/lib/ui/picker-card.js.map +1 -0
  219. package/lib/ui/picker.d.ts +50 -2
  220. package/lib/ui/picker.d.ts.map +1 -1
  221. package/lib/ui/picker.js +66 -7
  222. package/lib/ui/picker.js.map +1 -1
  223. package/lib/ui/prompt.d.ts +12 -2
  224. package/lib/ui/prompt.d.ts.map +1 -1
  225. package/lib/ui/prompt.js +25 -2
  226. package/lib/ui/prompt.js.map +1 -1
  227. package/lib/ui/rows.d.ts +8 -6
  228. package/lib/ui/rows.d.ts.map +1 -1
  229. package/lib/ui/rows.js +10 -18
  230. package/lib/ui/rows.js.map +1 -1
  231. package/lib/ui/stash-picker.d.ts +43 -0
  232. package/lib/ui/stash-picker.d.ts.map +1 -0
  233. package/lib/ui/stash-picker.js +78 -0
  234. package/lib/ui/stash-picker.js.map +1 -0
  235. package/lib/ui/status.d.ts +2 -0
  236. package/lib/ui/status.d.ts.map +1 -1
  237. package/lib/ui/status.js +25 -16
  238. package/lib/ui/status.js.map +1 -1
  239. package/lib/ui/theme-picker.d.ts +18 -0
  240. package/lib/ui/theme-picker.d.ts.map +1 -0
  241. package/lib/ui/theme-picker.js +68 -0
  242. package/lib/ui/theme-picker.js.map +1 -0
  243. package/lib/ui/view.d.ts +146 -22
  244. package/lib/ui/view.d.ts.map +1 -1
  245. package/lib/ui/view.js +461 -154
  246. package/lib/ui/view.js.map +1 -1
  247. package/lib/work.d.ts +3 -1
  248. package/lib/work.d.ts.map +1 -1
  249. package/lib/work.js +9 -1
  250. package/lib/work.js.map +1 -1
  251. package/package.json +7 -1
  252. package/themes/deepseek-blue.yaml +183 -0
  253. package/themes/violet-orbit.yaml +178 -0
@@ -0,0 +1,33 @@
1
+ import { VStack, type Component } from '@earendil-works/pi-tui';
2
+ /**
3
+ * The rows the input bar keeps when every other row is contested.
4
+ *
5
+ * The frame's two rules and one row of text: a bar that cannot show what the
6
+ * reader is typing is not a bar, so this floor is reserved before any work
7
+ * summary is allowed a row.
8
+ */
9
+ export declare const PROMPT_MIN_ROWS = 3;
10
+ /** The parts of the surface the root layout stacks, top to bottom. */
11
+ export interface SurfaceParts {
12
+ /** The transcript viewport, which takes every row nothing else claims. */
13
+ readonly transcript: Component;
14
+ /** The work board: goals, plans, running jobs, and running subagents. */
15
+ readonly dock: Component;
16
+ /** The prompts waiting behind the running turn. */
17
+ readonly queue: Component;
18
+ /** The prompt bar, borrowed when a question is answered in it. */
19
+ readonly prompt: Component;
20
+ /** The footer. */
21
+ readonly status: Component;
22
+ }
23
+ /**
24
+ * Stack the surface, deciding who gives up rows when the terminal is short.
25
+ *
26
+ * The transcript grows into whatever is left; the dock, the queue, and the bar
27
+ * shrink in that order of eagerness, and the bar keeps a floor no shrink can
28
+ * take. A dock that could not shrink held its whole height on a short terminal
29
+ * and pushed the draft, its frame, and the footer out of the frame entirely,
30
+ * because a clipped leaf cannot restore rows the allocator never gave it.
31
+ */
32
+ export declare function surfaceLayout(parts: SurfaceParts): VStack;
33
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/ui/layout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAE/D;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,IAAI,CAAA;AAEhC,sEAAsE;AACtE,MAAM,WAAW,YAAY;IAC3B,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAA;IAC9B,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,mDAAmD;IACnD,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAA;IAC1B,kBAAkB;IAClB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAA;CAC3B;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAazD"}
@@ -0,0 +1,33 @@
1
+ import { VStack } from '@earendil-works/pi-tui';
2
+ /**
3
+ * The rows the input bar keeps when every other row is contested.
4
+ *
5
+ * The frame's two rules and one row of text: a bar that cannot show what the
6
+ * reader is typing is not a bar, so this floor is reserved before any work
7
+ * summary is allowed a row.
8
+ */
9
+ export const PROMPT_MIN_ROWS = 3;
10
+ /**
11
+ * Stack the surface, deciding who gives up rows when the terminal is short.
12
+ *
13
+ * The transcript grows into whatever is left; the dock, the queue, and the bar
14
+ * shrink in that order of eagerness, and the bar keeps a floor no shrink can
15
+ * take. A dock that could not shrink held its whole height on a short terminal
16
+ * and pushed the draft, its frame, and the footer out of the frame entirely,
17
+ * because a clipped leaf cannot restore rows the allocator never gave it.
18
+ */
19
+ export function surfaceLayout(parts) {
20
+ return new VStack([
21
+ { component: parts.transcript, basis: 0, grow: 1, minSize: 1 },
22
+ // Work state earns rows only when there is some, and it gives them up first:
23
+ // a job ticker is worth less than the input the reader is typing into.
24
+ { component: parts.dock, basis: 'auto', shrink: 2, minSize: 0 },
25
+ // Queued input earns rows only while something is waiting, and it gives
26
+ // them up before the editor does: the bar being typed in outranks what is
27
+ // waiting behind it.
28
+ { component: parts.queue, basis: 'auto', shrink: 2, minSize: 0 },
29
+ { component: new VStack([{ component: parts.prompt, basis: 'auto', shrink: 1, minSize: 1 }]), basis: 'auto', shrink: 1, minSize: PROMPT_MIN_ROWS },
30
+ { component: parts.status, basis: 'auto', shrink: 0, minSize: 1 },
31
+ ]);
32
+ }
33
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/ui/layout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAkB,MAAM,wBAAwB,CAAA;AAE/D;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAA;AAgBhC;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,KAAmB;IAC/C,OAAO,IAAI,MAAM,CAAC;QAChB,EAAE,SAAS,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;QAC9D,6EAA6E;QAC7E,uEAAuE;QACvE,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;QAC/D,wEAAwE;QACxE,0EAA0E;QAC1E,qBAAqB;QACrB,EAAE,SAAS,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;QAChE,EAAE,SAAS,EAAE,IAAI,MAAM,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE;QAClJ,EAAE,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;KAClE,CAAC,CAAA;AACJ,CAAC"}
@@ -1,9 +1,29 @@
1
- import { type MarkdownTheme } from '@earendil-works/pi-tui';
1
+ import { type DefaultTextStyle, type MarkdownTheme } from '@earendil-works/pi-tui';
2
2
  import type { MermaidTransform } from './mermaid.ts';
3
3
  /** Parsed messages kept for redraws; everything above this is cold transcript. */
4
4
  export declare const MARKDOWN_CACHE_LIMIT = 64;
5
5
  /**
6
- * Render assistant text as markdown without reparsing the whole history.
6
+ * How a row is based where the markdown itself has no rule for the text.
7
+ *
8
+ * Two rows can carry identical words and still have to draw differently — a
9
+ * prompt repeated as a reply, a thought quoted in an answer — and a parse is
10
+ * cached with the lines it drew, so the face is part of what identifies a parse
11
+ * rather than a detail of one drawing.
12
+ */
13
+ export interface MarkdownFace {
14
+ /** Names the face in the parse cache; two faces never share a parse. */
15
+ readonly name: string;
16
+ /** The shade text keeps where the markdown has no rule for it. */
17
+ readonly base?: DefaultTextStyle;
18
+ /** A theme of this face's own, for a row the answer's colours would outshine. */
19
+ readonly theme?: MarkdownTheme;
20
+ /** Whether this face asks the renderer's transform for a drawing; a thought's does not. */
21
+ readonly transform?: boolean;
22
+ }
23
+ /** The face an answer is drawn in: the reader's markdown theme, drawings included. */
24
+ export declare const ANSWER_FACE: MarkdownFace;
25
+ /**
26
+ * Render one message as markdown without reparsing the whole history.
7
27
  *
8
28
  * A repaint walks every row, so parsing each message again on every keystroke
9
29
  * would make a long session crawl. Parsing is keyed by message text because
@@ -14,12 +34,14 @@ export declare class MarkdownRenderer {
14
34
  private readonly mermaid?;
15
35
  private readonly parsed;
16
36
  constructor(theme: MarkdownTheme, mermaid?: MermaidTransform | undefined);
17
- render(text: string, width: number, live?: boolean): string[];
37
+ render(text: string, width: number, live?: boolean, face?: MarkdownFace): string[];
18
38
  /**
19
39
  * The parse options this renderer installs, absent when nothing transforms.
20
40
  *
21
41
  * The flag is captured rather than read per call: the component caches the
22
42
  * rows it drew, so the value that built it is the only one it could honour.
43
+ * A face that refuses the transform keeps its fences as source, which is how a
44
+ * diagram cannot take over a row that was meant to stay subordinate.
23
45
  */
24
46
  private options;
25
47
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/ui/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAEpD,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,KAAK,CAAA;AAEtC;;;;;;GAMG;AACH,qBAAa,gBAAgB;IAGf,OAAO,CAAC,QAAQ,CAAC,KAAK;IAAiB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IAF5E,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;gBAExB,KAAK,EAAE,aAAa,EAAmB,OAAO,CAAC,EAAE,gBAAgB,YAAA;IAE9F,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,UAAQ,GAAG,MAAM,EAAE;IAqB3D;;;;;OAKG;IACH,OAAO,CAAC,OAAO;IAMf;;;;;;OAMG;IACH,UAAU,IAAI,IAAI;CAInB"}
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/ui/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,gBAAgB,EAAwB,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAClH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAEpD,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,KAAK,CAAA;AAEtC;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,kEAAkE;IAClE,QAAQ,CAAC,IAAI,CAAC,EAAE,gBAAgB,CAAA;IAChC,iFAAiF;IACjF,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAA;IAC9B,2FAA2F;IAC3F,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAC7B;AAED,sFAAsF;AACtF,eAAO,MAAM,WAAW,EAAE,YAAiC,CAAA;AAE3D;;;;;;GAMG;AACH,qBAAa,gBAAgB;IAGf,OAAO,CAAC,QAAQ,CAAC,KAAK;IAAiB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IAF5E,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;gBAExB,KAAK,EAAE,aAAa,EAAmB,OAAO,CAAC,EAAE,gBAAgB,YAAA;IAE9F,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,UAAQ,EAAE,IAAI,GAAE,YAA0B,GAAG,MAAM,EAAE;IAqB7F;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO;IAMf;;;;;;OAMG;IACH,UAAU,IAAI,IAAI;CAInB"}
@@ -1,8 +1,10 @@
1
1
  import { Markdown } from '@earendil-works/pi-tui';
2
2
  /** Parsed messages kept for redraws; everything above this is cold transcript. */
3
3
  export const MARKDOWN_CACHE_LIMIT = 64;
4
+ /** The face an answer is drawn in: the reader's markdown theme, drawings included. */
5
+ export const ANSWER_FACE = { name: 'answer' };
4
6
  /**
5
- * Render assistant text as markdown without reparsing the whole history.
7
+ * Render one message as markdown without reparsing the whole history.
6
8
  *
7
9
  * A repaint walks every row, so parsing each message again on every keystroke
8
10
  * would make a long session crawl. Parsing is keyed by message text because
@@ -16,12 +18,12 @@ export class MarkdownRenderer {
16
18
  this.theme = theme;
17
19
  this.mermaid = mermaid;
18
20
  }
19
- render(text, width, live = false) {
21
+ render(text, width, live = false, face = ANSWER_FACE) {
20
22
  // A streaming reply can settle on the very text it last streamed, and the
21
23
  // two renderings are not the same: only a settled one may report what a
22
- // drawing lost, and a mode may draw one and withhold the other. The flag is
23
- // part of a message's identity for the same reason the text is.
24
- const key = live ? `live\u0000${text}` : `settled\u0000${text}`;
24
+ // drawing lost, and a mode may draw one and withhold the other. The flag and
25
+ // the face are part of a message's identity for the same reason the text is.
26
+ const key = `${face.name}\u0000${live ? 'live' : 'settled'}\u0000${text}`;
25
27
  let message = this.parsed.get(key);
26
28
  if (message === undefined) {
27
29
  if (this.parsed.size >= MARKDOWN_CACHE_LIMIT) {
@@ -29,7 +31,7 @@ export class MarkdownRenderer {
29
31
  if (oldest.done !== true)
30
32
  this.parsed.delete(oldest.value);
31
33
  }
32
- message = new Markdown(text, 0, 0, this.theme, undefined, this.options(live));
34
+ message = new Markdown(text, 0, 0, face.theme ?? this.theme, face.base, this.options(face, live));
33
35
  }
34
36
  else {
35
37
  // Reinserting keeps a message that is still on screen from ageing out.
@@ -43,10 +45,12 @@ export class MarkdownRenderer {
43
45
  *
44
46
  * The flag is captured rather than read per call: the component caches the
45
47
  * rows it drew, so the value that built it is the only one it could honour.
48
+ * A face that refuses the transform keeps its fences as source, which is how a
49
+ * diagram cannot take over a row that was meant to stay subordinate.
46
50
  */
47
- options(live) {
51
+ options(face, live) {
48
52
  const mermaid = this.mermaid;
49
- if (mermaid === undefined)
53
+ if (mermaid === undefined || face.transform === false)
50
54
  return undefined;
51
55
  return { transform: (markdown, availableWidth) => mermaid(markdown, availableWidth, live) };
52
56
  }
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/ui/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAA4C,MAAM,wBAAwB,CAAA;AAG3F,kFAAkF;AAClF,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAA;AAEtC;;;;;;GAMG;AACH,MAAM,OAAO,gBAAgB;IAGE;IAAuC;IAFnD,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAA;IAErD,YAA6B,KAAoB,EAAmB,OAA0B;QAAjE,UAAK,GAAL,KAAK,CAAe;QAAmB,YAAO,GAAP,OAAO,CAAmB;IAAG,CAAC;IAElG,MAAM,CAAC,IAAY,EAAE,KAAa,EAAE,IAAI,GAAG,KAAK;QAC9C,0EAA0E;QAC1E,wEAAwE;QACxE,4EAA4E;QAC5E,gEAAgE;QAChE,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,CAAC,gBAAgB,IAAI,EAAE,CAAA;QAC/D,IAAI,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAClC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,oBAAoB,EAAE,CAAC;gBAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAA;gBACxC,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI;oBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAC5D,CAAC;YACD,OAAO,GAAG,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;QAC/E,CAAC;aAAM,CAAC;YACN,uEAAuE;YACvE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACzB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QAC7B,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACvD,CAAC;IAED;;;;;OAKG;IACK,OAAO,CAAC,IAAa;QAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;QAC5B,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,SAAS,CAAA;QAC3C,OAAO,EAAE,SAAS,EAAE,CAAC,QAAQ,EAAE,cAAc,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,CAAA;IAC7F,CAAC;IAED;;;;;;OAMG;IACH,UAAU;QACR,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;YAAE,OAAO,CAAC,UAAU,EAAE,CAAA;QAChE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAA;IACrB,CAAC;CACF"}
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/ui/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAmE,MAAM,wBAAwB,CAAA;AAGlH,kFAAkF;AAClF,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAA;AAqBtC,sFAAsF;AACtF,MAAM,CAAC,MAAM,WAAW,GAAiB,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;AAE3D;;;;;;GAMG;AACH,MAAM,OAAO,gBAAgB;IAGE;IAAuC;IAFnD,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAA;IAErD,YAA6B,KAAoB,EAAmB,OAA0B;QAAjE,UAAK,GAAL,KAAK,CAAe;QAAmB,YAAO,GAAP,OAAO,CAAmB;IAAG,CAAC;IAElG,MAAM,CAAC,IAAY,EAAE,KAAa,EAAE,IAAI,GAAG,KAAK,EAAE,OAAqB,WAAW;QAChF,0EAA0E;QAC1E,wEAAwE;QACxE,6EAA6E;QAC7E,6EAA6E;QAC7E,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,IAAI,SAAS,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,SAAS,IAAI,EAAE,CAAA;QACzE,IAAI,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAClC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,oBAAoB,EAAE,CAAC;gBAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAA;gBACxC,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI;oBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAC5D,CAAC;YACD,OAAO,GAAG,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;QACnG,CAAC;aAAM,CAAC;YACN,uEAAuE;YACvE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACzB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QAC7B,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACvD,CAAC;IAED;;;;;;;OAOG;IACK,OAAO,CAAC,IAAkB,EAAE,IAAa;QAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;QAC5B,IAAI,OAAO,KAAK,SAAS,IAAI,IAAI,CAAC,SAAS,KAAK,KAAK;YAAE,OAAO,SAAS,CAAA;QACvE,OAAO,EAAE,SAAS,EAAE,CAAC,QAAQ,EAAE,cAAc,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,CAAA;IAC7F,CAAC;IAED;;;;;;OAMG;IACH,UAAU;QACR,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;YAAE,OAAO,CAAC,UAAU,EAAE,CAAA;QAChE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAA;IACrB,CAAC;CACF"}
@@ -0,0 +1,42 @@
1
+ import { type Component, type SizeValue } from '@earendil-works/pi-tui';
2
+ import type { TuiTheme } from '../theme.ts';
3
+ import type { PickerCard } from './picker.ts';
4
+ /**
5
+ * The rows a list draws, wherever it is drawn.
6
+ *
7
+ * One renderer for the card, so the list at the end of the transcript and the
8
+ * box over it cannot drift apart: a field added to a card reaches both at once.
9
+ * The width is the room the rows themselves have, which is what a frame around
10
+ * them narrows before it is asked for.
11
+ */
12
+ export declare function pickerCardLines(picker: PickerCard, width: number, theme: TuiTheme): string[];
13
+ /** The same share as the library resolves a percentage SizeValue, so the option and the row budget cannot disagree. */
14
+ export declare const POPUP_MAX_HEIGHT: SizeValue;
15
+ /** The columns the box may take on a terminal this wide. */
16
+ export declare function popupWidth(columns: number): number;
17
+ /** The rows the box may occupy, resolved the way the library resolves a percentage. */
18
+ export declare function popupHeight(rows: number): number;
19
+ /** How many list rows fit in that height, with the frame's own rows and the scroll counts reserved. */
20
+ export declare function popupRowBudget(rows: number): number;
21
+ /**
22
+ * A list drawn as a box over the surface rather than at the end of it.
23
+ *
24
+ * The box is only a frame: the rows, the filter, and the counts are the card's,
25
+ * which the list builds to whatever budget this component can afford. A screen
26
+ * too short for the whole card shrinks the window rather than the box, because
27
+ * the hint naming the keys that close it is the last thing that may fall off.
28
+ */
29
+ export declare class PickerPopup implements Component {
30
+ private readonly card;
31
+ /** The terminal the box is drawn on, read per paint so a resize lands on the next frame. */
32
+ private readonly rows;
33
+ private readonly theme;
34
+ constructor(card: (rows: number) => PickerCard | undefined,
35
+ /** The terminal the box is drawn on, read per paint so a resize lands on the next frame. */
36
+ rows: () => number, theme: TuiTheme);
37
+ invalidate(): void;
38
+ /** The card closed into a frame, or nothing when the list is already gone. */
39
+ private box;
40
+ render(width: number): string[];
41
+ }
42
+ //# sourceMappingURL=picker-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"picker-card.d.ts","sourceRoot":"","sources":["../../src/ui/picker-card.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAkC,MAAM,wBAAwB,CAAA;AAEvG,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAE3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAoB7C;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,MAAM,EAAE,CAkC5F;AAuBD,uHAAuH;AACvH,eAAO,MAAM,gBAAgB,EAAE,SAAgC,CAAA;AAE/D,4DAA4D;AAC5D,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,uFAAuF;AACvF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,uGAAuG;AACvG,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;;;;GAOG;AACH,qBAAa,WAAY,YAAW,SAAS;IAEzC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,4FAA4F;IAC5F,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAHL,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS;IAC/D,4FAA4F;IAC3E,IAAI,EAAE,MAAM,MAAM,EAClB,KAAK,EAAE,QAAQ;IAGlC,UAAU,IAAI,IAAI;IAIlB,8EAA8E;IAC9E,OAAO,CAAC,GAAG;IAWX,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;CAchC"}
@@ -0,0 +1,148 @@
1
+ import { visibleWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
2
+ import { canFrame, frameLines, FRAME_COLUMNS } from "./frame.js";
3
+ /** The columns a list's rows are indented by, and the ones a wrapped line under them uses. */
4
+ const ROW_INDENT = ' ';
5
+ const TEXT_INDENT = ' ';
6
+ /** The mark a cursor falls back to when the reader has not set one, so no literal lives in a template. */
7
+ const CURSOR_MARK = '❯';
8
+ /** An unselected row has no cursor, and a blank column is not a value to configure. */
9
+ const NO_CURSOR = ' ';
10
+ /** One wrapped block of plain text under a prefix, at the width the card has. */
11
+ function pushWrapped(lines, text, width, prefix, token, theme) {
12
+ const lead = visibleWidth(prefix);
13
+ const indent = ' '.repeat(lead);
14
+ const wrapped = wrapTextWithAnsi(theme.rich(text, { token, column: lead }), Math.max(1, width - lead));
15
+ wrapped.forEach((line, index) => {
16
+ lines.push(theme.cut(`${index === 0 ? prefix : indent}${line}`, width, ''));
17
+ });
18
+ }
19
+ /**
20
+ * The rows a list draws, wherever it is drawn.
21
+ *
22
+ * One renderer for the card, so the list at the end of the transcript and the
23
+ * box over it cannot drift apart: a field added to a card reaches both at once.
24
+ * The width is the room the rows themselves have, which is what a frame around
25
+ * them narrows before it is asked for.
26
+ */
27
+ export function pickerCardLines(picker, width, theme) {
28
+ const lines = [];
29
+ if (theme.visible('picker.title')) {
30
+ const glyph = theme.glyph('picker.glyph');
31
+ const lead = glyph === '' ? '' : `${glyph} `;
32
+ lines.push(theme.cut(theme.rich(`${lead}${picker.title}`, { token: 'picker.title', column: visibleWidth(lead) }), width, ''));
33
+ }
34
+ if (picker.note !== undefined && theme.visible('picker.note')) {
35
+ pushWrapped(lines, picker.note, width, TEXT_INDENT, 'picker.note', theme);
36
+ }
37
+ if (picker.filter !== '' && theme.visible('picker.filter')) {
38
+ lines.push(theme.cut(theme.rich(`${TEXT_INDENT}filter: ${picker.filter}`, { token: 'picker.filter', column: visibleWidth(TEXT_INDENT) }), width, ''));
39
+ }
40
+ if (picker.above > 0 && theme.visible('picker.scrollNewer')) {
41
+ lines.push(theme.style('picker.scrollNewer', theme.cut(`${ROW_INDENT}… ${picker.above} newer`, width, '')));
42
+ }
43
+ for (const row of picker.rows) {
44
+ const token = row.current ? 'picker.rowCurrent' : 'picker.row';
45
+ if (!theme.visible(token))
46
+ continue;
47
+ const cursor = row.current ? theme.glyph('picker.cursor') || CURSOR_MARK : NO_CURSOR;
48
+ const text = row.description === undefined
49
+ ? `${cursor} ${row.label}`
50
+ : `${cursor} ${row.label} — ${row.description}`;
51
+ lines.push(theme.cut(theme.rich(`${ROW_INDENT}${text}`, { token, column: visibleWidth(ROW_INDENT) }), width, ''));
52
+ }
53
+ if (picker.below > 0 && theme.visible('picker.scrollOlder')) {
54
+ lines.push(theme.style('picker.scrollOlder', theme.cut(`${ROW_INDENT}… ${picker.below} older`, width, '')));
55
+ }
56
+ if (theme.visible('picker.hint')) {
57
+ // The hint names the keys that leave the list, so it folds rather than
58
+ // being cut: a narrow screen must still be told the way out.
59
+ pushWrapped(lines, picker.hint, width, ROW_INDENT, 'picker.hint', theme);
60
+ }
61
+ return lines;
62
+ }
63
+ /**
64
+ * Share of the terminal a popup may take.
65
+ *
66
+ * The box is an overlay on work the reader is in the middle of, so it never
67
+ * claims the screen: the rows behind it stay the context for what is being
68
+ * chosen, and the editor stays where the reader left it.
69
+ */
70
+ const HEIGHT_PERCENT = 80;
71
+ /** Air between the box and the terminal's edge, so the frame never touches one. */
72
+ const MARGIN = 1;
73
+ /** A box narrower than this cannot name an action and its keys on one row. */
74
+ const MIN_WIDTH = 40;
75
+ /** Past this the rows are mostly empty, and a list reads better narrow than stretched. */
76
+ const MAX_WIDTH = 100;
77
+ /** Rows the box spends before the list itself: two rules, the heading, the filter line, and the hint. */
78
+ const CHROME_ROWS = 5;
79
+ /** Rows the two scroll counts may take when the list is longer than the window. */
80
+ const MARKER_ROWS = 2;
81
+ /** A list that can show a row and still say the map continues. */
82
+ const MIN_ROWS = 3;
83
+ /** The same share as the library resolves a percentage SizeValue, so the option and the row budget cannot disagree. */
84
+ export const POPUP_MAX_HEIGHT = `${HEIGHT_PERCENT}%`;
85
+ /** The columns the box may take on a terminal this wide. */
86
+ export function popupWidth(columns) {
87
+ return Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, columns - MARGIN * 2));
88
+ }
89
+ /** The rows the box may occupy, resolved the way the library resolves a percentage. */
90
+ export function popupHeight(rows) {
91
+ return Math.floor((rows * HEIGHT_PERCENT) / 100);
92
+ }
93
+ /** How many list rows fit in that height, with the frame's own rows and the scroll counts reserved. */
94
+ export function popupRowBudget(rows) {
95
+ return Math.max(MIN_ROWS, popupHeight(rows) - CHROME_ROWS - MARKER_ROWS);
96
+ }
97
+ /**
98
+ * A list drawn as a box over the surface rather than at the end of it.
99
+ *
100
+ * The box is only a frame: the rows, the filter, and the counts are the card's,
101
+ * which the list builds to whatever budget this component can afford. A screen
102
+ * too short for the whole card shrinks the window rather than the box, because
103
+ * the hint naming the keys that close it is the last thing that may fall off.
104
+ */
105
+ export class PickerPopup {
106
+ card;
107
+ rows;
108
+ theme;
109
+ constructor(card,
110
+ /** The terminal the box is drawn on, read per paint so a resize lands on the next frame. */
111
+ rows, theme) {
112
+ this.card = card;
113
+ this.rows = rows;
114
+ this.theme = theme;
115
+ }
116
+ invalidate() {
117
+ // Nothing is cached: the card is read fresh on every frame.
118
+ }
119
+ /** The card closed into a frame, or nothing when the list is already gone. */
120
+ box(picker, width) {
121
+ if (picker === undefined)
122
+ return [];
123
+ const framed = canFrame(width, this.theme.visible('picker.border'));
124
+ const inside = framed ? width - FRAME_COLUMNS : width;
125
+ return frameLines(pickerCardLines(picker, inside, this.theme), width, {
126
+ text: line => line,
127
+ border: rule => this.theme.style('picker.border', rule),
128
+ framed,
129
+ });
130
+ }
131
+ render(width) {
132
+ if (width <= 0)
133
+ return [];
134
+ const rows = this.rows();
135
+ const room = popupHeight(rows);
136
+ let budget = popupRowBudget(rows);
137
+ for (;;) {
138
+ const lines = this.box(this.card(budget), width);
139
+ // A refusal note can spend rows the budget did not reserve, so a card that
140
+ // still overflows asks for a shorter window rather than lose its closing
141
+ // rule to the library's own clamp.
142
+ if (lines.length <= room || budget <= MIN_ROWS)
143
+ return lines;
144
+ budget = Math.max(MIN_ROWS, budget - (lines.length - room));
145
+ }
146
+ }
147
+ }
148
+ //# sourceMappingURL=picker-card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"picker-card.js","sourceRoot":"","sources":["../../src/ui/picker-card.ts"],"names":[],"mappings":"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"}
@@ -46,6 +46,14 @@ export interface PickerHints {
46
46
  readonly empty: () => string;
47
47
  readonly listed: () => string;
48
48
  }
49
+ /**
50
+ * Fold a value onto one drawn row.
51
+ *
52
+ * A filter can be seeded from a multiline draft or typed through a paste, and a
53
+ * raw newline inside a row would push the frame's own accounting apart; the
54
+ * needle the list matches with stays untouched.
55
+ */
56
+ export declare function singleLine(text: string): string;
49
57
  /** Human age of a session, so a list of ids becomes a list of moments. */
50
58
  export declare function describeAge(createdAt: number, now: number): string;
51
59
  /**
@@ -68,6 +76,15 @@ export declare class ListPicker<Row> {
68
76
  private readonly hints;
69
77
  /** The keys in force, read per press so a settings edit lands on the next key. */
70
78
  private readonly keys;
79
+ /**
80
+ * Told which row the cursor is on whenever it moves.
81
+ *
82
+ * A list whose rows can be shown as well as taken needs the row under the
83
+ * cursor before it is picked — a theme is judged by looking at the screen it
84
+ * paints — and only the list knows where the cursor landed once a filter has
85
+ * reordered the rows under it.
86
+ */
87
+ private readonly onCursor?;
71
88
  private cursor;
72
89
  private filter;
73
90
  private note;
@@ -77,7 +94,18 @@ export declare class ListPicker<Row> {
77
94
  /** Extra text the filter matches besides the row's own label. */
78
95
  haystackOf: (row: Row) => string, hints: PickerHints,
79
96
  /** The keys in force, read per press so a settings edit lands on the next key. */
80
- keys: () => Keymap);
97
+ keys: () => Keymap,
98
+ /** A draft to open already filtered by, so a search can start where the reader is. */
99
+ initialFilter?: string,
100
+ /**
101
+ * Told which row the cursor is on whenever it moves.
102
+ *
103
+ * A list whose rows can be shown as well as taken needs the row under the
104
+ * cursor before it is picked — a theme is judged by looking at the screen it
105
+ * paints — and only the list knows where the cursor landed once a filter has
106
+ * reordered the rows under it.
107
+ */
108
+ onCursor?: ((row: Row | undefined) => void) | undefined);
81
109
  /** Rows matching the typed filter, best match first. */
82
110
  visible(): readonly Row[];
83
111
  /**
@@ -90,7 +118,27 @@ export declare class ListPicker<Row> {
90
118
  setNote(text: string | undefined): void;
91
119
  /** Apply one key press; returns an action only when the picker settles. */
92
120
  handleKey(data: string): PickerAction | undefined;
93
- card(): PickerCard;
121
+ /**
122
+ * Say which row the cursor is on now.
123
+ *
124
+ * Announced after every press rather than on the arrows alone: typing a filter
125
+ * moves the cursor to the best match, and a row the filter just hid must not
126
+ * stay on screen as the one being shown. A filter that matches nothing reports
127
+ * no row, which is the one answer that is not a row.
128
+ */
129
+ private announce;
130
+ /** One key press, without the announcement every way out of it owes. */
131
+ private step;
132
+ /**
133
+ * The card as it draws right now, in the window the caller can afford.
134
+ *
135
+ * The window is a parameter because the caller is the one that knows how many
136
+ * rows it has to give: a list at the end of the transcript takes the usual
137
+ * window whatever the terminal, while a box over it has to leave room for its
138
+ * own frame on a short screen. The rule stays here, so both windows keep the
139
+ * cursor centred and the counts above and below honest.
140
+ */
141
+ card(window?: number): PickerCard;
94
142
  }
95
143
  /**
96
144
  * One stored session chosen by key press.
@@ -1 +1 @@
1
- {"version":3,"file":"picker.d.ts","sourceRoot":"","sources":["../../src/ui/picker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"picker.d.ts","sourceRoot":"","sources":["../../src/ui/picker.ts"],"names":[],"mappings":"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"}
package/lib/ui/picker.js CHANGED
@@ -12,6 +12,18 @@ import { describePreset } from "../agent/presets.js";
12
12
  * window stays around the cursor instead, and the counts say what is hidden.
13
13
  */
14
14
  export const PICKER_WINDOW = 12;
15
+ /** Runs of whitespace, including the newlines a seeded draft or a paste can carry. */
16
+ const WHITESPACE = /\s+/gu;
17
+ /**
18
+ * Fold a value onto one drawn row.
19
+ *
20
+ * A filter can be seeded from a multiline draft or typed through a paste, and a
21
+ * raw newline inside a row would push the frame's own accounting apart; the
22
+ * needle the list matches with stays untouched.
23
+ */
24
+ export function singleLine(text) {
25
+ return text.replace(WHITESPACE, ' ').trim();
26
+ }
15
27
  const MINUTE_MS = 60_000;
16
28
  const HOUR_MS = 60 * MINUTE_MS;
17
29
  const DAY_MS = 24 * HOUR_MS;
@@ -43,6 +55,7 @@ export class ListPicker {
43
55
  haystackOf;
44
56
  hints;
45
57
  keys;
58
+ onCursor;
46
59
  cursor = 0;
47
60
  filter = '';
48
61
  note;
@@ -52,7 +65,18 @@ export class ListPicker {
52
65
  /** Extra text the filter matches besides the row's own label. */
53
66
  haystackOf, hints,
54
67
  /** The keys in force, read per press so a settings edit lands on the next key. */
55
- keys) {
68
+ keys,
69
+ /** A draft to open already filtered by, so a search can start where the reader is. */
70
+ initialFilter = '',
71
+ /**
72
+ * Told which row the cursor is on whenever it moves.
73
+ *
74
+ * A list whose rows can be shown as well as taken needs the row under the
75
+ * cursor before it is picked — a theme is judged by looking at the screen it
76
+ * paints — and only the list knows where the cursor landed once a filter has
77
+ * reordered the rows under it.
78
+ */
79
+ onCursor) {
56
80
  this.source = source;
57
81
  this.heading = heading;
58
82
  this.idOf = idOf;
@@ -60,6 +84,8 @@ export class ListPicker {
60
84
  this.haystackOf = haystackOf;
61
85
  this.hints = hints;
62
86
  this.keys = keys;
87
+ this.onCursor = onCursor;
88
+ this.filter = initialFilter;
63
89
  }
64
90
  /** Rows matching the typed filter, best match first. */
65
91
  visible() {
@@ -86,6 +112,29 @@ export class ListPicker {
86
112
  }
87
113
  /** Apply one key press; returns an action only when the picker settles. */
88
114
  handleKey(data) {
115
+ const action = this.step(data);
116
+ // A press that settles the list needs no announcement: the row it settled on
117
+ // is the row already on screen.
118
+ if (action === undefined)
119
+ this.announce();
120
+ return action;
121
+ }
122
+ /**
123
+ * Say which row the cursor is on now.
124
+ *
125
+ * Announced after every press rather than on the arrows alone: typing a filter
126
+ * moves the cursor to the best match, and a row the filter just hid must not
127
+ * stay on screen as the one being shown. A filter that matches nothing reports
128
+ * no row, which is the one answer that is not a row.
129
+ */
130
+ announce() {
131
+ if (this.onCursor === undefined)
132
+ return;
133
+ const rows = this.visible();
134
+ this.onCursor(rows[Math.min(this.cursor, Math.max(0, rows.length - 1))]);
135
+ }
136
+ /** One key press, without the announcement every way out of it owes. */
137
+ step(data) {
89
138
  this.note = undefined;
90
139
  const rows = this.visible();
91
140
  // A picker owns the keyboard while it is open, so the interrupt key has to
@@ -122,21 +171,31 @@ export class ListPicker {
122
171
  }
123
172
  return undefined;
124
173
  }
125
- card() {
174
+ /**
175
+ * The card as it draws right now, in the window the caller can afford.
176
+ *
177
+ * The window is a parameter because the caller is the one that knows how many
178
+ * rows it has to give: a list at the end of the transcript takes the usual
179
+ * window whatever the terminal, while a box over it has to leave room for its
180
+ * own frame on a short screen. The rule stays here, so both windows keep the
181
+ * cursor centred and the counts above and below honest.
182
+ */
183
+ card(window = PICKER_WINDOW) {
126
184
  const rows = this.visible();
127
185
  const cursor = Math.min(this.cursor, Math.max(0, rows.length - 1));
128
- const start = Math.max(0, Math.min(cursor - Math.floor(PICKER_WINDOW / 2), rows.length - PICKER_WINDOW));
129
- const window = rows.slice(start, start + PICKER_WINDOW);
186
+ const size = Math.max(1, Math.min(window, rows.length));
187
+ const start = Math.max(0, Math.min(cursor - Math.floor(size / 2), rows.length - size));
188
+ const shown = rows.slice(start, start + size);
130
189
  return {
131
190
  title: this.heading(),
132
191
  note: this.note,
133
- rows: window.map((row, index) => ({
192
+ rows: shown.map((row, index) => ({
134
193
  ...this.describe(row),
135
194
  current: start + index === cursor,
136
195
  })),
137
196
  above: start,
138
- below: Math.max(0, rows.length - start - window.length),
139
- filter: this.filter,
197
+ below: Math.max(0, rows.length - start - shown.length),
198
+ filter: singleLine(this.filter),
140
199
  hint: rows.length === 0 ? this.hints.empty() : this.hints.listed(),
141
200
  };
142
201
  }