@linxiraos/pi-tui 1.2.1 → 1.3.1
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/CHANGELOG.md +245 -224
- package/README.md +36 -0
- package/THIRD-PARTY-NOTICES.txt +55 -5
- package/dist/types/app-keybindings.d.ts +18 -8
- package/dist/types/apps/git/diff-pane.d.ts +5 -0
- package/dist/types/apps/git/git-tui.d.ts +16 -2
- package/dist/types/apps/git/state.d.ts +11 -0
- package/dist/types/autocomplete.d.ts +2 -0
- package/dist/types/charts/chart-svg.d.ts +7 -13
- package/dist/types/chat/advisor-message.d.ts +4 -1
- package/dist/types/chat/assistant-message.d.ts +9 -4
- package/dist/types/chat/figure-markdown.d.ts +7 -2
- package/dist/types/chat/messages.d.ts +3 -1
- package/dist/types/chat/read-tool-group.d.ts +2 -2
- package/dist/types/chat/table-chart.d.ts +11 -15
- package/dist/types/chat/tool-execution.d.ts +32 -20
- package/dist/types/chat/user-message.d.ts +3 -2
- package/dist/types/chrome/countdown-timer.d.ts +3 -2
- package/dist/types/chrome/diff.d.ts +2 -3
- package/dist/types/chrome/status-notice.d.ts +2 -0
- package/dist/types/chrome/transcript-container.d.ts +2 -0
- package/dist/types/components/image.d.ts +2 -0
- package/dist/types/components/markdown.d.ts +49 -3
- package/dist/types/components/text.d.ts +2 -0
- package/dist/types/debug-server.d.ts +2 -0
- package/dist/types/keybinding-matchers.d.ts +6 -0
- package/dist/types/multiplexers/tmux.d.ts +1 -0
- package/dist/types/multiplexers/types.d.ts +6 -0
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +24 -12
- package/dist/types/overlays/agent-hub-projection.d.ts +10 -5
- package/dist/types/overlays/annotation-overlay.d.ts +19 -0
- package/dist/types/overlays/approval-dialog.d.ts +79 -0
- package/dist/types/overlays/ask-dialog.d.ts +11 -6
- package/dist/types/overlays/btw-history-panel.d.ts +2 -6
- package/dist/types/overlays/hook-editor.d.ts +7 -2
- package/dist/types/overlays/hook-input.d.ts +12 -4
- package/dist/types/overlays/job-tree.d.ts +50 -0
- package/dist/types/overlays/jobs-panel.d.ts +25 -11
- package/dist/types/overlays/login-dialog.d.ts +8 -3
- package/dist/types/overlays/mcp-add-wizard.d.ts +9 -1
- package/dist/types/overlays/model-browser.d.ts +6 -1
- package/dist/types/overlays/model-picker.d.ts +2 -0
- package/dist/types/overlays/pause-screen.d.ts +5 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +4 -0
- package/dist/types/overlays/report-panel.d.ts +5 -1
- package/dist/types/overlays/reset-usage-selector.d.ts +7 -5
- package/dist/types/overlays/session-account-selector.d.ts +4 -0
- package/dist/types/overlays/session-info-overlay.d.ts +7 -3
- package/dist/types/overlays/session-selector.d.ts +3 -1
- package/dist/types/overlays/usage-dashboard.d.ts +6 -6
- package/dist/types/prompt/composer.d.ts +23 -20
- package/dist/types/prompt/custom-editor.d.ts +20 -10
- package/dist/types/prompt/image-references.d.ts +22 -0
- package/dist/types/prompt/internal-url-autocomplete.d.ts +1 -0
- package/dist/types/prompt/macos-spelling.d.ts +2 -1
- package/dist/types/prompt/prose-gate.d.ts +3 -0
- package/dist/types/prompt/queued-messages.d.ts +1 -1
- package/dist/types/prompt/typing-word.d.ts +25 -0
- package/dist/types/prompt/welcome.d.ts +26 -6
- package/dist/types/render/hanging-wrap.d.ts +7 -0
- package/dist/types/render/hyperlink.d.ts +2 -0
- package/dist/types/render/render-utils.d.ts +9 -1
- package/dist/types/render/utils.d.ts +1 -1
- package/dist/types/setup/scenes/splash.d.ts +6 -5
- package/dist/types/status-line/context-usage.d.ts +4 -1
- package/dist/types/status-line/host.d.ts +6 -4
- package/dist/types/status-line/startup.d.ts +6 -6
- package/dist/types/status-line/types.d.ts +3 -0
- package/dist/types/stdin-buffer.d.ts +7 -0
- package/dist/types/terminal-capabilities.d.ts +16 -1
- package/dist/types/terminal-multiplexer.d.ts +7 -0
- package/dist/types/terminal.d.ts +8 -4
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme.d.ts +11 -0
- package/dist/types/thinking.d.ts +5 -0
- package/dist/types/tmux.d.ts +39 -4
- package/dist/types/tools/ast-edit.d.ts +2 -2
- package/dist/types/tools/ast-grep.d.ts +2 -2
- package/dist/types/tools/bash.d.ts +6 -1
- package/dist/types/tools/edit.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +53 -4
- package/dist/types/tools/find.d.ts +14 -4
- package/dist/types/tools/glob.d.ts +3 -3
- package/dist/types/tools/grep.d.ts +2 -2
- package/dist/types/tools/grouped-file-output.d.ts +9 -4
- package/dist/types/tools/native-view.d.ts +1 -1
- package/dist/types/tools/read.d.ts +32 -10
- package/dist/types/tools/renderer.d.ts +2 -0
- package/dist/types/tools/task.d.ts +31 -0
- package/dist/types/tools/web-search-types.d.ts +1 -0
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui.d.ts +20 -3
- package/package.json +10 -10
- package/src/app-keybindings.d.ts +18 -8
- package/src/app-keybindings.ts +20 -8
- package/src/apps/autoresearch-dashboard.ts +444 -27
- package/src/apps/debug/raw-sse.ts +2 -2
- package/src/apps/git/diff-pane.ts +39 -41
- package/src/apps/git/git-tui.ts +80 -14
- package/src/apps/git/sidebar.ts +10 -1
- package/src/apps/git/state.ts +12 -0
- package/src/autocomplete.d.ts +2 -0
- package/src/autocomplete.ts +21 -19
- package/src/bracketed-paste.ts +9 -2
- package/src/charts/chart-svg.d.ts +7 -13
- package/src/charts/chart-svg.ts +19 -22
- package/src/charts/table-data.ts +17 -6
- package/src/chat/advisor-message.d.ts +4 -1
- package/src/chat/advisor-message.ts +77 -24
- package/src/chat/assistant-message.d.ts +9 -4
- package/src/chat/assistant-message.ts +127 -31
- package/src/chat/bash-execution.ts +7 -2
- package/src/chat/figure-markdown.d.ts +7 -2
- package/src/chat/figure-markdown.ts +10 -3
- package/src/chat/image-loading.ts +9 -6
- package/src/chat/messages.d.ts +3 -1
- package/src/chat/messages.ts +4 -1
- package/src/chat/read-tool-group.d.ts +2 -2
- package/src/chat/read-tool-group.ts +97 -42
- package/src/chat/svg-figure.ts +3 -1
- package/src/chat/svg-source.ts +6 -3
- package/src/chat/table-chart.d.ts +11 -15
- package/src/chat/table-chart.ts +24 -57
- package/src/chat/tool-execution.d.ts +32 -20
- package/src/chat/tool-execution.ts +103 -36
- package/src/chat/user-message.d.ts +3 -2
- package/src/chat/user-message.ts +54 -34
- package/src/chrome/countdown-timer.ts +3 -3
- package/src/chrome/diff.d.ts +2 -3
- package/src/chrome/diff.ts +4 -9
- package/src/chrome/status-notice.ts +5 -0
- package/src/chrome/transcript-container.d.ts +2 -0
- package/src/chrome/transcript-container.ts +212 -28
- package/src/components/editor.ts +12 -12
- package/src/components/image.d.ts +2 -0
- package/src/components/image.ts +54 -20
- package/src/components/markdown.d.ts +49 -3
- package/src/components/markdown.ts +482 -12
- package/src/components/settings-list.ts +2 -4
- package/src/components/text.d.ts +2 -0
- package/src/components/text.ts +11 -1
- package/src/debug-server.d.ts +2 -0
- package/src/debug-server.ts +51 -69
- package/src/hotkeys-markdown.ts +2 -0
- package/src/keybinding-matchers.d.ts +6 -0
- package/src/keybinding-matchers.ts +19 -0
- package/src/multiplexers/tmux.d.ts +1 -0
- package/src/multiplexers/tmux.ts +3 -0
- package/src/multiplexers/types.d.ts +6 -0
- package/src/multiplexers/types.ts +6 -0
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +13 -5
- package/src/native/blobs.d.ts +24 -12
- package/src/native/blobs.ts +56 -7
- package/src/native/reconcile.ts +43 -21
- package/src/overlays/advisor-config.ts +259 -8
- package/src/overlays/agent-hub-projection.d.ts +10 -5
- package/src/overlays/agent-hub-projection.ts +63 -12
- package/src/overlays/annotation-overlay.ts +422 -168
- package/src/overlays/approval-dialog.ts +389 -0
- package/src/overlays/ask-dialog.ts +244 -105
- package/src/overlays/btw-history-panel.ts +208 -147
- package/src/overlays/extensions/extension-list.ts +4 -6
- package/src/overlays/hook-editor.ts +90 -32
- package/src/overlays/hook-input.ts +105 -19
- package/src/overlays/job-tree.ts +177 -0
- package/src/overlays/jobs-panel.ts +222 -142
- package/src/overlays/login-dialog.ts +201 -91
- package/src/overlays/mcp-add-wizard.ts +562 -279
- package/src/overlays/model-browser.ts +32 -7
- package/src/overlays/model-hub.ts +4 -10
- package/src/overlays/model-picker.ts +58 -21
- package/src/overlays/pause-screen.ts +81 -23
- package/src/overlays/plan-review-overlay.ts +16 -0
- package/src/overlays/plugin-selector.ts +17 -1
- package/src/overlays/queue-mode-selector.ts +10 -0
- package/src/overlays/report-panel.ts +6 -2
- package/src/overlays/reset-usage-selector.ts +130 -21
- package/src/overlays/session-account-selector.ts +36 -0
- package/src/overlays/session-info-overlay.ts +199 -76
- package/src/overlays/session-selector.ts +15 -10
- package/src/overlays/settings-selector.ts +3 -0
- package/src/overlays/show-images-selector.ts +1 -0
- package/src/overlays/snapcompact-shape-preview.ts +13 -6
- package/src/overlays/theme-selector.ts +6 -1
- package/src/overlays/thinking-selector.ts +8 -4
- package/src/overlays/tree-selector.ts +117 -2
- package/src/overlays/usage-dashboard.ts +498 -144
- package/src/prompt/composer-cache.ts +4 -1
- package/src/prompt/composer.ts +200 -74
- package/src/prompt/custom-editor.ts +168 -65
- package/src/prompt/image-references.d.ts +22 -0
- package/src/prompt/image-references.ts +79 -2
- package/src/prompt/internal-url-autocomplete.ts +1 -0
- package/src/prompt/macos-spelling.ts +28 -5
- package/src/prompt/prose-gate.ts +3 -0
- package/src/prompt/queued-messages.ts +9 -2
- package/src/prompt/tips.txt +1 -2
- package/src/prompt/typing-word.ts +59 -0
- package/src/prompt/welcome.ts +97 -45
- package/src/render/code-cell.ts +18 -5
- package/src/render/hanging-wrap.d.ts +7 -0
- package/src/render/hanging-wrap.ts +58 -0
- package/src/render/hyperlink.d.ts +2 -0
- package/src/render/hyperlink.ts +5 -0
- package/src/render/render-utils.d.ts +9 -1
- package/src/render/render-utils.ts +16 -2
- package/src/render/utils.d.ts +1 -1
- package/src/render/utils.ts +1 -1
- package/src/render/width-aware-text.ts +6 -5
- package/src/setup/scenes/splash.ts +22 -8
- package/src/status-line/component.ts +79 -11
- package/src/status-line/context-usage.ts +176 -73
- package/src/status-line/footer.ts +18 -35
- package/src/status-line/host.ts +6 -1
- package/src/status-line/segments.ts +16 -5
- package/src/status-line/startup.ts +6 -6
- package/src/status-line/types.ts +1 -0
- package/src/stdin-buffer.d.ts +7 -0
- package/src/stdin-buffer.ts +300 -62
- package/src/terminal-capabilities.d.ts +16 -1
- package/src/terminal-capabilities.ts +66 -28
- package/src/terminal-multiplexer.d.ts +7 -0
- package/src/terminal-multiplexer.ts +14 -0
- package/src/terminal.d.ts +8 -4
- package/src/terminal.ts +190 -63
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +1 -0
- package/src/theme/theme-class.ts +1 -0
- package/src/theme/theme.d.ts +11 -0
- package/src/theme/theme.ts +46 -14
- package/src/thinking.d.ts +5 -0
- package/src/thinking.ts +9 -1
- package/src/tmux.d.ts +39 -4
- package/src/tmux.ts +110 -16
- package/src/tools/ask.ts +20 -9
- package/src/tools/ast-edit.d.ts +2 -2
- package/src/tools/ast-edit.ts +30 -7
- package/src/tools/ast-grep.d.ts +2 -2
- package/src/tools/ast-grep.ts +39 -35
- package/src/tools/bash.d.ts +6 -1
- package/src/tools/bash.ts +15 -9
- package/src/tools/debug.ts +2 -1
- package/src/tools/default-renderer.ts +3 -2
- package/src/tools/edit.d.ts +2 -0
- package/src/tools/edit.ts +100 -21
- package/src/tools/eval.d.ts +53 -4
- package/src/tools/eval.ts +173 -137
- package/src/tools/find.d.ts +14 -4
- package/src/tools/find.ts +71 -18
- package/src/tools/glob.d.ts +3 -3
- package/src/tools/glob.ts +51 -17
- package/src/tools/grep.d.ts +2 -2
- package/src/tools/grep.ts +99 -22
- package/src/tools/grouped-file-output.d.ts +9 -4
- package/src/tools/grouped-file-output.ts +21 -6
- package/src/tools/lsp.ts +30 -18
- package/src/tools/mcp.ts +2 -2
- package/src/tools/native-view.d.ts +1 -1
- package/src/tools/native-view.ts +2 -2
- package/src/tools/read.d.ts +32 -10
- package/src/tools/read.ts +89 -43
- package/src/tools/renderer.d.ts +2 -0
- package/src/tools/renderer.ts +2 -0
- package/src/tools/task.d.ts +31 -0
- package/src/tools/task.ts +40 -7
- package/src/tools/todo.ts +3 -2
- package/src/tools/wait.ts +16 -30
- package/src/tools/web-search-types.d.ts +1 -0
- package/src/tools/web-search-types.ts +1 -0
- package/src/tools/web-search.ts +2 -1
- package/src/tools/write.d.ts +2 -0
- package/src/tools/write.ts +36 -12
- package/src/tools/yield.ts +2 -2
- package/src/tui.d.ts +20 -3
- package/src/tui.ts +160 -43
- package/src/utils.ts +5 -3
- package/dist/types/tools/eval-format/code-mode-declarations.d.ts +0 -5
- package/src/tools/eval-format/code-mode-declarations.ts +0 -61
package/src/stdin-buffer.ts
CHANGED
|
@@ -58,7 +58,7 @@ const PARTIAL_HOLD_MAX_MS = 150;
|
|
|
58
58
|
// so the string-terminator cap is generous.
|
|
59
59
|
const MAX_CSI_BYTES = 4096;
|
|
60
60
|
const MAX_STRING_SEQ_BYTES = 16 * 1024 * 1024;
|
|
61
|
-
// Torn-string discard bounds. When a capped
|
|
61
|
+
// Torn-string discard bounds. When a capped OSC/DCS/APC
|
|
62
62
|
// partial is dropped, the bytes that follow are mid-string payload (base64
|
|
63
63
|
// clipboard data, Sixel, …) up to the next ST/BEL. Replaying them as
|
|
64
64
|
// keystrokes types kilobytes of garbage into the focused component (kitty
|
|
@@ -69,6 +69,22 @@ const STRING_DISCARD_MAX_BYTES = 2 * MAX_STRING_SEQ_BYTES;
|
|
|
69
69
|
const STRING_DISCARD_INACTIVITY_MS = 1000;
|
|
70
70
|
// Partial that can only be the head of an OSC/DCS/APC string sequence.
|
|
71
71
|
const STRING_SEQ_PARTIAL = /^\x1b[\]P_]/;
|
|
72
|
+
// A pending string head is abandoned after this long without terminal-shaped
|
|
73
|
+
// progress. Generous: a slow clipboard owner or SSH link may stall a packet
|
|
74
|
+
// for a while, and abandoning a live packet types nothing but loses it.
|
|
75
|
+
const STRING_ABANDON_MS = 5000;
|
|
76
|
+
// How long an abandoned head stays dormant waiting for a late tail. Bounded
|
|
77
|
+
// so a stale packet cannot absorb unrelated multi-byte input later on; a
|
|
78
|
+
// resumed head that is abandoned again is dropped for good.
|
|
79
|
+
const STRING_DORMANT_MS = 10_000;
|
|
80
|
+
// Openings of the string replies omp receives, matched while still partial
|
|
81
|
+
// (each alternative accepts any prefix of its opener) so a legacy Alt chord
|
|
82
|
+
// that diverges is recognized as soon as it does:
|
|
83
|
+
// - OSC: numeric command then `;` (`\x1b]11;`, `\x1b]5522;`).
|
|
84
|
+
// - DCS: DECRQSS/XTGETTCAP `\x1bP1$r` / `\x1bP1+r`, XTVERSION `\x1bP>|`, DA3 `\x1bP!|`.
|
|
85
|
+
// - APC: kitty graphics `\x1b_G`, TSP `\x1b_tsp;`, Glyph Protocol `\x1b_25a1;`.
|
|
86
|
+
const LEGACY_STRING_REPLY_OPENER =
|
|
87
|
+
/^(?:\x1b\]\d+(?:;|$)|\x1bP(?:\d(?:[$+](?:r|$)|$)|[>!](?:\||$))|\x1b_(?:G|t(?:s(?:p(?:;|$)|$)|$)|2(?:5(?:a(?:1(?:;|$)|$)|$)|$)))/;
|
|
72
88
|
|
|
73
89
|
// SGR mouse report bodies live between `<` and the terminating `M`/`m`.
|
|
74
90
|
// Matched only when the trailing byte is a valid terminator, so the regex
|
|
@@ -108,6 +124,74 @@ function isRawMultilineBurst(text: string): boolean {
|
|
|
108
124
|
return false;
|
|
109
125
|
}
|
|
110
126
|
|
|
127
|
+
// `resolveEscapeEnd` result for an abandoned string sequence; see there.
|
|
128
|
+
const STRING_ABORTED = -3;
|
|
129
|
+
const OSC5522_PREFIX = "\x1b]5522;";
|
|
130
|
+
// Kitty clipboard metadata (`type=read:status=DATA:mime=…:pw=…:id=…`) is
|
|
131
|
+
// short; bounding the `;` search keeps a malformed packet O(n).
|
|
132
|
+
const MAX_OSC5522_METADATA = 1024;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Resolve an OSC/DCS/APC string sequence by its grammar, not by time. A
|
|
136
|
+
* string ends at ST (`ESC \`), or BEL for OSC. Its payload never contains
|
|
137
|
+
* another control byte or escape, and an OSC 5522 payload (after the
|
|
138
|
+
* metadata `;`) is strict base64. So a slow terminal — a clipboard owner
|
|
139
|
+
* serving a paste lazily — just extends the wait, while a keypress that
|
|
140
|
+
* cannot be payload (Enter, Backspace, Esc, arrows, space) proves the
|
|
141
|
+
* terminal abandoned the string: the caller drops the head and parses that
|
|
142
|
+
* key normally. Neither half of a stalled packet is ever typed as text.
|
|
143
|
+
*
|
|
144
|
+
* The scan is bounded to `[searchFrom, scanLimit)`; `resumeSearchFrom - 1`
|
|
145
|
+
* keeps a one-byte overlap so an `ESC \` split across chunks is still found.
|
|
146
|
+
*/
|
|
147
|
+
function resolveStringEnd(
|
|
148
|
+
buffer: string,
|
|
149
|
+
pos: number,
|
|
150
|
+
length: number,
|
|
151
|
+
resumeSearchFrom: number,
|
|
152
|
+
belTerminates: boolean,
|
|
153
|
+
): number {
|
|
154
|
+
const searchFrom = Math.max(pos + 2, resumeSearchFrom - 1);
|
|
155
|
+
const scanLimit = Math.min(length, pos + MAX_STRING_SEQ_BYTES);
|
|
156
|
+
const base64From = belTerminates ? osc5522PayloadStart(buffer, pos, scanLimit) : -1;
|
|
157
|
+
// ECMA-48 §8.3.27: a DCS command string may carry BS..CR (0x08-0x0D).
|
|
158
|
+
const dcs = buffer.charCodeAt(pos + 1) === 0x50; /* P */
|
|
159
|
+
for (let i = searchFrom; i < scanLimit; i++) {
|
|
160
|
+
const code = buffer.charCodeAt(i);
|
|
161
|
+
if (code === 0x1b /* ESC */) {
|
|
162
|
+
// A lone trailing ESC may open ST; re-examined next call.
|
|
163
|
+
if (i + 1 === scanLimit) break;
|
|
164
|
+
if (buffer.charCodeAt(i + 1) === 0x5c /* \ */) return i + 2;
|
|
165
|
+
return STRING_ABORTED - i;
|
|
166
|
+
}
|
|
167
|
+
if (code === 0x07 /* BEL */ && belTerminates) return i + 1;
|
|
168
|
+
if ((code < 0x20 || code === 0x7f) && !(dcs && code >= 0x08 && code <= 0x0d)) return STRING_ABORTED - i;
|
|
169
|
+
if (base64From !== -1 && i >= base64From && !isBase64Code(code)) return STRING_ABORTED - i;
|
|
170
|
+
}
|
|
171
|
+
return length - pos >= MAX_STRING_SEQ_BYTES ? -2 : -1;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Index where an OSC 5522 packet's base64 payload starts, or -1 if not (yet) known. */
|
|
175
|
+
function osc5522PayloadStart(buffer: string, pos: number, scanLimit: number): number {
|
|
176
|
+
if (!buffer.startsWith(OSC5522_PREFIX, pos)) return -1;
|
|
177
|
+
const metadataEnd = Math.min(scanLimit, pos + OSC5522_PREFIX.length + MAX_OSC5522_METADATA);
|
|
178
|
+
for (let i = pos + OSC5522_PREFIX.length; i < metadataEnd; i++) {
|
|
179
|
+
if (buffer.charCodeAt(i) === 0x3b /* ; */) return i + 1;
|
|
180
|
+
}
|
|
181
|
+
return -1;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function isBase64Code(code: number): boolean {
|
|
185
|
+
return (
|
|
186
|
+
(code >= 0x41 && code <= 0x5a) || // A-Z
|
|
187
|
+
(code >= 0x61 && code <= 0x7a) || // a-z
|
|
188
|
+
(code >= 0x30 && code <= 0x39) || // 0-9
|
|
189
|
+
code === 0x2b || // +
|
|
190
|
+
code === 0x2f || // /
|
|
191
|
+
code === 0x3d // =
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
111
195
|
/**
|
|
112
196
|
* Resolve the exclusive-end index of the escape sequence starting at `pos`
|
|
113
197
|
* (`buffer.charCodeAt(pos)` must be ESC). `resumeSearchFrom` is honored only
|
|
@@ -125,6 +209,9 @@ function isRawMultilineBurst(text: string): boolean {
|
|
|
125
209
|
* `-2` — incomplete and the prefix already spans the per-type cap;
|
|
126
210
|
* the caller cap-flushes CSI as raw bytes; string types
|
|
127
211
|
* (OSC/DCS/APC) are dropped and their tail discarded.
|
|
212
|
+
* `<= STRING_ABORTED` — an OSC/DCS/APC interrupted by a byte its payload
|
|
213
|
+
* cannot contain; `STRING_ABORTED - result` is that byte's
|
|
214
|
+
* index.
|
|
128
215
|
*/
|
|
129
216
|
function resolveEscapeEnd(buffer: string, pos: number, length: number, resumeSearchFrom: number): number {
|
|
130
217
|
if (pos + 1 >= length) return -1;
|
|
@@ -174,45 +261,13 @@ function resolveEscapeEnd(buffer: string, pos: number, length: number, resumeSea
|
|
|
174
261
|
}
|
|
175
262
|
return length - pos >= MAX_CSI_BYTES ? -2 : -1;
|
|
176
263
|
}
|
|
177
|
-
case 0x5d /* ] */:
|
|
178
|
-
// OSC: ESC ] ... BEL or ST (ESC \).
|
|
179
|
-
|
|
180
|
-
// an unterminated payload delivered as one huge chunk would
|
|
181
|
-
// otherwise be scanned to the end of the buffer — past the cap
|
|
182
|
-
// this function exists to enforce. `resumeSearchFrom - 1` keeps
|
|
183
|
-
// the one-byte overlap so an `ESC \` split across chunks is
|
|
184
|
-
// still found (the prior call's trailing ESC is re-inspected).
|
|
185
|
-
const searchFrom = Math.max(pos + 2, resumeSearchFrom - 1);
|
|
186
|
-
const scanLimit = Math.min(length, pos + MAX_STRING_SEQ_BYTES);
|
|
187
|
-
for (let i = searchFrom; i < scanLimit; i++) {
|
|
188
|
-
const code = buffer.charCodeAt(i);
|
|
189
|
-
if (code === 0x07 /* BEL */) return i + 1;
|
|
190
|
-
if (code === 0x1b /* ESC */) {
|
|
191
|
-
// `ESC \` (ST) must end within the cap; a lone trailing
|
|
192
|
-
// ESC at the buffer edge stays incomplete and is
|
|
193
|
-
// re-examined next call via the resume overlap.
|
|
194
|
-
if (i + 1 < scanLimit && buffer.charCodeAt(i + 1) === 0x5c /* \ */) return i + 2;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return length - pos >= MAX_STRING_SEQ_BYTES ? -2 : -1;
|
|
198
|
-
}
|
|
264
|
+
case 0x5d /* ] */:
|
|
265
|
+
// OSC: ESC ] ... BEL or ST (ESC \).
|
|
266
|
+
return resolveStringEnd(buffer, pos, length, resumeSearchFrom, true);
|
|
199
267
|
case 0x50 /* P */:
|
|
200
|
-
case 0x5f /* _ */:
|
|
201
|
-
// DCS / APC: ESC P/_ ... ST (ESC \).
|
|
202
|
-
|
|
203
|
-
const searchFrom = Math.max(pos + 2, resumeSearchFrom - 1);
|
|
204
|
-
const scanLimit = Math.min(length, pos + MAX_STRING_SEQ_BYTES);
|
|
205
|
-
for (let i = searchFrom; i < scanLimit; i++) {
|
|
206
|
-
if (
|
|
207
|
-
buffer.charCodeAt(i) === 0x1b /* ESC */ &&
|
|
208
|
-
i + 1 < scanLimit &&
|
|
209
|
-
buffer.charCodeAt(i + 1) === 0x5c /* \ */
|
|
210
|
-
) {
|
|
211
|
-
return i + 2;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return length - pos >= MAX_STRING_SEQ_BYTES ? -2 : -1;
|
|
215
|
-
}
|
|
268
|
+
case 0x5f /* _ */:
|
|
269
|
+
// DCS / APC: ESC P/_ ... ST (ESC \).
|
|
270
|
+
return resolveStringEnd(buffer, pos, length, resumeSearchFrom, false);
|
|
216
271
|
case 0x4f /* O */:
|
|
217
272
|
// SS3: ESC O + 1 char.
|
|
218
273
|
return pos + 3 <= length ? pos + 3 : -1;
|
|
@@ -236,10 +291,18 @@ function parseUnmodifiedKittyPrintableCodepoint(sequence: string): number | unde
|
|
|
236
291
|
function extractCompleteSequences(
|
|
237
292
|
buffer: string,
|
|
238
293
|
resumeSearchFrom: number,
|
|
239
|
-
): {
|
|
294
|
+
): {
|
|
295
|
+
sequences: string[];
|
|
296
|
+
remainder: string;
|
|
297
|
+
resumeSearchFrom: number;
|
|
298
|
+
discardFrom?: number;
|
|
299
|
+
/** The buffered string head at offset 0 was abandoned (see `resolveStringEnd`). */
|
|
300
|
+
abortedHead?: boolean;
|
|
301
|
+
} {
|
|
240
302
|
const sequences: string[] = [];
|
|
241
303
|
const length = buffer.length;
|
|
242
304
|
let pos = 0;
|
|
305
|
+
let abortedHead = false;
|
|
243
306
|
|
|
244
307
|
// Index-based scanning: this is the input hot path. Slicing the remaining
|
|
245
308
|
// buffer (or Array.from-ing it) per iteration would make plain-text bursts
|
|
@@ -273,7 +336,7 @@ function extractCompleteSequences(
|
|
|
273
336
|
// window so cases 1/2 can still arrive; if no follower
|
|
274
337
|
// arrives, `flush()` splits the held remainder into two ESC
|
|
275
338
|
// events (#3857).
|
|
276
|
-
return { sequences, remainder: buffer.slice(pos), resumeSearchFrom: 0 };
|
|
339
|
+
return { sequences, remainder: buffer.slice(pos), resumeSearchFrom: 0, abortedHead };
|
|
277
340
|
}
|
|
278
341
|
const third = buffer.charCodeAt(pos + 2);
|
|
279
342
|
if (third !== 0x5b && third !== 0x4f) {
|
|
@@ -292,7 +355,7 @@ function extractCompleteSequences(
|
|
|
292
355
|
// text (settings search filling with "[B" or "[<35;22;17M").
|
|
293
356
|
const innerEnd = resolveEscapeEnd(buffer, pos + 1, length, 0);
|
|
294
357
|
if (innerEnd === -1) {
|
|
295
|
-
return { sequences, remainder: buffer.slice(pos), resumeSearchFrom: 0 };
|
|
358
|
+
return { sequences, remainder: buffer.slice(pos), resumeSearchFrom: 0, abortedHead };
|
|
296
359
|
}
|
|
297
360
|
if (innerEnd === -2) {
|
|
298
361
|
// `third` is CSI/SS3 here, never a string type.
|
|
@@ -321,13 +384,21 @@ function extractCompleteSequences(
|
|
|
321
384
|
// Single ESC — resolve directly. Hint carries over from the previous
|
|
322
385
|
// call only when we are still on the buffered escape (pos === 0).
|
|
323
386
|
const end = resolveEscapeEnd(buffer, pos, length, pos === 0 ? hint : 0);
|
|
387
|
+
if (end <= STRING_ABORTED) {
|
|
388
|
+
// The terminal abandoned this string: drop the head (protocol
|
|
389
|
+
// data, never keys) and parse the interrupting byte normally.
|
|
390
|
+
if (pos === 0) abortedHead = true;
|
|
391
|
+
pos = STRING_ABORTED - end;
|
|
392
|
+
hint = 0;
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
324
395
|
if (end === -1) {
|
|
325
396
|
// Buffer for more. When this is the leading OSC/DCS/APC,
|
|
326
397
|
// remember how far we scanned so the next `process()` call
|
|
327
398
|
// resumes from there instead of rescanning the whole buffer.
|
|
328
399
|
const next = pos + 1 < length ? buffer.charCodeAt(pos + 1) : -1;
|
|
329
400
|
const nextHint = pos === 0 && (next === 0x5d || next === 0x50 || next === 0x5f) ? length : 0;
|
|
330
|
-
return { sequences, remainder: buffer.slice(pos), resumeSearchFrom: nextHint };
|
|
401
|
+
return { sequences, remainder: buffer.slice(pos), resumeSearchFrom: nextHint, abortedHead };
|
|
331
402
|
}
|
|
332
403
|
if (end === -2) {
|
|
333
404
|
const next = buffer.charCodeAt(pos + 1);
|
|
@@ -349,7 +420,7 @@ function extractCompleteSequences(
|
|
|
349
420
|
hint = 0;
|
|
350
421
|
}
|
|
351
422
|
|
|
352
|
-
return { sequences, remainder: "", resumeSearchFrom: 0 };
|
|
423
|
+
return { sequences, remainder: "", resumeSearchFrom: 0, abortedHead };
|
|
353
424
|
}
|
|
354
425
|
|
|
355
426
|
export type StdinBufferOptions = {
|
|
@@ -433,6 +504,20 @@ export class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
|
433
504
|
#stringDiscardBytes = 0;
|
|
434
505
|
#stringDiscardEscHeld = false;
|
|
435
506
|
#stringDiscardWatchdog?: NodeJS.Timeout;
|
|
507
|
+
// Pending string head bookkeeping (see `#trackStringRead`): when the head
|
|
508
|
+
// last made terminal-shaped progress (0 = no head tracked), keys typed
|
|
509
|
+
// while it stalled, and the abandonment timer.
|
|
510
|
+
#stringProgressMs = 0;
|
|
511
|
+
#stringTypedKeys: string[] = [];
|
|
512
|
+
// Buffer offset of each typed key, parallel to `#stringTypedKeys`.
|
|
513
|
+
#stringTypedAt: number[] = [];
|
|
514
|
+
#stringAbandonTimer?: NodeJS.Timeout;
|
|
515
|
+
// An abandoned string head, kept so a late terminal-shaped tail still
|
|
516
|
+
// completes it instead of being typed (see `#resumeDormantString`).
|
|
517
|
+
#dormantString: string | undefined;
|
|
518
|
+
#dormantUntilMs = 0;
|
|
519
|
+
// The pending head was resumed from dormancy; abandoning it again drops it.
|
|
520
|
+
#stringResumed = false;
|
|
436
521
|
|
|
437
522
|
constructor(options: StdinBufferOptions = {}) {
|
|
438
523
|
super();
|
|
@@ -442,6 +527,16 @@ export class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
|
442
527
|
this.#pasteByteLimit = options.pasteByteLimit ?? PASTE_MAX_BYTES;
|
|
443
528
|
}
|
|
444
529
|
|
|
530
|
+
/**
|
|
531
|
+
* Whether a bracketed paste has started and its end marker has not arrived.
|
|
532
|
+
* Terminal skips its per-read DEC 2004 reassertion meanwhile: the host is
|
|
533
|
+
* evidently in bracketed-paste mode, and a paste delivered one key record
|
|
534
|
+
* per read would otherwise cost one terminal write per pasted key (#15757).
|
|
535
|
+
*/
|
|
536
|
+
get pasting(): boolean {
|
|
537
|
+
return this.#pasteMode;
|
|
538
|
+
}
|
|
539
|
+
|
|
445
540
|
process(data: string | Buffer): void {
|
|
446
541
|
// Handle high-byte conversion (for compatibility with parseKeypress)
|
|
447
542
|
// If buffer has single byte > 127, convert to ESC + (byte - 128)
|
|
@@ -462,6 +557,11 @@ export class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
|
462
557
|
str = this.#consumeStringDiscard(str);
|
|
463
558
|
if (str.length === 0) return;
|
|
464
559
|
}
|
|
560
|
+
if (this.#dormantString !== undefined && this.#buffer.length === 0) {
|
|
561
|
+
if (Date.now() < this.#dormantUntilMs) this.#resumeDormantString(str);
|
|
562
|
+
else this.#dormantString = undefined;
|
|
563
|
+
}
|
|
564
|
+
if (this.#bufferIsStringHead()) this.#trackStringRead(str);
|
|
465
565
|
|
|
466
566
|
if (this.#flushDeferral && this.#isFreshEscapeAfterDeferredFlush(str)) {
|
|
467
567
|
// The buffered partial already hit its flush timeout. A new escape is
|
|
@@ -518,6 +618,8 @@ export class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
|
518
618
|
|
|
519
619
|
const startIndex = this.#buffer.indexOf(BRACKETED_PASTE_START);
|
|
520
620
|
if (startIndex !== -1) {
|
|
621
|
+
// Keys typed while a string head stalled precede the paste.
|
|
622
|
+
this.#emitStringTypedKeys();
|
|
521
623
|
if (startIndex > 0) {
|
|
522
624
|
const beforePaste = this.#buffer.slice(0, startIndex);
|
|
523
625
|
const result = extractCompleteSequences(beforePaste, 0);
|
|
@@ -544,6 +646,7 @@ export class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
|
544
646
|
const junk = this.#buffer.slice(result.discardFrom);
|
|
545
647
|
this.#buffer = "";
|
|
546
648
|
this.#escapeSearchOffset = 0;
|
|
649
|
+
this.#takeStringTypedKeys();
|
|
547
650
|
for (const sequence of result.sequences) {
|
|
548
651
|
this.#emitDataSequence(sequence);
|
|
549
652
|
}
|
|
@@ -555,14 +658,26 @@ export class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
|
555
658
|
this.#buffer = result.remainder;
|
|
556
659
|
this.#escapeSearchOffset = result.resumeSearchFrom;
|
|
557
660
|
|
|
661
|
+
// An abandoned string head hands back the keys typed while it stalled;
|
|
662
|
+
// they precede the byte that aborted it.
|
|
663
|
+
if (result.abortedHead) this.#emitStringTypedKeys();
|
|
558
664
|
for (const sequence of result.sequences) {
|
|
559
665
|
this.#emitDataSequence(sequence);
|
|
560
666
|
}
|
|
561
667
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
668
|
+
// A string head waits for its terminator, an aborting byte, or the
|
|
669
|
+
// abandonment bound (`#trackStringRead`), never the key flush window.
|
|
670
|
+
if (this.#bufferIsStringHead()) {
|
|
671
|
+
if (this.#stringProgressMs === 0) {
|
|
672
|
+
this.#dormantString = undefined;
|
|
673
|
+
this.#stringResumed = false;
|
|
674
|
+
this.#noteStringProgress();
|
|
675
|
+
}
|
|
565
676
|
this.#partialHoldStartMs = 0;
|
|
677
|
+
} else {
|
|
678
|
+
this.#takeStringTypedKeys();
|
|
679
|
+
if (this.#buffer.length > 0) this.#armFlushTimer();
|
|
680
|
+
else this.#partialHoldStartMs = 0;
|
|
566
681
|
}
|
|
567
682
|
}
|
|
568
683
|
|
|
@@ -578,7 +693,7 @@ export class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
|
578
693
|
const probe = this.#pasteOverlap + chunk;
|
|
579
694
|
if (probe.indexOf(BRACKETED_PASTE_END) === -1) {
|
|
580
695
|
this.#pasteChunks.push(chunk);
|
|
581
|
-
this.#pasteBytes += chunk
|
|
696
|
+
this.#pasteBytes += Buffer.byteLength(chunk, "utf8");
|
|
582
697
|
const keep = BRACKETED_PASTE_END.length - 1;
|
|
583
698
|
this.#pasteOverlap = probe.length > keep ? probe.slice(probe.length - keep) : probe;
|
|
584
699
|
if (this.#pasteBytes > this.#pasteByteLimit) {
|
|
@@ -741,7 +856,7 @@ export class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
|
741
856
|
this.#timeout = undefined;
|
|
742
857
|
this.#flushDeferral = setTimeout(() => {
|
|
743
858
|
this.#flushDeferral = undefined;
|
|
744
|
-
this.#flushExpired(
|
|
859
|
+
this.#flushExpired();
|
|
745
860
|
});
|
|
746
861
|
}, this.#timeoutMs);
|
|
747
862
|
}
|
|
@@ -788,8 +903,132 @@ export class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
|
788
903
|
return SGR_MOUSE_PARTIAL.test(this.#buffer) || isKittyProtocolActive();
|
|
789
904
|
}
|
|
790
905
|
|
|
906
|
+
/**
|
|
907
|
+
* Whether the buffer is the head of an OSC/DCS/APC string: terminal
|
|
908
|
+
* protocol data (OSC 5522 clipboard read, graphics reply), never a key.
|
|
909
|
+
* Without the kitty keyboard protocol `\x1b]`/`\x1bP`/`\x1b_` may be
|
|
910
|
+
* Alt+]/Alt+P/Alt+_, alone or batched with the next keys, so a head counts
|
|
911
|
+
* only while it follows the opening of a reply omp actually receives
|
|
912
|
+
* ({@link LEGACY_STRING_REPLY_OPENER}); the moment it diverges it falls
|
|
913
|
+
* back to the key flush timer.
|
|
914
|
+
*/
|
|
915
|
+
#bufferIsStringHead(): boolean {
|
|
916
|
+
const buffer = this.#buffer;
|
|
917
|
+
if (!STRING_SEQ_PARTIAL.test(buffer)) return false;
|
|
918
|
+
if (isKittyProtocolActive()) return true;
|
|
919
|
+
return buffer.length > 2 && LEGACY_STRING_REPLY_OPENER.test(buffer);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Classify a read arriving while a string head is pending. Chunk boundaries
|
|
924
|
+
* do not follow terminal writes (SSH, tmux), so no read is ever torn out of
|
|
925
|
+
* the string: every byte stays payload until the terminator completes it or
|
|
926
|
+
* an impossible byte aborts it, and every read refreshes the abandonment
|
|
927
|
+
* bound so a slow transfer is never abandoned while it still streams.
|
|
928
|
+
*
|
|
929
|
+
* Typing and terminal payload are indistinguishable by their bytes; when in
|
|
930
|
+
* doubt the string wins, because typed base64 is the failure this exists
|
|
931
|
+
* to prevent. Only a lone printable key arriving after the flush window
|
|
932
|
+
* passed without a read — the shape of typing — is remembered: a head that
|
|
933
|
+
* completes keeps it as payload, one that is abandoned (aborting byte, or
|
|
934
|
+
* {@link STRING_ABANDON_MS} without reads) replays it. Multi-byte reads,
|
|
935
|
+
* even keys an event-loop stall batched together, stay protocol data.
|
|
936
|
+
*/
|
|
937
|
+
#trackStringRead(str: string): void {
|
|
938
|
+
const stalled = Date.now() - this.#stringProgressMs >= this.#timeoutMs;
|
|
939
|
+
this.#noteStringProgress();
|
|
940
|
+
const code = str.codePointAt(0)!;
|
|
941
|
+
if (!stalled || str.length !== (code > 0xffff ? 2 : 1) || code < 0x20 || code === 0x7f) return;
|
|
942
|
+
// Only a key the string would keep as payload: a terminator completes
|
|
943
|
+
// it, and an impossible byte aborts it and is delivered by the parser.
|
|
944
|
+
const candidate = this.#buffer + str;
|
|
945
|
+
const end = resolveStringEnd(
|
|
946
|
+
candidate,
|
|
947
|
+
0,
|
|
948
|
+
candidate.length,
|
|
949
|
+
this.#buffer.length,
|
|
950
|
+
this.#buffer.charCodeAt(1) === 0x5d,
|
|
951
|
+
);
|
|
952
|
+
if (end !== -1) return;
|
|
953
|
+
this.#stringTypedKeys.push(str);
|
|
954
|
+
this.#stringTypedAt.push(this.#buffer.length);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
#noteStringProgress(): void {
|
|
958
|
+
this.#stringProgressMs = Date.now();
|
|
959
|
+
clearTimeout(this.#stringAbandonTimer);
|
|
960
|
+
this.#stringAbandonTimer = setTimeout(() => {
|
|
961
|
+
this.#stringAbandonTimer = undefined;
|
|
962
|
+
if (!this.#bufferIsStringHead()) {
|
|
963
|
+
this.#takeStringTypedKeys();
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
// The replayed keys are not payload: cut them from the dormant head
|
|
967
|
+
// so a late tail completes the packet the terminal actually sent.
|
|
968
|
+
let dormant = this.#buffer;
|
|
969
|
+
for (let i = this.#stringTypedAt.length - 1; i >= 0; i--) {
|
|
970
|
+
const at = this.#stringTypedAt[i]!;
|
|
971
|
+
dormant = dormant.slice(0, at) + dormant.slice(at + this.#stringTypedKeys[i]!.length);
|
|
972
|
+
}
|
|
973
|
+
if (this.#stringResumed) {
|
|
974
|
+
this.#dormantString = undefined;
|
|
975
|
+
} else {
|
|
976
|
+
this.#dormantString = dormant;
|
|
977
|
+
this.#dormantUntilMs = Date.now() + STRING_DORMANT_MS;
|
|
978
|
+
}
|
|
979
|
+
this.#buffer = "";
|
|
980
|
+
this.#escapeSearchOffset = 0;
|
|
981
|
+
this.#emitStringTypedKeys();
|
|
982
|
+
}, STRING_ABANDON_MS);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
/** End string head tracking; returns the keys typed while it stalled. */
|
|
986
|
+
#takeStringTypedKeys(): string[] {
|
|
987
|
+
if (this.#stringProgressMs === 0) return [];
|
|
988
|
+
const keys = this.#stringTypedKeys;
|
|
989
|
+
this.#stringTypedKeys = [];
|
|
990
|
+
this.#stringTypedAt = [];
|
|
991
|
+
this.#stringProgressMs = 0;
|
|
992
|
+
clearTimeout(this.#stringAbandonTimer);
|
|
993
|
+
this.#stringAbandonTimer = undefined;
|
|
994
|
+
return keys;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/** End string head tracking and replay the keys typed while it stalled. */
|
|
998
|
+
#emitStringTypedKeys(): void {
|
|
999
|
+
for (const keys of this.#takeStringTypedKeys()) {
|
|
1000
|
+
for (const sequence of extractCompleteSequences(keys, 0).sequences) this.#emitDataSequence(sequence);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* Re-attach a late tail to an abandoned string head. A read that completes
|
|
1006
|
+
* the head (even a lone BEL, or `\` after a held ESC), a lone ESC under the
|
|
1007
|
+
* kitty keyboard protocol (never a key there, so it opens a split ST), or a
|
|
1008
|
+
* multi-byte read that continues its grammar (strict base64 for OSC 5522)
|
|
1009
|
+
* resumes the head, as protocol data like any multi-byte read. Other lone
|
|
1010
|
+
* printable keys pass through as typing and keep the head dormant;
|
|
1011
|
+
* anything else means input moved on.
|
|
1012
|
+
*/
|
|
1013
|
+
#resumeDormantString(str: string): void {
|
|
1014
|
+
const head = this.#dormantString!;
|
|
1015
|
+
const candidate = head + str;
|
|
1016
|
+
const end = resolveStringEnd(candidate, 0, candidate.length, head.length, head.charCodeAt(1) === 0x5d);
|
|
1017
|
+
const code = str.codePointAt(0)!;
|
|
1018
|
+
const heldSt = str === ESC && isKittyProtocolActive();
|
|
1019
|
+
if (end <= 0 && !heldSt && str.length === (code > 0xffff ? 2 : 1)) {
|
|
1020
|
+
if (code < 0x20 || code === 0x7f) this.#dormantString = undefined;
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
this.#dormantString = undefined;
|
|
1024
|
+
if (end <= STRING_ABORTED) return;
|
|
1025
|
+
this.#buffer = head;
|
|
1026
|
+
this.#escapeSearchOffset = head.length;
|
|
1027
|
+
this.#stringResumed = true;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
791
1030
|
/** Timeout-driven flush: hold unambiguous partials (bounded), else deliver. */
|
|
792
|
-
#flushExpired(
|
|
1031
|
+
#flushExpired(): void {
|
|
793
1032
|
if (this.#buffer.length === 0) {
|
|
794
1033
|
this.#partialHoldStartMs = 0;
|
|
795
1034
|
return;
|
|
@@ -802,16 +1041,16 @@ export class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
|
802
1041
|
}
|
|
803
1042
|
}
|
|
804
1043
|
this.#partialHoldStartMs = 0;
|
|
805
|
-
for (const sequence of this.#drainBuffered(
|
|
1044
|
+
for (const sequence of this.#drainBuffered()) {
|
|
806
1045
|
this.#emitDataSequence(sequence);
|
|
807
1046
|
}
|
|
808
1047
|
}
|
|
809
1048
|
|
|
810
1049
|
flush(): string[] {
|
|
811
|
-
return this.#drainBuffered(
|
|
1050
|
+
return this.#drainBuffered();
|
|
812
1051
|
}
|
|
813
1052
|
|
|
814
|
-
#drainBuffered(
|
|
1053
|
+
#drainBuffered(): string[] {
|
|
815
1054
|
this.#clearFlushTimer();
|
|
816
1055
|
|
|
817
1056
|
const rawCandidate = this.#takeRawPasteCandidate();
|
|
@@ -822,6 +1061,7 @@ export class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
|
822
1061
|
return sequences;
|
|
823
1062
|
}
|
|
824
1063
|
|
|
1064
|
+
const stringHead = this.#bufferIsStringHead();
|
|
825
1065
|
const buffered = this.#buffer;
|
|
826
1066
|
this.#buffer = "";
|
|
827
1067
|
this.#escapeSearchOffset = 0;
|
|
@@ -833,15 +1073,10 @@ export class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
|
833
1073
|
// split in `extractCompleteSequences` and deliver two ESC events.
|
|
834
1074
|
if (buffered === `${ESC}${ESC}`) {
|
|
835
1075
|
sequences.push(ESC, ESC);
|
|
836
|
-
} else if (
|
|
837
|
-
//
|
|
838
|
-
//
|
|
839
|
-
|
|
840
|
-
// reply) whose tail is still in flight. Never deliver the head as a
|
|
841
|
-
// key; on a timeout flush also swallow the tail up to its
|
|
842
|
-
// terminator so kilobytes of base64 are not typed into the focused
|
|
843
|
-
// component (image paste spam).
|
|
844
|
-
if (discardTornString) this.#enterStringDiscard();
|
|
1076
|
+
} else if (stringHead) {
|
|
1077
|
+
// Unterminated OSC/DCS/APC head: protocol data, never delivered as a
|
|
1078
|
+
// key. Its tracking ends with it so the next head starts fresh.
|
|
1079
|
+
this.#takeStringTypedKeys();
|
|
845
1080
|
} else {
|
|
846
1081
|
sequences.push(buffered);
|
|
847
1082
|
}
|
|
@@ -920,6 +1155,9 @@ export class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
|
920
1155
|
this.#clearPasteWatchdog();
|
|
921
1156
|
this.#clearRawPasteTimer();
|
|
922
1157
|
this.#exitStringDiscard();
|
|
1158
|
+
this.#takeStringTypedKeys();
|
|
1159
|
+
this.#dormantString = undefined;
|
|
1160
|
+
this.#stringResumed = false;
|
|
923
1161
|
this.#buffer = "";
|
|
924
1162
|
this.#rawPasteCandidate = "";
|
|
925
1163
|
this.#pasteMode = false;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { HangulCompatibilityJamoWidth } from "./utils";
|
|
2
2
|
export * from "./terminal-multiplexer";
|
|
3
3
|
export { wrapTmuxPassthrough } from "./tmux";
|
|
4
|
+
/**
|
|
5
|
+
* Trusted terminal background when reported; otherwise black for detected dark
|
|
6
|
+
* appearance or white for detected light appearance, using theme auto-detection.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getTerminalBackgroundColor(): number;
|
|
9
|
+
/** Store an OSC 11 background, or reset to appearance detection (also for tests). */
|
|
10
|
+
export declare function setTerminalBackgroundColor(color: number | undefined): void;
|
|
4
11
|
export declare enum ImageProtocol {
|
|
5
12
|
Kitty = "\u001B_G",
|
|
6
13
|
Iterm2 = "\u001B]1337;File=",
|
|
@@ -339,6 +346,14 @@ export interface ParsedKittyPlacementLine {
|
|
|
339
346
|
* callers fall back to writing the line verbatim.
|
|
340
347
|
*/
|
|
341
348
|
export declare function parseKittyDirectPlacementLine(line: string): ParsedKittyPlacementLine | null;
|
|
349
|
+
/**
|
|
350
|
+
* Rows above its own line that an {@link Image} direct-placement line paints
|
|
351
|
+
* over: the `CUU(rows-1)` of its lead-in, or 0 for any other line. The renderer
|
|
352
|
+
* erases those rows before a SIXEL/iTerm2 block is written again, because
|
|
353
|
+
* those protocols have no delete command and the block's reserved rows are
|
|
354
|
+
* byte-identical between frames, so the diff never rewrites them.
|
|
355
|
+
*/
|
|
356
|
+
export declare function directPlacementLift(line: string): number;
|
|
342
357
|
/**
|
|
343
358
|
* Rebuild an {@link Image} direct-placement line for the viewport row it is
|
|
344
359
|
* written at. The component-rendered line encodes `CUU(rows-1)`, which clamps
|
|
@@ -397,7 +412,7 @@ export declare function getImageDimensions(base64Data: string, mimeType: string)
|
|
|
397
412
|
* thread; `null` when the encode fails. For output that cannot wait for an
|
|
398
413
|
* off-thread encode.
|
|
399
414
|
*/
|
|
400
|
-
export declare function encodeSixelNow(base64Data: string, widthPx: number, heightPx: number): string | null;
|
|
415
|
+
export declare function encodeSixelNow(base64Data: string, widthPx: number, heightPx: number, backgroundRgb?: number): string | null;
|
|
401
416
|
export declare function renderImage(base64Data: string, imageDimensions: ImageDimensions, options?: ImageRenderOptions): {
|
|
402
417
|
sequence?: string;
|
|
403
418
|
lines?: string[];
|