internal/lsp: fix formatting bug that keeps adding extra newlines

If a file ends with an empty newline, go/token treats the newline as the
final character of the previous line. VSCode, however, treats this as a
final line with no characters. We handle this by determining if the file
we are formatting ends with a newline character and updating the
protocol ranges accordingly.

Change-Id: Id8be0fd776ae65c8f0f937f3e718825e407cb217
Reviewed-on: https://go-review.googlesource.com/c/150338
Reviewed-by: Ian Cottrell <iancottrell@google.com>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index ad51fa4..5c9b4f2 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -277,21 +277,36 @@
 	} else {
 		r = fromProtocolRange(tok, *rng)
 	}
+	content, err := f.Read()
+	if err != nil {
+		return nil, err
+	}
 	edits, err := source.Format(ctx, f, r)
 	if err != nil {
 		return nil, err
 	}
-	return toProtocolEdits(tok, edits), nil
+	return toProtocolEdits(tok, content, edits), nil
 }
 
-func toProtocolEdits(f *token.File, edits []source.TextEdit) []protocol.TextEdit {
+func toProtocolEdits(tok *token.File, content []byte, edits []source.TextEdit) []protocol.TextEdit {
 	if edits == nil {
 		return nil
 	}
+	// When a file ends with an empty line, the newline character is counted
+	// as part of the previous line. This causes the formatter to insert
+	// another unnecessary newline on each formatting. We handle this case by
+	// checking if the file already ends with a newline character.
+	hasExtraNewline := content[len(content)-1] == '\n'
 	result := make([]protocol.TextEdit, len(edits))
 	for i, edit := range edits {
+		rng := toProtocolRange(tok, edit.Range)
+		// If the edit ends at the end of the file, add the extra line.
+		if hasExtraNewline && tok.Offset(edit.Range.End) == len(content) {
+			rng.End.Line++
+			rng.End.Character = 0
+		}
 		result[i] = protocol.TextEdit{
-			Range:   toProtocolRange(f, edit.Range),
+			Range:   rng,
 			NewText: edit.NewText,
 		}
 	}