Help us shape the future of Go! Take the 2026 Go Developer Survey

diff

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Sep 17, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package diff parses unified diff text into files, hunks, and lines, and turns a user's hunk and line selections back into a patch or into reconstructed files on disk.

Index

Constants

View Source
const (
	TabChar   = '→'
	SpaceChar = '·'
)

Glyphs standing in for whitespace when it is rendered visibly.

Variables

View Source
var ErrPathEscapesBase = errors.New("path escapes its base directory")

ErrPathEscapesBase reports a diff path that resolves outside the directory it was joined to.

Functions

func CompareDirectories

func CompareDirectories(leftDir, rightDir string) (string, error)

CompareDirectories generates a unified diff comparing two directories. Returns git-format diff text suitable for parsing by diff.Parse().

func ComputeHunkWordDiffs

func ComputeHunkWordDiffs(hunk *Hunk) map[int]WordDiffResult

ComputeHunkWordDiffs keys word diffs by hunk line index, storing one pair's result under both of its line indices. A line with no counterpart is absent, so a renderer treats a missing key as "no intra-line highlighting" instead of an error.

func CountTrailingWhitespace

func CountTrailingWhitespace(content string) int

CountTrailingWhitespace counts the trailing spaces and tabs as bytes, so a tab counts once whatever tab width the renderer expands it to.

func GeneratePatch

func GeneratePatch(files []FileChange, selection SelectionState) string

GeneratePatch renders the selected hunks as a unified diff that git apply accepts. A fully selected hunk is copied verbatim, and a partially selected one keeps the selected lines plus three lines of context on each side with recalculated header counts. A file with nothing selected is left out, so an empty selection returns the empty string.

func GeneratePatchForTag

func GeneratePatchForTag(files []FileChange, selection SelectionState) string

GeneratePatchForTag renders the patch for one split tag. The tag is already baked into the selection the caller passes, so the output matches GeneratePatch over that same selection.

func GetLeadingWhitespace

func GetLeadingWhitespace(content string) string

GetLeadingWhitespace extracts the leading whitespace from a string.

func GetSelectedHunksMap

func GetSelectedHunksMap(files []FileChange, selectionState interface {
	IsHunkSelected(filePath string, hunkIdx int) bool
},
) map[string]map[int]bool

GetSelectedHunksMap is deprecated but kept for backward compatibility.

func GetUnselectedFiles

func GetUnselectedFiles(files []FileChange, selection SelectionState) []string

GetUnselectedFiles returns paths of files with no selections. These files should be restored to their left state.

func HasTrailingWhitespace

func HasTrailingWhitespace(content string) bool

HasTrailingWhitespace reports whether content ends in a space or a tab. An empty string does not, and a line that is entirely whitespace does.

func IsWhitespaceOnlyChange

func IsWhitespaceOnlyChange(oldContent, newContent string) bool

IsWhitespaceOnlyChange returns true if the two strings differ only in whitespace.

func RenderWhitespaceSimple

func RenderWhitespaceSimple(content string, tabWidth int) string

RenderWhitespaceSimple substitutes whitespace glyphs without any styling, treating trailing whitespace the same as interior whitespace. The result carries no ANSI codes, which suits a caller that needs to slice or measure the output by index.

func SelectAll

func SelectAll(files []FileChange, selection interface {
	ToggleHunk(filePath string, hunkIdx int)
},
)

SelectAll toggles every hunk in every file, which diff-editor mode calls once on load because starting with nothing selected would make a no-op edit discard the whole working copy. It toggles rather than sets, so calling it on an already-full selection clears it.

Types

type Applier

type Applier struct {
	LeftDir  string
	RightDir string
}

Applier handles writing user selections back to the right directory. For diff-editor mode, jj expects the right directory to contain only the changes the user wants to keep.

func NewApplier

func NewApplier(leftDir, rightDir string) *Applier

NewApplier reconstructs files in rightDir from the originals in leftDir. Pass the two directories jj hands the diff editor: every path out of the diff is resolved under them and rejected if it escapes, and files are written 0600 under 0750 parents because both trees are temporary.

