@oh-my-pi/pi-tui 15.1.2 → 15.1.3
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/dist/types/autocomplete.d.ts +12 -0
- package/package.json +3 -3
- package/src/autocomplete.ts +9 -0
- package/src/components/editor.ts +29 -0
- package/src/components/markdown.ts +6 -4
- package/src/terminal.ts +3 -0
|
@@ -35,6 +35,18 @@ export interface AutocompleteProvider {
|
|
|
35
35
|
items: AutocompleteItem[];
|
|
36
36
|
prefix: string;
|
|
37
37
|
} | null;
|
|
38
|
+
/**
|
|
39
|
+
* Synchronously try to expand text immediately before the cursor (no async I/O).
|
|
40
|
+
* Called after every single-character insert. Implementations MUST cheaply
|
|
41
|
+
* early-return when the trailing context cannot trigger them.
|
|
42
|
+
* Returns the number of characters to delete immediately before the cursor
|
|
43
|
+
* and the literal string to insert in their place, or null to leave the
|
|
44
|
+
* buffer untouched.
|
|
45
|
+
*/
|
|
46
|
+
trySyncInlineReplace?(textBeforeCursor: string): {
|
|
47
|
+
replaceLen: number;
|
|
48
|
+
insert: string;
|
|
49
|
+
} | null;
|
|
38
50
|
}
|
|
39
51
|
export declare class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
40
52
|
#private;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-tui",
|
|
4
|
-
"version": "15.1.
|
|
4
|
+
"version": "15.1.3",
|
|
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": "15.1.
|
|
41
|
-
"@oh-my-pi/pi-utils": "15.1.
|
|
40
|
+
"@oh-my-pi/pi-natives": "15.1.3",
|
|
41
|
+
"@oh-my-pi/pi-utils": "15.1.3",
|
|
42
42
|
"lru-cache": "11.3.6",
|
|
43
43
|
"marked": "^18.0.3"
|
|
44
44
|
},
|
package/src/autocomplete.ts
CHANGED
|
@@ -199,6 +199,15 @@ export interface AutocompleteProvider {
|
|
|
199
199
|
/** Synchronously try to complete a slash command at the start of a line (no async I/O). */
|
|
200
200
|
/** Returns matched items and the full prefix, or null if not applicable. */
|
|
201
201
|
trySyncSlashCompletion?(textBeforeCursor: string): { items: AutocompleteItem[]; prefix: string } | null;
|
|
202
|
+
/**
|
|
203
|
+
* Synchronously try to expand text immediately before the cursor (no async I/O).
|
|
204
|
+
* Called after every single-character insert. Implementations MUST cheaply
|
|
205
|
+
* early-return when the trailing context cannot trigger them.
|
|
206
|
+
* Returns the number of characters to delete immediately before the cursor
|
|
207
|
+
* and the literal string to insert in their place, or null to leave the
|
|
208
|
+
* buffer untouched.
|
|
209
|
+
*/
|
|
210
|
+
trySyncInlineReplace?(textBeforeCursor: string): { replaceLen: number; insert: string } | null;
|
|
202
211
|
}
|
|
203
212
|
|
|
204
213
|
// Combined provider that handles both slash commands and file paths.
|
package/src/components/editor.ts
CHANGED
|
@@ -985,6 +985,7 @@ export class Editor implements Component, Focusable {
|
|
|
985
985
|
this.#setCursorCol(result.cursorCol);
|
|
986
986
|
|
|
987
987
|
this.#cancelAutocomplete();
|
|
988
|
+
this.onAutocompleteUpdate?.();
|
|
988
989
|
|
|
989
990
|
if (this.onChange) {
|
|
990
991
|
this.onChange(this.getText());
|
|
@@ -1044,6 +1045,7 @@ export class Editor implements Component, Focusable {
|
|
|
1044
1045
|
this.#setCursorCol(result.cursorCol);
|
|
1045
1046
|
|
|
1046
1047
|
this.#cancelAutocomplete();
|
|
1048
|
+
this.onAutocompleteUpdate?.();
|
|
1047
1049
|
|
|
1048
1050
|
if (this.onChange) {
|
|
1049
1051
|
this.onChange(this.getText());
|
|
@@ -1493,6 +1495,29 @@ export class Editor implements Component, Focusable {
|
|
|
1493
1495
|
this.onChange(this.getText());
|
|
1494
1496
|
}
|
|
1495
1497
|
|
|
1498
|
+
// Synchronous inline replacement (e.g. emoji shortcodes `:joy:` → 😂).
|
|
1499
|
+
// Runs before autocomplete trigger so the popup doesn't briefly chase a
|
|
1500
|
+
// prefix that's about to be rewritten.
|
|
1501
|
+
if (char.length === 1 && this.#autocompleteProvider?.trySyncInlineReplace) {
|
|
1502
|
+
const replaceLine = this.#state.lines[this.#state.cursorLine] || "";
|
|
1503
|
+
const textBeforeCursor = replaceLine.slice(0, this.#state.cursorCol);
|
|
1504
|
+
const replacement = this.#autocompleteProvider.trySyncInlineReplace(textBeforeCursor);
|
|
1505
|
+
if (replacement) {
|
|
1506
|
+
const before = replaceLine.slice(0, this.#state.cursorCol - replacement.replaceLen);
|
|
1507
|
+
const after = replaceLine.slice(this.#state.cursorCol);
|
|
1508
|
+
this.#state.lines[this.#state.cursorLine] = before + replacement.insert + after;
|
|
1509
|
+
this.#setCursorCol(before.length + replacement.insert.length);
|
|
1510
|
+
if (this.onChange) {
|
|
1511
|
+
this.onChange(this.getText());
|
|
1512
|
+
}
|
|
1513
|
+
if (this.#autocompleteState) {
|
|
1514
|
+
this.#cancelAutocomplete();
|
|
1515
|
+
this.onAutocompleteUpdate?.();
|
|
1516
|
+
}
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1496
1521
|
// Check if we should trigger or update autocomplete
|
|
1497
1522
|
if (!this.#autocompleteState) {
|
|
1498
1523
|
// Auto-trigger for "/" at the start of a line (slash commands)
|
|
@@ -1529,6 +1554,10 @@ export class Editor implements Component, Focusable {
|
|
|
1529
1554
|
else if (textBeforeCursor.match(/#[^\s#]*$/)) {
|
|
1530
1555
|
this.#tryTriggerAutocomplete();
|
|
1531
1556
|
}
|
|
1557
|
+
// Check if we're in a :emoji shortcode context
|
|
1558
|
+
else if (textBeforeCursor.match(/(?:^|[\s([{>]):[a-zA-Z0-9_+-]*$/)) {
|
|
1559
|
+
this.#tryTriggerAutocomplete();
|
|
1560
|
+
}
|
|
1532
1561
|
}
|
|
1533
1562
|
} else {
|
|
1534
1563
|
this.#debouncedUpdateAutocomplete();
|
|
@@ -47,14 +47,16 @@ export function clearRenderCache(): void {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// Stable numeric IDs for structural theme/style objects (no ID field on type).
|
|
50
|
-
//
|
|
51
|
-
const
|
|
50
|
+
// Symbol-keyed so the id travels with the object and is invisible to consumers.
|
|
51
|
+
const kObjectId = Symbol("markdown.objectId");
|
|
52
|
+
type WithObjectId = object & { [kObjectId]?: number };
|
|
52
53
|
let nextObjectId = 0;
|
|
53
54
|
function objectId(o: object): number {
|
|
54
|
-
|
|
55
|
+
const tagged = o as WithObjectId;
|
|
56
|
+
let id = tagged[kObjectId];
|
|
55
57
|
if (id === undefined) {
|
|
56
58
|
id = nextObjectId++;
|
|
57
|
-
|
|
59
|
+
tagged[kObjectId] = id;
|
|
58
60
|
}
|
|
59
61
|
return id;
|
|
60
62
|
}
|
package/src/terminal.ts
CHANGED
|
@@ -591,6 +591,9 @@ export class ProcessTerminal implements Terminal {
|
|
|
591
591
|
|
|
592
592
|
#safeWrite(data: string): void {
|
|
593
593
|
if (this.#dead) return;
|
|
594
|
+
// Skip control sequences when stdout isn't a TTY (piped output, tests, log
|
|
595
|
+
// files). They serve no purpose there and would surface as visible noise.
|
|
596
|
+
if (!process.stdout.isTTY) return;
|
|
594
597
|
try {
|
|
595
598
|
process.stdout.write(data);
|
|
596
599
|
} catch (err) {
|