Skip to content

Per Word Diff #13

Merged
amadeus merged 5 commits intomainfrom
amadeus/per-line-diff
Sep 29, 2025
Merged

Per Word Diff #13
amadeus merged 5 commits intomainfrom
amadeus/per-line-diff

Conversation

@amadeus
Copy link
Copy Markdown
Member

@amadeus amadeus commented Sep 26, 2025

This PR adds shiki decorations on deletion/addition lines to highlight specific word-based changes:

CleanShot 2025-09-26 at 16 00 30

Because of the way I do this, I've decided to disable the custom shiki decoration API because technically it won't be compatible given that shiki doesn't allow overlapping decorations.

Anyways, at a high level this PR adds jsdiff as a dependency and when we iterate over to render we figure out the word-based spans to add highlights for specific changes within a line. I initially used fast-diff to do this but it only could diff based on characters which I found a bit more annoying in practice. I think word based is much easier.

I also applied some additional logic to look for cases where there would be single white-space characters between spans and use that to join the spans together to avoid a sort of gap-tooth'd aesthetic.

I think we still need to iterate on what the spans look like and what colors they should adopt, but this is definitely a good foundation for us to build on.

Not sure if we should allow this to be configurable, but the word-based
diffing will probably be a better user experience in general and we'll
probably want to utilize `jsdiff` for other things in the future...

Also swapped over to use more of a word based diffing algo
@amadeus amadeus requested review from SlexAxton, fat and mdo September 26, 2025 23:04
@vercel
Copy link
Copy Markdown

vercel bot commented Sep 26, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
pierrejs-diff-demo Ready Ready Preview Sep 26, 2025 11:19pm
pierrejs-docs Ready Ready Preview Sep 26, 2025 11:19pm

_wrapper: HTMLPreElement,
_decorations?: DiffDecorationItem[]
) {
async render(_diff: FileMetadata, _wrapper: HTMLPreElement) {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The changes within the render function` here are mostly just to remove the decorations API

codeUnified,
}: RenderHunkProps) {
if (hunk.hunkContent == null) return;
const { additions, deletions, unified } = this.processLines(hunk);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I kinda refactored the layout of renderHunks to some sub-functions, but the core logic of rendering into the code elements is largely unchanged.

if (deletionLine.length > 1000 || additionLine.length > 1000) {
continue;
}
const lineDiff = diffWordsWithSpace(deletionLine, additionLine);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is where most of the magic happens -- diffGroups is essentially a data structure where we figure out arrays of additions and deletions without the context lines and figure out the line index relative to each group that can then be passed through to the decorations API

start: { line, character: spanStart },
end: { line, character: spanStart + spanLength },
properties: { 'data-diff-span': '' },
alwaysWrap: true,
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This property is important to make sure we never highlight the whole line and instead JUST the text even if all the content of the line changed (the decorations need to feel like specific text wrappers, not full line wrappers

patch: 'diff',
};

export const DIFF_DECORATIONS: Record<string, DiffDecorationItem[]> = {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This API is now kill... we can't really support it and highlighting diffs at the same time... which I think is ok in practice

@amadeus amadeus merged commit d1aa5df into main Sep 29, 2025
2 checks passed
@amadeus amadeus deleted the amadeus/per-line-diff branch September 29, 2025 19:22
@amadeus
Copy link
Copy Markdown
Member Author

amadeus commented Sep 29, 2025

alas, the review never came

SlexAxton added a commit that referenced this pull request Mar 30, 2026
Reduce first visible-row readiness for large virtualized trees by removing redundant tree-building/traversal work and tightening hot path data structures.

Experiments: #2, #3, #6, #7, #9, #10, #12, #13, #14, #19, #20, #21, #22
Metric: visible_rows_ready_median_ms 826.7ms -> 409.9ms (-50.4%)
SlexAxton added a commit that referenced this pull request Mar 30, 2026
Large-tree render hot-path optimizations

Reduce first visible-row readiness for large virtualized trees by removing redundant tree-building/traversal work and tightening hot path data structures.

Experiments: #2, #3, #6, #7, #9, #10, #12, #13, #14, #19, #20, #21, #22
Metric: visible_rows_ready_median_ms 826.7ms -> 409.9ms (-50.4%)
SlexAxton added a commit that referenced this pull request Apr 10, 2026
Reduce whole-tree startup work across the path-store tree controller and store setup. This combines the kept changes that removed duplicate controller metadata passes, made projection data lazy/partial, deferred child lookup maps, optimized initial expansion handling, and added file-only builder fast paths for the profile workload.

Experiments: #2, #3, #4, #5, #7, #8, #9, #10, #11, #12, #13, #14, #21, #23, #28, #29
Metric: visible_rows_ready_ms 1002.3 → 197.5 (-80.3%)
SlexAxton added a commit that referenced this pull request Apr 10, 2026
Overhaul path-store startup hot path

Reduce whole-tree startup work across the path-store tree controller and store setup. This combines the kept changes that removed duplicate controller metadata passes, made projection data lazy/partial, deferred child lookup maps, optimized initial expansion handling, and added file-only builder fast paths for the profile workload.

Experiments: #2, #3, #4, #5, #7, #8, #9, #10, #11, #12, #13, #14, #21, #23, #28, #29
Metric: visible_rows_ready_ms 1002.3 → 197.5 (-80.3%)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant