1
0
mirror of https://github.com/golang/go synced 2024-11-05 14:56:10 -07:00

internal/lsp: cleanup the diff package

Make sure everything is documented and move things to sensible files now all the
cross package shuffling is done

Change-Id: I884053a207d6741cda066afa5da91b00f1dfd31c
Reviewed-on: https://go-review.googlesource.com/c/tools/+/198877
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Ian Cottrell 2019-10-03 14:18:24 -04:00
parent 43d3a2ca2a
commit 5bee6a6eb8
4 changed files with 105 additions and 80 deletions

View File

@ -1,54 +0,0 @@
package diff
import (
"bytes"
"sort"
"golang.org/x/tools/internal/span"
)
func ApplyEdits(before string, edits []TextEdit) string {
// Preconditions:
// - all of the edits apply to before
// - and all the spans for each TextEdit have the same URI
// copy edits so we don't make a mess of the caller's slice
s := make([]TextEdit, len(edits))
copy(s, edits)
edits = s
// TODO(matloob): Initialize the Converter Once?
var conv span.Converter = span.NewContentConverter("", []byte(before))
offset := func(point span.Point) int {
if point.HasOffset() {
return point.Offset()
}
offset, err := conv.ToOffset(point.Line(), point.Column())
if err != nil {
panic(err)
}
return offset
}
// sort the copy
sort.Slice(edits, func(i, j int) bool { return offset(edits[i].Span.Start()) < offset(edits[j].Span.Start()) })
var after bytes.Buffer
beforeOffset := 0
for _, edit := range edits {
if offset(edit.Span.Start()) < beforeOffset {
panic("overlapping edits") // TODO(matloob): ApplyEdits doesn't return an error. What do we do?
} else if offset(edit.Span.Start()) > beforeOffset {
after.WriteString(before[beforeOffset:offset(edit.Span.Start())])
beforeOffset = offset(edit.Span.Start())
}
// offset(edit.Span.Start) is now equal to beforeOffset
after.WriteString(edit.NewText)
beforeOffset += offset(edit.Span.End()) - offset(edit.Span.Start())
}
if beforeOffset < len(before) {
after.WriteString(before[beforeOffset:])
beforeOffset = len(before[beforeOffset:]) // just to preserve invariants
}
return after.String()
}

View File

@ -6,6 +6,7 @@
package diff
import (
"bytes"
"sort"
"golang.org/x/tools/internal/span"
@ -18,29 +19,10 @@ type TextEdit struct {
NewText string
}
// ComputeEdits is the type for a function that produces a set of edits that
// convert from the before content to the after content.
type ComputeEdits func(uri span.URI, before, after string) []TextEdit
type OpKind int
const (
Delete OpKind = iota
Insert
Equal
)
func (k OpKind) String() string {
switch k {
case Delete:
return "delete"
case Insert:
return "insert"
case Equal:
return "equal"
default:
panic("unknown operation kind")
}
}
// SortTextEdits attempts to order all edits by their starting points.
// The sort is stable so that edits with the same starting point will not
// be reordered.
@ -50,3 +32,53 @@ func SortTextEdits(d []TextEdit) {
return span.Compare(d[i].Span, d[j].Span) < 0
})
}
// ApplyEdits applies the set of edits to the before and returns the resulting
// content.
// It may panic or produce garbage if the edits are not valid for the provided
// before content.
func ApplyEdits(before string, edits []TextEdit) string {
// Preconditions:
// - all of the edits apply to before
// - and all the spans for each TextEdit have the same URI
// copy edits so we don't make a mess of the caller's slice
s := make([]TextEdit, len(edits))
copy(s, edits)
edits = s
// TODO(matloob): Initialize the Converter Once?
var conv span.Converter = span.NewContentConverter("", []byte(before))
offset := func(point span.Point) int {
if point.HasOffset() {
return point.Offset()
}
offset, err := conv.ToOffset(point.Line(), point.Column())
if err != nil {
panic(err)
}
return offset
}
// sort the copy
sort.Slice(edits, func(i, j int) bool { return offset(edits[i].Span.Start()) < offset(edits[j].Span.Start()) })
var after bytes.Buffer
beforeOffset := 0
for _, edit := range edits {
if offset(edit.Span.Start()) < beforeOffset {
panic("overlapping edits") // TODO(matloob): ApplyEdits doesn't return an error. What do we do?
} else if offset(edit.Span.Start()) > beforeOffset {
after.WriteString(before[beforeOffset:offset(edit.Span.Start())])
beforeOffset = offset(edit.Span.Start())
}
// offset(edit.Span.Start) is now equal to beforeOffset
after.WriteString(edit.NewText)
beforeOffset += offset(edit.Span.End()) - offset(edit.Span.Start())
}
if beforeOffset < len(before) {
after.WriteString(before[beforeOffset:])
beforeOffset = len(before[beforeOffset:]) // just to preserve invariants
}
return after.String()
}

View File

@ -11,27 +11,72 @@ import (
"golang.org/x/tools/internal/span"
)
// Unified represents a set of edits as a unified diff.
type Unified struct {
From, To string
Hunks []*Hunk
// 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
ToLine int
Lines []Line
// 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 OpKind
// 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,
@ -121,6 +166,8 @@ func addEqualLines(h *Hunk, lines []string, start, end int) int {
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