mirror of
https://github.com/golang/go
synced 2024-11-18 16:54:43 -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:
parent
a754db16a4
commit
a24c58a209
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user