internal/lsp: clean up ApplyEdits

This should be a faster but equivalent implementation.

Change-Id: I7bc756644c601b953ba7715e093bfa10ca5ea97b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/198878
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/diff/diff.go b/internal/lsp/diff/diff.go
index eb4fa6d..a3560e8 100644
--- a/internal/lsp/diff/diff.go
+++ b/internal/lsp/diff/diff.go
@@ -6,8 +6,8 @@
 package diff
 
 import (
-	"bytes"
 	"sort"
+	"strings"
 
 	"golang.org/x/tools/internal/span"
 )
@@ -41,44 +41,33 @@
 	// 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
+	if len(edits) == 0 {
+		return before
 	}
-
-	// 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
+	edits = prepareEdits(edits)
+	c := span.NewContentConverter("", []byte(before))
+	after := strings.Builder{}
+	last := 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())
+		spn, _ := edit.Span.WithAll(c)
+		start := spn.Start().Offset()
+		if start > last {
+			after.WriteString(before[last:start])
+			last = start
 		}
-		// offset(edit.Span.Start) is now equal to beforeOffset
 		after.WriteString(edit.NewText)
-		beforeOffset += offset(edit.Span.End()) - offset(edit.Span.Start())
+		last = spn.End().Offset()
 	}
-	if beforeOffset < len(before) {
-		after.WriteString(before[beforeOffset:])
-		beforeOffset = len(before[beforeOffset:]) // just to preserve invariants
+	if last < len(before) {
+		after.WriteString(before[last:])
 	}
 	return after.String()
 }
+
+// prepareEdits returns a sorted copy of the edits
+func prepareEdits(edits []TextEdit) []TextEdit {
+	copied := make([]TextEdit, len(edits))
+	copy(copied, edits)
+	SortTextEdits(copied)
+	return copied
+}