@oh-my-pi/pi-tui 11.8.2 → 11.8.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/README.md +2 -2
- package/package.json +3 -3
- package/src/autocomplete.ts +39 -42
- package/src/components/box.ts +38 -38
- package/src/components/cancellable-loader.ts +4 -4
- package/src/components/editor.ts +636 -631
- package/src/components/image.ts +24 -24
- package/src/components/input.ts +166 -167
- package/src/components/loader.ts +18 -18
- package/src/components/markdown.ts +136 -139
- package/src/components/select-list.ts +24 -24
- package/src/components/settings-list.ts +53 -53
- package/src/components/spacer.ts +4 -4
- package/src/components/tab-bar.ts +25 -25
- package/src/components/text.ts +40 -40
- package/src/components/truncated-text.ts +14 -14
- package/src/keybindings.ts +10 -10
- package/src/kill-ring.ts +10 -10
- package/src/stdin-buffer.ts +49 -49
- package/src/terminal.ts +67 -67
- package/src/tui.ts +152 -152
- package/src/undo-stack.ts +0 -28
package/README.md
CHANGED
|
@@ -574,8 +574,8 @@ class MyInteractiveComponent implements Component {
|
|
|
574
574
|
private selectedIndex = 0;
|
|
575
575
|
private items = ["Option 1", "Option 2", "Option 3"];
|
|
576
576
|
|
|
577
|
-
|
|
578
|
-
|
|
577
|
+
onSelect?: (index: number) => void;
|
|
578
|
+
onCancel?: () => void;
|
|
579
579
|
|
|
580
580
|
handleInput(data: string): void {
|
|
581
581
|
if (isArrowUp(data)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-tui",
|
|
3
|
-
"version": "11.8.
|
|
3
|
+
"version": "11.8.3",
|
|
4
4
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"bun": ">=1.3.7"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@oh-my-pi/pi-natives": "11.8.
|
|
51
|
-
"@oh-my-pi/pi-utils": "11.8.
|
|
50
|
+
"@oh-my-pi/pi-natives": "11.8.3",
|
|
51
|
+
"@oh-my-pi/pi-utils": "11.8.3",
|
|
52
52
|
"@types/mime-types": "^3.0.1",
|
|
53
53
|
"chalk": "^5.6.2",
|
|
54
54
|
"marked": "^17.0.1",
|
package/src/autocomplete.ts
CHANGED
|
@@ -169,14 +169,14 @@ export interface AutocompleteProvider {
|
|
|
169
169
|
|
|
170
170
|
// Combined provider that handles both slash commands and file paths
|
|
171
171
|
export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
172
|
+
#commands: (SlashCommand | AutocompleteItem)[];
|
|
173
|
+
#basePath: string;
|
|
174
|
+
#dirCache: Map<string, { entries: fs.Dirent[]; timestamp: number }> = new Map();
|
|
175
|
+
readonly #DIR_CACHE_TTL = 2000; // 2 seconds
|
|
176
176
|
|
|
177
177
|
constructor(commands: (SlashCommand | AutocompleteItem)[] = [], basePath: string = process.cwd()) {
|
|
178
|
-
this
|
|
179
|
-
this
|
|
178
|
+
this.#commands = commands;
|
|
179
|
+
this.#basePath = basePath;
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
async getSuggestions(
|
|
@@ -188,15 +188,15 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
188
188
|
const textBeforeCursor = currentLine.slice(0, cursorCol);
|
|
189
189
|
|
|
190
190
|
// Check for @ file reference (fuzzy search) - must be after a delimiter or at start
|
|
191
|
-
const atPrefix = this
|
|
191
|
+
const atPrefix = this.#extractAtPrefix(textBeforeCursor);
|
|
192
192
|
if (atPrefix) {
|
|
193
193
|
const { rawPrefix, isQuotedPrefix } = parsePathPrefix(atPrefix);
|
|
194
194
|
const suggestions =
|
|
195
195
|
rawPrefix.length > 0
|
|
196
|
-
? await this
|
|
197
|
-
: await this
|
|
196
|
+
? await this.#getFuzzyFileSuggestions(rawPrefix, { isQuotedPrefix })
|
|
197
|
+
: await this.#getFileSuggestions("@");
|
|
198
198
|
if (suggestions.length === 0 && rawPrefix.length > 0) {
|
|
199
|
-
const fallback = await this
|
|
199
|
+
const fallback = await this.#getFileSuggestions(atPrefix);
|
|
200
200
|
if (fallback.length === 0) return null;
|
|
201
201
|
return { items: fallback, prefix: atPrefix };
|
|
202
202
|
}
|
|
@@ -218,7 +218,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
218
218
|
const lowerPrefix = prefix.toLowerCase();
|
|
219
219
|
|
|
220
220
|
// Filter commands using fuzzy matching (subsequence match)
|
|
221
|
-
const matches = this
|
|
221
|
+
const matches = this.#commands
|
|
222
222
|
.filter(cmd => {
|
|
223
223
|
const name = "name" in cmd ? cmd.name : cmd.value;
|
|
224
224
|
if (!name) return false;
|
|
@@ -255,7 +255,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
255
255
|
const commandName = textBeforeCursor.slice(1, spaceIndex); // Command without "/"
|
|
256
256
|
const argumentText = textBeforeCursor.slice(spaceIndex + 1); // Text after space
|
|
257
257
|
|
|
258
|
-
const command = this
|
|
258
|
+
const command = this.#commands.find(cmd => {
|
|
259
259
|
const name = "name" in cmd ? cmd.name : cmd.value;
|
|
260
260
|
return name === commandName;
|
|
261
261
|
});
|
|
@@ -276,10 +276,10 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
// Check for file paths - triggered by Tab or if we detect a path pattern
|
|
279
|
-
const pathMatch = this
|
|
279
|
+
const pathMatch = this.#extractPathPrefix(textBeforeCursor, false);
|
|
280
280
|
|
|
281
281
|
if (pathMatch !== null) {
|
|
282
|
-
const suggestions = await this
|
|
282
|
+
const suggestions = await this.#getFileSuggestions(pathMatch);
|
|
283
283
|
if (suggestions.length === 0) return null;
|
|
284
284
|
|
|
285
285
|
// Check if we have an exact match that is a directory
|
|
@@ -372,7 +372,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
372
372
|
}
|
|
373
373
|
|
|
374
374
|
// Extract @ prefix for fuzzy file suggestions
|
|
375
|
-
|
|
375
|
+
#extractAtPrefix(text: string): string | null {
|
|
376
376
|
const quotedPrefix = extractQuotedPrefix(text);
|
|
377
377
|
if (quotedPrefix?.startsWith('@"')) {
|
|
378
378
|
return quotedPrefix;
|
|
@@ -389,7 +389,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
389
389
|
}
|
|
390
390
|
|
|
391
391
|
// Extract a path-like prefix from the text before cursor
|
|
392
|
-
|
|
392
|
+
#extractPathPrefix(text: string, forceExtract: boolean = false): string | null {
|
|
393
393
|
const quotedPrefix = extractQuotedPrefix(text);
|
|
394
394
|
if (quotedPrefix) {
|
|
395
395
|
return quotedPrefix;
|
|
@@ -419,7 +419,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
419
419
|
}
|
|
420
420
|
|
|
421
421
|
// Expand home directory (~/) to actual home path
|
|
422
|
-
|
|
422
|
+
#expandHomePath(filePath: string): string {
|
|
423
423
|
if (filePath.startsWith("~/")) {
|
|
424
424
|
const expandedPath = path.join(os.homedir(), filePath.slice(2));
|
|
425
425
|
// Preserve trailing slash if original path had one
|
|
@@ -430,40 +430,40 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
430
430
|
return filePath;
|
|
431
431
|
}
|
|
432
432
|
|
|
433
|
-
|
|
433
|
+
async #getCachedDirEntries(searchDir: string): Promise<fs.Dirent[]> {
|
|
434
434
|
const now = Date.now();
|
|
435
|
-
const cached = this
|
|
435
|
+
const cached = this.#dirCache.get(searchDir);
|
|
436
436
|
|
|
437
|
-
if (cached && now - cached.timestamp < this
|
|
437
|
+
if (cached && now - cached.timestamp < this.#DIR_CACHE_TTL) {
|
|
438
438
|
return cached.entries;
|
|
439
439
|
}
|
|
440
440
|
|
|
441
441
|
const entries = await fs.promises.readdir(searchDir, { withFileTypes: true });
|
|
442
|
-
this
|
|
442
|
+
this.#dirCache.set(searchDir, { entries, timestamp: now });
|
|
443
443
|
|
|
444
|
-
if (this
|
|
445
|
-
const sortedKeys = [...this
|
|
444
|
+
if (this.#dirCache.size > 100) {
|
|
445
|
+
const sortedKeys = [...this.#dirCache.entries()]
|
|
446
446
|
.sort((a, b) => a[1].timestamp - b[1].timestamp)
|
|
447
447
|
.slice(0, 50)
|
|
448
448
|
.map(([key]) => key);
|
|
449
449
|
for (const key of sortedKeys) {
|
|
450
|
-
this
|
|
450
|
+
this.#dirCache.delete(key);
|
|
451
451
|
}
|
|
452
452
|
}
|
|
453
453
|
|
|
454
454
|
return entries;
|
|
455
455
|
}
|
|
456
456
|
|
|
457
|
-
|
|
457
|
+
invalidateDirCache(dir?: string): void {
|
|
458
458
|
if (dir) {
|
|
459
|
-
this
|
|
459
|
+
this.#dirCache.delete(dir);
|
|
460
460
|
} else {
|
|
461
|
-
this
|
|
461
|
+
this.#dirCache.clear();
|
|
462
462
|
}
|
|
463
463
|
}
|
|
464
464
|
|
|
465
465
|
// Get file/directory suggestions for a given path prefix
|
|
466
|
-
|
|
466
|
+
async #getFileSuggestions(prefix: string): Promise<AutocompleteItem[]> {
|
|
467
467
|
try {
|
|
468
468
|
let searchDir: string;
|
|
469
469
|
let searchPrefix: string;
|
|
@@ -472,7 +472,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
472
472
|
|
|
473
473
|
// Handle home directory expansion
|
|
474
474
|
if (expandedPrefix.startsWith("~")) {
|
|
475
|
-
expandedPrefix = this
|
|
475
|
+
expandedPrefix = this.#expandHomePath(expandedPrefix);
|
|
476
476
|
}
|
|
477
477
|
|
|
478
478
|
const isRootPrefix =
|
|
@@ -489,7 +489,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
489
489
|
if (rawPrefix.startsWith("~") || expandedPrefix.startsWith("/")) {
|
|
490
490
|
searchDir = expandedPrefix;
|
|
491
491
|
} else {
|
|
492
|
-
searchDir = path.join(this
|
|
492
|
+
searchDir = path.join(this.#basePath, expandedPrefix);
|
|
493
493
|
}
|
|
494
494
|
searchPrefix = "";
|
|
495
495
|
} else if (rawPrefix.endsWith("/")) {
|
|
@@ -497,7 +497,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
497
497
|
if (rawPrefix.startsWith("~") || expandedPrefix.startsWith("/")) {
|
|
498
498
|
searchDir = expandedPrefix;
|
|
499
499
|
} else {
|
|
500
|
-
searchDir = path.join(this
|
|
500
|
+
searchDir = path.join(this.#basePath, expandedPrefix);
|
|
501
501
|
}
|
|
502
502
|
searchPrefix = "";
|
|
503
503
|
} else {
|
|
@@ -507,12 +507,12 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
507
507
|
if (rawPrefix.startsWith("~") || expandedPrefix.startsWith("/")) {
|
|
508
508
|
searchDir = dir;
|
|
509
509
|
} else {
|
|
510
|
-
searchDir = path.join(this
|
|
510
|
+
searchDir = path.join(this.#basePath, dir);
|
|
511
511
|
}
|
|
512
512
|
searchPrefix = file;
|
|
513
513
|
}
|
|
514
514
|
|
|
515
|
-
const entries = await this
|
|
515
|
+
const entries = await this.#getCachedDirEntries(searchDir);
|
|
516
516
|
const suggestions: AutocompleteItem[] = [];
|
|
517
517
|
|
|
518
518
|
for (const entry of entries) {
|
|
@@ -600,7 +600,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
600
600
|
|
|
601
601
|
// Score an entry against the query (higher = better match)
|
|
602
602
|
// isDirectory adds bonus to prioritize folders
|
|
603
|
-
|
|
603
|
+
#scoreEntry(filePath: string, query: string, isDirectory: boolean): number {
|
|
604
604
|
const fileName = path.basename(filePath);
|
|
605
605
|
const lowerFileName = fileName.toLowerCase();
|
|
606
606
|
const lowerQuery = query.toLowerCase();
|
|
@@ -622,14 +622,11 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
622
622
|
return score;
|
|
623
623
|
}
|
|
624
624
|
|
|
625
|
-
|
|
626
|
-
query: string,
|
|
627
|
-
options: { isQuotedPrefix: boolean },
|
|
628
|
-
): Promise<AutocompleteItem[]> {
|
|
625
|
+
async #getFuzzyFileSuggestions(query: string, options: { isQuotedPrefix: boolean }): Promise<AutocompleteItem[]> {
|
|
629
626
|
try {
|
|
630
627
|
const result = await fuzzyFind({
|
|
631
628
|
query,
|
|
632
|
-
path: this
|
|
629
|
+
path: this.#basePath,
|
|
633
630
|
maxResults: 100,
|
|
634
631
|
hidden: true,
|
|
635
632
|
gitignore: true,
|
|
@@ -647,7 +644,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
647
644
|
.map(entry => ({
|
|
648
645
|
path: entry.path,
|
|
649
646
|
isDirectory: entry.isDirectory,
|
|
650
|
-
score: query ? this
|
|
647
|
+
score: query ? this.#scoreEntry(entry.path, query, entry.isDirectory) : 1,
|
|
651
648
|
}))
|
|
652
649
|
.filter(entry => entry.score > 0);
|
|
653
650
|
|
|
@@ -692,9 +689,9 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
692
689
|
}
|
|
693
690
|
|
|
694
691
|
// Force extract path prefix - this will always return something
|
|
695
|
-
const pathMatch = this
|
|
692
|
+
const pathMatch = this.#extractPathPrefix(textBeforeCursor, true);
|
|
696
693
|
if (pathMatch !== null) {
|
|
697
|
-
const suggestions = await this
|
|
694
|
+
const suggestions = await this.#getFileSuggestions(pathMatch);
|
|
698
695
|
if (suggestions.length === 0) return null;
|
|
699
696
|
|
|
700
697
|
return {
|
package/src/components/box.ts
CHANGED
|
@@ -11,66 +11,66 @@ type Cache = {
|
|
|
11
11
|
*/
|
|
12
12
|
export class Box implements Component {
|
|
13
13
|
children: Component[] = [];
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
#paddingX: number;
|
|
15
|
+
#paddingY: number;
|
|
16
|
+
#bgFn?: (text: string) => string;
|
|
17
17
|
|
|
18
18
|
// Cache for rendered output
|
|
19
|
-
|
|
19
|
+
#cached?: Cache;
|
|
20
20
|
|
|
21
21
|
constructor(paddingX = 1, paddingY = 1, bgFn?: (text: string) => string) {
|
|
22
|
-
this
|
|
23
|
-
this
|
|
24
|
-
this
|
|
22
|
+
this.#paddingX = paddingX;
|
|
23
|
+
this.#paddingY = paddingY;
|
|
24
|
+
this.#bgFn = bgFn;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
addChild(component: Component): void {
|
|
28
28
|
this.children.push(component);
|
|
29
|
-
this
|
|
29
|
+
this.#invalidateCache();
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
removeChild(component: Component): void {
|
|
33
33
|
const index = this.children.indexOf(component);
|
|
34
34
|
if (index !== -1) {
|
|
35
35
|
this.children.splice(index, 1);
|
|
36
|
-
this
|
|
36
|
+
this.#invalidateCache();
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
clear(): void {
|
|
41
41
|
this.children = [];
|
|
42
|
-
this
|
|
42
|
+
this.#invalidateCache();
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
setBgFn(bgFn?: (text: string) => string): void {
|
|
46
|
-
this
|
|
46
|
+
this.#bgFn = bgFn;
|
|
47
47
|
// Don't invalidate here - we'll detect bgFn changes by sampling output
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
this
|
|
50
|
+
#invalidateCache(): void {
|
|
51
|
+
this.#cached = undefined;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
Box
|
|
57
|
-
Box
|
|
58
|
-
let h = Bun.hash.xxHash64(Box
|
|
54
|
+
static #tmp = new Uint32Array(2);
|
|
55
|
+
#computeCacheKey(width: number, childLines: string[], bgSample: string | undefined): bigint {
|
|
56
|
+
Box.#tmp[0] = width;
|
|
57
|
+
Box.#tmp[1] = childLines.length;
|
|
58
|
+
let h = Bun.hash.xxHash64(Box.#tmp);
|
|
59
59
|
for (const line of childLines) {
|
|
60
|
-
Box
|
|
61
|
-
h = Bun.hash.xxHash64(Box
|
|
60
|
+
Box.#tmp[0] = line.length;
|
|
61
|
+
h = Bun.hash.xxHash64(Box.#tmp, h);
|
|
62
62
|
h = Bun.hash.xxHash64(line, h);
|
|
63
63
|
}
|
|
64
64
|
h = Bun.hash.xxHash64(bgSample ?? "", h);
|
|
65
65
|
return h;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
return this
|
|
68
|
+
#matchCache(cacheKey: bigint): boolean {
|
|
69
|
+
return this.#cached?.key === cacheKey;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
invalidate(): void {
|
|
73
|
-
this
|
|
73
|
+
this.#invalidateCache();
|
|
74
74
|
for (const child of this.children) {
|
|
75
75
|
child.invalidate?.();
|
|
76
76
|
}
|
|
@@ -81,8 +81,8 @@ export class Box implements Component {
|
|
|
81
81
|
return [];
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
const contentWidth = Math.max(1, width - this
|
|
85
|
-
const leftPad = padding(this
|
|
84
|
+
const contentWidth = Math.max(1, width - this.#paddingX * 2);
|
|
85
|
+
const leftPad = padding(this.#paddingX);
|
|
86
86
|
|
|
87
87
|
// Render all children
|
|
88
88
|
const childLines: string[] = [];
|
|
@@ -98,46 +98,46 @@ export class Box implements Component {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// Check if bgFn output changed by sampling
|
|
101
|
-
const bgSample = this
|
|
101
|
+
const bgSample = this.#bgFn ? this.#bgFn("test") : undefined;
|
|
102
102
|
|
|
103
|
-
const cacheKey = this
|
|
103
|
+
const cacheKey = this.#computeCacheKey(width, childLines, bgSample);
|
|
104
104
|
|
|
105
105
|
// Check cache validity
|
|
106
|
-
if (this
|
|
107
|
-
return this
|
|
106
|
+
if (this.#matchCache(cacheKey)) {
|
|
107
|
+
return this.#cached!.result;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
// Apply background and padding
|
|
111
111
|
const result: string[] = [];
|
|
112
112
|
|
|
113
113
|
// Top padding
|
|
114
|
-
for (let i = 0; i < this
|
|
115
|
-
result.push(this
|
|
114
|
+
for (let i = 0; i < this.#paddingY; i++) {
|
|
115
|
+
result.push(this.#applyBg("", width));
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
// Content
|
|
119
119
|
for (const line of childLines) {
|
|
120
|
-
result.push(this
|
|
120
|
+
result.push(this.#applyBg(line, width));
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
// Bottom padding
|
|
124
|
-
for (let i = 0; i < this
|
|
125
|
-
result.push(this
|
|
124
|
+
for (let i = 0; i < this.#paddingY; i++) {
|
|
125
|
+
result.push(this.#applyBg("", width));
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
// Update cache
|
|
129
|
-
this
|
|
129
|
+
this.#cached = { key: cacheKey, result };
|
|
130
130
|
|
|
131
131
|
return result;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
|
|
134
|
+
#applyBg(line: string, width: number): string {
|
|
135
135
|
const visLen = visibleWidth(line);
|
|
136
136
|
const padNeeded = Math.max(0, width - visLen);
|
|
137
137
|
const padded = line + padding(padNeeded);
|
|
138
138
|
|
|
139
|
-
if (this
|
|
140
|
-
return applyBackgroundToLine(padded, width, this
|
|
139
|
+
if (this.#bgFn) {
|
|
140
|
+
return applyBackgroundToLine(padded, width, this.#bgFn);
|
|
141
141
|
}
|
|
142
142
|
return padded;
|
|
143
143
|
}
|
|
@@ -11,24 +11,24 @@ import { Loader } from "./loader";
|
|
|
11
11
|
* doWork(loader.signal).then(done);
|
|
12
12
|
*/
|
|
13
13
|
export class CancellableLoader extends Loader {
|
|
14
|
-
|
|
14
|
+
#abortController = new AbortController();
|
|
15
15
|
|
|
16
16
|
/** Called when user presses Escape */
|
|
17
17
|
onAbort?: () => void;
|
|
18
18
|
|
|
19
19
|
/** AbortSignal that is aborted when user presses Escape */
|
|
20
20
|
get signal(): AbortSignal {
|
|
21
|
-
return this
|
|
21
|
+
return this.#abortController.signal;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/** Whether the loader was aborted */
|
|
25
25
|
get aborted(): boolean {
|
|
26
|
-
return this
|
|
26
|
+
return this.#abortController.signal.aborted;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
handleInput(data: string): void {
|
|
30
30
|
if (matchesKey(data, "escape") || matchesKey(data, "esc")) {
|
|
31
|
-
this
|
|
31
|
+
this.#abortController.abort();
|
|
32
32
|
this.onAbort?.();
|
|
33
33
|
}
|
|
34
34
|
}
|