// 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" "golang.org/x/tools/internal/span" ) 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, content string, edits []TextEdit) Unified { u := Unified{ From: from, To: to, } if len(edits) == 0 { return u } lines := splitLines(content) var h *Hunk last := 0 c := span.NewContentConverter(from, []byte(content)) toLine := 0 for _, edit := range edits { spn, _ := edit.Span.WithAll(c) start := spn.Start().Line() - 1 end := spn.End().Line() - 1 if spn.Start().Column() > 1 || spn.End().Column() > 1 { panic("cannot convert partial line edits to unified diff") } switch { case start < last: panic("cannot convert unsorted edits to unified diff") case h != nil && start == last: //direct extension case h != nil && start <= last+gap: //within range of previous lines, add the joiners addEqualLines(h, lines, last, start) 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) } toLine += start - last h = &Hunk{ FromLine: start + 1, ToLine: toLine + 1, } // add the edge to the new hunk delta := addEqualLines(h, lines, start-edge, start) h.FromLine -= delta h.ToLine -= delta } last = start if edit.NewText == "" { for i := start; i < end; i++ { h.Lines = append(h.Lines, Line{Kind: Delete, Content: lines[i]}) last++ } } else { for _, line := range splitLines(edit.NewText) { h.Lines = append(h.Lines, Line{Kind: Insert, Content: line}) toLine++ } } } 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 splitLines(text string) []string { lines := strings.SplitAfter(text, "\n") if lines[len(lines)-1] == "" { lines = lines[:len(lines)-1] } return lines } 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) { if len(u.Hunks) == 0 { return } 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") } } } }