mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 08:24:46 +00:00
fix(diff): align highlight tokens with normalized patch lines
Git can normalize CRLF worktrees while highlighting reads their original line endings. Match tokens to the emitted patch text and trim only a normalization CR before encoding runs; mismatched source tokens are not attached.
This commit is contained in:
@@ -485,3 +485,52 @@ test.each([true, false])(
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test.each([true, false])(
|
||||
"autocrlf=%s highlighted CRLF worktrees keep the revision readable",
|
||||
async (autocrlf) => {
|
||||
const cwd = await mkdtemp(join(tmpdir(), "diff-crlf-"));
|
||||
const git = (...args: string[]) => execFileSync("git", args, { cwd, encoding: "utf8" }).trim();
|
||||
const streams = new DiffSubscriptions({
|
||||
async requestWorkingTreeWatch(root) {
|
||||
return { repoRoot: root, unsubscribe: async () => {} };
|
||||
},
|
||||
});
|
||||
const frames: DiffFrame[] = [];
|
||||
try {
|
||||
git("init", "-b", "main");
|
||||
git("config", "user.name", "Test");
|
||||
git("config", "user.email", "test@example.com");
|
||||
git("config", "core.autocrlf", String(autocrlf));
|
||||
await writeFile(join(cwd, "a.ts"), "export const value = 1;\r\n");
|
||||
await writeFile(join(cwd, "b.txt"), "before\n");
|
||||
git("add", ".");
|
||||
git("-c", "commit.gpgsign=false", "commit", "-m", "base");
|
||||
await writeFile(join(cwd, "a.ts"), "export const value = 2;\r\n");
|
||||
await writeFile(join(cwd, "b.txt"), "after\n");
|
||||
const subscription = await streams.subscribe(
|
||||
{ cwd, base: { kind: "rev", rev: "HEAD" }, head: { kind: "worktree" } },
|
||||
async (frame) => {
|
||||
frames.push(frame);
|
||||
},
|
||||
);
|
||||
await subscription.start();
|
||||
expect(frames.at(-1)).toMatchObject({ opcode: DiffOpcode.End, error: null });
|
||||
const files = frames.flatMap((frame) =>
|
||||
frame.opcode === DiffOpcode.Bodies ? frame.files : [],
|
||||
);
|
||||
expect(files.map((file) => file.path)).toEqual(["a.ts", "b.txt"]);
|
||||
const line = files[0].hunks
|
||||
.flatMap((hunk) => hunk.lines)
|
||||
.find((candidate) => candidate.type === "add");
|
||||
expect(line?.content).toBe(`export const value = 2;${autocrlf ? "" : "\r"}`);
|
||||
expect(line?.runs?.reduce((length, run) => length + run.length, 0)).toBe(
|
||||
line?.content.length,
|
||||
);
|
||||
await subscription.release();
|
||||
} finally {
|
||||
await streams.close();
|
||||
await rm(cwd, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -468,7 +468,8 @@ function applyTokensToHunks(
|
||||
newLineNum++;
|
||||
}
|
||||
|
||||
return tokens ? { ...line, tokens } : line;
|
||||
const matchingTokens = tokensForPatchLine(tokens, line.content);
|
||||
return matchingTokens ? { ...line, tokens: matchingTokens } : line;
|
||||
});
|
||||
|
||||
return { ...hunk, lines: highlightedLines };
|
||||
@@ -477,6 +478,25 @@ function applyTokensToHunks(
|
||||
return { ...file, hunks: highlightedHunks };
|
||||
}
|
||||
|
||||
function tokensForPatchLine(
|
||||
tokens: HighlightToken[] | undefined,
|
||||
content: string,
|
||||
): HighlightToken[] | undefined {
|
||||
if (!tokens) return undefined;
|
||||
const source = tokens.map((token) => token.text).join("");
|
||||
if (source === content) return tokens;
|
||||
// Git can normalize a CRLF worktree while source highlighting retains its CR.
|
||||
if (source !== `${content}\r`) return undefined;
|
||||
const aligned: HighlightToken[] = [];
|
||||
let remaining = content.length;
|
||||
for (const token of tokens) {
|
||||
const text = token.text.slice(0, remaining);
|
||||
if (text) aligned.push({ text, style: token.style });
|
||||
remaining -= text.length;
|
||||
}
|
||||
return aligned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and highlight a complete diff, using actual file content when available.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user