diff

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Aug 11, 2026 License: AGPL-3.0 Imports: 6 Imported by: 0

Documentation

Overview

Package diff models a unified diff: the files it touches, their hunks, and the lines within them, down to the runs that changed inside a single line.

It knows nothing about terminals. The review screen renders what this package produces and addresses it by index, which is what lets the awkward parts — line numbering, intra-line spans, mapping a selection back to the position a provider will accept — be tested without a terminal at all.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Doc

type Doc struct {
	Files Files
	// contains filtered or unexported fields
}

Doc is a diff being reviewed: the files, how they are folded, and where the caret and selection are.

func New

func New(files Files) *Doc

New builds a document from an already-parsed diff, with every file expanded.

func NewDoc

func NewDoc(raw string) *Doc

NewDoc builds a document from raw unified diff text, with every file expanded.

func (*Doc) AllFolded

func (d *Doc) AllFolded() bool

AllFolded reports whether every file is folded.

func (*Doc) Current

func (d *Doc) Current() (Row, bool)

Current is the row under the caret.

func (*Doc) CurrentFile

func (d *Doc) CurrentFile() (File, int, bool)

CurrentFile is the file the caret is in.

func (*Doc) Cursor

func (d *Doc) Cursor() int

Cursor is the caret's row.

func (*Doc) CursorToFile

func (d *Doc) CursorToFile(file int)

CursorToFile puts the caret on a file's header row.

func (*Doc) CycleMode

func (d *Doc) CycleMode() Mode

CycleMode steps line → word → char → line.

One key rather than three: the modes are a progression, and a reviewer reaching for finer detail wants the next notch, not to remember which key means which.

func (*Doc) DropSelection

func (d *Doc) DropSelection()

DropSelection clears the selection, leaving the caret alone.

func (*Doc) ExtendSubLine

func (d *Doc) ExtendSubLine(delta int)

ExtendSubLine grows or shrinks the sub-line selection by one unit of the current mode: a word in Words, a rune in Chars.

func (*Doc) InSelection

func (d *Doc) InSelection(row int) bool

InSelection reports whether a row is inside the selection.

func (*Doc) Len

func (d *Doc) Len() int

Len is how many rows there are.

func (*Doc) Mode

func (d *Doc) Mode() Mode

Mode reports the current granularity.

func (*Doc) Move

func (d *Doc) Move(delta, height int)

Move steps the caret, dragging a selection with it and scrolling to keep it on screen.

func (*Doc) MoveFile

func (d *Doc) MoveFile(delta, height int)

MoveFile jumps to the next or previous file, stopping at the ends rather than wrapping — wrapping past the last file reads as a bug.

func (*Doc) MoveTo

func (d *Doc) MoveTo(row, height int)

MoveTo puts the caret on a row.

func (*Doc) Progress

func (d *Doc) Progress() (read, total int)

Progress is how many files have been read, of how many.

func (*Doc) Row

func (d *Doc) Row(i int) (Row, bool)

Row returns one row.

func (*Doc) Rows

func (d *Doc) Rows() []Row

Rows is every row in the document.

func (*Doc) Scroll

func (d *Doc) Scroll(delta, height int)

Scroll moves the window without moving the caret, which is what a mouse wheel does.

func (*Doc) ScrollIntoView

func (d *Doc) ScrollIntoView(height int)

ScrollIntoView moves the window the least it can to show the caret.

func (*Doc) SelectHunk

func (d *Doc) SelectHunk()

SelectHunk marks every content line of the hunk the caret is in.

func (*Doc) SelectedLines

func (d *Doc) SelectedLines() []Line

SelectedLines is the content lines the selection covers, in order.

func (*Doc) SelectedText

func (d *Doc) SelectedText() string

SelectedText is what the selection actually covers: the marked span of one line in a sub-line mode, the whole lines otherwise.

func (*Doc) Selecting

func (d *Doc) Selecting() bool

Selecting reports whether a selection is open.

func (*Doc) Selection

func (d *Doc) Selection() Selection

