@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.
- package/LICENSE +1 -1
- package/README.md +407 -53
- package/cordis.patch.yml +14 -0
- package/lib/agent/present.js +1 -1
- package/lib/agent/present.js.map +1 -1
- package/lib/agent/projections.d.ts +22 -6
- package/lib/agent/projections.d.ts.map +1 -1
- package/lib/agent/projections.js +21 -10
- package/lib/agent/projections.js.map +1 -1
- package/lib/agent/prompt-history.d.ts +89 -0
- package/lib/agent/prompt-history.d.ts.map +1 -0
- package/lib/agent/prompt-history.js +382 -0
- package/lib/agent/prompt-history.js.map +1 -0
- package/lib/agent/status.d.ts +6 -0
- package/lib/agent/status.d.ts.map +1 -1
- package/lib/agent/status.js +1 -0
- package/lib/agent/status.js.map +1 -1
- package/lib/cards.d.ts +83 -13
- package/lib/cards.d.ts.map +1 -1
- package/lib/cards.js +96 -28
- package/lib/cards.js.map +1 -1
- package/lib/export.d.ts +3 -3
- package/lib/export.d.ts.map +1 -1
- package/lib/export.js +23 -13
- package/lib/export.js.map +1 -1
- package/lib/fold-cursor.d.ts +12 -5
- package/lib/fold-cursor.d.ts.map +1 -1
- package/lib/fold-cursor.js +16 -5
- package/lib/fold-cursor.js.map +1 -1
- package/lib/gates.d.ts.map +1 -1
- package/lib/gates.js +42 -9
- package/lib/gates.js.map +1 -1
- package/lib/herdr/client.d.ts +57 -0
- package/lib/herdr/client.d.ts.map +1 -0
- package/lib/herdr/client.js +218 -0
- package/lib/herdr/client.js.map +1 -0
- package/lib/herdr/constants.d.ts +93 -0
- package/lib/herdr/constants.d.ts.map +1 -0
- package/lib/herdr/constants.js +79 -0
- package/lib/herdr/constants.js.map +1 -0
- package/lib/herdr/reporter.d.ts +60 -0
- package/lib/herdr/reporter.d.ts.map +1 -0
- package/lib/herdr/reporter.js +217 -0
- package/lib/herdr/reporter.js.map +1 -0
- package/lib/herdr/state.d.ts +57 -0
- package/lib/herdr/state.d.ts.map +1 -0
- package/lib/herdr/state.js +62 -0
- package/lib/herdr/state.js.map +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +700 -81
- package/lib/index.js.map +1 -1
- package/lib/injection.d.ts +9 -0
- package/lib/injection.d.ts.map +1 -0
- package/lib/injection.js +128 -0
- package/lib/injection.js.map +1 -0
- package/lib/input/actions.d.ts +19 -2
- package/lib/input/actions.d.ts.map +1 -1
- package/lib/input/actions.js +60 -11
- package/lib/input/actions.js.map +1 -1
- package/lib/input/cancel.d.ts +55 -0
- package/lib/input/cancel.d.ts.map +1 -0
- package/lib/input/cancel.js +47 -0
- package/lib/input/cancel.js.map +1 -0
- package/lib/input/completion.d.ts +5 -2
- package/lib/input/completion.d.ts.map +1 -1
- package/lib/input/completion.js +79 -4
- package/lib/input/completion.js.map +1 -1
- package/lib/input/file-search.d.ts +105 -0
- package/lib/input/file-search.d.ts.map +1 -0
- package/lib/input/file-search.js +665 -0
- package/lib/input/file-search.js.map +1 -0
- package/lib/input/fuzzy.d.ts +42 -0
- package/lib/input/fuzzy.d.ts.map +1 -0
- package/lib/input/fuzzy.js +289 -0
- package/lib/input/fuzzy.js.map +1 -0
- package/lib/input/ghost.d.ts +46 -0
- package/lib/input/ghost.d.ts.map +1 -0
- package/lib/input/ghost.js +58 -0
- package/lib/input/ghost.js.map +1 -0
- package/lib/input/keymap.d.ts +3 -1
- package/lib/input/keymap.d.ts.map +1 -1
- package/lib/input/keymap.js +16 -3
- package/lib/input/keymap.js.map +1 -1
- package/lib/input/match.d.ts +12 -4
- package/lib/input/match.d.ts.map +1 -1
- package/lib/input/match.js +50 -51
- package/lib/input/match.js.map +1 -1
- package/lib/input/submission.d.ts +34 -1
- package/lib/input/submission.d.ts.map +1 -1
- package/lib/input/submission.js +33 -5
- package/lib/input/submission.js.map +1 -1
- package/lib/keys-command.d.ts +19 -3
- package/lib/keys-command.d.ts.map +1 -1
- package/lib/keys-command.js +34 -31
- package/lib/keys-command.js.map +1 -1
- package/lib/settings-notice.d.ts +5 -0
- package/lib/settings-notice.d.ts.map +1 -1
- package/lib/settings-notice.js +9 -7
- package/lib/settings-notice.js.map +1 -1
- package/lib/stash/lock.d.ts +72 -0
- package/lib/stash/lock.d.ts.map +1 -0
- package/lib/stash/lock.js +374 -0
- package/lib/stash/lock.js.map +1 -0
- package/lib/stash/paths.d.ts +22 -0
- package/lib/stash/paths.d.ts.map +1 -0
- package/lib/stash/paths.js +84 -0
- package/lib/stash/paths.js.map +1 -0
- package/lib/stash/private-fs.d.ts +61 -0
- package/lib/stash/private-fs.d.ts.map +1 -0
- package/lib/stash/private-fs.js +379 -0
- package/lib/stash/private-fs.js.map +1 -0
- package/lib/stash/schema.d.ts +56 -0
- package/lib/stash/schema.d.ts.map +1 -0
- package/lib/stash/schema.js +108 -0
- package/lib/stash/schema.js.map +1 -0
- package/lib/stash/store.d.ts +92 -0
- package/lib/stash/store.d.ts.map +1 -0
- package/lib/stash/store.js +262 -0
- package/lib/stash/store.js.map +1 -0
- package/lib/stash.d.ts +122 -0
- package/lib/stash.d.ts.map +1 -0
- package/lib/stash.js +353 -0
- package/lib/stash.js.map +1 -0
- package/lib/terminal/external-editor.d.ts +93 -0
- package/lib/terminal/external-editor.d.ts.map +1 -0
- package/lib/terminal/external-editor.js +263 -0
- package/lib/terminal/external-editor.js.map +1 -0
- package/lib/terminal/host-writes.d.ts +39 -0
- package/lib/terminal/host-writes.d.ts.map +1 -0
- package/lib/terminal/host-writes.js +136 -0
- package/lib/terminal/host-writes.js.map +1 -0
- package/lib/terminal/signals.d.ts +25 -0
- package/lib/terminal/signals.d.ts.map +1 -0
- package/lib/terminal/signals.js +39 -0
- package/lib/terminal/signals.js.map +1 -0
- package/lib/terminal/warning-screen.d.ts +19 -1
- package/lib/terminal/warning-screen.d.ts.map +1 -1
- package/lib/terminal/warning-screen.js +37 -1
- package/lib/terminal/warning-screen.js.map +1 -1
- package/lib/terminal-text.d.ts +72 -0
- package/lib/terminal-text.d.ts.map +1 -0
- package/lib/terminal-text.js +603 -0
- package/lib/terminal-text.js.map +1 -0
- package/lib/text.d.ts +45 -6
- package/lib/text.d.ts.map +1 -1
- package/lib/text.js +106 -46
- package/lib/text.js.map +1 -1
- package/lib/theme-command.d.ts +7 -4
- package/lib/theme-command.d.ts.map +1 -1
- package/lib/theme-command.js +32 -12
- package/lib/theme-command.js.map +1 -1
- package/lib/theme-files.d.ts +110 -0
- package/lib/theme-files.d.ts.map +1 -0
- package/lib/theme-files.js +352 -0
- package/lib/theme-files.js.map +1 -0
- package/lib/theme-schema.d.ts +121 -0
- package/lib/theme-schema.d.ts.map +1 -0
- package/lib/theme-schema.js +87 -0
- package/lib/theme-schema.js.map +1 -0
- package/lib/theme-settings.d.ts +72 -10
- package/lib/theme-settings.d.ts.map +1 -1
- package/lib/theme-settings.js +162 -45
- package/lib/theme-settings.js.map +1 -1
- package/lib/theme-tokens.d.ts +39 -7
- package/lib/theme-tokens.d.ts.map +1 -1
- package/lib/theme-tokens.js +104 -13
- package/lib/theme-tokens.js.map +1 -1
- package/lib/theme.d.ts +24 -2
- package/lib/theme.d.ts.map +1 -1
- package/lib/theme.js +49 -6
- package/lib/theme.js.map +1 -1
- package/lib/todo-guard.d.ts +116 -0
- package/lib/todo-guard.d.ts.map +1 -0
- package/lib/todo-guard.js +259 -0
- package/lib/todo-guard.js.map +1 -0
- package/lib/tool-display.d.ts +57 -0
- package/lib/tool-display.d.ts.map +1 -0
- package/lib/tool-display.js +51 -0
- package/lib/tool-display.js.map +1 -0
- package/lib/transcript.d.ts +28 -0
- package/lib/transcript.d.ts.map +1 -1
- package/lib/transcript.js +126 -37
- package/lib/transcript.js.map +1 -1
- package/lib/ui/diff.d.ts +49 -0
- package/lib/ui/diff.d.ts.map +1 -0
- package/lib/ui/diff.js +208 -0
- package/lib/ui/diff.js.map +1 -0
- package/lib/ui/dock.d.ts.map +1 -1
- package/lib/ui/dock.js +9 -6
- package/lib/ui/dock.js.map +1 -1
- package/lib/ui/editor.d.ts +41 -1
- package/lib/ui/editor.d.ts.map +1 -1
- package/lib/ui/editor.js +100 -4
- package/lib/ui/editor.js.map +1 -1
- package/lib/ui/frame.d.ts +16 -5
- package/lib/ui/frame.d.ts.map +1 -1
- package/lib/ui/frame.js +32 -13
- package/lib/ui/frame.js.map +1 -1
- package/lib/ui/history-picker.d.ts +15 -0
- package/lib/ui/history-picker.d.ts.map +1 -0
- package/lib/ui/history-picker.js +26 -0
- package/lib/ui/history-picker.js.map +1 -0
- package/lib/ui/keymap-picker.d.ts +16 -0
- package/lib/ui/keymap-picker.d.ts.map +1 -0
- package/lib/ui/keymap-picker.js +41 -0
- package/lib/ui/keymap-picker.js.map +1 -0
- package/lib/ui/layout.d.ts +33 -0
- package/lib/ui/layout.d.ts.map +1 -0
- package/lib/ui/layout.js +33 -0
- package/lib/ui/layout.js.map +1 -0
- package/lib/ui/markdown.d.ts +25 -3
- package/lib/ui/markdown.d.ts.map +1 -1
- package/lib/ui/markdown.js +12 -8
- package/lib/ui/markdown.js.map +1 -1
- package/lib/ui/picker-card.d.ts +42 -0
- package/lib/ui/picker-card.d.ts.map +1 -0
- package/lib/ui/picker-card.js +148 -0
- package/lib/ui/picker-card.js.map +1 -0
- package/lib/ui/picker.d.ts +50 -2
- package/lib/ui/picker.d.ts.map +1 -1
- package/lib/ui/picker.js +66 -7
- package/lib/ui/picker.js.map +1 -1
- package/lib/ui/prompt.d.ts +12 -2
- package/lib/ui/prompt.d.ts.map +1 -1
- package/lib/ui/prompt.js +25 -2
- package/lib/ui/prompt.js.map +1 -1
- package/lib/ui/rows.d.ts +8 -6
- package/lib/ui/rows.d.ts.map +1 -1
- package/lib/ui/rows.js +10 -18
- package/lib/ui/rows.js.map +1 -1
- package/lib/ui/stash-picker.d.ts +43 -0
- package/lib/ui/stash-picker.d.ts.map +1 -0
- package/lib/ui/stash-picker.js +78 -0
- package/lib/ui/stash-picker.js.map +1 -0
- package/lib/ui/status.d.ts +2 -0
- package/lib/ui/status.d.ts.map +1 -1
- package/lib/ui/status.js +25 -16
- package/lib/ui/status.js.map +1 -1
- package/lib/ui/theme-picker.d.ts +18 -0
- package/lib/ui/theme-picker.d.ts.map +1 -0
- package/lib/ui/theme-picker.js +68 -0
- package/lib/ui/theme-picker.js.map +1 -0
- package/lib/ui/view.d.ts +146 -22
- package/lib/ui/view.d.ts.map +1 -1
- package/lib/ui/view.js +461 -154
- package/lib/ui/view.js.map +1 -1
- package/lib/work.d.ts +3 -1
- package/lib/work.d.ts.map +1 -1
- package/lib/work.js +9 -1
- package/lib/work.js.map +1 -1
- package/package.json +7 -1
- package/themes/deepseek-blue.yaml +183 -0
- 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"}
|
package/lib/ui/layout.js
ADDED
|
@@ -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"}
|
package/lib/ui/markdown.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
/**
|
package/lib/ui/markdown.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/ui/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
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"}
|
package/lib/ui/markdown.js
CHANGED
|
@@ -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
|
|
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
|
|
23
|
-
// part of a message's identity for the same reason the text is.
|
|
24
|
-
const key = live ?
|
|
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,
|
|
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
|
}
|
package/lib/ui/markdown.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/ui/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,
|
|
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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"}
|
package/lib/ui/picker.d.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
package/lib/ui/picker.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"picker.d.ts","sourceRoot":"","sources":["../../src/ui/picker.ts"],"names":[],"mappings":"AACA,OAAO,EAAqC,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAGpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAAqC,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACxG,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAExE,wCAAwC;AACxC,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IACxC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAC1B;AAED,sEAAsE;AACtE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,QAAQ,CAAC,IAAI,EAAE,SAAS,SAAS,EAAE,CAAA;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,6BAA6B;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED,iDAAiD;AACjD,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAA;AAE/B;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,KAAK,CAAA;AAE/B;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,MAAM,CAAA;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAA;CAC9B;AAMD,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMlE;AAED;;;;;;;;GAQG;AACH,qBAAa,UAAU,CAAC,GAAG;IAMvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,mCAAmC;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,iEAAiE;IACjE,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,kFAAkF;IAClF,OAAO,CAAC,QAAQ,CAAC,IAAI;
|
|
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
|
-
|
|
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
|
|
129
|
-
const
|
|
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:
|
|
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 -
|
|
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
|
}
|