@oh-my-pi/pi-tui 1.341.0 → 2.0.1337
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/package.json +1 -1
- package/src/autocomplete.ts +7 -5
- package/src/components/box.ts +2 -2
- package/src/components/cancellable-loader.ts +2 -2
- package/src/components/editor.ts +5 -5
- package/src/components/image.ts +2 -2
- package/src/components/input.ts +3 -3
- package/src/components/loader.ts +2 -2
- package/src/components/markdown.ts +2 -2
- package/src/components/select-list.ts +3 -3
- package/src/components/settings-list.ts +3 -3
- package/src/components/spacer.ts +1 -1
- package/src/components/tab-bar.ts +2 -2
- package/src/components/text.ts +6 -2
- package/src/components/truncated-text.ts +2 -2
- package/src/index.ts +19 -19
- package/src/tui.ts +37 -15
package/package.json
CHANGED
package/src/autocomplete.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { readdirSync, statSync } from "fs";
|
|
2
|
-
import { homedir } from "os";
|
|
3
|
-
import { basename, dirname, join } from "path";
|
|
1
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
4
4
|
|
|
5
5
|
// Use fd to walk directory tree (fast, respects .gitignore)
|
|
6
6
|
function walkDirectoryWithFd(
|
|
@@ -387,7 +387,8 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
387
387
|
const fullPath = join(searchDir, entry.name);
|
|
388
388
|
isDirectory = statSync(fullPath).isDirectory();
|
|
389
389
|
} catch {
|
|
390
|
-
// Broken symlink
|
|
390
|
+
// Broken symlink, file deleted between readdir and stat, or permission error
|
|
391
|
+
continue;
|
|
391
392
|
}
|
|
392
393
|
}
|
|
393
394
|
|
|
@@ -459,7 +460,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
459
460
|
});
|
|
460
461
|
|
|
461
462
|
return suggestions;
|
|
462
|
-
} catch
|
|
463
|
+
} catch {
|
|
463
464
|
// Directory doesn't exist or not accessible
|
|
464
465
|
return [];
|
|
465
466
|
}
|
|
@@ -527,6 +528,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
527
528
|
|
|
528
529
|
return suggestions;
|
|
529
530
|
} catch {
|
|
531
|
+
// Directory doesn't exist or not accessible
|
|
530
532
|
return [];
|
|
531
533
|
}
|
|
532
534
|
}
|
package/src/components/box.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Component } from "../tui
|
|
2
|
-
import { applyBackgroundToLine, visibleWidth } from "../utils
|
|
1
|
+
import type { Component } from "../tui";
|
|
2
|
+
import { applyBackgroundToLine, visibleWidth } from "../utils";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Box component - a container that applies padding and background to all children
|
package/src/components/editor.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AutocompleteProvider, CombinedAutocompleteProvider } from "../autocomplete
|
|
1
|
+
import type { AutocompleteProvider, CombinedAutocompleteProvider } from "../autocomplete";
|
|
2
2
|
import {
|
|
3
3
|
isAltBackspace,
|
|
4
4
|
isAltEnter,
|
|
@@ -24,10 +24,10 @@ import {
|
|
|
24
24
|
isHome,
|
|
25
25
|
isShiftEnter,
|
|
26
26
|
isTab,
|
|
27
|
-
} from "../keys
|
|
28
|
-
import type { Component } from "../tui
|
|
29
|
-
import { getSegmenter, isPunctuationChar, isWhitespaceChar, visibleWidth } from "../utils
|
|
30
|
-
import { SelectList, type SelectListTheme } from "./select-list
|
|
27
|
+
} from "../keys";
|
|
28
|
+
import type { Component } from "../tui";
|
|
29
|
+
import { getSegmenter, isPunctuationChar, isWhitespaceChar, visibleWidth } from "../utils";
|
|
30
|
+
import { SelectList, type SelectListTheme } from "./select-list";
|
|
31
31
|
|
|
32
32
|
const segmenter = getSegmenter();
|
|
33
33
|
|
package/src/components/image.ts
CHANGED
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
type ImageDimensions,
|
|
5
5
|
imageFallback,
|
|
6
6
|
renderImage,
|
|
7
|
-
} from "../terminal-image
|
|
8
|
-
import type { Component } from "../tui
|
|
7
|
+
} from "../terminal-image";
|
|
8
|
+
import type { Component } from "../tui";
|
|
9
9
|
|
|
10
10
|
export interface ImageTheme {
|
|
11
11
|
fallbackColor: (str: string) => string;
|
package/src/components/input.ts
CHANGED
|
@@ -14,9 +14,9 @@ import {
|
|
|
14
14
|
isCtrlW,
|
|
15
15
|
isDelete,
|
|
16
16
|
isEnter,
|
|
17
|
-
} from "../keys
|
|
18
|
-
import type { Component } from "../tui
|
|
19
|
-
import { getSegmenter, isPunctuationChar, isWhitespaceChar, visibleWidth } from "../utils
|
|
17
|
+
} from "../keys";
|
|
18
|
+
import type { Component } from "../tui";
|
|
19
|
+
import { getSegmenter, isPunctuationChar, isWhitespaceChar, visibleWidth } from "../utils";
|
|
20
20
|
|
|
21
21
|
const segmenter = getSegmenter();
|
|
22
22
|
|
package/src/components/loader.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { marked, type Token } from "marked";
|
|
2
|
-
import type { Component } from "../tui
|
|
3
|
-
import { applyBackgroundToLine, visibleWidth, wrapTextWithAnsi } from "../utils
|
|
2
|
+
import type { Component } from "../tui";
|
|
3
|
+
import { applyBackgroundToLine, visibleWidth, wrapTextWithAnsi } from "../utils";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Default text styling for markdown content.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { isArrowDown, isArrowUp, isCtrlC, isEnter, isEscape } from "../keys
|
|
2
|
-
import type { Component } from "../tui
|
|
3
|
-
import { truncateToWidth } from "../utils
|
|
1
|
+
import { isArrowDown, isArrowUp, isCtrlC, isEnter, isEscape } from "../keys";
|
|
2
|
+
import type { Component } from "../tui";
|
|
3
|
+
import { truncateToWidth } from "../utils";
|
|
4
4
|
|
|
5
5
|
export interface SelectItem {
|
|
6
6
|
value: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { isArrowDown, isArrowUp, isCtrlC, isEnter, isEscape } from "../keys
|
|
2
|
-
import type { Component } from "../tui
|
|
3
|
-
import { truncateToWidth, visibleWidth } from "../utils
|
|
1
|
+
import { isArrowDown, isArrowUp, isCtrlC, isEnter, isEscape } from "../keys";
|
|
2
|
+
import type { Component } from "../tui";
|
|
3
|
+
import { truncateToWidth, visibleWidth } from "../utils";
|
|
4
4
|
|
|
5
5
|
export interface SettingItem {
|
|
6
6
|
/** Unique identifier for this setting */
|
package/src/components/spacer.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* - Shift+Tab / Arrow Left: Previous tab (wraps around)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { isArrowLeft, isArrowRight, isShiftTab, isTab } from "../keys
|
|
13
|
-
import type { Component } from "../tui
|
|
12
|
+
import { isArrowLeft, isArrowRight, isShiftTab, isTab } from "../keys";
|
|
13
|
+
import type { Component } from "../tui";
|
|
14
14
|
|
|
15
15
|
/** Tab definition */
|
|
16
16
|
export interface Tab {
|
package/src/components/text.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Component } from "../tui
|
|
2
|
-
import { applyBackgroundToLine, visibleWidth, wrapTextWithAnsi } from "../utils
|
|
1
|
+
import type { Component } from "../tui";
|
|
2
|
+
import { applyBackgroundToLine, visibleWidth, wrapTextWithAnsi } from "../utils";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Text component - displays multi-line text with word wrapping
|
|
@@ -22,6 +22,10 @@ export class Text implements Component {
|
|
|
22
22
|
this.customBgFn = customBgFn;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
getText(): string {
|
|
26
|
+
return this.text;
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
setText(text: string): void {
|
|
26
30
|
this.text = text;
|
|
27
31
|
this.cachedText = undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Component } from "../tui
|
|
2
|
-
import { truncateToWidth, visibleWidth } from "../utils
|
|
1
|
+
import type { Component } from "../tui";
|
|
2
|
+
import { truncateToWidth, visibleWidth } from "../utils";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Text component that truncates to fit viewport width
|
package/src/index.ts
CHANGED
|
@@ -6,21 +6,21 @@ export {
|
|
|
6
6
|
type AutocompleteProvider,
|
|
7
7
|
CombinedAutocompleteProvider,
|
|
8
8
|
type SlashCommand,
|
|
9
|
-
} from "./autocomplete
|
|
9
|
+
} from "./autocomplete";
|
|
10
10
|
// Components
|
|
11
|
-
export { Box } from "./components/box
|
|
12
|
-
export { CancellableLoader } from "./components/cancellable-loader
|
|
13
|
-
export { Editor, type EditorTheme } from "./components/editor
|
|
14
|
-
export { Image, type ImageOptions, type ImageTheme } from "./components/image
|
|
15
|
-
export { Input } from "./components/input
|
|
16
|
-
export { Loader } from "./components/loader
|
|
17
|
-
export { type DefaultTextStyle, Markdown, type MarkdownTheme } from "./components/markdown
|
|
18
|
-
export { type SelectItem, SelectList, type SelectListTheme } from "./components/select-list
|
|
19
|
-
export { type SettingItem, SettingsList, type SettingsListTheme } from "./components/settings-list
|
|
20
|
-
export { Spacer } from "./components/spacer
|
|
21
|
-
export { type Tab, TabBar, type TabBarTheme } from "./components/tab-bar
|
|
22
|
-
export { Text } from "./components/text
|
|
23
|
-
export { TruncatedText } from "./components/truncated-text
|
|
11
|
+
export { Box } from "./components/box";
|
|
12
|
+
export { CancellableLoader } from "./components/cancellable-loader";
|
|
13
|
+
export { Editor, type EditorTheme } from "./components/editor";
|
|
14
|
+
export { Image, type ImageOptions, type ImageTheme } from "./components/image";
|
|
15
|
+
export { Input } from "./components/input";
|
|
16
|
+
export { Loader } from "./components/loader";
|
|
17
|
+
export { type DefaultTextStyle, Markdown, type MarkdownTheme } from "./components/markdown";
|
|
18
|
+
export { type SelectItem, SelectList, type SelectListTheme } from "./components/select-list";
|
|
19
|
+
export { type SettingItem, SettingsList, type SettingsListTheme } from "./components/settings-list";
|
|
20
|
+
export { Spacer } from "./components/spacer";
|
|
21
|
+
export { type Tab, TabBar, type TabBarTheme } from "./components/tab-bar";
|
|
22
|
+
export { Text } from "./components/text";
|
|
23
|
+
export { TruncatedText } from "./components/truncated-text";
|
|
24
24
|
// Kitty keyboard protocol helpers
|
|
25
25
|
export {
|
|
26
26
|
isAltBackspace,
|
|
@@ -60,9 +60,9 @@ export {
|
|
|
60
60
|
isShiftTab,
|
|
61
61
|
isTab,
|
|
62
62
|
Keys,
|
|
63
|
-
} from "./keys
|
|
63
|
+
} from "./keys";
|
|
64
64
|
// Terminal interface and implementations
|
|
65
|
-
export { emergencyTerminalRestore, ProcessTerminal, type Terminal } from "./terminal
|
|
65
|
+
export { emergencyTerminalRestore, ProcessTerminal, type Terminal } from "./terminal";
|
|
66
66
|
// Terminal image support
|
|
67
67
|
export {
|
|
68
68
|
type CellDimensions,
|
|
@@ -85,7 +85,7 @@ export {
|
|
|
85
85
|
resetCapabilitiesCache,
|
|
86
86
|
setCellDimensions,
|
|
87
87
|
type TerminalCapabilities,
|
|
88
|
-
} from "./terminal-image
|
|
89
|
-
export { type Component, Container, TUI } from "./tui
|
|
88
|
+
} from "./terminal-image";
|
|
89
|
+
export { type Component, Container, TUI } from "./tui";
|
|
90
90
|
// Utilities
|
|
91
|
-
export { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "./utils
|
|
91
|
+
export { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "./utils";
|
package/src/tui.ts
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import * as path from "node:path";
|
|
8
|
-
import { isShiftCtrlD } from "./keys
|
|
9
|
-
import type { Terminal } from "./terminal
|
|
10
|
-
import { getCapabilities, setCellDimensions } from "./terminal-image
|
|
11
|
-
import { visibleWidth } from "./utils
|
|
8
|
+
import { isShiftCtrlD } from "./keys";
|
|
9
|
+
import type { Terminal } from "./terminal";
|
|
10
|
+
import { getCapabilities, setCellDimensions } from "./terminal-image";
|
|
11
|
+
import { visibleWidth } from "./utils";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Component interface - all components must implement this
|
|
@@ -86,6 +86,7 @@ export class TUI extends Container {
|
|
|
86
86
|
private cursorRow = 0; // Track where cursor is (0-indexed, relative to our first line)
|
|
87
87
|
private inputBuffer = ""; // Buffer for parsing terminal responses
|
|
88
88
|
private cellSizeQueryPending = false;
|
|
89
|
+
private inputQueue: string[] = []; // Queue input during cell size query to avoid interleaving
|
|
89
90
|
|
|
90
91
|
constructor(terminal: Terminal) {
|
|
91
92
|
super();
|
|
@@ -142,9 +143,24 @@ export class TUI extends Container {
|
|
|
142
143
|
this.inputBuffer += data;
|
|
143
144
|
const filtered = this.parseCellSizeResponse();
|
|
144
145
|
if (filtered.length === 0) return;
|
|
145
|
-
|
|
146
|
+
if (filtered.length > 0) {
|
|
147
|
+
this.inputQueue.push(filtered);
|
|
148
|
+
}
|
|
149
|
+
// Process queued input after cell size response completes
|
|
150
|
+
if (!this.cellSizeQueryPending && this.inputQueue.length > 0) {
|
|
151
|
+
const queued = this.inputQueue;
|
|
152
|
+
this.inputQueue = [];
|
|
153
|
+
for (const item of queued) {
|
|
154
|
+
this.processInput(item);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
146
158
|
}
|
|
147
159
|
|
|
160
|
+
this.processInput(data);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private processInput(data: string): void {
|
|
148
164
|
// Global debug key handler (Shift+Ctrl+D)
|
|
149
165
|
if (isShiftCtrlD(data) && this.onDebug) {
|
|
150
166
|
this.onDebug();
|
|
@@ -169,16 +185,17 @@ export class TUI extends Container {
|
|
|
169
185
|
const heightPx = parseInt(match[1], 10);
|
|
170
186
|
const widthPx = parseInt(match[2], 10);
|
|
171
187
|
|
|
188
|
+
// Remove the response from buffer first
|
|
189
|
+
this.inputBuffer = this.inputBuffer.replace(responsePattern, "");
|
|
190
|
+
this.cellSizeQueryPending = false;
|
|
191
|
+
|
|
172
192
|
if (heightPx > 0 && widthPx > 0) {
|
|
173
193
|
setCellDimensions({ widthPx, heightPx });
|
|
174
194
|
// Invalidate all components so images re-render with correct dimensions
|
|
195
|
+
// This is safe now because cellSizeQueryPending=false prevents race with render
|
|
175
196
|
this.invalidate();
|
|
176
197
|
this.requestRender();
|
|
177
198
|
}
|
|
178
|
-
|
|
179
|
-
// Remove the response from buffer
|
|
180
|
-
this.inputBuffer = this.inputBuffer.replace(responsePattern, "");
|
|
181
|
-
this.cellSizeQueryPending = false;
|
|
182
199
|
}
|
|
183
200
|
|
|
184
201
|
// Check if we have a partial cell size response starting (wait for more data)
|
|
@@ -206,8 +223,11 @@ export class TUI extends Container {
|
|
|
206
223
|
}
|
|
207
224
|
|
|
208
225
|
private doRender(): void {
|
|
226
|
+
// Capture terminal dimensions at start to ensure consistency throughout render
|
|
209
227
|
const width = this.terminal.columns;
|
|
210
228
|
const height = this.terminal.rows;
|
|
229
|
+
// Snapshot cursor position at start of render for consistent viewport calculations
|
|
230
|
+
const currentCursorRow = this.cursorRow;
|
|
211
231
|
|
|
212
232
|
// Render all components to get new lines
|
|
213
233
|
const newLines = this.render(width);
|
|
@@ -267,10 +287,10 @@ export class TUI extends Container {
|
|
|
267
287
|
}
|
|
268
288
|
|
|
269
289
|
// Check if firstChanged is outside the viewport
|
|
270
|
-
//
|
|
271
|
-
// Viewport shows lines from (
|
|
290
|
+
// Use snapshotted cursor position for consistent viewport calculation
|
|
291
|
+
// Viewport shows lines from (currentCursorRow - height + 1) to currentCursorRow
|
|
272
292
|
// If firstChanged < viewportTop, we need full re-render
|
|
273
|
-
const viewportTop =
|
|
293
|
+
const viewportTop = currentCursorRow - height + 1;
|
|
274
294
|
if (firstChanged < viewportTop) {
|
|
275
295
|
// First change is above viewport - need full re-render
|
|
276
296
|
let buffer = "\x1b[?2026h"; // Begin synchronized output
|
|
@@ -291,8 +311,8 @@ export class TUI extends Container {
|
|
|
291
311
|
// Build buffer with all updates wrapped in synchronized output
|
|
292
312
|
let buffer = "\x1b[?2026h"; // Begin synchronized output
|
|
293
313
|
|
|
294
|
-
// Move cursor to first changed line
|
|
295
|
-
const lineDiff = firstChanged -
|
|
314
|
+
// Move cursor to first changed line using snapshotted position
|
|
315
|
+
const lineDiff = firstChanged - currentCursorRow;
|
|
296
316
|
if (lineDiff > 0) {
|
|
297
317
|
buffer += `\x1b[${lineDiff}B`; // Move down
|
|
298
318
|
} else if (lineDiff < 0) {
|
|
@@ -323,7 +343,9 @@ export class TUI extends Container {
|
|
|
323
343
|
try {
|
|
324
344
|
fs.mkdirSync(path.dirname(crashLogPath), { recursive: true });
|
|
325
345
|
fs.writeFileSync(crashLogPath, crashData);
|
|
326
|
-
} catch {
|
|
346
|
+
} catch {
|
|
347
|
+
// Ignore - crash log is best-effort
|
|
348
|
+
}
|
|
327
349
|
throw new Error(`Rendered line ${i} exceeds terminal width. Debug log written to ${crashLogPath}`);
|
|
328
350
|
}
|
|
329
351
|
buffer += line;
|