Selection is the marked range, normalised so From <= To.

With nothing selected it is the caret's own row, so every action that works on a selection also works on where you are standing — there is no state in which `c` has nothing to comment on.

func (*Doc) SetAllFolded

func (d *Doc) SetAllFolded(collapsed bool)

SetAllFolded folds or unfolds everything.

func (*Doc) SetFold

func (d *Doc) SetFold(file int, collapsed bool)

SetFold folds one file.

func (*Doc) SetNotes

func (d *Doc) SetNotes(notes map[NoteKey][]string)

SetNotes pins remarks under the lines they were made about. Passing a fresh map replaces the lot, which is what happens when one is added or dropped.

func (*Doc) Snippet

func (d *Doc) Snippet() string

Snippet is the selection as diff text, markers and all, for quoting back in a comment.

func (*Doc) State

func (d *Doc) State(file int) (collapsed, read bool)

State reports how a file is folded and whether it has been read.

func (*Doc) SubLineRange

func (d *Doc) SubLineRange(row int) (start, end int, ok bool)

SubLineRange is the byte range selected within a row, and whether there is one. Only a sub-line selection on the caret's own row has one.

func (*Doc) ToggleFold

func (d *Doc) ToggleFold()

ToggleFold folds or unfolds the file the caret is in — whichever file that is, which is the point of hanging it off the caret rather than off an index the screen has to track separately.

func (*Doc) ToggleRead

func (d *Doc) ToggleRead(height int)

ToggleRead marks the caret's file read and folds it, so the list shrinks to what is left to review; unmarking restores it.

func (*Doc) ToggleSelect

func (d *Doc) ToggleSelect()

ToggleSelect starts a selection at the caret, or drops the one there is.

func (*Doc) Top

func (d *Doc) Top() int

Top is the first row on screen.

type File

type File struct {
	// OldPath and NewPath differ on a rename, and one is empty when the file
	// was added or deleted.
	OldPath, NewPath string
	Hunks            []Hunk
	Added, Removed   int
	Binary           bool
	// Mode records a change the diff reports but has no lines for, such as a
	// permission change, so the file is not shown as empty for no reason.
	Mode string
}

File is one file's worth of a diff.

func (File) Path

func (f File) Path() string

Path is the name to show and to send to the provider: where the file ended up, or where it was if it is gone.

func (File) Renamed

func (f File) Renamed() bool

Renamed reports whether the file moved.

type Files

type Files []File

Files is a whole diff.

func Parse

func Parse(raw string) Files

Parse reads a unified diff.

It is written to survive whatever the providers actually send rather than only well-formed output: a file with no hunks, a hunk with no counts, a deleted file with no "+++ b/" line. Anything it cannot place is dropped rather than guessed at, because a diff that lies about line numbers would anchor a comment to the wrong line.

func (Files) Stat

func (fs Files) Stat() (added, removed int)

Stat totals what the diff changes.

type Highlighter

type Highlighter struct {
	// contains filtered or unexported fields
}

Highlighter tokenises source lines for one file.

It works per line rather than per file because the review screen only ever draws the visible window, and lexing a ten-thousand-line diff to show forty rows of it is work nobody asked for. The cost is that a lexer cannot see across lines — a multi-line string or comment is highlighted as if each line stood alone. For a diff that is the right trade: a diff is already a set of fragments, and hunks routinely start inside a construct anyway.

func NewHighlighter

func NewHighlighter(path string) *Highlighter

NewHighlighter picks a lexer from the file's name. A file whose language is unknown gets a highlighter that returns the line unchanged, so callers never have to check for nil.

func (*Highlighter) Enabled

func (h *Highlighter) Enabled() bool

Enabled reports whether the file's language was recognised.

func (*Highlighter) Tokens

func (h *Highlighter) Tokens(line string) []Token

Tokens splits one line into its syntactic runs.

A line that cannot be lexed comes back as a single untyped run rather than an error: losing colour on one line is not worth failing a review screen over.

type Hunk

