pi-open-tui 0.2.13 → 0.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -135
- package/README.zh-CN.md +141 -135
- package/extensions/open-tui/config.ts +16 -0
- package/extensions/open-tui/editor.ts +11 -0
- package/extensions/open-tui/fullscreen-scroll.ts +34 -0
- package/extensions/open-tui/index.ts +5 -1
- package/extensions/open-tui/settings-command.ts +83 -7
- package/extensions/open-tui/state.ts +11 -8
- package/extensions/open-tui/telemetry.ts +295 -298
- package/extensions/open-tui/utils.ts +365 -361
- package/package.json +2 -2
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { TUI } from "@earendil-works/pi-tui";
|
|
2
|
+
|
|
3
|
+
export const MIN_FULLSCREEN_WHEEL_SCROLL_LINES = 1;
|
|
4
|
+
export const MAX_FULLSCREEN_WHEEL_SCROLL_LINES = 10;
|
|
5
|
+
export const DEFAULT_FULLSCREEN_WHEEL_SCROLL_LINES = 4;
|
|
6
|
+
|
|
7
|
+
export function normalizeFullscreenWheelScrollLines(
|
|
8
|
+
value: unknown,
|
|
9
|
+
fallback = DEFAULT_FULLSCREEN_WHEEL_SCROLL_LINES,
|
|
10
|
+
): number {
|
|
11
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
12
|
+
return Math.min(
|
|
13
|
+
MAX_FULLSCREEN_WHEEL_SCROLL_LINES,
|
|
14
|
+
Math.max(MIN_FULLSCREEN_WHEEL_SCROLL_LINES, Math.floor(value)),
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type FullscreenWheelTui = TUI & { wheelScrollLines?: unknown };
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Pi 0.84.2 keeps this constructor option in a private runtime field. Remove
|
|
22
|
+
* this shim once Pi exposes a public setter; incompatible versions are no-ops.
|
|
23
|
+
*/
|
|
24
|
+
export function applyFullscreenWheelScrollLines(tui: TUI, value: number): boolean {
|
|
25
|
+
try {
|
|
26
|
+
const fullscreenTui = tui as FullscreenWheelTui;
|
|
27
|
+
if (fullscreenTui.mode !== "fullscreen" || typeof fullscreenTui.wheelScrollLines !== "number") {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return Reflect.set(fullscreenTui, "wheelScrollLines", normalizeFullscreenWheelScrollLines(value));
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -87,7 +87,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
87
87
|
},
|
|
88
88
|
},
|
|
89
89
|
);
|
|
90
|
-
editor = installEditor(pi, ctx, config.cursorStyle);
|
|
90
|
+
editor = installEditor(pi, ctx, config.cursorStyle, config.fullscreen.wheelScrollLines);
|
|
91
91
|
active = true;
|
|
92
92
|
}
|
|
93
93
|
};
|
|
@@ -273,11 +273,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
273
273
|
getConfig: () => config,
|
|
274
274
|
onConfigChanged: (newConfig) => {
|
|
275
275
|
const cursorStyleChanged = config.cursorStyle !== newConfig.cursorStyle;
|
|
276
|
+
const wheelScrollLinesChanged = config.fullscreen.wheelScrollLines !== newConfig.fullscreen.wheelScrollLines;
|
|
276
277
|
saveConfig(newConfig);
|
|
277
278
|
config = newConfig;
|
|
278
279
|
if (cursorStyleChanged && active && editor) {
|
|
279
280
|
editor.setCursorStyle(newConfig.cursorStyle);
|
|
280
281
|
}
|
|
282
|
+
if (wheelScrollLinesChanged && active && editor) {
|
|
283
|
+
editor.setWheelScrollLines(newConfig.fullscreen.wheelScrollLines);
|
|
284
|
+
}
|
|
281
285
|
if (lastCtx) {
|
|
282
286
|
pendingUiChange = getPendingUiChange(newConfig.enabled, active);
|
|
283
287
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import {
|
|
3
3
|
Box,
|
|
4
|
+
Input,
|
|
4
5
|
Key,
|
|
5
6
|
matchesKey,
|
|
6
7
|
SelectList,
|
|
@@ -9,6 +10,10 @@ import {
|
|
|
9
10
|
Text,
|
|
10
11
|
} from "@earendil-works/pi-tui";
|
|
11
12
|
import type { CursorStyle, IconMode, OpenTuiConfig, SettingsLanguage } from "./config.ts";
|
|
13
|
+
import {
|
|
14
|
+
DEFAULT_FULLSCREEN_WHEEL_SCROLL_LINES,
|
|
15
|
+
normalizeFullscreenWheelScrollLines,
|
|
16
|
+
} from "./fullscreen-scroll.ts";
|
|
12
17
|
|
|
13
18
|
interface SettingItem {
|
|
14
19
|
id: string;
|
|
@@ -24,10 +29,11 @@ const COPY = {
|
|
|
24
29
|
en: {
|
|
25
30
|
title: "Open TUI Settings",
|
|
26
31
|
tabs: { features: "General", icons: "Appearance", segments: "Footer", telemetry: "Telemetry" },
|
|
27
|
-
hint: "Tab/Shift+Tab/←/→: tabs · ↑/↓: move · Enter/Space: change · Esc/q: close",
|
|
32
|
+
hint: "Tab/Shift+Tab/←/→: tabs · ↑/↓: move · Enter/Space: change · Enter on wheel speed: type 1-10 · Esc/q: close",
|
|
28
33
|
labels: {
|
|
29
34
|
enabled: "Enabled",
|
|
30
35
|
language: "Language",
|
|
36
|
+
wheelScrollLines: "Mouse wheel speed",
|
|
31
37
|
cursorStyle: "Cursor style",
|
|
32
38
|
iconMode: "Icon mode",
|
|
33
39
|
cwd: "CWD",
|
|
@@ -49,6 +55,8 @@ const COPY = {
|
|
|
49
55
|
on: "On",
|
|
50
56
|
off: "Off",
|
|
51
57
|
languages: { en: "English", zh: "简体中文" },
|
|
58
|
+
wheelLines: (count: number) => `${count} ${count === 1 ? "line" : "lines"} / notch`,
|
|
59
|
+
wheelPrompt: (count: number) => `Wheel scroll lines per notch, 1-10 (current: ${count}). Enter: apply · Esc: cancel`,
|
|
52
60
|
cursorStyles: { block: "Block", bar: "Bar", underline: "Underline" },
|
|
53
61
|
icons: { auto: "Auto", nerd: "Nerd", ascii: "ASCII" },
|
|
54
62
|
},
|
|
@@ -56,10 +64,11 @@ const COPY = {
|
|
|
56
64
|
zh: {
|
|
57
65
|
title: "Open TUI 设置",
|
|
58
66
|
tabs: { features: "常规", icons: "外观", segments: "Footer", telemetry: "遥测" },
|
|
59
|
-
hint: "Tab/Shift+Tab/←/→:切页 · ↑/↓:移动 · Enter/Space:更改 · Esc/q:关闭",
|
|
67
|
+
hint: "Tab/Shift+Tab/←/→:切页 · ↑/↓:移动 · Enter/Space:更改 · 滚轮速度项 Enter 输入 1-10 · Esc/q:关闭",
|
|
60
68
|
labels: {
|
|
61
69
|
enabled: "启用",
|
|
62
70
|
language: "语言",
|
|
71
|
+
wheelScrollLines: "鼠标滚轮速度",
|
|
63
72
|
cursorStyle: "光标样式",
|
|
64
73
|
iconMode: "图标模式",
|
|
65
74
|
cwd: "当前目录",
|
|
@@ -81,6 +90,8 @@ const COPY = {
|
|
|
81
90
|
on: "开启",
|
|
82
91
|
off: "关闭",
|
|
83
92
|
languages: { en: "English", zh: "简体中文" },
|
|
93
|
+
wheelLines: (count: number) => `每格 ${count} 行`,
|
|
94
|
+
wheelPrompt: (count: number) => `滚轮每格滚动行数(当前 ${count},范围 1-10),输入后 Enter 应用 · Esc 取消`,
|
|
84
95
|
cursorStyles: { block: "块", bar: "竖线", underline: "下划线" },
|
|
85
96
|
icons: { auto: "自动", nerd: "Nerd", ascii: "ASCII" },
|
|
86
97
|
},
|
|
@@ -121,6 +132,19 @@ function cycleCursorStyle(config: OpenTuiConfig): OpenTuiConfig {
|
|
|
121
132
|
return { ...config, cursorStyle: next };
|
|
122
133
|
}
|
|
123
134
|
|
|
135
|
+
function setWheelScrollLines(config: OpenTuiConfig, raw: string): OpenTuiConfig | undefined {
|
|
136
|
+
if (!/^\d+$/.test(raw)) return undefined;
|
|
137
|
+
const parsed = Number(raw);
|
|
138
|
+
const bounded = Number.isFinite(parsed) ? parsed : Number.MAX_SAFE_INTEGER;
|
|
139
|
+
return {
|
|
140
|
+
...config,
|
|
141
|
+
fullscreen: {
|
|
142
|
+
...config.fullscreen,
|
|
143
|
+
wheelScrollLines: normalizeFullscreenWheelScrollLines(bounded, DEFAULT_FULLSCREEN_WHEEL_SCROLL_LINES),
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
124
148
|
function toggleTelemetry(config: OpenTuiConfig, key: keyof OpenTuiConfig["telemetry"]): OpenTuiConfig {
|
|
125
149
|
return {
|
|
126
150
|
...config,
|
|
@@ -132,6 +156,11 @@ function buildFeaturesItems(config: OpenTuiConfig, copy: SettingsCopy): SettingI
|
|
|
132
156
|
return [
|
|
133
157
|
{ id: "enabled", label: copy.labels.enabled, currentValue: config.enabled ? copy.values.on : copy.values.off },
|
|
134
158
|
{ id: "settingsLanguage", label: copy.labels.language, currentValue: copy.values.languages[config.settingsLanguage] },
|
|
159
|
+
{
|
|
160
|
+
id: "wheelScrollLines",
|
|
161
|
+
label: copy.labels.wheelScrollLines,
|
|
162
|
+
currentValue: copy.values.wheelLines(config.fullscreen.wheelScrollLines),
|
|
163
|
+
},
|
|
135
164
|
];
|
|
136
165
|
}
|
|
137
166
|
|
|
@@ -223,6 +252,7 @@ class SettingsUi implements SettingsUiHandle {
|
|
|
223
252
|
private cachedWidth: number | undefined;
|
|
224
253
|
private cachedLines: string[] | undefined;
|
|
225
254
|
private compact = false;
|
|
255
|
+
private wheelInput: Input | undefined;
|
|
226
256
|
|
|
227
257
|
constructor(
|
|
228
258
|
theme: Theme,
|
|
@@ -247,11 +277,35 @@ class SettingsUi implements SettingsUiHandle {
|
|
|
247
277
|
|
|
248
278
|
private applySetting(itemId: string): void {
|
|
249
279
|
this.selectedItemByTab[this.tab] = itemId;
|
|
280
|
+
if (this.tab === "features" && itemId === "wheelScrollLines") {
|
|
281
|
+
this.openWheelInput();
|
|
282
|
+
this.invalidate();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
250
285
|
this.config = handleSettingChange(this.tab, itemId, this.config);
|
|
251
286
|
this.onChange(this.config);
|
|
252
287
|
this.rebuild(itemId);
|
|
253
288
|
}
|
|
254
289
|
|
|
290
|
+
private openWheelInput(): void {
|
|
291
|
+
const input = new Input();
|
|
292
|
+
input.onSubmit = (value) => {
|
|
293
|
+
const next = setWheelScrollLines(this.config, value);
|
|
294
|
+
this.wheelInput = undefined;
|
|
295
|
+
if (next) {
|
|
296
|
+
this.config = next;
|
|
297
|
+
this.onChange(this.config);
|
|
298
|
+
}
|
|
299
|
+
this.rebuild("wheelScrollLines");
|
|
300
|
+
};
|
|
301
|
+
input.onEscape = () => {
|
|
302
|
+
this.wheelInput = undefined;
|
|
303
|
+
this.rebuild("wheelScrollLines");
|
|
304
|
+
};
|
|
305
|
+
this.wheelInput = input;
|
|
306
|
+
this.rebuild("wheelScrollLines");
|
|
307
|
+
}
|
|
308
|
+
|
|
255
309
|
private switchTab(offset: number): void {
|
|
256
310
|
const idx = TABS.indexOf(this.tab);
|
|
257
311
|
this.tab = TABS[(idx + offset + TABS.length) % TABS.length]!;
|
|
@@ -271,11 +325,17 @@ class SettingsUi implements SettingsUiHandle {
|
|
|
271
325
|
this.container.addChild(new Text(tabBar, 1, 0));
|
|
272
326
|
this.container.addChild(new Text(this.theme.fg("dim", copy.hint), 1, 0));
|
|
273
327
|
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
328
|
+
const editingWheel = this.tab === "features" && this.wheelInput !== undefined;
|
|
329
|
+
const items = buildItems(this.tab, this.config).map((item) => {
|
|
330
|
+
const editing = editingWheel && item.id === "wheelScrollLines";
|
|
331
|
+
return {
|
|
332
|
+
value: item.id,
|
|
333
|
+
label: editing
|
|
334
|
+
? (this.compact ? `${item.label}:` : item.label)
|
|
335
|
+
: (this.compact ? `${item.label}: ${item.currentValue}` : item.label),
|
|
336
|
+
description: editing || this.compact ? undefined : item.currentValue,
|
|
337
|
+
} as SelectItem;
|
|
338
|
+
});
|
|
279
339
|
this.selectList = new SelectList(items, Math.min(items.length, 10), {
|
|
280
340
|
selectedPrefix: (t) => this.theme.fg("accent", t),
|
|
281
341
|
selectedText: (t) => this.theme.fg("accent", t),
|
|
@@ -298,12 +358,28 @@ class SettingsUi implements SettingsUiHandle {
|
|
|
298
358
|
this.onClose();
|
|
299
359
|
};
|
|
300
360
|
this.container.addChild(this.selectList);
|
|
361
|
+
if (editingWheel) {
|
|
362
|
+
this.wheelInput!.focused = true;
|
|
363
|
+
const wheelInputGroup = new Box(4, 0);
|
|
364
|
+
wheelInputGroup.addChild(new Text(
|
|
365
|
+
this.theme.fg("muted", copy.values.wheelPrompt(this.config.fullscreen.wheelScrollLines)),
|
|
366
|
+
0,
|
|
367
|
+
0,
|
|
368
|
+
));
|
|
369
|
+
wheelInputGroup.addChild(this.wheelInput!);
|
|
370
|
+
this.container.addChild(wheelInputGroup);
|
|
371
|
+
}
|
|
301
372
|
|
|
302
373
|
this.cachedWidth = undefined;
|
|
303
374
|
this.cachedLines = undefined;
|
|
304
375
|
}
|
|
305
376
|
|
|
306
377
|
handleInput(data: string): void {
|
|
378
|
+
if (this.wheelInput && this.tab === "features") {
|
|
379
|
+
this.wheelInput.handleInput(data);
|
|
380
|
+
this.invalidate();
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
307
383
|
if (matchesKey(data, Key.tab) || matchesKey(data, Key.right)) {
|
|
308
384
|
this.switchTab(1);
|
|
309
385
|
this.invalidate();
|
|
@@ -3,7 +3,7 @@ import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
|
3
3
|
import type { GitStatus } from "./git.ts";
|
|
4
4
|
import { emptyGitStatus } from "./git.ts";
|
|
5
5
|
import type { RuntimeInfo } from "./runtime.ts";
|
|
6
|
-
import { fmtTokens, formatProviderLabel } from "./utils.ts";
|
|
6
|
+
import { finiteOrZero, fmtTokens, formatProviderLabel } from "./utils.ts";
|
|
7
7
|
|
|
8
8
|
export interface FooterState {
|
|
9
9
|
git: GitStatus;
|
|
@@ -43,14 +43,17 @@ export function getUsageTotals(ctx: ExtensionContext): UsageTotals {
|
|
|
43
43
|
const m = entry.message as AssistantMessage;
|
|
44
44
|
const u = m.usage;
|
|
45
45
|
if (!u) continue;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
totals.
|
|
50
|
-
totals.
|
|
51
|
-
|
|
46
|
+
const input = finiteOrZero(u.input);
|
|
47
|
+
const cacheRead = finiteOrZero(u.cacheRead);
|
|
48
|
+
const cacheWrite = finiteOrZero(u.cacheWrite);
|
|
49
|
+
totals.input += input;
|
|
50
|
+
totals.output += finiteOrZero(u.output);
|
|
51
|
+
totals.cacheRead += cacheRead;
|
|
52
|
+
totals.cacheWrite += cacheWrite;
|
|
53
|
+
totals.cost += finiteOrZero(u.cost?.total);
|
|
54
|
+
const promptTokens = input + cacheRead + cacheWrite;
|
|
52
55
|
if (promptTokens > 0) {
|
|
53
|
-
totals.latestCacheHitRate = (
|
|
56
|
+
totals.latestCacheHitRate = (cacheRead / promptTokens) * 100;
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
}
|