2019-04-04 15:42:34 -06:00
|
|
|
// 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.
|
|
|
|
|
2019-10-01 20:17:41 -06:00
|
|
|
package diff
|
2019-04-04 15:42:34 -06:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
2019-10-01 16:08:04 -06:00
|
|
|
|
2019-10-01 20:17:41 -06:00
|
|
|
"golang.org/x/tools/internal/span"
|
2019-04-04 15:42:34 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
type Unified struct {
|
|
|
|
From, To string
|
|
|
|
Hunks []*Hunk
|
|
|
|
}
|
|
|
|
|
|
|
|
type Hunk struct {
|
|
|
|
FromLine int
|
|
|
|
ToLine int
|
|
|
|
Lines []Line
|
|
|
|
}
|
|
|
|
|
|
|
|
type Line struct {
|
2019-10-01 20:17:41 -06:00
|
|
|
Kind OpKind
|
2019-04-04 15:42:34 -06:00
|
|
|
Content string
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
edge = 3
|
|
|
|
gap = edge * 2
|
|
|
|
)
|
|
|
|
|
2019-10-01 20:17:41 -06:00
|
|
|
func ToUnified(from, to string, content string, edits []TextEdit) Unified {
|
2019-04-04 15:42:34 -06:00
|
|
|
u := Unified{
|
|
|
|
From: from,
|
|
|
|
To: to,
|
|
|
|
}
|
2019-10-01 20:17:41 -06:00
|
|
|
if len(edits) == 0 {
|
2019-04-04 15:42:34 -06:00
|
|
|
return u
|
|
|
|
}
|
2019-10-01 20:17:41 -06:00
|
|
|
lines := splitLines(content)
|
2019-04-04 15:42:34 -06:00
|
|
|
var h *Hunk
|
2019-10-01 20:17:41 -06:00
|
|
|
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")
|
|
|
|
}
|
2019-04-04 15:42:34 -06:00
|
|
|
switch {
|
2019-10-01 20:17:41 -06:00
|
|
|
case start < last:
|
|
|
|
panic("cannot convert unsorted edits to unified diff")
|
|
|
|
case h != nil && start == last:
|
2019-04-04 15:42:34 -06:00
|
|
|
//direct extension
|
2019-10-01 20:17:41 -06:00
|
|
|
case h != nil && start <= last+gap:
|
2019-04-04 15:42:34 -06:00
|
|
|
//within range of previous lines, add the joiners
|
2019-10-01 20:17:41 -06:00
|
|
|
addEqualLines(h, lines, last, start)
|
2019-04-04 15:42:34 -06:00
|
|
|
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)
|
|
|
|
}
|
2019-10-01 20:17:41 -06:00
|
|
|
toLine += start - last
|
2019-04-04 15:42:34 -06:00
|
|
|
h = &Hunk{
|
2019-10-01 20:17:41 -06:00
|
|
|
FromLine: start + 1,
|
|
|
|
ToLine: toLine + 1,
|
2019-04-04 15:42:34 -06:00
|
|
|
}
|
|
|
|
// add the edge to the new hunk
|
2019-10-01 20:17:41 -06:00
|
|
|
delta := addEqualLines(h, lines, start-edge, start)
|
2019-04-04 15:42:34 -06:00
|
|
|
h.FromLine -= delta
|
|
|
|
h.ToLine -= delta
|
|
|
|
}
|
2019-10-01 20:17:41 -06:00
|
|
|
last = start
|
|
|
|
if edit.NewText == "" {
|
|
|
|
for i := start; i < end; i++ {
|
|
|
|
h.Lines = append(h.Lines, Line{Kind: Delete, Content: lines[i]})
|
2019-04-04 15:42:34 -06:00
|
|
|
last++
|
|
|
|
}
|
2019-10-01 20:17:41 -06:00
|
|
|
} else {
|
|
|
|
for _, line := range splitLines(edit.NewText) {
|
|
|
|
h.Lines = append(h.Lines, Line{Kind: Insert, Content: line})
|
|
|
|
toLine++
|
2019-04-04 15:42:34 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if h != nil {
|
|
|
|
// add the edge to the final hunk
|
|
|
|
addEqualLines(h, lines, last, last+edge)
|
|
|
|
u.Hunks = append(u.Hunks, h)
|
|
|
|
}
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
2019-10-01 20:17:41 -06:00
|
|
|
func splitLines(text string) []string {
|
|
|
|
lines := strings.SplitAfter(text, "\n")
|
|
|
|
if lines[len(lines)-1] == "" {
|
|
|
|
lines = lines[:len(lines)-1]
|
|
|
|
}
|
|
|
|
return lines
|
|
|
|
}
|
|
|
|
|
2019-04-04 15:42:34 -06:00
|
|
|
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
|
|
|
|
}
|
2019-10-01 20:17:41 -06:00
|
|
|
h.Lines = append(h.Lines, Line{Kind: Equal, Content: lines[i]})
|
2019-04-04 15:42:34 -06:00
|
|
|
delta++
|
|
|
|
}
|
|
|
|
return delta
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u Unified) Format(f fmt.State, r rune) {
|
2019-09-23 20:50:50 -06:00
|
|
|
if len(u.Hunks) == 0 {
|
|
|
|
return
|
|
|
|
}
|
2019-04-04 15:42:34 -06:00
|
|
|
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 {
|
2019-10-01 20:17:41 -06:00
|
|
|
case Delete:
|
2019-04-04 15:42:34 -06:00
|
|
|
fromCount++
|
2019-10-01 20:17:41 -06:00
|
|
|
case Insert:
|
2019-04-04 15:42:34 -06:00
|
|
|
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 {
|
2019-10-01 20:17:41 -06:00
|
|
|
case Delete:
|
2019-04-04 15:42:34 -06:00
|
|
|
fmt.Fprintf(f, "-%s", l.Content)
|
2019-10-01 20:17:41 -06:00
|
|
|
case Insert:
|
2019-04-04 15:42:34 -06:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|