type Hunk struct {
	OldStart, OldCount int
	NewStart, NewCount int
	// Section is the text after the closing @@, which git fills with the
	// enclosing function.
	Section string
	Lines   []Line
}

Hunk is one @@ block.

func (Hunk) Header

func (h Hunk) Header() string

Header renders the hunk's @@ line as git wrote it.

type Kind

type Kind int

Kind is what a line, or a run within one, represents.

const (
	Context Kind = iota
	Added
	Removed
	Equal = Context
)

The kinds of line. Equal doubles as "unchanged run" inside a line.

type Line

type Line struct {
	Kind Kind
	// Old and New are the line's number on each side, zero where the line does
	// not exist on that side.
	Old, New int
	Text     string
	// Segments splits Text into runs that changed and runs that did not, for a
	// changed line paired with its counterpart. A line with no counterpart
	// carries a single Equal segment covering the whole of it.
	Segments []Segment
	// NoNewline marks the line git flagged with "\ No newline at end of file",
	// which must survive a suggestion round-trip.
	NoNewline bool
}

Line is one line of one hunk.

func (Line) Marker

func (l Line) Marker() string

Marker is the +/- git prefixes the line with.

func (Line) Number

func (l Line) Number() int

Number is the line number to show: the new side, falling back to the old for a deleted line. One column carries both, because a line only ever has a number that matters on one side.

func (Line) Words

func (l Line) Words() []Segment

Words returns a line's tokens with their byte offsets, which is what a word-granular selection steps through.

type Mode

type Mode int

Mode is how far a selection reaches at a time.

const (
	Lines Mode = iota
	Words
	Chars
)

The three granularities, in the order the cycle key steps through them.

func (Mode) String

func (m Mode) String() string

String names the mode for the status line.

type NoteKey

type NoteKey struct {
	File     int
	Old, New int
}

NoteKey identifies the line a note hangs under. It is the line's numbers rather than its row index, because folding moves rows and does not move lines.

func KeyOf

func KeyOf(file int, l Line) NoteKey

KeyOf is the note key for a row's line.

type Row

type Row struct {
	Kind RowKind
	File int
	Hunk int
	// Line is the content the row shows; the zero value for a header row.
	Line Line
	// Note is the text of a NoteRow — a remark pinned under the line it is
	// about, so an unpublished comment is visible where it was made.
	Note string
}

Row is one line on screen: what it shows, and where it came from.

Rows are how the screen and the model agree on a position. The caret is a row index, so folding a file, marking it read, scrolling and selecting all work on the same number, and none of them can drift apart from the others.

func (Row) Selectable

func (r Row) Selectable() bool

Selectable reports whether the row is something a comment can be made about.

type RowKind

type RowKind int

RowKind is what a rendered row shows.

const (
	FileRow RowKind = iota
	HunkRow
	ContentRow
	NoteRow
	BlankRow
)

The rows a diff is drawn as.

type Segment

type Segment struct {
	Kind Kind
	Text string
	// Start is the byte offset of the run within the line, so a character
	// selection and a word selection address the same string.
	Start int
}

Segment is a run within a line: the text, and whether it is part of what changed.

func (Segment) End

func (s Segment) End() int

End is the offset one past the run.

type Selection

type Selection struct {
	Mode Mode
	// From and To are row indices, ordered.
	From, To int
	// Start and End are byte offsets into the row's line, used only in Words
	// and Chars mode.
	Start, End int
	Active     bool
}

Selection is what the caret has marked.

Line selections span rows. Word and character selections stay within one line — which is not a shortcut but the truth of what can be expressed: neither provider anchors a comment to a column, so a sub-line selection describes a span *within* a line, and a span across lines is a line selection by definition.

type Token

type Token struct {
	Text string
	// Type is chroma's classification. The renderer maps it to a colour; a
	// zero value means "no opinion", which is what an unknown language gives.
	Type chroma.TokenType
	// Start is the byte offset within the line, so highlighting and selection
	// address the same string.
	Start int
}

Token is a run of a line with a syntactic role, ready to be coloured.

Jump to

Keyboard shortcuts

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