@sagmans/dsh-tui 0.1.2 → 0.3.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/README.md +196 -17
- package/cordis.patch.yml +4 -2
- package/lib/agent/host.d.ts +14 -0
- package/lib/agent/host.d.ts.map +1 -1
- package/lib/agent/host.js +20 -4
- package/lib/agent/host.js.map +1 -1
- package/lib/agent/model.d.ts +50 -0
- package/lib/agent/model.d.ts.map +1 -1
- package/lib/agent/model.js +87 -1
- package/lib/agent/model.js.map +1 -1
- package/lib/agent/present.d.ts +7 -1
- package/lib/agent/present.d.ts.map +1 -1
- package/lib/agent/present.js +33 -4
- package/lib/agent/present.js.map +1 -1
- package/lib/agent/presets.d.ts +7 -1
- package/lib/agent/presets.d.ts.map +1 -1
- package/lib/agent/presets.js +7 -3
- package/lib/agent/presets.js.map +1 -1
- package/lib/agent/status.d.ts +10 -0
- package/lib/agent/status.d.ts.map +1 -1
- package/lib/agent/status.js +2 -0
- package/lib/agent/status.js.map +1 -1
- package/lib/cards.d.ts +127 -7
- package/lib/cards.d.ts.map +1 -1
- package/lib/cards.js +381 -52
- package/lib/cards.js.map +1 -1
- package/lib/export.d.ts +5 -4
- package/lib/export.d.ts.map +1 -1
- package/lib/export.js +62 -13
- package/lib/export.js.map +1 -1
- package/lib/fold-cursor.d.ts +23 -0
- package/lib/fold-cursor.d.ts.map +1 -0
- package/lib/fold-cursor.js +31 -0
- package/lib/fold-cursor.js.map +1 -0
- package/lib/gates.d.ts +145 -2
- package/lib/gates.d.ts.map +1 -1
- package/lib/gates.js +335 -40
- package/lib/gates.js.map +1 -1
- package/lib/index.d.ts +7 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +651 -92
- package/lib/index.js.map +1 -1
- package/lib/input/actions.d.ts +136 -0
- package/lib/input/actions.d.ts.map +1 -0
- package/lib/input/actions.js +740 -0
- package/lib/input/actions.js.map +1 -0
- package/lib/input/keymap-settings.d.ts +21 -0
- package/lib/input/keymap-settings.d.ts.map +1 -0
- package/lib/input/keymap-settings.js +28 -0
- package/lib/input/keymap-settings.js.map +1 -0
- package/lib/input/keymap.d.ts +91 -0
- package/lib/input/keymap.d.ts.map +1 -0
- package/lib/input/keymap.js +170 -0
- package/lib/input/keymap.js.map +1 -0
- package/lib/input/match.d.ts +10 -0
- package/lib/input/match.d.ts.map +1 -0
- package/lib/input/match.js +83 -0
- package/lib/input/match.js.map +1 -0
- package/lib/input/submission.d.ts +8 -1
- package/lib/input/submission.d.ts.map +1 -1
- package/lib/input/submission.js +9 -2
- package/lib/input/submission.js.map +1 -1
- package/lib/input.d.ts +17 -0
- package/lib/input.d.ts.map +1 -0
- package/lib/input.js +27 -0
- package/lib/input.js.map +1 -0
- package/lib/keys-command.d.ts +16 -0
- package/lib/keys-command.d.ts.map +1 -0
- package/lib/keys-command.js +60 -0
- package/lib/keys-command.js.map +1 -0
- package/lib/queue.d.ts +6 -0
- package/lib/queue.d.ts.map +1 -0
- package/lib/queue.js +68 -0
- package/lib/queue.js.map +1 -0
- package/lib/settings-notice.d.ts +19 -0
- package/lib/settings-notice.d.ts.map +1 -0
- package/lib/settings-notice.js +30 -0
- package/lib/settings-notice.js.map +1 -0
- package/lib/terminal/warning-screen.d.ts +8 -0
- package/lib/terminal/warning-screen.d.ts.map +1 -0
- package/lib/terminal/warning-screen.js +66 -0
- package/lib/terminal/warning-screen.js.map +1 -0
- package/lib/theme-capability.d.ts +36 -0
- package/lib/theme-capability.d.ts.map +1 -0
- package/lib/theme-capability.js +190 -0
- package/lib/theme-capability.js.map +1 -0
- package/lib/theme-command.d.ts +10 -0
- package/lib/theme-command.d.ts.map +1 -0
- package/lib/theme-command.js +61 -0
- package/lib/theme-command.js.map +1 -0
- package/lib/theme-settings.d.ts +196 -0
- package/lib/theme-settings.d.ts.map +1 -0
- package/lib/theme-settings.js +229 -0
- package/lib/theme-settings.js.map +1 -0
- package/lib/theme-tokens.d.ts +110 -0
- package/lib/theme-tokens.d.ts.map +1 -0
- package/lib/theme-tokens.js +427 -0
- package/lib/theme-tokens.js.map +1 -0
- package/lib/theme.d.ts +32 -35
- package/lib/theme.d.ts.map +1 -1
- package/lib/theme.js +112 -63
- package/lib/theme.js.map +1 -1
- package/lib/tokens.d.ts +15 -0
- package/lib/tokens.d.ts.map +1 -0
- package/lib/tokens.js +29 -0
- package/lib/tokens.js.map +1 -0
- package/lib/transcript.d.ts +26 -1
- package/lib/transcript.d.ts.map +1 -1
- package/lib/transcript.js +153 -25
- package/lib/transcript.js.map +1 -1
- package/lib/ui/dock.d.ts.map +1 -1
- package/lib/ui/dock.js +67 -28
- package/lib/ui/dock.js.map +1 -1
- package/lib/ui/editor.d.ts +51 -0
- package/lib/ui/editor.d.ts.map +1 -0
- package/lib/ui/editor.js +159 -0
- package/lib/ui/editor.js.map +1 -0
- package/lib/ui/frame.d.ts +54 -0
- package/lib/ui/frame.d.ts.map +1 -0
- package/lib/ui/frame.js +79 -0
- package/lib/ui/frame.js.map +1 -0
- package/lib/ui/gate-input.d.ts +20 -0
- package/lib/ui/gate-input.d.ts.map +1 -0
- package/lib/ui/gate-input.js +96 -0
- package/lib/ui/gate-input.js.map +1 -0
- package/lib/ui/markdown.d.ts +19 -2
- package/lib/ui/markdown.d.ts.map +1 -1
- package/lib/ui/markdown.js +37 -6
- package/lib/ui/markdown.js.map +1 -1
- package/lib/ui/mermaid.d.ts +44 -0
- package/lib/ui/mermaid.d.ts.map +1 -0
- package/lib/ui/mermaid.js +178 -0
- package/lib/ui/mermaid.js.map +1 -0
- package/lib/ui/picker.d.ts +66 -7
- package/lib/ui/picker.d.ts.map +1 -1
- package/lib/ui/picker.js +112 -19
- package/lib/ui/picker.js.map +1 -1
- package/lib/ui/prompt.d.ts +30 -0
- package/lib/ui/prompt.d.ts.map +1 -0
- package/lib/ui/prompt.js +45 -0
- package/lib/ui/prompt.js.map +1 -0
- package/lib/ui/queue.d.ts +24 -0
- package/lib/ui/queue.d.ts.map +1 -0
- package/lib/ui/queue.js +57 -0
- package/lib/ui/queue.js.map +1 -0
- package/lib/ui/status.d.ts +7 -1
- package/lib/ui/status.d.ts.map +1 -1
- package/lib/ui/status.js +84 -24
- package/lib/ui/status.js.map +1 -1
- package/lib/ui/view.d.ts +65 -2
- package/lib/ui/view.d.ts.map +1 -1
- package/lib/ui/view.js +319 -59
- package/lib/ui/view.js.map +1 -1
- package/lib/work.d.ts +48 -0
- package/lib/work.d.ts.map +1 -1
- package/lib/work.js +44 -0
- package/lib/work.js.map +1 -1
- package/package.json +5 -2
package/lib/ui/editor.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { Editor, isKittyProtocolActive, matchesKey, stripTerminalSequences, } from '@earendil-works/pi-tui';
|
|
2
|
+
import { ENTER_KEY, defaultKeymap } from "../input/actions.js";
|
|
3
|
+
import { promptKeys } from "../input/keymap.js";
|
|
4
|
+
import { FRAME_COLUMNS, FRAME_GLYPHS, MIN_BOX_WIDTH, PADDING_X } from "./frame.js";
|
|
5
|
+
/**
|
|
6
|
+
* Marks the rule that closes the input while one render is in flight.
|
|
7
|
+
*
|
|
8
|
+
* The base editor paints its completion menu after that rule, and this subclass
|
|
9
|
+
* has to know which rows those are to lift the menu above the box. The base
|
|
10
|
+
* offers no seam for that boundary, so one render pass tags its own closing rule
|
|
11
|
+
* and finds it again. The tag cannot collide with typed text, which the editor
|
|
12
|
+
* strips of control characters, and it never reaches the screen because the same
|
|
13
|
+
* pass removes it.
|
|
14
|
+
*/
|
|
15
|
+
const CLOSING_TAG = '\u0000';
|
|
16
|
+
/**
|
|
17
|
+
* The line feed a plain Enter is handed to the base class as.
|
|
18
|
+
*
|
|
19
|
+
* The base owns what a newline does to paste markers, history, and undo, and it
|
|
20
|
+
* reads this byte as one in every terminal mode, including the modes where it
|
|
21
|
+
* no longer calls Enter itself a newline.
|
|
22
|
+
*/
|
|
23
|
+
const NEWLINE_BYTE = '\n';
|
|
24
|
+
/** The one sequence a terminal that reports no modifiers sends for alt+enter. */
|
|
25
|
+
const LEGACY_ALT_ENTER = '\u001b\r';
|
|
26
|
+
/**
|
|
27
|
+
* The chords as the keyboard protocol spells them.
|
|
28
|
+
*
|
|
29
|
+
* The base class reads a legacy alt+enter and a bare line feed as line breaks
|
|
30
|
+
* before it looks at any binding, so each press is handed over as the chord the
|
|
31
|
+
* map actually holds for it. The submit path then runs whole, with the same
|
|
32
|
+
* guards a real chord meets, instead of a second sender beside it.
|
|
33
|
+
*/
|
|
34
|
+
const KITTY_ALT_ENTER = '\u001b[13;3u';
|
|
35
|
+
const KITTY_CTRL_J = '\u001b[106;5u';
|
|
36
|
+
/**
|
|
37
|
+
* The input bar drawn as a box, with its completion menu above it.
|
|
38
|
+
*
|
|
39
|
+
* The editor already draws the two rules and pads every row to the width it is
|
|
40
|
+
* given, so the frame comes from rendering two columns narrower and wrapping
|
|
41
|
+
* what comes back. The menu is lifted above the box because the reader scans it
|
|
42
|
+
* while writing: a list that grows upward never covers the line being typed.
|
|
43
|
+
*/
|
|
44
|
+
export class BoxedEditor extends Editor {
|
|
45
|
+
keymap;
|
|
46
|
+
/** Rows the menu took in the last render; a click there belongs to the list. */
|
|
47
|
+
menuRows = 0;
|
|
48
|
+
/** Rows between the rules in the last render; how the base editor counts its own. */
|
|
49
|
+
textRows = 0;
|
|
50
|
+
/** Whether the last render drew a frame, which is what a click is mapped through. */
|
|
51
|
+
boxed = false;
|
|
52
|
+
constructor(tui, theme, keymap = defaultKeymap) {
|
|
53
|
+
super(tui, theme, { paddingX: PADDING_X });
|
|
54
|
+
this.keymap = keymap;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Read the two presses the base class cannot place on its own.
|
|
58
|
+
*
|
|
59
|
+
* Enter breaks the line while the reader keeps that key for the line, so the
|
|
60
|
+
* press the base would submit with becomes a newline — except while it is
|
|
61
|
+
* picking a completion, the one press that chooses something instead of
|
|
62
|
+
* sending it, and except on a bar a question or a picker has borrowed, whose
|
|
63
|
+
* keys belong to whoever borrowed it.
|
|
64
|
+
*/
|
|
65
|
+
handleInput(data) {
|
|
66
|
+
if (this.disableSubmit) {
|
|
67
|
+
super.handleInput(data);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const keys = promptKeys(this.keymap());
|
|
71
|
+
// A bare line feed is a line break in the base class whatever the map says,
|
|
72
|
+
// so a reader who moved the line break off ctrl+j and sends with it would
|
|
73
|
+
// keep getting lines. It is read before the Return guard because a terminal
|
|
74
|
+
// without the protocol reports that byte as Return as well — and only
|
|
75
|
+
// without it, because a terminal that speaks the protocol sends the chord
|
|
76
|
+
// itself and reports a line feed for a key the reader meant as a line.
|
|
77
|
+
if (data === NEWLINE_BYTE && !isKittyProtocolActive() && keys.submit.includes('ctrl+j')) {
|
|
78
|
+
super.handleInput(KITTY_CTRL_J);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (keys.enterBreaksLine && matchesKey(data, ENTER_KEY) && !this.isShowingAutocomplete()) {
|
|
82
|
+
super.handleInput(NEWLINE_BYTE);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Only while the terminal cannot tell alt+enter apart from a mapping, and
|
|
86
|
+
// only for the reader who bound that key: with the protocol active the same
|
|
87
|
+
// sequence is the reader's shift+enter, and a bar that answered it as a send
|
|
88
|
+
// because some other chord contains the same bytes would be submitting a key
|
|
89
|
+
// the reader never bound. A reader who keeps the sequence for a line is
|
|
90
|
+
// answered by the library's own rule.
|
|
91
|
+
if (data === LEGACY_ALT_ENTER && !isKittyProtocolActive() && keys.submit.includes('alt+enter')) {
|
|
92
|
+
super.handleInput(KITTY_ALT_ENTER);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
super.handleInput(data);
|
|
96
|
+
}
|
|
97
|
+
/** Tag the closing rule so {@link render} can find where the box ends. */
|
|
98
|
+
renderBottomBorder(width, hiddenLineCount) {
|
|
99
|
+
return `${super.renderBottomBorder(width, hiddenLineCount)}${CLOSING_TAG}`;
|
|
100
|
+
}
|
|
101
|
+
/** A rule redrawn as a box edge, so the corners carry the border's own colour. */
|
|
102
|
+
edge(open, close, row) {
|
|
103
|
+
return this.borderColor(`${open}${stripTerminalSequences(row)}${close}`);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Rewrite one row of the text before it is framed, so a subclass can hide
|
|
107
|
+
* what it collects without hiding the box the reader types inside.
|
|
108
|
+
*/
|
|
109
|
+
decorateText(row) {
|
|
110
|
+
return row;
|
|
111
|
+
}
|
|
112
|
+
render(width) {
|
|
113
|
+
const side = this.borderColor(FRAME_GLYPHS.side);
|
|
114
|
+
// A frame narrower than its own furniture would eat the text it exists to
|
|
115
|
+
// hold, and a hidden border token asks for no frame at all: both cases keep
|
|
116
|
+
// the plain rules rather than reserve columns for what nobody can see.
|
|
117
|
+
if (width < MIN_BOX_WIDTH || side === '') {
|
|
118
|
+
this.boxed = false;
|
|
119
|
+
return super.render(width).map(row => this.decorateText(row));
|
|
120
|
+
}
|
|
121
|
+
const rows = super.render(width - FRAME_COLUMNS);
|
|
122
|
+
const closing = rows.findIndex(row => row.includes(CLOSING_TAG));
|
|
123
|
+
// No tagged rule means the base did not paint the shape this class knows;
|
|
124
|
+
// an unframed render reports that better than a frame drawn on a guess.
|
|
125
|
+
if (closing < 0)
|
|
126
|
+
return rows.map(row => row.replace(CLOSING_TAG, ''));
|
|
127
|
+
const menu = rows.slice(closing + 1);
|
|
128
|
+
const text = rows.slice(1, closing);
|
|
129
|
+
this.menuRows = menu.length;
|
|
130
|
+
this.textRows = text.length;
|
|
131
|
+
this.boxed = true;
|
|
132
|
+
// The menu keeps the page's own width; only its rows moved above the box.
|
|
133
|
+
const lines = menu.map(row => row + ' '.repeat(FRAME_COLUMNS));
|
|
134
|
+
lines.push(this.edge(FRAME_GLYPHS.topLeft, FRAME_GLYPHS.topRight, rows[0] ?? ''));
|
|
135
|
+
for (const row of text)
|
|
136
|
+
lines.push(`${side}${this.decorateText(row)}${side}`);
|
|
137
|
+
lines.push(this.edge(FRAME_GLYPHS.bottomLeft, FRAME_GLYPHS.bottomRight, rows[closing].replace(CLOSING_TAG, '')));
|
|
138
|
+
return lines;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Map a click back through the frame and the lifted menu.
|
|
142
|
+
*
|
|
143
|
+
* The base editor hit-tests the rows it painted in its own order — the input
|
|
144
|
+
* under the top rule, the menu after the bottom one — and measures columns
|
|
145
|
+
* from the edge of the width it was asked for. This subclass draws the menu
|
|
146
|
+
* first and indents every line by the frame, so a click has to be translated
|
|
147
|
+
* back before that hit test can say what it hit.
|
|
148
|
+
*/
|
|
149
|
+
handleMouse(event) {
|
|
150
|
+
if (!this.boxed)
|
|
151
|
+
return super.handleMouse(event);
|
|
152
|
+
const width = event.width - FRAME_COLUMNS;
|
|
153
|
+
if (event.y < this.menuRows) {
|
|
154
|
+
return super.handleMouse({ ...event, width, y: event.y + this.textRows + 2 });
|
|
155
|
+
}
|
|
156
|
+
return super.handleMouse({ ...event, width, x: event.x - 1, y: event.y - this.menuRows });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=editor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor.js","sourceRoot":"","sources":["../../src/ui/editor.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,qBAAqB,EACrB,UAAU,EACV,sBAAsB,GAKvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,aAAa,EAAe,MAAM,qBAAqB,CAAA;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAElF;;;;;;;;;GASG;AACH,MAAM,WAAW,GAAG,QAAQ,CAAA;AAE5B;;;;;;GAMG;AACH,MAAM,YAAY,GAAG,IAAI,CAAA;AAEzB,iFAAiF;AACjF,MAAM,gBAAgB,GAAG,UAAU,CAAA;AAEnC;;;;;;;GAOG;AACH,MAAM,eAAe,GAAG,cAAc,CAAA;AACtC,MAAM,YAAY,GAAG,eAAe,CAAA;AAEpC;;;;;;;GAOG;AACH,MAAM,OAAO,WAAY,SAAQ,MAAM;IAQsB;IAP3D,gFAAgF;IACxE,QAAQ,GAAG,CAAC,CAAA;IACpB,qFAAqF;IAC7E,QAAQ,GAAG,CAAC,CAAA;IACpB,qFAAqF;IAC7E,KAAK,GAAG,KAAK,CAAA;IAErB,YAAY,GAAQ,EAAE,KAAkB,EAAmB,SAAuB,aAAa;QAC7F,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAA;QADe,WAAM,GAAN,MAAM,CAA8B;IAE/F,CAAC;IAED;;;;;;;;OAQG;IACM,WAAW,CAAC,IAAY;QAC/B,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;YACvB,OAAM;QACR,CAAC;QACD,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;QACtC,4EAA4E;QAC5E,0EAA0E;QAC1E,4EAA4E;QAC5E,sEAAsE;QACtE,0EAA0E;QAC1E,uEAAuE;QACvE,IAAI,IAAI,KAAK,YAAY,IAAI,CAAC,qBAAqB,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YACxF,KAAK,CAAC,WAAW,CAAC,YAAY,CAAC,CAAA;YAC/B,OAAM;QACR,CAAC;QACD,IAAI,IAAI,CAAC,eAAe,IAAI,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,EAAE,CAAC;YACzF,KAAK,CAAC,WAAW,CAAC,YAAY,CAAC,CAAA;YAC/B,OAAM;QACR,CAAC;QACD,0EAA0E;QAC1E,4EAA4E;QAC5E,6EAA6E;QAC7E,6EAA6E;QAC7E,wEAAwE;QACxE,sCAAsC;QACtC,IAAI,IAAI,KAAK,gBAAgB,IAAI,CAAC,qBAAqB,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YAC/F,KAAK,CAAC,WAAW,CAAC,eAAe,CAAC,CAAA;YAClC,OAAM;QACR,CAAC;QACD,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IAED,0EAA0E;IACvD,kBAAkB,CAAC,KAAa,EAAE,eAAuB;QAC1E,OAAO,GAAG,KAAK,CAAC,kBAAkB,CAAC,KAAK,EAAE,eAAe,CAAC,GAAG,WAAW,EAAE,CAAA;IAC5E,CAAC;IAED,kFAAkF;IAC1E,IAAI,CAAC,IAAY,EAAE,KAAa,EAAE,GAAW;QACnD,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,IAAI,GAAG,sBAAsB,CAAC,GAAG,CAAC,GAAG,KAAK,EAAE,CAAC,CAAA;IAC1E,CAAC;IAED;;;OAGG;IACO,YAAY,CAAC,GAAW;QAChC,OAAO,GAAG,CAAA;IACZ,CAAC;IAEQ,MAAM,CAAC,KAAa;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,IAAI,CAAC,CAAA;QAChD,0EAA0E;QAC1E,4EAA4E;QAC5E,uEAAuE;QACvE,IAAI,KAAK,GAAG,aAAa,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;YACzC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;YAClB,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAA;QAC/D,CAAC;QACD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,GAAG,aAAa,CAAC,CAAA;QAChD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAA;QAChE,0EAA0E;QAC1E,wEAAwE;QACxE,IAAI,OAAO,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAA;QACrE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAA;QACpC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAA;QACnC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAA;QAC3B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAA;QAC3B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,0EAA0E;QAC1E,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAA;QAC9D,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;QACjF,KAAK,MAAM,GAAG,IAAI,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAA;QAC7E,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,YAAY,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAE,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;QACjH,OAAO,KAAK,CAAA;IACd,CAAC;IAED;;;;;;;;OAQG;IACM,WAAW,CAAC,KAAoB;QACvC,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;QAChD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,GAAG,aAAa,CAAA;QACzC,IAAI,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC5B,OAAO,KAAK,CAAC,WAAW,CAAC,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC,CAAA;QAC/E,CAAC;QACD,OAAO,KAAK,CAAC,WAAW,CAAC,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAA;IAC3F,CAAC;CACF"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The frame every prompt is closed into.
|
|
3
|
+
*
|
|
4
|
+
* The editor, the prompts queued above it, and the prompts already submitted
|
|
5
|
+
* draw the same glyphs at the same width, so one prompt reads as the same object
|
|
6
|
+
* through its whole life; keeping the shape here is what stops the three from
|
|
7
|
+
* drifting.
|
|
8
|
+
*/
|
|
9
|
+
export declare const FRAME_GLYPHS: {
|
|
10
|
+
readonly topLeft: "╭";
|
|
11
|
+
readonly topRight: "╮";
|
|
12
|
+
readonly bottomLeft: "╰";
|
|
13
|
+
readonly bottomRight: "╯";
|
|
14
|
+
readonly side: "│";
|
|
15
|
+
};
|
|
16
|
+
/** The frame costs one column each side, so the text wraps that much narrower. */
|
|
17
|
+
export declare const FRAME_COLUMNS = 2;
|
|
18
|
+
/** One column of air inside the frame, so a full line never touches the border. */
|
|
19
|
+
export declare const PADDING_X = 1;
|
|
20
|
+
/** A box needs both edges, both paddings, and one column left to type in. */
|
|
21
|
+
export declare const MIN_BOX_WIDTH: number;
|
|
22
|
+
/** The columns the text of a bar owns, given the width of everything inside its frame. */
|
|
23
|
+
export declare function textWidth(innerWidth: number): number;
|
|
24
|
+
/** One text row of a bar: the air, the text, and the rest of the inside width. */
|
|
25
|
+
export declare function textRow(text: string, innerWidth: number): string;
|
|
26
|
+
/**
|
|
27
|
+
* The rule that closes a bar, naming what the bar could not fit.
|
|
28
|
+
*
|
|
29
|
+
* A hidden count is a fact about the text, not extra text, so it rides the rule
|
|
30
|
+
* the way the editor's own scroll indicator does: the same label in the same
|
|
31
|
+
* place, at the same width, so both bars report a fold identically.
|
|
32
|
+
*/
|
|
33
|
+
export declare function frameRule(innerWidth: number, hiddenRows: number): string;
|
|
34
|
+
/** How one block of text paints itself inside the bar that carries it. */
|
|
35
|
+
export interface FrameFaces {
|
|
36
|
+
/** Paints one row of the block's own text. */
|
|
37
|
+
readonly text: (line: string) => string;
|
|
38
|
+
/** Paints the frame's rules and its two sides. */
|
|
39
|
+
readonly border: (rule: string) => string;
|
|
40
|
+
/** Whether the frame may be drawn at all. */
|
|
41
|
+
readonly framed: boolean;
|
|
42
|
+
}
|
|
43
|
+
/** Whether a bar has the width, and the visible border, to close a frame. */
|
|
44
|
+
export declare function canFrame(width: number, borderVisible: boolean): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* One block of text as a bar draws it: wrapped to the frame's own width, and
|
|
47
|
+
* closed into it when the frame can be drawn.
|
|
48
|
+
*
|
|
49
|
+
* A block with a limit is a preview, and the rows it drops are counted on the
|
|
50
|
+
* closing rule: a reader who cannot see the whole text still has to see that it
|
|
51
|
+
* continues. A block without a limit is the prompt itself, which is never cut.
|
|
52
|
+
*/
|
|
53
|
+
export declare function frameText(text: string, width: number, faces: FrameFaces, limit?: number): string[];
|
|
54
|
+
//# sourceMappingURL=frame.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame.d.ts","sourceRoot":"","sources":["../../src/ui/frame.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY;;;;;;CAAyF,CAAA;AAElH,kFAAkF;AAClF,eAAO,MAAM,aAAa,IAAI,CAAA;AAC9B,mFAAmF;AACnF,eAAO,MAAM,SAAS,IAAI,CAAA;AAC1B,6EAA6E;AAC7E,eAAO,MAAM,aAAa,QAAoC,CAAA;AAE9D,0FAA0F;AAC1F,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,kFAAkF;AAClF,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAGhE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAgBxE;AAED,0EAA0E;AAC1E,MAAM,WAAW,UAAU;IACzB,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACvC,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACzC,6CAA6C;IAC7C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CACzB;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,GAAG,OAAO,CAEvE;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,SAA2B,GAAG,MAAM,EAAE,CAYpH"}
|
package/lib/ui/frame.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { visibleWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
|
|
2
|
+
import { displayText } from "../text.js";
|
|
3
|
+
/**
|
|
4
|
+
* The frame every prompt is closed into.
|
|
5
|
+
*
|
|
6
|
+
* The editor, the prompts queued above it, and the prompts already submitted
|
|
7
|
+
* draw the same glyphs at the same width, so one prompt reads as the same object
|
|
8
|
+
* through its whole life; keeping the shape here is what stops the three from
|
|
9
|
+
* drifting.
|
|
10
|
+
*/
|
|
11
|
+
export const FRAME_GLYPHS = { topLeft: '╭', topRight: '╮', bottomLeft: '╰', bottomRight: '╯', side: '│' };
|
|
12
|
+
/** The frame costs one column each side, so the text wraps that much narrower. */
|
|
13
|
+
export const FRAME_COLUMNS = 2;
|
|
14
|
+
/** One column of air inside the frame, so a full line never touches the border. */
|
|
15
|
+
export const PADDING_X = 1;
|
|
16
|
+
/** A box needs both edges, both paddings, and one column left to type in. */
|
|
17
|
+
export const MIN_BOX_WIDTH = FRAME_COLUMNS + PADDING_X * 2 + 1;
|
|
18
|
+
/** The columns the text of a bar owns, given the width of everything inside its frame. */
|
|
19
|
+
export function textWidth(innerWidth) {
|
|
20
|
+
return Math.max(1, innerWidth - PADDING_X * 2);
|
|
21
|
+
}
|
|
22
|
+
/** One text row of a bar: the air, the text, and the rest of the inside width. */
|
|
23
|
+
export function textRow(text, innerWidth) {
|
|
24
|
+
const room = Math.max(0, textWidth(innerWidth) - visibleWidth(text));
|
|
25
|
+
return `${' '.repeat(PADDING_X)}${text}${' '.repeat(room)}${' '.repeat(PADDING_X)}`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The rule that closes a bar, naming what the bar could not fit.
|
|
29
|
+
*
|
|
30
|
+
* A hidden count is a fact about the text, not extra text, so it rides the rule
|
|
31
|
+
* the way the editor's own scroll indicator does: the same label in the same
|
|
32
|
+
* place, at the same width, so both bars report a fold identically.
|
|
33
|
+
*/
|
|
34
|
+
export function frameRule(innerWidth, hiddenRows) {
|
|
35
|
+
const width = Math.max(0, innerWidth);
|
|
36
|
+
if (hiddenRows <= 0)
|
|
37
|
+
return '─'.repeat(width);
|
|
38
|
+
const label = ` ↓ ${hiddenRows} more `;
|
|
39
|
+
const labelWidth = visibleWidth(label);
|
|
40
|
+
if (labelWidth + 2 <= width) {
|
|
41
|
+
const left = Math.floor((width - labelWidth) / 2);
|
|
42
|
+
return `${'─'.repeat(left)}${label}${'─'.repeat(width - left - labelWidth)}`;
|
|
43
|
+
}
|
|
44
|
+
// Too narrow for a centred label: name it as far as it fits instead of
|
|
45
|
+
// dropping the fact. Both the label and the fallback are ASCII, so slicing by
|
|
46
|
+
// character is slicing by column here.
|
|
47
|
+
const indicator = `─── ↓ ${hiddenRows} more `;
|
|
48
|
+
if (visibleWidth(indicator) <= width)
|
|
49
|
+
return indicator + '─'.repeat(width - visibleWidth(indicator));
|
|
50
|
+
const ellipsis = '...'.slice(0, width);
|
|
51
|
+
return indicator.slice(0, Math.max(0, width - ellipsis.length)) + ellipsis;
|
|
52
|
+
}
|
|
53
|
+
/** Whether a bar has the width, and the visible border, to close a frame. */
|
|
54
|
+
export function canFrame(width, borderVisible) {
|
|
55
|
+
return borderVisible && width >= MIN_BOX_WIDTH;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* One block of text as a bar draws it: wrapped to the frame's own width, and
|
|
59
|
+
* closed into it when the frame can be drawn.
|
|
60
|
+
*
|
|
61
|
+
* A block with a limit is a preview, and the rows it drops are counted on the
|
|
62
|
+
* closing rule: a reader who cannot see the whole text still has to see that it
|
|
63
|
+
* continues. A block without a limit is the prompt itself, which is never cut.
|
|
64
|
+
*/
|
|
65
|
+
export function frameText(text, width, faces, limit = Number.POSITIVE_INFINITY) {
|
|
66
|
+
const inside = faces.framed ? width - FRAME_COLUMNS : width;
|
|
67
|
+
const wrapped = wrapTextWithAnsi(displayText(text), textWidth(inside));
|
|
68
|
+
const body = wrapped.slice(0, Math.max(0, limit));
|
|
69
|
+
const rows = body.map(line => textRow(faces.text(line), inside));
|
|
70
|
+
if (!faces.framed)
|
|
71
|
+
return rows;
|
|
72
|
+
const side = faces.border(FRAME_GLYPHS.side);
|
|
73
|
+
return [
|
|
74
|
+
faces.border(`${FRAME_GLYPHS.topLeft}${frameRule(inside, 0)}${FRAME_GLYPHS.topRight}`),
|
|
75
|
+
...rows.map(row => `${side}${row}${side}`),
|
|
76
|
+
faces.border(`${FRAME_GLYPHS.bottomLeft}${frameRule(inside, wrapped.length - body.length)}${FRAME_GLYPHS.bottomRight}`),
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=frame.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame.js","sourceRoot":"","sources":["../../src/ui/frame.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { BoxedEditor } from './editor.ts';
|
|
2
|
+
/** How a question's answer is shown while it is written. */
|
|
3
|
+
export type GateInputMode = 'answer' | 'secret';
|
|
4
|
+
/**
|
|
5
|
+
* The input bar a gate collects an answer with.
|
|
6
|
+
*
|
|
7
|
+
* It is the prompt bar's own component, so an answer is written with the
|
|
8
|
+
* movement, deletion, undo, and paste that bar already has. Its mode decides
|
|
9
|
+
* only how the answer is shown: a question that declares a credential is
|
|
10
|
+
* answered in the same bar with the middle of the value hidden, never in a
|
|
11
|
+
* second widget that would have to grow its own editing.
|
|
12
|
+
*/
|
|
13
|
+
export declare class GateInputBar extends BoxedEditor {
|
|
14
|
+
private mode;
|
|
15
|
+
/** Switch what the bar shows without changing a character of what it holds. */
|
|
16
|
+
setMode(mode: GateInputMode): void;
|
|
17
|
+
/** Hide a credential's middle while its owner still recognizes both ends. */
|
|
18
|
+
protected decorateText(row: string): string;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=gate-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate-input.d.ts","sourceRoot":"","sources":["../../src/ui/gate-input.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEzC,4DAA4D;AAC5D,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,CAAA;AA2E/C;;;;;;;;GAQG;AACH,qBAAa,YAAa,SAAQ,WAAW;IAC3C,OAAO,CAAC,IAAI,CAA0B;IAEtC,+EAA+E;IAC/E,OAAO,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IAMlC,6EAA6E;cAC1D,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;CAGrD"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { visibleWidth } from '@earendil-works/pi-tui';
|
|
2
|
+
import { BoxedEditor } from "./editor.js";
|
|
3
|
+
/** One character of the mask, repeated to the width of what it hides. */
|
|
4
|
+
const MASKED = '*';
|
|
5
|
+
/** How much of a credential stays readable at each end, so its owner can recognize it. */
|
|
6
|
+
const MASK_HEAD = 4;
|
|
7
|
+
const MASK_TAIL = 4;
|
|
8
|
+
/**
|
|
9
|
+
* A terminal sequence, so masking can tell a row's styling from its text: the
|
|
10
|
+
* cursor is drawn as an escape around a character, and hiding the character
|
|
11
|
+
* must not hide the mark that says where the cursor is.
|
|
12
|
+
*
|
|
13
|
+
* The string forms carry payloads a terminal reads rather than shows, and a
|
|
14
|
+
* cursor marker is one of them: masking a marker's own text would replace the
|
|
15
|
+
* position the terminal was told to place the cursor at with asterisks.
|
|
16
|
+
*/
|
|
17
|
+
const SEQUENCE = /\u001b(?:\[[0-9;?]*[ -/]*[@-~]|[\][P_^X][^\u0007\u001b]*(?:\u0007|\u001b\\)|[@-Z\\-_])/gu;
|
|
18
|
+
const GRAPHEMES = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
19
|
+
/**
|
|
20
|
+
* One visible grapheme, and where it sits among the row's visible characters.
|
|
21
|
+
*
|
|
22
|
+
* Whitespace is not among them: the editor lays the answer out with spaces that
|
|
23
|
+
* belong to its own drawing, and hiding them would move the text under the
|
|
24
|
+
* cursor rather than hide it.
|
|
25
|
+
*/
|
|
26
|
+
function visibleAt(text) {
|
|
27
|
+
const found = [];
|
|
28
|
+
let position = 0;
|
|
29
|
+
for (const { segment } of GRAPHEMES.segment(text)) {
|
|
30
|
+
if (segment.trim() === '') {
|
|
31
|
+
found.push({ segment, position: -1 });
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
found.push({ segment, position });
|
|
35
|
+
position += 1;
|
|
36
|
+
}
|
|
37
|
+
return found;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Hide the middle of a credential and keep its ends readable.
|
|
41
|
+
*
|
|
42
|
+
* The reader is checking which key they pasted, and a row of asterisks answers
|
|
43
|
+
* nothing; a bystander is not meant to read it either, so only the first and
|
|
44
|
+
* last few characters survive. Rows are masked one at a time because that is
|
|
45
|
+
* how the editor hands them over, and a credential is written on one line.
|
|
46
|
+
*/
|
|
47
|
+
function maskCredentialRow(row) {
|
|
48
|
+
const segments = [];
|
|
49
|
+
let last = 0;
|
|
50
|
+
for (const match of row.matchAll(SEQUENCE)) {
|
|
51
|
+
segments.push({ escape: false, text: row.slice(last, match.index) });
|
|
52
|
+
segments.push({ escape: true, text: match[0] });
|
|
53
|
+
last = match.index + match[0].length;
|
|
54
|
+
}
|
|
55
|
+
segments.push({ escape: false, text: row.slice(last) });
|
|
56
|
+
const visible = segments.filter(segment => !segment.escape).flatMap(segment => visibleAt(segment.text));
|
|
57
|
+
const total = visible.reduce((count, entry) => count + (entry.position >= 0 ? 1 : 0), 0);
|
|
58
|
+
if (total <= MASK_HEAD + MASK_TAIL)
|
|
59
|
+
return row;
|
|
60
|
+
let seen = 0;
|
|
61
|
+
return segments.map((segment) => {
|
|
62
|
+
if (segment.escape)
|
|
63
|
+
return segment.text;
|
|
64
|
+
return visibleAt(segment.text).map((entry) => {
|
|
65
|
+
if (entry.position < 0)
|
|
66
|
+
return entry.segment;
|
|
67
|
+
const readable = seen < MASK_HEAD || seen >= total - MASK_TAIL;
|
|
68
|
+
seen += 1;
|
|
69
|
+
return readable ? entry.segment : MASKED.repeat(Math.max(1, visibleWidth(entry.segment)));
|
|
70
|
+
}).join('');
|
|
71
|
+
}).join('');
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The input bar a gate collects an answer with.
|
|
75
|
+
*
|
|
76
|
+
* It is the prompt bar's own component, so an answer is written with the
|
|
77
|
+
* movement, deletion, undo, and paste that bar already has. Its mode decides
|
|
78
|
+
* only how the answer is shown: a question that declares a credential is
|
|
79
|
+
* answered in the same bar with the middle of the value hidden, never in a
|
|
80
|
+
* second widget that would have to grow its own editing.
|
|
81
|
+
*/
|
|
82
|
+
export class GateInputBar extends BoxedEditor {
|
|
83
|
+
mode = 'answer';
|
|
84
|
+
/** Switch what the bar shows without changing a character of what it holds. */
|
|
85
|
+
setMode(mode) {
|
|
86
|
+
if (this.mode === mode)
|
|
87
|
+
return;
|
|
88
|
+
this.mode = mode;
|
|
89
|
+
this.invalidate();
|
|
90
|
+
}
|
|
91
|
+
/** Hide a credential's middle while its owner still recognizes both ends. */
|
|
92
|
+
decorateText(row) {
|
|
93
|
+
return this.mode === 'secret' ? maskCredentialRow(row) : row;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=gate-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate-input.js","sourceRoot":"","sources":["../../src/ui/gate-input.ts"],"names":[],"mappings":"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"}
|
package/lib/ui/markdown.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type MarkdownTheme } from '@earendil-works/pi-tui';
|
|
2
|
+
import type { MermaidTransform } from './mermaid.ts';
|
|
2
3
|
/** Parsed messages kept for redraws; everything above this is cold transcript. */
|
|
3
4
|
export declare const MARKDOWN_CACHE_LIMIT = 64;
|
|
4
5
|
/**
|
|
@@ -10,8 +11,24 @@ export declare const MARKDOWN_CACHE_LIMIT = 64;
|
|
|
10
11
|
*/
|
|
11
12
|
export declare class MarkdownRenderer {
|
|
12
13
|
private readonly theme;
|
|
14
|
+
private readonly mermaid?;
|
|
13
15
|
private readonly parsed;
|
|
14
|
-
constructor(theme: MarkdownTheme);
|
|
15
|
-
render(text: string, width: number): string[];
|
|
16
|
+
constructor(theme: MarkdownTheme, mermaid?: MermaidTransform | undefined);
|
|
17
|
+
render(text: string, width: number, live?: boolean): string[];
|
|
18
|
+
/**
|
|
19
|
+
* The parse options this renderer installs, absent when nothing transforms.
|
|
20
|
+
*
|
|
21
|
+
* The flag is captured rather than read per call: the component caches the
|
|
22
|
+
* rows it drew, so the value that built it is the only one it could honour.
|
|
23
|
+
*/
|
|
24
|
+
private options;
|
|
25
|
+
/**
|
|
26
|
+
* Drop parsed messages after a theme change.
|
|
27
|
+
*
|
|
28
|
+
* A `Markdown` caches the lines it rendered for a width, so keeping the
|
|
29
|
+
* instance would keep the old escapes; the reader's new shade would land
|
|
30
|
+
* everywhere except the answer.
|
|
31
|
+
*/
|
|
32
|
+
invalidate(): void;
|
|
16
33
|
}
|
|
17
34
|
//# sourceMappingURL=markdown.d.ts.map
|
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,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"}
|
package/lib/ui/markdown.js
CHANGED
|
@@ -10,26 +10,57 @@ export const MARKDOWN_CACHE_LIMIT = 64;
|
|
|
10
10
|
*/
|
|
11
11
|
export class MarkdownRenderer {
|
|
12
12
|
theme;
|
|
13
|
+
mermaid;
|
|
13
14
|
parsed = new Map();
|
|
14
|
-
constructor(theme) {
|
|
15
|
+
constructor(theme, mermaid) {
|
|
15
16
|
this.theme = theme;
|
|
17
|
+
this.mermaid = mermaid;
|
|
16
18
|
}
|
|
17
|
-
render(text, width) {
|
|
18
|
-
|
|
19
|
+
render(text, width, live = false) {
|
|
20
|
+
// A streaming reply can settle on the very text it last streamed, and the
|
|
21
|
+
// 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}`;
|
|
25
|
+
let message = this.parsed.get(key);
|
|
19
26
|
if (message === undefined) {
|
|
20
27
|
if (this.parsed.size >= MARKDOWN_CACHE_LIMIT) {
|
|
21
28
|
const oldest = this.parsed.keys().next();
|
|
22
29
|
if (oldest.done !== true)
|
|
23
30
|
this.parsed.delete(oldest.value);
|
|
24
31
|
}
|
|
25
|
-
message = new Markdown(text, 0, 0, this.theme);
|
|
32
|
+
message = new Markdown(text, 0, 0, this.theme, undefined, this.options(live));
|
|
26
33
|
}
|
|
27
34
|
else {
|
|
28
35
|
// Reinserting keeps a message that is still on screen from ageing out.
|
|
29
|
-
this.parsed.delete(
|
|
36
|
+
this.parsed.delete(key);
|
|
30
37
|
}
|
|
31
|
-
this.parsed.set(
|
|
38
|
+
this.parsed.set(key, message);
|
|
32
39
|
return message.render(Math.max(1, Math.floor(width)));
|
|
33
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* The parse options this renderer installs, absent when nothing transforms.
|
|
43
|
+
*
|
|
44
|
+
* The flag is captured rather than read per call: the component caches the
|
|
45
|
+
* rows it drew, so the value that built it is the only one it could honour.
|
|
46
|
+
*/
|
|
47
|
+
options(live) {
|
|
48
|
+
const mermaid = this.mermaid;
|
|
49
|
+
if (mermaid === undefined)
|
|
50
|
+
return undefined;
|
|
51
|
+
return { transform: (markdown, availableWidth) => mermaid(markdown, availableWidth, live) };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Drop parsed messages after a theme change.
|
|
55
|
+
*
|
|
56
|
+
* A `Markdown` caches the lines it rendered for a width, so keeping the
|
|
57
|
+
* instance would keep the old escapes; the reader's new shade would land
|
|
58
|
+
* everywhere except the answer.
|
|
59
|
+
*/
|
|
60
|
+
invalidate() {
|
|
61
|
+
for (const message of this.parsed.values())
|
|
62
|
+
message.invalidate();
|
|
63
|
+
this.parsed.clear();
|
|
64
|
+
}
|
|
34
65
|
}
|
|
35
66
|
//# sourceMappingURL=markdown.js.map
|
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,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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { MermaidMode } from '../theme-settings.ts';
|
|
2
|
+
import type { TuiTheme } from '../theme.ts';
|
|
3
|
+
/** Drawings kept before the oldest is laid out again; a streaming reply redraws per delta. */
|
|
4
|
+
export declare const MERMAID_CACHE_LIMIT = 32;
|
|
5
|
+
/**
|
|
6
|
+
* The largest source this surface lays out.
|
|
7
|
+
*
|
|
8
|
+
* Layout is synchronous and happens inside a frame, and a streaming reply asks
|
|
9
|
+
* for it again on every delta, so past this the frame costs more than the
|
|
10
|
+
* drawing is worth and the source stays. Well above what the renderer's own
|
|
11
|
+
* node cap can fill, so no drawing a reader actually sees is kept out.
|
|
12
|
+
*/
|
|
13
|
+
export declare const MERMAID_MAX_SOURCE: number;
|
|
14
|
+
export interface MermaidOptions {
|
|
15
|
+
readonly theme: TuiTheme;
|
|
16
|
+
/** Read per render, so a settings edit reaches a session that is already on screen. */
|
|
17
|
+
readonly mode: () => MermaidMode;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Rewrite one message's markdown, with `live` telling a streaming reply from a settled one.
|
|
21
|
+
*
|
|
22
|
+
* The width is the columns the message actually has, and a drawing that needs
|
|
23
|
+
* more than that is left as source rather than truncated: a mutilated diagram
|
|
24
|
+
* is worse than the text a reader can still read.
|
|
25
|
+
*/
|
|
26
|
+
export type MermaidTransform = (markdown: string, availableWidth: number, live: boolean) => string;
|
|
27
|
+
/**
|
|
28
|
+
* Draw a reply's mermaid fences as terminal art.
|
|
29
|
+
*
|
|
30
|
+
* Replacements are spliced into the message at the offsets the lexer reported,
|
|
31
|
+
* never rebuilt from the tokens: lexing can decide something is not worth a
|
|
32
|
+
* token — a duplicate reference definition, for one — and a message that merely
|
|
33
|
+
* mentions mermaid would then change under a reader who asked for nothing. Text
|
|
34
|
+
* the transform does not draw is copied through byte for byte.
|
|
35
|
+
*
|
|
36
|
+
* A fence is replaced before the markdown is parsed, because that is the only
|
|
37
|
+
* moment the source is still whole: once tokens are flowing the drawing's rows
|
|
38
|
+
* would be re-wrapped as prose and lose the spacing they are made of. Each row
|
|
39
|
+
* is therefore emitted as an inline code span, which the markdown renderer
|
|
40
|
+
* keeps verbatim, and rows are separated by hard breaks so a diagram survives
|
|
41
|
+
* as one block instead of one paragraph.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createMermaidTransform(options: MermaidOptions): MermaidTransform;
|
|
44
|
+
//# sourceMappingURL=mermaid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mermaid.d.ts","sourceRoot":"","sources":["../../src/ui/mermaid.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAEvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAQ3C,8FAA8F;AAC9F,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,QAAY,CAAA;AA8B3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAA;IACxB,uFAAuF;IACvF,QAAQ,CAAC,IAAI,EAAE,MAAM,WAAW,CAAA;CACjC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,MAAM,CAAA;AAgElG;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,cAAc,GAAG,gBAAgB,CAkEhF"}
|