@oh-my-pi/pi-tui 18.12.0 → 18.12.2
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 +31 -76
- package/dist/types/chat/advisor-message.d.ts +4 -1
- package/dist/types/chat/messages.d.ts +3 -1
- package/dist/types/chrome/diff.d.ts +2 -3
- package/dist/types/chrome/status-notice.d.ts +2 -0
- package/dist/types/prompt/composer.d.ts +2 -0
- package/dist/types/tmux.d.ts +9 -0
- package/dist/types/tools/eval.d.ts +51 -7
- package/dist/types/tools/grouped-file-output.d.ts +3 -3
- package/dist/types/tools/read.d.ts +1 -1
- package/dist/types/tools/web-search-types.d.ts +1 -0
- package/package.json +9 -9
- package/src/charts/table-data.ts +17 -6
- package/src/chat/advisor-message.ts +77 -24
- package/src/chat/messages.ts +4 -1
- package/src/chat/read-tool-group.ts +2 -2
- package/src/chrome/diff.ts +4 -9
- package/src/chrome/status-notice.ts +5 -0
- package/src/components/markdown.ts +21 -1
- package/src/prompt/composer-cache.ts +3 -0
- package/src/prompt/composer.ts +8 -2
- package/src/status-line/component.ts +56 -1
- package/src/stdin-buffer.ts +289 -61
- package/src/terminal-capabilities.ts +7 -1
- package/src/terminal.ts +30 -17
- package/src/tmux.ts +30 -0
- package/src/tools/ast-edit.ts +2 -3
- package/src/tools/ast-grep.ts +1 -2
- package/src/tools/edit.ts +3 -3
- package/src/tools/eval.ts +132 -132
- package/src/tools/find.ts +6 -3
- package/src/tools/grep.ts +0 -2
- package/src/tools/grouped-file-output.ts +6 -5
- package/src/tools/read.ts +3 -3
- package/src/tools/web-search-types.ts +1 -0
- package/src/tools/write.ts +5 -3
- package/src/tui.ts +20 -7
package/src/chrome/diff.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { diffWords } from "@oh-my-pi/pi-natives";
|
|
|
2
2
|
import { DEFAULT_TAB_WIDTH, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
3
3
|
import type { TspDiffHunk, TspProps } from "@oh-my-pi/pi-wire";
|
|
4
4
|
import { theme as activeTheme, getLanguageFromPath, highlightCode, type Theme } from "../theme/index";
|
|
5
|
-
import { type CodeFrameMarker, formatCodeFrameLine, replaceTabs
|
|
5
|
+
import { type CodeFrameMarker, formatCodeFrameLine, replaceTabs } from "../render/render-utils";
|
|
6
6
|
import { node } from "../native/describe";
|
|
7
7
|
import type { NativeNode } from "../native/node";
|
|
8
8
|
/** SGR dim on / normal intensity — additive, preserves fg/bg colors. */
|
|
@@ -106,9 +106,9 @@ function renderIntraLineDiff(
|
|
|
106
106
|
* becomes one hunk; the terminal draws gutters, word emphasis and highlighting.
|
|
107
107
|
* Removed rows carry old line numbers, added rows new ones and context rows
|
|
108
108
|
* old ones, so the other side is derived from the running line delta.
|
|
109
|
+
* Tern picks the grammar from `path`.
|
|
109
110
|
*/
|
|
110
|
-
export function nativeDiff(diffText: string,
|
|
111
|
-
const { filePath, ...props } = options;
|
|
111
|
+
export function nativeDiff(diffText: string, props: TspProps<"diff"> = {}): NativeNode {
|
|
112
112
|
const hunks: TspDiffHunk[] = [];
|
|
113
113
|
let lines: string[] = [];
|
|
114
114
|
let oldStart = 0;
|
|
@@ -148,12 +148,7 @@ export function nativeDiff(diffText: string, options: { filePath?: string } & Ts
|
|
|
148
148
|
lines.push(`${parsed.prefix}${parsed.content}`);
|
|
149
149
|
}
|
|
150
150
|
close();
|
|
151
|
-
return node("diff", {
|
|
152
|
-
...props,
|
|
153
|
-
hunks,
|
|
154
|
-
path: props.path ?? (filePath ? shortenPath(filePath) : undefined),
|
|
155
|
-
lang: props.lang ?? (filePath ? getLanguageFromPath(filePath) : undefined),
|
|
156
|
-
});
|
|
151
|
+
return node("diff", { ...props, hunks });
|
|
157
152
|
}
|
|
158
153
|
|
|
159
154
|
export interface RenderDiffOptions {
|
|
@@ -42,6 +42,11 @@ export class StatusNotice extends Container {
|
|
|
42
42
|
this.addChild(this.#text);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/** The text the notice currently shows. */
|
|
46
|
+
get message(): string {
|
|
47
|
+
return this.#message;
|
|
48
|
+
}
|
|
49
|
+
|
|
45
50
|
setMessage(message: string, options: StatusNoticeOptions = {}): void {
|
|
46
51
|
const toast = options.toast ?? true;
|
|
47
52
|
this.#text.setStyleFn(options.styleFn);
|
|
@@ -4611,7 +4611,27 @@ export class Markdown implements Component {
|
|
|
4611
4611
|
const text = this.#renderInlineTokens(cell.tokens || [], styleContext);
|
|
4612
4612
|
return this.#wrapCellText(text, columnWidths[i]);
|
|
4613
4613
|
});
|
|
4614
|
-
|
|
4614
|
+
let rowLineCount = Math.max(...rowCellLines.map(c => c.length));
|
|
4615
|
+
// The open cell is laid out in widths measured without it, so it can
|
|
4616
|
+
// wrap past the lines its row's finished cells need. Those extra lines
|
|
4617
|
+
// come and go as the cell grows and the column widens, moving every row
|
|
4618
|
+
// below the table each frame; hold the row at its finished cells' height
|
|
4619
|
+
// until the cell closes and counts toward the widths. While lines are
|
|
4620
|
+
// held back, only the last kept line makes room for a `…`: it is
|
|
4621
|
+
// re-wrapped one column narrower, since the wrapper closes the line's
|
|
4622
|
+
// links and styles where truncation would cut them off, and dropped when
|
|
4623
|
+
// even that overflows (a wide glyph in a two-cell column).
|
|
4624
|
+
if (rowIndex === lastRow && rowCellLines[openCell]) {
|
|
4625
|
+
const settledLineCount = Math.max(1, ...rowCellLines.filter((_, i) => i !== openCell).map(c => c.length));
|
|
4626
|
+
if (rowCellLines[openCell].length > settledLineCount) {
|
|
4627
|
+
const width = columnWidths[openCell];
|
|
4628
|
+
const kept = rowCellLines[openCell].slice(0, settledLineCount);
|
|
4629
|
+
const last = width > 1 ? (this.#wrapCellText(kept[settledLineCount - 1], width - 1)[0] ?? "") : "";
|
|
4630
|
+
kept[settledLineCount - 1] = `${visibleWidth(last) < width ? last : ""}…`;
|
|
4631
|
+
rowCellLines[openCell] = kept;
|
|
4632
|
+
}
|
|
4633
|
+
rowLineCount = settledLineCount;
|
|
4634
|
+
}
|
|
4615
4635
|
|
|
4616
4636
|
for (let lineIdx = 0; lineIdx < rowLineCount; lineIdx++) {
|
|
4617
4637
|
const rowParts = rowCellLines.map((cellLines, colIdx) => {
|
|
@@ -94,6 +94,7 @@ function parseUiState(
|
|
|
94
94
|
showHardwareCursor,
|
|
95
95
|
maxInlineImages,
|
|
96
96
|
resizeScrollback,
|
|
97
|
+
completeScrollback,
|
|
97
98
|
imeSafeCursor,
|
|
98
99
|
autocompleteMaxVisible,
|
|
99
100
|
spellingTypoDetection,
|
|
@@ -109,6 +110,7 @@ function parseUiState(
|
|
|
109
110
|
resizeScrollback !== "append" &&
|
|
110
111
|
resizeScrollback !== "rebuild" &&
|
|
111
112
|
resizeScrollback !== "preserve") ||
|
|
113
|
+
(completeScrollback !== undefined && typeof completeScrollback !== "boolean") ||
|
|
112
114
|
typeof imeSafeCursor !== "boolean" ||
|
|
113
115
|
typeof autocompleteMaxVisible !== "number" ||
|
|
114
116
|
typeof spellingTypoDetection !== "boolean" ||
|
|
@@ -136,6 +138,7 @@ function parseUiState(
|
|
|
136
138
|
showHardwareCursor,
|
|
137
139
|
maxInlineImages,
|
|
138
140
|
resizeScrollback: resizeScrollback ?? "rebuild",
|
|
141
|
+
completeScrollback: completeScrollback ?? false,
|
|
139
142
|
imeSafeCursor,
|
|
140
143
|
autocompleteMaxVisible,
|
|
141
144
|
spellingTypoDetection,
|
package/src/prompt/composer.ts
CHANGED
|
@@ -37,6 +37,8 @@ export interface ComposerPreferences {
|
|
|
37
37
|
readonly showHardwareCursor: boolean;
|
|
38
38
|
readonly maxInlineImages: number;
|
|
39
39
|
readonly resizeScrollback: ResizeScrollbackMode;
|
|
40
|
+
/** Send rows a growing HUD or dialog pushes off screen straight to scrollback instead of holding them back until it shrinks. */
|
|
41
|
+
readonly completeScrollback: boolean;
|
|
40
42
|
readonly imeSafeCursor: boolean;
|
|
41
43
|
readonly autocompleteMaxVisible: number;
|
|
42
44
|
readonly spellingTypoDetection: boolean;
|
|
@@ -51,6 +53,7 @@ export const COMPOSER_DEFAULTS: ComposerPreferences = {
|
|
|
51
53
|
showHardwareCursor: true,
|
|
52
54
|
maxInlineImages: 8,
|
|
53
55
|
resizeScrollback: "rebuild",
|
|
56
|
+
completeScrollback: false,
|
|
54
57
|
imeSafeCursor: false,
|
|
55
58
|
autocompleteMaxVisible: 10,
|
|
56
59
|
spellingTypoDetection: true,
|
|
@@ -271,6 +274,8 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
271
274
|
// displaced rows, so those rows refill the screen when it shrinks (#11007).
|
|
272
275
|
// Displaced rows reach history when newer output pushes them past the floor
|
|
273
276
|
// or persistent idle chrome settles and raises it.
|
|
277
|
+
// With `completeScrollback`, rows the chrome pushes off screen go straight
|
|
278
|
+
// to scrollback, and when it shrinks the freed rows stay blank.
|
|
274
279
|
// The floor is terminal-height independent — chrome heights do not scale
|
|
275
280
|
// with rows — so it is retained across resizes rather than rediscovered
|
|
276
281
|
// from whatever chrome is expanded when the height changes.
|
|
@@ -429,7 +434,7 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
429
434
|
const promotedChromeContracted = this.#chromeFloorPromoted && eligibleChrome < (this.#chromeFloor ?? 0);
|
|
430
435
|
this.#chromeFloor = Math.min(this.#chromeFloor ?? eligibleChrome, eligibleChrome);
|
|
431
436
|
this.#rebaselineChromeFloor(this.#runActive || transcript.hasActiveBlock(), eligibleChrome);
|
|
432
|
-
const belowFloor = this.#chromeFloor;
|
|
437
|
+
const belowFloor = this.#preferences.completeScrollback ? after.length : this.#chromeFloor;
|
|
433
438
|
// Offer history under capacity pressure only: blocks stay live (and keep
|
|
434
439
|
// reflowing to the current width) while the screen has room. A batch
|
|
435
440
|
// leaves the mutable viewport in the same frame it is appended, so its
|
|
@@ -447,7 +452,8 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
447
452
|
// billed against, so its compaction allocator (one row per block, no
|
|
448
453
|
// inter-block blanks) engages only when a block genuinely cannot retire.
|
|
449
454
|
// Rows the chrome above its floor displaces are clipped from the top by
|
|
450
|
-
// the `drop` slice below; they stay live until the chrome shrinks.
|
|
455
|
+
// the `drop` slice below; they stay live until the chrome shrinks. With
|
|
456
|
+
// `completeScrollback` none are held back.
|
|
451
457
|
const active = transcript.renderViewport(width, Math.max(0, rows - before.length - belowFloor), frame);
|
|
452
458
|
const activeSpans: ViewportClickSpan[] = [];
|
|
453
459
|
for (const span of transcript.getLastViewportSpans()) {
|
|
@@ -43,7 +43,7 @@ import type { TspMeterMark, TspProps } from "@oh-my-pi/pi-wire";
|
|
|
43
43
|
import type { NativeNode, NativeUiEvent } from "../native/node";
|
|
44
44
|
import { col, node, span } from "../native/describe";
|
|
45
45
|
import { getContextMeterThresholds } from "../chrome/context-thresholds";
|
|
46
|
-
import { isNativeRendering } from "../native/state";
|
|
46
|
+
import { isNativeRendering, onNativeRenderingChange } from "../native/state";
|
|
47
47
|
import { getSeparator } from "./separators";
|
|
48
48
|
import type {
|
|
49
49
|
CollabStatus,
|
|
@@ -154,6 +154,10 @@ const WATCHER_FAILURE_POLL_TTL_MS = 5000;
|
|
|
154
154
|
const BRAND_FADE_MS = 450;
|
|
155
155
|
/** Repaint cadence while the brand fade is in flight (rust omp's `FADE_FRAME`). */
|
|
156
156
|
const BRAND_FADE_FRAME_MS = 40;
|
|
157
|
+
/** Repaint cadence while a turn's active-processing window is open: keeps the
|
|
158
|
+
* status bar's clock-driven readouts (pi spinner, turn timer) animating when
|
|
159
|
+
* no other component is requesting renders. */
|
|
160
|
+
const ACTIVE_WINDOW_REPAINT_MS = 250;
|
|
157
161
|
|
|
158
162
|
/**
|
|
159
163
|
* Providers whose subscription quota is a single monthly bucket, so their
|
|
@@ -690,6 +694,8 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
690
694
|
#gitWatcherUnavailable = false;
|
|
691
695
|
#onBranchChange: (() => void) | null = null;
|
|
692
696
|
#disposed = false;
|
|
697
|
+
/** Unsubscribe for the native-rendering feed; re-arms the turn-window ticker when a TSP surface is revoked. */
|
|
698
|
+
#unwatchNativeRendering: (() => void) | undefined;
|
|
693
699
|
#autoCompactEnabled: boolean = true;
|
|
694
700
|
/** Pulse timer for the running-speculation indicator; live only while speculation runs. */
|
|
695
701
|
#speculationBlinkTimer: NodeJS.Timeout | undefined;
|
|
@@ -700,6 +706,8 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
700
706
|
#brandWorking = false;
|
|
701
707
|
/** Frame timer driving repaints while the brand fade is unsettled. */
|
|
702
708
|
#brandFadeTimer: NodeJS.Timeout | undefined;
|
|
709
|
+
/** Repaint ticker while a turn's active-processing window is open (see #syncActiveWindowRepaint). */
|
|
710
|
+
#activeWindowRepaintTimer: NodeJS.Timeout | undefined;
|
|
703
711
|
/** One wall-clock wakeup for the active model's next tariff change, including while idle. */
|
|
704
712
|
#pricingTimer: NodeJS.Timeout | undefined;
|
|
705
713
|
#pricingTimerCost: ModelCost | undefined;
|
|
@@ -825,6 +833,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
825
833
|
private readonly host: StatusLineHost<TSession>,
|
|
826
834
|
) {
|
|
827
835
|
this.#settings = host.getSettings();
|
|
836
|
+
this.#unwatchNativeRendering = onNativeRenderingChange(() => this.#syncActiveWindowRepaint());
|
|
828
837
|
}
|
|
829
838
|
|
|
830
839
|
#gitEnabled(): boolean {
|
|
@@ -931,6 +940,12 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
931
940
|
this.#invalidateSessionCaches();
|
|
932
941
|
this.#closeStaleActiveWindow();
|
|
933
942
|
}
|
|
943
|
+
// The ticker self-stops once its tick sees no open window for the
|
|
944
|
+
// newly-attached session; re-pointing focus at a session whose turn
|
|
945
|
+
// is still running must re-arm it, and no other path reliably fires
|
|
946
|
+
// here (closeStaleActiveWindow returns early while streaming, and
|
|
947
|
+
// the synthesized agent_start no-ops on an already-open window).
|
|
948
|
+
this.#syncActiveWindowRepaint();
|
|
934
949
|
this.invalidate();
|
|
935
950
|
}
|
|
936
951
|
|
|
@@ -1029,6 +1044,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1029
1044
|
meter.activeMs = 0;
|
|
1030
1045
|
meter.activeStartedAt = null;
|
|
1031
1046
|
this.#invalidateStatusLineRenderCache();
|
|
1047
|
+
this.#syncActiveWindowRepaint();
|
|
1032
1048
|
}
|
|
1033
1049
|
|
|
1034
1050
|
/**
|
|
@@ -1043,6 +1059,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1043
1059
|
if (meter.activeStartedAt !== null) return;
|
|
1044
1060
|
meter.activeStartedAt = Date.now();
|
|
1045
1061
|
this.#invalidateStatusLineRenderCache();
|
|
1062
|
+
this.#syncActiveWindowRepaint();
|
|
1046
1063
|
}
|
|
1047
1064
|
|
|
1048
1065
|
/**
|
|
@@ -1057,6 +1074,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1057
1074
|
meter.activeMs += Math.max(0, Date.now() - meter.activeStartedAt);
|
|
1058
1075
|
meter.activeStartedAt = null;
|
|
1059
1076
|
this.#invalidateStatusLineRenderCache();
|
|
1077
|
+
this.#syncActiveWindowRepaint();
|
|
1060
1078
|
}
|
|
1061
1079
|
|
|
1062
1080
|
/**
|
|
@@ -1282,8 +1300,11 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1282
1300
|
this.#onBranchChange = null;
|
|
1283
1301
|
this.#stopSpeculationBlink();
|
|
1284
1302
|
this.#stopBrandFadeTimer();
|
|
1303
|
+
this.#stopActiveWindowRepaint();
|
|
1285
1304
|
this.#stopPricingTimer();
|
|
1286
1305
|
this.#clearUsageStartTimer();
|
|
1306
|
+
this.#unwatchNativeRendering?.();
|
|
1307
|
+
this.#unwatchNativeRendering = undefined;
|
|
1287
1308
|
this.#onCodexResetFireworks = undefined;
|
|
1288
1309
|
this.#onResetExpiryNotice = undefined;
|
|
1289
1310
|
this.#codexResetSnapshots.clear();
|
|
@@ -1387,6 +1408,40 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1387
1408
|
this.#brandFadeTimer = undefined;
|
|
1388
1409
|
}
|
|
1389
1410
|
|
|
1411
|
+
/**
|
|
1412
|
+
* Keep the status bar repainting while a turn's active-processing window
|
|
1413
|
+
* is open. The `pi` segment's spinner and turn timer are painted from the
|
|
1414
|
+
* wall clock at render time, so they only animate when frames flow; during
|
|
1415
|
+
* a quiet turn the working-row loader's 80 ms tick is normally the sole
|
|
1416
|
+
* repaint source, and paths that stop or bypass that loader mid-turn (an
|
|
1417
|
+
* error banner, a stale retry/compaction loader reference) froze the
|
|
1418
|
+
* spinner until the next keystroke. The status line owns the active
|
|
1419
|
+
* window, so it owns a repaint source for it; a native surface clocks its
|
|
1420
|
+
* own spinner and needs none.
|
|
1421
|
+
*/
|
|
1422
|
+
#syncActiveWindowRepaint(): void {
|
|
1423
|
+
if (this.#disposed || isNativeRendering() || this.getTurnElapsedMs() === null) {
|
|
1424
|
+
this.#stopActiveWindowRepaint();
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
if (this.#activeWindowRepaintTimer) return;
|
|
1428
|
+
const timer = setInterval(() => {
|
|
1429
|
+
if (this.#disposed || isNativeRendering() || this.getTurnElapsedMs() === null) {
|
|
1430
|
+
this.#stopActiveWindowRepaint();
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
this.#onBranchChange?.();
|
|
1434
|
+
}, ACTIVE_WINDOW_REPAINT_MS);
|
|
1435
|
+
timer.unref?.();
|
|
1436
|
+
this.#activeWindowRepaintTimer = timer;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
#stopActiveWindowRepaint(): void {
|
|
1440
|
+
if (!this.#activeWindowRepaintTimer) return;
|
|
1441
|
+
clearInterval(this.#activeWindowRepaintTimer);
|
|
1442
|
+
this.#activeWindowRepaintTimer = undefined;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1390
1445
|
#stopPricingTimer(): void {
|
|
1391
1446
|
clearTimeout(this.#pricingTimer);
|
|
1392
1447
|
this.#pricingTimer = undefined;
|