func (*Applier) ApplySelections

func (a *Applier) ApplySelections(files []FileChange, selection SelectionState) error

ApplySelections reconstructs files in rightDir based on user selections. - Selected hunks: keep the change (content from right) - Unselected hunks: revert the change (content from left).

type ChangeType

type ChangeType int

ChangeType is what a diff header says happened to a file. String returns the one-letter status jj and git print.

const (
	ChangeTypeModified ChangeType = iota
	ChangeTypeAdded
	ChangeTypeDeleted
	ChangeTypeRenamed
)

Change kinds a file header can describe. ChangeTypeModified is the zero value and the fallback when the header carries no "new file", "deleted file", or "rename from" marker.

func (ChangeType) String

func (ct ChangeType) String() string

type DirectorySource

type DirectorySource struct {
	LeftPath  string
	RightPath string
}

DirectorySource generates diffs by comparing two directories. Used for diff-editor mode where jj passes $left and $right directories.

func NewDirectorySource

func NewDirectorySource(leftPath, rightPath string) *DirectorySource

NewDirectorySource compares two directory trees, which is how jj invokes a diff editor. Pass jj's $left and $right in that order, because the diff reads left as the old side.

func (*DirectorySource) GetDiff

func (s *DirectorySource) GetDiff() (string, error)

GetDiff walks both trees and diffs every file that differs, so it blocks for as long as the trees are large and belongs in a tea.Cmd rather than in Update.

func (*DirectorySource) GetSourceLabel

func (*DirectorySource) GetSourceLabel() string

GetSourceLabel returns a fixed label, because the directories jj passes are temporary paths that mean nothing to the user.

func (*DirectorySource) SupportsRevisions

func (*DirectorySource) SupportsRevisions() bool

SupportsRevisions reports false, because a diff editor sees two trees and has no revset to resolve against.

type FileChange

type FileChange struct {
	Path       string
	Hunks      []Hunk
	ChangeType ChangeType
}

FileChange holds one file's hunks in the order the diff lists them. Path is the "b/" side of the diff header, so a renamed file carries its new path.

func Parse

func Parse(diffText string) []FileChange

Parse splits unified diff text into one FileChange per "diff --git" header, dropping any section whose header does not parse. Empty input returns an empty slice rather than nil.

func (*FileChange) AddedLines

func (fc *FileChange) AddedLines() int

AddedLines counts added lines across every hunk, which with DeletedLines gives the +/- pair shown beside a file. A whitespace-only edit still counts on both sides.

func (*FileChange) DeletedLines

func (fc *FileChange) DeletedLines() int

DeletedLines counts deleted lines across every hunk, counting the parsed diff rather than the file, so a line replaced by an edit counts here and in AddedLines.

func (*FileChange) TotalLines

func (fc *FileChange) TotalLines() int

TotalLines counts every line across the file's hunks, context included, so it is the height the file's hunks occupy rather than the size of the change.

type Hunk

type Hunk struct {
	Header   string
	Lines    []Line
	OldStart int
	OldLines int
	NewStart int
	NewLines int
}

Hunk is one @@ section. OldStart and NewStart are 1-based line numbers in the old and new file, and Lines interleaves context, additions, and deletions in diff order.

type IntraLineSpan

type IntraLineSpan struct {
	Text  string
	Start int
	End   int
	Type  SpanType
}

IntraLineSpan is a run of one line's content. Start and End are byte offsets into that line's Content with End exclusive, so a span's byte width is not its column width.

type Line

type Line struct {
	Content    string
	Type       LineType
	OldLineNum int
	NewLineNum int
}

Line is one diff line with the leading +, -, or space stripped from Content. OldLineNum and NewLineNum are 1-based and both are always set, so an addition still records where it falls on the old side.

func ProcessHunkHideWhitespace

func ProcessHunkHideWhitespace(lines []Line) []Line

ProcessHunkHideWhitespace transforms a hunk to hide whitespace-only changes Returns a new set of lines with whitespace changes handled.

type LinePair

