Skip to content
Merged
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
61 changes: 60 additions & 1 deletion packages/diff-viewer/src/lib/utils/inlineDiff.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ describe('computeLineDiff', () => {
expect(result.modifiedPairs).toHaveLength(1);
const pair = result.modifiedPairs[0];

// "1;" -> "2;" are the changed tokens
// "1" -> "2" are the changed tokens
expect(pair.beforeHighlights.length).toBeGreaterThan(0);
expect(pair.afterHighlights.length).toBeGreaterThan(0);
});
Expand Down Expand Up @@ -412,6 +412,65 @@ describe('computeLineDiff', () => {
expect(result.modifiedPairs).toHaveLength(0);
});

it('highlights only the trailing comma when value is otherwise identical', () => {
const before = [' "dmg:publish": "node scripts/publish-dmg-to-github-release.mjs"'];
const after = [' "release:dmg:publish": "node scripts/publish-dmg-to-github-release.mjs",'];
const result = computeLineDiff(before, after);

expect(result.modifiedPairs).toHaveLength(1);
const pair = result.modifiedPairs[0];

// Only "release:" prefix and trailing "," should be highlighted on the after side,
// NOT the entire value string.
const afterHighlightedText = pair.afterHighlights.map(h =>
after[0].slice(h.start, h.end),
);
expect(afterHighlightedText).toContain(',');
expect(afterHighlightedText.join('')).toContain('release');

// The shared value portion should NOT be highlighted
const totalHighlighted = pair.afterHighlights.reduce((sum, h) => sum + (h.end - h.start), 0);
expect(totalHighlighted).toBeLessThan(after[0].length / 2);
});

it('highlights only the changed accented word', () => {
const before = ['le café est bon'];
const after = ['le thé est bon'];
const result = computeLineDiff(before, after);

expect(result.modifiedPairs).toHaveLength(1);
const pair = result.modifiedPairs[0];

const beforeHighlightedText = pair.beforeHighlights.map(h =>
before[0].slice(h.start, h.end),
);
expect(beforeHighlightedText).toEqual(['café']);

const afterHighlightedText = pair.afterHighlights.map(h =>
after[0].slice(h.start, h.end),
);
expect(afterHighlightedText).toEqual(['thé']);
});

it('highlights only the changed emoji', () => {
const before = ['status: 🎉 done'];
const after = ['status: 🚀 done'];
const result = computeLineDiff(before, after);

expect(result.modifiedPairs).toHaveLength(1);
const pair = result.modifiedPairs[0];

const beforeHighlightedText = pair.beforeHighlights.map(h =>
before[0].slice(h.start, h.end),
);
expect(beforeHighlightedText).toEqual(['🎉']);

const afterHighlightedText = pair.afterHighlights.map(h =>
after[0].slice(h.start, h.end),
);
expect(afterHighlightedText).toEqual(['🚀']);
});

it('handles interleaved unchanged and changed lines', () => {
const before = ['A', 'B', 'C', 'D', 'E'];
const after = ['A', 'B2', 'C', 'D2', 'E'];
Expand Down
2 changes: 1 addition & 1 deletion packages/diff-viewer/src/lib/utils/inlineDiff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function similarity(a: string, b: string): number {
const SIMILARITY_THRESHOLD = 0.55;

function splitWords(text: string): string[] {
return text.split(/(\s+)/);
return text.match(/[\p{L}\p{N}_]+|\s+|[^\s\p{L}\p{N}_]/gu) ?? [];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Cap tokenization granularity before LCS diffing

computeCharHighlights passes splitWords output into lcsIndices, which allocates a full (m+1)*(n+1) dynamic-programming table. With this regex, punctuation-heavy lines now produce one token per symbol, so a single long modified line (such as minified JSON/lockfile content) can jump from ~1 token to thousands and create tens of millions of DP cells, causing major UI stalls or memory spikes. Please keep the improved precision but add a fallback (for example, max token count or coarse tokenization) when token streams get large.

Useful? React with 👍 / 👎.

}

function computeCharHighlights(
Expand Down