internal/lsp: fix unified diffs of edits at the end of a file

Change-Id: I3a7db9261f0bc16609af6d58b363ec52474cb07e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/200697
Run-TryBot: Ian Cottrell <iancottrell@google.com>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/diff/diff.go b/internal/lsp/diff/diff.go
index 2aad513..5536c3b 100644
--- a/internal/lsp/diff/diff.go
+++ b/internal/lsp/diff/diff.go
@@ -83,7 +83,9 @@
 	for i, edit := range edits {
 		edit.Span, _ = edit.Span.WithAll(c)
 		copied[i] = edit
-		partial = partial || edit.Span.Start().Column() > 1 || edit.Span.End().Column() > 1
+		partial = partial ||
+			edit.Span.Start().Offset() >= len(before) ||
+			edit.Span.Start().Column() > 1 || edit.Span.End().Column() > 1
 	}
 	SortTextEdits(copied)
 	return c, copied, partial
@@ -127,6 +129,19 @@
 		edit.Span = span.New(edit.Span.URI(), start, end)
 		edit.NewText = before[start.Offset():start.Offset()+delta] + edit.NewText
 	}
+	if start.Offset() >= len(before) && start.Line() > 1 && before[len(before)-1] != '\n' {
+		// after end of file that does not end in eol, so join to last line of file
+		// to do this we need to know where the start of the last line was
+		eol := strings.LastIndex(before, "\n")
+		if eol < 0 {
+			// file is one non terminated line
+			eol = 0
+		}
+		delta := len(before) - eol
+		start = span.NewPoint(start.Line()-1, 1, start.Offset()-delta)
+		edit.Span = span.New(edit.Span.URI(), start, end)
+		edit.NewText = before[start.Offset():start.Offset()+delta] + edit.NewText
+	}
 	if end.Column() > 1 {
 		remains := before[end.Offset():]
 		eol := strings.IndexRune(remains, '\n')
diff --git a/internal/lsp/diff/difftest/difftest.go b/internal/lsp/diff/difftest/difftest.go
index 9a8f187..297515f 100644
--- a/internal/lsp/diff/difftest/difftest.go
+++ b/internal/lsp/diff/difftest/difftest.go
@@ -115,6 +115,31 @@
 `[1:],
 	Edits: []diff.TextEdit{{Span: newSpan(0, 1), NewText: "B"}},
 }, {
+	Name: "add_end",
+	In:   "A",
+	Out:  "AB",
+	Unified: UnifiedPrefix + `
+@@ -1 +1 @@
+-A
+\ No newline at end of file
++AB
+\ No newline at end of file
+`[1:],
+	Edits:     []diff.TextEdit{{Span: newSpan(1, 1), NewText: "B"}},
+	LineEdits: []diff.TextEdit{{Span: newSpan(0, 1), NewText: "AB"}},
+}, {
+	Name: "add_newline",
+	In:   "A",
+	Out:  "A\n",
+	Unified: UnifiedPrefix + `
+@@ -1 +1 @@
+-A
+\ No newline at end of file
++A
+`[1:],
+	Edits:     []diff.TextEdit{{Span: newSpan(1, 1), NewText: "\n"}},
+	LineEdits: []diff.TextEdit{{Span: newSpan(0, 1), NewText: "A\n"}},
+}, {
 	Name: "delete_front",
 	In:   "A\nB\nC\nA\nB\nB\nA\n",
 	Out:  "C\nB\nA\nB\nA\nC\n",