diff

package
v0.0.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 31, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package diff holds the canonical, view-independent representation of a set of changes under review, plus projections of that model into rows for display.

The model here is deliberately decoupled from any terminal concern: rendered screen rows are never canonical comment locations. Comments point at semantic positions in this model (see the review package), which survive terminal resize, context folding, and switching between unified and split layouts.

Index

Constants

This section is empty.

Variables

View Source
var TabWidth = 4

TabWidth is the number of columns a tab expands to when parsing diff lines. It defaults to 4 and may be overridden from configuration before parsing.

Functions

func NormalizeContent added in v0.0.5

func NormalizeContent(b []byte) []byte

NormalizeContent expands tabs in each line of raw file content to the display form hunk text uses (TabWidth tab stops). Fetched whole files must pass through this before being compared with — or rendered next to — parsed diff lines: the parser expands tabs at parse time, so raw content would mismatch on every indented line.

func Relocate

func Relocate(files []FileDiff, loc Location) (Location, RelocateResult)

Relocate attempts to map loc onto the given (newer) set of file diffs using the location's captured context anchor. It first checks whether the exact side+line still holds the same content; failing that, it searches the file (following a rename) for a line with the same anchor text and non-conflicting surrounding context, relocating only when the match is unique. When several candidates match — or none do — the location is reported as orphaned and left unchanged so the reviewer can decide.

Types

type AnnotationEdge

type AnnotationEdge uint8

AnnotationEdge distinguishes the border rows of a boxed annotation from its text rows. EdgeDivider separates items of a thread that share one box.

const (
	EdgeNone AnnotationEdge = iota
	EdgeTop
	EdgeBottom
	EdgeDivider
)

type ContextAnchor

type ContextAnchor struct {
	HunkHeader  string
	Before      []string
	AnchorText  string
	After       []string
	ContentHash string
}

ContextAnchor records enough surrounding content to relocate a comment after the diff changes: the hunk header, a few lines of context around the anchor, and a hash of the anchored content.

func NewContextAnchor

func NewContextAnchor(f *FileDiff, hunkIndex, lineIndex, n int) ContextAnchor

NewContextAnchor builds an anchor for the line at (hunkIndex, lineIndex) in f, capturing up to n lines of context on each side.

type DiffLine

type DiffLine struct {
	Kind LineKind
	Text string

	// Raw is the line's original text when tab expansion changed it, and ""
	// when Text is already verbatim. Display uses Text; anything that leaves
	// the program as code — change suggestions especially — must use
	// RawText, or the patch would silently swap tabs for spaces.
	Raw string

	// OldLine is the 1-based line number on the old side, or nil for additions.
	OldLine *int
	// NewLine is the 1-based line number on the new side, or nil for deletions.
	NewLine *int

	// PatchPosition is the 1-based offset of this line within the file's patch
	// (counting from the first hunk header, as GitHub numbers review positions),
	// or nil for lines that have no patch position. Preserved to reconcile our
	// parsed model with comments returned by the GitHub API.
	PatchPosition *int
}

DiffLine is one logical line of a hunk. Exactly one of OldLine/NewLine is nil for pure additions/deletions; both are set for context lines.

func (*DiffLine) Changed

func (l *DiffLine) Changed() bool

Changed reports whether the line is an addition or deletion.

func (*DiffLine) LineNumber

func (l *DiffLine) LineNumber(side Side) (int, bool)

LineNumber returns the line number a DiffLine carries on the given side, or (0, false) if the line does not exist on that side (e.g. the new side of a deletion).

func (*DiffLine) RawText added in v0.0.7

func (l *DiffLine) RawText() string

RawText returns the line's verbatim source text: the original bytes when tab expansion altered the display form, Text otherwise. This is what code leaving the program (suggestion bodies, patches) must be built from.

type DisplayCell

type DisplayCell struct {
	LineNumber *int
	Kind       LineKind
	Text       string
}

DisplayCell is one rendered cell: a line number (optional), the kind of line it represents (for styling), and the raw text. A nil cell renders as blank (used for the empty side of an unpaired addition/deletion in split view).

type DisplayRow

