@prometheus-ai/tui 0.5.3 → 0.5.8
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/dist/types/autocomplete.d.ts +3 -1
- package/dist/types/components/box.d.ts +1 -1
- package/dist/types/components/editor.d.ts +35 -2
- package/dist/types/components/image.d.ts +22 -3
- package/dist/types/components/input.d.ts +6 -1
- package/dist/types/components/loader.d.ts +9 -2
- package/dist/types/components/markdown.d.ts +3 -1
- package/dist/types/components/scroll-view.d.ts +23 -1
- package/dist/types/components/select-list.d.ts +19 -1
- package/dist/types/components/settings-list.d.ts +87 -7
- package/dist/types/components/spacer.d.ts +1 -1
- package/dist/types/components/tab-bar.d.ts +37 -4
- package/dist/types/components/text.d.ts +2 -2
- package/dist/types/components/truncated-text.d.ts +1 -1
- package/dist/types/fuzzy.d.ts +10 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/keybindings.d.ts +5 -3
- package/dist/types/keys.d.ts +1 -1
- package/dist/types/kill-ring.d.ts +0 -7
- package/dist/types/kitty-graphics.d.ts +16 -31
- package/dist/types/loop-watchdog.d.ts +39 -0
- package/dist/types/mouse.d.ts +41 -0
- package/dist/types/stdin-buffer.d.ts +17 -0
- package/dist/types/terminal-capabilities.d.ts +74 -18
- package/dist/types/terminal.d.ts +34 -36
- package/dist/types/tui.d.ts +191 -79
- package/dist/types/utils.d.ts +5 -2
- package/package.json +4 -4
- package/src/autocomplete.ts +79 -65
- package/src/components/box.ts +43 -63
- package/src/components/editor.ts +471 -136
- package/src/components/image.ts +85 -9
- package/src/components/input.ts +12 -3
- package/src/components/loader.ts +35 -21
- package/src/components/markdown.ts +174 -53
- package/src/components/scroll-view.ts +63 -2
- package/src/components/select-list.ts +233 -38
- package/src/components/settings-list.ts +626 -64
- package/src/components/spacer.ts +9 -5
- package/src/components/tab-bar.ts +153 -28
- package/src/components/text.ts +6 -2
- package/src/components/truncated-text.ts +10 -2
- package/src/fuzzy.ts +214 -59
- package/src/index.ts +3 -1
- package/src/keybindings.ts +72 -14
- package/src/keys.ts +1 -1
- package/src/kill-ring.ts +5 -0
- package/src/kitty-graphics.ts +2 -101
- package/src/loop-watchdog.ts +106 -0
- package/src/mouse.ts +55 -0
- package/src/stdin-buffer.ts +291 -81
- package/src/terminal-capabilities.ts +206 -168
- package/src/terminal.ts +367 -110
- package/src/tui.ts +2102 -1729
- package/src/utils.ts +92 -60
|
@@ -2,9 +2,7 @@ import { encodeSixel } from "@prometheus-ai/natives";
|
|
|
2
2
|
import { $env, isBunTestRuntime } from "@prometheus-ai/utils";
|
|
3
3
|
import {
|
|
4
4
|
detectKittyUnicodePlaceholdersSupport,
|
|
5
|
-
encodeKittyTempFileTransmit,
|
|
6
5
|
getKittyGraphics,
|
|
7
|
-
isPngBase64,
|
|
8
6
|
KITTY_PLACEHOLDER,
|
|
9
7
|
kittyPlaceholdersFit,
|
|
10
8
|
renderKittyPlaceholderLines,
|
|
@@ -25,7 +23,31 @@ export enum NotifyProtocol {
|
|
|
25
23
|
|
|
26
24
|
export type TerminalId = "kitty" | "ghostty" | "wezterm" | "iterm2" | "vscode" | "alacritty" | "base" | "trueColor";
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
function hasNeedleBefore(line: string, needle: string, limit: number): boolean {
|
|
27
|
+
const index = line.indexOf(needle);
|
|
28
|
+
return index !== -1 && index + needle.length <= limit;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function hasSixelDcsStart(line: string): boolean {
|
|
32
|
+
const limit = Math.min(line.length, 128);
|
|
33
|
+
let from = 0;
|
|
34
|
+
for (;;) {
|
|
35
|
+
const start = line.indexOf("\x1bP", from);
|
|
36
|
+
if (start === -1 || start + 3 > limit) return false;
|
|
37
|
+
let i = start + 2;
|
|
38
|
+
while (i < limit) {
|
|
39
|
+
const code = line.charCodeAt(i);
|
|
40
|
+
if ((code >= 0x30 && code <= 0x39) || code === 0x3b) {
|
|
41
|
+
i++;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
if (i < limit && line.charCodeAt(i) === 0x71) return true;
|
|
47
|
+
from = start + 2;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
29
51
|
/** Terminal capability details used for rendering and protocol selection. */
|
|
30
52
|
export class TerminalInfo {
|
|
31
53
|
constructor(
|
|
@@ -34,20 +56,28 @@ export class TerminalInfo {
|
|
|
34
56
|
public readonly trueColor: boolean,
|
|
35
57
|
public readonly hyperlinks: boolean,
|
|
36
58
|
public readonly notifyProtocol: NotifyProtocol = NotifyProtocol.Bell,
|
|
37
|
-
public readonly eagerEraseScrollbackRisk: boolean = false,
|
|
38
59
|
public readonly deccara: boolean = false,
|
|
39
60
|
readonly supportsScreenToScrollback: boolean = false,
|
|
40
61
|
/** Renders the Kitty OSC 66 text-sizing protocol (scaled spans). Kitty only. */
|
|
41
62
|
public readonly textSizing: boolean = false,
|
|
42
63
|
) {}
|
|
43
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Mutable clone for the {@link TERMINAL} singleton: copies every field and
|
|
67
|
+
* keeps the prototype methods, so the builder and runtime setters flip
|
|
68
|
+
* runtime-resolved {@link RuntimeTerminal} capabilities in place instead of
|
|
69
|
+
* reconstructing positional constructor args.
|
|
70
|
+
*/
|
|
71
|
+
clone(): RuntimeTerminal {
|
|
72
|
+
return Object.assign(Object.create(TerminalInfo.prototype), this) as RuntimeTerminal;
|
|
73
|
+
}
|
|
74
|
+
|
|
44
75
|
isImageLine(line: string): boolean {
|
|
45
76
|
if (!this.imageProtocol) return false;
|
|
46
77
|
if (this.imageProtocol === ImageProtocol.Sixel) {
|
|
47
|
-
return
|
|
78
|
+
return hasSixelDcsStart(line);
|
|
48
79
|
}
|
|
49
|
-
|
|
50
|
-
return head.includes(this.imageProtocol) || head.includes(KITTY_PLACEHOLDER);
|
|
80
|
+
return hasNeedleBefore(line, this.imageProtocol, 64) || hasNeedleBefore(line, KITTY_PLACEHOLDER, 64);
|
|
51
81
|
}
|
|
52
82
|
|
|
53
83
|
formatNotification(message: string | TerminalNotification): string {
|
|
@@ -118,110 +148,75 @@ export function isWindowsTerminalPreviewSixelSupported(
|
|
|
118
148
|
}
|
|
119
149
|
|
|
120
150
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* terminals either clamp a scrolled reader back to the active tail or erase host
|
|
126
|
-
* scrollback when ED3 lands. The important property is not the brand name — it
|
|
127
|
-
* is that an unknown viewport position cannot be proven safe. Environment
|
|
128
|
-
* markers are therefore only used to prove *risk* or a strongly-known profile;
|
|
129
|
-
* unknown POSIX/remote/multiplexer shapes default to risky for passive renders.
|
|
130
|
-
*
|
|
131
|
-
* Native win32 is excluded here because the renderer has dedicated ConPTY
|
|
132
|
-
* deferral paths; a `WT_SESSION` sighting on POSIX means Windows Terminal is the
|
|
133
|
-
* outer host fronting WSL, where the same ED3 yank applies. See #1610/#1682/#1799.
|
|
151
|
+
* Resolve an explicit user override for DEC 2026 synchronized output. Returns
|
|
152
|
+
* `false` for an opt-out, `true` for a force-on, or `null` when the user has
|
|
153
|
+
* expressed no preference. Shared by the static default and the runtime DECRQM
|
|
154
|
+
* probe so both honor the same precedence — an opt-out beats a force-on.
|
|
134
155
|
*/
|
|
135
|
-
export function
|
|
136
|
-
env
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const term = env.TERM?.toLowerCase() ?? "";
|
|
142
|
-
const termProgram = env.TERM_PROGRAM?.toLowerCase() ?? "";
|
|
143
|
-
const colorTerm = env.COLORTERM?.toLowerCase() ?? "";
|
|
156
|
+
export function synchronizedOutputUserOverride(env: NodeJS.ProcessEnv = Bun.env): boolean | null {
|
|
157
|
+
if (env.PROMETHEUS_NO_SYNC_OUTPUT || env.PROMETHEUS_TUI_SYNC_OUTPUT === "0") return false;
|
|
158
|
+
if (env.PROMETHEUS_FORCE_SYNC_OUTPUT === "1" || env.PROMETHEUS_TUI_SYNC_OUTPUT === "1") return true;
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
144
161
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
env.ZELLIJ ||
|
|
154
|
-
term.startsWith("tmux") ||
|
|
155
|
-
term.startsWith("screen")
|
|
156
|
-
) {
|
|
157
|
-
return true;
|
|
158
|
-
}
|
|
159
|
-
if (
|
|
160
|
-
env.WEZTERM_PANE ||
|
|
161
|
-
env.KITTY_WINDOW_ID ||
|
|
162
|
-
env.GHOSTTY_RESOURCES_DIR ||
|
|
163
|
-
env.ALACRITTY_WINDOW_ID ||
|
|
164
|
-
env.VTE_VERSION ||
|
|
165
|
-
env.ITERM_SESSION_ID
|
|
166
|
-
) {
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
169
|
-
switch (termProgram) {
|
|
170
|
-
case "alacritty":
|
|
171
|
-
case "apple_terminal":
|
|
172
|
-
case "ghostty":
|
|
173
|
-
case "gnome-terminal":
|
|
174
|
-
case "iterm.app":
|
|
175
|
-
case "kgx":
|
|
176
|
-
case "kitty":
|
|
177
|
-
case "ptyxis":
|
|
178
|
-
case "wezterm":
|
|
179
|
-
case "xfce4-terminal":
|
|
180
|
-
return true;
|
|
181
|
-
default:
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
if (platform === "linux" && (colorTerm === "truecolor" || colorTerm === "24bit")) return true;
|
|
185
|
-
// Unknown POSIX terminals have no scroll-position oracle. Treat them as risky
|
|
186
|
-
// for passive ED3 until a positive terminal-specific integration proves safe.
|
|
187
|
-
return true;
|
|
162
|
+
/**
|
|
163
|
+
* Whether `TERM_FEATURES` advertises DEC 2026 synchronized output via the `Sy`
|
|
164
|
+
* capability token. `TERM_FEATURES` is a run of capitalized two-letter codes
|
|
165
|
+
* (e.g. `…Sy…`), so a case-sensitive substring match is unambiguous: `Sy`
|
|
166
|
+
* cannot straddle a code boundary because those are always lowercase→uppercase.
|
|
167
|
+
*/
|
|
168
|
+
function advertisesSynchronizedOutput(termFeatures: string | undefined): boolean {
|
|
169
|
+
return termFeatures?.includes("Sy") ?? false;
|
|
188
170
|
}
|
|
189
171
|
|
|
190
|
-
/**
|
|
172
|
+
/**
|
|
173
|
+
* Whether DEC 2026 synchronized-output wrappers should be enabled by default.
|
|
174
|
+
*
|
|
175
|
+
* Policy (highest precedence first):
|
|
176
|
+
* 1. Explicit user override (`PROMETHEUS_NO_SYNC_OUTPUT`/`PROMETHEUS_TUI_SYNC_OUTPUT=0` off,
|
|
177
|
+
* `PROMETHEUS_FORCE_SYNC_OUTPUT=1`/`PROMETHEUS_TUI_SYNC_OUTPUT=1` on).
|
|
178
|
+
* 2. Positive `TERM_FEATURES` advertisement (`Sy`) — survives SSH/mux wrapping.
|
|
179
|
+
* 3. Windows Terminal (1.24+) via `WT_SESSION`, on native win32 and the
|
|
180
|
+
* WSL/SSH-fronted host alike.
|
|
181
|
+
* 4. Known direct terminals with confirmed support. SSH does *not* disable —
|
|
182
|
+
* DEC 2026 passes through SSH when the outer terminal honors it.
|
|
183
|
+
* 5. Everything else starts off, including risky multiplexers; the runtime
|
|
184
|
+
* DECRQM probe upgrades any of them when the terminal actually reports
|
|
185
|
+
* `?2026` supported (current zellij, tmux master, foot, contour, mintty…).
|
|
186
|
+
*/
|
|
191
187
|
export function shouldEnableSynchronizedOutputByDefault(
|
|
192
188
|
env: NodeJS.ProcessEnv = Bun.env,
|
|
193
|
-
platform: NodeJS.Platform = process.platform,
|
|
194
189
|
terminalId: TerminalId = TERMINAL_ID,
|
|
195
190
|
): boolean {
|
|
196
|
-
|
|
197
|
-
if (
|
|
198
|
-
if (platform === "win32") return false;
|
|
191
|
+
const override = synchronizedOutputUserOverride(env);
|
|
192
|
+
if (override !== null) return override;
|
|
199
193
|
|
|
194
|
+
if (advertisesSynchronizedOutput(env.TERM_FEATURES)) return true;
|
|
195
|
+
if (env.WT_SESSION) return true;
|
|
196
|
+
|
|
197
|
+
// Risky multiplexers start off even when an inner terminal id leaks through:
|
|
198
|
+
// older tmux/screen synchronized-output handling is flaky and a mux may not
|
|
199
|
+
// pass DEC 2026 to the outer host. The DECRQM probe re-enables sync when the
|
|
200
|
+
// mux reports `?2026` supported.
|
|
200
201
|
const term = env.TERM?.toLowerCase() ?? "";
|
|
201
|
-
|
|
202
|
-
if (
|
|
203
|
-
env.SSH_CONNECTION ||
|
|
204
|
-
env.SSH_CLIENT ||
|
|
205
|
-
env.SSH_TTY ||
|
|
206
|
-
env.TMUX ||
|
|
207
|
-
env.STY ||
|
|
208
|
-
env.ZELLIJ ||
|
|
209
|
-
term.startsWith("tmux") ||
|
|
210
|
-
term.startsWith("screen")
|
|
211
|
-
) {
|
|
202
|
+
if (env.TMUX || env.STY || env.ZELLIJ || term.startsWith("tmux") || term.startsWith("screen")) {
|
|
212
203
|
return false;
|
|
213
204
|
}
|
|
214
|
-
|
|
215
|
-
switch (
|
|
216
|
-
case "
|
|
217
|
-
case "
|
|
218
|
-
case "
|
|
219
|
-
case "
|
|
220
|
-
|
|
205
|
+
|
|
206
|
+
switch (terminalId) {
|
|
207
|
+
case "kitty":
|
|
208
|
+
case "ghostty":
|
|
209
|
+
case "wezterm":
|
|
210
|
+
case "iterm2":
|
|
211
|
+
case "alacritty":
|
|
212
|
+
case "vscode":
|
|
213
|
+
return true;
|
|
221
214
|
default:
|
|
222
|
-
|
|
215
|
+
// VTE family, GNU screen, Apple Terminal, legacy native console host
|
|
216
|
+
// (no WT_SESSION), and bare/unknown xterm profiles stay off until the
|
|
217
|
+
// DECRQM probe proves support.
|
|
218
|
+
return false;
|
|
223
219
|
}
|
|
224
|
-
return terminalId === "kitty" || terminalId === "ghostty" || terminalId === "wezterm" || terminalId === "iterm2";
|
|
225
220
|
}
|
|
226
221
|
|
|
227
222
|
/**
|
|
@@ -254,6 +249,83 @@ export function detectRectangularSgrSupport(terminalId: TerminalId, env: NodeJS.
|
|
|
254
249
|
}
|
|
255
250
|
return true;
|
|
256
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Resolve an explicit user override for OSC 8 hyperlinks. Returns `false` for
|
|
254
|
+
* an opt-out, `true` for a force-on, or `null` when the user has expressed no
|
|
255
|
+
* preference. Opt-out beats force-on so a kill switch is unambiguous, mirroring
|
|
256
|
+
* {@link synchronizedOutputUserOverride}.
|
|
257
|
+
*/
|
|
258
|
+
export function hyperlinksUserOverride(env: NodeJS.ProcessEnv = Bun.env): boolean | null {
|
|
259
|
+
if (env.PROMETHEUS_NO_HYPERLINKS === "1") return false;
|
|
260
|
+
if (env.PROMETHEUS_FORCE_HYPERLINKS === "1") return true;
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Parse tmux's self-reported version from `TERM_PROGRAM_VERSION`. tmux sets
|
|
266
|
+
* `TERM_PROGRAM=tmux` and `TERM_PROGRAM_VERSION=<version>` automatically since
|
|
267
|
+
* 3.2a; older releases (or any path that does not surface the version) yield
|
|
268
|
+
* `null` and the caller treats tmux conservatively.
|
|
269
|
+
*/
|
|
270
|
+
function parseTmuxVersionFromEnv(env: NodeJS.ProcessEnv): { major: number; minor: number } | null {
|
|
271
|
+
if (env.TERM_PROGRAM?.toLowerCase() !== "tmux") return null;
|
|
272
|
+
return parseMajorMinorVersion(env.TERM_PROGRAM_VERSION);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Whether OSC 8 hyperlinks should be enabled by default.
|
|
277
|
+
*
|
|
278
|
+
* Policy (highest precedence first):
|
|
279
|
+
* 1. Explicit user override (`PROMETHEUS_NO_HYPERLINKS=1` off, `PROMETHEUS_FORCE_HYPERLINKS=1`
|
|
280
|
+
* on). Opt-out wins ties.
|
|
281
|
+
* 2. Static terminal capability — terminals whose {@link TerminalInfo} marks
|
|
282
|
+
* `hyperlinks: false` (e.g. `base`) stay off unless the user forced on.
|
|
283
|
+
* 3. GNU screen's explicit session marker (`STY`) always off, even if tmux is
|
|
284
|
+
* also present: a screen layer anywhere in the path cannot forward OSC 8.
|
|
285
|
+
* 4. tmux session (`TMUX` set): enabled when tmux self-reports >= 3.4 via
|
|
286
|
+
* `TERM_PROGRAM_VERSION` (tmux 3.4 stores OSC 8 as a cell attribute and
|
|
287
|
+
* forwards it to outer terminals whose `terminal-features` include
|
|
288
|
+
* `hyperlinks`). Older or unknown versions stay off; on outer terminals
|
|
289
|
+
* without the feature configured, tmux silently drops the sequence —
|
|
290
|
+
* identical to today. Checked before the screen-family TERM heuristic
|
|
291
|
+
* because tmux's historical `default-terminal` is `screen-256color`, so
|
|
292
|
+
* `TERM=screen*` inside a tmux session must NOT short-circuit to off.
|
|
293
|
+
* 5. screen-family TERM without `TMUX` always off: screen never gained OSC 8
|
|
294
|
+
* support.
|
|
295
|
+
* 6. tmux-family TERM without `TMUX` env — unusual (e.g. inspection scripts);
|
|
296
|
+
* no version available, so off.
|
|
297
|
+
* 7. Otherwise honor the static terminal capability.
|
|
298
|
+
*/
|
|
299
|
+
export function shouldEnableHyperlinksByDefault(
|
|
300
|
+
env: NodeJS.ProcessEnv = Bun.env,
|
|
301
|
+
terminalId: TerminalId = TERMINAL_ID,
|
|
302
|
+
): boolean {
|
|
303
|
+
const override = hyperlinksUserOverride(env);
|
|
304
|
+
if (override !== null) return override;
|
|
305
|
+
|
|
306
|
+
if (!getTerminalInfo(terminalId).hyperlinks) return false;
|
|
307
|
+
|
|
308
|
+
// STY is GNU screen's explicit session marker. It vetoes tmux enabling when
|
|
309
|
+
// multiplexers are nested because screen cannot forward OSC 8 anywhere in the
|
|
310
|
+
// path.
|
|
311
|
+
if (env.STY) return false;
|
|
312
|
+
|
|
313
|
+
// tmux check before TERM heuristics: TMUX is the authoritative current-session
|
|
314
|
+
// signal and supersedes TERM, which may be `screen-256color` under tmux's
|
|
315
|
+
// historical default-terminal setting.
|
|
316
|
+
if (env.TMUX) {
|
|
317
|
+
const version = parseTmuxVersionFromEnv(env);
|
|
318
|
+
if (!version) return false;
|
|
319
|
+
return version.major > 3 || (version.major === 3 && version.minor >= 4);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const term = env.TERM?.toLowerCase() ?? "";
|
|
323
|
+
if (term.startsWith("screen")) return false;
|
|
324
|
+
if (term.startsWith("tmux")) return false;
|
|
325
|
+
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
|
|
257
329
|
function getFallbackImageProtocol(terminalId: TerminalId): ImageProtocol | null {
|
|
258
330
|
if (!process.stdout.isTTY) return null;
|
|
259
331
|
if (terminalId === "vscode" || terminalId === "alacritty") return null;
|
|
@@ -268,12 +340,12 @@ const KNOWN_TERMINALS = Object.freeze({
|
|
|
268
340
|
base: new TerminalInfo("base", null, false, false, NotifyProtocol.Bell),
|
|
269
341
|
trueColor: new TerminalInfo("trueColor", null, true, false, NotifyProtocol.Bell),
|
|
270
342
|
// Recognized terminals
|
|
271
|
-
kitty: new TerminalInfo("kitty", ImageProtocol.Kitty, true, true, NotifyProtocol.Osc99, true, true, true
|
|
272
|
-
ghostty: new TerminalInfo("ghostty", ImageProtocol.Kitty, true, true, NotifyProtocol.Osc9
|
|
273
|
-
wezterm: new TerminalInfo("wezterm", ImageProtocol.Kitty, true, true, NotifyProtocol.Osc9
|
|
274
|
-
iterm2: new TerminalInfo("iterm2", ImageProtocol.Iterm2, true, true, NotifyProtocol.Osc9
|
|
343
|
+
kitty: new TerminalInfo("kitty", ImageProtocol.Kitty, true, true, NotifyProtocol.Osc99, true, true, true),
|
|
344
|
+
ghostty: new TerminalInfo("ghostty", ImageProtocol.Kitty, true, true, NotifyProtocol.Osc9),
|
|
345
|
+
wezterm: new TerminalInfo("wezterm", ImageProtocol.Kitty, true, true, NotifyProtocol.Osc9),
|
|
346
|
+
iterm2: new TerminalInfo("iterm2", ImageProtocol.Iterm2, true, true, NotifyProtocol.Osc9),
|
|
275
347
|
vscode: new TerminalInfo("vscode", null, true, true, NotifyProtocol.Bell),
|
|
276
|
-
alacritty: new TerminalInfo("alacritty", null, true, true, NotifyProtocol.Bell
|
|
348
|
+
alacritty: new TerminalInfo("alacritty", null, true, true, NotifyProtocol.Bell),
|
|
277
349
|
});
|
|
278
350
|
|
|
279
351
|
export const TERMINAL_ID: TerminalId = (() => {
|
|
@@ -317,65 +389,43 @@ export const TERMINAL_ID: TerminalId = (() => {
|
|
|
317
389
|
return "base";
|
|
318
390
|
})();
|
|
319
391
|
|
|
320
|
-
/**
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
base.notifyProtocol,
|
|
337
|
-
overrides.eagerEraseScrollbackRisk !== undefined
|
|
338
|
-
? overrides.eagerEraseScrollbackRisk
|
|
339
|
-
: base.eagerEraseScrollbackRisk,
|
|
340
|
-
overrides.deccara !== undefined ? overrides.deccara : base.deccara,
|
|
341
|
-
overrides.supportsScreenToScrollback !== undefined
|
|
342
|
-
? overrides.supportsScreenToScrollback
|
|
343
|
-
: base.supportsScreenToScrollback,
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
export const TERMINAL = (() => {
|
|
348
|
-
let resolved = getTerminalInfo(TERMINAL_ID);
|
|
349
|
-
const eagerEraseScrollbackRisk = detectTerminalEagerEraseScrollbackRisk(Bun.env, process.platform);
|
|
350
|
-
if (resolved.eagerEraseScrollbackRisk !== eagerEraseScrollbackRisk) {
|
|
351
|
-
resolved = withTerminalOverrides(resolved, { eagerEraseScrollbackRisk });
|
|
352
|
-
}
|
|
392
|
+
/**
|
|
393
|
+
* The process-wide {@link TERMINAL} singleton: a {@link TerminalInfo} whose
|
|
394
|
+
* post-construction capabilities — the image protocol and the probe-driven
|
|
395
|
+
* flags — are writable, so the runtime setters and tests mutate them directly
|
|
396
|
+
* instead of through an unsound cast. Every other field stays readonly.
|
|
397
|
+
*/
|
|
398
|
+
export interface RuntimeTerminal extends TerminalInfo {
|
|
399
|
+
imageProtocol: ImageProtocol | null;
|
|
400
|
+
hyperlinks: boolean;
|
|
401
|
+
deccara: boolean;
|
|
402
|
+
supportsScreenToScrollback: boolean;
|
|
403
|
+
textSizing: boolean;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export const TERMINAL: RuntimeTerminal = (() => {
|
|
407
|
+
const resolved = getTerminalInfo(TERMINAL_ID).clone();
|
|
353
408
|
|
|
354
409
|
const forcedImageProtocol = getForcedImageProtocol();
|
|
355
410
|
if (forcedImageProtocol !== undefined) {
|
|
356
|
-
resolved =
|
|
411
|
+
resolved.imageProtocol = forcedImageProtocol;
|
|
357
412
|
} else if (!resolved.imageProtocol) {
|
|
358
413
|
const fallbackImageProtocol = getFallbackImageProtocol(resolved.id);
|
|
359
|
-
if (fallbackImageProtocol)
|
|
360
|
-
resolved = withTerminalOverrides(resolved, { imageProtocol: fallbackImageProtocol });
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
// tmux and screen multiplexers do not reliably forward OSC 8 hyperlinks
|
|
364
|
-
// to the outer terminal, so force them off regardless of detected terminal.
|
|
365
|
-
const term = Bun.env.TERM?.toLowerCase() ?? "";
|
|
366
|
-
if (resolved.hyperlinks && (Bun.env.TMUX || term.startsWith("tmux") || term.startsWith("screen"))) {
|
|
367
|
-
resolved = withTerminalOverrides(resolved, { hyperlinks: false });
|
|
414
|
+
if (fallbackImageProtocol) resolved.imageProtocol = fallbackImageProtocol;
|
|
368
415
|
}
|
|
416
|
+
// Hyperlink (OSC 8) capability. The static per-terminal flag lives on
|
|
417
|
+
// KNOWN_TERMINALS; shouldEnableHyperlinksByDefault folds in runtime context —
|
|
418
|
+
// PROMETHEUS_FORCE_HYPERLINKS / PROMETHEUS_NO_HYPERLINKS overrides plus a tmux>=3.4 gate so
|
|
419
|
+
// modern tmux forwards OSC 8 to outer terminals that opt in via
|
|
420
|
+
// `terminal-features "*:hyperlinks"`.
|
|
421
|
+
resolved.hyperlinks = shouldEnableHyperlinksByDefault(Bun.env, resolved.id);
|
|
369
422
|
// DECCARA rectangular-SGR background fills. The static per-terminal capability
|
|
370
423
|
// lives on KNOWN_TERMINALS; here we fold in runtime context — multiplexer and
|
|
371
424
|
// the PROMETHEUS_NO_DECCARA kill switch via detectRectangularSgrSupport — and force it
|
|
372
425
|
// off inside the test runtime so the xterm.js-backed virtual terminal (which
|
|
373
426
|
// ignores DECCARA) exercises the padded-string fallback. Integration tests opt
|
|
374
427
|
// in explicitly through setTerminalDeccara.
|
|
375
|
-
|
|
376
|
-
if (resolved.deccara !== deccara) {
|
|
377
|
-
resolved = withTerminalOverrides(resolved, { deccara });
|
|
378
|
-
}
|
|
428
|
+
resolved.deccara = detectRectangularSgrSupport(resolved.id, Bun.env) && !isBunTestRuntime();
|
|
379
429
|
return resolved;
|
|
380
430
|
})();
|
|
381
431
|
|
|
@@ -385,18 +435,11 @@ export const TERMINAL = (() => {
|
|
|
385
435
|
// glyphs, which is the "ASCII artifact + laggy scrolling" reported in #1877.
|
|
386
436
|
setKittyGraphics({ unicodePlaceholders: detectKittyUnicodePlaceholdersSupport(TERMINAL.id, Bun.env) });
|
|
387
437
|
|
|
388
|
-
type MutableTerminalInfo = {
|
|
389
|
-
imageProtocol: ImageProtocol | null;
|
|
390
|
-
deccara: boolean;
|
|
391
|
-
supportsScreenToScrollback: boolean;
|
|
392
|
-
textSizing: boolean;
|
|
393
|
-
};
|
|
394
|
-
|
|
395
438
|
/**
|
|
396
439
|
* Override terminal image protocol at runtime after capability probes complete.
|
|
397
440
|
*/
|
|
398
441
|
export function setTerminalImageProtocol(imageProtocol: ImageProtocol | null): void {
|
|
399
|
-
|
|
442
|
+
TERMINAL.imageProtocol = imageProtocol;
|
|
400
443
|
}
|
|
401
444
|
|
|
402
445
|
/**
|
|
@@ -405,12 +448,12 @@ export function setTerminalImageProtocol(imageProtocol: ImageProtocol | null): v
|
|
|
405
448
|
* resolved once at import and force-disabled under the test runtime.
|
|
406
449
|
*/
|
|
407
450
|
export function setTerminalDeccara(enabled: boolean): void {
|
|
408
|
-
|
|
451
|
+
TERMINAL.deccara = enabled;
|
|
409
452
|
}
|
|
410
453
|
|
|
411
454
|
/** Override screen-to-scrollback clear support for targeted renderer tests. */
|
|
412
455
|
export function setTerminalScreenToScrollback(enabled: boolean): void {
|
|
413
|
-
|
|
456
|
+
TERMINAL.supportsScreenToScrollback = enabled;
|
|
414
457
|
}
|
|
415
458
|
|
|
416
459
|
/**
|
|
@@ -419,7 +462,7 @@ export function setTerminalScreenToScrollback(enabled: boolean): void {
|
|
|
419
462
|
* capability); tests flip it directly to exercise the scaled-heading path.
|
|
420
463
|
*/
|
|
421
464
|
export function setTerminalTextSizing(enabled: boolean): void {
|
|
422
|
-
|
|
465
|
+
TERMINAL.textSizing = enabled;
|
|
423
466
|
}
|
|
424
467
|
|
|
425
468
|
export function getTerminalInfo(terminalId: TerminalId): TerminalInfo {
|
|
@@ -772,16 +815,11 @@ export function renderImage(
|
|
|
772
815
|
if (options.imageId != null) {
|
|
773
816
|
const placementId = options.placementId ?? options.imageId;
|
|
774
817
|
const graphics = getKittyGraphics();
|
|
775
|
-
// Transmit-once (keyed by id).
|
|
776
|
-
//
|
|
777
|
-
// the stored image, so the transmit is only emitted when requested.
|
|
818
|
+
// Transmit-once (keyed by id). Repaints reuse the stored image, so the
|
|
819
|
+
// transmit is only emitted when requested.
|
|
778
820
|
let transmit: string | undefined;
|
|
779
821
|
if (options.includeTransmit) {
|
|
780
|
-
|
|
781
|
-
graphics.transmissionMedium === "temp-file" && isPngBase64(base64Data)
|
|
782
|
-
? encodeKittyTempFileTransmit(base64Data, options.imageId)
|
|
783
|
-
: null;
|
|
784
|
-
transmit = tempFile ?? encodeKittyTransmit(base64Data, options.imageId);
|
|
822
|
+
transmit = encodeKittyTransmit(base64Data, options.imageId);
|
|
785
823
|
}
|
|
786
824
|
// Unicode placeholders render the image as real text cells (which survive
|
|
787
825
|
// horizontal slicing, reflow and overlaps) instead of a cursor-positioned
|