@oh-my-pi/pi-tui 18.12.6 → 18.13.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 +35 -34
- package/dist/types/app-keybindings.d.ts +5 -0
- package/dist/types/components/image.d.ts +18 -0
- package/dist/types/components/loader.d.ts +1 -1
- package/dist/types/native/blobs.d.ts +9 -0
- package/dist/types/overlays/agent-hub-projection.d.ts +3 -1
- package/dist/types/overlays/btw-history-panel.d.ts +16 -2
- package/dist/types/overlays/btw-history.d.ts +7 -0
- package/dist/types/overlays/btw-panel.d.ts +2 -0
- package/dist/types/overlays/mcp-add-wizard.d.ts +2 -0
- package/dist/types/prompt/macos-spelling.d.ts +2 -2
- package/dist/types/render/hyperlink.d.ts +15 -2
- package/dist/types/render/output-pane.d.ts +1 -1
- package/dist/types/render/render-utils.d.ts +7 -1
- package/dist/types/terminal-capabilities.d.ts +7 -0
- package/dist/types/tools/edit.d.ts +3 -1
- package/dist/types/tools/grouped-file-output.d.ts +2 -0
- package/dist/types/tools/mcp.d.ts +3 -3
- package/dist/types/tools/wait.d.ts +10 -2
- package/dist/types/vim.d.ts +39 -0
- package/package.json +9 -9
- package/src/app-keybindings.ts +5 -0
- package/src/chat/bash-execution.ts +7 -1
- package/src/chat/read-tool-group.ts +1 -2
- package/src/chat/skill-message.ts +2 -6
- package/src/chrome/transcript-container.ts +61 -32
- package/src/components/editor.ts +432 -13
- package/src/components/image.ts +52 -16
- package/src/components/loader.ts +1 -1
- package/src/components/markdown.ts +95 -5
- package/src/hotkeys-markdown.ts +1 -0
- package/src/native/blobs.ts +3 -2
- package/src/overlays/agent-hub-projection.ts +6 -5
- package/src/overlays/agent-hub.ts +51 -42
- package/src/overlays/btw-history-panel.ts +124 -12
- package/src/overlays/btw-history.ts +11 -0
- package/src/overlays/btw-panel.ts +8 -4
- package/src/overlays/mcp-add-wizard.ts +5 -0
- package/src/overlays/usage-dashboard.ts +15 -2
- package/src/prompt/macos-spelling.ts +24 -15
- package/src/render/hyperlink.ts +41 -18
- package/src/render/output-pane.ts +60 -17
- package/src/render/render-utils.ts +11 -2
- package/src/setup/scenes/sign-in.ts +6 -0
- package/src/terminal-capabilities.ts +10 -0
- package/src/tools/ast-grep.ts +3 -2
- package/src/tools/edit.ts +8 -4
- package/src/tools/find.ts +3 -10
- package/src/tools/glob.ts +4 -3
- package/src/tools/grep.ts +3 -4
- package/src/tools/grouped-file-output.ts +8 -2
- package/src/tools/mcp.ts +27 -9
- package/src/tools/native-view.ts +2 -2
- package/src/tools/vibe.ts +14 -3
- package/src/tools/wait.ts +10 -2
- package/src/tui.ts +36 -16
- package/src/vim.ts +313 -22
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.13.0] - 2026-10-26
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- `CoordinationOp` no longer includes `"jobs"`, which no tool has produced since `wait` and `proc://` replaced the `hub` tool ([#16763](https://github.com/can1357/oh-my-pi/pull/16763) by [@will-bogusz](https://github.com/will-bogusz))
|
|
10
|
+
- Fixed a table's last rows (such as its bottom border) going blank in scrollback when its SIXEL chart was drawn while the reply kept streaming, with the chart painted over them ([#16870](https://github.com/can1357/oh-my-pi/issues/16870))
|
|
11
|
+
- Fixed Tern MCP tool cards showing only a clipped one-line summary of the arguments; the call and result cards now include a folded `Args` section with the full arguments ([#16765](https://github.com/can1357/oh-my-pi/issues/16765))
|
|
12
|
+
- Fixed pasting an API key or redirect URL into the setup wizard's provider sign-in prompt silently doing nothing ([#16898](https://github.com/can1357/oh-my-pi/pull/16898) by [@camiloeferrera](https://github.com/camiloeferrera))
|
|
13
|
+
- Fixed the usage dashboard averaging distinct quota buckets that share a label and window (Google Antigravity's Gemini and Gemini 4 Argon weeklies) into one row, which masked a drained bucket as "50% left"; buckets now group by quota identity, and shared Claude/GPT copies still merge ([#16790](https://github.com/can1357/oh-my-pi/issues/16790))
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Added `modelEnds` to `GroupedFileSection`: per file, the model-line index just past its last line, so a caller cutting the output can tell which files it shows whole ([#16594](https://github.com/can1357/oh-my-pi/pull/16594) by [@will-bogusz](https://github.com/will-bogusz))
|
|
18
|
+
- Added an optional `title` to `BtwHistoryRecord` and a `title` option to `BtwPanelComponent`: the `/btw` panel, history list and thread show it as the topic heading in place of the question, so a side command reads as `/sitrep` instead of its expanded template ([#16815](https://github.com/can1357/oh-my-pi/pull/16815) by [@wolfiesch](https://github.com/wolfiesch))
|
|
19
|
+
- Vim mode now supports `.` to repeat the last change, including the text typed in Insert or Replace mode, with Vim's count rules; `u` undoes a whole Insert session or `.` in one step ([#16872](https://github.com/can1357/oh-my-pi/pull/16872) by [@Shadorain](https://github.com/Shadorain))
|
|
20
|
+
- Added the `app.local.open` keybinding (default `Alt+G`) and its keyboard shortcuts row ([#16873](https://github.com/can1357/oh-my-pi/pull/16873) by [@Shadorain](https://github.com/Shadorain))
|
|
21
|
+
- Added `errorKind` to `EditToolDetails`, set on a failed edit ([#16608](https://github.com/can1357/oh-my-pi/pull/16608) by [@will-bogusz](https://github.com/will-bogusz))
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Command and eval output panes no longer re-style and re-join their whole output on every frame, so a long command's output stops costing frame time while it sits in the transcript ([#16779](https://github.com/can1357/oh-my-pi/pull/16779) by [@yuzu-octopus](https://github.com/yuzu-octopus))
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Fixed typo underlines changing when the cursor or selection splits a word ([#16821](https://github.com/can1357/oh-my-pi/issues/16821))
|
|
30
|
+
- Fixed completed `vibe_wait` and `vibe_list` worker cards repeatedly entering scrollback as elapsed time advanced after partial retirement; completed walls now retain a static snapshot while in-flight waits continue updating.
|
|
31
|
+
- Fixed resuming a session with a recorded tool-call path that cannot be turned into a link, which crashed the TUI while it rebuilt the context ([#16806](https://github.com/can1357/oh-my-pi/pull/16806) by [@laviRZ](https://github.com/laviRZ))
|
|
32
|
+
- Fixed the Agent Hub reordering its rows on every heartbeat while the session's saved subagents loaded, so no row could be selected until the load ended: a row's place is now fixed when the hub first shows it, an agent the load finds takes its place in the status/recency ranking, and every other new generation goes on top ([#10524](https://github.com/can1357/oh-my-pi/issues/10524), [#16952](https://github.com/can1357/oh-my-pi/pull/16952) by [@lemonleks](https://github.com/lemonleks))
|
|
33
|
+
- Fixed the BTW history follow-up composer refusing pasted images; it now attaches them as `[Image #N]` markers and sends the ones whose marker stays in the draft ([#16805](https://github.com/can1357/oh-my-pi/pull/16805) by [@lemonleks](https://github.com/lemonleks))
|
|
34
|
+
- Fixed long URLs in fenced code blocks opening only their first visual line on ctrl/cmd-click; each wrapped fragment now carries the full OSC 8 hyperlink when the terminal supports it ([#16740](https://github.com/can1357/oh-my-pi/pull/16740) by [@OriginalByteMe](https://github.com/OriginalByteMe))
|
|
35
|
+
- Fixed the newest inline charts and images at the bottom of the screen showing as text placeholders while older ones stayed rendered once a session (or a resumed one) holds more than `tui.maxInlineImages` images ([#16660](https://github.com/can1357/oh-my-pi/issues/16660))
|
|
36
|
+
- Fixed Markdown tables in the terminal ignoring their column alignment, so right-aligned and centered columns rendered flush left ([#16658](https://github.com/can1357/oh-my-pi/pull/16658) by [@adi-IL](https://github.com/adi-IL))
|
|
37
|
+
- Grep, Glob, Find, and AST Grep result headers wrap a long pattern instead of cutting it off, so the match/file counts and `truncated` warning stay visible ([#16635](https://github.com/can1357/oh-my-pi/issues/16635))
|
|
38
|
+
|
|
5
39
|
## [18.12.6] - 2026-10-21
|
|
6
40
|
|
|
7
41
|
### Changed
|
|
@@ -2450,37 +2484,4 @@
|
|
|
2450
2484
|
|
|
2451
2485
|
- Fixed slash-command autocomplete repainting when a Windows Terminal session cannot report native scrollback position; live input renders can now bypass the unknown-viewport deferral without weakening background scrollback protection. ([#1550](https://github.com/can1357/oh-my-pi/issues/1550))
|
|
2452
2486
|
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
### Added
|
|
2456
|
-
|
|
2457
|
-
- Added autocomplete triggering for internal URL scheme tokens such as `local://` and `skill://` while typing in the editor
|
|
2458
|
-
|
|
2459
|
-
### Fixed
|
|
2460
|
-
|
|
2461
|
-
- Fixed streaming output staying invisible in Windows Terminal + WSL2 until the window was minimized + restored. The 15.5.14 WSL branch of `requiresNativeViewportProofForReplay` treated an unknown native viewport state as "scrolled into history" — but `ProcessTerminal.isNativeViewportAtBottom` can only return a real answer through `kernel32.dll` FFI, which a Linux user-space process inside WSL cannot load, so the probe was permanently `undefined`. Every row-inserting structural mutation (each new streaming token row above the bottom-anchored prompt) was therefore classified as `deferredMutation` and emitted zero bytes. Any geometry change (resize/minimize/restore) bypassed the gate via a different render intent, which is why the output became visible only on window resize. The WSL clause is removed; on platforms where the probe cannot answer, unknown is treated as at-bottom (the pre-15.5.14 behaviour) so the live render path runs again. Native Win32 keeps the conservative "assume scrolled when unknown" heuristic since `kernel32` FFI does succeed there and unknown means the probe transiently failed. ([#1534](https://github.com/can1357/oh-my-pi/issues/1534))
|
|
2462
|
-
|
|
2463
|
-
## [15.5.14] - 2026-05-29
|
|
2464
|
-
|
|
2465
|
-
### Added
|
|
2466
|
-
|
|
2467
|
-
- `Markdown` now renders a small color-chip swatch, painted with the referenced color, in front of CSS hex colors mentioned in prose, thinking traces, lists, tables, and blockquotes (e.g. `#C5FFD6` or `` `#C5FFD6` ``). The chip glyph comes from the theme's symbol set so it degrades across tiers (Nerd Font / Unicode `■` → ASCII `[]`) and is overridable via the `md.colorSwatch` symbol. Truecolor terminals get an exact 24-bit chip; others fall back to the nearest 256-color cell. Bare prose requires a hex letter for 3/4-digit forms so short issue/PR references (`#123`, `#1011`) don't sprout swatches; backticked codes are always treated as colors.
|
|
2468
|
-
|
|
2469
|
-
### Fixed
|
|
2470
|
-
|
|
2471
|
-
- Fixed the terminal hardware cursor disappearing in Ghostty. `resolveHardwareCursorPreference` force-hid the hardware cursor whenever it detected a Ghostty session (to fight bar-cursor afterimage "trails"), but the editor was simultaneously kept in terminal-cursor (marker-only) mode via `getUseTerminalCursorMarker()`, which renders no glyph and relies on the now-hidden hardware cursor — so Ghostty users had no visible caret at all, regardless of `PI_HARDWARE_CURSOR`. The Ghostty/`PI_FORCE_HARDWARE_CURSOR` override and the redundant `useTerminalCursorMarker` state are removed: `showHardwareCursor` is honored as-requested again (hardware cursor on by default), and disabling it cleanly falls back to the steady software-cursor glyph. The per-paint anti-trail mitigations (hide-cursor + autowrap-off inside the synchronized-output block) are retained, which is the actual trail fix.
|
|
2472
|
-
|
|
2473
|
-
## [15.5.12] - 2026-05-29
|
|
2474
|
-
|
|
2475
|
-
### Fixed
|
|
2476
|
-
|
|
2477
|
-
- Fixed terminal resizes corrupting native scrollback with duplicated rows. The 15.4.0 change that defers a destructive scrollback clear+replay (so a user scrolled into history is not yanked while a streaming tail cell mutates) also caught genuine width/height resizes: a resize reflows the terminal's own committed scrollback at the new geometry, but repainting only the viewport left the stale old-size rows in history, so every overflowed row showed up twice (old-size wrap + new-size copy) when scrolling back, until the next prompt submit cleaned it up. `#planRender` now rebuilds history synchronously when the frame's geometry actually changed (`widthChanged || heightChanged`) via the restored `historyRebuild` intent, and defers the rebuild only for pure content mutations where the user may be reading scrollback mid-stream.
|
|
2478
|
-
|
|
2479
|
-
## [15.5.0] - 2026-05-26
|
|
2480
|
-
|
|
2481
|
-
### Fixed
|
|
2482
|
-
|
|
2483
|
-
- Fixed `@` file mention autocomplete stalling for seconds when the query references something outside the project root (e.g. `@../`, `@~/`, `@/abs/`). `CombinedAutocompleteProvider` now short-circuits to plain immediate-directory prefix listing in those cases instead of dispatching a recursive `fuzzyFind` walk over a sibling directory full of unrelated projects. Inside-cwd queries keep the existing fuzzy-then-prefix behavior. ([#1395](https://github.com/can1357/oh-my-pi/issues/1395))
|
|
2484
|
-
- Gated the Hangul Compatibility Jamo width correction (U+3131..U+318E → 1 cell, originally landed in 15.0.1 for the IME / hardware-cursor displacement bug) behind `process.platform === "darwin"` in the TS path and `cfg!(target_os = "macos")` in the `pi-natives` Rust path. macOS terminals (Ghostty / Terminal.app / iTerm2) render jamo as 1 cell despite UAX#11 classifying them as Wide, but WezTerm and most Linux terminals honor UAX#11 and render them as 2 cells. The unconditional correction therefore desynced the TUI's column bookkeeping from the terminal's actual rendering off-darwin, producing corrupted layout and broken Korean input on Linux. On non-darwin the helpers now defer entirely to `Bun.stringWidth` / `UnicodeWidthStr` (also a small perf win on the multi-char-grapheme path). ([#1410](https://github.com/can1357/oh-my-pi/issues/1410))
|
|
2485
|
-
|
|
2486
|
-
Older entries are archived in [packages/tui/CHANGELOG.md@ec051608a87a](https://github.com/can1357/oh-my-pi/blob/ec051608a87ad78d4930a46af7f5582192005ada/packages/tui/CHANGELOG.md).
|
|
2487
|
+
Older entries are archived in [packages/tui/CHANGELOG.md@539f05ae39f8](https://github.com/can1357/oh-my-pi/blob/539f05ae39f81319dc75d650757dad3c76482343/packages/tui/CHANGELOG.md).
|
|
@@ -20,6 +20,7 @@ interface AppKeybindings {
|
|
|
20
20
|
"app.ask.hide": true;
|
|
21
21
|
"app.approval.approve": true;
|
|
22
22
|
"app.editor.external": true;
|
|
23
|
+
"app.local.open": true;
|
|
23
24
|
"app.message.followUp": true;
|
|
24
25
|
"app.retry": true;
|
|
25
26
|
"app.message.dequeue": true;
|
|
@@ -252,6 +253,10 @@ export declare const KEYBINDINGS: {
|
|
|
252
253
|
readonly defaultKeys: "ctrl+g";
|
|
253
254
|
readonly description: "Open external editor";
|
|
254
255
|
};
|
|
256
|
+
readonly "app.local.open": {
|
|
257
|
+
readonly defaultKeys: "alt+g";
|
|
258
|
+
readonly description: "Open a local:// file in external editor";
|
|
259
|
+
};
|
|
255
260
|
readonly "app.message.followUp": {
|
|
256
261
|
readonly defaultKeys: ["ctrl+q", "ctrl+enter"];
|
|
257
262
|
readonly description: "Send follow-up message";
|
|
@@ -31,6 +31,19 @@ export interface ImageOptions {
|
|
|
31
31
|
* synchronously instead of committing its reserved blank rows.
|
|
32
32
|
*/
|
|
33
33
|
export declare function renderForScrollback<T>(render: () => T): T;
|
|
34
|
+
/**
|
|
35
|
+
* Run `render` to measure rows outside display order — a newest-first height
|
|
36
|
+
* walk, say. {@link ImageBudget} derives demotion from the order images are
|
|
37
|
+
* observed in, so a budgeted {@link Image} inside this scope claims no display
|
|
38
|
+
* slot: it replays the decision its slot in the surface's last full pass
|
|
39
|
+
* implies. `images` reports whether any budgeted image was observed; the caller
|
|
40
|
+
* MUST render every such block it lays out again, outside this scope and in
|
|
41
|
+
* display order, or its images keep no slot in the pass.
|
|
42
|
+
*/
|
|
43
|
+
export declare function measureRender<T>(render: () => T): {
|
|
44
|
+
value: T;
|
|
45
|
+
images: boolean;
|
|
46
|
+
};
|
|
34
47
|
/** Widest an {@link Image} rendered at `width` columns draws; callers sizing a raster for it (`SvgFigure`) match it. */
|
|
35
48
|
export declare function imageMaxColumns(width: number): number;
|
|
36
49
|
/** Default count of inline images kept as live graphics before older ones fall back to text. */
|
|
@@ -118,6 +131,11 @@ export declare class ImageBudget {
|
|
|
118
131
|
* During a `stable` pass ({@link beginPass}) the call order and visible subset
|
|
119
132
|
* are not authoritative, so the decision is the surface's committed
|
|
120
133
|
* on-terminal split, keyed by id — order- and partiality-independent.
|
|
134
|
+
*
|
|
135
|
+
* Inside {@link measureRender} the call claims no slot: it answers what the
|
|
136
|
+
* image's slot in the last full pass implies under the planned threshold,
|
|
137
|
+
* which is what the display-order render that follows will decide once the
|
|
138
|
+
* split has converged.
|
|
121
139
|
*/
|
|
122
140
|
observe(imageId: number): boolean;
|
|
123
141
|
/**
|
|
@@ -75,6 +75,6 @@ export declare class Loader extends Text {
|
|
|
75
75
|
* session title). Re-evaluated every paint; dropped when the row leaves
|
|
76
76
|
* less than a two-cell gap. */
|
|
77
77
|
setTrailer(trailer: (() => string | undefined) | undefined): void;
|
|
78
|
-
setMessage(message: string): void;
|
|
78
|
+
setMessage(message: string | (() => string)): void;
|
|
79
79
|
}
|
|
80
80
|
export {};
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-addressed binary blobs (images) for `image` nodes.
|
|
3
|
+
*
|
|
4
|
+
* `describe()` registers the bytes and puts the returned id in `image.p.blob`;
|
|
5
|
+
* the native backend delivers each referenced blob once per terminal
|
|
6
|
+
* connection (asking the terminal first which it holds, or through its blob
|
|
7
|
+
* cache), else uploads it with verb `b` before the frame that first
|
|
8
|
+
* references it.
|
|
9
|
+
*/
|
|
1
10
|
import type { TspProps } from "@oh-my-pi/pi-wire";
|
|
2
11
|
import type { NativeChild, NativeNode } from "./node.js";
|
|
3
12
|
export interface NativeBlob {
|
|
@@ -44,5 +44,7 @@ export declare function aggregateMetrics<TRecord extends AgentRecordLike>(args:
|
|
|
44
44
|
hasFallbackLiveSessions: boolean;
|
|
45
45
|
};
|
|
46
46
|
/** Parent-before-child projection preserving initial subtree rank and prepending new siblings. */
|
|
47
|
-
export declare function projectAgentTree<TRecord extends AgentRecordLike>(refs: readonly TRecord[],
|
|
47
|
+
export declare function projectAgentTree<TRecord extends AgentRecordLike>(refs: readonly TRecord[],
|
|
48
|
+
/** Rows spawned while the hub is open; such a child never lifts its parent's group. */
|
|
49
|
+
spawnedWhileOpen?: (ref: TRecord) => boolean): AgentTreeProjection<TRecord>;
|
|
48
50
|
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
1
2
|
import { type Component, type Focusable, Input } from "../index.js";
|
|
2
3
|
import { type BtwHistoryRecord } from "./btw-history.js";
|
|
3
4
|
import type { SpaceHoldHandler } from "../space-hold.js";
|
|
@@ -9,7 +10,11 @@ interface BtwHistoryPanelOptions {
|
|
|
9
10
|
onCopy: (record: BtwHistoryRecord) => void;
|
|
10
11
|
onCancel: (record: BtwHistoryRecord) => void;
|
|
11
12
|
canFollowUp?: (record: BtwHistoryRecord) => boolean;
|
|
12
|
-
onFollowUp?: (record: BtwHistoryRecord, question: string, signal: AbortSignal) => Promise<boolean>;
|
|
13
|
+
onFollowUp?: (record: BtwHistoryRecord, question: string, signal: AbortSignal, images?: ImageContent[]) => Promise<boolean>;
|
|
14
|
+
/** Read a clipboard image into the follow-up (an empty paste, as terminals send for one). */
|
|
15
|
+
onPasteImage?: () => Promise<unknown>;
|
|
16
|
+
/** Attach an image file whose path was pasted or dropped into the follow-up. */
|
|
17
|
+
onPasteImagePath?: (path: string) => Promise<void>;
|
|
13
18
|
/** Configured push-to-talk keys for a follow-up composer. */
|
|
14
19
|
spaceHoldKeys?: readonly KeyId[];
|
|
15
20
|
spaceHold?: (input: Input) => SpaceHoldHandler;
|
|
@@ -37,7 +42,16 @@ export declare class BtwHistoryPanel implements Component, Focusable {
|
|
|
37
42
|
showRecord(recordId: string): void;
|
|
38
43
|
/** Only an active follow-up composer can claim its configured push-to-talk keys. */
|
|
39
44
|
capturesInput(data: string): boolean;
|
|
40
|
-
/**
|
|
45
|
+
/** False while a follow-up is sent: it already took its images, and the composer clears after. */
|
|
46
|
+
get acceptsImages(): boolean;
|
|
47
|
+
/** Attach an image to the follow-up; returns its `[Image #N, WxH]` marker to paste. */
|
|
48
|
+
attachImage(image: ImageContent, dims?: {
|
|
49
|
+
width: number;
|
|
50
|
+
height: number;
|
|
51
|
+
}): string | undefined;
|
|
52
|
+
/** Reserve ordered delivery for an image paste. Completion delivers its marker once, or releases on undefined. */
|
|
53
|
+
beginPaste(): (text: string | undefined) => boolean;
|
|
54
|
+
/** Enhanced clipboard pastes belong only to the active composer, behind any image still loading. */
|
|
41
55
|
pasteText(text: string): void;
|
|
42
56
|
handleInput(data: string): void;
|
|
43
57
|
/** A large glass sheet (the list beside the reader), palette-sized while there is nothing to list. */
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
1
2
|
export interface BtwHistoryTurn {
|
|
2
3
|
question: string;
|
|
4
|
+
/** Images attached to the question, sent again with it on later follow-ups. */
|
|
5
|
+
images?: readonly ImageContent[];
|
|
3
6
|
answer: string;
|
|
4
7
|
status: "running" | "complete" | "cancelled" | "error" | "interrupted";
|
|
5
8
|
createdAt: number;
|
|
@@ -9,8 +12,12 @@ export interface BtwHistoryTurn {
|
|
|
9
12
|
export interface BtwHistoryRecord extends BtwHistoryTurn {
|
|
10
13
|
id: string;
|
|
11
14
|
leafId: string | null;
|
|
15
|
+
/** Display heading for the topic, e.g. `/sitrep`; `question` keeps the full text sent to the model. */
|
|
16
|
+
title?: string;
|
|
12
17
|
followUps?: readonly BtwHistoryTurn[];
|
|
13
18
|
}
|
|
19
|
+
/** The topic's display heading: its title, else the opening question. */
|
|
20
|
+
export declare function getBtwTopicLabel(record: BtwHistoryRecord): string;
|
|
14
21
|
export declare function getBtwLatestTurn(record: BtwHistoryRecord): BtwHistoryTurn;
|
|
15
22
|
export declare function getBtwTurns(record: BtwHistoryRecord): readonly BtwHistoryTurn[];
|
|
16
23
|
/** Copy the most recent nonblank answer, preserving its original whitespace. */
|
|
@@ -3,6 +3,8 @@ import { OverlayPanel } from "../chrome/overlay-box.js";
|
|
|
3
3
|
import type { NativeNode } from "../native/node.js";
|
|
4
4
|
interface BtwPanelComponentOptions {
|
|
5
5
|
question: string;
|
|
6
|
+
/** Heading shown instead of `/btw <question>`, e.g. `/sitrep the migration` for a side command. */
|
|
7
|
+
title?: string;
|
|
6
8
|
tui: TUI;
|
|
7
9
|
canBranch?: () => boolean;
|
|
8
10
|
canFollowUp?: () => boolean;
|
|
@@ -94,6 +94,8 @@ export declare class MCPAddWizard extends OverlayPanel {
|
|
|
94
94
|
* arrows and pressing Enter; the footer's buttons run what their keys run.
|
|
95
95
|
*/
|
|
96
96
|
handleNativeEvent(event: NativeUiEvent): void;
|
|
97
|
+
/** Route enhanced clipboard text into the active wizard field, never the hidden chat editor. */
|
|
98
|
+
pasteText(text: string): void;
|
|
97
99
|
handleInput(keyData: string): void;
|
|
98
100
|
}
|
|
99
101
|
export {};
|
|
@@ -31,8 +31,8 @@ export declare class MacOSSpellingProvider implements EditorTextAssistProvider {
|
|
|
31
31
|
/**
|
|
32
32
|
* Misspelled prose ranges in `text` (offsets into it), in order and
|
|
33
33
|
* non-overlapping, except ones touching `context.typing` (the word still
|
|
34
|
-
* being typed).
|
|
35
|
-
*
|
|
34
|
+
* being typed). Checks the full logical line so rendering cuts cannot
|
|
35
|
+
* change spelling or prose boundaries. Uncached lines arrive through {@link onUpdate}.
|
|
36
36
|
*/
|
|
37
37
|
typoRanges(text: string, context: SpellingDecorationContext): readonly native.SpellingRange[];
|
|
38
38
|
/** Return the confident macOS correction after a completed prose word. */
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import type { TspSpan } from "@oh-my-pi/pi-wire";
|
|
2
2
|
import { type TerminalId } from "../terminal-capabilities.js";
|
|
3
|
+
/**
|
|
4
|
+
* A `file:` URL for `filePath`, or `undefined` when Node cannot encode it.
|
|
5
|
+
*
|
|
6
|
+
* Node rejects a malformed UNC target (a UNC path escaped twice by its author,
|
|
7
|
+
* e.g. `\\\wsl.localhost\…`) with `ERR_INVALID_ARG_VALUE: Empty UNC servername`.
|
|
8
|
+
* A stored session keeps whatever a tool call recorded, so a transcript must
|
|
9
|
+
* render such a path without a link rather than throw while it rebuilds.
|
|
10
|
+
*/
|
|
11
|
+
export declare function tryFileUri(filePath: string): string | undefined;
|
|
3
12
|
/**
|
|
4
13
|
* Build the OSC 8 target for a file path on `terminalId`.
|
|
5
14
|
*
|
|
@@ -15,11 +24,14 @@ import { type TerminalId } from "../terminal-capabilities.js";
|
|
|
15
24
|
* `vscode://file/<path>:<line>:<col>` form. Everywhere else the plain `file:`
|
|
16
25
|
* URI is right, with the location left in the visible text — terminals that
|
|
17
26
|
* parse a `path:line:col` suffix resolve that on their own.
|
|
27
|
+
*
|
|
28
|
+
* Returns `undefined` when `filePath` cannot be encoded — a malformed UNC
|
|
29
|
+
* target on the `file:` branch, or a lone surrogate on the `vscode://` branch.
|
|
18
30
|
*/
|
|
19
31
|
export declare function fileUriForTerminal(filePath: string, opts: {
|
|
20
32
|
line?: number;
|
|
21
33
|
col?: number;
|
|
22
|
-
} | undefined, terminalId: TerminalId): string;
|
|
34
|
+
} | undefined, terminalId: TerminalId): string | undefined;
|
|
23
35
|
/**
|
|
24
36
|
* Returns true when OSC 8 hyperlinks should be emitted.
|
|
25
37
|
*
|
|
@@ -84,7 +96,8 @@ export declare function fileLinkSpan(filePath: string, displayText: string, s?:
|
|
|
84
96
|
* Returns `displayText` unchanged when hyperlinks are disabled or when
|
|
85
97
|
* the text already contains an OSC 8 sequence (prevents double-wrapping).
|
|
86
98
|
* Relative paths resolve against the current working directory before URI
|
|
87
|
-
* encoding so the OSC 8 target is always a valid `file://` URL.
|
|
99
|
+
* encoding so the OSC 8 target is always a valid `file://` URL. A path Node
|
|
100
|
+
* cannot encode renders as `displayText` with no link.
|
|
88
101
|
*
|
|
89
102
|
* @param filePath - Filesystem path
|
|
90
103
|
* @param displayText - Text to render as the hyperlink anchor (may contain ANSI codes)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DescribeContext, NativeNode } from "../native/node.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type Theme } from "../theme/theme.js";
|
|
3
3
|
import type { Component } from "../tui.js";
|
|
4
4
|
/** Which side of an output stream remains visible when the pane is capped. */
|
|
5
5
|
export type OutputPaneEdge = "head" | "tail";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* tool renderers to ensure a unified TUI experience.
|
|
6
6
|
*/
|
|
7
7
|
import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
8
|
-
import
|
|
8
|
+
import { Ellipsis } from "@oh-my-pi/pi-natives";
|
|
9
9
|
import type { Theme } from "../theme/theme.js";
|
|
10
10
|
import type { Component } from "../tui.js";
|
|
11
11
|
export { Ellipsis } from "@oh-my-pi/pi-natives";
|
|
@@ -284,6 +284,12 @@ export declare function capParseErrors(errors: string[] | undefined, limit?: num
|
|
|
284
284
|
export declare function createCachedComponent(getExpanded: () => boolean, compute: (width: number, expanded: boolean) => string[], options?: {
|
|
285
285
|
paddingX?: number;
|
|
286
286
|
}): Component;
|
|
287
|
+
/**
|
|
288
|
+
* Fit a status header plus body rows to `width` for search-style results. The
|
|
289
|
+
* header soft-wraps so its trailing summary meta (counts, `truncated`) stays
|
|
290
|
+
* visible behind a long pattern; body rows keep single-row truncation.
|
|
291
|
+
*/
|
|
292
|
+
export declare function fitHeaderedRows(header: string, rows: readonly string[], width: number): string[];
|
|
287
293
|
/**
|
|
288
294
|
* Single-slot memo for an expensive rendered string (syntax highlighting, diff
|
|
289
295
|
* coloring) keyed by the exact inputs that shape the bytes: theme instance,
|
|
@@ -342,6 +342,13 @@ export declare function parseKittyDirectPlacementLine(line: string): ParsedKitty
|
|
|
342
342
|
* byte-identical between frames, so the diff never rewrites them.
|
|
343
343
|
*/
|
|
344
344
|
export declare function directPlacementLift(line: string): number;
|
|
345
|
+
/**
|
|
346
|
+
* Re-anchor an {@link Image} direct-placement line `lift` rows above its own
|
|
347
|
+
* row instead of its encoded `CUU(rows-1)`. The renderer lowers the lift when
|
|
348
|
+
* the block's top is clipped by the frame: the cursor-up would otherwise climb
|
|
349
|
+
* past the frame's first row into whatever the terminal shows above it.
|
|
350
|
+
*/
|
|
351
|
+
export declare function withDirectPlacementLift(line: string, lift: number): string;
|
|
345
352
|
/**
|
|
346
353
|
* Rebuild an {@link Image} direct-placement line for the viewport row it is
|
|
347
354
|
* written at. The component-rendered line encodes `CUU(rows-1)`, which clamps
|
|
@@ -51,8 +51,10 @@ export interface EditToolPerFileResult {
|
|
|
51
51
|
}
|
|
52
52
|
/** Diff, diagnostics, and snapshots returned by an edit. */
|
|
53
53
|
export interface EditToolDetails {
|
|
54
|
-
/** Unified diff of the changes made */
|
|
54
|
+
/** Unified diff of the changes made; empty when the edit failed */
|
|
55
55
|
diff: string;
|
|
56
|
+
/** Why the edit failed, as the engine classed it (`unseen_lines`, `stale_tag`, `parse`, …) */
|
|
57
|
+
errorKind?: string;
|
|
56
58
|
/** Line number of the first change in the new file (for editor navigation) */
|
|
57
59
|
firstChangedLine?: number;
|
|
58
60
|
/** Diagnostic result (if available) */
|
|
@@ -18,6 +18,8 @@ export interface GroupedFileSection {
|
|
|
18
18
|
export interface GroupedFilesOutput {
|
|
19
19
|
model: string[];
|
|
20
20
|
display: string[];
|
|
21
|
+
/** Per file, the `model` index just past its last line: a head cut keeping `n` lines shows it whole when this is <= n. */
|
|
22
|
+
modelEnds: Map<string, number>;
|
|
21
23
|
}
|
|
22
24
|
/**
|
|
23
25
|
* Render a list of files as a multi-level, prefix-folded directory tree shared by
|
|
@@ -22,12 +22,12 @@ export declare function renderMCPResult(result: {
|
|
|
22
22
|
details?: MCPToolDetails;
|
|
23
23
|
isError?: boolean;
|
|
24
24
|
}, options: RenderResultOptions, theme: Theme, args?: Record<string, unknown>): Component;
|
|
25
|
-
/** Native MCP call view: the head
|
|
25
|
+
/** Native MCP call view: the head plus the folded args, inline while pending. */
|
|
26
26
|
export declare function describeMCPCall(args: Record<string, unknown>, label: string): NativeToolView;
|
|
27
27
|
/**
|
|
28
28
|
* Native MCP result view: JSON output as a JSON tree, Markdown when the
|
|
29
|
-
* preference is on, otherwise the raw text as tool output
|
|
30
|
-
*
|
|
29
|
+
* preference is on, otherwise the raw text as tool output; the folded Args
|
|
30
|
+
* section follows, so the collapsed preview still leads with the output.
|
|
31
31
|
*/
|
|
32
32
|
export declare function describeMCPResult(result: {
|
|
33
33
|
content: Array<{
|
|
@@ -10,8 +10,8 @@ import type { NativeToolView, ToolRenderResult } from "./renderer.js";
|
|
|
10
10
|
/** Whether a wait snapshot contains only running jobs and no cancellations. */
|
|
11
11
|
export declare function isWaitingPollDetails(details: unknown): boolean;
|
|
12
12
|
/** Coordination details retained by wait and background job operations. */
|
|
13
|
-
export type CoordinationOp = "send" | "wait" | "
|
|
14
|
-
/** Background-job row surfaced by `wait`/`cancel
|
|
13
|
+
export type CoordinationOp = "send" | "wait" | "cancel";
|
|
14
|
+
/** Background-job row surfaced by `wait`/`cancel` results and `proc://` listings. */
|
|
15
15
|
export interface JobSnapshot {
|
|
16
16
|
id: string;
|
|
17
17
|
type: "bash" | "task" | "eval";
|
|
@@ -32,6 +32,14 @@ export interface JobSnapshot {
|
|
|
32
32
|
completionPercent?: number;
|
|
33
33
|
/** The task agent's latest stage in its own words; see `AgentProgress.stage`. */
|
|
34
34
|
stage?: string;
|
|
35
|
+
/** Running task only: assistant requests so far; see `AgentProgress.requests`. */
|
|
36
|
+
requests?: number;
|
|
37
|
+
/** Running task only: cumulative cost in USD; see `AgentProgress.cost`. */
|
|
38
|
+
cost?: number;
|
|
39
|
+
/** Running task only: latest per-turn context size; see `AgentProgress.contextTokens`. */
|
|
40
|
+
contextTokens?: number;
|
|
41
|
+
/** Running task only: the model's context window; see `AgentProgress.contextWindow`. */
|
|
42
|
+
contextWindow?: number;
|
|
35
43
|
/** The job's live widget, while one is being drawn; frozen at its last state once the job settles. */
|
|
36
44
|
widget?: JobWidgetView;
|
|
37
45
|
resultText?: string;
|
package/dist/types/vim.d.ts
CHANGED
|
@@ -64,13 +64,43 @@ export type VimCommand = {
|
|
|
64
64
|
fromLine: number;
|
|
65
65
|
toLine: number;
|
|
66
66
|
out: boolean;
|
|
67
|
+
} | {
|
|
68
|
+
kind: "retype";
|
|
69
|
+
record: VimInsertRecord;
|
|
70
|
+
times: number;
|
|
71
|
+
newline: boolean;
|
|
72
|
+
overwrite: boolean;
|
|
67
73
|
};
|
|
74
|
+
/** What an Insert/Replace session typed, relative to where it started; `.` replays it. */
|
|
75
|
+
export interface VimInsertRecord {
|
|
76
|
+
/** Graphemes removed before the session's start cursor (Backspace past the insert point). */
|
|
77
|
+
deleteBefore: number;
|
|
78
|
+
/** Graphemes removed after it (forward delete). */
|
|
79
|
+
deleteAfter: number;
|
|
80
|
+
text: string;
|
|
81
|
+
}
|
|
68
82
|
/** Start offset of the grapheme after `col`, clamped to `text.length`. */
|
|
69
83
|
export declare function nextGraphemeStart(text: string, col: number): number;
|
|
70
84
|
/** Start offset of the grapheme before `col`, clamped to 0. */
|
|
71
85
|
export declare function prevGraphemeStart(text: string, col: number): number;
|
|
72
86
|
/** Offset of the final grapheme — where a Normal-mode cursor rests on a non-empty line. */
|
|
73
87
|
export declare function lastGraphemeStart(text: string): number;
|
|
88
|
+
export declare function countGraphemes(text: string): number;
|
|
89
|
+
/** Offset `count` graphemes forward or backward from `offset`, clamped to the text. */
|
|
90
|
+
export declare function stepGraphemes(text: string, offset: number, count: number, forward: boolean): number;
|
|
91
|
+
/**
|
|
92
|
+
* What a session turned `before` into, anchored at its start `cursor`: the prefix match stops at the
|
|
93
|
+
* cursor and the suffix match never crosses it, so typing `aa` before an existing `a` records `aa`
|
|
94
|
+
* at the cursor rather than after the old one. Comparisons are whole graphemes, so a shared high
|
|
95
|
+
* surrogate cannot split an emoji into the replayed text.
|
|
96
|
+
*/
|
|
97
|
+
export declare function diffInsertSession(before: string, cursor: number, after: string): VimInsertRecord;
|
|
98
|
+
/** Buffer flattened to one string plus the cursor's offset in it, so scans can cross lines. */
|
|
99
|
+
export declare function flatten(buf: VimBuffer): {
|
|
100
|
+
text: string;
|
|
101
|
+
cursor: number;
|
|
102
|
+
};
|
|
103
|
+
export declare function offsetToPos(lines: readonly string[], offset: number): VimPosition;
|
|
74
104
|
export declare class VimState {
|
|
75
105
|
#private;
|
|
76
106
|
mode: VimMode;
|
|
@@ -94,6 +124,15 @@ export declare class VimState {
|
|
|
94
124
|
* through to its regular handling (and, for Escape in Normal mode, to the app's interrupt).
|
|
95
125
|
*/
|
|
96
126
|
handleKey(key: string, buf: VimBuffer): VimCommand[] | null;
|
|
127
|
+
/**
|
|
128
|
+
* Attach what an Insert/Replace session typed to the change that opened it. After an arrow key
|
|
129
|
+
* split the session, Vim's redo becomes a plain insert of the text typed since.
|
|
130
|
+
*/
|
|
131
|
+
recordInsert(record: VimInsertRecord, afterArrow: boolean): void;
|
|
132
|
+
/** Commit a staged change when the editor confirms text changed. */
|
|
133
|
+
commitChange(): void;
|
|
134
|
+
/** Discard a staged change when the command was a no-op (e.g. `J` on the last line). */
|
|
135
|
+
discardChange(): void;
|
|
97
136
|
}
|
|
98
137
|
/**
|
|
99
138
|
* Normalized, end-exclusive span covered by a Visual selection.
|
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.13.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.13.0",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "18.13.0",
|
|
46
|
+
"@oh-my-pi/pi-ai": "18.13.0",
|
|
47
|
+
"@oh-my-pi/pi-catalog": "18.13.0",
|
|
48
|
+
"@oh-my-pi/pi-natives": "18.13.0",
|
|
49
|
+
"@oh-my-pi/pi-utils": "18.13.0",
|
|
50
|
+
"@oh-my-pi/pi-wire": "18.13.0",
|
|
51
|
+
"@oh-my-pi/snapcompact": "18.13.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"kitty-vt-wasm": "^0.2.0"
|
package/src/app-keybindings.ts
CHANGED
|
@@ -40,6 +40,7 @@ interface AppKeybindings {
|
|
|
40
40
|
"app.ask.hide": true;
|
|
41
41
|
"app.approval.approve": true;
|
|
42
42
|
"app.editor.external": true;
|
|
43
|
+
"app.local.open": true;
|
|
43
44
|
"app.message.followUp": true;
|
|
44
45
|
"app.retry": true;
|
|
45
46
|
"app.message.dequeue": true;
|
|
@@ -154,6 +155,10 @@ export const KEYBINDINGS = {
|
|
|
154
155
|
defaultKeys: "ctrl+g",
|
|
155
156
|
description: "Open external editor",
|
|
156
157
|
},
|
|
158
|
+
"app.local.open": {
|
|
159
|
+
defaultKeys: "alt+g",
|
|
160
|
+
description: "Open a local:// file in external editor",
|
|
161
|
+
},
|
|
157
162
|
"app.message.followUp": {
|
|
158
163
|
// Ctrl+Enter is preserved for terminals that deliver it (Kitty/iTerm2/WezTerm/Ghostty),
|
|
159
164
|
// but Windows Terminal does not emit a distinct event for Ctrl+Enter — Ctrl+Q is listed
|
|
@@ -253,7 +253,13 @@ export class BashExecutionComponent extends Container {
|
|
|
253
253
|
/** Switch to PTY rendering and feed raw terminal bytes through the vterm replay. */
|
|
254
254
|
appendPtyChunk(chunk: string): void {
|
|
255
255
|
if (this.#status !== "running" && !this.#ptyWriting && this.#ptyQueue.length === 0) return;
|
|
256
|
-
this.#ptyMode
|
|
256
|
+
if (!this.#ptyMode) {
|
|
257
|
+
this.#ptyMode = true;
|
|
258
|
+
// The pane's `styleLine` reads `#ptyMode`, so its styled rows change
|
|
259
|
+
// the moment this flips — with the rows themselves unchanged. The pane
|
|
260
|
+
// memoizes its styled output, so it has to be told.
|
|
261
|
+
this.#outputPane.invalidate();
|
|
262
|
+
}
|
|
257
263
|
this.#ptyReplayFinalized = false;
|
|
258
264
|
this.#ptyQueue.push(chunk);
|
|
259
265
|
if (this.#ptyQueue.length > MAX_PTY_QUEUE_CHUNKS) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
-
import { pathToFileURL } from "node:url";
|
|
3
2
|
import type { AssistantMessage, Usage } from "@oh-my-pi/pi-ai";
|
|
4
3
|
import { type Component } from "../tui";
|
|
5
4
|
import { Container } from "../tui";
|
|
@@ -876,7 +875,7 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
876
875
|
const linkPath = linkPathForTargets(row.targets);
|
|
877
876
|
const spans: TspSpan[] = [];
|
|
878
877
|
if (filePath) {
|
|
879
|
-
const href = linkPath
|
|
878
|
+
const href = linkPath ? fileHref(linkPath) : undefined;
|
|
880
879
|
spans.push(span(filePath, "path", href ? { href } : undefined));
|
|
881
880
|
} else {
|
|
882
881
|
spans.push(span("…", "toolOutput"));
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { pathToFileURL } from "node:url";
|
|
2
1
|
import type { TextContent } from "@oh-my-pi/pi-ai";
|
|
3
2
|
import { type Component } from "../tui";
|
|
4
3
|
import { Box } from "../components/box";
|
|
@@ -10,6 +9,7 @@ import { Text } from "../components/text";
|
|
|
10
9
|
import { getMarkdownTheme, theme } from "../theme";
|
|
11
10
|
import type { CustomMessage, SkillPromptDetails } from "./messages";
|
|
12
11
|
import { fileHyperlink } from "../render";
|
|
12
|
+
import { tryFileUri } from "../render/hyperlink";
|
|
13
13
|
import { collapseSkillTokens, skillChipLabel, skillChipStyle, skillToken } from "../prompt/composer-attachments";
|
|
14
14
|
import { type UserBubbleOptions, UserMessageComponent, userBubbleColor } from "./user-message";
|
|
15
15
|
import type { TspSpan } from "@oh-my-pi/pi-wire";
|
|
@@ -86,11 +86,7 @@ export class SkillMessageComponent extends Container {
|
|
|
86
86
|
const leading = display.startsWith(label) && /^\s*$/.test(display.charAt(label.length));
|
|
87
87
|
const draft = leading ? display.slice(label.length).trim() : display;
|
|
88
88
|
const head: TspSpan[] = [
|
|
89
|
-
span(
|
|
90
|
-
label,
|
|
91
|
-
"customMessageLabel strong",
|
|
92
|
-
details?.path ? { href: pathToFileURL(details.path).href } : undefined,
|
|
93
|
-
),
|
|
89
|
+
span(label, "customMessageLabel strong", details?.path ? { href: tryFileUri(details.path) } : undefined),
|
|
94
90
|
];
|
|
95
91
|
if (typeof details?.lineCount === "number") {
|
|
96
92
|
head.push(span(` ${details.lineCount} ${details.lineCount === 1 ? "line" : "lines"}`, "muted"));
|