@oh-my-pi/pi-tui 18.5.0 → 18.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -11
- package/dist/types/active-terminal.d.ts +30 -0
- package/dist/types/components/editor.d.ts +3 -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/overlays/model-hub.d.ts +14 -0
- package/dist/types/prompt/custom-editor.d.ts +4 -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 +33 -1
- package/package.json +9 -9
- package/src/active-terminal.ts +80 -0
- package/src/bracketed-paste.ts +10 -3
- package/src/components/editor.ts +6 -0
- package/src/components/input.ts +1 -0
- 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 +137 -12
- package/src/prompt/custom-editor.ts +35 -10
- package/src/terminal-capabilities.ts +22 -3
- package/src/terminal.ts +64 -62
- 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 +204 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.6.0] - 2026-10-03
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed Alt+Up (restore queued steering), arrows and other escape-sequence keys on Windows acting as Esc (interrupting the turn) and typing `[1;3A` into the editor when the console host relays them one byte at a time ([#14216](https://github.com/can1357/oh-my-pi/pull/14216) by [@H4vC](https://github.com/H4vC)).
|
|
10
|
+
|
|
11
|
+
## [18.5.1] - 2026-10-03
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Added native terminal support for submitting explicit composer prompts atomically, preserving displaced drafts and attachments for local recall.
|
|
16
|
+
- Added progress percentages to subagent entries in the agent tree, task, and wait views.
|
|
17
|
+
- Added a `Rebuilding…` indicator for lengthy tmux resize redraws without flashing it for quick updates.
|
|
18
|
+
- Added `writeTerminalSequence()` for sending supported terminal escape sequences through the active terminal output stream, with stdout fallback when no terminal is active.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Clipboard payloads are now represented by their length in `PI_TUI_WRITE_LOG` rather than by their contents.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Fixed pending clipboard-image attachments being bypassed when subsequent input or native prompts were submitted before loading completed.
|
|
27
|
+
- Improved narrow `/models` layouts by retaining the scope sidebar while simplifying and truncating model rows as needed.
|
|
28
|
+
- Fixed Markdown rendering with themes that do not define their own symbol set; the active theme's symbols are now used as a fallback.
|
|
29
|
+
- Fixed model role assignment showing models that are unavailable for the selected provider.
|
|
30
|
+
- 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.
|
|
31
|
+
- Fixed multiline paste on Windows inserting escape-code fragments instead of line breaks, and prevented bracketed-paste markers from appearing in the composer.
|
|
32
|
+
- Fixed terminal notifications occasionally corrupting the screen with stray escape-code fragments during streaming output.
|
|
33
|
+
- Added rendering and notification compatibility for Monstar terminals, including Kitty graphics, hyperlinks, synchronized output, styled underlines, progress keepalives, and Monstar-focused desktop notifications.
|
|
34
|
+
- Fixed Agent Hub transcripts crashing when assistant messages do not include usage or cost data.
|
|
35
|
+
- Fixed idle terminal activity notifications while preserving bracketed-paste recovery during input and rendering.
|
|
36
|
+
- Fixed diff blocks in ask questions losing their layout; additions and removals now retain separate diff highlighting.
|
|
37
|
+
- Fixed duplicate ASCII Plan, Prewalk, and Goal labels in the status line.
|
|
38
|
+
- 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.
|
|
39
|
+
|
|
5
40
|
## [18.5.0] - 2026-10-03
|
|
6
41
|
|
|
7
42
|
### Breaking Changes
|
|
@@ -2988,14 +3023,4 @@ Initial release under @oh-my-pi scope. See previous releases at [badlogic/pi-mon
|
|
|
2988
3023
|
- 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
3024
|
- 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
3025
|
|
|
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))
|
|
3026
|
+
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;
|
|
@@ -138,6 +138,9 @@ export declare class Editor implements Component, Focusable {
|
|
|
138
138
|
onLargePaste?: (text: string, lineCount: number, options: PasteOptions) => boolean;
|
|
139
139
|
onAutocompleteCancel?: () => void;
|
|
140
140
|
disableSubmit: boolean;
|
|
141
|
+
/** Base editors accept native edits, not atomic sends. Implementations that
|
|
142
|
+
* handle `send` override this readiness check for their submission path. */
|
|
143
|
+
protected get nativeSendable(): boolean;
|
|
141
144
|
/** Placeholder painted right-aligned on the cursor row while the editor is empty and no
|
|
142
145
|
* autocomplete is open; hidden when it can't keep {@link PLACEHOLDER_MIN_GAP} cells from the
|
|
143
146
|
* cursor. The host styles it (ANSI allowed). Re-evaluated on every render, so hosts can derive
|
|
@@ -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. */
|
|
@@ -12,6 +12,14 @@ export interface ModelHubSource extends ModelBrowserSource {
|
|
|
12
12
|
getProjectModelRole(role: string): string | undefined;
|
|
13
13
|
getGlobalModelRole(role: string): string | undefined;
|
|
14
14
|
getModelRoleSource(role: string): "global" | "project" | "default";
|
|
15
|
+
/**
|
|
16
|
+
* Saved model presets (in switch order) and the one the current role setup
|
|
17
|
+
* matches, if any. Absent hosts have no presets to switch between.
|
|
18
|
+
*/
|
|
19
|
+
getModelPresets?(): {
|
|
20
|
+
names: readonly string[];
|
|
21
|
+
active: string | undefined;
|
|
22
|
+
};
|
|
15
23
|
}
|
|
16
24
|
/** Catalog capabilities required by the model hub. */
|
|
17
25
|
export interface ModelHubRegistry extends ModelBrowserRegistry {
|
|
@@ -48,6 +56,12 @@ export interface ModelHubCallbacks {
|
|
|
48
56
|
onLoginRequest?: (providerId: string) => void;
|
|
49
57
|
/** Save the current role assignments and default thinking level as a named model preset. */
|
|
50
58
|
onSavePreset?: (name: string) => void;
|
|
59
|
+
/**
|
|
60
|
+
* Apply a saved model preset (ctrl+←/→ or p/⇧P in the Roles view) and report
|
|
61
|
+
* the outcome itself. A switch that writes nothing (a refused preset) still
|
|
62
|
+
* advances the hub's preset cursor, so the next press moves past it.
|
|
63
|
+
*/
|
|
64
|
+
onSwitchPreset?: (name: string) => void | Promise<void>;
|
|
51
65
|
/** Persist a new quick-switch cycle order (the ctrl+p role cycle). */
|
|
52
66
|
onCycleOrderChange?: (order: string[]) => void;
|
|
53
67
|
onCancel: () => void;
|
|
@@ -213,6 +213,8 @@ export declare class CustomEditor extends Editor {
|
|
|
213
213
|
* once after construction (and again after `setEditorComponent` swaps the
|
|
214
214
|
* editor). Passing `undefined` clears any pending frame. */
|
|
215
215
|
setShimmerRepaintHandler(handler: (() => void) | undefined): void;
|
|
216
|
+
/** Editing is available during bootstrap; atomic sends wait until submission is wired and enabled. */
|
|
217
|
+
protected get nativeSendable(): boolean;
|
|
216
218
|
/** Viewing a subagent, the draft goes to it: the placeholder names it. */
|
|
217
219
|
describePlaceholder: () => string;
|
|
218
220
|
/** A shell-mode draft highlights as its language. */
|
|
@@ -237,7 +239,8 @@ export declare class CustomEditor extends Editor {
|
|
|
237
239
|
* Clicks on the composer's controls take the same paths as their keys: ⇧⇥,
|
|
238
240
|
* ⏎ and Esc; the viewing header's links (`focus:<id>`) go to the host,
|
|
239
241
|
* the status facts' clicks (`status.*`) to their source. Selection edits
|
|
240
|
-
* go to the buffer
|
|
242
|
+
* go to the buffer; `send` submits its own prompt after saving the old draft
|
|
243
|
+
* for recall and waiting for in-flight clipboard work.
|
|
241
244
|
*/
|
|
242
245
|
handleNativeEvent(event: NativeUiEvent): void;
|
|
243
246
|
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;
|
|
@@ -16,10 +16,26 @@
|
|
|
16
16
|
* terminal sends; chords legacy encoding loses (modified Enter/Tab/Escape,
|
|
17
17
|
* Ctrl+Shift+letter, …) translate to kitty CSI-u, which key matching already
|
|
18
18
|
* parses.
|
|
19
|
+
*
|
|
20
|
+
* A host terminal that writes VT instead of key records (any ConPTY host
|
|
21
|
+
* besides Windows Terminal's native path) reaches us as one `Vk = 0` text
|
|
22
|
+
* record per byte: Alt+Up arrives as `ESC`, `[`, `1`, `;`, `3`, `A`. Those
|
|
23
|
+
* bytes are reassembled into the escape sequence they spell; delivering them
|
|
24
|
+
* one at a time turns every such key into a bare Escape plus typed garbage.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Stateful decoder: joins UTF-16 surrogate halves that arrive as separate
|
|
28
|
+
* records, and reassembles VT escape sequences relayed as text records.
|
|
19
29
|
*/
|
|
20
|
-
/** Stateful decoder: joins UTF-16 surrogate halves that arrive as separate records. */
|
|
21
30
|
export declare class Win32InputModeDecoder {
|
|
22
31
|
#private;
|
|
32
|
+
/** Whether relayed VT text is held waiting for the rest of its sequence. */
|
|
33
|
+
get hasPendingSequence(): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Release held VT text as-is. The caller invokes this once no further bytes
|
|
36
|
+
* arrived in time, so a lone relayed ESC still acts as the Escape key.
|
|
37
|
+
*/
|
|
38
|
+
flushPendingSequence(): string[];
|
|
23
39
|
/**
|
|
24
40
|
* Translate one stdin sequence. Returns `undefined` when `data` is not a
|
|
25
41
|
* win32-input-mode record (pass it through unchanged), otherwise the key
|
|
@@ -35,3 +51,19 @@ export declare class Win32InputModeDecoder {
|
|
|
35
51
|
*/
|
|
36
52
|
decodePaste(content: string): string;
|
|
37
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Restores paste delimiters that a Windows console host emitted as individual
|
|
56
|
+
* win32-input-mode key records before StdinBuffer searches for the delimiters.
|
|
57
|
+
* Non-matching records remain untouched for normal key and paste decoding.
|
|
58
|
+
*/
|
|
59
|
+
export declare class Win32PasteMarkerNormalizer {
|
|
60
|
+
#private;
|
|
61
|
+
/**
|
|
62
|
+
* `onExpired` runs after held records are released because their wait timed
|
|
63
|
+
* out, so a downstream holder can treat the same wait as already elapsed.
|
|
64
|
+
*/
|
|
65
|
+
constructor(onInput: (data: string) => void, onExpired?: () => void);
|
|
66
|
+
process(data: string): void;
|
|
67
|
+
/** Release an incomplete marker as its original key records. */
|
|
68
|
+
flush(): void;
|
|
69
|
+
}
|
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.
|
|
4
|
+
"version": "18.6.0",
|
|
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.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "18.
|
|
46
|
-
"@oh-my-pi/pi-ai": "18.
|
|
47
|
-
"@oh-my-pi/pi-catalog": "18.
|
|
48
|
-
"@oh-my-pi/pi-natives": "18.
|
|
49
|
-
"@oh-my-pi/pi-utils": "18.
|
|
50
|
-
"@oh-my-pi/pi-wire": "18.
|
|
51
|
-
"@oh-my-pi/snapcompact": "18.
|
|
44
|
+
"@oh-my-pi/omptype": "18.6.0",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "18.6.0",
|
|
46
|
+
"@oh-my-pi/pi-ai": "18.6.0",
|
|
47
|
+
"@oh-my-pi/pi-catalog": "18.6.0",
|
|
48
|
+
"@oh-my-pi/pi-natives": "18.6.0",
|
|
49
|
+
"@oh-my-pi/pi-utils": "18.6.0",
|
|
50
|
+
"@oh-my-pi/pi-wire": "18.6.0",
|
|
51
|
+
"@oh-my-pi/snapcompact": "18.6.0"
|
|
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 };
|
package/src/components/editor.ts
CHANGED
|
@@ -754,6 +754,11 @@ export class Editor implements Component, Focusable {
|
|
|
754
754
|
onLargePaste?: (text: string, lineCount: number, options: PasteOptions) => boolean;
|
|
755
755
|
onAutocompleteCancel?: () => void;
|
|
756
756
|
disableSubmit: boolean = false;
|
|
757
|
+
/** Base editors accept native edits, not atomic sends. Implementations that
|
|
758
|
+
* handle `send` override this readiness check for their submission path. */
|
|
759
|
+
protected get nativeSendable(): boolean {
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
757
762
|
/** Placeholder painted right-aligned on the cursor row while the editor is empty and no
|
|
758
763
|
* autocomplete is open; hidden when it can't keep {@link PLACEHOLDER_MIN_GAP} cells from the
|
|
759
764
|
* cursor. The host styles it (ANSI allowed). Re-evaluated on every render, so hosts can derive
|
|
@@ -1687,6 +1692,7 @@ export class Editor implements Component, Focusable {
|
|
|
1687
1692
|
const props: TspEditorProps = {
|
|
1688
1693
|
text,
|
|
1689
1694
|
cursor,
|
|
1695
|
+
sendable: this.nativeSendable,
|
|
1690
1696
|
anchor: anchor ?? undefined,
|
|
1691
1697
|
decor: decor.decor,
|
|
1692
1698
|
ghost: ghost ? plainText(ghost) : undefined,
|