@oh-my-pi/pi-tui 16.5.2 → 17.0.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 +27 -0
- package/dist/types/components/editor.d.ts +2 -0
- package/dist/types/components/markdown.d.ts +10 -2
- package/dist/types/terminal.d.ts +5 -3
- package/dist/types/tui.d.ts +11 -1
- package/package.json +3 -3
- package/src/autocomplete.ts +14 -16
- package/src/components/editor.ts +26 -3
- package/src/components/markdown.ts +268 -31
- package/src/latex-block.ts +110 -2
- package/src/terminal-capabilities.ts +50 -3
- package/src/terminal.ts +29 -22
- package/src/tui.ts +66 -50
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.0.1] - 2026-07-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added native cmux notification delivery targeted to the current terminal surface.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed a tmux regression where every non-Kitty pane was forced into legacy keyboard input, collapsing Ctrl+H into Backspace and Shift+Enter into Enter even with `extended-keys on`; the xterm modifyOtherKeys fallback is requested again so tmux honors or ignores it per its own `extended-keys` setting ([#5620](https://github.com/can1357/oh-my-pi/issues/5620)).
|
|
14
|
+
- Fixed `@` file-reference and path completion falling through incorrectly inside slash command arguments when command-specific argument completion has no matches ([#5580](https://github.com/can1357/oh-my-pi/issues/5580)).
|
|
15
|
+
- Fixed streamed Markdown tables reflowing rows already written to native scrollback when later cells widen a column.
|
|
16
|
+
- Fixed fullscreen session-replacement overlays and resize drags exposing stale normal-buffer frames on terminals without effective DEC 2026: asynchronous replacements now keep their overlay visible until the rebuilt transcript is ready, overlay exit is fused into the destructive paint, and resize viewport frames rewrite the normal buffer without alternate-screen switches. Inconclusive DECRQM probes also no longer disable statically detected synchronized output ([#5319](https://github.com/can1357/oh-my-pi/issues/5319)).
|
|
17
|
+
- Fixed autocomplete popups moving Windows Terminal IME candidate windows away from the prompt by keeping the terminal cursor anchored at the text insertion point ([#4760](https://github.com/can1357/oh-my-pi/issues/4760)).
|
|
18
|
+
|
|
19
|
+
## [17.0.0] - 2026-07-15
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Improved LaTeX rendering for \underbrace, \overbrace, \overset, \underset, and \stackrel to use drawn horizontal braces with centered labels and stacked annotations instead of flat inline glyphs.
|
|
24
|
+
- Improved LaTeX rendering of multi-letter subscripts and superscripts by displaying them as raised or lowered blocks instead of ragged per-character Unicode glyphs.
|
|
25
|
+
- Added an opt-in Editor.setImeSafeCursorLayout() method to protect macOS IME preedit while retaining the compact bordered layout by default.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Fixed SIXEL image rendering where images with cell heights not divisible by 6 would have their bottom portion overwritten by subsequent content.
|
|
30
|
+
- Fixed an issue where the Kitty OSC 99 desktop-notification capability probe would leak raw text into the terminal pane when running inside a multiplexer like tmux or screen.
|
|
31
|
+
|
|
5
32
|
## [16.5.2] - 2026-07-14
|
|
6
33
|
|
|
7
34
|
### Fixed
|
|
@@ -92,6 +92,8 @@ export declare class Editor implements Component, Focusable {
|
|
|
92
92
|
* Use the real terminal cursor instead of rendering a cursor glyph.
|
|
93
93
|
*/
|
|
94
94
|
setUseTerminalCursor(useTerminalCursor: boolean): void;
|
|
95
|
+
/** Render a dedicated bottom border so terminal-local IME preedit cannot shift editor chrome. */
|
|
96
|
+
setImeSafeCursorLayout(enabled: boolean): void;
|
|
95
97
|
getUseTerminalCursor(): boolean;
|
|
96
98
|
setMaxHeight(maxHeight: number | undefined): void;
|
|
97
99
|
setPaddingX(paddingX: number): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SymbolTheme } from "../symbols.js";
|
|
2
|
-
import type { Component } from "../tui.js";
|
|
2
|
+
import type { Component, NativeScrollbackCommittedRows, NativeScrollbackReplay } from "../tui.js";
|
|
3
3
|
/** Drop all L2 cache entries. Call on theme change to prevent stale styled output. */
|
|
4
4
|
export declare function clearRenderCache(): void;
|
|
5
5
|
/**
|
|
@@ -47,7 +47,7 @@ export interface MarkdownTheme {
|
|
|
47
47
|
resolveMermaidAscii?: (source: string, maxWidth?: number) => string | null;
|
|
48
48
|
symbols: SymbolTheme;
|
|
49
49
|
}
|
|
50
|
-
export declare class Markdown implements Component {
|
|
50
|
+
export declare class Markdown implements Component, NativeScrollbackCommittedRows, NativeScrollbackReplay {
|
|
51
51
|
#private;
|
|
52
52
|
setIgnoreTight(ignore: boolean): this;
|
|
53
53
|
constructor(text: string, paddingX: number, paddingY: number, theme: MarkdownTheme, defaultTextStyle?: DefaultTextStyle, codeBlockIndent?: number);
|
|
@@ -65,6 +65,14 @@ export declare class Markdown implements Component {
|
|
|
65
65
|
* lineage), and on cache-served non-streaming renders.
|
|
66
66
|
*/
|
|
67
67
|
getLastRenderSettledRows(): number;
|
|
68
|
+
/**
|
|
69
|
+
* Freeze every table whose first physical row is already part of the native
|
|
70
|
+
* scrollback prefix. The recorded widths came from the exact frame that was
|
|
71
|
+
* just emitted, so the next streamed delta cannot retroactively widen it.
|
|
72
|
+
*/
|
|
73
|
+
setNativeScrollbackCommittedRows(rows: number): void;
|
|
74
|
+
/** A destructive replay removes the immutable tape this layout was guarding. */
|
|
75
|
+
prepareNativeScrollbackReplay(): void;
|
|
68
76
|
render(width: number): readonly string[];
|
|
69
77
|
}
|
|
70
78
|
/**
|
package/dist/types/terminal.d.ts
CHANGED
|
@@ -67,9 +67,11 @@ export interface Terminal {
|
|
|
67
67
|
get appearance(): TerminalAppearance | undefined;
|
|
68
68
|
/**
|
|
69
69
|
* Register a callback fired once per DEC private mode when its DECRQM support
|
|
70
|
-
* status resolves.
|
|
70
|
+
* status resolves. `confirmed` is false when the terminal answered the DA1
|
|
71
|
+
* sentinel without answering DECRQM, which proves only that querying support
|
|
72
|
+
* is unavailable — not that the private mode itself is unsupported.
|
|
71
73
|
*/
|
|
72
|
-
onPrivateModeReport?(callback: (mode: number, supported: boolean) => void): void;
|
|
74
|
+
onPrivateModeReport?(callback: (mode: number, supported: boolean, confirmed?: boolean) => void): void;
|
|
73
75
|
}
|
|
74
76
|
/**
|
|
75
77
|
* True when stdout flows through a ConPTY pseudo-console (native win32, or
|
|
@@ -91,7 +93,7 @@ export declare class ProcessTerminal implements Terminal {
|
|
|
91
93
|
get keyboardEnhancementExitSequence(): string | null;
|
|
92
94
|
get appearance(): TerminalAppearance | undefined;
|
|
93
95
|
onAppearanceChange(callback: (appearance: TerminalAppearance) => void): void;
|
|
94
|
-
onPrivateModeReport(callback: (mode: number, supported: boolean) => void): void;
|
|
96
|
+
onPrivateModeReport(callback: (mode: number, supported: boolean, confirmed?: boolean) => void): void;
|
|
95
97
|
start(onInput: (data: string) => void, onResize: () => void): void;
|
|
96
98
|
drainInput(maxMs?: number, idleMs?: number): Promise<void>;
|
|
97
99
|
stop(): void;
|
package/dist/types/tui.d.ts
CHANGED
|
@@ -251,7 +251,7 @@ export interface OverlayHandle {
|
|
|
251
251
|
/**
|
|
252
252
|
* Container - a component that contains other components
|
|
253
253
|
*/
|
|
254
|
-
export declare class Container implements Component {
|
|
254
|
+
export declare class Container implements Component, NativeScrollbackCommittedRows, NativeScrollbackReplay {
|
|
255
255
|
#private;
|
|
256
256
|
children: Component[];
|
|
257
257
|
setIgnoreTight(ignore: boolean): this;
|
|
@@ -267,6 +267,16 @@ export declare class Container implements Component {
|
|
|
267
267
|
* {@link clear} for that). Idempotent per child via each child's own dispose.
|
|
268
268
|
*/
|
|
269
269
|
dispose(): void;
|
|
270
|
+
/**
|
|
271
|
+
* Split the committed prefix from the container's most recently rendered
|
|
272
|
+
* rows across its children. The memoized child arrays are the exact geometry
|
|
273
|
+
* that produced that frame; when the child list was invalidated or rebuilt,
|
|
274
|
+
* there is no safe old-to-new coordinate mapping, so propagation waits for
|
|
275
|
+
* the next render/post-emit publication.
|
|
276
|
+
*/
|
|
277
|
+
setNativeScrollbackCommittedRows(rows: number): void;
|
|
278
|
+
/** Recursively discard layout locks that are meaningful only to the old tape. */
|
|
279
|
+
prepareNativeScrollbackReplay(): void;
|
|
270
280
|
render(width: number): readonly string[];
|
|
271
281
|
}
|
|
272
282
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-tui",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "17.0.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": "Can Boluk",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"fmt": "biome format --write ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@oh-my-pi/pi-natives": "
|
|
41
|
-
"@oh-my-pi/pi-utils": "
|
|
40
|
+
"@oh-my-pi/pi-natives": "17.0.1",
|
|
41
|
+
"@oh-my-pi/pi-utils": "17.0.1",
|
|
42
42
|
"lru-cache": "11.5.2",
|
|
43
43
|
"marked": "^18.0.6"
|
|
44
44
|
},
|
package/src/autocomplete.ts
CHANGED
|
@@ -464,10 +464,9 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
464
464
|
// path (`/tmp/fo` at prompt start, `see /tmp` mid-prompt); fall
|
|
465
465
|
// through to file-path completion.
|
|
466
466
|
} else if (!isMidPromptSkillLookup) {
|
|
467
|
-
//
|
|
468
|
-
//
|
|
469
|
-
//
|
|
470
|
-
// completions because submit treats them as normal prompt text.
|
|
467
|
+
// Give matched commands first chance to complete arguments, then
|
|
468
|
+
// fall through to prompt-composer file completion when they have
|
|
469
|
+
// no argument provider or it has no matches.
|
|
471
470
|
const commandName = commandText.slice(1, spaceIndex); // Command without "/"
|
|
472
471
|
const argumentText = commandText.slice(spaceIndex + 1); // Text after space
|
|
473
472
|
|
|
@@ -475,20 +474,19 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
475
474
|
if (command && "allowArgs" in command && command.allowArgs === false && !/\S/.test(argumentText)) {
|
|
476
475
|
return null;
|
|
477
476
|
}
|
|
478
|
-
if (
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
477
|
+
if (
|
|
478
|
+
command &&
|
|
479
|
+
(!("allowArgs" in command) || command.allowArgs !== false) &&
|
|
480
|
+
"getArgumentCompletions" in command &&
|
|
481
|
+
command.getArgumentCompletions
|
|
482
|
+
) {
|
|
483
483
|
const argumentSuggestions = await command.getArgumentCompletions(argumentText);
|
|
484
|
-
if (
|
|
485
|
-
return
|
|
484
|
+
if (Array.isArray(argumentSuggestions) && argumentSuggestions.length > 0) {
|
|
485
|
+
return {
|
|
486
|
+
items: argumentSuggestions,
|
|
487
|
+
prefix: argumentText,
|
|
488
|
+
};
|
|
486
489
|
}
|
|
487
|
-
|
|
488
|
-
return {
|
|
489
|
-
items: argumentSuggestions,
|
|
490
|
-
prefix: argumentText,
|
|
491
|
-
};
|
|
492
490
|
}
|
|
493
491
|
}
|
|
494
492
|
}
|
package/src/components/editor.ts
CHANGED
|
@@ -382,6 +382,7 @@ export class Editor implements Component, Focusable {
|
|
|
382
382
|
|
|
383
383
|
#theme: EditorTheme;
|
|
384
384
|
#useTerminalCursor = false;
|
|
385
|
+
#imeSafeCursorLayout = false;
|
|
385
386
|
|
|
386
387
|
/** When set, replaces the normal cursor glyph at end-of-text with this ANSI-styled string. */
|
|
387
388
|
cursorOverride: string | undefined;
|
|
@@ -539,6 +540,11 @@ export class Editor implements Component, Focusable {
|
|
|
539
540
|
this.#useTerminalCursor = useTerminalCursor;
|
|
540
541
|
}
|
|
541
542
|
|
|
543
|
+
/** Render a dedicated bottom border so terminal-local IME preedit cannot shift editor chrome. */
|
|
544
|
+
setImeSafeCursorLayout(enabled: boolean): void {
|
|
545
|
+
this.#imeSafeCursorLayout = enabled;
|
|
546
|
+
}
|
|
547
|
+
|
|
542
548
|
getUseTerminalCursor(): boolean {
|
|
543
549
|
return this.#useTerminalCursor;
|
|
544
550
|
}
|
|
@@ -852,8 +858,9 @@ export class Editor implements Component, Focusable {
|
|
|
852
858
|
}
|
|
853
859
|
|
|
854
860
|
// Render each layout line
|
|
855
|
-
//
|
|
856
|
-
|
|
861
|
+
// Keep the hardware cursor at the text insertion point while autocomplete
|
|
862
|
+
// rows render below it; terminals use that position to anchor IME candidates.
|
|
863
|
+
const emitCursorMarker = this.focused;
|
|
857
864
|
const lineContentWidth = contentAreaWidth;
|
|
858
865
|
|
|
859
866
|
// Compute inline hint text (dim ghost text after cursor)
|
|
@@ -866,6 +873,7 @@ export class Editor implements Component, Focusable {
|
|
|
866
873
|
let displayWidth = visibleWidth(layoutLine.text);
|
|
867
874
|
let cursorPaddingOverflow = 0;
|
|
868
875
|
let decorated = false;
|
|
876
|
+
let imeSafeCursorTail = false;
|
|
869
877
|
const showPromptGutter = promptGutter !== undefined && visibleIndex === 0;
|
|
870
878
|
const gutterText =
|
|
871
879
|
promptGutter === undefined ? "" : showPromptGutter ? promptGutter.firstLine : promptGutter.continuation;
|
|
@@ -922,7 +930,13 @@ export class Editor implements Component, Focusable {
|
|
|
922
930
|
if (marker) {
|
|
923
931
|
const before = displayText.slice(0, layoutLine.cursorPos);
|
|
924
932
|
const after = displayText.slice(layoutLine.cursorPos);
|
|
925
|
-
if (after.length === 0 &&
|
|
933
|
+
if (this.#imeSafeCursorLayout && after.length === 0 && borderVisible) {
|
|
934
|
+
// Terminal frontends render IME marked text locally before committed bytes
|
|
935
|
+
// reach the application. Keep the end-of-input cursor row empty to its
|
|
936
|
+
// right so that insertion cannot shift box chrome onto the next row.
|
|
937
|
+
displayText = before + marker;
|
|
938
|
+
imeSafeCursorTail = true;
|
|
939
|
+
} else if (after.length === 0 && inlineHint) {
|
|
926
940
|
const availWidth = Math.max(0, lineContentWidth - displayWidth);
|
|
927
941
|
const hintText = hintStyle(truncateToWidth(inlineHint, availWidth));
|
|
928
942
|
displayText = before + marker + hintText;
|
|
@@ -1022,6 +1036,15 @@ export class Editor implements Component, Focusable {
|
|
|
1022
1036
|
// trailing `─`, but never the corner/vertical bar itself.
|
|
1023
1037
|
const isLastLine = visibleIndex === visibleLayoutLines.length - 1;
|
|
1024
1038
|
const rightChromeCells = Math.max(1, paddingX + 1 - cursorPaddingOverflow);
|
|
1039
|
+
if (isLastLine && imeSafeCursorTail) {
|
|
1040
|
+
const leftBorder = this.borderColor(`${box.vertical}${padding(paddingX)}`);
|
|
1041
|
+
const bottomBorder = this.borderColor(
|
|
1042
|
+
`${box.bottomLeft}${box.horizontal.repeat(Math.max(0, width - 2))}${box.bottomRight}`,
|
|
1043
|
+
);
|
|
1044
|
+
result.push(leftBorder + displayText);
|
|
1045
|
+
result.push(bottomBorder);
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1025
1048
|
if (isLastLine) {
|
|
1026
1049
|
const rightPad = Math.max(0, rightChromeCells - 2);
|
|
1027
1050
|
const includeHorizontal = rightChromeCells >= 2;
|