@oh-my-pi/pi-tui 18.5.0 → 18.5.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 +30 -11
- package/dist/types/active-terminal.d.ts +30 -0
- package/dist/types/components/markdown.d.ts +13 -4
- package/dist/types/native/encode.d.ts +1 -0
- package/dist/types/native/node.d.ts +6 -0
- package/dist/types/overlays/hub-frame.d.ts +4 -1
- package/dist/types/prompt/custom-editor.d.ts +2 -1
- package/dist/types/terminal-capabilities.d.ts +1 -1
- package/dist/types/terminal.d.ts +1 -10
- package/dist/types/tools/bash.d.ts +7 -2
- package/dist/types/windows-input-mode.d.ts +12 -0
- package/package.json +9 -9
- package/src/active-terminal.ts +80 -0
- package/src/bracketed-paste.ts +10 -3
- package/src/components/markdown.ts +473 -105
- package/src/kitty-graphics.ts +7 -1
- package/src/native/backend.ts +11 -1
- package/src/native/encode.ts +13 -1
- package/src/native/node.ts +3 -1
- package/src/overlays/ask-dialog.ts +3 -7
- package/src/overlays/hub-frame.ts +19 -1
- package/src/overlays/model-browser.ts +30 -16
- package/src/overlays/model-hub.ts +17 -4
- package/src/prompt/custom-editor.ts +31 -10
- package/src/terminal-capabilities.ts +22 -3
- package/src/terminal.ts +34 -60
- package/src/theme/symbols.ts +5 -3
- package/src/tools/bash.ts +24 -5
- package/src/tui.ts +283 -53
- package/src/windows-input-mode.ts +110 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.5.1] - 2026-10-03
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added native terminal support for submitting explicit composer prompts atomically, preserving displaced drafts and attachments for local recall.
|
|
10
|
+
- Added progress percentages to subagent entries in the agent tree, task, and wait views.
|
|
11
|
+
- Added a `Rebuilding…` indicator for lengthy tmux resize redraws without flashing it for quick updates.
|
|
12
|
+
- Added `writeTerminalSequence()` for sending supported terminal escape sequences through the active terminal output stream, with stdout fallback when no terminal is active.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Clipboard payloads are now represented by their length in `PI_TUI_WRITE_LOG` rather than by their contents.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Fixed pending clipboard-image attachments being bypassed when subsequent input or native prompts were submitted before loading completed.
|
|
21
|
+
- Improved narrow `/models` layouts by retaining the scope sidebar while simplifying and truncating model rows as needed.
|
|
22
|
+
- Fixed Markdown rendering with themes that do not define their own symbol set; the active theme's symbols are now used as a fallback.
|
|
23
|
+
- Fixed model role assignment showing models that are unavailable for the selected provider.
|
|
24
|
+
- Improved tmux resize, zoom, and rebuild behavior, including smoother coalesced redraws, reduced visual artifacts and extra output, correct transcript restoration after rapid size changes, and preservation of history when panes are resized.
|
|
25
|
+
- Fixed multiline paste on Windows inserting escape-code fragments instead of line breaks, and prevented bracketed-paste markers from appearing in the composer.
|
|
26
|
+
- Fixed terminal notifications occasionally corrupting the screen with stray escape-code fragments during streaming output.
|
|
27
|
+
- Added rendering and notification compatibility for Monstar terminals, including Kitty graphics, hyperlinks, synchronized output, styled underlines, progress keepalives, and Monstar-focused desktop notifications.
|
|
28
|
+
- Fixed Agent Hub transcripts crashing when assistant messages do not include usage or cost data.
|
|
29
|
+
- Fixed idle terminal activity notifications while preserving bracketed-paste recovery during input and rendering.
|
|
30
|
+
- Fixed diff blocks in ask questions losing their layout; additions and removals now retain separate diff highlighting.
|
|
31
|
+
- Fixed duplicate ASCII Plan, Prewalk, and Goal labels in the status line.
|
|
32
|
+
- Improved Markdown rendering for long and streamed messages, including display math, fenced code, reference links, lists, whitespace, emphasis, and line breaks, so completed content no longer shows raw markup, misplaced blank lines, or broken block layout.
|
|
33
|
+
|
|
5
34
|
## [18.5.0] - 2026-10-03
|
|
6
35
|
|
|
7
36
|
### Breaking Changes
|
|
@@ -2988,14 +3017,4 @@ Initial release under @oh-my-pi scope. See previous releases at [badlogic/pi-mon
|
|
|
2988
3017
|
- Crash in `visibleWidth()` and grapheme iteration when encountering undefined code points ([#372](https://github.com/badlogic/pi-mono/pull/372) by [@HACKE-RC](https://github.com/HACKE-RC))
|
|
2989
3018
|
- ZWJ emoji sequences (rainbow flag, family, etc.) now render with correct width instead of being split into multiple characters ([#369](https://github.com/badlogic/pi-mono/pull/369) by [@nathyong](https://github.com/nathyong))
|
|
2990
3019
|
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
### Added
|
|
2994
|
-
|
|
2995
|
-
- **Auto-space before pasted file paths**: When pasting a file path (starting with `/`, `~`, or `.`) and the cursor is after a word character, a space is automatically prepended for better readability. Useful when dragging screenshots from macOS. ([#307](https://github.com/badlogic/pi-mono/pull/307) by [@mitsuhiko](https://github.com/mitsuhiko))
|
|
2996
|
-
- **Word navigation for Input component**: Added Ctrl+Left/Right and Alt+Left/Right support for word-by-word cursor movement. ([#306](https://github.com/badlogic/pi-mono/pull/306) by [@kim0](https://github.com/kim0))
|
|
2997
|
-
- **Full Unicode input**: Input component now accepts Unicode characters beyond ASCII. ([#306](https://github.com/badlogic/pi-mono/pull/306) by [@kim0](https://github.com/kim0))
|
|
2998
|
-
|
|
2999
|
-
### Fixed
|
|
3000
|
-
|
|
3001
|
-
- **Readline-style Ctrl+W**: Now skips trailing whitespace before deleting the preceding word, matching standard readline behavior. ([#306](https://github.com/badlogic/pi-mono/pull/306) by [@kim0](https://github.com/kim0))
|
|
3020
|
+
Older entries are archived in [packages/tui/CHANGELOG.md@bac7e83b5b0e](https://github.com/can1357/oh-my-pi/blob/bac7e83b5b0eb86c909c17830a6666efc359578b/packages/tui/CHANGELOG.md).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ownership of stdout by a started {@link ProcessTerminal}, kept in a leaf
|
|
3
|
+
* module so writers that `terminal.ts` itself imports (terminal capabilities,
|
|
4
|
+
* notifications) can route through the active terminal without an import cycle.
|
|
5
|
+
*/
|
|
6
|
+
import type { ProcessTerminal } from "./terminal.js";
|
|
7
|
+
/** The started terminal that owns stdout in this thread, if any. */
|
|
8
|
+
export declare function getActiveTerminal(): ProcessTerminal | null;
|
|
9
|
+
/** Record the terminal that owns stdout (`ProcessTerminal.start`), or release it with `null` (`stop`). */
|
|
10
|
+
export declare function setActiveTerminal(terminal: ProcessTerminal | null): void;
|
|
11
|
+
/** Observe stdout errors through the shared listener. Returns the unregister callback. */
|
|
12
|
+
export declare function registerStdoutErrorHandler(handler: (err: Error) => void): () => void;
|
|
13
|
+
/**
|
|
14
|
+
* Hand `data` to the active terminal's ordered output path. Returns false when
|
|
15
|
+
* no terminal in this thread has started. On the main thread that means the
|
|
16
|
+
* caller owns stdout; a worker thread never sees the main thread's terminal, so
|
|
17
|
+
* there `false` is not permission to write. Prefer {@link writeTerminalSequence}.
|
|
18
|
+
*/
|
|
19
|
+
export declare function writeThroughActiveTerminal(data: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Write an out-of-band escape sequence (window title, OSC 52 clipboard,
|
|
22
|
+
* notification). While a TUI owns stdout, its frames go through an off-thread
|
|
23
|
+
* pump in chunks that ignore escape boundaries; a direct `process.stdout.write`
|
|
24
|
+
* can land between two chunks and split a frame's escape sequence, and the host
|
|
25
|
+
* terminal then prints its tail as text. So the sequence takes the active
|
|
26
|
+
* terminal's ordered output path, and goes straight to stdout, best-effort, only
|
|
27
|
+
* when no terminal owns it. A worker thread cannot reach the main thread's
|
|
28
|
+
* terminal and would write around its pump, so it writes nothing.
|
|
29
|
+
*/
|
|
30
|
+
export declare function writeTerminalSequence(data: string): void;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { DescribeContext, NativeNode } from "../native/node.js";
|
|
2
2
|
import type { SymbolTheme } from "../symbols.js";
|
|
3
3
|
import type { Component } from "../tui.js";
|
|
4
|
-
/** @internal exported for tests — the grown-line
|
|
4
|
+
/** @internal exported for tests — the grown-line block-kind gate. */
|
|
5
5
|
export declare function fastLineStartHazard(grownLine: string): boolean;
|
|
6
|
+
/** @internal exported for tests — the windowed lexer's first-probe window size. */
|
|
7
|
+
export declare const LEX_WINDOW_BYTES: number;
|
|
6
8
|
/** A hyperlink as the renderer sees it: inline `[text](href)`, `<autolink>`, bare GFM URL, or reference link. */
|
|
7
9
|
export interface MarkdownLink {
|
|
8
10
|
/** Flattened visible label with whitespace collapsed to one row; falls back to `href` when empty. */
|
|
@@ -82,7 +84,12 @@ export interface MarkdownTheme {
|
|
|
82
84
|
* Return null to fall back to fenced code rendering.
|
|
83
85
|
*/
|
|
84
86
|
resolveMermaidAscii?: (source: string, maxWidth?: number) => string | null;
|
|
85
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Glyphs for quote borders, rules, tables and color swatches. Optional so themes
|
|
89
|
+
* built to the upstream pi-tui `MarkdownTheme` shape (which has no `symbols`)
|
|
90
|
+
* still render; omitted symbols fall back to the active theme's set.
|
|
91
|
+
*/
|
|
92
|
+
symbols?: SymbolTheme;
|
|
86
93
|
}
|
|
87
94
|
export declare class Markdown implements Component {
|
|
88
95
|
#private;
|
|
@@ -101,8 +108,10 @@ export declare class Markdown implements Component {
|
|
|
101
108
|
describe(_cx: DescribeContext): NativeNode;
|
|
102
109
|
/**
|
|
103
110
|
* Width-independent source prefix of the last render ending at a frozen
|
|
104
|
-
* Markdown block boundary
|
|
105
|
-
*
|
|
111
|
+
* Markdown block boundary that no append can move: it stops in front of a
|
|
112
|
+
* display-math opener whose block an append could still close. Only
|
|
113
|
+
* meaningful while streaming (transient render cache on); grows
|
|
114
|
+
* monotonically under append-only `setText`.
|
|
106
115
|
*/
|
|
107
116
|
getLastRenderStableText(): string;
|
|
108
117
|
get transientRenderCache(): boolean;
|
|
@@ -29,6 +29,7 @@ export declare function encodeTspJson(verb: TspVerb, value: unknown, params?: Ts
|
|
|
29
29
|
* tells the terminal that omp applies its `edit` events (TSP §8.5), so it may keep a
|
|
30
30
|
* native selection in omp's editors; without it, every key stays omp's. `"undo"`
|
|
31
31
|
* says omp applies `undo` events, so the terminal may turn ⌃Z in a field into one.
|
|
32
|
+
* `"send"` accepts an explicit prompt for a live composer without simulating keys.
|
|
32
33
|
*/
|
|
33
34
|
export declare function encodeTspHelloQuery(version?: string): string;
|
|
34
35
|
/** One decoded APC message: verb, parameters and raw body. */
|
|
@@ -135,6 +135,12 @@ export type NativeUiEvent = {
|
|
|
135
135
|
| {
|
|
136
136
|
readonly type: "undo";
|
|
137
137
|
readonly key: string;
|
|
138
|
+
}
|
|
139
|
+
/** Submit an explicit prompt through the composer's normal path, preserving the previous draft for recall. */
|
|
140
|
+
| {
|
|
141
|
+
readonly type: "send";
|
|
142
|
+
readonly key: string;
|
|
143
|
+
readonly text: string;
|
|
138
144
|
};
|
|
139
145
|
/**
|
|
140
146
|
* A primitive edit the terminal made over its own text selection (cut,
|
|
@@ -61,7 +61,10 @@ export declare class HubFrame {
|
|
|
61
61
|
constructor(title: string, sidebarBounds: {
|
|
62
62
|
min: number;
|
|
63
63
|
max: number;
|
|
64
|
-
}, renderSidebar: (width: number, rows: number) => string[], renderBody: (width: number, height: number | undefined) => readonly string[]
|
|
64
|
+
}, renderSidebar: (width: number, rows: number) => string[], renderBody: (width: number, height: number | undefined) => readonly string[], options?: {
|
|
65
|
+
bodyMinWidth?: number;
|
|
66
|
+
preserveSidebar?: boolean;
|
|
67
|
+
});
|
|
65
68
|
/** Invalidate the composed layout. */
|
|
66
69
|
invalidate(): void;
|
|
67
70
|
/** Measure labels and annotations within this hub's sidebar width bounds. */
|
|
@@ -237,7 +237,8 @@ export declare class CustomEditor extends Editor {
|
|
|
237
237
|
* Clicks on the composer's controls take the same paths as their keys: ⇧⇥,
|
|
238
238
|
* ⏎ and Esc; the viewing header's links (`focus:<id>`) go to the host,
|
|
239
239
|
* the status facts' clicks (`status.*`) to their source. Selection edits
|
|
240
|
-
* go to the buffer
|
|
240
|
+
* go to the buffer; `send` submits its own prompt after saving the old draft
|
|
241
|
+
* for recall and waiting for in-flight clipboard work.
|
|
241
242
|
*/
|
|
242
243
|
handleNativeEvent(event: NativeUiEvent): void;
|
|
243
244
|
onEscape?: () => void;
|
|
@@ -11,7 +11,7 @@ export declare enum NotifyProtocol {
|
|
|
11
11
|
Osc99 = "\u001B]99;;",
|
|
12
12
|
Osc9 = "\u001B]9;"
|
|
13
13
|
}
|
|
14
|
-
export type TerminalId = "kitty" | "ghostty" | "wezterm" | "iterm2" | "vscode" | "alacritty" | "warp" | "orca" | "otty" | "rio" | "tern" | "base" | "trueColor";
|
|
14
|
+
export type TerminalId = "kitty" | "ghostty" | "wezterm" | "iterm2" | "vscode" | "alacritty" | "warp" | "orca" | "otty" | "rio" | "tern" | "monstar" | "base" | "trueColor";
|
|
15
15
|
/** Terminal capability details used for rendering and protocol selection. */
|
|
16
16
|
export declare class TerminalInfo {
|
|
17
17
|
readonly id: TerminalId;
|
package/dist/types/terminal.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type TspHello } from "./native/encode.js";
|
|
2
|
+
export { writeTerminalSequence, writeThroughActiveTerminal } from "./active-terminal.js";
|
|
2
3
|
/**
|
|
3
4
|
* Split `data` into chunks whose encoded UTF-8 byte length is no greater than
|
|
4
5
|
* `maxChunkBytes`, preferring a line boundary (`\n`) as the cut point so
|
|
@@ -69,16 +70,6 @@ export declare class StdoutStallWatchdog {
|
|
|
69
70
|
}
|
|
70
71
|
/** Record alternate-screen state (called by the TUI on `?1049h`/`?1049l` writes). */
|
|
71
72
|
export declare function setAltScreenActive(active: boolean): void;
|
|
72
|
-
/**
|
|
73
|
-
* Route an out-of-band escape sequence (e.g. an OSC title update) through the
|
|
74
|
-
* active terminal's output path. While a TUI owns stdout, frame paints go
|
|
75
|
-
* through the off-thread write pump and can split across multiple write(2)
|
|
76
|
-
* calls; a direct main-thread `process.stdout.write` can land between two of
|
|
77
|
-
* them — mid escape sequence — and the host terminal then prints the payload
|
|
78
|
-
* as literal text at the cursor position. Returns false when no terminal has
|
|
79
|
-
* started, in which case the caller owns stdout and may write directly.
|
|
80
|
-
*/
|
|
81
|
-
export declare function writeThroughActiveTerminal(data: string): boolean;
|
|
82
73
|
/**
|
|
83
74
|
* Emergency terminal restore - call this from signal/crash handlers
|
|
84
75
|
* Resets terminal state without requiring access to the ProcessTerminal instance
|
|
@@ -4,8 +4,13 @@ import { type OutputMeta } from "./output-meta.js";
|
|
|
4
4
|
import type { NativeToolView, RenderResultContextOptions, RenderResultOptions } from "./renderer.js";
|
|
5
5
|
/** Default collapsed shell output preview height. */
|
|
6
6
|
export declare const BASH_DEFAULT_PREVIEW_LINES = 10;
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* LLM-facing footer appended when a tool call becomes a background job. It states the job's kill
|
|
9
|
+
* deadline (`timeoutSec`, `undefined` when disabled) so the model knows the job will die at it before
|
|
10
|
+
* it waits on the result. The deadline counts the job's whole run time, not time left from now: an
|
|
11
|
+
* auto-backgrounded call has already spent its foreground wait.
|
|
12
|
+
*/
|
|
13
|
+
export declare function formatBackgroundNotice(jobId: string, timeoutSec: number | undefined): string;
|
|
9
14
|
/** Shell execution metadata used by transcript rendering. */
|
|
10
15
|
export interface BashToolDetails {
|
|
11
16
|
meta?: OutputMeta;
|
|
@@ -35,3 +35,15 @@ export declare class Win32InputModeDecoder {
|
|
|
35
35
|
*/
|
|
36
36
|
decodePaste(content: string): string;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Restores paste delimiters that a Windows console host emitted as individual
|
|
40
|
+
* win32-input-mode key records before StdinBuffer searches for the delimiters.
|
|
41
|
+
* Non-matching records remain untouched for normal key and paste decoding.
|
|
42
|
+
*/
|
|
43
|
+
export declare class Win32PasteMarkerNormalizer {
|
|
44
|
+
#private;
|
|
45
|
+
constructor(onInput: (data: string) => void);
|
|
46
|
+
process(data: string): void;
|
|
47
|
+
/** Release an incomplete marker as its original key records. */
|
|
48
|
+
flush(): void;
|
|
49
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-tui",
|
|
4
|
-
"version": "18.5.
|
|
4
|
+
"version": "18.5.1",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": {
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
"gen:glyphs": "bun scripts/gen-glyphs.ts"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@oh-my-pi/omptype": "18.5.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "18.5.
|
|
46
|
-
"@oh-my-pi/pi-ai": "18.5.
|
|
47
|
-
"@oh-my-pi/pi-catalog": "18.5.
|
|
48
|
-
"@oh-my-pi/pi-natives": "18.5.
|
|
49
|
-
"@oh-my-pi/pi-utils": "18.5.
|
|
50
|
-
"@oh-my-pi/pi-wire": "18.5.
|
|
51
|
-
"@oh-my-pi/snapcompact": "18.5.
|
|
44
|
+
"@oh-my-pi/omptype": "18.5.1",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "18.5.1",
|
|
46
|
+
"@oh-my-pi/pi-ai": "18.5.1",
|
|
47
|
+
"@oh-my-pi/pi-catalog": "18.5.1",
|
|
48
|
+
"@oh-my-pi/pi-natives": "18.5.1",
|
|
49
|
+
"@oh-my-pi/pi-utils": "18.5.1",
|
|
50
|
+
"@oh-my-pi/pi-wire": "18.5.1",
|
|
51
|
+
"@oh-my-pi/snapcompact": "18.5.1"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"kitty-vt-wasm": "^0.2.0"
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ownership of stdout by a started {@link ProcessTerminal}, kept in a leaf
|
|
3
|
+
* module so writers that `terminal.ts` itself imports (terminal capabilities,
|
|
4
|
+
* notifications) can route through the active terminal without an import cycle.
|
|
5
|
+
*/
|
|
6
|
+
import type { ProcessTerminal } from "./terminal";
|
|
7
|
+
|
|
8
|
+
// The started terminal that owns stdout in this thread; also consulted by the
|
|
9
|
+
// emergency restore on crash.
|
|
10
|
+
let activeTerminal: ProcessTerminal | null = null;
|
|
11
|
+
|
|
12
|
+
/** The started terminal that owns stdout in this thread, if any. */
|
|
13
|
+
export function getActiveTerminal(): ProcessTerminal | null {
|
|
14
|
+
return activeTerminal;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Record the terminal that owns stdout (`ProcessTerminal.start`), or release it with `null` (`stop`). */
|
|
18
|
+
export function setActiveTerminal(terminal: ProcessTerminal | null): void {
|
|
19
|
+
activeTerminal = terminal;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const stdoutErrorHandlers = new Set<(err: Error) => void>();
|
|
23
|
+
let stdoutErrorListenerInstalled = false;
|
|
24
|
+
|
|
25
|
+
function onStdoutError(err: Error): void {
|
|
26
|
+
for (const handler of stdoutErrorHandlers) handler(err);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Install the one shared `process.stdout` `error` listener. Its presence alone
|
|
31
|
+
* keeps a stdout error (EPIPE on a closed pipe, EIO on a revoked PTY) from
|
|
32
|
+
* crashing the process as an unhandled `error` event.
|
|
33
|
+
*/
|
|
34
|
+
function installStdoutErrorListener(): void {
|
|
35
|
+
if (stdoutErrorListenerInstalled) return;
|
|
36
|
+
process.stdout.on("error", onStdoutError);
|
|
37
|
+
stdoutErrorListenerInstalled = true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Observe stdout errors through the shared listener. Returns the unregister callback. */
|
|
41
|
+
export function registerStdoutErrorHandler(handler: (err: Error) => void): () => void {
|
|
42
|
+
stdoutErrorHandlers.add(handler);
|
|
43
|
+
installStdoutErrorListener();
|
|
44
|
+
return () => {
|
|
45
|
+
stdoutErrorHandlers.delete(handler);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Hand `data` to the active terminal's ordered output path. Returns false when
|
|
51
|
+
* no terminal in this thread has started. On the main thread that means the
|
|
52
|
+
* caller owns stdout; a worker thread never sees the main thread's terminal, so
|
|
53
|
+
* there `false` is not permission to write. Prefer {@link writeTerminalSequence}.
|
|
54
|
+
*/
|
|
55
|
+
export function writeThroughActiveTerminal(data: string): boolean {
|
|
56
|
+
if (!activeTerminal) return false;
|
|
57
|
+
activeTerminal.write(data);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Write an out-of-band escape sequence (window title, OSC 52 clipboard,
|
|
63
|
+
* notification). While a TUI owns stdout, its frames go through an off-thread
|
|
64
|
+
* pump in chunks that ignore escape boundaries; a direct `process.stdout.write`
|
|
65
|
+
* can land between two chunks and split a frame's escape sequence, and the host
|
|
66
|
+
* terminal then prints its tail as text. So the sequence takes the active
|
|
67
|
+
* terminal's ordered output path, and goes straight to stdout, best-effort, only
|
|
68
|
+
* when no terminal owns it. A worker thread cannot reach the main thread's
|
|
69
|
+
* terminal and would write around its pump, so it writes nothing.
|
|
70
|
+
*/
|
|
71
|
+
export function writeTerminalSequence(data: string): void {
|
|
72
|
+
if (!Bun.isMainThread) return;
|
|
73
|
+
if (writeThroughActiveTerminal(data)) return;
|
|
74
|
+
installStdoutErrorListener();
|
|
75
|
+
try {
|
|
76
|
+
process.stdout.write(data);
|
|
77
|
+
} catch {
|
|
78
|
+
// Best-effort: a closed stdout must not fail the caller.
|
|
79
|
+
}
|
|
80
|
+
}
|
package/src/bracketed-paste.ts
CHANGED
|
@@ -85,10 +85,17 @@ export class BracketedPasteHandler {
|
|
|
85
85
|
* buffer); omitted when still buffering.
|
|
86
86
|
*/
|
|
87
87
|
process(data: string): PasteResult {
|
|
88
|
-
|
|
88
|
+
const start = data.indexOf(PASTE_START);
|
|
89
|
+
const end = data.indexOf(PASTE_END);
|
|
90
|
+
if (start !== -1 && (end === -1 || start < end)) {
|
|
91
|
+
if (!this.#active) this.#buffer = "";
|
|
89
92
|
this.#active = true;
|
|
90
|
-
this
|
|
91
|
-
|
|
93
|
+
// Only strip nested starts before this paste's first end. Later starts
|
|
94
|
+
// belong to the next paste in `remaining`.
|
|
95
|
+
data =
|
|
96
|
+
end === -1
|
|
97
|
+
? data.replaceAll(PASTE_START, "")
|
|
98
|
+
: data.slice(0, end).replaceAll(PASTE_START, "") + data.slice(end);
|
|
92
99
|
}
|
|
93
100
|
|
|
94
101
|
if (!this.#active) return { handled: false };
|