--- /dev/null
+// 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"
+)
+
+// Unified represents a set of edits as a unified diff.
+type Unified struct {
+ // From is the name of the original file.
+ From string
+ // To is the name of the modified file.
+ To string
+ // Hunks is the set of edit hunks needed to transform the file content.
+ Hunks []*Hunk
+}
+
+// Hunk represents a contiguous set of line edits to apply.
+type Hunk struct {
+ // The line in the original source where the hunk starts.
+ FromLine int
+ // The line in the original source where the hunk finishes.
+ ToLine int
+ // The set of line based edits to apply.
+ Lines []Line
+}
+
+// Line represents a single line operation to apply as part of a Hunk.
+type Line struct {
+ // Kind is the type of line this represents, deletion, insertion or copy.
+ Kind OpKind
+ // Content is the content of this line.
+ // For deletion it is the line being removed, for all others it is the line
+ // to put in the output.
+ Content string
+}
+
+// OpKind is used to denote the type of operation a line represents.
+type OpKind int
+
+const (
+ // Delete is the operation kind for a line that is present in the input
+ // but not in the output.
+ Delete OpKind = iota
+ // Insert is the operation kind for a line that is new in the output.
+ Insert
+ // Equal is the operation kind for a line that is the same in the input and
+ // output, often used to provide context around edited lines.
+ Equal
+)
+
+// String returns a human readable representation of an OpKind. It is not
+// intended for machine processing.
+func (k OpKind) String() string {
+ switch k {
+ case Delete:
+ return "delete"
+ case Insert:
+ return "insert"
+ case Equal:
+ return "equal"
+ default:
+ panic("unknown operation kind")
+ }
+}
+
+const (
+ edge = 3
+ gap = edge * 2
+)
+
+// ToUnified takes a file contents and a sequence of edits, and calculates
+// a unified diff that represents those edits.
+func ToUnified(from, to string, content string, edits []TextEdit) Unified {
+ u := Unified{
+ From: from,
+ To: to,
+ }
+ if len(edits) == 0 {
+ return u
+ }
+ c, edits, partial := prepareEdits(content, edits)
+ if partial {
+ edits = lineEdits(content, c, edits)
+ }
+ lines := splitLines(content)
+ var h *Hunk
+ last := 0
+ toLine := 0
+ for _, edit := range edits {
+ start := edit.Span.Start().Line() - 1
+ end := edit.Span.End().Line() - 1
+ switch {
+ 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
+ for i := start; i < end; i++ {
+ h.Lines = append(h.Lines, Line{Kind: Delete, Content: lines[i]})
+ last++
+ }
+ if edit.NewText != "" {
+ 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
+}
+
+// Format converts a unified diff to the standard textual form for that diff.
+// The output of this function can be passed to tools like patch.
+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")
+ }
+ }
+ }
+}