@linxiraos/pi-tui 1.0.0
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 +2219 -0
- package/README.md +705 -0
- package/dist/types/autocomplete.d.ts +116 -0
- package/dist/types/bracketed-paste.d.ts +51 -0
- package/dist/types/components/box.d.ts +31 -0
- package/dist/types/components/cancellable-loader.d.ts +21 -0
- package/dist/types/components/editor.d.ts +162 -0
- package/dist/types/components/image.d.ts +112 -0
- package/dist/types/components/input.d.ts +25 -0
- package/dist/types/components/loader.d.ts +25 -0
- package/dist/types/components/markdown.d.ts +88 -0
- package/dist/types/components/scroll-view.d.ts +62 -0
- package/dist/types/components/select-list.d.ts +69 -0
- package/dist/types/components/settings-list.d.ts +123 -0
- package/dist/types/components/spacer.d.ts +11 -0
- package/dist/types/components/tab-bar.d.ts +89 -0
- package/dist/types/components/text.d.ts +27 -0
- package/dist/types/components/truncated-text.d.ts +10 -0
- package/dist/types/deccara.d.ts +49 -0
- package/dist/types/desktop-notify.d.ts +52 -0
- package/dist/types/editor-component.d.ts +38 -0
- package/dist/types/fuzzy.d.ts +48 -0
- package/dist/types/index.d.ts +32 -0
- package/dist/types/keybindings.d.ts +197 -0
- package/dist/types/keys.d.ts +210 -0
- package/dist/types/kill-ring.d.ts +20 -0
- package/dist/types/kitty-graphics.d.ts +76 -0
- package/dist/types/latex-block.d.ts +8 -0
- package/dist/types/latex-to-unicode.d.ts +50 -0
- package/dist/types/loop-watchdog.d.ts +44 -0
- package/dist/types/mouse.d.ts +67 -0
- package/dist/types/stdin-buffer.d.ts +60 -0
- package/dist/types/symbols.d.ts +25 -0
- package/dist/types/terminal-capabilities.d.ts +285 -0
- package/dist/types/terminal.d.ts +175 -0
- package/dist/types/tmux.d.ts +6 -0
- package/dist/types/ttyid.d.ts +9 -0
- package/dist/types/tui.d.ts +457 -0
- package/dist/types/utils.d.ts +100 -0
- package/package.json +70 -0
- package/src/autocomplete.ts +1079 -0
- package/src/bracketed-paste.ts +123 -0
- package/src/components/box.ts +236 -0
- package/src/components/cancellable-loader.ts +40 -0
- package/src/components/editor.ts +3301 -0
- package/src/components/image.ts +460 -0
- package/src/components/input.ts +482 -0
- package/src/components/loader.ts +174 -0
- package/src/components/markdown.ts +3119 -0
- package/src/components/scroll-view.ts +227 -0
- package/src/components/select-list.ts +539 -0
- package/src/components/settings-list.ts +793 -0
- package/src/components/spacer.ts +32 -0
- package/src/components/tab-bar.ts +300 -0
- package/src/components/text.ts +173 -0
- package/src/components/truncated-text.ts +69 -0
- package/src/deccara.ts +314 -0
- package/src/desktop-notify.ts +192 -0
- package/src/editor-component.ts +74 -0
- package/src/fuzzy.ts +384 -0
- package/src/index.ts +51 -0
- package/src/keybindings.ts +346 -0
- package/src/keys.ts +566 -0
- package/src/kill-ring.ts +51 -0
- package/src/kitty-graphics.ts +171 -0
- package/src/latex-block.ts +1338 -0
- package/src/latex-to-unicode.ts +2017 -0
- package/src/loop-watchdog.ts +115 -0
- package/src/mouse.ts +105 -0
- package/src/stdin-buffer.ts +781 -0
- package/src/symbols.ts +26 -0
- package/src/terminal-capabilities.ts +1211 -0
- package/src/terminal.ts +1854 -0
- package/src/tmux.ts +14 -0
- package/src/ttyid.ts +84 -0
- package/src/tui.ts +4275 -0
- package/src/utils.ts +619 -0
package/src/fuzzy.ts
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fuzzy matching utilities.
|
|
3
|
+
*
|
|
4
|
+
* Matching is deliberately word-local for normal words. This keeps a query like
|
|
5
|
+
* "image provider" from matching a long setting description only because the
|
|
6
|
+
* letters i-m-a-g-e appear somewhere in order across unrelated words.
|
|
7
|
+
*
|
|
8
|
+
* Lower score = better match.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface FuzzyMatch {
|
|
12
|
+
matches: boolean;
|
|
13
|
+
score: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface FuzzyFilterResult<T> {
|
|
17
|
+
item: T;
|
|
18
|
+
score: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface CharacterMatch {
|
|
22
|
+
matches: boolean;
|
|
23
|
+
score: number;
|
|
24
|
+
span: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface SearchWord {
|
|
28
|
+
text: string;
|
|
29
|
+
index: number;
|
|
30
|
+
ordinal: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface SearchIndex {
|
|
34
|
+
normalized: string;
|
|
35
|
+
compact: string;
|
|
36
|
+
/** Start offsets of each word within `compact` (cumulative word lengths). */
|
|
37
|
+
compactWordStarts: Set<number>;
|
|
38
|
+
words: SearchWord[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const ALPHANUMERIC_SWAP_PENALTY = 5;
|
|
42
|
+
const COMPACT_PHRASE_BONUS = 1200;
|
|
43
|
+
const PHRASE_BONUS = 1000;
|
|
44
|
+
|
|
45
|
+
function normalizeForSearch(value: string): string {
|
|
46
|
+
return value
|
|
47
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
|
|
48
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
49
|
+
.toLowerCase()
|
|
50
|
+
.replace(/[^\p{Letter}\p{Mark}\p{Number}]+/gu, " ")
|
|
51
|
+
.trim()
|
|
52
|
+
.replace(/\s+/g, " ");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Module-level memo of the per-text search index. `buildSearchIndex` is a pure
|
|
56
|
+
// function of `text`, but selectors call it once per candidate per keystroke —
|
|
57
|
+
// the same stable candidate list is re-filtered as the user types. Caching the
|
|
58
|
+
// index across keystrokes eliminates the redundant normalize + word-split + Set
|
|
59
|
+
// build on every character. Consumers only read the result, so sharing is safe.
|
|
60
|
+
//
|
|
61
|
+
// Admission is conservative so the cache helps the repeated-filter hot path
|
|
62
|
+
// without paying for one-off text: only short texts are cached (long inputs —
|
|
63
|
+
// pasted prompts, transcripts searched via the message selector — would bloat
|
|
64
|
+
// memory), and admission stops at the cap instead of evicting, so a stream of
|
|
65
|
+
// unique texts (message/session search) can't churn the map.
|
|
66
|
+
const INDEX_CACHE_MAX = 4096;
|
|
67
|
+
const MAX_CACHED_TEXT_LEN = 4096;
|
|
68
|
+
const indexCache = new Map<string, SearchIndex>();
|
|
69
|
+
|
|
70
|
+
function buildSearchIndex(text: string): SearchIndex {
|
|
71
|
+
// Long inputs (pasted prompts, transcripts) are never cached; bypass the Map
|
|
72
|
+
// entirely so they don't pay a hash lookup on every search.
|
|
73
|
+
if (text.length > MAX_CACHED_TEXT_LEN) return buildUncachedSearchIndex(text);
|
|
74
|
+
|
|
75
|
+
const cached = indexCache.get(text);
|
|
76
|
+
if (cached !== undefined) return cached;
|
|
77
|
+
|
|
78
|
+
const result = buildUncachedSearchIndex(text);
|
|
79
|
+
if (indexCache.size < INDEX_CACHE_MAX) {
|
|
80
|
+
indexCache.set(text, result);
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function buildUncachedSearchIndex(text: string): SearchIndex {
|
|
86
|
+
const normalized = normalizeForSearch(text);
|
|
87
|
+
if (normalized.length === 0) {
|
|
88
|
+
return { normalized, compact: "", compactWordStarts: new Set(), words: [] };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const words: SearchWord[] = [];
|
|
92
|
+
const compactWordStarts = new Set<number>();
|
|
93
|
+
let index = 0;
|
|
94
|
+
let compactIndex = 0;
|
|
95
|
+
let ordinal = 0;
|
|
96
|
+
for (const word of normalized.split(" ")) {
|
|
97
|
+
words.push({ text: word, index, ordinal });
|
|
98
|
+
compactWordStarts.add(compactIndex);
|
|
99
|
+
index += word.length + 1;
|
|
100
|
+
compactIndex += word.length;
|
|
101
|
+
ordinal++;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { normalized, compact: normalized.replaceAll(" ", ""), compactWordStarts, words };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function scoreCharacters(queryLower: string, textLower: string): CharacterMatch {
|
|
108
|
+
if (queryLower.length === 0) {
|
|
109
|
+
return { matches: true, score: 0, span: 0 };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (queryLower.length > textLower.length) {
|
|
113
|
+
return { matches: false, score: 0, span: 0 };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let queryIndex = 0;
|
|
117
|
+
let score = 0;
|
|
118
|
+
let firstMatchIndex = -1;
|
|
119
|
+
let lastMatchIndex = -1;
|
|
120
|
+
let consecutiveMatches = 0;
|
|
121
|
+
|
|
122
|
+
for (let i = 0; i < textLower.length && queryIndex < queryLower.length; i++) {
|
|
123
|
+
if (textLower[i] === queryLower[queryIndex]) {
|
|
124
|
+
if (firstMatchIndex < 0) firstMatchIndex = i;
|
|
125
|
+
|
|
126
|
+
if (lastMatchIndex === i - 1) {
|
|
127
|
+
consecutiveMatches++;
|
|
128
|
+
score -= consecutiveMatches * 5;
|
|
129
|
+
} else {
|
|
130
|
+
consecutiveMatches = 0;
|
|
131
|
+
if (lastMatchIndex >= 0) {
|
|
132
|
+
score += (i - lastMatchIndex - 1) * 2;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
score += i * 0.1;
|
|
137
|
+
lastMatchIndex = i;
|
|
138
|
+
queryIndex++;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (queryIndex < queryLower.length) {
|
|
143
|
+
return { matches: false, score: 0, span: 0 };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { matches: true, score, span: lastMatchIndex - firstMatchIndex + 1 };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function buildAlphanumericSwapQueries(queryLower: string): string[] {
|
|
150
|
+
const variants = new Set<string>();
|
|
151
|
+
for (let i = 0; i < queryLower.length - 1; i++) {
|
|
152
|
+
const current = queryLower[i];
|
|
153
|
+
const next = queryLower[i + 1];
|
|
154
|
+
const isAlphaNumSwap =
|
|
155
|
+
(current && /[a-z]/.test(current) && next && /\d/.test(next)) ||
|
|
156
|
+
(current && /\d/.test(current) && next && /[a-z]/.test(next));
|
|
157
|
+
if (!isAlphaNumSwap) continue;
|
|
158
|
+
const swapped = queryLower.slice(0, i) + next + current + queryLower.slice(i + 2);
|
|
159
|
+
variants.add(swapped);
|
|
160
|
+
}
|
|
161
|
+
return [...variants];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function withPosition(score: number, index: number): number {
|
|
165
|
+
return score + index * 0.01;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function isWordBoundaryPhrase(normalized: string, index: number, length: number): boolean {
|
|
169
|
+
const before = index === 0 || normalized[index - 1] === " ";
|
|
170
|
+
const afterIndex = index + length;
|
|
171
|
+
const after = afterIndex === normalized.length || normalized[afterIndex] === " ";
|
|
172
|
+
return before && after;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function scoreTokenAgainstWord(token: string, word: SearchWord): FuzzyMatch | null {
|
|
176
|
+
if (word.text === token) {
|
|
177
|
+
return { matches: true, score: withPosition(-200, word.index) };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (word.text.startsWith(token)) {
|
|
181
|
+
return { matches: true, score: withPosition(-170 + (word.text.length - token.length) * 0.5, word.index) };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (token.startsWith(word.text) && token.length - word.text.length <= 2) {
|
|
185
|
+
return { matches: true, score: withPosition(-150 + token.length - word.text.length, word.index) };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const substringIndex = word.text.indexOf(token);
|
|
189
|
+
if (substringIndex >= 0) {
|
|
190
|
+
return { matches: true, score: withPosition(-20 + substringIndex, word.index) };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const characterMatch = scoreCharacters(token, word.text);
|
|
194
|
+
if (!characterMatch.matches) return null;
|
|
195
|
+
|
|
196
|
+
const maxSpan = Math.max(token.length + 2, Math.ceil(token.length * 1.8));
|
|
197
|
+
if (characterMatch.span > maxSpan) return null;
|
|
198
|
+
|
|
199
|
+
return { matches: true, score: withPosition(-40 + characterMatch.score, word.index) };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function scoreAcronym(token: string, index: SearchIndex): FuzzyMatch | null {
|
|
203
|
+
if (token.length < 2 || token.length > 4 || index.words.length === 0) return null;
|
|
204
|
+
|
|
205
|
+
let queryIndex = 0;
|
|
206
|
+
let firstOrdinal = -1;
|
|
207
|
+
let lastOrdinal = -1;
|
|
208
|
+
let firstTextIndex = 0;
|
|
209
|
+
|
|
210
|
+
for (const word of index.words) {
|
|
211
|
+
if (word.text[0] !== token[queryIndex]) continue;
|
|
212
|
+
if (firstOrdinal < 0) {
|
|
213
|
+
firstOrdinal = word.ordinal;
|
|
214
|
+
firstTextIndex = word.index;
|
|
215
|
+
}
|
|
216
|
+
lastOrdinal = word.ordinal;
|
|
217
|
+
queryIndex++;
|
|
218
|
+
if (queryIndex === token.length) break;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (queryIndex < token.length || firstOrdinal < 0 || lastOrdinal < 0) return null;
|
|
222
|
+
|
|
223
|
+
const wordSpan = lastOrdinal - firstOrdinal + 1;
|
|
224
|
+
if (wordSpan > token.length + 2) return null;
|
|
225
|
+
|
|
226
|
+
return { matches: true, score: withPosition(-30 + wordSpan * 4 - token.length * 2, firstTextIndex) };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function scoreTokenDirect(token: string, index: SearchIndex): FuzzyMatch {
|
|
230
|
+
if (token.length === 0) return { matches: true, score: 0 };
|
|
231
|
+
|
|
232
|
+
let best: FuzzyMatch | null = null;
|
|
233
|
+
const compactIndex = index.compact.indexOf(token);
|
|
234
|
+
if (compactIndex >= 0 && index.compactWordStarts.has(compactIndex)) {
|
|
235
|
+
best = { matches: true, score: withPosition(-140, compactIndex) };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
for (const word of index.words) {
|
|
239
|
+
const match = scoreTokenAgainstWord(token, word);
|
|
240
|
+
if (match && (!best || match.score < best.score)) {
|
|
241
|
+
best = match;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const acronym = scoreAcronym(token, index);
|
|
246
|
+
if (acronym && (!best || acronym.score < best.score)) {
|
|
247
|
+
best = acronym;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return best ?? { matches: false, score: 0 };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function scoreToken(token: string, index: SearchIndex): FuzzyMatch {
|
|
254
|
+
let best = scoreTokenDirect(token, index);
|
|
255
|
+
if (best.matches) return best;
|
|
256
|
+
|
|
257
|
+
for (const variant of buildAlphanumericSwapQueries(token)) {
|
|
258
|
+
const match = scoreTokenDirect(variant, index);
|
|
259
|
+
if (!match.matches) continue;
|
|
260
|
+
const score = match.score + ALPHANUMERIC_SWAP_PENALTY;
|
|
261
|
+
if (!best.matches || score < best.score) {
|
|
262
|
+
best = { matches: true, score };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return best;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** A query normalized and split once, so `fuzzyRank` doesn't re-normalize the
|
|
270
|
+
* same query for every candidate in the list. */
|
|
271
|
+
interface PreparedQuery {
|
|
272
|
+
normalized: string;
|
|
273
|
+
tokens: string[];
|
|
274
|
+
compact: string;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function prepareQuery(query: string): PreparedQuery | null {
|
|
278
|
+
const normalized = normalizeForSearch(query);
|
|
279
|
+
if (normalized.length === 0) return null;
|
|
280
|
+
return { normalized, tokens: normalized.split(" "), compact: normalized.replaceAll(" ", "") };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function fuzzyMatchCore(pq: PreparedQuery | null, index: SearchIndex): FuzzyMatch {
|
|
284
|
+
if (pq === null) {
|
|
285
|
+
return { matches: true, score: 0 };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (index.words.length === 0) {
|
|
289
|
+
return { matches: false, score: 0 };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
let totalScore = 0;
|
|
293
|
+
const phraseIndex = index.normalized.indexOf(pq.normalized);
|
|
294
|
+
if (phraseIndex >= 0 && isWordBoundaryPhrase(index.normalized, phraseIndex, pq.normalized.length)) {
|
|
295
|
+
totalScore -= PHRASE_BONUS;
|
|
296
|
+
totalScore += phraseIndex * 0.01;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const compactPhraseIndex = index.compact.indexOf(pq.compact);
|
|
300
|
+
if (compactPhraseIndex >= 0 && index.compactWordStarts.has(compactPhraseIndex)) {
|
|
301
|
+
totalScore -= COMPACT_PHRASE_BONUS;
|
|
302
|
+
totalScore += compactPhraseIndex * 0.01;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
for (const token of pq.tokens) {
|
|
306
|
+
const match = scoreToken(token, index);
|
|
307
|
+
if (!match.matches) {
|
|
308
|
+
return { matches: false, score: 0 };
|
|
309
|
+
}
|
|
310
|
+
totalScore += match.score;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return { matches: true, score: totalScore };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function fuzzyMatch(query: string, text: string): FuzzyMatch {
|
|
317
|
+
const pq = prepareQuery(query);
|
|
318
|
+
if (pq === null) return { matches: true, score: 0 };
|
|
319
|
+
return fuzzyMatchCore(pq, buildSearchIndex(text));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* A text prepared once for repeated fuzzy matching.
|
|
324
|
+
*
|
|
325
|
+
* `fuzzyMatch` builds a search index per call; the module cache only admits
|
|
326
|
+
* texts up to {@link MAX_CACHED_TEXT_LEN}, so long corpora (session or
|
|
327
|
+
* transcript search) rebuild the index on every keystroke — the dominant cost
|
|
328
|
+
* when a selector re-filters a stable candidate list as the user types. Build
|
|
329
|
+
* one `FuzzyText` per candidate and call {@link match} per query instead; the
|
|
330
|
+
* index lives exactly as long as the caller's reference.
|
|
331
|
+
*/
|
|
332
|
+
export class FuzzyText {
|
|
333
|
+
readonly #index: SearchIndex;
|
|
334
|
+
|
|
335
|
+
constructor(text: string) {
|
|
336
|
+
this.#index = buildUncachedSearchIndex(text);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Match `query` (space-separated tokens; all must match) against the prepared text. */
|
|
340
|
+
match(query: string): FuzzyMatch {
|
|
341
|
+
return fuzzyMatchCore(prepareQuery(query), this.#index);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Filter and sort items by fuzzy match quality (best matches first).
|
|
347
|
+
* Supports space-separated tokens: all tokens must match.
|
|
348
|
+
*/
|
|
349
|
+
export function fuzzyRank<T>(items: T[], query: string, getText: (item: T) => string): FuzzyFilterResult<T>[] {
|
|
350
|
+
if (!query.trim()) {
|
|
351
|
+
return items.map(item => ({ item, score: 0 }));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// A non-blank query that normalizes to empty (pure punctuation) matches
|
|
355
|
+
// everything with score 0, but still calls getText per item — consumers rely
|
|
356
|
+
// on its side effects (see fuzzy-cache.test.ts).
|
|
357
|
+
const pq = prepareQuery(query);
|
|
358
|
+
const results: FuzzyFilterResult<T>[] = [];
|
|
359
|
+
for (const item of items) {
|
|
360
|
+
const text = getText(item);
|
|
361
|
+
const match = pq === null ? { matches: true, score: 0 } : fuzzyMatchCore(pq, buildSearchIndex(text));
|
|
362
|
+
if (match.matches) {
|
|
363
|
+
results.push({ item, score: match.score });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
results.sort((a, b) => a.score - b.score);
|
|
368
|
+
return results;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function fuzzyFilter<T>(items: T[], query: string, getText: (item: T) => string): T[] {
|
|
372
|
+
return fuzzyRank(items, query, getText).map(result => result.item);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Clear the fuzzy search-index cache. Intended for tests/benchmarks so a fresh
|
|
377
|
+
* cold-start typing session can be measured on demand; not part of the supported
|
|
378
|
+
* TUI API.
|
|
379
|
+
*
|
|
380
|
+
* @internal
|
|
381
|
+
*/
|
|
382
|
+
export function resetFuzzyIndexCache(): void {
|
|
383
|
+
indexCache.clear();
|
|
384
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Core TUI interfaces and classes
|
|
2
|
+
|
|
3
|
+
// Autocomplete support
|
|
4
|
+
export * from "./autocomplete";
|
|
5
|
+
// Components
|
|
6
|
+
export * from "./components/box";
|
|
7
|
+
export * from "./components/cancellable-loader";
|
|
8
|
+
export * from "./components/editor";
|
|
9
|
+
export * from "./components/image";
|
|
10
|
+
export * from "./components/input";
|
|
11
|
+
export * from "./components/loader";
|
|
12
|
+
export * from "./components/markdown";
|
|
13
|
+
export * from "./components/scroll-view";
|
|
14
|
+
export * from "./components/select-list";
|
|
15
|
+
export * from "./components/settings-list";
|
|
16
|
+
export * from "./components/spacer";
|
|
17
|
+
export * from "./components/tab-bar";
|
|
18
|
+
export * from "./components/text";
|
|
19
|
+
export * from "./components/truncated-text";
|
|
20
|
+
// DECCARA rectangular-SGR background-fill optimizer
|
|
21
|
+
export * from "./deccara";
|
|
22
|
+
// Desktop notifications via D-Bus (Linux freedesktop notifications)
|
|
23
|
+
export * from "./desktop-notify";
|
|
24
|
+
// Editor component interface (for custom editors)
|
|
25
|
+
export type * from "./editor-component";
|
|
26
|
+
// Fuzzy matching
|
|
27
|
+
export * from "./fuzzy";
|
|
28
|
+
// Keybindings
|
|
29
|
+
export * from "./keybindings";
|
|
30
|
+
// Kitty keyboard protocol helpers
|
|
31
|
+
export * from "./keys";
|
|
32
|
+
// Kitty graphics: Unicode placeholders
|
|
33
|
+
export * from "./kitty-graphics";
|
|
34
|
+
// LaTeX → Unicode/ANSI math rendering
|
|
35
|
+
export * from "./latex-block";
|
|
36
|
+
export * from "./latex-to-unicode";
|
|
37
|
+
// SGR mouse report parsing
|
|
38
|
+
export * from "./mouse";
|
|
39
|
+
// Mermaid diagram support
|
|
40
|
+
// Input buffering for batch splitting
|
|
41
|
+
export * from "./stdin-buffer";
|
|
42
|
+
export type * from "./symbols";
|
|
43
|
+
// Terminal interface and implementations
|
|
44
|
+
export * from "./terminal";
|
|
45
|
+
// Terminal image support
|
|
46
|
+
export * from "./terminal-capabilities";
|
|
47
|
+
// TTY ID
|
|
48
|
+
export * from "./ttyid";
|
|
49
|
+
export * from "./tui";
|
|
50
|
+
// Utilities
|
|
51
|
+
export * from "./utils";
|