Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions extensions/pretty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,20 @@ export default function pretty(pi: ExtensionAPI) {
return diff ? renderPreviewDiff(theme, diff, args?.path) : null;
}

/**
* pi's edit tool details provide both `patch` (standard unified diff with
* hunk headers) and `diff` (pre-formatted line-numbered diff). Prefer the
* standard patch so hunk-based word emphasis and side-by-side split work as
* designed, falling back to diff.
*/
function extractEditDiff(result: unknown): string {
if (!isRecord(result) || !isRecord(result.details)) return "";
const { patch, diff } = result.details;
if (typeof patch === "string" && patch) return patch;
if (typeof diff === "string") return diff;
return "";
}

/** Renderers per tool; delegate execution to the original untouched. */
function renderersFor(tool: PrettyTool): Record<string, unknown> {
switch (tool) {
Expand Down Expand Up @@ -451,10 +465,7 @@ export default function pretty(pi: ExtensionAPI) {
if (isFailed(result)) {
return new Text(theme.fg("error", failureLine(textContent(result).split("\n")[0] || "Edit failed", clipWidth())), 0, 0);
}
const diff =
isRecord(result) && isRecord(result.details) && typeof result.details.diff === "string"
? result.details.diff
: "";
const diff = extractEditDiff(result);
const stats = statsLabel(theme, diffStats(diff), settings.diffStatMeter);
if (!options.expanded) return new Text(stats, 0, 0);
const body = preview(diff, true, diffLimits());
Expand Down
59 changes: 59 additions & 0 deletions test/wire.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,62 @@ test("f090: syntaxHighlight:false leaves the read body plain", async () => {
assert.ok(out.includes("const x = 1;"), "content is still shown, just unhighlighted");
});
});

// ── edit diff source (details.patch vs details.diff) ─────────────────

test("edit result prefers details.patch over details.diff", async () => {
await withTmp(async (cwd) => {
mkdirSync(join(cwd, ".pi"));
writeFileSync(join(cwd, ".pi", "pretty.json"), JSON.stringify({ diffSplit: true }));
const env = makeEnv(cwd, () => BUILTIN_BASH);
await env.sessionStart();
const edit = env.tools.get("edit")!.renderResult as (
r: unknown,
o: unknown,
t: unknown,
c: unknown,
) => unknown;

const EDIT_RESULT = {
content: [{ type: "text", text: "Successfully replaced 1 block(s)." }],
details: {
diff: " 1 alpha\n-2 bravo\n+2 bravo-mod\n 3 charlie",
patch:
"===================================================================\n--- a.ts\n+++ a.ts\n@@ -1,3 +1,3 @@\n alpha\n-bravo\n+bravo-mod\n charlie",
},
};

// Fake terminal columns so splitFits succeeds in headless test
const origCols = process.stdout.columns;
try {
Object.defineProperty(process.stdout, "columns", { value: 140, configurable: true });
const out = textOf(edit(EDIT_RESULT, { expanded: true }, IDENTITY_THEME, { args: { path: "a.ts" } }));
assert.ok(out.includes("│"), "split view separator present when details.patch is consumed");
} finally {
Object.defineProperty(process.stdout, "columns", { value: origCols, configurable: true });
}
});
});

test("edit result falls back to details.diff when details.patch is absent", async () => {
await withTmp(async (cwd) => {
const env = makeEnv(cwd, () => BUILTIN_BASH);
await env.sessionStart();
const edit = env.tools.get("edit")!.renderResult as (
r: unknown,
o: unknown,
t: unknown,
c: unknown,
) => unknown;

const EDIT_RESULT_NO_PATCH = {
content: [{ type: "text", text: "Successfully replaced 1 block(s)." }],
details: {
diff: " 1 alpha\n-2 bravo\n+2 bravo-mod\n 3 charlie",
},
};

const out = textOf(edit(EDIT_RESULT_NO_PATCH, { expanded: true }, IDENTITY_THEME, { args: { path: "a.ts" } }));
assert.ok(out.includes("bravo-mod"), "falls back to details.diff content");
});
});