pi-tui-footer 1.2.5 → 1.2.6
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/extensions/config.ts +6 -22
- package/extensions/editor.ts +0 -11
- package/extensions/footer.ts +16 -3
- package/extensions/index.ts +1 -6
- package/extensions/rounded-tools.ts +1 -1
- package/extensions/settings-command.ts +8 -103
- package/extensions/utils.ts +22 -0
- package/package.json +1 -1
- package/extensions/fullscreen-scroll.ts +0 -34
package/extensions/config.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import {
|
|
5
|
-
DEFAULT_FULLSCREEN_WHEEL_SCROLL_LINES,
|
|
6
|
-
normalizeFullscreenWheelScrollLines,
|
|
7
|
-
} from "./fullscreen-scroll.ts";
|
|
8
4
|
import type { IconMode } from "./icons.ts";
|
|
9
5
|
|
|
10
6
|
export type SettingsLanguage = "en" | "zh";
|
|
@@ -42,17 +38,12 @@ export interface ThinkingPeekConfig {
|
|
|
42
38
|
lines: ThinkingPeekLines;
|
|
43
39
|
}
|
|
44
40
|
|
|
45
|
-
export interface FullscreenConfig {
|
|
46
|
-
wheelScrollLines: number;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
41
|
export interface OpenTuiConfig {
|
|
50
42
|
enabled: boolean;
|
|
51
43
|
roundedTools: boolean;
|
|
52
44
|
inlineFooter: boolean;
|
|
53
45
|
settingsLanguage: SettingsLanguage;
|
|
54
46
|
cursorStyle: CursorStyle;
|
|
55
|
-
fullscreen: FullscreenConfig;
|
|
56
47
|
icons: {
|
|
57
48
|
mode: IconMode;
|
|
58
49
|
};
|
|
@@ -67,9 +58,6 @@ export const DEFAULT_CONFIG: OpenTuiConfig = {
|
|
|
67
58
|
inlineFooter: false,
|
|
68
59
|
settingsLanguage: "en",
|
|
69
60
|
cursorStyle: "block",
|
|
70
|
-
fullscreen: {
|
|
71
|
-
wheelScrollLines: DEFAULT_FULLSCREEN_WHEEL_SCROLL_LINES,
|
|
72
|
-
},
|
|
73
61
|
icons: {
|
|
74
62
|
mode: "auto",
|
|
75
63
|
},
|
|
@@ -155,27 +143,23 @@ export function loadConfig(notify?: (msg: string, level: "warning" | "info") =>
|
|
|
155
143
|
const raw = readFileSync(path, "utf8");
|
|
156
144
|
const parsed: unknown = JSON.parse(raw);
|
|
157
145
|
const config = deepMerge(DEFAULT_CONFIG, parsed);
|
|
146
|
+
if (typeof config.inlineFooter !== "boolean") {
|
|
147
|
+
config.inlineFooter = DEFAULT_CONFIG.inlineFooter;
|
|
148
|
+
}
|
|
149
|
+
if (typeof config.roundedTools !== "boolean") {
|
|
150
|
+
config.roundedTools = DEFAULT_CONFIG.roundedTools;
|
|
151
|
+
}
|
|
158
152
|
if (config.settingsLanguage !== "en" && config.settingsLanguage !== "zh") {
|
|
159
153
|
config.settingsLanguage = DEFAULT_CONFIG.settingsLanguage;
|
|
160
154
|
}
|
|
161
155
|
if (config.cursorStyle !== "block" && config.cursorStyle !== "bar" && config.cursorStyle !== "underline") {
|
|
162
156
|
config.cursorStyle = DEFAULT_CONFIG.cursorStyle;
|
|
163
157
|
}
|
|
164
|
-
config.fullscreen.wheelScrollLines = normalizeFullscreenWheelScrollLines(
|
|
165
|
-
config.fullscreen.wheelScrollLines,
|
|
166
|
-
DEFAULT_CONFIG.fullscreen.wheelScrollLines,
|
|
167
|
-
);
|
|
168
158
|
if (typeof config.thinkingPeek !== "object" || config.thinkingPeek === null || Array.isArray(config.thinkingPeek)) {
|
|
169
159
|
config.thinkingPeek = structuredClone(DEFAULT_CONFIG.thinkingPeek);
|
|
170
160
|
} else {
|
|
171
161
|
config.thinkingPeek.lines = normalizeThinkingPeekLines(config.thinkingPeek.lines);
|
|
172
162
|
}
|
|
173
|
-
if (typeof config.inlineFooter !== "boolean") {
|
|
174
|
-
config.inlineFooter = DEFAULT_CONFIG.inlineFooter;
|
|
175
|
-
}
|
|
176
|
-
if (typeof config.roundedTools !== "boolean") {
|
|
177
|
-
config.roundedTools = DEFAULT_CONFIG.roundedTools;
|
|
178
|
-
}
|
|
179
163
|
return config;
|
|
180
164
|
} catch (err) {
|
|
181
165
|
notify?.(`pi-tui config parse error: ${err instanceof Error ? err.message : String(err)}`, "warning");
|
package/extensions/editor.ts
CHANGED
|
@@ -7,10 +7,6 @@ import {
|
|
|
7
7
|
import type { EditorTheme, TuiMouseEvent, TuiMouseEventResult, TUI } from "@earendil-works/pi-tui";
|
|
8
8
|
import { CURSOR_MARKER, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
9
9
|
import type { CursorStyle } from "./config.ts";
|
|
10
|
-
import {
|
|
11
|
-
applyFullscreenWheelScrollLines,
|
|
12
|
-
DEFAULT_FULLSCREEN_WHEEL_SCROLL_LINES,
|
|
13
|
-
} from "./fullscreen-scroll.ts";
|
|
14
10
|
import { findBottomBorderIndex, isEditorBorderLine, stripAnsi } from "./utils.ts";
|
|
15
11
|
|
|
16
12
|
function fillLine(content: string, width: number): string {
|
|
@@ -316,14 +312,12 @@ export function installEditor(
|
|
|
316
312
|
_pi: ExtensionAPI,
|
|
317
313
|
ctx: ExtensionContext,
|
|
318
314
|
cursorStyle: CursorStyle = "block",
|
|
319
|
-
wheelScrollLines = DEFAULT_FULLSCREEN_WHEEL_SCROLL_LINES,
|
|
320
315
|
inlineFooter?: InlineFooterRenderer,
|
|
321
316
|
) {
|
|
322
317
|
let activeTui: TUI | undefined;
|
|
323
318
|
let activeEditor: OpenTuiEditor | undefined;
|
|
324
319
|
let previousHardwareCursor: boolean | undefined;
|
|
325
320
|
let currentCursorStyle = cursorStyle;
|
|
326
|
-
let currentWheelScrollLines = wheelScrollLines;
|
|
327
321
|
let hiddenThinkingTarget: HiddenThinkingLabelComponent | undefined;
|
|
328
322
|
const getActiveTui = (): TUI => {
|
|
329
323
|
if (!activeTui) throw new Error("Open TUI editor is not mounted");
|
|
@@ -333,7 +327,6 @@ export function installEditor(
|
|
|
333
327
|
ctx.ui.setEditorComponent((tui, editorTheme, keybindings) => {
|
|
334
328
|
activeTui = tui;
|
|
335
329
|
hiddenThinkingTarget = undefined;
|
|
336
|
-
applyFullscreenWheelScrollLines(tui, currentWheelScrollLines);
|
|
337
330
|
previousHardwareCursor = tui.getShowHardwareCursor();
|
|
338
331
|
activeEditor = new OpenTuiEditor(tui, editorTheme, keybindings, currentCursorStyle, inlineFooter);
|
|
339
332
|
return activeEditor;
|
|
@@ -359,10 +352,6 @@ export function installEditor(
|
|
|
359
352
|
currentCursorStyle = nextCursorStyle;
|
|
360
353
|
activeEditor?.setCursorStyle(nextCursorStyle, previousHardwareCursor);
|
|
361
354
|
},
|
|
362
|
-
setWheelScrollLines(nextWheelScrollLines: number): void {
|
|
363
|
-
currentWheelScrollLines = nextWheelScrollLines;
|
|
364
|
-
if (activeTui) applyFullscreenWheelScrollLines(activeTui, currentWheelScrollLines);
|
|
365
|
-
},
|
|
366
355
|
cleanup(): void {
|
|
367
356
|
hiddenThinkingTarget = undefined;
|
|
368
357
|
ctx.ui.setEditorComponent(undefined);
|
package/extensions/footer.ts
CHANGED
|
@@ -16,8 +16,9 @@ import {
|
|
|
16
16
|
formatCwd,
|
|
17
17
|
formatDuration,
|
|
18
18
|
formatInputBreakdown,
|
|
19
|
+
hasAnsiStyles,
|
|
19
20
|
providerColor,
|
|
20
|
-
|
|
21
|
+
sanitizeStatusPreservingStyles,
|
|
21
22
|
stressColor,
|
|
22
23
|
truncateBranch,
|
|
23
24
|
truncatePath,
|
|
@@ -181,6 +182,18 @@ function renderStatsBlock(
|
|
|
181
182
|
return stats.join(` ${theme.fg("dim", "|")} `);
|
|
182
183
|
}
|
|
183
184
|
|
|
185
|
+
const ANSI_RESET = "\x1b[0m";
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Keep the colours an extension applied to its own status text. Plain text stays
|
|
189
|
+
* muted so unstyled statuses keep the quiet footer look. The trailing reset
|
|
190
|
+
* stops an unclosed style from tinting the separator and the next status.
|
|
191
|
+
*/
|
|
192
|
+
function renderStatusText(theme: Theme, status: string): string {
|
|
193
|
+
if (!hasAnsiStyles(status)) return theme.fg("muted", status);
|
|
194
|
+
return `${status}${ANSI_RESET}`;
|
|
195
|
+
}
|
|
196
|
+
|
|
184
197
|
function renderExtensionStatusLines(
|
|
185
198
|
theme: Theme,
|
|
186
199
|
extensionStatuses: ReadonlyMap<string, string>,
|
|
@@ -189,12 +202,12 @@ function renderExtensionStatusLines(
|
|
|
189
202
|
): string[] {
|
|
190
203
|
const statuses = Array.from(extensionStatuses.entries())
|
|
191
204
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
192
|
-
.map(([, text]) =>
|
|
205
|
+
.map(([, text]) => sanitizeStatusPreservingStyles(text))
|
|
193
206
|
.filter((text) => text.length > 0);
|
|
194
207
|
if (statuses.length === 0) return [];
|
|
195
208
|
|
|
196
209
|
const separator = ` ${theme.fg("dim", "|")} `;
|
|
197
|
-
const statusText = statuses.map((status) => theme
|
|
210
|
+
const statusText = statuses.map((status) => renderStatusText(theme, status)).join(separator);
|
|
198
211
|
const line = `${theme.fg("mdLink", glyphs.extensions)} ${statusText}`;
|
|
199
212
|
return wrapTextWithAnsi(line, width);
|
|
200
213
|
}
|
package/extensions/index.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* thinking peek preview, rounded tool frames, and turn telemetry.
|
|
5
5
|
*
|
|
6
6
|
* Integrated from:
|
|
7
|
-
* - pi-open-tui (v0.3.
|
|
7
|
+
* - pi-open-tui (v0.3.11, MIT, by OldSun, https://github.com/OldSuns/pi-open-tui)
|
|
8
8
|
* - pi-rounded-tools (v0.1.3, MIT, by OrionPax, https://github.com/orionpax1997/pi-rounded-tools)
|
|
9
9
|
*
|
|
10
10
|
* Security audit notes:
|
|
@@ -169,7 +169,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
169
169
|
pi,
|
|
170
170
|
ctx,
|
|
171
171
|
config.cursorStyle,
|
|
172
|
-
config.fullscreen.wheelScrollLines,
|
|
173
172
|
{
|
|
174
173
|
enabled: () => config.inlineFooter,
|
|
175
174
|
render: footer.renderInline,
|
|
@@ -423,7 +422,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
423
422
|
const wasEnabled = config.enabled;
|
|
424
423
|
const wasRoundedTools = config.roundedTools;
|
|
425
424
|
const cursorStyleChanged = config.cursorStyle !== newConfig.cursorStyle;
|
|
426
|
-
const wheelScrollLinesChanged = config.fullscreen.wheelScrollLines !== newConfig.fullscreen.wheelScrollLines;
|
|
427
425
|
const thinkingPeekLinesChanged = config.thinkingPeek.lines !== newConfig.thinkingPeek.lines;
|
|
428
426
|
saveConfig(newConfig);
|
|
429
427
|
config = newConfig;
|
|
@@ -436,9 +434,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
436
434
|
if (cursorStyleChanged && active && editor) {
|
|
437
435
|
editor.setCursorStyle(newConfig.cursorStyle);
|
|
438
436
|
}
|
|
439
|
-
if (wheelScrollLinesChanged && active && editor) {
|
|
440
|
-
editor.setWheelScrollLines(newConfig.fullscreen.wheelScrollLines);
|
|
441
|
-
}
|
|
442
437
|
if (lastCtx) {
|
|
443
438
|
pendingUiChange = getPendingUiChange(newConfig.enabled, active);
|
|
444
439
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Merged 2026-08 from the standalone local extension rounded-tools.ts (a
|
|
5
5
|
* localized copy of npm:pi-rounded-tools@0.1.3, MIT, by OrionPax,
|
|
6
6
|
* https://github.com/orionpax1997/pi-rounded-tools), integrated into pi-tui
|
|
7
|
-
* (based on https://github.com/OldSuns/pi-open-tui v0.3.
|
|
7
|
+
* (based on https://github.com/OldSuns/pi-open-tui v0.3.11, MIT, by
|
|
8
8
|
* OldSun) and gated behind `roundedTools` in pi-tui config
|
|
9
9
|
* (~/.pi/agent/pi-tui.json, default on).
|
|
10
10
|
*
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import {
|
|
3
3
|
Box,
|
|
4
|
-
Input,
|
|
5
4
|
Key,
|
|
6
5
|
matchesKey,
|
|
7
6
|
SelectList,
|
|
@@ -11,10 +10,6 @@ import {
|
|
|
11
10
|
Text,
|
|
12
11
|
} from "@earendil-works/pi-tui";
|
|
13
12
|
import type { CursorStyle, IconMode, OpenTuiConfig, SettingsLanguage, ThinkingPeekLines } from "./config.ts";
|
|
14
|
-
import {
|
|
15
|
-
DEFAULT_FULLSCREEN_WHEEL_SCROLL_LINES,
|
|
16
|
-
normalizeFullscreenWheelScrollLines,
|
|
17
|
-
} from "./fullscreen-scroll.ts";
|
|
18
13
|
|
|
19
14
|
interface SettingItem {
|
|
20
15
|
id: string;
|
|
@@ -30,14 +25,13 @@ const COPY = {
|
|
|
30
25
|
en: {
|
|
31
26
|
title: "Pi TUI Settings",
|
|
32
27
|
tabs: { features: "General", icons: "Appearance", segments: "Footer", telemetry: "Telemetry" },
|
|
33
|
-
hint: "Tab/Shift+Tab/←/→: tabs · ↑/↓: move · Enter/Space: change ·
|
|
28
|
+
hint: "Tab/Shift+Tab/←/→: tabs · ↑/↓: move · Enter/Space: change · Esc/q: close",
|
|
34
29
|
labels: {
|
|
35
30
|
enabled: "Enabled",
|
|
36
31
|
roundedTools: "Rounded tool frames",
|
|
37
32
|
inlineFooter: "Inline footer",
|
|
38
33
|
thinkingPeek: "Thinking peek",
|
|
39
34
|
language: "Language",
|
|
40
|
-
wheelScrollLines: "Mouse wheel speed",
|
|
41
35
|
cursorStyle: "Cursor style",
|
|
42
36
|
iconMode: "Icon mode",
|
|
43
37
|
cwd: "CWD",
|
|
@@ -62,8 +56,6 @@ const COPY = {
|
|
|
62
56
|
off: "Off",
|
|
63
57
|
thinkingPeek: { off: "Off", one: "1 line", two: "2 lines" },
|
|
64
58
|
languages: { en: "English", zh: "简体中文" },
|
|
65
|
-
wheelLines: (count: number) => `${count} ${count === 1 ? "line" : "lines"} / notch`,
|
|
66
|
-
wheelPrompt: (count: number) => `Wheel scroll lines per notch, 1-10 (current: ${count}). Enter: apply · Esc: cancel`,
|
|
67
59
|
cursorStyles: { block: "Block", bar: "Bar", underline: "Underline" },
|
|
68
60
|
icons: { auto: "Auto", nerd: "Nerd", unicode: "Unicode", ascii: "ASCII" },
|
|
69
61
|
},
|
|
@@ -71,14 +63,13 @@ const COPY = {
|
|
|
71
63
|
zh: {
|
|
72
64
|
title: "Pi TUI 设置",
|
|
73
65
|
tabs: { features: "常规", icons: "外观", segments: "Footer", telemetry: "遥测" },
|
|
74
|
-
hint: "Tab/Shift+Tab/←/→:切页 · ↑/↓:移动 · Enter/Space:更改 ·
|
|
66
|
+
hint: "Tab/Shift+Tab/←/→:切页 · ↑/↓:移动 · Enter/Space:更改 · Esc/q:关闭",
|
|
75
67
|
labels: {
|
|
76
68
|
enabled: "启用",
|
|
77
69
|
roundedTools: "圆角工具边框",
|
|
78
70
|
inlineFooter: "内联底栏",
|
|
79
71
|
thinkingPeek: "思考预览",
|
|
80
72
|
language: "语言",
|
|
81
|
-
wheelScrollLines: "鼠标滚轮速度",
|
|
82
73
|
cursorStyle: "光标样式",
|
|
83
74
|
iconMode: "图标模式",
|
|
84
75
|
cwd: "当前目录",
|
|
@@ -103,8 +94,6 @@ const COPY = {
|
|
|
103
94
|
off: "关闭",
|
|
104
95
|
thinkingPeek: { off: "关闭", one: "单行", two: "双行" },
|
|
105
96
|
languages: { en: "English", zh: "简体中文" },
|
|
106
|
-
wheelLines: (count: number) => `每格 ${count} 行`,
|
|
107
|
-
wheelPrompt: (count: number) => `滚轮每格滚动行数(当前 ${count},范围 1-10),输入后 Enter 应用 · Esc 取消`,
|
|
108
97
|
cursorStyles: { block: "块", bar: "竖线", underline: "下划线" },
|
|
109
98
|
icons: { auto: "自动", nerd: "Nerd", unicode: "Unicode", ascii: "ASCII" },
|
|
110
99
|
},
|
|
@@ -150,19 +139,6 @@ function cycleCursorStyle(config: OpenTuiConfig): OpenTuiConfig {
|
|
|
150
139
|
return { ...config, cursorStyle: next };
|
|
151
140
|
}
|
|
152
141
|
|
|
153
|
-
function setWheelScrollLines(config: OpenTuiConfig, raw: string): OpenTuiConfig | undefined {
|
|
154
|
-
if (!/^\d+$/.test(raw)) return undefined;
|
|
155
|
-
const parsed = Number(raw);
|
|
156
|
-
const bounded = Number.isFinite(parsed) ? parsed : Number.MAX_SAFE_INTEGER;
|
|
157
|
-
return {
|
|
158
|
-
...config,
|
|
159
|
-
fullscreen: {
|
|
160
|
-
...config.fullscreen,
|
|
161
|
-
wheelScrollLines: normalizeFullscreenWheelScrollLines(bounded, DEFAULT_FULLSCREEN_WHEEL_SCROLL_LINES),
|
|
162
|
-
},
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
142
|
function toggleTelemetry(config: OpenTuiConfig, key: keyof OpenTuiConfig["telemetry"]): OpenTuiConfig {
|
|
167
143
|
return {
|
|
168
144
|
...config,
|
|
@@ -184,11 +160,6 @@ function buildFeaturesItems(config: OpenTuiConfig, copy: SettingsCopy): SettingI
|
|
|
184
160
|
{ id: "enabled", label: copy.labels.enabled, currentValue: flag(config.enabled) },
|
|
185
161
|
{ id: "roundedTools", label: copy.labels.roundedTools, currentValue: flag(config.roundedTools) },
|
|
186
162
|
{ id: "settingsLanguage", label: copy.labels.language, currentValue: copy.values.languages[config.settingsLanguage] },
|
|
187
|
-
{
|
|
188
|
-
id: "wheelScrollLines",
|
|
189
|
-
label: copy.labels.wheelScrollLines,
|
|
190
|
-
currentValue: copy.values.wheelLines(config.fullscreen.wheelScrollLines),
|
|
191
|
-
},
|
|
192
163
|
{ id: "thinkingPeek", label: copy.labels.thinkingPeek, currentValue: formatThinkingPeekLines(config.thinkingPeek.lines, copy) },
|
|
193
164
|
{ id: "inlineFooter", label: copy.labels.inlineFooter, currentValue: flag(config.inlineFooter) },
|
|
194
165
|
];
|
|
@@ -275,23 +246,6 @@ interface SettingsUiHandle {
|
|
|
275
246
|
handleInput: (data: string) => void;
|
|
276
247
|
}
|
|
277
248
|
|
|
278
|
-
function insertComponentAfter(list: Component, child: Component, index: () => number): Component {
|
|
279
|
-
// SelectList renders one line per item; splice the editor into that output.
|
|
280
|
-
return {
|
|
281
|
-
render(width: number): string[] {
|
|
282
|
-
const lines = list.render(width);
|
|
283
|
-
const insertAt = index();
|
|
284
|
-
if (insertAt < 0 || insertAt >= lines.length) return lines;
|
|
285
|
-
const childLines = child.render(width);
|
|
286
|
-
return [...lines.slice(0, insertAt + 1), ...childLines, ...lines.slice(insertAt + 1)];
|
|
287
|
-
},
|
|
288
|
-
invalidate(): void {
|
|
289
|
-
list.invalidate();
|
|
290
|
-
child.invalidate();
|
|
291
|
-
},
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
|
|
295
249
|
class SettingsUi implements SettingsUiHandle {
|
|
296
250
|
private tab: Tab = "features";
|
|
297
251
|
private config: OpenTuiConfig;
|
|
@@ -304,7 +258,6 @@ class SettingsUi implements SettingsUiHandle {
|
|
|
304
258
|
private cachedWidth: number | undefined;
|
|
305
259
|
private cachedLines: string[] | undefined;
|
|
306
260
|
private compact = false;
|
|
307
|
-
private wheelInput: Input | undefined;
|
|
308
261
|
|
|
309
262
|
constructor(
|
|
310
263
|
theme: Theme,
|
|
@@ -329,35 +282,11 @@ class SettingsUi implements SettingsUiHandle {
|
|
|
329
282
|
|
|
330
283
|
private applySetting(itemId: string): void {
|
|
331
284
|
this.selectedItemByTab[this.tab] = itemId;
|
|
332
|
-
if (this.tab === "features" && itemId === "wheelScrollLines") {
|
|
333
|
-
this.openWheelInput();
|
|
334
|
-
this.invalidate();
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
285
|
this.config = handleSettingChange(this.tab, itemId, this.config);
|
|
338
286
|
this.onChange(this.config);
|
|
339
287
|
this.rebuild(itemId);
|
|
340
288
|
}
|
|
341
289
|
|
|
342
|
-
private openWheelInput(): void {
|
|
343
|
-
const input = new Input();
|
|
344
|
-
input.onSubmit = (value) => {
|
|
345
|
-
const next = setWheelScrollLines(this.config, value);
|
|
346
|
-
this.wheelInput = undefined;
|
|
347
|
-
if (next) {
|
|
348
|
-
this.config = next;
|
|
349
|
-
this.onChange(this.config);
|
|
350
|
-
}
|
|
351
|
-
this.rebuild("wheelScrollLines");
|
|
352
|
-
};
|
|
353
|
-
input.onEscape = () => {
|
|
354
|
-
this.wheelInput = undefined;
|
|
355
|
-
this.rebuild("wheelScrollLines");
|
|
356
|
-
};
|
|
357
|
-
this.wheelInput = input;
|
|
358
|
-
this.rebuild("wheelScrollLines");
|
|
359
|
-
}
|
|
360
|
-
|
|
361
290
|
private switchTab(offset: number): void {
|
|
362
291
|
const idx = TABS.indexOf(this.tab);
|
|
363
292
|
this.tab = TABS[(idx + offset + TABS.length) % TABS.length]!;
|
|
@@ -377,17 +306,11 @@ class SettingsUi implements SettingsUiHandle {
|
|
|
377
306
|
this.container.addChild(new Text(tabBar, 1, 0));
|
|
378
307
|
this.container.addChild(new Text(this.theme.fg("dim", copy.hint), 1, 0));
|
|
379
308
|
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
label: editing
|
|
386
|
-
? (this.compact ? `${item.label}:` : item.label)
|
|
387
|
-
: (this.compact ? `${item.label}: ${item.currentValue}` : item.label),
|
|
388
|
-
description: editing || this.compact ? undefined : item.currentValue,
|
|
389
|
-
} as SelectItem;
|
|
390
|
-
});
|
|
309
|
+
const items = buildItems(this.tab, this.config).map((item) => ({
|
|
310
|
+
value: item.id,
|
|
311
|
+
label: this.compact ? `${item.label}: ${item.currentValue}` : item.label,
|
|
312
|
+
description: this.compact ? undefined : item.currentValue,
|
|
313
|
+
} as SelectItem));
|
|
391
314
|
this.selectList = new SelectList(items, Math.min(items.length, 12), {
|
|
392
315
|
selectedPrefix: (t) => this.theme.fg("accent", t),
|
|
393
316
|
selectedText: (t) => this.theme.fg("accent", t),
|
|
@@ -409,31 +332,13 @@ class SettingsUi implements SettingsUiHandle {
|
|
|
409
332
|
this.selectList.onCancel = () => {
|
|
410
333
|
this.onClose();
|
|
411
334
|
};
|
|
412
|
-
|
|
413
|
-
this.wheelInput!.focused = true;
|
|
414
|
-
const wheelInputGroup = new Box(4, 0);
|
|
415
|
-
wheelInputGroup.addChild(new Text(
|
|
416
|
-
this.theme.fg("muted", copy.values.wheelPrompt(this.config.fullscreen.wheelScrollLines)),
|
|
417
|
-
0,
|
|
418
|
-
0,
|
|
419
|
-
));
|
|
420
|
-
wheelInputGroup.addChild(this.wheelInput!);
|
|
421
|
-
const selectedIndex = () => items.findIndex((item) => item.value === this.selectList.getSelectedItem()?.value);
|
|
422
|
-
this.container.addChild(insertComponentAfter(this.selectList, wheelInputGroup, selectedIndex));
|
|
423
|
-
} else {
|
|
424
|
-
this.container.addChild(this.selectList);
|
|
425
|
-
}
|
|
335
|
+
this.container.addChild(this.selectList);
|
|
426
336
|
|
|
427
337
|
this.cachedWidth = undefined;
|
|
428
338
|
this.cachedLines = undefined;
|
|
429
339
|
}
|
|
430
340
|
|
|
431
341
|
handleInput(data: string): void {
|
|
432
|
-
if (this.wheelInput && this.tab === "features") {
|
|
433
|
-
this.wheelInput.handleInput(data);
|
|
434
|
-
this.invalidate();
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
342
|
if (matchesKey(data, Key.tab) || matchesKey(data, Key.right)) {
|
|
438
343
|
this.switchTab(1);
|
|
439
344
|
this.invalidate();
|
package/extensions/utils.ts
CHANGED
|
@@ -296,6 +296,28 @@ export function sanitizeStatus(text: string): string {
|
|
|
296
296
|
.trim();
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
+
// One status token is an SGR sequence, another escape sequence, or one control
|
|
300
|
+
// character. SGR carries colours. Everything else moves the cursor or rewrites
|
|
301
|
+
// the terminal, so it must never reach the footer row.
|
|
302
|
+
const STATUS_TOKEN = /(?:\x1b\[|\x9b)[0-?]*[ -/]*m|(?:\x1b\[|\x9b)[0-?]*[ -/]*[@-~]|(?:\x1bP|\x90|\x1bX|\x98|\x1b\^|\x9e|\x1b_|\x9f)[\s\S]*?(?:\x1b\\|\x9c|$)|(?:\x1b\]|\x9d)[\s\S]*?(?:\x07|\x1b\\|\x9c|$)|\x1b[@-Z\\_]|[\u0000-\u001f\u007f-\u009f]/g;
|
|
303
|
+
const SGR_TOKEN = /^(?:\x1b\[|\x9b)[0-9;:]*m$/;
|
|
304
|
+
const SGR_SEQUENCE = /(?:\x1b\[|\x9b)[0-9;:]*m/;
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Sanitize a status text the same way {@link sanitizeStatus} does, but keep the
|
|
308
|
+
* SGR sequences the extension set. Extensions colour their own status text with
|
|
309
|
+
* `ctx.ui.theme.fg()`. Stripping those codes renders every status grey.
|
|
310
|
+
*/
|
|
311
|
+
export function sanitizeStatusPreservingStyles(text: string): string {
|
|
312
|
+
const cleaned = text.replace(STATUS_TOKEN, (token) => (SGR_TOKEN.test(token) ? token : " "));
|
|
313
|
+
return cleaned.replace(/ {2,}/g, " ").trim();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** True when the text carries SGR colour or style sequences. */
|
|
317
|
+
export function hasAnsiStyles(text: string): boolean {
|
|
318
|
+
return SGR_SEQUENCE.test(text);
|
|
319
|
+
}
|
|
320
|
+
|
|
299
321
|
export function formatThinkingLabel(level: string): string {
|
|
300
322
|
if (level === "off") return "thinking off";
|
|
301
323
|
return `${level} effort`;
|
package/package.json
CHANGED
|
@@ -1,34 +0,0 @@
|
|
|
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
|
-
}
|