codediff.nvim
repository·main·Indexed 23 days ago
https://github.com/esmuellert/codediff.nvimA Neovim plugin that replicates the VSCode diff experience with high-performance, two-tier highlighting. It supports side-by-side and inline layouts, git revision comparisons via the :CodeDiff command, and moved code detection. The plugin utilizes a C-based diff computation engine with FFI and OpenMP for parallelization. It also includes a declarative argparse library for building command-line interfaces within Neovim.
What's inside codediff.nvim
- codediff.nvim is a Neovim plugin that provides VSCode-style diff rendering. It features a two-tier highlighting system: light backgrounds for modified lines (green for insertions, red for deletions) and deep/dark character-level highlights for exact changes within those lines. It supports both side-by-side and inline (unified) layouts.
Cross-Platform Compatibility Overview
mainThecodediff.nvimC diff core is designed to be cross-platform, supporting Windows (MSVC, MinGW), Linux, macOS, and BSD. It achieves this by using a portable compatibility layer that avoids POSIX-specific dependencies and uses standard C89/C99 compliant code. This ensures the core can be compiled with various compilers like GCC, Clang, and MSVC without platform-specific errors.Project structure of codediff.nvim
mainThe repository is organized into the following structure:
nvim-vscode-diff/ ├── README.md # Installation & usage instructions ├── Makefile # Build automation for C module ├── plugin/ │ └── vscode-diff.lua # Lazy.nvim entry point ├── lua/ │ └── vscode-diff/ │ ├── init.lua # Main Lua interface │ ├── render.lua # Buffer rendering logic │ └── config.lua # Plugin configuration ├── c-diff-core/ │ ├── diff_core.c # C implementation (diff + render plan) │ ├── diff_core.h # C header file │ └── test_diff_core.c # C unit tests └── tests/ ├── test_render.lua # Lua rendering tests └── fixtures/ ├── file_a.txt # Test input file A └── file_b.txt # Test input file BCompare VSCode diff output with the C implementation
mainThe generated JavaScript tool produces output in the exact same format as the C implementation's
print_linesdifffunction. This makes it an ideal 'source of truth' for validation, regression testing, and debugging.Direct Comparison via CLI
You can compare the outputs of the VSCode tool and the C tool directly using the
diffcommand:diff <(node vscode-diff.mjs f1 f2) <(./build/diff f1 f2)Integration Patterns
- Validation: Use the JS tool as an oracle to verify the correctness of the C implementation.
- Regression Testing: Ensure that changes to the C code do not cause algorithmic divergence.
- Automated Testing: Compare outputs byte-for-byte in test suites to detect any format or logic discrepancies.
Understand the codediff.nvim architecture
mainThe plugin mimics VSCode's diff rendering architecture by splitting responsibilities between C and Lua:
- Diff Computation (C): The
c-diff-core/diff_core.cmodule handles the heavy lifting of computing diffs and generating a 'render plan'. This is designed for performance. - Buffer Rendering (Lua): The
lua/vscode-diff/render.luamodule receives the render plan from C and uses Neovim APIs to apply decorations, filler lines, and virtual text to the buffers.
This architecture ensures that the computationally expensive parts of the diffing process are handled by C, while the UI-specific logic leverages Neovim's Lua integration.
- Diff Computation (C): The
Word Boundary Extension Algorithm
mainThe
extendDiffsToEntireWordIfAppropriate()function ensures that diffs don't cut words in half. It follows these steps:- Invert diffs to identify the 'equal' (unchanged) regions.
- Scan equal regions at both the start and end of the region to find words containing the boundary characters.
- Evaluate extension:
- Normal mode: Extend the diff if the number of equal characters in the word is less than $2/3$ of the total word length (
equal_chars < word_len * 2/3). - Force mode (used for subwords): Extend if any part of the word is changed (
equal_chars < word_len).
- Normal mode: Extend the diff if the number of equal characters in the word is less than $2/3$ of the total word length (
- Merge the newly extended word ranges with the original diffs, maintaining sorted order and handling overlaps.
Populate custom data in Neo-tree nodes
mainWhen building a custom tree in the
navigatefunction, you can attach arbitrary data to a node using theextrafield. This data is accessible to components during the rendering phase.-- Inside M.navigate local success, item = pcall(file_items.create_item, context, full_path, "file") if success then item.status = status item.extra = { git_status = status, diff_stats = diff_stats, -- Custom data for components commit1 = commit1, commit2 = commit2, } enditem.extra = { git_status = status, diff_stats = diff_stats, commit1 = commit1, commit2 = commit2, }Understand the Diff Parity Evaluation Process
mainThe project uses a 'Parity Evaluation' process to measure how closely its C implementation of the diff algorithm matches the VS Code TypeScript implementation. This is measured across several stages of the diff pipeline: Core Myers diff, Line-level heuristics, and Character refinement.
Parity is scored using two different scales depending on the evaluation phase:
- Phase 1 scale: 0.0–1.0 (where 1.0 is full parity).
- Phase 3+ scale: 1–5 (where 5 is full parity, 3 is partial, and 1 is missing).
Developers can use these evaluation documents to identify gaps in algorithm selection, scoring models, or data handling (such as whitespace bookkeeping) that cause the C implementation to diverge from VS Code's expected behavior.
How the Filler Line Algorithm works
mainThe filler line algorithm in
codediff.nvimensures that corresponding lines in a side-by-side diff view remain vertically aligned. When one side of a diff has more lines than the other within a changed region, the algorithm inserts "filler lines" (blank placeholder lines) on the shorter side.This mechanism replicates VSCode's diff rendering behavior to ensure that even when lines are added or removed, the user can easily track which lines correspond to each other across the two editors.
Algorithm Architecture
The process is divided into two distinct phases:
- Phase 1: Create Alignments: The algorithm processes each diff mapping to identify ranges of lines on both the original and modified sides that must be vertically synchronized. These are called "alignments."
- Phase 2: Convert Alignments to Fillers: For every alignment where the number of lines on the original side does not match the number of lines on the modified side, the algorithm calculates the difference and inserts the necessary number of filler lines on the side with fewer lines.
Example of Phase 2 logic: If an alignment maps 1 line on the original side to 4 lines on the modified side (
orig[35,36) → mod[35,39)), the algorithm will insert 3 filler lines on the original side to maintain vertical synchronization.Logic for `removeVeryShortMatchingLinesBetweenDiffs`
mainThe
removeVeryShortMatchingLinesBetweenDiffsfunction is responsible for the most visible line-level optimization. It joins consecutive diffs that are separated by very small amounts of unchanged content (e.g., a single blank line or a closing brace).The Joining Algorithm: It iterates up to 10 times for convergence. A pair of consecutive diffs is merged if they meet two specific criteria:
- Gap Constraint: The number of non-whitespace characters in the unchanged gap between the diffs must be ≤ 4.
- Size Constraint: At least one of the two diffs must have a total range sum (the sum of its length in
seq1and its length inseq2) > 5.
This logic prevents the algorithm from merging tiny, insignificant changes while ensuring that larger changes separated by minor separators are treated as a single cohesive block.
// Logic Summary if (non_ws_chars <= 4 && (current_total > 5 || next_total > 5)) { merge(current, next); changed = true; }Line-level diff pipeline architecture
mainThe line-level diffing process is orchestrated by
line_level.c, which manages the transition from raw lines to optimized alignments. The architecture follows a hierarchical structure:line_level.c(Orchestrator): Consolidates the pipeline steps and calls the underlying algorithms.myers.c(Diff Algorithm): Provides different implementations depending on sequence size:myers_diff_algorithm()myers_dp_diff_algorithm()(for smaller sequences)myers_nd_diff_algorithm()(for larger sequences)
optimize.c(Shared Optimizations): Provides primitives used by both line and character-level diffs, such asoptimize_sequence_diffs()andremove_very_short_matching_lines_*().char_level.c(Character Refinement): Performs the final step of refining line alignments using character-level diffs.
How boundary scoring works in line optimization
mainDuring the
shiftSequenceDiffsstep, the algorithm usesgetBoundaryScore()to determine the best positions to shift diff boundaries. The scoring is based on indentation levels to ensure boundaries occur at less-indented code blocks, which results in cleaner diffs.The scoring formula is:
score = 1000 - (indentation_before + indentation_after)- Lower indentation results in a higher score.
indentation_beforeis the number of leading spaces/tabs on the line immediately preceding the boundary.indentation_afteris the number of leading spaces/tabs on the line at the boundary.