type LinePair struct {
	OldLine    *Line
	NewLine    *Line
	OldLineIdx int
	NewLineIdx int
}

LinePair links a deletion to the addition it is compared against. Both indices are positions in the hunk's Lines slice, and OldLine and NewLine point into that same slice.

func FindLinePairs

func FindLinePairs(hunk *Hunk) []LinePair

FindLinePairs pairs each deletion in a hunk with the addition at the same position in the addition run that follows it, leaving the surplus on the longer side unpaired. The returned pointers alias hunk.Lines, so the hunk must outlive the pairs.

type LineType

type LineType int

LineType marks a diff line as context, an addition, or a deletion. String returns the diff marker, which patch rendering writes back in front of the content.

const (
	LineContext LineType = iota
	LineAddition
	LineDeletion
)

Line kinds inside a hunk. LineContext is the zero value, so a line the parser cannot classify from its first byte is treated as context and its content is kept whole.

func (LineType) String

func (lt LineType) String() string

type RevisionSource

type RevisionSource struct {
	Client   *jj.Client
	Revision string
}

RevisionSource generates diffs from jj revisions.

func NewRevisionSource

func NewRevisionSource(client *jj.Client, revision string) *RevisionSource

NewRevisionSource reads diffs from a jj revision through client. The revision is resolved on each GetDiff call, so a revset such as @ follows the working copy as it moves.

func (*RevisionSource) GetDiff

func (s *RevisionSource) GetDiff() (string, error)

GetDiff shells out to jj for the revision's diff, so it blocks and belongs in a tea.Cmd rather than in Update.

func (*RevisionSource) GetSourceLabel

func (s *RevisionSource) GetSourceLabel() string

GetSourceLabel returns the revset the caller asked for, unresolved, which is what the header shows.

func (*RevisionSource) SupportsRevisions

func (*RevisionSource) SupportsRevisions() bool

SupportsRevisions reports true, so interactive mode opens the revision picker.

type SelectionState

type SelectionState interface {
	IsHunkSelected(filePath string, hunkIdx int) bool
	HasPartialSelection(filePath string, hunkIdx int) bool
	IsLineSelected(filePath string, hunkIdx, lineIdx int) bool
}

SelectionState interface for accessing selection state.

type SidePair added in v0.4.0

type SidePair struct {
	Left     *Line
	Right    *Line
	LeftIdx  int
	RightIdx int
}

SidePair is one row of a two-column diff. Left or Right is nil where that side has no line, which is how an added line sits opposite blank space. LeftIdx and RightIdx index the hunk's Lines and are -1 for the nil side, so a caller can still reach anything keyed by line index.

func PairSides added in v0.4.0

func PairSides(lines []Line) []SidePair

PairSides groups a hunk's lines into two-column rows. A run of deletions is matched against the run of additions that follows it, so a modified line lands opposite what replaced it rather than below it. Context appears on both sides.

type Source added in v0.1.2

type Source interface {
	GetDiff() (string, error)
	GetSourceLabel() string
	SupportsRevisions() bool
}

Source is where a diff comes from, either a jj revision or a pair of directories. GetDiff blocks on external work, so the UI calls it from a tea.Cmd rather than from Update.

type SpanType

type SpanType int

SpanType marks a run inside a line as unchanged, added, or deleted.

const (
	SpanEqual SpanType = iota
	SpanAdded
	SpanDeleted
)

Span kinds inside a line. SpanEqual is the zero value, so a span left unset renders without intra-line highlighting.

type WordDiffResult

type WordDiffResult struct {
	OldSpans []IntraLineSpan
	NewSpans []IntraLineSpan
}

WordDiffResult holds the spans for one deleted and added line pair. Each side's spans are contiguous and in order, so concatenating their Text rebuilds that side's line.

func ComputeWordDiff

func ComputeWordDiff(oldLine, newLine string) WordDiffResult

ComputeWordDiff diffs two single lines into highlight spans. Pass content only, without the diff marker, because offsets are relative to the start of the content the renderer draws.

Jump to

Keyboard shortcuts

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