type DisplayRow struct {
	Left  *DisplayCell
	Right *DisplayCell

	// Source is the semantic location of this row (start line = end line here);
	// nil for rows that cannot be commented on (e.g. hunk headers). For split
	// rows carrying both sides it points at the right/new side.
	Source *Location

	// AltSource is the location of the opposite (left/old) side when a split
	// row carries both sides — a paired deletion/addition or a context line —
	// so the cursor can select either side. Nil elsewhere.
	AltSource *Location

	// Annotation marks a display-only row injected under a diff line (e.g. an
	// inline comment preview). Annotation rows carry no Source, are skipped by
	// navigation, and are not hunk headers.
	Annotation bool

	// Edge marks the horizontal border rows of a boxed annotation; text rows
	// keep EdgeNone. Meaningful only when Annotation is set.
	Edge AnnotationEdge

	// Continuation marks the overflow of a wrapped diff line: it renders with
	// the line's own styling but carries no line numbers or Source, so
	// navigation and selection treat the logical line as one unit.
	Continuation bool

	// Separator marks the boundary row drawn between hunks, so the reader can
	// see where one excerpt ends and the next begins when the surrounding
	// file context is not shown. Display-only: no Source, skipped by
	// navigation.
	Separator bool

	// Pre marks an annotation text row whose content is preformatted terminal
	// output (an image rendered to cells): renderers must clip it ANSI-aware
	// and never wrap or restyle it. Meaningful only with Annotation set.
	Pre bool
}

DisplayRow is one rendered row of a diff view. In unified layout only Left is used to carry the number gutter is folded into a single cell; in split layout Left and Right are the two sides. Source points back at the semantic location this row anchors to, so a comment made on a screen row resolves to a stable Location rather than a terminal coordinate.

func RenderSplit

func RenderSplit(f *FileDiff) []DisplayRow

RenderSplit projects a FileDiff into split-layout rows: a left (old) column and a right (new) column. Context lines occupy both columns. Change blocks are paired with a simple rule — gather consecutive deletions, then the immediately following additions, pair them by index, and render any leftover against a blank cell on the opposite side. Similarity/intraline matching is intentionally out of scope for v1.

func RenderUnified

func RenderUnified(f *FileDiff) []DisplayRow

RenderUnified projects a FileDiff into unified-layout rows: each logical diff line becomes exactly one row. The Left cell carries the old-side number, the Right cell the new-side number, and the text is shared; callers style by Kind. Every content row gets a Source so it can be commented on.

func RenderUnifiedContext added in v0.0.4

func RenderUnifiedContext(f *FileDiff, content []byte) ([]DisplayRow, error)

RenderUnifiedContext projects a FileDiff over the full new-side file content: every file line becomes a context row, with the hunks' own rows (including deletions) spliced in at their positions — the diff as it reads inside the file, like an unbounded -U. Each hunk is bracketed by boundary rows (a rule plus its @@ header on entry, a rule on exit) so the reader can tell exactly where the reviewed excerpt begins and ends inside the surrounding file. Gap rows carry both line numbers but no Source: hosts anchor review comments to diff positions, so lines outside the hunks are deliberately not commentable. Hunk rows keep their exact Sources, so comments, threads, and navigation behave identically in both views.

Every context/addition line is verified against the file content; a mismatch aborts with an error rather than rendering a lie — it means the content is from the wrong revision.

type FileDiff

type FileDiff struct {
	OldPath string
	NewPath string

	Status FileStatus
	Hunks  []Hunk

	IsBinary  bool
	IsRenamed bool

	// RawPatch is the original unified-diff text for this file, preserved for
	// export snippets and (later) GitHub reconciliation.
	RawPatch string
}

FileDiff is the full set of changes to a single file.

func FindFileFor added in v0.0.2

func FindFileFor(files []FileDiff, path string) *FileDiff

FindFileFor returns the file whose old or new path matches path (so a comment made before a rename still finds its file). Exported because exchange import needs the same rename-tolerant lookup when anchoring foreign comments.

func ParsePatch

func ParsePatch(r io.Reader) ([]FileDiff, error)

ParsePatch reads a unified/Git patch and adapts it into our canonical model.

