@oh-my-pi/pi-tui 18.6.1 → 18.7.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 +50 -58
- package/README.md +6 -2
- package/dist/types/app-keybindings.d.ts +7 -2
- package/dist/types/autocomplete.d.ts +5 -2
- package/dist/types/charts/chart-plan.d.ts +92 -0
- package/dist/types/charts/chart-svg.d.ts +30 -0
- package/dist/types/charts/table-data.d.ts +107 -0
- package/dist/types/chat/assistant-message.d.ts +10 -1
- package/dist/types/chat/chat-transcript-builder.d.ts +3 -0
- package/dist/types/chat/figure-markdown.d.ts +42 -0
- package/dist/types/chat/svg-figure.d.ts +33 -0
- package/dist/types/chat/svg-source.d.ts +49 -0
- package/dist/types/chat/table-chart.d.ts +88 -0
- package/dist/types/chat/user-message.d.ts +2 -2
- package/dist/types/chrome/status-notice.d.ts +15 -3
- package/dist/types/components/editor.d.ts +5 -1
- package/dist/types/components/image.d.ts +23 -0
- package/dist/types/components/input.d.ts +1 -0
- package/dist/types/components/loader.d.ts +5 -6
- package/dist/types/components/markdown.d.ts +9 -0
- package/dist/types/fuzzy.d.ts +14 -0
- package/dist/types/key-hint-format.d.ts +8 -0
- package/dist/types/keybindings.d.ts +1 -1
- package/dist/types/native/backend.d.ts +2 -2
- package/dist/types/native/node.d.ts +6 -0
- package/dist/types/native/overlay.d.ts +3 -3
- package/dist/types/overlays/agent-hub.d.ts +1 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +1 -0
- package/dist/types/overlays/agents-hub.d.ts +9 -0
- package/dist/types/overlays/annotation-overlay.d.ts +5 -2
- package/dist/types/overlays/btw-history-panel.d.ts +5 -1
- package/dist/types/overlays/copy-selector.d.ts +1 -0
- package/dist/types/overlays/model-browser.d.ts +25 -3
- package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
- package/dist/types/overlays/rewind-selector.d.ts +1 -0
- package/dist/types/overlays/usage-dashboard.d.ts +4 -4
- package/dist/types/prompt/custom-editor.d.ts +10 -4
- package/dist/types/prompt/interactive-context-helpers.d.ts +3 -0
- package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
- package/dist/types/render/composite.d.ts +2 -0
- package/dist/types/space-hold.d.ts +40 -25
- package/dist/types/terminal-capabilities.d.ts +13 -0
- package/dist/types/tools/glob.d.ts +4 -0
- package/dist/types/tui.d.ts +5 -0
- package/dist/types/utils.d.ts +6 -0
- package/package.json +9 -9
- package/src/app-keybindings.ts +6 -1
- package/src/apps/git/diff-pane.ts +42 -16
- package/src/apps/git/git-tui.ts +9 -9
- package/src/apps/git/sidebar.ts +6 -1
- package/src/autocomplete.ts +8 -10
- package/src/charts/chart-plan.ts +367 -0
- package/src/charts/chart-svg.ts +693 -0
- package/src/charts/table-data.ts +436 -0
- package/src/chat/assistant-message.ts +165 -25
- package/src/chat/chat-transcript-builder.ts +8 -0
- package/src/chat/figure-markdown.ts +174 -0
- package/src/chat/svg-figure.ts +364 -0
- package/src/chat/svg-source.ts +188 -0
- package/src/chat/table-chart.ts +301 -0
- package/src/chat/tool-execution.ts +6 -1
- package/src/chat/user-message.ts +16 -5
- package/src/chrome/status-notice.ts +25 -7
- package/src/chrome/transcript-container.ts +13 -4
- package/src/components/editor.ts +66 -30
- package/src/components/image.ts +95 -2
- package/src/components/input.ts +14 -6
- package/src/components/loader.ts +10 -17
- package/src/components/markdown.ts +6 -3
- package/src/fuzzy.ts +59 -19
- package/src/hotkeys-markdown.ts +9 -2
- package/src/key-hint-format.ts +25 -10
- package/src/keybindings.ts +1 -1
- package/src/native/backend.ts +10 -3
- package/src/native/node.ts +6 -0
- package/src/native/overlay.ts +4 -4
- package/src/overlays/agent-hub.ts +4 -0
- package/src/overlays/agent-transcript-viewer.ts +4 -0
- package/src/overlays/agents-hub.ts +14 -1
- package/src/overlays/annotation-overlay.ts +179 -80
- package/src/overlays/ask-dialog.ts +1 -1
- package/src/overlays/btw-history-panel.ts +9 -2
- package/src/overlays/copy-selector.ts +2 -0
- package/src/overlays/extensions/extension-dashboard.ts +4 -4
- package/src/overlays/model-browser.ts +169 -76
- package/src/overlays/pause-screen.ts +2 -2
- package/src/overlays/plan-review-overlay.ts +41 -4
- package/src/overlays/rewind-selector.ts +2 -0
- package/src/overlays/usage-dashboard.ts +17 -8
- package/src/overlays/usage-row.ts +22 -8
- package/src/prompt/composer.ts +2 -14
- package/src/prompt/custom-editor.ts +188 -67
- package/src/prompt/interactive-context-helpers.ts +5 -0
- package/src/prompt/model-mention-autocomplete.ts +14 -14
- package/src/prompt/queued-messages.ts +6 -4
- package/src/render/composite.ts +62 -0
- package/src/space-hold.ts +156 -52
- package/src/status-line/segments.ts +3 -1
- package/src/terminal-capabilities.ts +42 -23
- package/src/tools/glob.ts +23 -6
- package/src/tui.ts +154 -119
- package/src/utils.ts +19 -0
|
@@ -1,45 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { type KeyId } from "./keys.js";
|
|
2
|
+
/** Printable text a push-to-talk candidate key types (plain or shifted single characters and Space),
|
|
3
|
+
* or undefined for non-printable keys and chords, which the gesture reserves instead of typing. */
|
|
4
|
+
export declare function getSpaceHoldText(data: string, canonical: string | undefined): string | undefined;
|
|
5
|
+
/** Max gap (ms) between two configured-key presses for a later one to count as OS key auto-repeat
|
|
6
|
+
* rather than a deliberate press. OS auto-repeat is fast; a deliberate tap (even a fast one) is slower. */
|
|
3
7
|
export declare const SPACE_REPEAT_MAX_GAP_MS = 120;
|
|
4
|
-
/** Two consecutive inter-
|
|
8
|
+
/** Two consecutive inter-key gaps are "mechanical" (machine-driven auto-repeat) when both are
|
|
5
9
|
* within {@link SPACE_REPEAT_MAX_GAP_MS} and differ by no more than this — an absolute jitter floor
|
|
6
10
|
* or, for slower repeat rates, {@link SPACE_REPEAT_JITTER_RATIO} of the smaller gap. OS key-repeat
|
|
7
|
-
* is metronomic;
|
|
8
|
-
* steady. */
|
|
11
|
+
* is metronomic; human typing is irregular, so its deltas do not stay this steady. */
|
|
9
12
|
export declare const SPACE_REPEAT_JITTER_MS = 18;
|
|
10
13
|
export declare const SPACE_REPEAT_JITTER_RATIO = 0.35;
|
|
11
|
-
/** Consecutive mechanical (fast + steady) deltas that confirm
|
|
12
|
-
* recording. Needs a sustained metronomic cadence, so jittery
|
|
14
|
+
/** Consecutive mechanical (fast + steady) deltas that confirm a configured key is held and start
|
|
15
|
+
* recording. Needs a sustained metronomic cadence, so jittery typing and deliberate taps never
|
|
13
16
|
* reach it. */
|
|
14
17
|
export declare const SPACE_HOLD_MECHANICAL_RUN = 2;
|
|
15
|
-
/** Idle gap (ms) after the last repeated
|
|
16
|
-
*
|
|
18
|
+
/** Idle gap (ms) after the last repeated configured-key press that counts as release and ends
|
|
19
|
+
* push-to-talk. Must comfortably exceed the OS key-repeat interval. */
|
|
17
20
|
export declare const SPACE_HOLD_RELEASE_MS = 250;
|
|
18
|
-
/**
|
|
21
|
+
/** A release timer firing more than this (ms) past its deadline means the event loop stalled (a
|
|
22
|
+
* synchronous microphone open, GC, a heavy render) while the bar may still be held: the repeats
|
|
23
|
+
* emitted during the stall are queued but unread, so the stall must not read as a release. */
|
|
24
|
+
export declare const SPACE_HOLD_STALL_SLACK_MS = 100;
|
|
25
|
+
/** What a recognized push-to-talk hold drives — the start and stop callbacks. */
|
|
19
26
|
export interface SpaceHoldHandler {
|
|
20
|
-
/** Gate for the gesture. Returns false to
|
|
21
|
-
* disabling push-to-talk restores plain space behavior. */
|
|
27
|
+
/** Gate for the gesture. Returns false to let matching keys behave normally. */
|
|
22
28
|
enabled(): boolean;
|
|
23
|
-
/** A sustained hold was recognized
|
|
29
|
+
/** A sustained hold was recognized; any safely typed candidate text has already been deleted. */
|
|
24
30
|
onStart(): void;
|
|
25
|
-
/** The held
|
|
31
|
+
/** The held key was released (an idle gap with no further repeats, or any other key). */
|
|
26
32
|
onEnd(): void;
|
|
27
33
|
}
|
|
28
34
|
/** What the host must do with a keypress after {@link SpaceHoldGesture.process} has seen it:
|
|
29
|
-
* `"pass"` — handle it normally; `"type"` — insert
|
|
30
|
-
* turns into a hold); `"swallow"` — drop it, the gesture consumed it. */
|
|
35
|
+
* `"pass"` — handle it normally; `"type"` — insert its safely decoded printable text now (it is
|
|
36
|
+
* tracked back out if the run turns into a hold); `"swallow"` — drop it, the gesture consumed it. */
|
|
31
37
|
export type SpaceHoldStep = "pass" | "type" | "swallow";
|
|
32
|
-
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* taps are too slow, so neither escalates and both keep typing real spaces; the few spaces typed
|
|
37
|
-
* before a real hold is recognized are tracked back out of the host input. */
|
|
38
|
+
/** Push-to-talk key-hold state machine. A held key emits OS auto-repeat at a steady fast interval.
|
|
39
|
+
* We watch inter-key deltas and only recognize a hold once {@link SPACE_HOLD_MECHANICAL_RUN}
|
|
40
|
+
* consecutive deltas are mechanical (see {@link gapsAreMechanical}). Deliberate taps and jittery
|
|
41
|
+
* runs stay typed; only text typed during the current steady run is removed on recognition. */
|
|
38
42
|
export declare class SpaceHoldGesture {
|
|
39
43
|
#private;
|
|
40
|
-
/**
|
|
44
|
+
/** Configured alternative keys. Alternatives are tracked independently and never combine into one run. */
|
|
45
|
+
get keys(): readonly KeyId[];
|
|
46
|
+
set keys(keys: readonly KeyId[]);
|
|
47
|
+
/** Unset (the default) keeps configured keys behaving normally. */
|
|
41
48
|
handler: SpaceHoldHandler | undefined;
|
|
42
49
|
constructor(deleteTyped: (count: number) => void, allowed?: () => boolean);
|
|
43
|
-
/**
|
|
44
|
-
|
|
50
|
+
/** Pure focused-owner preflight on raw input: whether the key must reach the host before
|
|
51
|
+
* TUI-wide input listeners. Only non-printable bindings are claimed — they would otherwise
|
|
52
|
+
* trigger app shortcuts mid-hold; printable candidates keep the normal listener path so
|
|
53
|
+
* raw-input observers (e.g. extensions) still see typed text, as they did before remapping. */
|
|
54
|
+
shouldRoute(data: string): boolean;
|
|
55
|
+
/** Feed one canonical keypress and, when safely printable, its decoded text length. The host
|
|
56
|
+
* records the actual inserted length with {@link recordTyped} after applying normal typing. */
|
|
57
|
+
process(key: string | undefined, insertedCharacterLength?: number): SpaceHoldStep;
|
|
58
|
+
/** Account for text the host actually inserted for the preceding `"type"` step. */
|
|
59
|
+
recordTyped(insertedCharacterLength: number): void;
|
|
45
60
|
}
|
|
@@ -284,6 +284,13 @@ export interface ImageRenderOptions {
|
|
|
284
284
|
placementId?: number;
|
|
285
285
|
/** When true (Kitty + {@link imageId}), also return the one-time transmit sequence. */
|
|
286
286
|
includeTransmit?: boolean;
|
|
287
|
+
/**
|
|
288
|
+
* SIXEL sequence for the target pixel size renderImage computes, so the
|
|
289
|
+
* caller can encode off the JS thread: answer `undefined` while the encode
|
|
290
|
+
* is pending (the image's rows stay reserved) and `null` once it failed.
|
|
291
|
+
* Without a provider, renderImage encodes synchronously.
|
|
292
|
+
*/
|
|
293
|
+
sixel?: (widthPx: number, heightPx: number) => string | null | undefined;
|
|
287
294
|
}
|
|
288
295
|
export declare function getCellDimensions(): CellDimensions;
|
|
289
296
|
export declare function setCellDimensions(dims: CellDimensions): void;
|
|
@@ -381,6 +388,12 @@ export declare function getJpegDimensions(base64Data: string): ImageDimensions |
|
|
|
381
388
|
export declare function getGifDimensions(base64Data: string): ImageDimensions | null;
|
|
382
389
|
export declare function getWebpDimensions(base64Data: string): ImageDimensions | null;
|
|
383
390
|
export declare function getImageDimensions(base64Data: string, mimeType: string): ImageDimensions | null;
|
|
391
|
+
/**
|
|
392
|
+
* SIXEL sequence for `base64Data` at the given pixel size, encoded on the JS
|
|
393
|
+
* thread; `null` when the encode fails. For output that cannot wait for an
|
|
394
|
+
* off-thread encode.
|
|
395
|
+
*/
|
|
396
|
+
export declare function encodeSixelNow(base64Data: string, widthPx: number, heightPx: number): string | null;
|
|
384
397
|
export declare function renderImage(base64Data: string, imageDimensions: ImageDimensions, options?: ImageRenderOptions): {
|
|
385
398
|
sequence?: string;
|
|
386
399
|
lines?: string[];
|
|
@@ -12,6 +12,10 @@ export interface GlobToolDetails {
|
|
|
12
12
|
fileCount?: number;
|
|
13
13
|
files?: string[];
|
|
14
14
|
truncated?: boolean;
|
|
15
|
+
/** The scan hit the tool deadline, so the listed files are an incomplete
|
|
16
|
+
* set rather than a complete answer. Distinct from `truncated`, which also
|
|
17
|
+
* covers result-limit and output truncation. */
|
|
18
|
+
timedOut?: boolean;
|
|
15
19
|
error?: string;
|
|
16
20
|
/** Working directory at search time. Used by the renderer to resolve relative
|
|
17
21
|
* file paths to absolute paths for OSC 8 hyperlinks. */
|
package/dist/types/tui.d.ts
CHANGED
|
@@ -150,6 +150,11 @@ export interface Component {
|
|
|
150
150
|
describeScreen?(cx: DescribeContext): NativeScreen;
|
|
151
151
|
/** User actions on nodes this component described (toggle, select, activate, custom). */
|
|
152
152
|
handleNativeEvent?(event: NativeUiEvent): void;
|
|
153
|
+
/**
|
|
154
|
+
* Pure preflight for focused input that must precede TUI-wide input listeners
|
|
155
|
+
* and debug shortcuts. A true result routes the event to this component first.
|
|
156
|
+
*/
|
|
157
|
+
capturesInput?(data: string): boolean;
|
|
153
158
|
/**
|
|
154
159
|
* Optional handler for keyboard input when component has focus
|
|
155
160
|
*/
|
package/dist/types/utils.d.ts
CHANGED
|
@@ -26,6 +26,12 @@ export interface TextSizingOptions {
|
|
|
26
26
|
*/
|
|
27
27
|
export declare function encodeTextSized(text: string, options?: TextSizingOptions): string;
|
|
28
28
|
export declare function sliceWithWidth(line: string, startCol: number, length: number, strict?: boolean | null): SliceResult;
|
|
29
|
+
/**
|
|
30
|
+
* Hard-wrap one literal terminal row without trimming spaces or splitting graphemes.
|
|
31
|
+
* An oversized grapheme is emitted intact even when it exceeds the requested width.
|
|
32
|
+
* Zero-cell rows remain one empty row, matching restored-terminal reflow.
|
|
33
|
+
*/
|
|
34
|
+
export declare function wrapLiteralLine(line: string, width: number): string[];
|
|
29
35
|
export declare function truncateToWidth(text: string, maxWidth: number, ellipsisKind?: Ellipsis | null | "", pad?: boolean | null): string;
|
|
30
36
|
export declare function wrapTextWithAnsi(text: string, width: number): string[];
|
|
31
37
|
export declare function extractSegments(line: string, beforeEnd: number, afterStart: number, afterLen: number, strictAfter: boolean): ExtractSegmentsResult;
|
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.
|
|
4
|
+
"version": "18.7.0",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": {
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
"gen:glyphs": "bun scripts/gen-glyphs.ts"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@oh-my-pi/omptype": "18.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "18.
|
|
46
|
-
"@oh-my-pi/pi-ai": "18.
|
|
47
|
-
"@oh-my-pi/pi-catalog": "18.
|
|
48
|
-
"@oh-my-pi/pi-natives": "18.
|
|
49
|
-
"@oh-my-pi/pi-utils": "18.
|
|
50
|
-
"@oh-my-pi/pi-wire": "18.
|
|
51
|
-
"@oh-my-pi/snapcompact": "18.
|
|
44
|
+
"@oh-my-pi/omptype": "18.7.0",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "18.7.0",
|
|
46
|
+
"@oh-my-pi/pi-ai": "18.7.0",
|
|
47
|
+
"@oh-my-pi/pi-catalog": "18.7.0",
|
|
48
|
+
"@oh-my-pi/pi-natives": "18.7.0",
|
|
49
|
+
"@oh-my-pi/pi-utils": "18.7.0",
|
|
50
|
+
"@oh-my-pi/pi-wire": "18.7.0",
|
|
51
|
+
"@oh-my-pi/snapcompact": "18.7.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"kitty-vt-wasm": "^0.2.0"
|
package/src/app-keybindings.ts
CHANGED
|
@@ -60,6 +60,7 @@ interface AppKeybindings {
|
|
|
60
60
|
"app.tree.unfoldOrDown": true;
|
|
61
61
|
"app.plan.toggle": true;
|
|
62
62
|
"app.history.search": true;
|
|
63
|
+
"app.stt.pushToTalk": true;
|
|
63
64
|
"app.stt.toggle": true;
|
|
64
65
|
"app.live.toggle": true;
|
|
65
66
|
}
|
|
@@ -237,9 +238,13 @@ export const KEYBINDINGS = {
|
|
|
237
238
|
defaultKeys: "ctrl+r",
|
|
238
239
|
description: "Search history",
|
|
239
240
|
},
|
|
241
|
+
"app.stt.pushToTalk": {
|
|
242
|
+
defaultKeys: "space",
|
|
243
|
+
description: "Hold to record speech-to-text",
|
|
244
|
+
},
|
|
240
245
|
"app.stt.toggle": {
|
|
241
246
|
defaultKeys: [],
|
|
242
|
-
description: "Toggle speech-to-text
|
|
247
|
+
description: "Toggle speech-to-text recording",
|
|
243
248
|
},
|
|
244
249
|
"app.live.toggle": {
|
|
245
250
|
defaultKeys: "ctrl+l",
|
|
@@ -601,6 +601,12 @@ export class DiffPane {
|
|
|
601
601
|
#layoutCache: { key: string; visuals: Visual[] } | undefined;
|
|
602
602
|
/** Per visible row: clickable hunk-button ranges recorded during render. */
|
|
603
603
|
#hits: ({ hunk: number; primary?: [number, number]; discard?: [number, number] } | undefined)[] = [];
|
|
604
|
+
/**
|
|
605
|
+
* Asset images from the last render, by placement and size. Reusing them
|
|
606
|
+
* lets a SIXEL image show the sequence its off-thread encode delivers on a
|
|
607
|
+
* later render; a fresh `Image` per render would restart the encode forever.
|
|
608
|
+
*/
|
|
609
|
+
#assetImages = new Map<string, Image>();
|
|
604
610
|
constructor(imageBudget?: ImageBudget) {
|
|
605
611
|
this.#imageBudget = imageBudget;
|
|
606
612
|
}
|
|
@@ -1103,8 +1109,16 @@ export class DiffPane {
|
|
|
1103
1109
|
const leftWidth = Math.max(1, Math.floor((width - 1) / 2));
|
|
1104
1110
|
const rightWidth = Math.max(1, width - leftWidth - 1);
|
|
1105
1111
|
const bodyHeight = Math.max(0, height - 1);
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1112
|
+
const previousImages = this.#assetImages;
|
|
1113
|
+
this.#assetImages = new Map();
|
|
1114
|
+
const oldLines = this.#renderAssetSide(asset.old, leftWidth, bodyHeight, `old:${asset.filePath}`, previousImages);
|
|
1115
|
+
const newLines = this.#renderAssetSide(
|
|
1116
|
+
asset.new,
|
|
1117
|
+
rightWidth,
|
|
1118
|
+
bodyHeight,
|
|
1119
|
+
`new:${asset.filePath}`,
|
|
1120
|
+
previousImages,
|
|
1121
|
+
);
|
|
1108
1122
|
const border = theme.fg("borderMuted", "│");
|
|
1109
1123
|
const lines: string[] = [];
|
|
1110
1124
|
for (let index = 0; index < height; index++) {
|
|
@@ -1125,24 +1139,36 @@ export class DiffPane {
|
|
|
1125
1139
|
return lines;
|
|
1126
1140
|
}
|
|
1127
1141
|
|
|
1128
|
-
#renderAssetSide(
|
|
1142
|
+
#renderAssetSide(
|
|
1143
|
+
side: FileAssetSide,
|
|
1144
|
+
width: number,
|
|
1145
|
+
height: number,
|
|
1146
|
+
placementKey: string,
|
|
1147
|
+
previousImages: ReadonlyMap<string, Image>,
|
|
1148
|
+
): readonly string[] {
|
|
1129
1149
|
if (height <= 0) return [];
|
|
1130
1150
|
let content: readonly string[];
|
|
1131
1151
|
if (side.kind === "image") {
|
|
1132
1152
|
const image = side.image;
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1153
|
+
const imageKey = `git-review:${placementKey}:${image.key}`;
|
|
1154
|
+
const cacheKey = `${imageKey}:${width}x${height}`;
|
|
1155
|
+
const component =
|
|
1156
|
+
previousImages.get(cacheKey) ??
|
|
1157
|
+
new Image(
|
|
1158
|
+
image.data,
|
|
1159
|
+
image.mimeType,
|
|
1160
|
+
{ fallbackColor: text => theme.fg("dim", text) },
|
|
1161
|
+
{
|
|
1162
|
+
maxWidthCells: Math.max(1, width - 2),
|
|
1163
|
+
maxHeightCells: height,
|
|
1164
|
+
filename: this.#asset?.filePath,
|
|
1165
|
+
budget: this.#imageBudget,
|
|
1166
|
+
imageKey,
|
|
1167
|
+
},
|
|
1168
|
+
{ widthPx: image.widthPx, heightPx: image.heightPx },
|
|
1169
|
+
);
|
|
1170
|
+
this.#assetImages.set(cacheKey, component);
|
|
1171
|
+
content = component.render(width);
|
|
1146
1172
|
} else {
|
|
1147
1173
|
let details: string[];
|
|
1148
1174
|
switch (side.kind) {
|
package/src/apps/git/git-tui.ts
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
import type { TspSpan } from "@oh-my-pi/pi-wire";
|
|
29
|
-
import { formatKeyHint, formatKeyHints } from "../../app-keybindings";
|
|
29
|
+
import { formatKeyHint, formatKeyHints, formatTooltipKey } from "../../app-keybindings";
|
|
30
30
|
import { SplitPane } from "../../components/layout/split-pane";
|
|
31
31
|
import { Stack } from "../../components/layout/stack";
|
|
32
32
|
import { matchesKey } from "../../keys";
|
|
@@ -1030,8 +1030,8 @@ class GitTuiComponent implements Component {
|
|
|
1030
1030
|
const right: NativeNode[] = [
|
|
1031
1031
|
row(
|
|
1032
1032
|
[
|
|
1033
|
-
iconButton("chev-up", "prev-hunk", `Previous change (${
|
|
1034
|
-
iconButton("chev", "next-hunk", `Next change (${
|
|
1033
|
+
iconButton("chev-up", "prev-hunk", `Previous change (${formatTooltipKey("alt+up")})`),
|
|
1034
|
+
iconButton("chev", "next-hunk", `Next change (${formatTooltipKey("alt+down")})`),
|
|
1035
1035
|
],
|
|
1036
1036
|
{ gap: "none", align: "center" },
|
|
1037
1037
|
),
|
|
@@ -1052,13 +1052,13 @@ class GitTuiComponent implements Component {
|
|
|
1052
1052
|
"type",
|
|
1053
1053
|
"whitespace",
|
|
1054
1054
|
this.#whitespace === "off"
|
|
1055
|
-
? `Ignore whitespace (${
|
|
1055
|
+
? `Ignore whitespace (${formatTooltipKey("b")})`
|
|
1056
1056
|
: this.#whitespace === "whitespace"
|
|
1057
|
-
? `Ignoring whitespace; also ignore formatting (${
|
|
1058
|
-
: `Ignoring formatting and imports; show all (${
|
|
1057
|
+
? `Ignoring whitespace; also ignore formatting (${formatTooltipKey("b")})`
|
|
1058
|
+
: `Ignoring formatting and imports; show all (${formatTooltipKey("b")})`,
|
|
1059
1059
|
this.#whitespace !== "off",
|
|
1060
1060
|
),
|
|
1061
|
-
iconButton("corner-down-right", "wrap", `Wrap lines (${
|
|
1061
|
+
iconButton("corner-down-right", "wrap", `Wrap lines (${formatTooltipKey("w")})`, this.#pane.wrap),
|
|
1062
1062
|
);
|
|
1063
1063
|
}
|
|
1064
1064
|
if (file?.area === "unstaged" || file?.area === "staged") {
|
|
@@ -1070,8 +1070,8 @@ class GitTuiComponent implements Component {
|
|
|
1070
1070
|
);
|
|
1071
1071
|
}
|
|
1072
1072
|
right.push(
|
|
1073
|
-
iconButton("keyboard", "help", `Keyboard shortcuts (${
|
|
1074
|
-
iconButton("x", "close", `Close (${
|
|
1073
|
+
iconButton("keyboard", "help", `Keyboard shortcuts (${formatTooltipKey("?")})`),
|
|
1074
|
+
iconButton("x", "close", `Close (${formatTooltipKey("q")})`),
|
|
1075
1075
|
);
|
|
1076
1076
|
return keyed(
|
|
1077
1077
|
row(
|
package/src/apps/git/sidebar.ts
CHANGED
|
@@ -1699,7 +1699,12 @@ export class Sidebar {
|
|
|
1699
1699
|
png,
|
|
1700
1700
|
"image/png",
|
|
1701
1701
|
{ fallbackColor: text => theme.fg("dim", text) },
|
|
1702
|
-
{
|
|
1702
|
+
{
|
|
1703
|
+
maxHeightCells: 3,
|
|
1704
|
+
budget: this.#imageBudget,
|
|
1705
|
+
imageKey: `git-avatar:${email}`,
|
|
1706
|
+
requestRender: this.#requestRender,
|
|
1707
|
+
},
|
|
1703
1708
|
),
|
|
1704
1709
|
};
|
|
1705
1710
|
}
|
package/src/autocomplete.ts
CHANGED
|
@@ -263,8 +263,11 @@ export interface AutocompleteProvider {
|
|
|
263
263
|
|
|
264
264
|
/** Get inline hint text to show as dim ghost text after the cursor */
|
|
265
265
|
getInlineHint?(lines: string[], cursorLine: number, cursorCol: number): string | null;
|
|
266
|
-
/**
|
|
267
|
-
|
|
266
|
+
/**
|
|
267
|
+
* Synchronously list slash command-name completions for a leading slash token (no async I/O).
|
|
268
|
+
* Mirrors the command-name branch of {@link getSuggestions}, including the bare `/` listing and
|
|
269
|
+
* the collapsed `/skill:` namespace row. Returns null outside a command-name token or with no match.
|
|
270
|
+
*/
|
|
268
271
|
trySyncSlashCompletion?(textBeforeCursor: string): { items: AutocompleteItem[]; prefix: string } | null;
|
|
269
272
|
/**
|
|
270
273
|
* Synchronously try to expand text immediately before the cursor (no async I/O).
|
|
@@ -806,7 +809,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
806
809
|
};
|
|
807
810
|
}
|
|
808
811
|
|
|
809
|
-
// Slash command suggestions can be accepted before
|
|
812
|
+
// Slash command suggestions can be accepted before an async refresh
|
|
810
813
|
// catches up to newly typed characters. Replace the live command token,
|
|
811
814
|
// not only the prefix captured when the suggestion list was rendered.
|
|
812
815
|
// Absolute-path completions share the leading-slash prefix shape but
|
|
@@ -1300,19 +1303,14 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
1300
1303
|
const slashStart = findLeadingSlashCommandStart(textBeforeCursor);
|
|
1301
1304
|
if (slashStart === null) return null;
|
|
1302
1305
|
const commandText = textBeforeCursor.slice(slashStart);
|
|
1303
|
-
if (commandText.length <= 1) return null; // Bare "/" alone, don't auto-complete
|
|
1304
1306
|
if (commandText.includes(" ")) return null; // Only complete command name, not args
|
|
1305
1307
|
|
|
1306
|
-
const
|
|
1307
|
-
const lowerPrefix = prefix.toLowerCase();
|
|
1308
|
-
|
|
1309
|
-
// The `/skill:` namespace row is excluded here: the sync path submits
|
|
1310
|
-
// immediately after applying, and the bare namespace is not a command.
|
|
1308
|
+
const lowerPrefix = commandText.slice(1).toLowerCase();
|
|
1311
1309
|
const matches = buildSlashCommandCompletions(
|
|
1312
1310
|
collapseSkillNamespace(this.#commands, lowerPrefix),
|
|
1313
1311
|
lowerPrefix,
|
|
1314
1312
|
this.#commandUsage,
|
|
1315
|
-
)
|
|
1313
|
+
);
|
|
1316
1314
|
|
|
1317
1315
|
if (matches.length === 0) return null;
|
|
1318
1316
|
// Mirror `getSuggestions`: preserve leading whitespace so the editor's
|