| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732 |
- package diffview
- import (
- "fmt"
- "image/color"
- "strconv"
- "strings"
- "github.com/alecthomas/chroma/v2"
- "github.com/alecthomas/chroma/v2/lexers"
- "github.com/aymanbagabas/go-udiff"
- "github.com/aymanbagabas/go-udiff/myers"
- "github.com/charmbracelet/lipgloss/v2"
- "github.com/charmbracelet/x/ansi"
- )
- const (
- leadingSymbolsSize = 2
- lineNumPadding = 1
- )
- type file struct {
- path string
- content string
- }
- type layout int
- const (
- layoutUnified layout = iota + 1
- layoutSplit
- )
- // DiffView represents a view for displaying differences between two files.
- type DiffView struct {
- layout layout
- before file
- after file
- contextLines int
- lineNumbers bool
- height int
- width int
- xOffset int
- yOffset int
- infiniteYScroll bool
- style Style
- tabWidth int
- chromaStyle *chroma.Style
- isComputed bool
- err error
- unified udiff.UnifiedDiff
- edits []udiff.Edit
- splitHunks []splitHunk
- totalLines int
- codeWidth int
- fullCodeWidth int // with leading symbols
- extraColOnAfter bool // add extra column on after panel
- beforeNumDigits int
- afterNumDigits int
- }
- // New creates a new DiffView with default settings.
- func New() *DiffView {
- dv := &DiffView{
- layout: layoutUnified,
- contextLines: udiff.DefaultContextLines,
- lineNumbers: true,
- tabWidth: 8,
- }
- dv.style = DefaultDarkStyle()
- return dv
- }
- // Unified sets the layout of the DiffView to unified.
- func (dv *DiffView) Unified() *DiffView {
- dv.layout = layoutUnified
- return dv
- }
- // Split sets the layout of the DiffView to split (side-by-side).
- func (dv *DiffView) Split() *DiffView {
- dv.layout = layoutSplit
- return dv
- }
- // Before sets the "before" file for the DiffView.
- func (dv *DiffView) Before(path, content string) *DiffView {
- dv.before = file{path: path, content: content}
- return dv
- }
- // After sets the "after" file for the DiffView.
- func (dv *DiffView) After(path, content string) *DiffView {
- dv.after = file{path: path, content: content}
- return dv
- }
- // ContextLines sets the number of context lines for the DiffView.
- func (dv *DiffView) ContextLines(contextLines int) *DiffView {
- dv.contextLines = contextLines
- return dv
- }
- // Style sets the style for the DiffView.
- func (dv *DiffView) Style(style Style) *DiffView {
- dv.style = style
- return dv
- }
- // LineNumbers sets whether to display line numbers in the DiffView.
- func (dv *DiffView) LineNumbers(lineNumbers bool) *DiffView {
- dv.lineNumbers = lineNumbers
- return dv
- }
- // Height sets the height of the DiffView.
- func (dv *DiffView) Height(height int) *DiffView {
- dv.height = height
- return dv
- }
- // Width sets the width of the DiffView.
- func (dv *DiffView) Width(width int) *DiffView {
- dv.width = width
- return dv
- }
- // XOffset sets the horizontal offset for the DiffView.
- func (dv *DiffView) XOffset(xOffset int) *DiffView {
- dv.xOffset = xOffset
- return dv
- }
- // YOffset sets the vertical offset for the DiffView.
- func (dv *DiffView) YOffset(yOffset int) *DiffView {
- dv.yOffset = yOffset
- return dv
- }
- // InfiniteYScroll allows the YOffset to scroll beyond the last line.
- func (dv *DiffView) InfiniteYScroll(infiniteYScroll bool) *DiffView {
- dv.infiniteYScroll = infiniteYScroll
- return dv
- }
- // TabWidth sets the tab width. Only relevant for code that contains tabs, like
- // Go code.
- func (dv *DiffView) TabWidth(tabWidth int) *DiffView {
- dv.tabWidth = tabWidth
- return dv
- }
- // ChromaStyle sets the chroma style for syntax highlighting.
- // If nil, no syntax highlighting will be applied.
- func (dv *DiffView) ChromaStyle(style *chroma.Style) *DiffView {
- dv.chromaStyle = style
- return dv
- }
- // String returns the string representation of the DiffView.
- func (dv *DiffView) String() string {
- dv.replaceTabs()
- if err := dv.computeDiff(); err != nil {
- return err.Error()
- }
- dv.convertDiffToSplit()
- dv.adjustStyles()
- dv.detectNumDigits()
- dv.detectTotalLines()
- dv.preventInfiniteYScroll()
- if dv.width <= 0 {
- dv.detectCodeWidth()
- } else {
- dv.resizeCodeWidth()
- }
- style := lipgloss.NewStyle()
- if dv.width > 0 {
- style = style.MaxWidth(dv.width)
- }
- if dv.height > 0 {
- style = style.MaxHeight(dv.height)
- }
- switch dv.layout {
- case layoutUnified:
- return style.Render(strings.TrimSuffix(dv.renderUnified(), "\n"))
- case layoutSplit:
- return style.Render(strings.TrimSuffix(dv.renderSplit(), "\n"))
- default:
- panic("unknown diffview layout")
- }
- }
- // replaceTabs replaces tabs in the before and after file contents with spaces
- // according to the specified tab width.
- func (dv *DiffView) replaceTabs() {
- spaces := strings.Repeat(" ", dv.tabWidth)
- dv.before.content = strings.ReplaceAll(dv.before.content, "\t", spaces)
- dv.after.content = strings.ReplaceAll(dv.after.content, "\t", spaces)
- }
- // computeDiff computes the differences between the "before" and "after" files.
- func (dv *DiffView) computeDiff() error {
- if dv.isComputed {
- return dv.err
- }
- dv.isComputed = true
- dv.edits = myers.ComputeEdits( //nolint:staticcheck
- dv.before.content,
- dv.after.content,
- )
- dv.unified, dv.err = udiff.ToUnifiedDiff(
- dv.before.path,
- dv.after.path,
- dv.before.content,
- dv.edits,
- dv.contextLines,
- )
- return dv.err
- }
- // convertDiffToSplit converts the unified diff to a split diff if the layout is
- // set to split.
- func (dv *DiffView) convertDiffToSplit() {
- if dv.layout != layoutSplit {
- return
- }
- dv.splitHunks = make([]splitHunk, len(dv.unified.Hunks))
- for i, h := range dv.unified.Hunks {
- dv.splitHunks[i] = hunkToSplit(h)
- }
- }
- // adjustStyles adjusts adds padding and alignment to the styles.
- func (dv *DiffView) adjustStyles() {
- setPadding := func(s lipgloss.Style) lipgloss.Style {
- return s.Padding(0, lineNumPadding).Align(lipgloss.Right)
- }
- dv.style.MissingLine.LineNumber = setPadding(dv.style.MissingLine.LineNumber)
- dv.style.DividerLine.LineNumber = setPadding(dv.style.DividerLine.LineNumber)
- dv.style.EqualLine.LineNumber = setPadding(dv.style.EqualLine.LineNumber)
- dv.style.InsertLine.LineNumber = setPadding(dv.style.InsertLine.LineNumber)
- dv.style.DeleteLine.LineNumber = setPadding(dv.style.DeleteLine.LineNumber)
- }
- // detectNumDigits calculates the maximum number of digits needed for before and
- // after line numbers.
- func (dv *DiffView) detectNumDigits() {
- dv.beforeNumDigits = 0
- dv.afterNumDigits = 0
- for _, h := range dv.unified.Hunks {
- dv.beforeNumDigits = max(dv.beforeNumDigits, len(strconv.Itoa(h.FromLine+len(h.Lines))))
- dv.afterNumDigits = max(dv.afterNumDigits, len(strconv.Itoa(h.ToLine+len(h.Lines))))
- }
- }
- func (dv *DiffView) detectTotalLines() {
- dv.totalLines = 0
- switch dv.layout {
- case layoutUnified:
- for _, h := range dv.unified.Hunks {
- dv.totalLines += 1 + len(h.Lines)
- }
- case layoutSplit:
- for _, h := range dv.splitHunks {
- dv.totalLines += 1 + len(h.lines)
- }
- }
- }
- func (dv *DiffView) preventInfiniteYScroll() {
- if dv.infiniteYScroll {
- return
- }
- // clamp yOffset to prevent scrolling beyond the last line
- if dv.height > 0 {
- maxYOffset := max(0, dv.totalLines-dv.height)
- dv.yOffset = min(dv.yOffset, maxYOffset)
- } else {
- // if no height limit, ensure yOffset doesn't exceed total lines
- dv.yOffset = min(dv.yOffset, max(0, dv.totalLines-1))
- }
- dv.yOffset = max(0, dv.yOffset) // ensure yOffset is not negative
- }
- // detectCodeWidth calculates the maximum width of code lines in the diff view.
- func (dv *DiffView) detectCodeWidth() {
- switch dv.layout {
- case layoutUnified:
- dv.detectUnifiedCodeWidth()
- case layoutSplit:
- dv.detectSplitCodeWidth()
- }
- dv.fullCodeWidth = dv.codeWidth + leadingSymbolsSize
- }
- // detectUnifiedCodeWidth calculates the maximum width of code lines in a
- // unified diff.
- func (dv *DiffView) detectUnifiedCodeWidth() {
- dv.codeWidth = 0
- for _, h := range dv.unified.Hunks {
- shownLines := ansi.StringWidth(dv.hunkLineFor(h))
- for _, l := range h.Lines {
- lineWidth := ansi.StringWidth(strings.TrimSuffix(l.Content, "\n")) + 1
- dv.codeWidth = max(dv.codeWidth, lineWidth, shownLines)
- }
- }
- }
- // detectSplitCodeWidth calculates the maximum width of code lines in a
- // split diff.
- func (dv *DiffView) detectSplitCodeWidth() {
- dv.codeWidth = 0
- for i, h := range dv.splitHunks {
- shownLines := ansi.StringWidth(dv.hunkLineFor(dv.unified.Hunks[i]))
- for _, l := range h.lines {
- if l.before != nil {
- codeWidth := ansi.StringWidth(strings.TrimSuffix(l.before.Content, "\n")) + 1
- dv.codeWidth = max(dv.codeWidth, codeWidth, shownLines)
- }
- if l.after != nil {
- codeWidth := ansi.StringWidth(strings.TrimSuffix(l.after.Content, "\n")) + 1
- dv.codeWidth = max(dv.codeWidth, codeWidth, shownLines)
- }
- }
- }
- }
- // resizeCodeWidth resizes the code width to fit within the specified width.
- func (dv *DiffView) resizeCodeWidth() {
- fullNumWidth := dv.beforeNumDigits + dv.afterNumDigits
- fullNumWidth += lineNumPadding * 4 // left and right padding for both line numbers
- switch dv.layout {
- case layoutUnified:
- dv.codeWidth = dv.width - fullNumWidth - leadingSymbolsSize
- case layoutSplit:
- remainingWidth := dv.width - fullNumWidth - leadingSymbolsSize*2
- dv.codeWidth = remainingWidth / 2
- dv.extraColOnAfter = isOdd(remainingWidth)
- }
- dv.fullCodeWidth = dv.codeWidth + leadingSymbolsSize
- }
- // renderUnified renders the unified diff view as a string.
- func (dv *DiffView) renderUnified() string {
- var b strings.Builder
- fullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth)
- printedLines := -dv.yOffset
- shouldWrite := func() bool { return printedLines >= 0 }
- getContent := func(in string, ls LineStyle) (content string, leadingEllipsis bool) {
- content = strings.TrimSuffix(in, "\n")
- content = dv.hightlightCode(content, ls.Code.GetBackground())
- content = ansi.GraphemeWidth.Cut(content, dv.xOffset, len(content))
- content = ansi.Truncate(content, dv.codeWidth, "…")
- leadingEllipsis = dv.xOffset > 0 && strings.TrimSpace(content) != ""
- return
- }
- outer:
- for i, h := range dv.unified.Hunks {
- if shouldWrite() {
- ls := dv.style.DividerLine
- if dv.lineNumbers {
- b.WriteString(ls.LineNumber.Render(pad("…", dv.beforeNumDigits)))
- b.WriteString(ls.LineNumber.Render(pad("…", dv.afterNumDigits)))
- }
- content := ansi.Truncate(dv.hunkLineFor(h), dv.fullCodeWidth, "…")
- b.WriteString(ls.Code.Width(dv.fullCodeWidth).Render(content))
- b.WriteString("\n")
- }
- printedLines++
- beforeLine := h.FromLine
- afterLine := h.ToLine
- for j, l := range h.Lines {
- // print ellipis if we don't have enough space to print the rest of the diff
- hasReachedHeight := dv.height > 0 && printedLines+1 == dv.height
- isLastHunk := i+1 == len(dv.unified.Hunks)
- isLastLine := j+1 == len(h.Lines)
- if hasReachedHeight && (!isLastHunk || !isLastLine) {
- if shouldWrite() {
- ls := dv.lineStyleForType(l.Kind)
- if dv.lineNumbers {
- b.WriteString(ls.LineNumber.Render(pad("…", dv.beforeNumDigits)))
- b.WriteString(ls.LineNumber.Render(pad("…", dv.afterNumDigits)))
- }
- b.WriteString(fullContentStyle.Render(
- ls.Code.Width(dv.fullCodeWidth).Render(" …"),
- ))
- b.WriteRune('\n')
- }
- break outer
- }
- switch l.Kind {
- case udiff.Equal:
- if shouldWrite() {
- ls := dv.style.EqualLine
- content, leadingEllipsis := getContent(l.Content, ls)
- if dv.lineNumbers {
- b.WriteString(ls.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
- b.WriteString(ls.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
- }
- b.WriteString(fullContentStyle.Render(
- ls.Code.Width(dv.fullCodeWidth).Render(ternary(leadingEllipsis, " …", " ") + content),
- ))
- }
- beforeLine++
- afterLine++
- case udiff.Insert:
- if shouldWrite() {
- ls := dv.style.InsertLine
- content, leadingEllipsis := getContent(l.Content, ls)
- if dv.lineNumbers {
- b.WriteString(ls.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
- b.WriteString(ls.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
- }
- b.WriteString(fullContentStyle.Render(
- ls.Symbol.Render(ternary(leadingEllipsis, "+…", "+ ")) +
- ls.Code.Width(dv.codeWidth).Render(content),
- ))
- }
- afterLine++
- case udiff.Delete:
- if shouldWrite() {
- ls := dv.style.DeleteLine
- content, leadingEllipsis := getContent(l.Content, ls)
- if dv.lineNumbers {
- b.WriteString(ls.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
- b.WriteString(ls.LineNumber.Render(pad(" ", dv.afterNumDigits)))
- }
- b.WriteString(fullContentStyle.Render(
- ls.Symbol.Render(ternary(leadingEllipsis, "-…", "- ")) +
- ls.Code.Width(dv.codeWidth).Render(content),
- ))
- }
- beforeLine++
- }
- if shouldWrite() {
- b.WriteRune('\n')
- }
- printedLines++
- }
- }
- for printedLines < dv.height {
- if shouldWrite() {
- ls := dv.style.MissingLine
- if dv.lineNumbers {
- b.WriteString(ls.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
- b.WriteString(ls.LineNumber.Render(pad(" ", dv.afterNumDigits)))
- }
- b.WriteString(ls.Code.Width(dv.fullCodeWidth).Render(" "))
- b.WriteRune('\n')
- }
- printedLines++
- }
- return b.String()
- }
- // renderSplit renders the split (side-by-side) diff view as a string.
- func (dv *DiffView) renderSplit() string {
- var b strings.Builder
- beforeFullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth)
- afterFullContentStyle := lipgloss.NewStyle().MaxWidth(dv.fullCodeWidth + btoi(dv.extraColOnAfter))
- printedLines := -dv.yOffset
- shouldWrite := func() bool { return printedLines >= 0 }
- getContent := func(in string, ls LineStyle) (content string, leadingEllipsis bool) {
- content = strings.TrimSuffix(in, "\n")
- content = dv.hightlightCode(content, ls.Code.GetBackground())
- content = ansi.GraphemeWidth.Cut(content, dv.xOffset, len(content))
- content = ansi.Truncate(content, dv.codeWidth, "…")
- leadingEllipsis = dv.xOffset > 0 && strings.TrimSpace(content) != ""
- return
- }
- outer:
- for i, h := range dv.splitHunks {
- if shouldWrite() {
- ls := dv.style.DividerLine
- if dv.lineNumbers {
- b.WriteString(ls.LineNumber.Render(pad("…", dv.beforeNumDigits)))
- }
- content := ansi.Truncate(dv.hunkLineFor(dv.unified.Hunks[i]), dv.fullCodeWidth, "…")
- b.WriteString(ls.Code.Width(dv.fullCodeWidth).Render(content))
- if dv.lineNumbers {
- b.WriteString(ls.LineNumber.Render(pad("…", dv.afterNumDigits)))
- }
- b.WriteString(ls.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "))
- b.WriteRune('\n')
- }
- printedLines++
- beforeLine := h.fromLine
- afterLine := h.toLine
- for j, l := range h.lines {
- // print ellipis if we don't have enough space to print the rest of the diff
- hasReachedHeight := dv.height > 0 && printedLines+1 == dv.height
- isLastHunk := i+1 == len(dv.unified.Hunks)
- isLastLine := j+1 == len(h.lines)
- if hasReachedHeight && (!isLastHunk || !isLastLine) {
- if shouldWrite() {
- ls := dv.style.MissingLine
- if l.before != nil {
- ls = dv.lineStyleForType(l.before.Kind)
- }
- if dv.lineNumbers {
- b.WriteString(ls.LineNumber.Render(pad("…", dv.beforeNumDigits)))
- }
- b.WriteString(beforeFullContentStyle.Render(
- ls.Code.Width(dv.fullCodeWidth).Render(" …"),
- ))
- ls = dv.style.MissingLine
- if l.after != nil {
- ls = dv.lineStyleForType(l.after.Kind)
- }
- if dv.lineNumbers {
- b.WriteString(ls.LineNumber.Render(pad("…", dv.afterNumDigits)))
- }
- b.WriteString(afterFullContentStyle.Render(
- ls.Code.Width(dv.fullCodeWidth).Render(" …"),
- ))
- b.WriteRune('\n')
- }
- break outer
- }
- switch {
- case l.before == nil:
- if shouldWrite() {
- ls := dv.style.MissingLine
- if dv.lineNumbers {
- b.WriteString(ls.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
- }
- b.WriteString(beforeFullContentStyle.Render(
- ls.Code.Width(dv.fullCodeWidth).Render(" "),
- ))
- }
- case l.before.Kind == udiff.Equal:
- if shouldWrite() {
- ls := dv.style.EqualLine
- content, leadingEllipsis := getContent(l.before.Content, ls)
- if dv.lineNumbers {
- b.WriteString(ls.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
- }
- b.WriteString(beforeFullContentStyle.Render(
- ls.Code.Width(dv.fullCodeWidth).Render(ternary(leadingEllipsis, " …", " ") + content),
- ))
- }
- beforeLine++
- case l.before.Kind == udiff.Delete:
- if shouldWrite() {
- ls := dv.style.DeleteLine
- content, leadingEllipsis := getContent(l.before.Content, ls)
- if dv.lineNumbers {
- b.WriteString(ls.LineNumber.Render(pad(beforeLine, dv.beforeNumDigits)))
- }
- b.WriteString(beforeFullContentStyle.Render(
- ls.Symbol.Render(ternary(leadingEllipsis, "-…", "- ")) +
- ls.Code.Width(dv.codeWidth).Render(content),
- ))
- }
- beforeLine++
- }
- switch {
- case l.after == nil:
- if shouldWrite() {
- ls := dv.style.MissingLine
- if dv.lineNumbers {
- b.WriteString(ls.LineNumber.Render(pad(" ", dv.afterNumDigits)))
- }
- b.WriteString(afterFullContentStyle.Render(
- ls.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "),
- ))
- }
- case l.after.Kind == udiff.Equal:
- if shouldWrite() {
- ls := dv.style.EqualLine
- content, leadingEllipsis := getContent(l.after.Content, ls)
- if dv.lineNumbers {
- b.WriteString(ls.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
- }
- b.WriteString(afterFullContentStyle.Render(
- ls.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(ternary(leadingEllipsis, " …", " ") + content),
- ))
- }
- afterLine++
- case l.after.Kind == udiff.Insert:
- if shouldWrite() {
- ls := dv.style.InsertLine
- content, leadingEllipsis := getContent(l.after.Content, ls)
- if dv.lineNumbers {
- b.WriteString(ls.LineNumber.Render(pad(afterLine, dv.afterNumDigits)))
- }
- b.WriteString(afterFullContentStyle.Render(
- ls.Symbol.Render(ternary(leadingEllipsis, "+…", "+ ")) +
- ls.Code.Width(dv.codeWidth+btoi(dv.extraColOnAfter)).Render(content),
- ))
- }
- afterLine++
- }
- if shouldWrite() {
- b.WriteRune('\n')
- }
- printedLines++
- }
- }
- for printedLines < dv.height {
- if shouldWrite() {
- ls := dv.style.MissingLine
- if dv.lineNumbers {
- b.WriteString(ls.LineNumber.Render(pad(" ", dv.beforeNumDigits)))
- }
- b.WriteString(ls.Code.Width(dv.fullCodeWidth).Render(" "))
- if dv.lineNumbers {
- b.WriteString(ls.LineNumber.Render(pad(" ", dv.afterNumDigits)))
- }
- b.WriteString(ls.Code.Width(dv.fullCodeWidth + btoi(dv.extraColOnAfter)).Render(" "))
- b.WriteRune('\n')
- }
- printedLines++
- }
- return b.String()
- }
- // hunkLineFor formats the header line for a hunk in the unified diff view.
- func (dv *DiffView) hunkLineFor(h *udiff.Hunk) string {
- beforeShownLines, afterShownLines := dv.hunkShownLines(h)
- return fmt.Sprintf(
- " @@ -%d,%d +%d,%d @@ ",
- h.FromLine,
- beforeShownLines,
- h.ToLine,
- afterShownLines,
- )
- }
- // hunkShownLines calculates the number of lines shown in a hunk for both before
- // and after versions.
- func (dv *DiffView) hunkShownLines(h *udiff.Hunk) (before, after int) {
- for _, l := range h.Lines {
- switch l.Kind {
- case udiff.Equal:
- before++
- after++
- case udiff.Insert:
- after++
- case udiff.Delete:
- before++
- }
- }
- return
- }
- func (dv *DiffView) lineStyleForType(t udiff.OpKind) LineStyle {
- switch t {
- case udiff.Equal:
- return dv.style.EqualLine
- case udiff.Insert:
- return dv.style.InsertLine
- case udiff.Delete:
- return dv.style.DeleteLine
- default:
- return dv.style.MissingLine
- }
- }
- func (dv *DiffView) hightlightCode(source string, bgColor color.Color) string {
- if dv.chromaStyle == nil {
- return source
- }
- l := dv.getChromaLexer(source)
- f := dv.getChromaFormatter(bgColor)
- it, err := l.Tokenise(nil, source)
- if err != nil {
- return source
- }
- var b strings.Builder
- if err := f.Format(&b, dv.chromaStyle, it); err != nil {
- return source
- }
- return b.String()
- }
- func (dv *DiffView) getChromaLexer(source string) chroma.Lexer {
- l := lexers.Match(dv.before.path)
- if l == nil {
- l = lexers.Analyse(source)
- }
- if l == nil {
- l = lexers.Fallback
- }
- return chroma.Coalesce(l)
- }
- func (dv *DiffView) getChromaFormatter(gbColor color.Color) chroma.Formatter {
- return chromaFormatter{
- bgColor: gbColor,
- }
- }
|