@gavin-hu/my-pi-agent 0.14.0 → 0.15.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/README.md +55 -33
- package/README.zh-CN.md +44 -27
- package/extensions/bytefly/README.md +193 -140
- package/extensions/bytefly/block/component.ts +7 -15
- package/extensions/bytefly/block/register.ts +6 -1
- package/extensions/bytefly/doctor/register.ts +24 -5
- package/extensions/bytefly/doctor/tools.ts +4 -0
- package/extensions/bytefly/footer/collect/snapshot.ts +6 -1
- package/extensions/bytefly/footer/config.ts +10 -6
- package/extensions/bytefly/footer/price/price.ts +5 -3
- package/extensions/bytefly/footer/price/rates.ts +63 -26
- package/extensions/bytefly/footer/render/status.ts +3 -3
- package/extensions/bytefly/header/component.ts +3 -2
- package/extensions/bytefly/index.ts +3 -3
- package/extensions/bytefly/notice/component.ts +8 -3
- package/extensions/bytefly/notify/config.ts +6 -11
- package/extensions/bytefly/notify/dispatch.ts +13 -5
- package/extensions/bytefly/panel/commands.ts +21 -1
- package/extensions/bytefly/panel/format.ts +5 -2
- package/extensions/bytefly/panel/settings-runtime.ts +26 -6
- package/extensions/bytefly/panel/settings.ts +41 -4
- package/extensions/bytefly/panel/themes.ts +32 -0
- package/extensions/bytefly/panel/tui/editing.ts +77 -0
- package/extensions/bytefly/panel/tui/entries.ts +24 -0
- package/extensions/bytefly/panel/tui/extensions-view.ts +13 -8
- package/extensions/bytefly/panel/tui/frame.ts +121 -4
- package/extensions/bytefly/panel/tui/settings-view.ts +54 -15
- package/extensions/bytefly/panel/tui.ts +94 -152
- package/extensions/bytefly/picker/discovery.ts +1 -0
- package/extensions/bytefly/picker/types.ts +2 -0
- package/extensions/bytefly/separator/component.ts +14 -4
- package/extensions/bytefly/update/cache.ts +58 -19
- package/extensions/bytefly/update/check.ts +35 -0
- package/extensions/bytefly/update/format.ts +76 -35
- package/extensions/bytefly/update/register.ts +144 -55
- package/extensions/bytefly/update/state.ts +4 -2
- package/extensions/bytefly/update/suppress.ts +66 -0
- package/extensions/bytefly/update/types.ts +15 -0
- package/extensions/check/README.md +253 -0
- package/extensions/check/commands.ts +128 -0
- package/extensions/check/config.ts +39 -0
- package/extensions/check/detect.ts +269 -0
- package/extensions/check/failures.ts +230 -0
- package/extensions/check/format.ts +135 -0
- package/extensions/check/index.ts +26 -0
- package/extensions/check/render.ts +185 -0
- package/extensions/check/runner.ts +176 -0
- package/extensions/check/runtime.ts +244 -0
- package/extensions/check/source.ts +80 -0
- package/extensions/check/tools.ts +173 -0
- package/extensions/check/tui.ts +195 -0
- package/extensions/check/types.ts +99 -0
- package/extensions/document/README.md +1 -1
- package/extensions/document/extract/pdf.ts +8 -16
- package/extensions/document/formats.ts +2 -2
- package/extensions/file-browse/README.md +66 -32
- package/extensions/file-browse/client.ts +95 -32
- package/extensions/file-browse/config.ts +9 -9
- package/extensions/file-browse/git.ts +37 -47
- package/extensions/file-browse/html.ts +39 -58
- package/extensions/file-browse/markdown.ts +110 -92
- package/extensions/file-browse/raw-html.ts +478 -0
- package/extensions/file-browse/router.ts +32 -70
- package/extensions/file-browse/server.ts +18 -56
- package/extensions/file-browse/urls.ts +80 -0
- package/extensions/goal/README.md +103 -47
- package/extensions/goal/clarify-parse.ts +4 -9
- package/extensions/goal/clarify.ts +27 -4
- package/extensions/goal/commands.ts +24 -11
- package/extensions/goal/format.ts +41 -10
- package/extensions/goal/index.ts +39 -16
- package/extensions/goal/plan-route.ts +51 -0
- package/extensions/goal/schema.ts +6 -23
- package/extensions/goal/state.ts +39 -57
- package/extensions/goal/tools.ts +26 -20
- package/extensions/goal/types.ts +11 -1
- package/extensions/job/README.md +20 -6
- package/extensions/job/format.ts +18 -68
- package/extensions/job/index.ts +26 -41
- package/extensions/job/log-writer.ts +208 -0
- package/extensions/job/logs.ts +3 -2
- package/extensions/job/process.ts +6 -99
- package/extensions/job/runtime.ts +42 -181
- package/extensions/job/schema.ts +1 -4
- package/extensions/job/tools.ts +1 -15
- package/extensions/job/tui.ts +117 -307
- package/extensions/job/ui.ts +7 -33
- package/extensions/memory/README.md +1 -1
- package/extensions/memory/errors.ts +5 -6
- package/extensions/memory/tui.ts +14 -24
- package/extensions/plan/README.md +94 -18
- package/extensions/plan/commands.ts +9 -5
- package/extensions/plan/format.ts +87 -0
- package/extensions/plan/index.ts +31 -15
- package/extensions/plan/plans.ts +2 -3
- package/extensions/plan/policy.ts +0 -6
- package/extensions/plan/runtime.ts +4 -2
- package/extensions/plan/steps.ts +37 -8
- package/extensions/plan/todos.ts +44 -3
- package/extensions/plan/tools/enter.ts +94 -0
- package/extensions/plan/tools/exit.ts +296 -0
- package/extensions/plan/tools/nested.ts +21 -0
- package/extensions/plan/tools/steps.ts +136 -0
- package/extensions/plan/tools/todo-seed.ts +76 -0
- package/extensions/plan/tools/write.ts +127 -0
- package/extensions/plan/tools.ts +18 -703
- package/extensions/plan/web-client.ts +6 -22
- package/extensions/plan/web-page.ts +19 -30
- package/extensions/plan/web-router.ts +34 -71
- package/extensions/plan/web.ts +13 -40
- package/extensions/question/answers.ts +3 -8
- package/extensions/question/tui.ts +6 -6
- package/extensions/rewind/README.md +95 -22
- package/extensions/rewind/checklist.ts +185 -0
- package/extensions/rewind/commands.ts +8 -0
- package/extensions/rewind/config.ts +3 -11
- package/extensions/rewind/format.ts +220 -2
- package/extensions/rewind/git.ts +74 -7
- package/extensions/rewind/hunk-picker.ts +114 -0
- package/extensions/rewind/patch.ts +88 -0
- package/extensions/rewind/path-picker.ts +67 -100
- package/extensions/rewind/paths.ts +93 -19
- package/extensions/rewind/restore.ts +53 -10
- package/extensions/rewind/review-tui.ts +320 -0
- package/extensions/rewind/review.ts +397 -0
- package/extensions/rewind/rewind-tui.ts +4 -3
- package/extensions/rewind/runtime.ts +94 -17
- package/extensions/subagent/README.md +61 -6
- package/extensions/subagent/background-tui.ts +208 -0
- package/extensions/subagent/background.ts +470 -0
- package/extensions/subagent/commands.ts +80 -0
- package/extensions/subagent/config.ts +4 -0
- package/extensions/subagent/format.ts +24 -1
- package/extensions/subagent/index.ts +91 -2
- package/extensions/subagent/render.ts +13 -0
- package/extensions/subagent/run.ts +17 -16
- package/extensions/subagent/schema.ts +12 -0
- package/extensions/subagent/types.ts +11 -0
- package/extensions/subagent/ui.ts +50 -0
- package/extensions/todo/README.md +18 -13
- package/extensions/todo/format.ts +3 -2
- package/extensions/todo/runtime.ts +3 -2
- package/extensions/todo/schema.ts +8 -84
- package/extensions/todo/state.ts +5 -46
- package/extensions/todo/tools.ts +2 -1
- package/extensions/todo/types.ts +6 -15
- package/extensions/watch/README.md +14 -6
- package/extensions/watch/child.ts +150 -0
- package/extensions/watch/format.ts +8 -48
- package/extensions/watch/index.ts +42 -58
- package/extensions/watch/runtime.ts +49 -128
- package/extensions/watch/schema.ts +1 -4
- package/extensions/watch/tools.ts +1 -11
- package/extensions/watch/tui.ts +103 -273
- package/extensions/watch/ui.ts +5 -32
- package/extensions/web-access/README.md +1 -1
- package/extensions/web-access/fetch/page.ts +1 -1
- package/extensions/web-access/fetch/tool.ts +6 -2
- package/extensions/web-access/search/search.ts +2 -5
- package/extensions/wechat/README.md +1 -1
- package/extensions/wechat/client.ts +23 -0
- package/extensions/wechat/commands.ts +57 -42
- package/extensions/wechat/deliver.ts +5 -9
- package/extensions/wechat/errors.ts +3 -6
- package/extensions/wechat/inbound.ts +2 -5
- package/extensions/wechat/index.ts +2 -8
- package/extensions/wechat/outbound.ts +2 -4
- package/extensions/wechat/peer-worker.ts +3 -8
- package/extensions/wechat/poll.ts +10 -9
- package/extensions/wechat/presence.ts +35 -62
- package/extensions/wechat/reply-pipeline.ts +3 -1
- package/extensions/wechat/status-screen.ts +1 -1
- package/extensions/wechat/timer.ts +57 -0
- package/extensions/worktree/config.ts +8 -20
- package/extensions/worktree/enter.ts +7 -12
- package/extensions/worktree/git.ts +38 -69
- package/extensions/worktree/index.ts +5 -4
- package/extensions/worktree/registry.ts +7 -7
- package/extensions/worktree/root-tools.ts +2 -2
- package/extensions/worktree/status.ts +3 -2
- package/lib/README.md +22 -8
- package/lib/config.ts +50 -3
- package/lib/errors.ts +12 -0
- package/lib/format.ts +50 -0
- package/lib/git/read.ts +8 -0
- package/lib/goal-state.ts +23 -8
- package/lib/http/headers.ts +54 -0
- package/lib/http/index.ts +13 -0
- package/lib/http/response.ts +46 -0
- package/lib/http/router.ts +52 -0
- package/lib/http/server.ts +91 -0
- package/lib/lines.ts +77 -0
- package/lib/pdf.ts +82 -0
- package/lib/plan-entry.ts +71 -0
- package/lib/plan-state.ts +34 -0
- package/lib/report.ts +99 -0
- package/lib/requirements.ts +25 -19
- package/lib/spawn.ts +170 -0
- package/lib/todo-state.ts +110 -23
- package/lib/tool-names.ts +7 -0
- package/lib/tool-result.ts +33 -0
- package/lib/tui/README.md +68 -3
- package/lib/tui/chrome.ts +28 -4
- package/lib/tui/columns.ts +6 -1
- package/lib/tui/confirm.ts +65 -0
- package/lib/tui/dock-screen.ts +410 -0
- package/lib/tui/glyphs.ts +2 -1
- package/lib/tui/indent.ts +1 -1
- package/lib/tui/index.ts +2 -0
- package/lib/tui/rows.ts +21 -0
- package/lib/tui/status-controller.ts +35 -1
- package/lib/tui/tool.ts +59 -1
- package/lib/web/components.ts +20 -1
- package/lib/web/markup.ts +32 -0
- package/lib/web/runtime.ts +50 -0
- package/package.json +22 -5
- package/skills/init/SKILL.md +165 -0
- package/skills/init/references/agents-md-quality.md +103 -0
- package/skills/init/references/interview-questions.md +81 -0
- package/skills/refactor/SKILL.md +141 -0
- package/extensions/watch/process.ts +0 -63
- package/extensions/web-access/fetch/pdf.ts +0 -45
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two screens `/review` opens: the changed-file list and the per-file diff.
|
|
3
|
+
*
|
|
4
|
+
* The list is the timeline's sibling — same dock frame, cursor, and width-safe
|
|
5
|
+
* row grammar — with a status mark and line counts instead of a snapshot
|
|
6
|
+
* marker. The diff is the shared `LinePane` inside the same frame, so scrolling
|
|
7
|
+
* and the range row behave like `/jobs` and `/watch`.
|
|
8
|
+
*
|
|
9
|
+
* The screens own only their cursor, scroll, and the action they return. Git
|
|
10
|
+
* reads, confirmations, and restores stay in `review.ts`, so a component never
|
|
11
|
+
* mutates the working tree and a test can drive it without a repository.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import type { Component, TuiMouseEvent, TuiMouseEventResult } from "@earendil-works/pi-tui";
|
|
16
|
+
import { clip } from "../../lib/tui/columns.ts";
|
|
17
|
+
import { detailBlockRows, dockListFrame, screenHint, type ViewportRowsSource } from "../../lib/tui/chrome.ts";
|
|
18
|
+
import { sanitizeLine } from "../../lib/format.ts";
|
|
19
|
+
import { fitRows, formatRange, isCloseKey, wheelDelta } from "../../lib/tui/cursor.ts";
|
|
20
|
+
import { SEPARATORS } from "../../lib/tui/glyphs.ts";
|
|
21
|
+
import { LinePane } from "../../lib/tui/line-pane.ts";
|
|
22
|
+
import { checkbox, emptyState, listRow } from "../../lib/tui/rows.ts";
|
|
23
|
+
import { titleRule } from "../../lib/tui/rules.ts";
|
|
24
|
+
import { paint, type TuiRole } from "../../lib/tui/tokens.ts";
|
|
25
|
+
import { ChecklistScreen } from "./checklist.ts";
|
|
26
|
+
import { formatReviewDetail, formatReviewEntryCounts, formatReviewSummary } from "./format.ts";
|
|
27
|
+
import { SELECTION_MARKS, type PathChange, type ReviewEntry } from "./paths.ts";
|
|
28
|
+
|
|
29
|
+
/** Rows a diff screen shows when the terminal height is unknown. */
|
|
30
|
+
const DIFF_DEFAULT_ITEMS = 20;
|
|
31
|
+
/** Header, summary, blank, blank, hint, and blank rows around the diff. */
|
|
32
|
+
const DIFF_CHROME_ROWS = 6;
|
|
33
|
+
|
|
34
|
+
/** Lines one wheel notch moves in the diff pane. */
|
|
35
|
+
const DIFF_WHEEL_ROWS = 3;
|
|
36
|
+
|
|
37
|
+
/** What the list returns to `review.ts`. */
|
|
38
|
+
export type ReviewListAction =
|
|
39
|
+
| { kind: "diff"; path: string }
|
|
40
|
+
| { kind: "revert"; paths: string[] }
|
|
41
|
+
| { kind: "revertAll" };
|
|
42
|
+
|
|
43
|
+
/** The list's close result: the action and the selection to restore next time. */
|
|
44
|
+
export interface ReviewListResult {
|
|
45
|
+
action?: ReviewListAction;
|
|
46
|
+
selected: string[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** What the diff returns to `review.ts`. */
|
|
50
|
+
export type ReviewDiffAction = "back" | "revert" | "hunks";
|
|
51
|
+
|
|
52
|
+
/** The role for a change mark. */
|
|
53
|
+
function markRole(change: PathChange): TuiRole {
|
|
54
|
+
if (change === "added") return "ok";
|
|
55
|
+
if (change === "deleted") return "bad";
|
|
56
|
+
return "meta";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The role for one diff line. Headers are checked before their `+`/`-` prefix. */
|
|
60
|
+
export function diffLineRole(line: string): TuiRole {
|
|
61
|
+
if (line.startsWith("… diff truncated")) return "warn";
|
|
62
|
+
if (line.startsWith("@@")) return "accent";
|
|
63
|
+
if (
|
|
64
|
+
line.startsWith("diff --git ") ||
|
|
65
|
+
line.startsWith("index ") ||
|
|
66
|
+
line.startsWith("--- ") ||
|
|
67
|
+
line.startsWith("+++ ") ||
|
|
68
|
+
line.startsWith("new file mode ") ||
|
|
69
|
+
line.startsWith("deleted file mode ") ||
|
|
70
|
+
line.startsWith("similarity index ") ||
|
|
71
|
+
line.startsWith("rename ") ||
|
|
72
|
+
line.startsWith("Binary files ")
|
|
73
|
+
) {
|
|
74
|
+
return "meta";
|
|
75
|
+
}
|
|
76
|
+
if (line.startsWith("+")) return "ok";
|
|
77
|
+
if (line.startsWith("-")) return "bad";
|
|
78
|
+
return "label";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface ReviewListOptions {
|
|
82
|
+
entries: readonly ReviewEntry[];
|
|
83
|
+
/** The prompt summary the snapshot precedes. */
|
|
84
|
+
prompt?: string;
|
|
85
|
+
theme: Theme;
|
|
86
|
+
/** Focused row to restore when returning from a diff. */
|
|
87
|
+
initialIndex?: number;
|
|
88
|
+
/** Rows selected before this mount, e.g. after returning from a diff. */
|
|
89
|
+
initialSelected?: readonly string[];
|
|
90
|
+
/** Whether the working tree is clean against HEAD, so the diff is all committed. */
|
|
91
|
+
committed?: boolean;
|
|
92
|
+
/** Called once when the screen closes, with the action and the current selection. */
|
|
93
|
+
onClose: (result: ReviewListResult) => void;
|
|
94
|
+
requestRender: () => void;
|
|
95
|
+
viewportRows?: ViewportRowsSource;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The changed-file list `/review` opens. */
|
|
99
|
+
export class ReviewListComponent extends ChecklistScreen<ReviewEntry> {
|
|
100
|
+
private readonly view: ReviewListOptions;
|
|
101
|
+
|
|
102
|
+
constructor(view: ReviewListOptions) {
|
|
103
|
+
super({
|
|
104
|
+
title: "Review",
|
|
105
|
+
theme: view.theme,
|
|
106
|
+
requestRender: view.requestRender,
|
|
107
|
+
viewportRows: view.viewportRows,
|
|
108
|
+
emptyLabel: "No changes to review.",
|
|
109
|
+
});
|
|
110
|
+
this.view = view;
|
|
111
|
+
this.cursor.selected = Math.min(Math.max(0, view.initialIndex ?? 0), Math.max(0, view.entries.length - 1));
|
|
112
|
+
// Returning from a diff can land deep in the list; put the focused row in
|
|
113
|
+
// view before the first render finds a window that omits it.
|
|
114
|
+
this.cursor.scrollTop = this.cursor.selected;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
protected rows(): readonly ReviewEntry[] {
|
|
118
|
+
return this.view.entries;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
protected keyOf(entry: ReviewEntry): string {
|
|
122
|
+
return entry.path;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
protected initialSelection(): readonly string[] {
|
|
126
|
+
const present = new Set(this.view.entries.map((entry) => entry.path));
|
|
127
|
+
return (this.view.initialSelected ?? []).filter((path) => present.has(path));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Close with an action and the current selection. */
|
|
131
|
+
private close(action?: ReviewListAction): void {
|
|
132
|
+
this.view.onClose({ action, selected: [...this.chosen] });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
protected onEnter(): void {
|
|
136
|
+
const entry = this.focused;
|
|
137
|
+
if (entry) this.close({ kind: "diff", path: entry.path });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
protected onCancel(): void {
|
|
141
|
+
this.close();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
protected verb(data: string): void {
|
|
145
|
+
const entry = this.focused;
|
|
146
|
+
if (!entry) return;
|
|
147
|
+
if (data === "r") {
|
|
148
|
+
const paths = this.chosen.size > 0 ? [...this.chosen] : [entry.path];
|
|
149
|
+
this.close({ kind: "revert", paths });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (data === "R") this.close({ kind: "revertAll" });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
protected rowLine(entry: ReviewEntry, width: number, selected: boolean): string {
|
|
156
|
+
return listRow(this.theme, width, {
|
|
157
|
+
selected,
|
|
158
|
+
leading: checkbox(this.theme, this.chosen.has(entry.path)),
|
|
159
|
+
glyph: SELECTION_MARKS[entry.change],
|
|
160
|
+
glyphRole: markRole(entry.change),
|
|
161
|
+
label: sanitizeLine(entry.path),
|
|
162
|
+
labelRole: selected ? "accent" : "label",
|
|
163
|
+
trailing: formatReviewEntryCounts(entry),
|
|
164
|
+
trailingRole: "meta",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
protected summaryText(): string {
|
|
169
|
+
const label = sanitizeLine(this.view.prompt || "the last prompt");
|
|
170
|
+
const parts = [formatReviewSummary(this.view.entries, this.chosen.size)];
|
|
171
|
+
if (this.view.committed) parts.push("committed");
|
|
172
|
+
return `after "${label}"${SEPARATORS.item}${parts.join(SEPARATORS.item)}`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
protected detailLines(width: number): string[] {
|
|
176
|
+
const detail: string[] = [];
|
|
177
|
+
const focused = this.focused;
|
|
178
|
+
if (focused) detail.push(clip(` ${paint(this.theme, "meta", formatReviewDetail(focused))}`, width));
|
|
179
|
+
const selectionLine =
|
|
180
|
+
this.chosen.size > 0
|
|
181
|
+
? `r reverts ${this.chosen.size} selected file${this.chosen.size === 1 ? "" : "s"}; R reverts all.`
|
|
182
|
+
: undefined;
|
|
183
|
+
const committedLine = this.view.committed
|
|
184
|
+
? "These changes are already committed; reverting rewrites the working tree only."
|
|
185
|
+
: undefined;
|
|
186
|
+
for (const line of [selectionLine, committedLine]) {
|
|
187
|
+
if (line) detail.push(clip(` ${paint(this.theme, "meta", line)}`, width));
|
|
188
|
+
}
|
|
189
|
+
return detail;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** The key-hint row, with the primary action in the accent role. */
|
|
193
|
+
protected hint(width: number): string {
|
|
194
|
+
return screenHint(this.theme, width, [
|
|
195
|
+
"Esc close",
|
|
196
|
+
{ text: "Enter diff", color: "accent" },
|
|
197
|
+
"Space select",
|
|
198
|
+
{ text: "r revert", color: "warning" },
|
|
199
|
+
{ text: "R all", color: "warning" },
|
|
200
|
+
"a/n all/none",
|
|
201
|
+
]);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export interface ReviewDiffOptions {
|
|
206
|
+
entry: ReviewEntry;
|
|
207
|
+
/** The prompt summary the snapshot precedes. */
|
|
208
|
+
prompt?: string;
|
|
209
|
+
/** Sanitized, tab-expanded patch lines from `boundDiff`. */
|
|
210
|
+
lines: readonly string[];
|
|
211
|
+
/** Whether this file has textual hunks that can be reverted selectively. */
|
|
212
|
+
canRevertHunks?: boolean;
|
|
213
|
+
/** Whether the working tree is clean against HEAD, so the diff is all committed. */
|
|
214
|
+
committed?: boolean;
|
|
215
|
+
theme: Theme;
|
|
216
|
+
/** Called once when the screen closes, with the chosen action or undefined. */
|
|
217
|
+
onClose: (action?: ReviewDiffAction) => void;
|
|
218
|
+
requestRender: () => void;
|
|
219
|
+
viewportRows?: ViewportRowsSource;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** One file's patch in a scrollable dock screen. */
|
|
223
|
+
export class ReviewDiffComponent implements Component {
|
|
224
|
+
private readonly pane = new LinePane();
|
|
225
|
+
|
|
226
|
+
constructor(private readonly options: ReviewDiffOptions) {
|
|
227
|
+
this.pane.open(options.entry.path, { title: options.entry.path, lines: [...options.lines] });
|
|
228
|
+
// A diff reads from its first line, unlike a log tail.
|
|
229
|
+
this.pane.setScroll(0);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
handleInput(data: string): void {
|
|
233
|
+
if (isCloseKey(data)) {
|
|
234
|
+
this.options.onClose("back");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (data === "r") {
|
|
238
|
+
this.options.onClose("revert");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (data === "h" && this.options.canRevertHunks) {
|
|
242
|
+
this.options.onClose("hunks");
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (this.pane.handleKey(data)) this.options.requestRender();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Wheel scrolling moves the pane; regular mode leaves the wheel to the terminal. */
|
|
249
|
+
handleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined {
|
|
250
|
+
const delta = wheelDelta(event);
|
|
251
|
+
if (delta === undefined) return undefined;
|
|
252
|
+
this.pane.setScroll(this.pane.scroll + delta * DIFF_WHEEL_ROWS);
|
|
253
|
+
this.options.requestRender();
|
|
254
|
+
return { handled: true };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
dispose(): void {}
|
|
258
|
+
|
|
259
|
+
invalidate(): void {}
|
|
260
|
+
|
|
261
|
+
render(width: number): string[] {
|
|
262
|
+
const w = Math.max(1, width);
|
|
263
|
+
const header = titleRule(this.theme, w, "Review");
|
|
264
|
+
const entry = this.options.entry;
|
|
265
|
+
const total = this.pane.count;
|
|
266
|
+
const summary = clip(
|
|
267
|
+
` ${paint(this.theme, "meta", `${sanitizeLine(entry.path)}${SEPARATORS.item}${SELECTION_MARKS[entry.change]}${SEPARATORS.item}${formatReviewEntryCounts(entry)}`)}`,
|
|
268
|
+
w,
|
|
269
|
+
);
|
|
270
|
+
const label = sanitizeLine(this.options.prompt || "the last prompt");
|
|
271
|
+
const detail = [clip(` ${paint(this.theme, "meta", `after "${label}"`)}`, w)];
|
|
272
|
+
if (entry.change === "renamed") {
|
|
273
|
+
detail.push(clip(` ${paint(this.theme, "meta", `renamed from ${sanitizeLine(entry.from)}`)}`, w));
|
|
274
|
+
}
|
|
275
|
+
if (this.options.committed) {
|
|
276
|
+
detail.push(
|
|
277
|
+
clip(
|
|
278
|
+
` ${paint(this.theme, "meta", "These changes are already committed; reverting rewrites the working tree only.")}`,
|
|
279
|
+
w,
|
|
280
|
+
),
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
this.pane.visible = fitRows(
|
|
284
|
+
this.options.viewportRows,
|
|
285
|
+
total,
|
|
286
|
+
DIFF_CHROME_ROWS + detailBlockRows(detail),
|
|
287
|
+
DIFF_DEFAULT_ITEMS,
|
|
288
|
+
);
|
|
289
|
+
this.pane.fit(this.pane.visible);
|
|
290
|
+
const { start, end } = this.pane.window();
|
|
291
|
+
const body = this.options.lines
|
|
292
|
+
.slice(start, end)
|
|
293
|
+
.map((line) => clip(paint(this.theme, diffLineRole(line), line), w));
|
|
294
|
+
if (body.length === 0) body.push(...emptyState(this.theme, w, { label: "No textual diff." }));
|
|
295
|
+
|
|
296
|
+
return dockListFrame({
|
|
297
|
+
header,
|
|
298
|
+
summary,
|
|
299
|
+
body,
|
|
300
|
+
range: start > 0 || end < total ? formatRange(this.theme, w, { start, end, total, label: "line" }) : undefined,
|
|
301
|
+
detail,
|
|
302
|
+
hint: this.hint(w),
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private get theme(): Theme {
|
|
307
|
+
return this.options.theme;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** The key-hint row, with revert in the warn role. */
|
|
311
|
+
private hint(width: number): string {
|
|
312
|
+
return screenHint(this.theme, width, [
|
|
313
|
+
"Esc back",
|
|
314
|
+
...(this.options.canRevertHunks ? [{ text: "h hunks", color: "accent" as const }] : []),
|
|
315
|
+
{ text: "r revert file", color: "warning" },
|
|
316
|
+
"j/k scroll",
|
|
317
|
+
"g/G top/end",
|
|
318
|
+
]);
|
|
319
|
+
}
|
|
320
|
+
}
|