From f51deb12297b1e16403d6bbb89eb010d9aa761ec Mon Sep 17 00:00:00 2001 From: Charles Packer Date: Fri, 26 Dec 2025 09:37:20 -0800 Subject: [PATCH] fix: improve word-level diff highlighting (#397) Co-authored-by: Letta --- src/cli/components/AdvancedDiffRenderer.tsx | 27 ++++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/cli/components/AdvancedDiffRenderer.tsx b/src/cli/components/AdvancedDiffRenderer.tsx index 2e245df..3040535 100644 --- a/src/cli/components/AdvancedDiffRenderer.tsx +++ b/src/cli/components/AdvancedDiffRenderer.tsx @@ -53,6 +53,21 @@ function padLeft(n: number, width: number): string { return s.length >= width ? s : " ".repeat(width - s.length) + s; } +// Calculate word-level similarity between two strings (0-1) +// Used to decide whether to show word-level highlighting +function wordSimilarity(a: string, b: string): number { + const wordsA = new Set(a.toLowerCase().split(/\s+/).filter(Boolean)); + const wordsB = new Set(b.toLowerCase().split(/\s+/).filter(Boolean)); + if (wordsA.size === 0 && wordsB.size === 0) return 1; + if (wordsA.size === 0 || wordsB.size === 0) return 0; + const intersection = [...wordsA].filter((w) => wordsB.has(w)).length; + const union = new Set([...wordsA, ...wordsB]).size; + return intersection / union; // Jaccard similarity +} + +// Threshold: only show word-level highlighting if lines share enough words +const WORD_SIMILARITY_THRESHOLD = 0.3; + // Render a single line with gutters and optional word-diff highlighting function Line({ kind, @@ -91,7 +106,10 @@ function Line({ ? colors.diff.removedWordBg : undefined; - // Char-level diff only for '-' or '+' when pairText is present + // Word-level diff only for '-' or '+' when pairText is present AND lines are similar enough + // If lines are too different, word-level highlighting becomes noise - show full-line colors instead + const similarity = + enableWord && pairText ? wordSimilarity(text, pairText) : 0; const charParts: Array<{ value: string; added?: boolean; @@ -100,10 +118,11 @@ function Line({ enableWord && pairText && (kind === "add" || kind === "remove") && - pairText !== text + pairText !== text && + similarity >= WORD_SIMILARITY_THRESHOLD ? kind === "add" - ? Diff.diffChars(pairText, text) - : Diff.diffChars(text, pairText) + ? Diff.diffWordsWithSpace(pairText, text) + : Diff.diffWordsWithSpace(text, pairText) : null; // Build prefix: " 1 + " (line number + symbol)