It computes per-line old/new numbers by walking each fragment from its starting positions, and assigns each line a GitHub-style PatchPosition: the 1-based offset counted down from the file's first "@@" header, where every content line and every subsequent hunk header advances the position.

func ParsePatchBytes

func ParsePatchBytes(b []byte) ([]FileDiff, error)

ParsePatchBytes is a convenience wrapper over ParsePatch for in-memory patches.

func (*FileDiff) FindBySideLine

func (f *FileDiff) FindBySideLine(side Side, number int) (hunkIndex, lineIndex int, ok bool)

FindBySideLine locates the (hunkIndex, lineIndex) of the line that occupies the given number on the given side. It returns (-1, -1, false) when no such line is present in the diff.

func (*FileDiff) LineAt

func (f *FileDiff) LineAt(hunkIndex, lineIndex int) *DiffLine

LineAt returns the line at (hunkIndex, lineIndex), or nil if out of range.

func (*FileDiff) Path

func (f *FileDiff) Path() string

Path returns the most meaningful path for display: the new path unless the file was deleted, in which case the old path.

type FileStatus

type FileStatus uint8

FileStatus describes what happened to a file across the diff.

const (
	// StatusModified is the default: content changed in place.
	StatusModified FileStatus = iota
	StatusAdded
	StatusDeleted
	StatusRenamed
	StatusCopied
	StatusBinary
)

func (FileStatus) String

func (s FileStatus) String() string

String renders a short human label for the status.

type Hunk

type Hunk struct {
	Header string
	Lines  []DiffLine
}

Hunk is a contiguous block of changes with its "@@ ... @@" header.

type LineKind

type LineKind uint8

LineKind classifies a single logical diff line.

const (
	// LineContext is an unchanged line present on both sides.
	LineContext LineKind = iota
	// LineAddition is a line added on the new side.
	LineAddition
	// LineDeletion is a line removed from the old side.
	LineDeletion
	// LineMetadata is a non-content marker line (e.g. "No newline at end of file").
	LineMetadata
)

func (LineKind) Side

func (k LineKind) Side() Side

Side reports which diff side a line of this kind naturally belongs to. Additions and context default to the right; deletions to the left.

type Location

type Location struct {
	Path string

	// CommitOID is the head commit the location was captured against, when
	// known (empty in pure patch-file mode).
	CommitOID string

	Side Side

	// StartLine and EndLine are 1-based line numbers on Side, inclusive.
	StartLine int
	EndLine   int

	// HunkIndex and LineIndex point at the anchor line within a specific
	// FileDiff (the start of the selection). They are convenient for rendering
	// but are always re-derivable from Path+Side+StartLine.
	HunkIndex int
	LineIndex int

	// Anchor captures surrounding content so the comment can be relocated when
	// the underlying diff changes (e.g. the PR is updated).
	Anchor ContextAnchor
}

Location is a semantic, view-independent position in a diff. It is the canonical anchor for a review comment: it survives terminal resize, context folding, and switching between unified and split layouts, and it maps cleanly onto GitHub's line-oriented review-comment fields (path, side, line, start_line, start_side).

func (Location) Single

func (l Location) Single() bool

Single reports whether the location covers exactly one line.

type RelocateResult

type RelocateResult uint8

RelocateResult reports the outcome of trying to map a Location captured against one diff onto a newer diff.

const (
	// RelocateExact: the location still resolves at the same place.
	RelocateExact RelocateResult = iota
	// RelocateMoved: the location was uniquely re-found elsewhere.
	RelocateMoved
	// RelocateOrphaned: no unique match exists; a human must reposition it.
	RelocateOrphaned
)

type Side

type Side uint8

Side identifies which side of a diff a line or selection refers to. It maps directly onto GitHub's LEFT/RIGHT review-comment sides so that PR mode is a no-op switch over the same model.

const (
	// SideLeft is the old (pre-image) side: deleted lines live here.
	SideLeft Side = iota
	// SideRight is the new (post-image) side: added and context lines live here.
	SideRight
)

func (Side) String

func (s Side) String() string

String returns the GitHub API spelling of the side ("LEFT"/"RIGHT").

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL