1
0
mirror of https://github.com/golang/go synced 2024-11-18 19:34:41 -07:00

internal/lsp: implement incremental updates to document

gopls return Incremental for TextDocumentSyncKind.

Change-Id: I7b302a540a4d2ef9eaa079cea5155859febb9a95
Reviewed-on: https://go-review.googlesource.com/c/162921
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Yasuhiro Matsumoto 2019-02-18 15:00:10 +09:00 committed by Rebecca Stambler
parent a754db16a4
commit a24c58a209
3 changed files with 103 additions and 7 deletions

View File

@ -443,3 +443,31 @@ func (d definitions) collect(fset *token.FileSet, src, target packagestest.Range
loc := toProtocolLocation(fset, source.Range(src))
d[loc] = toProtocolLocation(fset, source.Range(target))
}
func TestBytesOffset(t *testing.T) {
tests := []struct {
text string
pos protocol.Position
want int
}{
{text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 0}, want: 0},
{text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 1}, want: 1},
{text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 2}, want: 1},
{text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 3}, want: 5},
{text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 4}, want: -1},
{text: "aaa\nbbb\n", pos: protocol.Position{Line: 0, Character: 3}, want: 3},
{text: "aaa\nbbb\n", pos: protocol.Position{Line: 0, Character: 4}, want: -1},
{text: "aaa\nbbb\n", pos: protocol.Position{Line: 1, Character: 0}, want: 4},
{text: "aaa\nbbb\n", pos: protocol.Position{Line: 1, Character: 3}, want: 7},
{text: "aaa\nbbb\n", pos: protocol.Position{Line: 1, Character: 4}, want: -1},
{text: "aaa\nbbb\n", pos: protocol.Position{Line: 2, Character: 0}, want: -1},
{text: "aaa\nbbb\n\n", pos: protocol.Position{Line: 2, Character: 0}, want: 8},
}
for _, test := range tests {
got := bytesOffset([]byte(test.text), test.pos)
if got != test.want {
t.Errorf("want %d for %q(Line:%d,Character:%d), but got %d", test.want, test.text, int(test.pos.Line), int(test.pos.Character), got)
}
}
}

View File

@ -38,7 +38,7 @@ type TextDocumentContentChangeEvent struct {
/**
* The range of the document that changed.
*/
Range Range `json:"range,omitempty"`
Range *Range `json:"range,omitempty"`
/**
* The length of the range that got replaced.

View File

@ -5,6 +5,7 @@
package lsp
import (
"bytes"
"context"
"fmt"
"go/ast"
@ -13,6 +14,7 @@ import (
"net"
"os"
"sync"
"unicode/utf8"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/jsonrpc2"
@ -122,7 +124,7 @@ func (s *server) Initialize(ctx context.Context, params *protocol.InitializePara
TriggerCharacters: []string{"(", ","},
},
TextDocumentSync: protocol.TextDocumentSyncOptions{
Change: float64(protocol.Full), // full contents of file sent on each update
Change: float64(protocol.Incremental),
OpenClose: true,
},
TypeDefinitionProvider: true,
@ -177,16 +179,82 @@ func (s *server) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocume
return nil
}
func bytesOffset(content []byte, pos protocol.Position) int {
var line, char, offset int
for len(content) > 0 {
if line == int(pos.Line) && char == int(pos.Character) {
return offset
}
r, size := utf8.DecodeRune(content)
char++
// The offsets are based on a UTF-16 string representation.
// So the rune should be checked twice for two code units in UTF-16.
if r >= 0x10000 {
if line == int(pos.Line) && char == int(pos.Character) {
return offset
}
char++
}
offset += size
content = content[size:]
if r == '\n' {
line++
char = 0
}
}
return -1
}
func (s *server) applyChanges(ctx context.Context, params *protocol.DidChangeTextDocumentParams) (string, error) {
if len(params.ContentChanges) == 1 && params.ContentChanges[0].Range == nil {
// If range is empty, we expect the full content of file, i.e. a single change with no range.
change := params.ContentChanges[0]
if change.RangeLength != 0 {
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "unexpected change range provided")
}
return change.Text, nil
}
sourceURI, err := fromProtocolURI(params.TextDocument.URI)
if err != nil {
return "", err
}
file, err := s.view.GetFile(ctx, sourceURI)
if err != nil {
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found")
}
content := file.GetContent()
for _, change := range params.ContentChanges {
start := bytesOffset(content, change.Range.Start)
if start == -1 {
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "invalid range for content change")
}
end := bytesOffset(content, change.Range.End)
if end == -1 {
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "invalid range for content change")
}
var buf bytes.Buffer
buf.Write(content[:start])
buf.WriteString(change.Text)
buf.Write(content[end:])
content = buf.Bytes()
}
return string(content), nil
}
func (s *server) DidChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error {
if len(params.ContentChanges) < 1 {
return jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "no content changes provided")
}
// We expect the full content of file, i.e. a single change with no range.
change := params.ContentChanges[0]
if change.RangeLength != 0 {
return jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "unexpected change range provided")
text, err := s.applyChanges(ctx, params)
if err != nil {
return err
}
s.cacheAndDiagnose(ctx, params.TextDocument.URI, change.Text)
s.cacheAndDiagnose(ctx, params.TextDocument.URI, text)
return nil
}