@oh-my-pi/pi-tui 18.1.4 → 18.1.5
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 +7 -0
- package/dist/types/components/editor.d.ts +9 -3
- package/dist/types/stdin-buffer.d.ts +6 -1
- package/package.json +3 -3
- package/src/components/editor.ts +13 -6
- package/src/stdin-buffer.ts +27 -4
- package/src/terminal.ts +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.1.5] - 2026-09-03
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed terminal query support in supervised PTY processes, including cursor position reports.
|
|
10
|
+
- Fixed paste-and-submit handling so an Enter keypress received with a bracketed paste is delivered to the previously focused component; `Editor.onLargePaste` now receives `PasteOptions` describing the queued submit.
|
|
11
|
+
|
|
5
12
|
## [18.1.3] - 2026-09-02
|
|
6
13
|
|
|
7
14
|
### Fixed
|
|
@@ -57,6 +57,11 @@ export interface EditorTextAssistProvider {
|
|
|
57
57
|
/** Return replacement candidates for the misspelled word at the cursor. */
|
|
58
58
|
getWordReplacements?(lines: string[], cursorLine: number, cursorCol: number): EditorWordReplacements | null | Promise<EditorWordReplacements | null>;
|
|
59
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
|
+
}
|
|
60
65
|
export declare class Editor implements Component, Focusable {
|
|
61
66
|
#private;
|
|
62
67
|
/** Focusable interface - set by TUI when focus changes */
|
|
@@ -89,8 +94,9 @@ export declare class Editor implements Component, Focusable {
|
|
|
89
94
|
* the editor inserts nothing and records no undo state, leaving insertion to the host (e.g. a
|
|
90
95
|
* "wrap in a code block / XML / attach as file" menu for very large pastes), which re-inserts
|
|
91
96
|
* via {@link insertPaste} or {@link insertText}. Return `false` (or leave unset) for the
|
|
92
|
-
* default collapse-to-marker behavior. `lineCount` is the sanitized paste's line count
|
|
93
|
-
|
|
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;
|
|
94
100
|
onAutocompleteCancel?: () => void;
|
|
95
101
|
disableSubmit: boolean;
|
|
96
102
|
constructor(theme: EditorTheme);
|
|
@@ -210,7 +216,7 @@ export declare class Editor implements Component, Focusable {
|
|
|
210
216
|
/** Drop any volatile preview, then insert `text` as a single undoable edit. */
|
|
211
217
|
commitVolatileText(text: string): void;
|
|
212
218
|
/** Apply terminal paste semantics to text from non-bracketed paste transports. */
|
|
213
|
-
pasteText(text: string): void;
|
|
219
|
+
pasteText(text: string, options?: PasteOptions): void;
|
|
214
220
|
/** Insert `content` as a collapsed `[Paste #N]` marker (stored for expansion on submit via
|
|
215
221
|
* {@link getExpandedText}). Hosts that intercept large pastes through {@link onLargePaste} use
|
|
216
222
|
* this to re-insert a (possibly transformed) paste without re-triggering the interception hook. */
|
|
@@ -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.
|
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.1.5",
|
|
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.1.5",
|
|
41
|
+
"@oh-my-pi/pi-utils": "18.1.5"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"kitty-vt-wasm": "^0.2.0"
|
package/src/components/editor.ts
CHANGED
|
@@ -467,6 +467,12 @@ export interface EditorTextAssistProvider {
|
|
|
467
467
|
type HistoryCursorAnchor = "start" | "end";
|
|
468
468
|
type AutocompleteRequest = { kind: "regular"; explicitTab: boolean } | { kind: "force" };
|
|
469
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
|
+
|
|
470
476
|
export class Editor implements Component, Focusable {
|
|
471
477
|
#state: EditorState = {
|
|
472
478
|
lines: [""],
|
|
@@ -581,8 +587,9 @@ export class Editor implements Component, Focusable {
|
|
|
581
587
|
* the editor inserts nothing and records no undo state, leaving insertion to the host (e.g. a
|
|
582
588
|
* "wrap in a code block / XML / attach as file" menu for very large pastes), which re-inserts
|
|
583
589
|
* via {@link insertPaste} or {@link insertText}. Return `false` (or leave unset) for the
|
|
584
|
-
* default collapse-to-marker behavior. `lineCount` is the sanitized paste's line count
|
|
585
|
-
|
|
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;
|
|
586
593
|
onAutocompleteCancel?: () => void;
|
|
587
594
|
disableSubmit: boolean = false;
|
|
588
595
|
|
|
@@ -2148,8 +2155,8 @@ export class Editor implements Component, Focusable {
|
|
|
2148
2155
|
}
|
|
2149
2156
|
|
|
2150
2157
|
/** Apply terminal paste semantics to text from non-bracketed paste transports. */
|
|
2151
|
-
pasteText(text: string): void {
|
|
2152
|
-
this.#handlePaste(text);
|
|
2158
|
+
pasteText(text: string, options: PasteOptions = {}): void {
|
|
2159
|
+
this.#handlePaste(text, options);
|
|
2153
2160
|
}
|
|
2154
2161
|
|
|
2155
2162
|
/** Insert `content` as a collapsed `[Paste #N]` marker (stored for expansion on submit via
|
|
@@ -2289,7 +2296,7 @@ export class Editor implements Component, Focusable {
|
|
|
2289
2296
|
}
|
|
2290
2297
|
}
|
|
2291
2298
|
|
|
2292
|
-
#handlePaste(pastedText: string): void {
|
|
2299
|
+
#handlePaste(pastedText: string, options: PasteOptions = {}): void {
|
|
2293
2300
|
let filteredText = this.#sanitizePastedText(pastedText);
|
|
2294
2301
|
|
|
2295
2302
|
// If pasting a file path (starts with /, ~, or .) and the character before
|
|
@@ -2311,7 +2318,7 @@ export class Editor implements Component, Focusable {
|
|
|
2311
2318
|
// Let the host intercept marker-sized pastes (e.g. the large-paste menu). When it takes
|
|
2312
2319
|
// over, the editor inserts nothing and records no undo state — the host re-inserts via
|
|
2313
2320
|
// `insertPaste`/`insertText` once the user chooses.
|
|
2314
|
-
if (isMarkerSized && this.onLargePaste?.(filteredText, pastedLines.length)) {
|
|
2321
|
+
if (isMarkerSized && this.onLargePaste?.(filteredText, pastedLines.length, options)) {
|
|
2315
2322
|
return;
|
|
2316
2323
|
}
|
|
2317
2324
|
|
package/src/stdin-buffer.ts
CHANGED
|
@@ -375,9 +375,27 @@ export type StdinBufferOptions = {
|
|
|
375
375
|
pasteByteLimit?: number;
|
|
376
376
|
};
|
|
377
377
|
|
|
378
|
+
const KITTY_ENTER = /^\x1b\[13(?:;1)?u/u;
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* The Enter keypress that immediately follows a bracketed-paste end marker in
|
|
382
|
+
* the same stdin read: legacy CR/LF, or the kitty CSI-u encoding with no
|
|
383
|
+
* modifier. Nothing else qualifies — any other trailing byte takes the normal
|
|
384
|
+
* data route.
|
|
385
|
+
*/
|
|
386
|
+
function leadingEnter(input: string): string | undefined {
|
|
387
|
+
if (input.startsWith("\r") || input.startsWith("\n")) return input[0];
|
|
388
|
+
return KITTY_ENTER.exec(input)?.[0];
|
|
389
|
+
}
|
|
390
|
+
|
|
378
391
|
export type StdinBufferEventMap = {
|
|
379
392
|
data: [string];
|
|
380
|
-
|
|
393
|
+
/**
|
|
394
|
+
* A completed bracketed paste. `enter` carries an Enter keypress that shared
|
|
395
|
+
* the paste's stdin read, so the terminal can dispatch paste and submit to
|
|
396
|
+
* the same focused component before a paste-triggered overlay can take focus.
|
|
397
|
+
*/
|
|
398
|
+
paste: [content: string, enter?: string];
|
|
381
399
|
};
|
|
382
400
|
|
|
383
401
|
/**
|
|
@@ -577,10 +595,15 @@ export class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
|
577
595
|
this.#pasteBytes = 0;
|
|
578
596
|
this.#pendingKittyPrintableCodepoint = undefined;
|
|
579
597
|
|
|
580
|
-
|
|
598
|
+
// A paste-and-Enter burst (automation, terminals that batch reads) must
|
|
599
|
+
// not split into two dispatches: the paste may open an overlay that
|
|
600
|
+
// would then swallow the Enter meant to submit it.
|
|
601
|
+
const enter = leadingEnter(remaining);
|
|
602
|
+
this.emit("paste", pastedContent, enter);
|
|
581
603
|
|
|
582
|
-
|
|
583
|
-
|
|
604
|
+
const rest = enter === undefined ? remaining : remaining.slice(enter.length);
|
|
605
|
+
if (rest.length > 0) {
|
|
606
|
+
this.process(rest);
|
|
584
607
|
}
|
|
585
608
|
}
|
|
586
609
|
|
package/src/terminal.ts
CHANGED
|
@@ -1359,10 +1359,13 @@ export class ProcessTerminal implements Terminal {
|
|
|
1359
1359
|
}
|
|
1360
1360
|
});
|
|
1361
1361
|
|
|
1362
|
-
// Re-wrap paste content with bracketed paste markers for existing editor
|
|
1363
|
-
|
|
1362
|
+
// Re-wrap paste content with bracketed paste markers for existing editor
|
|
1363
|
+
// handling. An Enter that shared the paste's stdin read rides along so
|
|
1364
|
+
// paste and submit reach the component focused right now, not one the
|
|
1365
|
+
// paste itself is about to open.
|
|
1366
|
+
this.#stdinBuffer.on("paste", (content: string, enter?: string) => {
|
|
1364
1367
|
if (this.#inputHandler) {
|
|
1365
|
-
this.#inputHandler(`\x1b[200~${content}\x1b[201
|
|
1368
|
+
this.#inputHandler(`\x1b[200~${content}\x1b[201~${enter ?? ""}`);
|
|
1366
1369
|
}
|
|
1367
1370
|
});
|
|
1368
1371
|
|