// Copyright 2019 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package diff import ( "fmt" "strings" ) type Unified struct { From, To string Hunks []*Hunk } type Hunk struct { FromLine int ToLine int Lines []Line } type Line struct { Kind OpKind Content string } const ( edge = 3 gap = edge * 2 ) func ToUnified(from, to string, lines []string, ops []*Op) Unified { u := Unified{ From: from, To: to, } if len(ops) == 0 { return u } var h *Hunk last := -(gap + 2) for _, op := range ops { switch { case op.I1 < last: panic("cannot convert unsorted operations to unified diff") case op.I1 == last: //direct extension case op.I1 <= last+gap: //within range of previous lines, add the joiners addEqualLines(h, lines, last, op.I1) default: //need to start a new hunk if h != nil { // add the edge to the previous hunk addEqualLines(h, lines, last, last+edge) u.Hunks = append(u.Hunks, h) } h = &Hunk{ FromLine: op.I1 + 1, ToLine: op.J1 + 1, } // add the edge to the new hunk delta := addEqualLines(h, lines, op.I1-edge, op.I1) h.FromLine -= delta h.ToLine -= delta } last = op.I1 switch op.Kind { case Delete: for i := op.I1; i < op.I2; i++ { h.Lines = append(h.Lines, Line{Kind: Delete, Content: lines[i]}) last++ } case Insert: for _, c := range op.Content { h.Lines = append(h.Lines, Line{Kind: Insert, Content: c}) } default: // all other op types ignored } } if h != nil { // add the edge to the final hunk addEqualLines(h, lines, last, last+edge) u.Hunks = append(u.Hunks, h) } return u } func addEqualLines(h *Hunk, lines []string, start, end int) int { delta := 0 for i := start; i < end; i++ { if i < 0 { continue } if i >= len(lines) { return delta } h.Lines = append(h.Lines, Line{Kind: Equal, Content: lines[i]}) delta++ } return delta } func (u Unified) Format(f fmt.State, r rune) { fmt.Fprintf(f, "--- %s\n", u.From) fmt.Fprintf(f, "+++ %s\n", u.To) for _, hunk := range u.Hunks { fromCount, toCount := 0, 0 for _, l := range hunk.Lines { switch l.Kind { case Delete: fromCount++ case Insert: toCount++ default: fromCount++ toCount++ } } fmt.Fprint(f, "@@") if fromCount > 1 { fmt.Fprintf(f, " -%d,%d", hunk.FromLine, fromCount) } else { fmt.Fprintf(f, " -%d", hunk.FromLine) } if toCount > 1 { fmt.Fprintf(f, " +%d,%d", hunk.ToLine, toCount) } else { fmt.Fprintf(f, " +%d", hunk.ToLine) } fmt.Fprint(f, " @@\n") for _, l := range hunk.Lines { switch l.Kind { case Delete: fmt.Fprintf(f, "-%s", l.Content) case Insert: fmt.Fprintf(f, "+%s", l.Content) default: fmt.Fprintf(f, " %s", l.Content) } if !strings.HasSuffix(l.Content, "\n") { fmt.Fprintf(f, "\n\\ No newline at end of file\n") } } } }