pi-pretty-tui 0.3.4 → 0.3.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/README.md +18 -2
- package/README.zh-CN.md +18 -2
- package/extensions/edit-diff.ts +6 -3
- package/extensions/index.ts +107 -33
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -58,9 +58,25 @@ pi --tui-mode fullscreen
|
|
|
58
58
|
|
|
59
59
|
Dragging selects text rather than copying a whole message. Links keep their normal behavior; image attachments are not included in text copies.
|
|
60
60
|
|
|
61
|
+
**Message copy** controls user-message click-to-copy and the final-answer `[Copy]` button.
|
|
62
|
+
**Code block copy** controls snippet `[Copy]` buttons independently.
|
|
63
|
+
Both are on by default. Open `/pretty-tui` → **Copy settings** to configure message, code-block and selection copying, or use:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
/pretty-tui message-copy off
|
|
67
|
+
/pretty-tui message-copy on
|
|
68
|
+
/pretty-tui code-block-copy off
|
|
69
|
+
/pretty-tui code-block-copy on
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Toggling either option saves the setting and reloads Pi.
|
|
73
|
+
The `messageCopy` and `codeBlockCopy` booleans are stored in `pretty-tui.json` in Pi's agent directory, alongside `selectionCopy`.
|
|
74
|
+
Disabling message copying keeps the response duration and status; disabling code-block copying keeps code frames, highlighting and content.
|
|
75
|
+
Neither option changes selection copying.
|
|
76
|
+
|
|
61
77
|
### Optional border-free selection
|
|
62
78
|
|
|
63
|
-
Enable **Selection copy** in `/pretty-tui
|
|
79
|
+
Enable **Selection copy** in `/pretty-tui` → **Copy settings**, or run:
|
|
64
80
|
|
|
65
81
|
```text
|
|
66
82
|
/pretty-tui selection-copy on
|
|
@@ -73,7 +89,7 @@ In regular mode, the terminal handles selection and copying, so this option has
|
|
|
73
89
|
|
|
74
90
|
## Notes
|
|
75
91
|
|
|
76
|
-
- Tested with **Pi 1.5.
|
|
92
|
+
- Tested with **Pi 1.5.1**. Future Pi updates may require compatibility fixes.
|
|
77
93
|
- Keeps Pi's native input behavior and tool execution, including third-party tool implementations.
|
|
78
94
|
- Nested-tool text details are saved with sessions for restoration and may be truncated. Session files can contain sensitive tool output.
|
|
79
95
|
- See [Releases](https://github.com/ykn0309/pi-pretty-tui/releases) for changes and contributor acknowledgments.
|
package/README.zh-CN.md
CHANGED
|
@@ -58,9 +58,25 @@ pi --tui-mode fullscreen
|
|
|
58
58
|
|
|
59
59
|
拖动鼠标按选区处理,不触发整条消息复制。链接保留正常行为;文本复制不包含图片附件。
|
|
60
60
|
|
|
61
|
+
**Message copy** 控制用户消息点击复制和最终回答的 `[Copy]` 按钮。
|
|
62
|
+
**Code block copy** 独立控制代码块的 `[Copy]` 按钮。
|
|
63
|
+
两项均默认开启。在 `/pretty-tui` → **Copy settings** 子菜单中统一配置消息、代码块和选区复制,也可运行:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
/pretty-tui message-copy off
|
|
67
|
+
/pretty-tui message-copy on
|
|
68
|
+
/pretty-tui code-block-copy off
|
|
69
|
+
/pretty-tui code-block-copy on
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
切换任一选项会保存设置并自动重载 Pi。
|
|
73
|
+
布尔值 `messageCopy` 和 `codeBlockCopy` 与 `selectionCopy` 一起保存在 Pi agent 目录下的 `pretty-tui.json` 中。
|
|
74
|
+
关闭消息复制仍保留回答耗时和状态;关闭代码块复制仍保留边框、语法高亮和内容。
|
|
75
|
+
两项均不影响选区复制。
|
|
76
|
+
|
|
61
77
|
### 可选的无边框选区复制
|
|
62
78
|
|
|
63
|
-
在 `/pretty-tui` 中切换 **Selection copy**,或运行:
|
|
79
|
+
在 `/pretty-tui` → **Copy settings** 中切换 **Selection copy**,或运行:
|
|
64
80
|
|
|
65
81
|
```text
|
|
66
82
|
/pretty-tui selection-copy on
|
|
@@ -73,7 +89,7 @@ regular 非全屏模式由终端处理选择与复制,开启此选项也不会
|
|
|
73
89
|
|
|
74
90
|
## 必要说明
|
|
75
91
|
|
|
76
|
-
- 当前已在 **Pi 1.5.
|
|
92
|
+
- 当前已在 **Pi 1.5.1** 上测试;未来 Pi 更新可能需要适配。
|
|
77
93
|
- 保留 Pi 原生输入行为和工具执行逻辑,包括第三方工具实现。
|
|
78
94
|
- 嵌套工具的文本详情会随会话保存以便恢复,内容可能截断;会话文件可能包含工具输出中的敏感信息。
|
|
79
95
|
- 版本变更与贡献者致谢见 [Releases](https://github.com/ykn0309/pi-pretty-tui/releases)。
|
package/extensions/edit-diff.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getLanguageFromPath, highlightCode } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { sliceByColumn, visibleWidth } from "@earendil-works/pi-tui";
|
|
2
|
+
import { mixColors, sliceByColumn, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
3
|
|
|
4
4
|
type Range = [number, number];
|
|
5
5
|
type Row = { marker: string; number?: string; text: string; styled: string; changes: Range[] };
|
|
@@ -82,13 +82,16 @@ export function editDiffLayout(theme: any, diff: string, path?: string) {
|
|
|
82
82
|
const color = row.marker === "+" ? "toolDiffAdded" : row.marker === "-" ? "toolDiffRemoved" : "toolDiffContext";
|
|
83
83
|
const background = row.marker === "+" ? "toolSuccessBg" : row.marker === "-" ? "toolErrorBg" : undefined;
|
|
84
84
|
const paint = (text: string) => background && theme.bg ? theme.bg(background, text) : text;
|
|
85
|
+
const changeBackground = background && theme.colors?.[background] && theme.colors?.[color]
|
|
86
|
+
? mixColors(theme.colors[background], theme.colors[color], 0.3) : undefined;
|
|
85
87
|
const width = visibleWidth(row.text);
|
|
86
88
|
let content = "", cursor = 0;
|
|
87
89
|
for (const [start, end] of row.changes) {
|
|
88
90
|
content += sliceByColumn(row.styled, cursor, start - cursor);
|
|
89
91
|
const changed = sliceByColumn(row.styled, start, end - start);
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
+
// A stronger theme-derived background preserves syntax foregrounds and
|
|
93
|
+
// avoids adding font emphasis to the changed graphemes.
|
|
94
|
+
content += changeBackground && theme.style ? theme.style(changed, { bg: changeBackground }) : paint(changed);
|
|
92
95
|
cursor = end;
|
|
93
96
|
}
|
|
94
97
|
content += sliceByColumn(row.styled, cursor, Math.max(0, width - cursor));
|
package/extensions/index.ts
CHANGED
|
@@ -37,12 +37,15 @@ import {
|
|
|
37
37
|
copyToClipboard,
|
|
38
38
|
getAgentDir,
|
|
39
39
|
getMarkdownTheme,
|
|
40
|
+
getLanguageFromPath,
|
|
41
|
+
highlightCode,
|
|
40
42
|
type ExtensionAPI,
|
|
41
43
|
} from "@earendil-works/pi-coding-agent";
|
|
42
44
|
import {
|
|
43
45
|
type Component,
|
|
44
46
|
Markdown,
|
|
45
47
|
stripTerminalSequences,
|
|
48
|
+
sliceByColumn,
|
|
46
49
|
truncateToWidth,
|
|
47
50
|
TuiAltScreen,
|
|
48
51
|
type TuiMouseEvent,
|
|
@@ -71,6 +74,8 @@ const RESPONSE_FOOTER_ENTRY_TYPE = "pretty-tui-response-footer";
|
|
|
71
74
|
type PrettyTuiConfig = {
|
|
72
75
|
enabled?: boolean;
|
|
73
76
|
mode?: PrettyTuiMode;
|
|
77
|
+
messageCopy?: boolean;
|
|
78
|
+
codeBlockCopy?: boolean;
|
|
74
79
|
/** Exclude plugin decoration from fullscreen selections. Opt-in. */
|
|
75
80
|
selectionCopy?: boolean;
|
|
76
81
|
/** "native" keeps Pi's flags-only strong and emphasis. Read when the extension loads. */
|
|
@@ -114,6 +119,8 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
114
119
|
const configPath = join(getAgentDir(), "pretty-tui.json");
|
|
115
120
|
let config = loadConfig(configPath);
|
|
116
121
|
const activeForSession = config.enabled !== false;
|
|
122
|
+
const messageCopyEnabled = config.messageCopy !== false;
|
|
123
|
+
const codeBlockCopyEnabled = config.codeBlockCopy !== false;
|
|
117
124
|
const configuredMode = config.mode ?? config.bash?.mode;
|
|
118
125
|
let renderMode: PrettyTuiMode = configuredMode === "full" || configuredMode === "compact" || configuredMode === "clean"
|
|
119
126
|
? configuredMode
|
|
@@ -763,6 +770,10 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
763
770
|
{ value: "full", label: "full", description: "Full tool details and output" },
|
|
764
771
|
{ value: "compact", label: "compact", description: "Concise summaries for all built-in tools" },
|
|
765
772
|
{ value: "clean", label: "clean", description: "Group supported tools into Running/Done status" },
|
|
773
|
+
{ value: "message-copy on", label: "message-copy on", description: "Enable user-message and final-answer copying and reload" },
|
|
774
|
+
{ value: "message-copy off", label: "message-copy off", description: "Disable user-message and final-answer copying and reload" },
|
|
775
|
+
{ value: "code-block-copy on", label: "code-block-copy on", description: "Show code-block Copy controls and reload" },
|
|
776
|
+
{ value: "code-block-copy off", label: "code-block-copy off", description: "Hide code-block Copy controls and reload" },
|
|
766
777
|
{ value: "selection-copy on", label: "selection-copy on", description: "Copy selected text without UI borders in fullscreen mode; enable and reload" },
|
|
767
778
|
{ value: "selection-copy off", label: "selection-copy off", description: "Restore native selection copying and reload" },
|
|
768
779
|
{ value: "status", label: "status", description: "Show the current settings" },
|
|
@@ -772,25 +783,32 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
772
783
|
},
|
|
773
784
|
handler: async (args, ctx) => {
|
|
774
785
|
let requested = args.trim().toLowerCase();
|
|
786
|
+
if (!requested && !ctx.hasUI) requested = "status";
|
|
775
787
|
|
|
776
|
-
|
|
777
|
-
if (!ctx.hasUI) {
|
|
778
|
-
ctx.ui.notify(
|
|
779
|
-
`pi-pretty-tui: ${config.enabled !== false ? "enabled" : "disabled"} · mode: ${renderMode} · selection copy: ${config.selectionCopy === true ? "on" : "off"}`,
|
|
780
|
-
"info",
|
|
781
|
-
);
|
|
782
|
-
return;
|
|
783
|
-
}
|
|
788
|
+
while (!requested) {
|
|
784
789
|
const enabled = `Enabled — ${config.enabled !== false ? "on" : "off"}`;
|
|
785
790
|
const full = `Full — full tool details and output${renderMode === "full" ? " (current)" : ""}`;
|
|
786
791
|
const compact = `Compact — concise tool summaries and nested calls${renderMode === "compact" ? " (current)" : ""}`;
|
|
787
792
|
const clean = `Clean — group supported tools into Running/Done status${renderMode === "clean" ? " (current)" : ""}`;
|
|
788
793
|
const selectionCopy = `Selection copy — ${config.selectionCopy === true ? "on" : "off"} · copy text without UI borders (fullscreen, experimental)`;
|
|
789
|
-
const
|
|
794
|
+
const messageCopy = `Message copy — ${config.messageCopy !== false ? "on" : "off"} · user messages and final answers`;
|
|
795
|
+
const codeBlockCopy = `Code block copy — ${config.codeBlockCopy !== false ? "on" : "off"} · code-block Copy controls`;
|
|
796
|
+
const copySettings = "Copy settings — message, code block and selection copying";
|
|
797
|
+
const selected = await ctx.ui.select("pi-pretty-tui settings", [enabled, full, compact, clean, copySettings]);
|
|
790
798
|
if (!selected) return;
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
799
|
+
if (selected === copySettings) {
|
|
800
|
+
const back = "Back";
|
|
801
|
+
const copy = await ctx.ui.select("Copy settings", [messageCopy, codeBlockCopy, selectionCopy, back]);
|
|
802
|
+
if (!copy) return;
|
|
803
|
+
if (copy === back) continue;
|
|
804
|
+
requested = copy === messageCopy
|
|
805
|
+
? config.messageCopy !== false ? "message-copy off" : "message-copy on"
|
|
806
|
+
: copy === codeBlockCopy
|
|
807
|
+
? config.codeBlockCopy !== false ? "code-block-copy off" : "code-block-copy on"
|
|
808
|
+
: config.selectionCopy === true ? "selection-copy off" : "selection-copy on";
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
requested = selected === enabled
|
|
794
812
|
? config.enabled !== false ? "disable" : "enable"
|
|
795
813
|
: selected === full
|
|
796
814
|
? "full"
|
|
@@ -801,21 +819,26 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
801
819
|
|
|
802
820
|
if (requested === "status") {
|
|
803
821
|
ctx.ui.notify(
|
|
804
|
-
`pi-pretty-tui: ${config.enabled !== false ? "enabled" : "disabled"} · mode: ${renderMode} · selection copy: ${config.selectionCopy === true ? "on" : "off"}`,
|
|
822
|
+
`pi-pretty-tui: ${config.enabled !== false ? "enabled" : "disabled"} · mode: ${renderMode} · message copy: ${config.messageCopy !== false ? "on" : "off"} · code block copy: ${config.codeBlockCopy !== false ? "on" : "off"} · selection copy: ${config.selectionCopy === true ? "on" : "off"}`,
|
|
805
823
|
"info",
|
|
806
824
|
);
|
|
807
825
|
return;
|
|
808
826
|
}
|
|
809
|
-
|
|
810
|
-
|
|
827
|
+
const copySetting = /^(message-copy|code-block-copy|selection-copy) (on|off)$/.exec(requested);
|
|
828
|
+
if (copySetting) {
|
|
829
|
+
const key = copySetting[1] === "message-copy" ? "messageCopy"
|
|
830
|
+
: copySetting[1] === "code-block-copy" ? "codeBlockCopy" : "selectionCopy";
|
|
831
|
+
const enabled = copySetting[2] === "on";
|
|
811
832
|
try {
|
|
812
|
-
saveConfig({ ...config,
|
|
833
|
+
saveConfig({ ...config, [key]: enabled });
|
|
813
834
|
} catch (error) {
|
|
814
835
|
const message = error instanceof Error ? error.message : String(error);
|
|
815
|
-
ctx.ui.notify(`Could not save
|
|
836
|
+
ctx.ui.notify(`Could not save ${copySetting[1]} setting: ${message}`, "error");
|
|
816
837
|
return;
|
|
817
838
|
}
|
|
818
|
-
|
|
839
|
+
const label = key === "messageCopy" ? "Message copying"
|
|
840
|
+
: key === "codeBlockCopy" ? "Code-block copying" : "Border-free selection copying";
|
|
841
|
+
ctx.ui.notify(`${label} ${enabled ? "enabled" : "disabled"}; reloading…`, "info");
|
|
819
842
|
try { await ctx.reload(); }
|
|
820
843
|
catch (error) {
|
|
821
844
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -846,7 +869,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
846
869
|
}
|
|
847
870
|
}
|
|
848
871
|
if (requested !== "full" && requested !== "compact" && requested !== "clean") {
|
|
849
|
-
ctx.ui.notify("Usage: /pretty-tui [enable|disable|full|compact|clean|status|selection-copy on|selection-copy off]", "error");
|
|
872
|
+
ctx.ui.notify("Usage: /pretty-tui [enable|disable|full|compact|clean|status|message-copy on|message-copy off|code-block-copy on|code-block-copy off|selection-copy on|selection-copy off]", "error");
|
|
850
873
|
return;
|
|
851
874
|
}
|
|
852
875
|
|
|
@@ -1122,7 +1145,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1122
1145
|
}));
|
|
1123
1146
|
if (result?.handled) return result;
|
|
1124
1147
|
}
|
|
1125
|
-
if (fullscreenTui && event.type === "click" && event.button === "left" && userText.trim()) {
|
|
1148
|
+
if (messageCopyEnabled && fullscreenTui && event.type === "click" && event.button === "left" && userText.trim()) {
|
|
1126
1149
|
copyTranscriptText(userText);
|
|
1127
1150
|
return { handled: true };
|
|
1128
1151
|
}
|
|
@@ -1134,8 +1157,17 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1134
1157
|
};
|
|
1135
1158
|
|
|
1136
1159
|
const bubbleClickHitTest = (event: any) => {
|
|
1137
|
-
if (!fullscreenTui
|
|
1160
|
+
if (!fullscreenTui) return false;
|
|
1138
1161
|
const measured = layout(event.width);
|
|
1162
|
+
if (!messageCopyEnabled || !userText.trim()) {
|
|
1163
|
+
return mouseClickHitTests.get(markdown)?.({
|
|
1164
|
+
...event,
|
|
1165
|
+
x: event.x - measured.leftPad - (measured.framed ? 2 : 0),
|
|
1166
|
+
y: event.y - (measured.framed ? 1 : 0),
|
|
1167
|
+
width: measured.contentWidth,
|
|
1168
|
+
height: measured.body.length,
|
|
1169
|
+
}) ?? false;
|
|
1170
|
+
}
|
|
1139
1171
|
return event.x >= measured.leftPad && event.x < measured.leftPad + measured.frameWidth &&
|
|
1140
1172
|
event.y >= 0 && event.y < measured.body.length + (measured.framed ? 2 : 0);
|
|
1141
1173
|
};
|
|
@@ -2420,7 +2452,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2420
2452
|
|
|
2421
2453
|
const patchedRender = function (this: any, width: number): string[] {
|
|
2422
2454
|
const scoped = transcriptMarkdownActive();
|
|
2423
|
-
mouseClickHitTests.set(this, (event) => fullscreenTui &&
|
|
2455
|
+
mouseClickHitTests.set(this, (event) => codeBlockCopyEnabled && fullscreenTui &&
|
|
2424
2456
|
this[transcriptMarkdownStateKey] === true &&
|
|
2425
2457
|
(this[codeBlockRegionsKey] ?? []).some((region: any) =>
|
|
2426
2458
|
event.y === region.y && event.x >= region.xStart && event.x < region.xEnd));
|
|
@@ -2576,7 +2608,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2576
2608
|
}
|
|
2577
2609
|
|
|
2578
2610
|
const copyLabel = "[Copy]";
|
|
2579
|
-
const showCopyButton = fullscreenTui && maxWidth >= 18;
|
|
2611
|
+
const showCopyButton = codeBlockCopyEnabled && fullscreenTui && maxWidth >= 18;
|
|
2580
2612
|
const topSuffix = showCopyButton ? ` ${copyLabel} ╮` : "╮";
|
|
2581
2613
|
const rawLanguage = typeof token.lang === "string" ? token.lang.trim() : "";
|
|
2582
2614
|
const language = rawLanguage.split(/\s+/, 1)[0] || "code";
|
|
@@ -2648,7 +2680,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2648
2680
|
delete this[codeBlockRegionsKey];
|
|
2649
2681
|
return originalHandleMouse?.call(this, event);
|
|
2650
2682
|
}
|
|
2651
|
-
if (event.button === "left" && (event.type === "press" || event.type === "click")) {
|
|
2683
|
+
if (codeBlockCopyEnabled && fullscreenTui && event.button === "left" && (event.type === "press" || event.type === "click")) {
|
|
2652
2684
|
const region = (this[codeBlockRegionsKey] ?? []).find(
|
|
2653
2685
|
(candidate: any) =>
|
|
2654
2686
|
event.y === candidate.y && event.x >= candidate.xStart && event.x < candidate.xEnd,
|
|
@@ -2828,8 +2860,22 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2828
2860
|
|
|
2829
2861
|
const paint = (line: string) => row.background
|
|
2830
2862
|
? row.background(line + " ".repeat(Math.max(0, contentWidth - visibleWidth(line)))) : line;
|
|
2831
|
-
|
|
2832
|
-
|
|
2863
|
+
const corner = renderMode === "clean" ? stripTerminalSequences(prefix).indexOf("└") : -1;
|
|
2864
|
+
if (corner >= 0 && wrapped.length > 1) {
|
|
2865
|
+
// Close the branch on the final physical row, not the first row of
|
|
2866
|
+
// a logical source line that may wrap or contain explicit newlines.
|
|
2867
|
+
const column = visibleWidth(stripTerminalSequences(prefix).slice(0, corner));
|
|
2868
|
+
const guide = (symbol: string) => sliceByColumn(continuation, 0, column) +
|
|
2869
|
+
defaultActivityTheme().fg("dim", symbol) +
|
|
2870
|
+
sliceByColumn(continuation, column + 1, Math.max(0, visibleWidth(continuation) - column - 1));
|
|
2871
|
+
rendered.push(prefix.replace("└", "│") + paint(wrapped[0] ?? ""));
|
|
2872
|
+
for (let index = 1; index < wrapped.length; index++) {
|
|
2873
|
+
rendered.push(guide(index === wrapped.length - 1 ? "└" : "│") + paint(wrapped[index]));
|
|
2874
|
+
}
|
|
2875
|
+
} else {
|
|
2876
|
+
rendered.push(prefix + paint(wrapped[0] ?? ""));
|
|
2877
|
+
for (const line of wrapped.slice(1)) rendered.push(continuation + paint(line));
|
|
2878
|
+
}
|
|
2833
2879
|
}
|
|
2834
2880
|
|
|
2835
2881
|
return rendered;
|
|
@@ -2850,7 +2896,8 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2850
2896
|
if (renderMode === "compact") return name === "bash" ? theme.fg("toolTitle", "$ ") : "";
|
|
2851
2897
|
return theme.fg(color, "● ");
|
|
2852
2898
|
},
|
|
2853
|
-
continuation: renderMode === "
|
|
2899
|
+
continuation: renderMode === "clean" && expanded ? theme.fg("dim", "│ ") :
|
|
2900
|
+
renderMode === "compact" && name !== "bash" ? "" : " ",
|
|
2854
2901
|
content: renderMode === "compact" ? theme.fg("muted", detail) :
|
|
2855
2902
|
theme.fg("text", theme.bold(name)) +
|
|
2856
2903
|
theme.fg("dim", "(") +
|
|
@@ -3569,7 +3616,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3569
3616
|
const dimPhase = responseRunStartedAt !== undefined &&
|
|
3570
3617
|
Math.floor(Math.max(0, Date.now() - responseRunStartedAt) / RUNNING_DOT_INTERVAL_MS) % 2 === 1;
|
|
3571
3618
|
const color = currentActivity === "done"
|
|
3572
|
-
? collapsedDone ? "dim" : "
|
|
3619
|
+
? collapsedDone ? "dim" : "text"
|
|
3573
3620
|
: dimPhase ? "dim" : "accent";
|
|
3574
3621
|
return currentActivity !== "done" && !dimPhase
|
|
3575
3622
|
? runningColor(theme, "● ")
|
|
@@ -3580,7 +3627,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3580
3627
|
const currentActivity = typeof activity === "function" ? activity() : activity;
|
|
3581
3628
|
const label = currentActivity === "done" ? "Done" : "Running";
|
|
3582
3629
|
const color = label === "Done"
|
|
3583
|
-
? collapsedDone ? "dim" : "
|
|
3630
|
+
? collapsedDone ? "dim" : "text"
|
|
3584
3631
|
: "accent";
|
|
3585
3632
|
const detailColor = label === "Done" && collapsedDone ? "dim" : "text";
|
|
3586
3633
|
const title = label === "Running"
|
|
@@ -3787,14 +3834,14 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3787
3834
|
return {
|
|
3788
3835
|
render(width: number): string[] {
|
|
3789
3836
|
const data = entry.data;
|
|
3790
|
-
mouseClickHitTests.set(this, (event) => fullscreenTui && copyStart >= 0 &&
|
|
3837
|
+
mouseClickHitTests.set(this, (event) => messageCopyEnabled && fullscreenTui && copyStart >= 0 &&
|
|
3791
3838
|
event.y === 0 && event.x >= copyStart && event.x < copyStart + copyWidth);
|
|
3792
3839
|
if (!data || width <= 0) return [];
|
|
3793
3840
|
copyStart = -1;
|
|
3794
3841
|
copyWidth = 0;
|
|
3795
3842
|
const answer = data.answerEntryId ? responseAnswerTexts.get(data.answerEntryId) ?? "" : "";
|
|
3796
3843
|
const copyLabel = "[Copy]";
|
|
3797
|
-
const showCopy = fullscreenTui && Boolean(answer.trim()) && width >= 30;
|
|
3844
|
+
const showCopy = messageCopyEnabled && fullscreenTui && Boolean(answer.trim()) && width >= 30;
|
|
3798
3845
|
const duration = formatResponseDuration(data.durationMs);
|
|
3799
3846
|
const statusText = data.outcome === "stopped"
|
|
3800
3847
|
? `⚠ Stopped after ${duration}`
|
|
@@ -3820,7 +3867,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3820
3867
|
},
|
|
3821
3868
|
handleMouse(event: TuiMouseEvent) {
|
|
3822
3869
|
if (
|
|
3823
|
-
copyStart < 0 ||
|
|
3870
|
+
!messageCopyEnabled || !fullscreenTui || copyStart < 0 ||
|
|
3824
3871
|
event.button !== "left" ||
|
|
3825
3872
|
(event.type !== "press" && event.type !== "click") ||
|
|
3826
3873
|
event.y !== 0 ||
|
|
@@ -4652,7 +4699,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
4652
4699
|
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme, writeLabel, context.toolCallId);
|
|
4653
4700
|
const content = typeof args.content === "string" ? args.content : "";
|
|
4654
4701
|
const path = String(args.path ?? "");
|
|
4655
|
-
if (useCompactToolView(context.toolCallId, context.expanded)) {
|
|
4702
|
+
if (renderMode === "clean" || useCompactToolView(context.toolCallId, context.expanded)) {
|
|
4656
4703
|
const lines = content.replace(/\t/g, " ").split("\n");
|
|
4657
4704
|
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
4658
4705
|
const total = lines.length;
|
|
@@ -4665,6 +4712,33 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
4665
4712
|
if (hideCleanTool(context.toolCallId, options.expanded, context.executionStarted)) return hiddenToolResult(context, options, output);
|
|
4666
4713
|
if (options.isPartial) return partialResult(context, theme, "Writing…");
|
|
4667
4714
|
const failed = completeStatus(context);
|
|
4715
|
+
if (renderMode === "clean") {
|
|
4716
|
+
if (failed) return result(theme, "Write failed", output, options.expanded, true);
|
|
4717
|
+
const status = output ? `Written · ${output}` : "Written";
|
|
4718
|
+
const rows: DisplayRow[] = [{
|
|
4719
|
+
prefix: theme.fg("dim", "└ "), continuation: " ",
|
|
4720
|
+
content: theme.fg("toolOutput", status),
|
|
4721
|
+
}];
|
|
4722
|
+
if (options.expanded) {
|
|
4723
|
+
const content = typeof context.args?.content === "string" ? context.args.content : "";
|
|
4724
|
+
const source = content.replace(/\r\n|\r/g, "\n").replace(/\t/g, " ").split("\n");
|
|
4725
|
+
// A final newline terminates the last line; additional blank lines
|
|
4726
|
+
// are actual file content and retain their own line numbers.
|
|
4727
|
+
if (source.at(-1) === "") source.pop();
|
|
4728
|
+
let highlighted: string[];
|
|
4729
|
+
try { highlighted = highlightCode(source.join("\n"), getLanguageFromPath(String(context.args?.path ?? ""))); }
|
|
4730
|
+
catch { highlighted = source; }
|
|
4731
|
+
const digits = String(source.length).length;
|
|
4732
|
+
for (let index = 0; index < source.length; index++) {
|
|
4733
|
+
rows.push({
|
|
4734
|
+
prefix: theme.fg("dim", index === source.length - 1 ? " └ " : " │ ") + theme.fg("dim", `${String(index + 1).padStart(digits)} `),
|
|
4735
|
+
continuation: (index === source.length - 1 ? " " : theme.fg("dim", " │ ")) + " ".repeat(digits + 1),
|
|
4736
|
+
content: highlighted[index] ?? source[index],
|
|
4737
|
+
});
|
|
4738
|
+
}
|
|
4739
|
+
}
|
|
4740
|
+
return block(rows);
|
|
4741
|
+
}
|
|
4668
4742
|
return result(
|
|
4669
4743
|
theme,
|
|
4670
4744
|
failed ? output.split("\n")[0] || "Write failed" : "Written",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-pretty-tui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "A beautiful, polished TUI for the Pi coding agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Kainan Yang <ykn0309@whu.edu.cn>",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"@earendil-works/pi-tui": "*"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@earendil-works/pi-coding-agent": "1.5.
|
|
49
|
-
"@earendil-works/pi-tui": "1.5.
|
|
48
|
+
"@earendil-works/pi-coding-agent": "1.5.1",
|
|
49
|
+
"@earendil-works/pi-tui": "1.5.1",
|
|
50
50
|
"@types/node": "^24.0.0",
|
|
51
51
|
"jiti": "^2.7.0",
|
|
52
52
|
"typescript": "^5.9.0"
|