@oh-my-pi/pi-tui 18.1.22 → 18.2.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 +32 -0
- package/THIRD-PARTY-NOTICES.txt +54 -25
- package/dist/types/autocomplete.d.ts +2 -1
- package/dist/types/components/editor.d.ts +16 -0
- package/dist/types/components/image.d.ts +49 -7
- package/dist/types/terminal-capabilities.d.ts +24 -0
- package/dist/types/terminal-multiplexer.d.ts +17 -1
- package/dist/types/terminal.d.ts +17 -2
- package/package.json +3 -3
- package/src/autocomplete.ts +48 -19
- package/src/components/editor.ts +95 -54
- package/src/components/image.ts +244 -45
- package/src/terminal-capabilities.ts +104 -38
- package/src/terminal-multiplexer.ts +31 -10
- package/src/terminal.ts +29 -12
- package/src/tui.ts +404 -136
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.2.1] - 2026-09-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `Editor.deleteCharForward()` exposes the `tui.editor.deleteCharForward` operation to hosts that resolve the chord themselves, applying the same transient-state teardown the key dispatch does (pending character jump, spelling-assist popup) and routing through Vim's `x` in Normal and Visual modes.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Inline image limits now bound Kitty graphics retained from earlier frames and fullscreen overlays; older scrollback images are evicted until replayed.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Enter now runs a slash command whose argument completes to a directory, instead of descending into it. ([#12112](https://github.com/can1357/oh-my-pi/pull/12112) by [@Huang-404-Q](https://github.com/Huang-404-Q))
|
|
18
|
+
- Fixed a resize on Windows leaving the screen with a scrolled-up duplicate transcript and no visible response to input. The in-place resize path (default-on for Warp) anchors one settled repaint on a DSR round trip, but a ConPTY host owns that grid: measured on conhost, resizing the pseudoconsole re-emits its whole viewport from `CSI H` with absolute addressing while the application writes nothing, and re-homes the cursor, so the reply reports column 1 and can never be attributed to its probe tag. ConPTY sessions now keep the alternate-screen borrow, whose settled transaction ends in the `ResizeScrollbackMode` rebuild, and skip that unattributable anchor probe — except inside a multiplexer, which answers the DSR from its own grid, and under `PI_TUI_RESIZE_IN_PLACE=1`, which restores the whole pre-change path ([#11625](https://github.com/can1357/oh-my-pi/pull/11625) by [@bse-ai](https://github.com/bse-ai)).
|
|
19
|
+
- Stopped exact-width live rows from entering native scrollback during ConPTY repaints ([#9783](https://github.com/can1357/oh-my-pi/issues/9783)).
|
|
20
|
+
- Forward delete no longer leaves the Vim Normal-mode cursor one column past the end of a line after deleting the final grapheme.
|
|
21
|
+
- Fixed Tabby CMD sessions enabling synchronized output from a spoofed `WT_SESSION`, which caused streaming repaints to overlap when Tabby's terminal chain mishandled DEC 2026.
|
|
22
|
+
- Extension command argument completions now refresh after typing a Space when the previous argument had no suggestions. ([#11060](https://github.com/can1357/oh-my-pi/issues/11060))
|
|
23
|
+
- Detect the wmux Windows terminal multiplexer (`WMUX` / `WMUX_SURFACE_ID`) so its panes take the in-place viewport repaint path instead of the direct-terminal scrollback path.
|
|
24
|
+
|
|
25
|
+
## [18.2.0] - 2026-09-15
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- Added `Editor.textRevision` for content-dependent render caches, including undo and draft restoration.
|
|
30
|
+
- Added collapseToAtom method to compress text spans into UI-friendly atoms
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- Redrawing unchanged terminal rows now avoids rescanning ANSI, hyperlinks, and images.
|
|
35
|
+
- Terminal UIs reach their first frame with a smaller startup module graph.
|
|
36
|
+
|
|
5
37
|
## [18.1.17] - 2026-09-10
|
|
6
38
|
|
|
7
39
|
### Added
|
package/THIRD-PARTY-NOTICES.txt
CHANGED
|
@@ -10843,7 +10843,7 @@ RUST RUNTIME DEPENDENCY LICENSES
|
|
|
10843
10843
|
|
|
10844
10844
|
Generated from Cargo.lock by cargo-about 0.8.2 in locked, offline, workspace, all-feature, all-target mode, then restricted using cargo metadata to normal and build edges reachable from the workspace (development-only edges are excluded). cargo-deny independently evaluates the complete all-target graph.
|
|
10845
10845
|
|
|
10846
|
-
Native Opus (
|
|
10846
|
+
Native Opus (opus/opusic-sys) and PCRE2 (pcre2/pcre2-sys) are covered by the exact crate license payloads below. inferno 0.12.8 is package-scoped as CDDL-1.0: its CDDL-covered source remains available in the locked crates.io source archive at https://crates.io/crates/inferno/0.12.8 and https://github.com/jonhoo/inferno. The full locked license text follows below.
|
|
10847
10847
|
|
|
10848
10848
|
-------------------------------------------------------------------------------
|
|
10849
10849
|
License: Apache License 2.0 (Apache-2.0)
|
|
@@ -12509,6 +12509,58 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
|
12509
12509
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
12510
12510
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
12511
12511
|
|
|
12512
|
+
-------------------------------------------------------------------------------
|
|
12513
|
+
License: BSD 3-Clause "New" or "Revised" License (BSD-3-Clause)
|
|
12514
|
+
Used by:
|
|
12515
|
+
- opusic-sys 0.7.5 (https://github.com/DoumanAsh/opusic-sys)
|
|
12516
|
+
|
|
12517
|
+
Full license text:
|
|
12518
|
+
|
|
12519
|
+
Copyright 2001-2023 Xiph.Org, Skype Limited, Octasic,
|
|
12520
|
+
Jean-Marc Valin, Timothy B. Terriberry,
|
|
12521
|
+
CSIRO, Gregory Maxwell, Mark Borgerding,
|
|
12522
|
+
Erik de Castro Lopo, Mozilla, Amazon
|
|
12523
|
+
|
|
12524
|
+
Redistribution and use in source and binary forms, with or without
|
|
12525
|
+
modification, are permitted provided that the following conditions
|
|
12526
|
+
are met:
|
|
12527
|
+
|
|
12528
|
+
- Redistributions of source code must retain the above copyright
|
|
12529
|
+
notice, this list of conditions and the following disclaimer.
|
|
12530
|
+
|
|
12531
|
+
- Redistributions in binary form must reproduce the above copyright
|
|
12532
|
+
notice, this list of conditions and the following disclaimer in the
|
|
12533
|
+
documentation and/or other materials provided with the distribution.
|
|
12534
|
+
|
|
12535
|
+
- Neither the name of Internet Society, IETF or IETF Trust, nor the
|
|
12536
|
+
names of specific contributors, may be used to endorse or promote
|
|
12537
|
+
products derived from this software without specific prior written
|
|
12538
|
+
permission.
|
|
12539
|
+
|
|
12540
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
12541
|
+
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
12542
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
12543
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
|
12544
|
+
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
12545
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
12546
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
12547
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
12548
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
12549
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
12550
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
12551
|
+
|
|
12552
|
+
Opus is subject to the royalty-free patent licenses which are
|
|
12553
|
+
specified at:
|
|
12554
|
+
|
|
12555
|
+
Xiph.Org Foundation:
|
|
12556
|
+
https://datatracker.ietf.org/ipr/1524/
|
|
12557
|
+
|
|
12558
|
+
Microsoft Corporation:
|
|
12559
|
+
https://datatracker.ietf.org/ipr/1914/
|
|
12560
|
+
|
|
12561
|
+
Broadcom Corporation:
|
|
12562
|
+
https://datatracker.ietf.org/ipr/1526/
|
|
12563
|
+
|
|
12512
12564
|
-------------------------------------------------------------------------------
|
|
12513
12565
|
License: BSD 3-Clause "New" or "Revised" License (BSD-3-Clause)
|
|
12514
12566
|
Used by:
|
|
@@ -13290,29 +13342,6 @@ jurisdiction of the Federal Courts of the Northern District of
|
|
|
13290
13342
|
California and the state courts of the State of California, with
|
|
13291
13343
|
venue lying in Santa Clara County, California.
|
|
13292
13344
|
|
|
13293
|
-
-------------------------------------------------------------------------------
|
|
13294
|
-
License: ISC License (ISC)
|
|
13295
|
-
Used by:
|
|
13296
|
-
- audiopus_sys 0.2.2 (https://github.com/lakelezz/audiopus_sys.git)
|
|
13297
|
-
|
|
13298
|
-
Full license text:
|
|
13299
|
-
|
|
13300
|
-
ISC License
|
|
13301
|
-
|
|
13302
|
-
Copyright (c) 2019, Lakelezz
|
|
13303
|
-
|
|
13304
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
13305
|
-
purpose with or without fee is hereby granted, provided that the above
|
|
13306
|
-
copyright notice and this permission notice appear in all copies.
|
|
13307
|
-
|
|
13308
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
13309
|
-
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
13310
|
-
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
13311
|
-
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13312
|
-
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
13313
|
-
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
13314
|
-
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
13315
|
-
|
|
13316
13345
|
-------------------------------------------------------------------------------
|
|
13317
13346
|
License: ISC License (ISC)
|
|
13318
13347
|
Used by:
|
|
@@ -18734,7 +18763,7 @@ DEALINGS IN THE SOFTWARE.
|
|
|
18734
18763
|
-------------------------------------------------------------------------------
|
|
18735
18764
|
License: MIT License (MIT)
|
|
18736
18765
|
Used by:
|
|
18737
|
-
- opus 0.
|
|
18766
|
+
- opus 0.4.0 (https://github.com/SpaceManiac/opus-rs)
|
|
18738
18767
|
|
|
18739
18768
|
Full license text:
|
|
18740
18769
|
|
|
@@ -99,7 +99,8 @@ export declare const SKILL_NAMESPACE = "skill:";
|
|
|
99
99
|
* popup alive through fuzzy name/description hits, so a token only matches as
|
|
100
100
|
* - a prefix of the `skill:` namespace (incl. the bare `/` entry point),
|
|
101
101
|
* - an explicit `skill:…` query (full fuzzy name/description search), or
|
|
102
|
-
* - a prefix of the skill
|
|
102
|
+
* - a prefix at the start of the skill bare name or one of its hyphen-delimited
|
|
103
|
+
* segments (`/hum` → `skill:humanizer`, `/last` → `skill:research-last30days`).
|
|
103
104
|
* Anything else yields no items, letting the caller fall through to path
|
|
104
105
|
* completion or close the popup. Shared with the editor's accept-time
|
|
105
106
|
* staleness guard so Tab/Enter never accepts a skill the refreshed popup
|
|
@@ -196,6 +196,8 @@ export declare class Editor implements Component, Focusable {
|
|
|
196
196
|
render(width: number): readonly string[];
|
|
197
197
|
handleInput(data: string): void;
|
|
198
198
|
getText(): string;
|
|
199
|
+
/** Monotonic buffer-content revision for render caches. Cursor-only movement does not advance it. */
|
|
200
|
+
get textRevision(): number;
|
|
199
201
|
/** Whether the buffer text equals `value`, without `getText()`'s full join —
|
|
200
202
|
* O(1) for the hot per-keystroke probes against short single-line values. */
|
|
201
203
|
textEquals(value: string): boolean;
|
|
@@ -206,6 +208,10 @@ export declare class Editor implements Component, Focusable {
|
|
|
206
208
|
* to `expansion` on submit. Pair with {@link atomicTokenPattern} so the label deletes as a
|
|
207
209
|
* unit. */
|
|
208
210
|
insertAtom(label: string, expansion: string): void;
|
|
211
|
+
/** Collapse the typed span `[start, end)` on `line` into the atom `label` (expanding back
|
|
212
|
+
* to `expansion` on submit). A cursor at or past the span keeps its position relative to
|
|
213
|
+
* the span end; a cursor inside it lands after the label. */
|
|
214
|
+
collapseToAtom(line: number, start: number, end: number, label: string, expansion: string): void;
|
|
209
215
|
/** Drop every registered atom expansion (draft cleared or replaced by the host). */
|
|
210
216
|
clearAtoms(): void;
|
|
211
217
|
/**
|
|
@@ -222,6 +228,16 @@ export declare class Editor implements Component, Focusable {
|
|
|
222
228
|
moveToLineEnd(): void;
|
|
223
229
|
moveToMessageStart(): void;
|
|
224
230
|
moveToMessageEnd(): void;
|
|
231
|
+
/** The `tui.editor.deleteCharForward` operation, callable by hosts that resolve the chord
|
|
232
|
+
* themselves rather than redispatching the raw key (see CustomEditor's exit-chord overlap).
|
|
233
|
+
* Mirrors the transient state the key dispatch tears down before this action so the two
|
|
234
|
+
* cannot diverge: a pending character jump is cancelled by any other key, and an open
|
|
235
|
+
* spelling-assist popup is dismissed by anything that is not one of its accept keys (its
|
|
236
|
+
* debounced refresh skips assist mode, so a surviving list would hang around forever).
|
|
237
|
+
* While Vim owns the buffer (Normal or Visual) the operation is Vim's `x` — deleting the
|
|
238
|
+
* selection and returning to Normal in Visual mode, the grapheme under the cursor
|
|
239
|
+
* otherwise. Only Insert mode and Vim-off editors delete straight through. */
|
|
240
|
+
deleteCharForward(): void;
|
|
225
241
|
/**
|
|
226
242
|
* Undo the last meaningful edit while ignoring transient text that is still present at the cursor.
|
|
227
243
|
* Used for command-like autocomplete actions whose typed trigger should not count as the edit being undone.
|
|
@@ -32,6 +32,16 @@ export declare const DEFAULT_MAX_INLINE_IMAGES = 8;
|
|
|
32
32
|
* rewritten) plus an explicit graphics purge of the demoted ids. {@link Image}
|
|
33
33
|
* reports display order via {@link observe}; when that reveals a stricter split,
|
|
34
34
|
* the TUI repeats the pass before emitting its terminal frame.
|
|
35
|
+
* Retired frames no longer observe their images, so the resident store is also
|
|
36
|
+
* bounded across passes. Evicting retired graphics removes their scrollback
|
|
37
|
+
* placements; a later replay can render or demote those images again.
|
|
38
|
+
*
|
|
39
|
+
* `cap` bounds one surface's live images, not the terminal's whole store. A
|
|
40
|
+
* fullscreen overlay's frame and the normal screen standing behind it are both
|
|
41
|
+
* on the terminal, and neither may delete the other's graphics — see
|
|
42
|
+
* {@link limitResidentImages} — so while a modal is up the store legitimately
|
|
43
|
+
* holds up to `cap` per surface. Read `cap` as "how many images one frame shows
|
|
44
|
+
* as graphics", not as a hard residency ceiling.
|
|
35
45
|
*
|
|
36
46
|
* `cap <= 0` disables budgeting: every image stays a live graphic.
|
|
37
47
|
*/
|
|
@@ -48,22 +58,40 @@ export declare class ImageBudget {
|
|
|
48
58
|
* gets a fresh id every call.
|
|
49
59
|
*/
|
|
50
60
|
acquireId(key?: string): number;
|
|
61
|
+
/**
|
|
62
|
+
* Start an alternate-buffer lifecycle. Call once per `?1049h`, before the
|
|
63
|
+
* first pass of the fullscreen overlay or resize borrow that owns the buffer.
|
|
64
|
+
*
|
|
65
|
+
* The alt split is a claim about the frame standing on that surface, and
|
|
66
|
+
* `?1049h` hands over a cleared one: the previous occupant's threshold would
|
|
67
|
+
* suppress this buffer's leading images against a frame that no longer
|
|
68
|
+
* exists, painting them as text until a corrective render lands. Passes
|
|
69
|
+
* *within* one lifecycle must keep sharing the split — that is what lets an
|
|
70
|
+
* over-cap discovery pass converge before the frame is emitted.
|
|
71
|
+
*/
|
|
72
|
+
beginAltScreenLifecycle(): void;
|
|
51
73
|
/**
|
|
52
74
|
* Begin a render pass. Called by the renderer before composing the frame.
|
|
53
75
|
* Pass `stable: true` for a partial/throwaway pass that does not walk the
|
|
54
76
|
* whole tree in display order (the resize viewport fast path): {@link observe}
|
|
55
77
|
* then replays the last committed per-id decision instead of one derived from
|
|
56
78
|
* call order, and the pass must NOT be closed with {@link endPass}.
|
|
79
|
+
*
|
|
80
|
+
* Pass `altScreen: true` when the frame is painted on the alternate buffer
|
|
81
|
+
* (fullscreen overlay, resize borrow). The pass then reads and writes that
|
|
82
|
+
* surface's own {@link SurfaceSplit} and its live set adds to the recorded
|
|
83
|
+
* normal-screen one instead of replacing it, so a modal's threshold never
|
|
84
|
+
* reaches the transcript standing behind it.
|
|
57
85
|
*/
|
|
58
|
-
beginPass(stable?: boolean): void;
|
|
86
|
+
beginPass(stable?: boolean, altScreen?: boolean): void;
|
|
59
87
|
/**
|
|
60
88
|
* Record an image in display order and report whether it must render its text
|
|
61
89
|
* fallback this frame. Called by every {@link Image} during render — including
|
|
62
90
|
* on a cache hit, so the image keeps its display-order slot.
|
|
63
91
|
*
|
|
64
92
|
* During a `stable` pass ({@link beginPass}) the call order and visible subset
|
|
65
|
-
* are not authoritative, so the decision is the committed
|
|
66
|
-
*
|
|
93
|
+
* are not authoritative, so the decision is the surface's committed
|
|
94
|
+
* on-terminal split, keyed by id — order- and partiality-independent.
|
|
67
95
|
*/
|
|
68
96
|
observe(imageId: number): boolean;
|
|
69
97
|
/**
|
|
@@ -71,6 +99,20 @@ export declare class ImageBudget {
|
|
|
71
99
|
* and must be repeated before its terminal frame is emitted.
|
|
72
100
|
*/
|
|
73
101
|
endPass(): boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Bound the terminal's image store to `cap`. Demotion ({@link endPass}) already
|
|
104
|
+
* retires the graphics this frame replaced with text; this sweeps the ones no
|
|
105
|
+
* frame shows any more — images the pass simply stopped observing.
|
|
106
|
+
*
|
|
107
|
+
* Also records what this frame leaves standing on its surface, which is how
|
|
108
|
+
* the next pass on the *other* surface knows what it may not destroy.
|
|
109
|
+
*/
|
|
110
|
+
limitResidentImages(): void;
|
|
111
|
+
/**
|
|
112
|
+
* Image ids a destructive reset must delete explicitly, alongside its `d=A`.
|
|
113
|
+
* Emit only from that reset's repaint; clears the queue.
|
|
114
|
+
*/
|
|
115
|
+
takeResetPurgeIds(): readonly number[];
|
|
74
116
|
/** Image ids to delete from the terminal this frame; clears the pending set. */
|
|
75
117
|
takePurgeIds(): readonly number[];
|
|
76
118
|
/** All image ids believed to be loaded in the terminal store; clears tracking. */
|
|
@@ -135,10 +177,10 @@ export declare class ImageBudget {
|
|
|
135
177
|
/** Whether a frame has image data queued but not yet written to the terminal. */
|
|
136
178
|
hasPendingTransmits(): boolean;
|
|
137
179
|
/**
|
|
138
|
-
* True when the budget has nothing in flight: no live images
|
|
139
|
-
* the last pass, no queued transmits, no pending purges, and no
|
|
140
|
-
* threshold left to apply. A component-scoped frame may skip the
|
|
141
|
-
* pass only then — a partial tree walk would under-count display order.
|
|
180
|
+
* True when the budget has nothing in flight on either surface: no live images
|
|
181
|
+
* observed on the last pass, no queued transmits, no pending purges, and no
|
|
182
|
+
* stricter threshold left to apply. A component-scoped frame may skip the
|
|
183
|
+
* observe pass only then — a partial tree walk would under-count display order.
|
|
142
184
|
*/
|
|
143
185
|
get quiescent(): boolean;
|
|
144
186
|
/** Transmit sequences to write before this frame's placements; clears the queue. */
|
|
@@ -45,6 +45,12 @@ export declare class TerminalInfo {
|
|
|
45
45
|
* reconstructing positional constructor args.
|
|
46
46
|
*/
|
|
47
47
|
clone(): RuntimeTerminal;
|
|
48
|
+
/**
|
|
49
|
+
* Whether an image marker begins at `start`. Kept as the shared primitive
|
|
50
|
+
* for both standalone image checks and the renderer's combined ANSI/width
|
|
51
|
+
* scan, so their protocol windows and sixel grammar cannot drift.
|
|
52
|
+
*/
|
|
53
|
+
hasImageMarkerAt(line: string, start: number): boolean;
|
|
48
54
|
isImageLine(line: string): boolean;
|
|
49
55
|
formatNotification(message: string | TerminalNotification): string;
|
|
50
56
|
sendNotification(message: string | TerminalNotification): void;
|
|
@@ -119,6 +125,22 @@ export declare function shouldEnableSynchronizedOutputByDefault(env?: NodeJS.Pro
|
|
|
119
125
|
* `PI_NO_DECCARA` kill switch. Pure helper for tests and `TERMINAL` construction.
|
|
120
126
|
*/
|
|
121
127
|
export declare function detectRectangularSgrSupport(terminalId: TerminalId, env?: NodeJS.ProcessEnv): boolean;
|
|
128
|
+
/**
|
|
129
|
+
* Whether the terminal implements colon-subparameter SGR styled underlines —
|
|
130
|
+
* `CSI 4 : 3 m` (curly) plus `CSI 58` / `CSI 59` underline color — as opposed to
|
|
131
|
+
* only the legacy `CSI 4 m` / `CSI 24 m` on/off underline.
|
|
132
|
+
*
|
|
133
|
+
* This is an underline-style capability, not a color depth, so it is keyed on
|
|
134
|
+
* the detected terminal, never on `TERM`/`COLORTERM`. kitty, Ghostty, WezTerm,
|
|
135
|
+
* and iTerm2 (>= 3.5) implement the full pair. Apple Terminal does NOT: it
|
|
136
|
+
* renders `CSI 4 : 0 m` (the reset half) as a solid black background that
|
|
137
|
+
* persists to end of line, and ignores SGR 58/59 — so it, along with every
|
|
138
|
+
* other unproven terminal, gets the flat underline instead. Disabled under any
|
|
139
|
+
* multiplexer: GNU screen and older tmux drop colon-form SGR, and the outer
|
|
140
|
+
* terminal's id leaks into the session env, so a proven id is not proof the
|
|
141
|
+
* bytes survive — the same reason DECCARA and synchronized output gate on it.
|
|
142
|
+
*/
|
|
143
|
+
export declare function detectStyledUnderlineSupport(terminalId: TerminalId, env?: NodeJS.ProcessEnv): boolean;
|
|
122
144
|
/**
|
|
123
145
|
* Resolve an explicit user override for OSC 8 hyperlinks. Returns `false` for
|
|
124
146
|
* an opt-out, `true` for a force-on, or `null` when the user has expressed no
|
|
@@ -191,6 +213,8 @@ export interface RuntimeTerminal extends TerminalInfo {
|
|
|
191
213
|
supportsScreenToScrollback: boolean;
|
|
192
214
|
/** Whether OSC 66 text sizing is currently enabled. */
|
|
193
215
|
textSizing: boolean;
|
|
216
|
+
/** Whether the terminal implements colon-subparameter styled underlines (curly + colored). */
|
|
217
|
+
styledUnderlines: boolean;
|
|
194
218
|
}
|
|
195
219
|
export declare const TERMINAL: RuntimeTerminal;
|
|
196
220
|
/**
|
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
/** True when this process is running inside a Herdr pane. */
|
|
2
2
|
export declare function isInsideHerdr(env?: NodeJS.ProcessEnv): boolean;
|
|
3
|
-
/**
|
|
3
|
+
/** Terminal multiplexers omp recognizes as owning the screen grid. */
|
|
4
|
+
export type TerminalMultiplexer = "herdr" | "tmux" | "screen" | "zellij" | "cmux" | "wmux";
|
|
5
|
+
/**
|
|
6
|
+
* Classify which terminal multiplexer owns the current screen grid, or `null`
|
|
7
|
+
* for a direct terminal. Single source of truth for both the render-path gate
|
|
8
|
+
* (`isInsideTerminalMultiplexer`) and the debug snapshot label.
|
|
9
|
+
*
|
|
10
|
+
* TMUX/STY/ZELLIJ, Herdr, and the CMUX/WMUX workspace/surface/remote-transport
|
|
11
|
+
* markers are authoritative session signals. TERM can also survive when those
|
|
12
|
+
* are stripped (`sudo` without -E, `su`, env-sanitizing launchers/ssh). Do not
|
|
13
|
+
* use CMUX_SOCKET_PATH / WMUX_CLI / WMUX_PIPE here: they are CLI socket/path
|
|
14
|
+
* overrides and can be set outside a CMUX/WMUX terminal. wmux is a Windows
|
|
15
|
+
* multiplexer (Electron + xterm.js) modeled on cmux/herdr that repaints its
|
|
16
|
+
* pane in place and exports WMUX=1 plus a native WMUX_SURFACE_ID.
|
|
17
|
+
*/
|
|
18
|
+
export declare function classifyTerminalMultiplexer(env?: NodeJS.ProcessEnv): TerminalMultiplexer | null;
|
|
19
|
+
/** True when a terminal multiplexer owns the current screen grid. */
|
|
4
20
|
export declare function isInsideTerminalMultiplexer(env?: NodeJS.ProcessEnv): boolean;
|
package/dist/types/terminal.d.ts
CHANGED
|
@@ -142,6 +142,19 @@ export interface Terminal {
|
|
|
142
142
|
* built against older pi-tui versions keep working.
|
|
143
143
|
*/
|
|
144
144
|
readonly pendingOutputBytes?: number;
|
|
145
|
+
/**
|
|
146
|
+
* Whether a pseudoconsole host owns the grid this terminal writes to, so
|
|
147
|
+
* neither the cursor nor the painted rows survive a resize under the
|
|
148
|
+
* application's own model. Measured on Windows conhost: resizing the
|
|
149
|
+
* pseudoconsole makes it re-emit its whole viewport from `CSI H` with
|
|
150
|
+
* absolute addressing while the application writes nothing, and it re-homes
|
|
151
|
+
* the cursor, so a DSR reply after a resize reports column 1 instead of the
|
|
152
|
+
* column the application parked. The renderer's resize anchor recovery needs
|
|
153
|
+
* both properties, so it takes the rebuild path instead when this is set.
|
|
154
|
+
* Optional so custom Terminals built against older pi-tui versions keep
|
|
155
|
+
* working; absent means the terminal itself owns the grid.
|
|
156
|
+
*/
|
|
157
|
+
readonly hostOwnsGridOnResize?: boolean;
|
|
145
158
|
get kittyProtocolActive(): boolean;
|
|
146
159
|
get kittyEnableSequence(): string | null;
|
|
147
160
|
readonly keyboardEnhancementEnterSequence?: string | null;
|
|
@@ -209,8 +222,9 @@ export declare function isConPTYHosted(): boolean;
|
|
|
209
222
|
export interface ProcessTerminalOptions {
|
|
210
223
|
/**
|
|
211
224
|
* Force ConPTY-hosted behavior on or off. Defaults to live detection via
|
|
212
|
-
* {@link isConPTYHosted}. Tests set this so the kitty-flag
|
|
213
|
-
*
|
|
225
|
+
* {@link isConPTYHosted}. Tests set this so the kitty-flag, write-chunking
|
|
226
|
+
* and resize-routing ({@link Terminal.hostOwnsGridOnResize}) paths stay
|
|
227
|
+
* hermetic regardless of the ambient WSL env (`WSL_DISTRO_NAME` /
|
|
214
228
|
* `WSL_INTEROP`) — the suite must behave identically on WSL and on CI.
|
|
215
229
|
*/
|
|
216
230
|
conpty?: boolean;
|
|
@@ -247,6 +261,7 @@ export declare class ProcessTerminal implements Terminal {
|
|
|
247
261
|
write(data: string): void;
|
|
248
262
|
get columns(): number;
|
|
249
263
|
get pendingOutputBytes(): number;
|
|
264
|
+
get hostOwnsGridOnResize(): boolean;
|
|
250
265
|
get rows(): number;
|
|
251
266
|
moveBy(lines: number): void;
|
|
252
267
|
hideCursor(force?: boolean): void;
|
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.1
|
|
4
|
+
"version": "18.2.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": "Stencil Labs, Inc.",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@oh-my-pi/pi-natives": "18.1
|
|
41
|
-
"@oh-my-pi/pi-utils": "18.1
|
|
40
|
+
"@oh-my-pi/pi-natives": "18.2.1",
|
|
41
|
+
"@oh-my-pi/pi-utils": "18.2.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"kitty-vt-wasm": "^0.2.0"
|
package/src/autocomplete.ts
CHANGED
|
@@ -351,7 +351,7 @@ function buildSlashCommandCompletions(
|
|
|
351
351
|
: isSkillCommand
|
|
352
352
|
? Math.max(
|
|
353
353
|
scoreCommandTextMatch(lowerPrefix, name.toLowerCase()),
|
|
354
|
-
|
|
354
|
+
skillBareNameBreakoutTier(lowerPrefix, name.slice(SKILL_NAMESPACE.length).toLowerCase()),
|
|
355
355
|
)
|
|
356
356
|
: scoreCommandTextMatch(lowerPrefix, name.toLowerCase());
|
|
357
357
|
const lowerDesc = staticDesc.toLowerCase();
|
|
@@ -410,29 +410,54 @@ function hasPromptTextBeforeSlash(
|
|
|
410
410
|
|
|
411
411
|
export const SKILL_NAMESPACE = "skill:";
|
|
412
412
|
|
|
413
|
-
/**
|
|
414
|
-
|
|
415
|
-
* names when deciding whether the skill may break out of the collapsed
|
|
416
|
-
* `skill:` group: exact (1000) > prefix (900) > anything weaker (0). Fuzzy
|
|
417
|
-
* hits deliberately map to 0 — a fuzzy skill match is never strong enough to
|
|
418
|
-
* mix skills into the command popup.
|
|
419
|
-
*/
|
|
420
|
-
function skillBreakoutTier(lowerPrefix: string, lowerTarget: string): number {
|
|
413
|
+
/** Exact/leading-prefix tier for ordinary command names and aliases. */
|
|
414
|
+
function commandBreakoutTier(lowerPrefix: string, lowerTarget: string): number {
|
|
421
415
|
if (lowerPrefix === lowerTarget) return 1000;
|
|
422
416
|
if (lowerTarget.startsWith(lowerPrefix)) return 900;
|
|
423
417
|
return 0;
|
|
424
418
|
}
|
|
425
419
|
|
|
420
|
+
/**
|
|
421
|
+
* Match a bare skill name from the beginning of any hyphen-delimited segment.
|
|
422
|
+
* This stays allocation-free on the hot path: it scans segment boundaries
|
|
423
|
+
* in-place and never materializes split/slice arrays.
|
|
424
|
+
*/
|
|
425
|
+
function skillBareNameBreakoutTier(lowerPrefix: string, lowerBareName: string): number {
|
|
426
|
+
if (lowerPrefix.length === 0) return 0;
|
|
427
|
+
if (lowerPrefix === lowerBareName) return 1000;
|
|
428
|
+
if (lowerBareName.startsWith(lowerPrefix)) return 900;
|
|
429
|
+
|
|
430
|
+
let segmentStart = 0;
|
|
431
|
+
while (segmentStart < lowerBareName.length) {
|
|
432
|
+
while (segmentStart < lowerBareName.length && lowerBareName.charCodeAt(segmentStart) !== 45) {
|
|
433
|
+
segmentStart += 1;
|
|
434
|
+
}
|
|
435
|
+
segmentStart += 1;
|
|
436
|
+
if (segmentStart >= lowerBareName.length) break;
|
|
437
|
+
|
|
438
|
+
if (lowerBareName.startsWith(lowerPrefix, segmentStart)) {
|
|
439
|
+
let segmentEnd = segmentStart;
|
|
440
|
+
while (segmentEnd < lowerBareName.length && lowerBareName.charCodeAt(segmentEnd) !== 45) {
|
|
441
|
+
segmentEnd += 1;
|
|
442
|
+
}
|
|
443
|
+
return lowerPrefix.length === segmentEnd - segmentStart ? 1000 : 900;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return 0;
|
|
448
|
+
}
|
|
449
|
+
|
|
426
450
|
/**
|
|
427
451
|
* Collapse `skill:*` commands into a single `/skill:` namespace row while the
|
|
428
452
|
* typed prefix has not committed to the namespace. A lone group entry (shown
|
|
429
453
|
* only while the prefix is still a prefix of `skill:`) keeps the `/` popup
|
|
430
454
|
* readable. A skill breaks out of the group only when its bare name matches
|
|
431
|
-
* the prefix at
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
* never surface. Accepting the group
|
|
435
|
-
* space so the reopened popup expands to
|
|
455
|
+
* the prefix at the beginning of the name or a hyphen-delimited segment, at a
|
|
456
|
+
* strictly stronger tier than every non-skill command name and alias. Ordinary
|
|
457
|
+
* commands keep exact/leading-prefix tiers only; a tie keeps the popup
|
|
458
|
+
* command-only, and fuzzy-only skill hits never surface. Accepting the group
|
|
459
|
+
* inserts `/skill:` without a trailing space so the reopened popup expands to
|
|
460
|
+
* the individual skills.
|
|
436
461
|
*/
|
|
437
462
|
function collapseSkillNamespace(commands: CommandEntry[], lowerPrefix: string): CommandEntry[] {
|
|
438
463
|
if (lowerPrefix.startsWith(SKILL_NAMESPACE)) return commands;
|
|
@@ -442,9 +467,9 @@ function collapseSkillNamespace(commands: CommandEntry[], lowerPrefix: string):
|
|
|
442
467
|
for (const cmd of commands) {
|
|
443
468
|
const name = getCommandName(cmd);
|
|
444
469
|
if (!name || name.startsWith(SKILL_NAMESPACE)) continue;
|
|
445
|
-
commandTier = Math.max(commandTier,
|
|
470
|
+
commandTier = Math.max(commandTier, commandBreakoutTier(lowerPrefix, name.toLowerCase()));
|
|
446
471
|
for (const alias of getCommandAliases(cmd)) {
|
|
447
|
-
commandTier = Math.max(commandTier,
|
|
472
|
+
commandTier = Math.max(commandTier, commandBreakoutTier(lowerPrefix, alias.toLowerCase()));
|
|
448
473
|
}
|
|
449
474
|
if (commandTier === 1000) break;
|
|
450
475
|
}
|
|
@@ -458,7 +483,7 @@ function collapseSkillNamespace(commands: CommandEntry[], lowerPrefix: string):
|
|
|
458
483
|
skillIcon ??= cmd.icon;
|
|
459
484
|
return (
|
|
460
485
|
!approachesNamespace &&
|
|
461
|
-
|
|
486
|
+
skillBareNameBreakoutTier(lowerPrefix, name.slice(SKILL_NAMESPACE.length).toLowerCase()) > commandTier
|
|
462
487
|
);
|
|
463
488
|
});
|
|
464
489
|
if (skillCount === 0) return commands;
|
|
@@ -478,7 +503,8 @@ function collapseSkillNamespace(commands: CommandEntry[], lowerPrefix: string):
|
|
|
478
503
|
* popup alive through fuzzy name/description hits, so a token only matches as
|
|
479
504
|
* - a prefix of the `skill:` namespace (incl. the bare `/` entry point),
|
|
480
505
|
* - an explicit `skill:…` query (full fuzzy name/description search), or
|
|
481
|
-
* - a prefix of the skill
|
|
506
|
+
* - a prefix at the start of the skill bare name or one of its hyphen-delimited
|
|
507
|
+
* segments (`/hum` → `skill:humanizer`, `/last` → `skill:research-last30days`).
|
|
482
508
|
* Anything else yields no items, letting the caller fall through to path
|
|
483
509
|
* completion or close the popup. Shared with the editor's accept-time
|
|
484
510
|
* staleness guard so Tab/Enter never accepts a skill the refreshed popup
|
|
@@ -491,7 +517,10 @@ export function midPromptSkillTokenMatches(lowerToken: string, name: string, des
|
|
|
491
517
|
if (scoreCommandTextMatch(lowerToken, lowerName) > 0) return true;
|
|
492
518
|
return !!description && scoreCommandTextMatch(lowerToken, description.toLowerCase()) > 0;
|
|
493
519
|
}
|
|
494
|
-
return
|
|
520
|
+
return (
|
|
521
|
+
lowerName.startsWith(SKILL_NAMESPACE) &&
|
|
522
|
+
skillBareNameBreakoutTier(lowerToken, lowerName.slice(SKILL_NAMESPACE.length)) > 0
|
|
523
|
+
);
|
|
495
524
|
}
|
|
496
525
|
|
|
497
526
|
function buildMidPromptSkillCompletions(commands: CommandEntry[], lowerPrefix: string): AutocompleteItem[] {
|