In-memory line diffing and hunk operations.
Uses List.myers_difference/2 to compute line-level diffs between
two versions of text. Produces hunk structs consumed by git gutter
rendering, hunk navigation, AI diff review, and patch generation.
Pure computation with no side effects.
Summary
Types
A hunk represents a contiguous group of changed lines.
A contiguous region of change.
Result of a three-way merge.
A hunk in a three-way merge result.
Functions
Diffs base lines against current lines and returns a list of hunks.
Generates a unified diff patch for a single hunk.
Finds the hunk containing or nearest to the given buffer line.
Three-way merge: given a common ancestor and two divergent versions (fork and parent), produces a merged result or identifies conflicts.
Finds the start line of the next hunk after the given line.
Returns nil if no hunk follows.
Finds the start line of the previous hunk before the given line.
Returns nil if no hunk precedes.
Returns buffer lines with the given hunk reverted to base content.
Converts hunks to a per-line sign map for the gutter renderer.
Types
@type hunk() :: %{ type: hunk_type(), start_line: non_neg_integer(), count: non_neg_integer(), old_start: non_neg_integer(), old_count: non_neg_integer(), old_lines: [String.t()] }
A hunk represents a contiguous group of changed lines.
type— the kind of changestart_line— first buffer line affected (0-indexed)count— number of buffer lines in this hunk (0 for pure deletions)old_start— first line in the base content (0-indexed)old_count— number of base lines replaced/deletedold_lines— the original lines from base (for revert and preview)
@type hunk_type() :: :added | :modified | :deleted
A contiguous region of change.
:added— new lines not in the base:modified— lines that differ from the base:deleted— lines removed from the base (rendered on the line above)
@type merge3_result() :: {:ok, [String.t()]} | {:conflict, [merge_hunk()]}
Result of a three-way merge.
A hunk in a three-way merge result.
{:resolved, lines}— auto-merged content (from either side or unchanged){:conflict, fork_lines, parent_lines}— both sides changed the same region
Functions
Diffs base lines against current lines and returns a list of hunks.
Both inputs should be lists of strings (one per line, without trailing newlines).
Generates a unified diff patch for a single hunk.
The patch can be fed to git apply --cached to stage just this hunk.
Uses the standard unified diff format with a/ and b/ path prefixes.
@spec hunk_at_line([hunk()], non_neg_integer()) :: hunk() | nil
Finds the hunk containing or nearest to the given buffer line.
@spec merge3([String.t()], [String.t()], [String.t()]) :: {:ok, [String.t()]} | {:conflict, [merge_hunk()]}
Three-way merge: given a common ancestor and two divergent versions (fork and parent), produces a merged result or identifies conflicts.
Non-overlapping changes from both sides are merged automatically. Overlapping changes (both sides modified the same region of the ancestor) become conflicts.
Returns {:ok, merged_lines} when all changes merge cleanly, or
{:conflict, merge_hunks} when at least one conflict exists.
The hunks list contains both resolved and conflicting regions.
@spec next_hunk_line([hunk()], non_neg_integer()) :: non_neg_integer() | nil
Finds the start line of the next hunk after the given line.
Returns nil if no hunk follows.
@spec prev_hunk_line([hunk()], non_neg_integer()) :: non_neg_integer() | nil
Finds the start line of the previous hunk before the given line.
Returns nil if no hunk precedes.
Returns buffer lines with the given hunk reverted to base content.
For added hunks: removes the added lines. For modified hunks: replaces with the old lines. For deleted hunks: re-inserts the old lines at the deletion point.
@spec signs_for_hunks([hunk()]) :: %{required(non_neg_integer()) => hunk_type()}
Converts hunks to a per-line sign map for the gutter renderer.
Returns %{line_number => :added | :modified | :deleted}.
Deleted hunks are placed on the line above the deletion point
(or line 0 if deleted at the start).