@linxiraos/pi-tui 1.1.6 → 1.1.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/CHANGELOG.md +3 -3
- package/dist/types/components/composer/registry.d.ts +7 -0
- package/dist/types/components/composer/types.d.ts +6 -0
- package/dist/types/components/editor.d.ts +11 -3
- package/dist/types/components/image.d.ts +5 -6
- package/dist/types/keys.d.ts +1 -1
- package/dist/types/kitty-graphics.d.ts +5 -5
- package/dist/types/stdin-buffer.d.ts +6 -1
- package/dist/types/terminal-capabilities.d.ts +21 -7
- package/dist/types/terminal-multiplexer.d.ts +4 -0
- package/dist/types/terminal.d.ts +46 -21
- package/dist/types/tui.d.ts +18 -0
- package/package.json +3 -3
- package/src/autocomplete.ts +1 -1
- package/src/components/composer/field.ts +1 -0
- package/src/components/composer/rail.ts +1 -0
- package/src/components/composer/registry.ts +10 -0
- package/src/components/composer/types.ts +6 -0
- package/src/components/editor.ts +20 -7
- package/src/components/image.ts +31 -24
- package/src/components/markdown.ts +26 -15
- package/src/components/select-list.ts +1 -0
- package/src/components/spacer.ts +1 -1
- package/src/components/text.ts +1 -1
- package/src/deccara.ts +2 -0
- package/src/keys.ts +1 -1
- package/src/kitty-graphics.ts +9 -6
- package/src/latex-block.ts +1 -0
- package/src/stdin-buffer.ts +28 -5
- package/src/terminal-capabilities.ts +34 -21
- package/src/terminal-multiplexer.ts +24 -0
- package/src/terminal.ts +173 -85
- package/src/tui.ts +158 -23
package/CHANGELOG.md
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
-
## [1.1.
|
|
5
|
+
## [1.1.8] - 2026-09-04
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
7
|
+
- Restored the sidebar gutter engine + `SidebarComponent` + `/sidebar` command (Zeta-only surface dropped by an earlier upstream merge).
|
|
8
|
+
- OMP sync v18.1.2–v18.1.5: sub-frame history ownership (the frame provider owns history), CoW worktree cloning support, and renderer fixes.
|
|
9
9
|
|
|
10
10
|
## [1.1.3] - 2026-08-25
|
|
11
11
|
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { ComposerStyle, EditorBorderStyle } from "./types.js";
|
|
2
2
|
/** Whether an id names a composer style shipped by pi-tui. */
|
|
3
3
|
export declare function isBuiltinComposerStyle(id: string): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Whether a style paints its own row foreground.
|
|
6
|
+
*
|
|
7
|
+
* Extensions registered before `filledSurface` existed received undecorated
|
|
8
|
+
* row text, so an omitted flag remains filled for extension-owned ids.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isFilledComposerStyle(style: ComposerStyle): boolean;
|
|
4
11
|
/**
|
|
5
12
|
* Register one extension-owned composer style for this process.
|
|
6
13
|
*
|
|
@@ -60,6 +60,12 @@ export interface ComposerRowContext extends ComposerChromeContext {
|
|
|
60
60
|
}
|
|
61
61
|
export interface ComposerStyle {
|
|
62
62
|
readonly id: EditorBorderStyle;
|
|
63
|
+
/**
|
|
64
|
+
* True when rows paint their own foreground through `surfaceColor`.
|
|
65
|
+
* Built-ins default to transparent; registered extensions that omit this
|
|
66
|
+
* field retain the pre-field behavior and receive undecorated row text.
|
|
67
|
+
*/
|
|
68
|
+
readonly filledSurface?: boolean;
|
|
63
69
|
/** Content rows carry left/right border glyphs; drives the cursor-reserve
|
|
64
70
|
* column, IME-safe layout, and the right-border scrollbar. */
|
|
65
71
|
readonly sideBorders: boolean;
|
|
@@ -10,6 +10,8 @@ export interface EditorTheme {
|
|
|
10
10
|
accentColor?: (str: string) => string;
|
|
11
11
|
/** Background fill used by filled composer styles. */
|
|
12
12
|
surfaceColor?: (str: string) => string;
|
|
13
|
+
/** Foreground used when the composer shape leaves its text surface transparent. */
|
|
14
|
+
textColor?: (str: string) => string;
|
|
13
15
|
selectList: SelectListTheme;
|
|
14
16
|
symbols: SymbolTheme;
|
|
15
17
|
editorPaddingX?: number;
|
|
@@ -55,6 +57,11 @@ export interface EditorTextAssistProvider {
|
|
|
55
57
|
/** Return replacement candidates for the misspelled word at the cursor. */
|
|
56
58
|
getWordReplacements?(lines: string[], cursorLine: number, cursorCol: number): EditorWordReplacements | null | Promise<EditorWordReplacements | null>;
|
|
57
59
|
}
|
|
60
|
+
/** What the paste transport knew about the input burst before the editor inserts the payload. */
|
|
61
|
+
export interface PasteOptions {
|
|
62
|
+
/** A submit keypress arrived in the same input burst and will be dispatched right after the paste. */
|
|
63
|
+
submitAfterPaste?: boolean;
|
|
64
|
+
}
|
|
58
65
|
export declare class Editor implements Component, Focusable {
|
|
59
66
|
#private;
|
|
60
67
|
/** Focusable interface - set by TUI when focus changes */
|
|
@@ -87,8 +94,9 @@ export declare class Editor implements Component, Focusable {
|
|
|
87
94
|
* the editor inserts nothing and records no undo state, leaving insertion to the host (e.g. a
|
|
88
95
|
* "wrap in a code block / XML / attach as file" menu for very large pastes), which re-inserts
|
|
89
96
|
* via {@link insertPaste} or {@link insertText}. Return `false` (or leave unset) for the
|
|
90
|
-
* default collapse-to-marker behavior. `lineCount` is the sanitized paste's line count
|
|
91
|
-
|
|
97
|
+
* default collapse-to-marker behavior. `lineCount` is the sanitized paste's line count;
|
|
98
|
+
* `options` carries what the paste transport knew about the burst. */
|
|
99
|
+
onLargePaste?: (text: string, lineCount: number, options: PasteOptions) => boolean;
|
|
92
100
|
onAutocompleteCancel?: () => void;
|
|
93
101
|
disableSubmit: boolean;
|
|
94
102
|
constructor(theme: EditorTheme);
|
|
@@ -208,7 +216,7 @@ export declare class Editor implements Component, Focusable {
|
|
|
208
216
|
/** Drop any volatile preview, then insert `text` as a single undoable edit. */
|
|
209
217
|
commitVolatileText(text: string): void;
|
|
210
218
|
/** Apply terminal paste semantics to text from non-bracketed paste transports. */
|
|
211
|
-
pasteText(text: string): void;
|
|
219
|
+
pasteText(text: string, options?: PasteOptions): void;
|
|
212
220
|
/** Insert `content` as a collapsed `[Paste #N]` marker (stored for expansion on submit via
|
|
213
221
|
* {@link getExpandedText}). Hosts that intercept large pastes through {@link onLargePaste} use
|
|
214
222
|
* this to re-insert a (possibly transformed) paste without re-triggering the interception hook. */
|
|
@@ -29,9 +29,9 @@ export declare const DEFAULT_MAX_INLINE_IMAGES = 8;
|
|
|
29
29
|
*
|
|
30
30
|
* The budget keeps the most recent `cap` images live and demotes older ones to
|
|
31
31
|
* their text fallback. Demotion needs a full redraw (so off-screen rows are
|
|
32
|
-
* rewritten) plus an explicit graphics purge of the demoted ids
|
|
33
|
-
* reports display order via {@link observe}
|
|
34
|
-
*
|
|
32
|
+
* rewritten) plus an explicit graphics purge of the demoted ids. {@link Image}
|
|
33
|
+
* reports display order via {@link observe}; when that reveals a stricter split,
|
|
34
|
+
* the TUI repeats the pass before emitting its terminal frame.
|
|
35
35
|
*
|
|
36
36
|
* `cap <= 0` disables budgeting: every image stays a live graphic.
|
|
37
37
|
*/
|
|
@@ -67,9 +67,8 @@ export declare class ImageBudget {
|
|
|
67
67
|
*/
|
|
68
68
|
observe(imageId: number): boolean;
|
|
69
69
|
/**
|
|
70
|
-
* End a render pass. Returns true when
|
|
71
|
-
*
|
|
72
|
-
* {@link takePurgeIds}.
|
|
70
|
+
* End a render pass. Returns true when the pass discovered a stricter budget
|
|
71
|
+
* and must be repeated before its terminal frame is emitted.
|
|
73
72
|
*/
|
|
74
73
|
endPass(): boolean;
|
|
75
74
|
/** Image ids to delete from the terminal this frame; clears the pending set. */
|
package/dist/types/keys.d.ts
CHANGED
|
@@ -63,7 +63,7 @@ export type KeyId = BaseKey | ModifiedKeyId<BaseKey>;
|
|
|
63
63
|
* modifier methods return precisely-typed concatenations (e.g. `Key.ctrl("c")`
|
|
64
64
|
* is `"ctrl+c"`, not just `string`). This mirrors the upstream
|
|
65
65
|
* `@mariozechner/pi-tui` `Key` export verbatim so plugins built against any
|
|
66
|
-
* scope alias (`@mariozechner`, `@earendil-works`, `@
|
|
66
|
+
* scope alias (`@mariozechner`, `@earendil-works`, `@linxiraos`) keep working
|
|
67
67
|
* once the specifier shim remaps them to this package.
|
|
68
68
|
*/
|
|
69
69
|
export declare const Key: {
|
|
@@ -26,12 +26,12 @@ export interface KittyGraphicsFeatures {
|
|
|
26
26
|
* Whether the detected terminal renders Kitty Unicode placeholders (`U=1` +
|
|
27
27
|
* U+10EEEE with row/column diacritics).
|
|
28
28
|
*
|
|
29
|
-
* Kitty and Ghostty advertise placeholder support directly. A
|
|
29
|
+
* Kitty and Ghostty advertise placeholder support directly. A multiplexer
|
|
30
30
|
* cannot use cursor-positioned placements because the outer terminal does not
|
|
31
|
-
* know pane scroll/reflow state
|
|
32
|
-
*
|
|
33
|
-
* fallback stays off because
|
|
34
|
-
*
|
|
31
|
+
* know pane scroll/reflow state. An explicit `PI_FORCE_IMAGE_PROTOCOL=kitty`
|
|
32
|
+
* opts into placeholders under any multiplexer — matching `timg -pk`.
|
|
33
|
+
* Automatic Herdr fallback stays off because its pane marker does not prove
|
|
34
|
+
* that the experimental Kitty renderer is enabled.
|
|
35
35
|
*
|
|
36
36
|
* `PI_NO_KITTY_PLACEHOLDERS=1` and `PI_KITTY_PLACEHOLDERS=0` remain hard
|
|
37
37
|
* opt-outs; `PI_KITTY_PLACEHOLDERS=1` explicitly opts in anywhere else.
|
|
@@ -43,7 +43,12 @@ export type StdinBufferOptions = {
|
|
|
43
43
|
};
|
|
44
44
|
export type StdinBufferEventMap = {
|
|
45
45
|
data: [string];
|
|
46
|
-
|
|
46
|
+
/**
|
|
47
|
+
* A completed bracketed paste. `enter` carries an Enter keypress that shared
|
|
48
|
+
* the paste's stdin read, so the terminal can dispatch paste and submit to
|
|
49
|
+
* the same focused component before a paste-triggered overlay can take focus.
|
|
50
|
+
*/
|
|
51
|
+
paste: [content: string, enter?: string];
|
|
47
52
|
};
|
|
48
53
|
/**
|
|
49
54
|
* Buffers stdin input and emits complete sequences via the 'data' event.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { HangulCompatibilityJamoWidth } from "./utils.js";
|
|
2
|
+
export * from "./terminal-multiplexer.js";
|
|
2
3
|
export { isInsideTmux, wrapTmuxPassthrough } from "./tmux.js";
|
|
3
4
|
export declare enum ImageProtocol {
|
|
4
5
|
Kitty = "\u001B_G",
|
|
@@ -48,8 +49,6 @@ export declare class TerminalInfo {
|
|
|
48
49
|
formatNotification(message: string | TerminalNotification): string;
|
|
49
50
|
sendNotification(message: string | TerminalNotification): void;
|
|
50
51
|
}
|
|
51
|
-
/** Detect terminal multiplexers where scrollback clearing and height-change redraws are hostile. */
|
|
52
|
-
export declare function isInsideTerminalMultiplexer(env?: NodeJS.ProcessEnv): boolean;
|
|
53
52
|
/**
|
|
54
53
|
* Whether the agent process is running inside a Zellij session. Read fresh on
|
|
55
54
|
* each call (like {@link isInsideTmux}) so a session attached/detached mid-run
|
|
@@ -86,9 +85,15 @@ export declare function synchronizedOutputUserOverride(env?: NodeJS.ProcessEnv):
|
|
|
86
85
|
* 2. Positive `TERM_FEATURES` advertisement (`Sy`) — survives SSH/mux wrapping.
|
|
87
86
|
* 3. Windows Terminal (1.24+) via `WT_SESSION`, on native win32 and the
|
|
88
87
|
* WSL/SSH-fronted host alike.
|
|
89
|
-
* 4.
|
|
88
|
+
* 4. Herdr panes. Herdr is otherwise treated as a multiplexer so leaked
|
|
89
|
+
* kitty/ghostty identities cannot enable placeholder graphics, but its
|
|
90
|
+
* pane VTE is libghostty and already suppresses compositing while DEC 2026
|
|
91
|
+
* is set. Leaving sync off lets CUP-diff paints and split write(2) chunks
|
|
92
|
+
* composite as dirty-row patches — the live viewport tears, with the top
|
|
93
|
+
* frozen while only the bottom refreshes.
|
|
94
|
+
* 5. Known direct terminals with confirmed support. SSH does *not* disable —
|
|
90
95
|
* DEC 2026 passes through SSH when the outer terminal honors it.
|
|
91
|
-
*
|
|
96
|
+
* 6. Everything else starts off, including risky multiplexers; the runtime
|
|
92
97
|
* DECRQM probe upgrades any of them when the terminal actually reports
|
|
93
98
|
* `?2026` supported (current zellij, tmux master, foot, contour, mintty…).
|
|
94
99
|
*/
|
|
@@ -165,9 +170,9 @@ export declare function isPaseoEmbedder(env?: NodeJS.ProcessEnv): boolean;
|
|
|
165
170
|
/**
|
|
166
171
|
* Resolve the image protocol for a non-forced runtime: static per-terminal
|
|
167
172
|
* support (with Warp's platform carve-out), then the multiplexer fallback,
|
|
168
|
-
* then
|
|
169
|
-
*
|
|
170
|
-
*
|
|
173
|
+
* then host carve-outs. `isTTY` is injectable because the fallback only fires
|
|
174
|
+
* on a real TTY — a piped subprocess cannot exercise that path, so regression
|
|
175
|
+
* tests call this directly.
|
|
171
176
|
*/
|
|
172
177
|
export declare function resolveImageProtocol(terminalId: TerminalId, env?: NodeJS.ProcessEnv, isTTY?: boolean): ImageProtocol | null;
|
|
173
178
|
/** Resolve terminal identity from environment markers used by common emulators. */
|
|
@@ -206,6 +211,15 @@ export declare function setTerminalScreenToScrollback(enabled: boolean): void;
|
|
|
206
211
|
* capability); tests flip it directly to exercise the scaled-heading path.
|
|
207
212
|
*/
|
|
208
213
|
export declare function setTerminalTextSizing(enabled: boolean): void;
|
|
214
|
+
/**
|
|
215
|
+
* Override OSC 8 hyperlink capability at runtime. The coding-agent calls this
|
|
216
|
+
* from the `tui.hyperlinks` setting so its resolved policy (`off`/`auto`/`always`)
|
|
217
|
+
* drives every renderer that gates on {@link TERMINAL}`.hyperlinks` — notably the
|
|
218
|
+
* Markdown component's `[text](url)`/bare-URL links — consistently with the
|
|
219
|
+
* path/resource links that already consult the setting directly. Tests flip it
|
|
220
|
+
* to exercise the OSC 8 and plain-text paths deterministically.
|
|
221
|
+
*/
|
|
222
|
+
export declare function setTerminalHyperlinks(enabled: boolean): void;
|
|
209
223
|
export declare function getTerminalInfo(terminalId: TerminalId, platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): TerminalInfo;
|
|
210
224
|
export interface CellDimensions {
|
|
211
225
|
widthPx: number;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** True when this process is running inside a Herdr pane. */
|
|
2
|
+
export declare function isInsideHerdr(env?: NodeJS.ProcessEnv): boolean;
|
|
3
|
+
/** Detect whether a terminal multiplexer owns the current screen grid. */
|
|
4
|
+
export declare function isInsideTerminalMultiplexer(env?: NodeJS.ProcessEnv): boolean;
|
package/dist/types/terminal.d.ts
CHANGED
|
@@ -20,33 +20,50 @@
|
|
|
20
20
|
*/
|
|
21
21
|
export declare function chunkForConPTY(data: string, maxChunkBytes?: number): string[];
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* Backlog at or below which stdout is healthy again: the pump has kept up, the
|
|
24
|
+
* TUI resumes composing frames, and a {@link StdoutStallWatchdog} episode ends.
|
|
25
|
+
* The TUI render gate (`TUI.#MAX_PENDING_OUTPUT_BYTES`) is this same value, so
|
|
26
|
+
* the watchdog stays armed across the entire range where frames are deferred —
|
|
27
|
+
* otherwise a consumer that wedges between this level and the arm cap is never
|
|
28
|
+
* re-sampled and the session freezes instead of disconnecting (#10434 review).
|
|
29
|
+
*/
|
|
30
|
+
export declare const STDOUT_BACKLOG_CLEAR_BYTES: number;
|
|
31
|
+
/**
|
|
32
|
+
* Bounds a never-draining stdout backlog without killing a single large but
|
|
33
|
+
* actively-draining frame.
|
|
25
34
|
*
|
|
26
35
|
* `process.stdout.write()` returns `false` once its buffer exceeds the stream
|
|
27
|
-
* high-water mark
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
36
|
+
* high-water mark, and the off-thread pump's `pending()` climbs the same way;
|
|
37
|
+
* a stalled-but-alive PTY reader never throws, so the byte count is the only
|
|
38
|
+
* signal that output is going nowhere. Tripping on the instantaneous count
|
|
39
|
+
* alone is wrong: a legitimate oversized frame (a resume repaint of dozens of
|
|
40
|
+
* inline screenshots, #10430) briefly exceeds the cap and then drains.
|
|
41
|
+
*
|
|
42
|
+
* An episode starts when the backlog first exceeds `armBytes` and lasts until
|
|
43
|
+
* it drains back to `clearBytes` (healthy). The backlog can fall below
|
|
44
|
+
* `armBytes` while still unhealthy, so the episode must outlive that dip
|
|
45
|
+
* (#10434): during it the watchdog declares the terminal disconnected only when
|
|
46
|
+
* the backlog makes no drain progress (no new low-water mark) for `stallMs` —
|
|
47
|
+
* a draining terminal keeps lowering the mark and never trips, while a wedged
|
|
48
|
+
* one (#6854) still tears down within the window.
|
|
34
49
|
*
|
|
35
50
|
* Exported for unit testing; `ProcessTerminal` is the sole production user.
|
|
36
51
|
*/
|
|
37
|
-
export declare class
|
|
52
|
+
export declare class StdoutStallWatchdog {
|
|
38
53
|
#private;
|
|
39
|
-
private readonly
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
private readonly armBytes;
|
|
55
|
+
private readonly clearBytes;
|
|
56
|
+
private readonly stallMs;
|
|
57
|
+
constructor(armBytes?: number, clearBytes?: number, stallMs?: number);
|
|
58
|
+
/** True while an episode is active and the backlog must be polled to completion. */
|
|
59
|
+
get armed(): boolean;
|
|
43
60
|
/**
|
|
44
|
-
*
|
|
45
|
-
* `
|
|
46
|
-
*
|
|
61
|
+
* Feed the current pending-byte count and clock reading. Returns true once an
|
|
62
|
+
* armed episode has gone `stallMs` with no drain progress, at which point the
|
|
63
|
+
* caller treats the terminal as disconnected.
|
|
47
64
|
*/
|
|
48
|
-
|
|
49
|
-
/**
|
|
65
|
+
sample(pending: number, nowMs: number): boolean;
|
|
66
|
+
/** Episode ended (drained) or terminal torn down: stop watching. */
|
|
50
67
|
reset(): void;
|
|
51
68
|
}
|
|
52
69
|
/** Record alternate-screen state (called by the TUI on `?1049h`/`?1049l` writes). */
|
|
@@ -82,6 +99,13 @@ export interface TerminalStartOptions {
|
|
|
82
99
|
}
|
|
83
100
|
/** Identity of an accepted explicit terminal appearance refresh request. */
|
|
84
101
|
export type TerminalAppearanceRequestToken = number;
|
|
102
|
+
/**
|
|
103
|
+
* Fired once per DEC private mode when DECRQM support resolves.
|
|
104
|
+
* `confirmed` is false when only the DA1 sentinel arrived.
|
|
105
|
+
* `status` is the DECRPM value (0 unrecognized, 1/2 set/reset, 3 permanently
|
|
106
|
+
* set, 4 permanently reset) when the terminal answered DECRQM.
|
|
107
|
+
*/
|
|
108
|
+
export type PrivateModeReportHandler = (mode: number, supported: boolean, confirmed?: boolean, status?: number) => void;
|
|
85
109
|
export interface Terminal {
|
|
86
110
|
start(onInput: (data: string) => void, onResize: () => void, onDisconnect?: () => void, options?: TerminalStartOptions): void;
|
|
87
111
|
/**
|
|
@@ -160,8 +184,9 @@ export interface Terminal {
|
|
|
160
184
|
* status resolves. `confirmed` is false when the terminal answered the DA1
|
|
161
185
|
* sentinel without answering DECRQM, which proves only that querying support
|
|
162
186
|
* is unavailable — not that the private mode itself is unsupported.
|
|
187
|
+
* `status` is the DECRPM value when the terminal answered DECRQM.
|
|
163
188
|
*/
|
|
164
|
-
onPrivateModeReport?(callback:
|
|
189
|
+
onPrivateModeReport?(callback: PrivateModeReportHandler): void;
|
|
165
190
|
}
|
|
166
191
|
/**
|
|
167
192
|
* True when stdout flows through a ConPTY pseudo-console (native win32, or
|
|
@@ -206,7 +231,7 @@ export declare class ProcessTerminal implements Terminal {
|
|
|
206
231
|
* probes remain direct. Suppressed while inactive, headless, or after teardown.
|
|
207
232
|
*/
|
|
208
233
|
refreshAppearance(requestToken?: TerminalAppearanceRequestToken): TerminalAppearanceRequestToken | void;
|
|
209
|
-
onPrivateModeReport(callback:
|
|
234
|
+
onPrivateModeReport(callback: PrivateModeReportHandler): void;
|
|
210
235
|
start(onInput: (data: string) => void, onResize: () => void, onDisconnect?: () => void, options?: TerminalStartOptions): void;
|
|
211
236
|
enableInput(): void;
|
|
212
237
|
drainInput(maxMs?: number, idleMs?: number): Promise<void>;
|
package/dist/types/tui.d.ts
CHANGED
|
@@ -285,6 +285,24 @@ export declare class TUI extends Container {
|
|
|
285
285
|
* plus a full redraw on the frame after a new image exceeds the cap.
|
|
286
286
|
*/
|
|
287
287
|
setMaxInlineImages(cap: number): void;
|
|
288
|
+
/**
|
|
289
|
+
* Yield `width` columns from the right edge of the terminal as a sidebar
|
|
290
|
+
* margin, repainted per frame from the gutter component (see
|
|
291
|
+
* {@link setGutterComponent}). The main area composes and paints at
|
|
292
|
+
* `terminal.columns - width`, which keeps every committed row — and
|
|
293
|
+
* therefore native scrollback — free of gutter text. `null` restores
|
|
294
|
+
* full-width rendering. Frames where that would drop the main area below
|
|
295
|
+
* {@link MIN_MAIN_AREA_COLUMNS}, or where a fullscreen-capable overlay is
|
|
296
|
+
* visible, ignore the reservation and paint at the physical width.
|
|
297
|
+
*/
|
|
298
|
+
setMainWidth(width: number | null): void;
|
|
299
|
+
/**
|
|
300
|
+
* Component rendered into the right-hand margin created by
|
|
301
|
+
* {@link setMainWidth}. Its rows are painted viewport-only via absolute
|
|
302
|
+
* cursor addressing inside each frame's synchronized block; they never enter
|
|
303
|
+
* the composed frame or scrollback. `null` clears the margin.
|
|
304
|
+
*/
|
|
305
|
+
setGutterComponent(component: Component | null): void;
|
|
288
306
|
/** Return how settled resizes refresh native scrollback. */
|
|
289
307
|
getResizeScrollback(): ResizeScrollbackMode;
|
|
290
308
|
/** Set how settled resizes refresh native scrollback. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@linxiraos/pi-tui",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.8",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://linxira-os.github.io/zeta/",
|
|
7
7
|
"author": "Stencil Labs, Inc.",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"fmt": "biome format --write ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@linxiraos/pi-natives": "1.1.
|
|
41
|
-
"@linxiraos/pi-utils": "1.1.
|
|
40
|
+
"@linxiraos/pi-natives": "1.1.8",
|
|
41
|
+
"@linxiraos/pi-utils": "1.1.8"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"kitty-vt-wasm": "^0.2.0"
|
package/src/autocomplete.ts
CHANGED
|
@@ -382,7 +382,7 @@ function buildSlashCommandCompletions(
|
|
|
382
382
|
// Equal text-match scores fall back to usage frequency, then to the
|
|
383
383
|
// stable registry order.
|
|
384
384
|
.sort((a, b) => b.score - a.score || b.usage - a.usage)
|
|
385
|
-
.map(({ score:
|
|
385
|
+
.map(({ score: _score, usage: _usage, ...rest }) => rest)
|
|
386
386
|
);
|
|
387
387
|
}
|
|
388
388
|
|
|
@@ -10,6 +10,7 @@ const ACCENT_RAIL = "▎";
|
|
|
10
10
|
/** Filled composer surface anchored by a single left accent rail. */
|
|
11
11
|
export const railComposerStyle: ComposerStyle = {
|
|
12
12
|
id: "rail",
|
|
13
|
+
filledSurface: true,
|
|
13
14
|
sideBorders: true,
|
|
14
15
|
verticalChrome: 0,
|
|
15
16
|
statusAttachment: "none",
|
|
@@ -25,6 +25,16 @@ export function isBuiltinComposerStyle(id: string): boolean {
|
|
|
25
25
|
return Object.hasOwn(BUILTIN_COMPOSER_STYLES, id);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Whether a style paints its own row foreground.
|
|
30
|
+
*
|
|
31
|
+
* Extensions registered before `filledSurface` existed received undecorated
|
|
32
|
+
* row text, so an omitted flag remains filled for extension-owned ids.
|
|
33
|
+
*/
|
|
34
|
+
export function isFilledComposerStyle(style: ComposerStyle): boolean {
|
|
35
|
+
return style.filledSurface ?? !isBuiltinComposerStyle(style.id);
|
|
36
|
+
}
|
|
37
|
+
|
|
28
38
|
/**
|
|
29
39
|
* Register one extension-owned composer style for this process.
|
|
30
40
|
*
|
|
@@ -77,6 +77,12 @@ export interface ComposerRowContext extends ComposerChromeContext {
|
|
|
77
77
|
|
|
78
78
|
export interface ComposerStyle {
|
|
79
79
|
readonly id: EditorBorderStyle;
|
|
80
|
+
/**
|
|
81
|
+
* True when rows paint their own foreground through `surfaceColor`.
|
|
82
|
+
* Built-ins default to transparent; registered extensions that omit this
|
|
83
|
+
* field retain the pre-field behavior and receive undecorated row text.
|
|
84
|
+
*/
|
|
85
|
+
readonly filledSurface?: boolean;
|
|
80
86
|
/** Content rows carry left/right border glyphs; drives the cursor-reserve
|
|
81
87
|
* column, IME-safe layout, and the right-border scrollbar. */
|
|
82
88
|
readonly sideBorders: boolean;
|
package/src/components/editor.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
type EditorBorderStyle,
|
|
33
33
|
type EditorTopBorder,
|
|
34
34
|
getComposerStyle,
|
|
35
|
+
isFilledComposerStyle,
|
|
35
36
|
} from "./composer";
|
|
36
37
|
|
|
37
38
|
export type { EditorBorderStyle, EditorTopBorder };
|
|
@@ -401,6 +402,8 @@ export interface EditorTheme {
|
|
|
401
402
|
accentColor?: (str: string) => string;
|
|
402
403
|
/** Background fill used by filled composer styles. */
|
|
403
404
|
surfaceColor?: (str: string) => string;
|
|
405
|
+
/** Foreground used when the composer shape leaves its text surface transparent. */
|
|
406
|
+
textColor?: (str: string) => string;
|
|
404
407
|
selectList: SelectListTheme;
|
|
405
408
|
symbols: SymbolTheme;
|
|
406
409
|
editorPaddingX?: number;
|
|
@@ -464,6 +467,12 @@ export interface EditorTextAssistProvider {
|
|
|
464
467
|
type HistoryCursorAnchor = "start" | "end";
|
|
465
468
|
type AutocompleteRequest = { kind: "regular"; explicitTab: boolean } | { kind: "force" };
|
|
466
469
|
|
|
470
|
+
/** What the paste transport knew about the input burst before the editor inserts the payload. */
|
|
471
|
+
export interface PasteOptions {
|
|
472
|
+
/** A submit keypress arrived in the same input burst and will be dispatched right after the paste. */
|
|
473
|
+
submitAfterPaste?: boolean;
|
|
474
|
+
}
|
|
475
|
+
|
|
467
476
|
export class Editor implements Component, Focusable {
|
|
468
477
|
#state: EditorState = {
|
|
469
478
|
lines: [""],
|
|
@@ -578,8 +587,9 @@ export class Editor implements Component, Focusable {
|
|
|
578
587
|
* the editor inserts nothing and records no undo state, leaving insertion to the host (e.g. a
|
|
579
588
|
* "wrap in a code block / XML / attach as file" menu for very large pastes), which re-inserts
|
|
580
589
|
* via {@link insertPaste} or {@link insertText}. Return `false` (or leave unset) for the
|
|
581
|
-
* default collapse-to-marker behavior. `lineCount` is the sanitized paste's line count
|
|
582
|
-
|
|
590
|
+
* default collapse-to-marker behavior. `lineCount` is the sanitized paste's line count;
|
|
591
|
+
* `options` carries what the paste transport knew about the burst. */
|
|
592
|
+
onLargePaste?: (text: string, lineCount: number, options: PasteOptions) => boolean;
|
|
583
593
|
onAutocompleteCancel?: () => void;
|
|
584
594
|
disableSubmit: boolean = false;
|
|
585
595
|
|
|
@@ -1271,13 +1281,16 @@ export class Editor implements Component, Focusable {
|
|
|
1271
1281
|
displayWidth = visibleWidth(displayText);
|
|
1272
1282
|
}
|
|
1273
1283
|
}
|
|
1284
|
+
const renderedText = isFilledComposerStyle(style)
|
|
1285
|
+
? displayText
|
|
1286
|
+
: (this.#theme.textColor ?? PASSTHROUGH_COLOR)(displayText);
|
|
1274
1287
|
|
|
1275
1288
|
const linePad = padding(Math.max(0, lineContentWidth - displayWidth));
|
|
1276
1289
|
|
|
1277
1290
|
result.push(
|
|
1278
1291
|
...style.renderRow({
|
|
1279
1292
|
...chromeCtx,
|
|
1280
|
-
text:
|
|
1293
|
+
text: renderedText,
|
|
1281
1294
|
pad: linePad,
|
|
1282
1295
|
gutter: gutterText,
|
|
1283
1296
|
isLastRow: visibleIndex === visibleLayoutLines.length - 1,
|
|
@@ -2142,8 +2155,8 @@ export class Editor implements Component, Focusable {
|
|
|
2142
2155
|
}
|
|
2143
2156
|
|
|
2144
2157
|
/** Apply terminal paste semantics to text from non-bracketed paste transports. */
|
|
2145
|
-
pasteText(text: string): void {
|
|
2146
|
-
this.#handlePaste(text);
|
|
2158
|
+
pasteText(text: string, options: PasteOptions = {}): void {
|
|
2159
|
+
this.#handlePaste(text, options);
|
|
2147
2160
|
}
|
|
2148
2161
|
|
|
2149
2162
|
/** Insert `content` as a collapsed `[Paste #N]` marker (stored for expansion on submit via
|
|
@@ -2283,7 +2296,7 @@ export class Editor implements Component, Focusable {
|
|
|
2283
2296
|
}
|
|
2284
2297
|
}
|
|
2285
2298
|
|
|
2286
|
-
#handlePaste(pastedText: string): void {
|
|
2299
|
+
#handlePaste(pastedText: string, options: PasteOptions = {}): void {
|
|
2287
2300
|
let filteredText = this.#sanitizePastedText(pastedText);
|
|
2288
2301
|
|
|
2289
2302
|
// If pasting a file path (starts with /, ~, or .) and the character before
|
|
@@ -2305,7 +2318,7 @@ export class Editor implements Component, Focusable {
|
|
|
2305
2318
|
// Let the host intercept marker-sized pastes (e.g. the large-paste menu). When it takes
|
|
2306
2319
|
// over, the editor inserts nothing and records no undo state — the host re-inserts via
|
|
2307
2320
|
// `insertPaste`/`insertText` once the user chooses.
|
|
2308
|
-
if (isMarkerSized && this.onLargePaste?.(filteredText, pastedLines.length)) {
|
|
2321
|
+
if (isMarkerSized && this.onLargePaste?.(filteredText, pastedLines.length, options)) {
|
|
2309
2322
|
return;
|
|
2310
2323
|
}
|
|
2311
2324
|
|