@oh-my-pi/pi-tui 18.2.1 → 18.2.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/CHANGELOG.md +18 -0
- package/dist/types/components/editor.d.ts +2 -0
- package/dist/types/components/markdown.d.ts +0 -6
- package/dist/types/fuzzy.d.ts +1 -1
- package/package.json +3 -3
- package/src/components/editor.ts +36 -0
- package/src/components/input.ts +1 -1
- package/src/components/markdown.ts +0 -9
- package/src/fuzzy.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.2.3] - 2026-09-17
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Editors support whitespace-delimited `^` mention autocomplete and expose registered atoms for host-defined chip rendering.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- `fuzzyRank` accepts readonly candidate arrays without copying them.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Masked inputs no longer expose their text in diagnostic previews.
|
|
18
|
+
|
|
19
|
+
### Removed
|
|
20
|
+
|
|
21
|
+
- Removed the internal `fastTailSplices` and `resetFastTailSplices` Markdown instrumentation exports.
|
|
22
|
+
|
|
5
23
|
## [18.2.1] - 2026-09-15
|
|
6
24
|
|
|
7
25
|
### Added
|
|
@@ -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
|
+
/** Host-registered atomic chip labels mapped to their submit-time expansions. */
|
|
200
|
+
get atoms(): ReadonlyMap<string, string>;
|
|
199
201
|
/** Monotonic buffer-content revision for render caches. Cursor-only movement does not advance it. */
|
|
200
202
|
get textRevision(): number;
|
|
201
203
|
/** Whether the buffer text equals `value`, without `getText()`'s full join —
|
|
@@ -4,12 +4,6 @@ import type { Component } from "../tui.js";
|
|
|
4
4
|
export declare function autolinkSchemeScanIndex(src: string): number | undefined;
|
|
5
5
|
/** @internal exported for tests — must never return false for a src the built-in url regex matches. */
|
|
6
6
|
export declare function urlTokenPossible(src: string): boolean;
|
|
7
|
-
/** @internal exported for tests — counts fast-tail splice frames. A future
|
|
8
|
-
* regression that silently disarms the fast path (e.g. an over-broad gate)
|
|
9
|
-
* leaves byte-identity intact but drops the counter to zero. */
|
|
10
|
-
export declare let fastTailSplices: number;
|
|
11
|
-
/** @internal exported for tests — resets the splice counter. */
|
|
12
|
-
export declare function resetFastTailSplices(): void;
|
|
13
7
|
/** @internal exported for tests — the grown-line-start block-kind gate. */
|
|
14
8
|
export declare function fastLineStartHazard(grownLine: string): boolean;
|
|
15
9
|
/** A hyperlink as the renderer sees it: inline `[text](href)`, `<autolink>`, bare GFM URL, or reference link. */
|
package/dist/types/fuzzy.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export declare class FuzzyText {
|
|
|
36
36
|
* Filter and sort items by fuzzy match quality (best matches first).
|
|
37
37
|
* Supports space-separated tokens: all tokens must match.
|
|
38
38
|
*/
|
|
39
|
-
export declare function fuzzyRank<T>(items: T[], query: string, getText: (item: T) => string): FuzzyFilterResult<T>[];
|
|
39
|
+
export declare function fuzzyRank<T>(items: readonly T[], query: string, getText: (item: T) => string): FuzzyFilterResult<T>[];
|
|
40
40
|
export declare function fuzzyFilter<T>(items: T[], query: string, getText: (item: T) => string): T[];
|
|
41
41
|
/**
|
|
42
42
|
* Clear the fuzzy search-index cache. Intended for tests/benchmarks so a fresh
|
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.2.
|
|
4
|
+
"version": "18.2.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": "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.2.
|
|
41
|
-
"@oh-my-pi/pi-utils": "18.2.
|
|
40
|
+
"@oh-my-pi/pi-natives": "18.2.3",
|
|
41
|
+
"@oh-my-pi/pi-utils": "18.2.3"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"kitty-vt-wasm": "^0.2.0"
|
package/src/components/editor.ts
CHANGED
|
@@ -51,6 +51,7 @@ export type { EditorBorderStyle, EditorTopBorder };
|
|
|
51
51
|
import { type SelectItem, SelectList, type SelectListLayoutOptions, type SelectListTheme } from "./select-list";
|
|
52
52
|
|
|
53
53
|
const PASSTHROUGH_COLOR = (text: string): string => text;
|
|
54
|
+
const MENTION_CONTEXT_RE = /(?:^|\s)\^[^\s]*$/;
|
|
54
55
|
|
|
55
56
|
const AUTOCOMPLETE_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {
|
|
56
57
|
overflowSearch: false,
|
|
@@ -2392,6 +2393,11 @@ export class Editor implements Component, Focusable {
|
|
|
2392
2393
|
return this.#state.lines.join("\n");
|
|
2393
2394
|
}
|
|
2394
2395
|
|
|
2396
|
+
/** Host-registered atomic chip labels mapped to their submit-time expansions. */
|
|
2397
|
+
get atoms(): ReadonlyMap<string, string> {
|
|
2398
|
+
return this.#atoms;
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2395
2401
|
/** Monotonic buffer-content revision for render caches. Cursor-only movement does not advance it. */
|
|
2396
2402
|
get textRevision(): number {
|
|
2397
2403
|
return this.#textRevision;
|
|
@@ -2787,6 +2793,16 @@ export class Editor implements Component, Focusable {
|
|
|
2787
2793
|
this.#tryTriggerAutocomplete();
|
|
2788
2794
|
}
|
|
2789
2795
|
}
|
|
2796
|
+
// Auto-trigger for "^" model mentions
|
|
2797
|
+
else if (char === "^") {
|
|
2798
|
+
const currentLine = this.#state.lines[this.#state.cursorLine] || "";
|
|
2799
|
+
const textBeforeCursor = currentLine.slice(0, this.#state.cursorCol);
|
|
2800
|
+
// Only trigger if ^ is after whitespace or at start of line
|
|
2801
|
+
const charBeforeCaret = textBeforeCursor[textBeforeCursor.length - 2];
|
|
2802
|
+
if (textBeforeCursor.length === 1 || charBeforeCaret === " " || charBeforeCaret === "\t") {
|
|
2803
|
+
this.#tryTriggerAutocomplete();
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2790
2806
|
// Auto-trigger for "#" prompt actions anywhere in the current token
|
|
2791
2807
|
else if (char === "#") {
|
|
2792
2808
|
this.#tryTriggerAutocomplete();
|
|
@@ -2807,6 +2823,10 @@ export class Editor implements Component, Focusable {
|
|
|
2807
2823
|
else if (textBeforeCursor.match(/(?:^|[\s])@[^\s]*$/)) {
|
|
2808
2824
|
this.#tryTriggerAutocomplete();
|
|
2809
2825
|
}
|
|
2826
|
+
// Check if we're in a model mention context
|
|
2827
|
+
else if (MENTION_CONTEXT_RE.test(textBeforeCursor)) {
|
|
2828
|
+
this.#tryTriggerAutocomplete();
|
|
2829
|
+
}
|
|
2810
2830
|
// Check if we're in a # prompt action context
|
|
2811
2831
|
else if (textBeforeCursor.match(/#[^\s#]*$/)) {
|
|
2812
2832
|
this.#tryTriggerAutocomplete();
|
|
@@ -2926,6 +2946,8 @@ export class Editor implements Component, Focusable {
|
|
|
2926
2946
|
this.#tryTriggerAutocomplete();
|
|
2927
2947
|
} else if (textBeforeCursor.match(/(?:^|[\s])@[^\s]*$/)) {
|
|
2928
2948
|
this.#tryTriggerAutocomplete();
|
|
2949
|
+
} else if (MENTION_CONTEXT_RE.test(textBeforeCursor)) {
|
|
2950
|
+
this.#tryTriggerAutocomplete();
|
|
2929
2951
|
} else if (textBeforeCursor.match(/#[^\s#]*$/)) {
|
|
2930
2952
|
this.#tryTriggerAutocomplete();
|
|
2931
2953
|
} else if (this.#textTriggersUrlAutocomplete(textBeforeCursor)) {
|
|
@@ -3101,6 +3123,10 @@ export class Editor implements Component, Focusable {
|
|
|
3101
3123
|
else if (textBeforeCursor.match(/(?:^|[\s])@[^\s]*$/)) {
|
|
3102
3124
|
this.#tryTriggerAutocomplete();
|
|
3103
3125
|
}
|
|
3126
|
+
// model mention context
|
|
3127
|
+
else if (MENTION_CONTEXT_RE.test(textBeforeCursor)) {
|
|
3128
|
+
this.#tryTriggerAutocomplete();
|
|
3129
|
+
}
|
|
3104
3130
|
// # prompt action context
|
|
3105
3131
|
else if (textBeforeCursor.match(/#[^\s#]*$/)) {
|
|
3106
3132
|
this.#tryTriggerAutocomplete();
|
|
@@ -3264,6 +3290,8 @@ export class Editor implements Component, Focusable {
|
|
|
3264
3290
|
this.#tryTriggerAutocomplete();
|
|
3265
3291
|
} else if (textBeforeCursor.match(/(?:^|[\s])@[^\s]*$/)) {
|
|
3266
3292
|
this.#tryTriggerAutocomplete();
|
|
3293
|
+
} else if (MENTION_CONTEXT_RE.test(textBeforeCursor)) {
|
|
3294
|
+
this.#tryTriggerAutocomplete();
|
|
3267
3295
|
} else if (textBeforeCursor.match(/#[^\s#]*$/)) {
|
|
3268
3296
|
this.#tryTriggerAutocomplete();
|
|
3269
3297
|
} else if (this.#textTriggersUrlAutocomplete(textBeforeCursor)) {
|
|
@@ -3600,6 +3628,10 @@ export class Editor implements Component, Focusable {
|
|
|
3600
3628
|
else if (textBeforeCursor.match(/(?:^|[\s])@[^\s]*$/)) {
|
|
3601
3629
|
this.#tryTriggerAutocomplete();
|
|
3602
3630
|
}
|
|
3631
|
+
// model mention context
|
|
3632
|
+
else if (MENTION_CONTEXT_RE.test(textBeforeCursor)) {
|
|
3633
|
+
this.#tryTriggerAutocomplete();
|
|
3634
|
+
}
|
|
3603
3635
|
// # prompt action context
|
|
3604
3636
|
else if (textBeforeCursor.match(/#[^\s#]*$/)) {
|
|
3605
3637
|
this.#tryTriggerAutocomplete();
|
|
@@ -3893,6 +3925,10 @@ export class Editor implements Component, Focusable {
|
|
|
3893
3925
|
return /(?:^|\s)@[^\s]*$/.test(currentTextBeforeCursor);
|
|
3894
3926
|
}
|
|
3895
3927
|
|
|
3928
|
+
if (this.#autocompletePrefix.startsWith("^")) {
|
|
3929
|
+
return MENTION_CONTEXT_RE.test(currentTextBeforeCursor);
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3896
3932
|
return currentTextBeforeCursor.endsWith(this.#autocompletePrefix);
|
|
3897
3933
|
}
|
|
3898
3934
|
|
package/src/components/input.ts
CHANGED
|
@@ -54,7 +54,7 @@ export class Input implements Component, Focusable {
|
|
|
54
54
|
/** Return bounded input content and cursor state for debug inspection. */
|
|
55
55
|
debugState(): Record<string, unknown> {
|
|
56
56
|
return {
|
|
57
|
-
textPreview: this.#value.slice(0, 120),
|
|
57
|
+
textPreview: this.mask ? "********" : this.#value.slice(0, 120),
|
|
58
58
|
textLength: this.#value.length,
|
|
59
59
|
previewTruncated: this.#value.length > 120,
|
|
60
60
|
cursor: this.#cursor,
|
|
@@ -988,14 +988,6 @@ const FAST_LINE_START_HAZARD_RE =
|
|
|
988
988
|
// chars are in ASCENDING code-point order (no reversed ranges that
|
|
989
989
|
// rely on engine leniency): * + = – — ─ ━ ═ then the literal `-`.
|
|
990
990
|
/^ {0,3}(?:#{1,6}(?:[ \t]|$)|>|\d{1,9}[.)](?:[ \t]|$)|[*+=–—─━═-](?:[ \t]|$)|(?:[*+=–—─━═-][ \t]*){2,}[ \t]*$)/;
|
|
991
|
-
/** @internal exported for tests — counts fast-tail splice frames. A future
|
|
992
|
-
* regression that silently disarms the fast path (e.g. an over-broad gate)
|
|
993
|
-
* leaves byte-identity intact but drops the counter to zero. */
|
|
994
|
-
export let fastTailSplices = 0;
|
|
995
|
-
/** @internal exported for tests — resets the splice counter. */
|
|
996
|
-
export function resetFastTailSplices(): void {
|
|
997
|
-
fastTailSplices = 0;
|
|
998
|
-
}
|
|
999
991
|
|
|
1000
992
|
/** @internal exported for tests — the grown-line-start block-kind gate. */
|
|
1001
993
|
export function fastLineStartHazard(grownLine: string): boolean {
|
|
@@ -2199,7 +2191,6 @@ export class Markdown implements Component {
|
|
|
2199
2191
|
rowEnd: recipe.rowStart + wrapped.length,
|
|
2200
2192
|
signature: recipe.signature,
|
|
2201
2193
|
};
|
|
2202
|
-
fastTailSplices++;
|
|
2203
2194
|
return fastResult;
|
|
2204
2195
|
}
|
|
2205
2196
|
}
|
package/src/fuzzy.ts
CHANGED
|
@@ -403,7 +403,7 @@ export class FuzzyText {
|
|
|
403
403
|
* Filter and sort items by fuzzy match quality (best matches first).
|
|
404
404
|
* Supports space-separated tokens: all tokens must match.
|
|
405
405
|
*/
|
|
406
|
-
export function fuzzyRank<T>(items: T[], query: string, getText: (item: T) => string): FuzzyFilterResult<T>[] {
|
|
406
|
+
export function fuzzyRank<T>(items: readonly T[], query: string, getText: (item: T) => string): FuzzyFilterResult<T>[] {
|
|
407
407
|
if (!query.trim()) {
|
|
408
408
|
return items.map(item => ({ item, score: 0 }));
|
|
409
409
|
}
|