codediff.nvim

repository·main·Indexed 23 days ago

https://github.com/esmuellert/codediff.nvim

A 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.

Tokens
44.4K
Snippets
81
Records
191
Agent score
81%

What's inside codediff.nvim

  1. Overview of codediff.nvim

    main
    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.
  2. Cross-Platform Compatibility Overview

    main
    The codediff.nvim C 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.
  3. Project structure of codediff.nvim

    main

    The 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 B
  4. Compare VSCode diff output with the C implementation

    main

    The generated JavaScript tool produces output in the exact same format as the C implementation's print_linesdiff function. 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 diff command:

    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.
  5. Understand the codediff.nvim architecture

    main

    The plugin mimics VSCode's diff rendering architecture by splitting responsibilities between C and Lua:

    • Diff Computation (C): The c-diff-core/diff_core.c module 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.lua module 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.

  6. Word Boundary Extension Algorithm

    main

    The extendDiffsToEntireWordIfAppropriate() function ensures that diffs don't cut words in half. It follows these steps:

    1. Invert diffs to identify the 'equal' (unchanged) regions.
    2. Scan equal regions at both the start and end of the region to find words containing the boundary characters.
    3. 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).
    4. Merge the newly extended word ranges with the original diffs, maintaining sorted order and handling overlaps.
  7. Populate custom data in Neo-tree nodes

    main

    When building a custom tree in the navigate function, you can attach arbitrary data to a node using the extra field. 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,
      }
    end
    item.extra = {
      git_status = status,
      diff_stats = diff_stats,
      commit1 = commit1,
      commit2 = commit2,
    }
  8. Understand the Diff Parity Evaluation Process

    main

    The 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.

  9. How the Filler Line Algorithm works

    main

    The filler line algorithm in codediff.nvim ensures 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:

    1. 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."
    2. 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.

  10. Logic for `removeVeryShortMatchingLinesBetweenDiffs`

    main

    The removeVeryShortMatchingLinesBetweenDiffs function 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:

    1. Gap Constraint: The number of non-whitespace characters in the unchanged gap between the diffs must be ≤ 4.
    2. Size Constraint: At least one of the two diffs must have a total range sum (the sum of its length in seq1 and its length in seq2) > 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;
    }
  11. Line-level diff pipeline architecture

    main

    The 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:

    1. line_level.c (Orchestrator): Consolidates the pipeline steps and calls the underlying algorithms.
    2. 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)
    3. optimize.c (Shared Optimizations): Provides primitives used by both line and character-level diffs, such as optimize_sequence_diffs() and remove_very_short_matching_lines_*().
    4. char_level.c (Character Refinement): Performs the final step of refining line alignments using character-level diffs.
  12. How boundary scoring works in line optimization

    main

    During the shiftSequenceDiffs step, the algorithm uses getBoundaryScore() 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_before is the number of leading spaces/tabs on the line immediately preceding the boundary.
    • indentation_after is the number of leading spaces/tabs on the line at the boundary.