1
0
mirror of https://github.com/golang/go synced 2024-11-18 23:34:45 -07:00
go/internal/lsp/fake/editor.go

445 lines
12 KiB
Go
Raw Normal View History

internal/lsp/fake: add fakes for testing editor interaction A lot of bug reports originating from LSP clients are related to either the timing or sequence of editor interactions with gopls (or at least they're originally reported this way). For example: "when I open a package and then create a new file, I lose diagnostics for existing files". These conditions are often hard to reproduce, and to isolate as either a gopls bug or a bug in the editor. Right now we're relying on govim integration tests to catch these regressions, but it's important to also have a testing framework that can exercise this functionality in-process. As a starting point this CL adds test fakes that implement a high level API for scripting editor interactions. A fake workspace can be used to sandbox file operations; a fake editor provides an interface for text editing operations; a fake LSP client can be used to connect the fake editor to a gopls instance. Some tests are added to the lsprpc package to demonstrate the API. The primary goal of these fakes should be to simulate an client that complies to the LSP spec. Put another way: if we have a bug report that we can't reproduce with our regression tests, it should either be a bug in our test fakes or a bug in the LSP client originating the report. I did my best to comply with the spec in this implementation, but it will certainly develop as we write more tests. We will also need to add to the editor API in the future for testing more language features. Updates golang/go#36879 Updates golang/go#34111 Change-Id: Ib81188683a7066184b8a254275ed5525191a2d68 Reviewed-on: https://go-review.googlesource.com/c/tools/+/217598 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-02-02 10:53:30 -07:00
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fake
import (
"context"
"fmt"
"strings"
"sync"
"golang.org/x/tools/internal/jsonrpc2"
"golang.org/x/tools/internal/lsp/protocol"
)
// Editor is a fake editor client. It keeps track of client state and can be
// used for writing LSP tests.
type Editor struct {
// server, client, and workspace are concurrency safe and written only at
// construction, so do not require synchronization.
server protocol.Server
client *Client
ws *Workspace
// Since this editor is intended just for testing, we use very coarse
// locking.
mu sync.Mutex
// Editor state.
buffers map[string]buffer
lastMessage *protocol.ShowMessageParams
logs []*protocol.LogMessageParams
diagnostics *protocol.PublishDiagnosticsParams
events []interface{}
// Capabilities / Options
serverCapabilities protocol.ServerCapabilities
}
type buffer struct {
version int
path string
content []string
}
func (b buffer) text() string {
return strings.Join(b.content, "\n")
}
// NewConnectedEditor creates a new editor that dispatches the LSP across the
// provided jsonrpc2 connection.
//
// The returned editor is initialized and ready to use.
func NewConnectedEditor(ctx context.Context, ws *Workspace, conn *jsonrpc2.Conn) (*Editor, error) {
e := NewEditor(ws)
e.server = protocol.ServerDispatcher(conn)
e.client = &Client{Editor: e}
conn.AddHandler(protocol.ClientHandler(e.client))
if err := e.initialize(ctx); err != nil {
return nil, err
}
e.ws.AddWatcher(e.onFileChanges)
return e, nil
}
// NewEditor Creates a new Editor.
func NewEditor(ws *Workspace) *Editor {
return &Editor{
buffers: make(map[string]buffer),
ws: ws,
}
}
// Shutdown issues the 'shutdown' LSP notification.
func (e *Editor) Shutdown(ctx context.Context) error {
internal/lsp/fake: add fakes for testing editor interaction A lot of bug reports originating from LSP clients are related to either the timing or sequence of editor interactions with gopls (or at least they're originally reported this way). For example: "when I open a package and then create a new file, I lose diagnostics for existing files". These conditions are often hard to reproduce, and to isolate as either a gopls bug or a bug in the editor. Right now we're relying on govim integration tests to catch these regressions, but it's important to also have a testing framework that can exercise this functionality in-process. As a starting point this CL adds test fakes that implement a high level API for scripting editor interactions. A fake workspace can be used to sandbox file operations; a fake editor provides an interface for text editing operations; a fake LSP client can be used to connect the fake editor to a gopls instance. Some tests are added to the lsprpc package to demonstrate the API. The primary goal of these fakes should be to simulate an client that complies to the LSP spec. Put another way: if we have a bug report that we can't reproduce with our regression tests, it should either be a bug in our test fakes or a bug in the LSP client originating the report. I did my best to comply with the spec in this implementation, but it will certainly develop as we write more tests. We will also need to add to the editor API in the future for testing more language features. Updates golang/go#36879 Updates golang/go#34111 Change-Id: Ib81188683a7066184b8a254275ed5525191a2d68 Reviewed-on: https://go-review.googlesource.com/c/tools/+/217598 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-02-02 10:53:30 -07:00
if e.server != nil {
if err := e.server.Shutdown(ctx); err != nil {
return fmt.Errorf("Shutdown: %v", err)
}
}
return nil
}
// Exit issues the 'exit' LSP notification.
func (e *Editor) Exit(ctx context.Context) error {
if e.server != nil {
internal/lsp/fake: add fakes for testing editor interaction A lot of bug reports originating from LSP clients are related to either the timing or sequence of editor interactions with gopls (or at least they're originally reported this way). For example: "when I open a package and then create a new file, I lose diagnostics for existing files". These conditions are often hard to reproduce, and to isolate as either a gopls bug or a bug in the editor. Right now we're relying on govim integration tests to catch these regressions, but it's important to also have a testing framework that can exercise this functionality in-process. As a starting point this CL adds test fakes that implement a high level API for scripting editor interactions. A fake workspace can be used to sandbox file operations; a fake editor provides an interface for text editing operations; a fake LSP client can be used to connect the fake editor to a gopls instance. Some tests are added to the lsprpc package to demonstrate the API. The primary goal of these fakes should be to simulate an client that complies to the LSP spec. Put another way: if we have a bug report that we can't reproduce with our regression tests, it should either be a bug in our test fakes or a bug in the LSP client originating the report. I did my best to comply with the spec in this implementation, but it will certainly develop as we write more tests. We will also need to add to the editor API in the future for testing more language features. Updates golang/go#36879 Updates golang/go#34111 Change-Id: Ib81188683a7066184b8a254275ed5525191a2d68 Reviewed-on: https://go-review.googlesource.com/c/tools/+/217598 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-02-02 10:53:30 -07:00
// Not all LSP clients issue the exit RPC, but we do so here to ensure that
// we gracefully handle it on multi-session servers.
if err := e.server.Exit(ctx); err != nil {
return fmt.Errorf("Exit: %v", err)
}
}
return nil
}
// Client returns the LSP client for this editor.
func (e *Editor) Client() *Client {
return e.client
}
func (e *Editor) configuration() map[string]interface{} {
return map[string]interface{}{
"env": map[string]interface{}{
"GOPATH": e.ws.GOPATH(),
"GO111MODULE": "on",
},
}
}
func (e *Editor) initialize(ctx context.Context) error {
params := &protocol.ParamInitialize{}
params.ClientInfo.Name = "fakeclient"
params.ClientInfo.Version = "v1.0.0"
params.RootURI = e.ws.RootURI()
// TODO: set client capabilities.
params.Trace = "messages"
// TODO: support workspace folders.
if e.server != nil {
resp, err := e.server.Initialize(ctx, params)
if err != nil {
return fmt.Errorf("initialize: %v", err)
internal/lsp/fake: add fakes for testing editor interaction A lot of bug reports originating from LSP clients are related to either the timing or sequence of editor interactions with gopls (or at least they're originally reported this way). For example: "when I open a package and then create a new file, I lose diagnostics for existing files". These conditions are often hard to reproduce, and to isolate as either a gopls bug or a bug in the editor. Right now we're relying on govim integration tests to catch these regressions, but it's important to also have a testing framework that can exercise this functionality in-process. As a starting point this CL adds test fakes that implement a high level API for scripting editor interactions. A fake workspace can be used to sandbox file operations; a fake editor provides an interface for text editing operations; a fake LSP client can be used to connect the fake editor to a gopls instance. Some tests are added to the lsprpc package to demonstrate the API. The primary goal of these fakes should be to simulate an client that complies to the LSP spec. Put another way: if we have a bug report that we can't reproduce with our regression tests, it should either be a bug in our test fakes or a bug in the LSP client originating the report. I did my best to comply with the spec in this implementation, but it will certainly develop as we write more tests. We will also need to add to the editor API in the future for testing more language features. Updates golang/go#36879 Updates golang/go#34111 Change-Id: Ib81188683a7066184b8a254275ed5525191a2d68 Reviewed-on: https://go-review.googlesource.com/c/tools/+/217598 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-02-02 10:53:30 -07:00
}
e.mu.Lock()
e.serverCapabilities = resp.Capabilities
e.mu.Unlock()
if err := e.server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
return fmt.Errorf("initialized: %v", err)
internal/lsp/fake: add fakes for testing editor interaction A lot of bug reports originating from LSP clients are related to either the timing or sequence of editor interactions with gopls (or at least they're originally reported this way). For example: "when I open a package and then create a new file, I lose diagnostics for existing files". These conditions are often hard to reproduce, and to isolate as either a gopls bug or a bug in the editor. Right now we're relying on govim integration tests to catch these regressions, but it's important to also have a testing framework that can exercise this functionality in-process. As a starting point this CL adds test fakes that implement a high level API for scripting editor interactions. A fake workspace can be used to sandbox file operations; a fake editor provides an interface for text editing operations; a fake LSP client can be used to connect the fake editor to a gopls instance. Some tests are added to the lsprpc package to demonstrate the API. The primary goal of these fakes should be to simulate an client that complies to the LSP spec. Put another way: if we have a bug report that we can't reproduce with our regression tests, it should either be a bug in our test fakes or a bug in the LSP client originating the report. I did my best to comply with the spec in this implementation, but it will certainly develop as we write more tests. We will also need to add to the editor API in the future for testing more language features. Updates golang/go#36879 Updates golang/go#34111 Change-Id: Ib81188683a7066184b8a254275ed5525191a2d68 Reviewed-on: https://go-review.googlesource.com/c/tools/+/217598 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-02-02 10:53:30 -07:00
}
}
return nil
}
func (e *Editor) onFileChanges(ctx context.Context, evts []FileEvent) {
if e.server == nil {
return
}
var lspevts []protocol.FileEvent
for _, evt := range evts {
lspevts = append(lspevts, evt.ProtocolEvent)
}
e.server.DidChangeWatchedFiles(ctx, &protocol.DidChangeWatchedFilesParams{
Changes: lspevts,
})
}
// OpenFile creates a buffer for the given workspace-relative file.
func (e *Editor) OpenFile(ctx context.Context, path string) error {
content, err := e.ws.ReadFile(path)
if err != nil {
return err
}
buf := newBuffer(path, content)
e.mu.Lock()
e.buffers[path] = buf
item := textDocumentItem(e.ws, buf)
e.mu.Unlock()
if e.server != nil {
if err := e.server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{
TextDocument: item,
}); err != nil {
return fmt.Errorf("DidOpen: %v", err)
}
}
return nil
}
func newBuffer(path, content string) buffer {
return buffer{
version: 1,
path: path,
content: strings.Split(content, "\n"),
}
}
func textDocumentItem(ws *Workspace, buf buffer) protocol.TextDocumentItem {
uri := ws.URI(buf.path)
languageID := ""
if strings.HasSuffix(buf.path, ".go") {
// TODO: what about go.mod files? What is their language ID?
languageID = "go"
}
return protocol.TextDocumentItem{
URI: uri,
LanguageID: languageID,
Version: float64(buf.version),
Text: buf.text(),
}
}
// CreateBuffer creates a new unsaved buffer corresponding to the workspace
// path, containing the given textual content.
func (e *Editor) CreateBuffer(ctx context.Context, path, content string) error {
buf := newBuffer(path, content)
e.mu.Lock()
e.buffers[path] = buf
item := textDocumentItem(e.ws, buf)
e.mu.Unlock()
if e.server != nil {
if err := e.server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{
TextDocument: item,
}); err != nil {
return fmt.Errorf("DidOpen: %v", err)
}
}
return nil
}
// CloseBuffer removes the current buffer (regardless of whether it is saved).
func (e *Editor) CloseBuffer(ctx context.Context, path string) error {
e.mu.Lock()
_, ok := e.buffers[path]
if !ok {
e.mu.Unlock()
return fmt.Errorf("unknown path %q", path)
}
delete(e.buffers, path)
e.mu.Unlock()
if e.server != nil {
if err := e.server.DidClose(ctx, &protocol.DidCloseTextDocumentParams{
TextDocument: protocol.TextDocumentIdentifier{
URI: e.ws.URI(path),
},
}); err != nil {
return fmt.Errorf("DidClose: %v", err)
}
}
return nil
}
// SaveBuffer writes the content of the buffer specified by the given path to
internal/lsp/fake: add fakes for testing editor interaction A lot of bug reports originating from LSP clients are related to either the timing or sequence of editor interactions with gopls (or at least they're originally reported this way). For example: "when I open a package and then create a new file, I lose diagnostics for existing files". These conditions are often hard to reproduce, and to isolate as either a gopls bug or a bug in the editor. Right now we're relying on govim integration tests to catch these regressions, but it's important to also have a testing framework that can exercise this functionality in-process. As a starting point this CL adds test fakes that implement a high level API for scripting editor interactions. A fake workspace can be used to sandbox file operations; a fake editor provides an interface for text editing operations; a fake LSP client can be used to connect the fake editor to a gopls instance. Some tests are added to the lsprpc package to demonstrate the API. The primary goal of these fakes should be to simulate an client that complies to the LSP spec. Put another way: if we have a bug report that we can't reproduce with our regression tests, it should either be a bug in our test fakes or a bug in the LSP client originating the report. I did my best to comply with the spec in this implementation, but it will certainly develop as we write more tests. We will also need to add to the editor API in the future for testing more language features. Updates golang/go#36879 Updates golang/go#34111 Change-Id: Ib81188683a7066184b8a254275ed5525191a2d68 Reviewed-on: https://go-review.googlesource.com/c/tools/+/217598 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-02-02 10:53:30 -07:00
// the filesystem.
func (e *Editor) SaveBuffer(ctx context.Context, path string) error {
if err := e.OrganizeImports(ctx, path); err != nil {
return fmt.Errorf("organizing imports before save: %v", err)
}
if err := e.FormatBuffer(ctx, path); err != nil {
return fmt.Errorf("formatting before save: %v", err)
}
internal/lsp/fake: add fakes for testing editor interaction A lot of bug reports originating from LSP clients are related to either the timing or sequence of editor interactions with gopls (or at least they're originally reported this way). For example: "when I open a package and then create a new file, I lose diagnostics for existing files". These conditions are often hard to reproduce, and to isolate as either a gopls bug or a bug in the editor. Right now we're relying on govim integration tests to catch these regressions, but it's important to also have a testing framework that can exercise this functionality in-process. As a starting point this CL adds test fakes that implement a high level API for scripting editor interactions. A fake workspace can be used to sandbox file operations; a fake editor provides an interface for text editing operations; a fake LSP client can be used to connect the fake editor to a gopls instance. Some tests are added to the lsprpc package to demonstrate the API. The primary goal of these fakes should be to simulate an client that complies to the LSP spec. Put another way: if we have a bug report that we can't reproduce with our regression tests, it should either be a bug in our test fakes or a bug in the LSP client originating the report. I did my best to comply with the spec in this implementation, but it will certainly develop as we write more tests. We will also need to add to the editor API in the future for testing more language features. Updates golang/go#36879 Updates golang/go#34111 Change-Id: Ib81188683a7066184b8a254275ed5525191a2d68 Reviewed-on: https://go-review.googlesource.com/c/tools/+/217598 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-02-02 10:53:30 -07:00
e.mu.Lock()
buf, ok := e.buffers[path]
if !ok {
e.mu.Unlock()
return fmt.Errorf(fmt.Sprintf("unknown buffer: %q", path))
}
content := buf.text()
includeText := false
syncOptions, ok := e.serverCapabilities.TextDocumentSync.(protocol.TextDocumentSyncOptions)
if ok {
includeText = syncOptions.Save.IncludeText
}
e.mu.Unlock()
docID := protocol.TextDocumentIdentifier{
URI: e.ws.URI(buf.path),
}
if e.server != nil {
if err := e.server.WillSave(ctx, &protocol.WillSaveTextDocumentParams{
TextDocument: docID,
Reason: protocol.Manual,
}); err != nil {
return fmt.Errorf("WillSave: %v", err)
}
}
if err := e.ws.WriteFile(ctx, path, content); err != nil {
return fmt.Errorf("writing %q: %v", path, err)
}
if e.server != nil {
params := &protocol.DidSaveTextDocumentParams{
TextDocument: protocol.VersionedTextDocumentIdentifier{
Version: float64(buf.version),
TextDocumentIdentifier: docID,
},
}
if includeText {
params.Text = &content
}
if err := e.server.DidSave(ctx, params); err != nil {
return fmt.Errorf("DidSave: %v", err)
}
}
return nil
}
// EditBuffer applies the given test edits to the buffer identified by path.
func (e *Editor) EditBuffer(ctx context.Context, path string, edits []Edit) error {
e.mu.Lock()
defer e.mu.Unlock()
return e.editBufferLocked(ctx, path, edits)
internal/lsp/fake: add fakes for testing editor interaction A lot of bug reports originating from LSP clients are related to either the timing or sequence of editor interactions with gopls (or at least they're originally reported this way). For example: "when I open a package and then create a new file, I lose diagnostics for existing files". These conditions are often hard to reproduce, and to isolate as either a gopls bug or a bug in the editor. Right now we're relying on govim integration tests to catch these regressions, but it's important to also have a testing framework that can exercise this functionality in-process. As a starting point this CL adds test fakes that implement a high level API for scripting editor interactions. A fake workspace can be used to sandbox file operations; a fake editor provides an interface for text editing operations; a fake LSP client can be used to connect the fake editor to a gopls instance. Some tests are added to the lsprpc package to demonstrate the API. The primary goal of these fakes should be to simulate an client that complies to the LSP spec. Put another way: if we have a bug report that we can't reproduce with our regression tests, it should either be a bug in our test fakes or a bug in the LSP client originating the report. I did my best to comply with the spec in this implementation, but it will certainly develop as we write more tests. We will also need to add to the editor API in the future for testing more language features. Updates golang/go#36879 Updates golang/go#34111 Change-Id: Ib81188683a7066184b8a254275ed5525191a2d68 Reviewed-on: https://go-review.googlesource.com/c/tools/+/217598 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-02-02 10:53:30 -07:00
}
// BufferText returns the content of the buffer with the given name.
func (e *Editor) BufferText(name string) string {
internal/lsp/fake: add fakes for testing editor interaction A lot of bug reports originating from LSP clients are related to either the timing or sequence of editor interactions with gopls (or at least they're originally reported this way). For example: "when I open a package and then create a new file, I lose diagnostics for existing files". These conditions are often hard to reproduce, and to isolate as either a gopls bug or a bug in the editor. Right now we're relying on govim integration tests to catch these regressions, but it's important to also have a testing framework that can exercise this functionality in-process. As a starting point this CL adds test fakes that implement a high level API for scripting editor interactions. A fake workspace can be used to sandbox file operations; a fake editor provides an interface for text editing operations; a fake LSP client can be used to connect the fake editor to a gopls instance. Some tests are added to the lsprpc package to demonstrate the API. The primary goal of these fakes should be to simulate an client that complies to the LSP spec. Put another way: if we have a bug report that we can't reproduce with our regression tests, it should either be a bug in our test fakes or a bug in the LSP client originating the report. I did my best to comply with the spec in this implementation, but it will certainly develop as we write more tests. We will also need to add to the editor API in the future for testing more language features. Updates golang/go#36879 Updates golang/go#34111 Change-Id: Ib81188683a7066184b8a254275ed5525191a2d68 Reviewed-on: https://go-review.googlesource.com/c/tools/+/217598 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-02-02 10:53:30 -07:00
e.mu.Lock()
defer e.mu.Unlock()
return e.buffers[name].text()
}
func (e *Editor) editBufferLocked(ctx context.Context, path string, edits []Edit) error {
internal/lsp/fake: add fakes for testing editor interaction A lot of bug reports originating from LSP clients are related to either the timing or sequence of editor interactions with gopls (or at least they're originally reported this way). For example: "when I open a package and then create a new file, I lose diagnostics for existing files". These conditions are often hard to reproduce, and to isolate as either a gopls bug or a bug in the editor. Right now we're relying on govim integration tests to catch these regressions, but it's important to also have a testing framework that can exercise this functionality in-process. As a starting point this CL adds test fakes that implement a high level API for scripting editor interactions. A fake workspace can be used to sandbox file operations; a fake editor provides an interface for text editing operations; a fake LSP client can be used to connect the fake editor to a gopls instance. Some tests are added to the lsprpc package to demonstrate the API. The primary goal of these fakes should be to simulate an client that complies to the LSP spec. Put another way: if we have a bug report that we can't reproduce with our regression tests, it should either be a bug in our test fakes or a bug in the LSP client originating the report. I did my best to comply with the spec in this implementation, but it will certainly develop as we write more tests. We will also need to add to the editor API in the future for testing more language features. Updates golang/go#36879 Updates golang/go#34111 Change-Id: Ib81188683a7066184b8a254275ed5525191a2d68 Reviewed-on: https://go-review.googlesource.com/c/tools/+/217598 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-02-02 10:53:30 -07:00
buf, ok := e.buffers[path]
if !ok {
return fmt.Errorf("unknown buffer %q", path)
internal/lsp/fake: add fakes for testing editor interaction A lot of bug reports originating from LSP clients are related to either the timing or sequence of editor interactions with gopls (or at least they're originally reported this way). For example: "when I open a package and then create a new file, I lose diagnostics for existing files". These conditions are often hard to reproduce, and to isolate as either a gopls bug or a bug in the editor. Right now we're relying on govim integration tests to catch these regressions, but it's important to also have a testing framework that can exercise this functionality in-process. As a starting point this CL adds test fakes that implement a high level API for scripting editor interactions. A fake workspace can be used to sandbox file operations; a fake editor provides an interface for text editing operations; a fake LSP client can be used to connect the fake editor to a gopls instance. Some tests are added to the lsprpc package to demonstrate the API. The primary goal of these fakes should be to simulate an client that complies to the LSP spec. Put another way: if we have a bug report that we can't reproduce with our regression tests, it should either be a bug in our test fakes or a bug in the LSP client originating the report. I did my best to comply with the spec in this implementation, but it will certainly develop as we write more tests. We will also need to add to the editor API in the future for testing more language features. Updates golang/go#36879 Updates golang/go#34111 Change-Id: Ib81188683a7066184b8a254275ed5525191a2d68 Reviewed-on: https://go-review.googlesource.com/c/tools/+/217598 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-02-02 10:53:30 -07:00
}
var (
content = make([]string, len(buf.content))
err error
evts []protocol.TextDocumentContentChangeEvent
)
copy(content, buf.content)
content, err = editContent(content, edits)
if err != nil {
return err
internal/lsp/fake: add fakes for testing editor interaction A lot of bug reports originating from LSP clients are related to either the timing or sequence of editor interactions with gopls (or at least they're originally reported this way). For example: "when I open a package and then create a new file, I lose diagnostics for existing files". These conditions are often hard to reproduce, and to isolate as either a gopls bug or a bug in the editor. Right now we're relying on govim integration tests to catch these regressions, but it's important to also have a testing framework that can exercise this functionality in-process. As a starting point this CL adds test fakes that implement a high level API for scripting editor interactions. A fake workspace can be used to sandbox file operations; a fake editor provides an interface for text editing operations; a fake LSP client can be used to connect the fake editor to a gopls instance. Some tests are added to the lsprpc package to demonstrate the API. The primary goal of these fakes should be to simulate an client that complies to the LSP spec. Put another way: if we have a bug report that we can't reproduce with our regression tests, it should either be a bug in our test fakes or a bug in the LSP client originating the report. I did my best to comply with the spec in this implementation, but it will certainly develop as we write more tests. We will also need to add to the editor API in the future for testing more language features. Updates golang/go#36879 Updates golang/go#34111 Change-Id: Ib81188683a7066184b8a254275ed5525191a2d68 Reviewed-on: https://go-review.googlesource.com/c/tools/+/217598 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-02-02 10:53:30 -07:00
}
internal/lsp/fake: add fakes for testing editor interaction A lot of bug reports originating from LSP clients are related to either the timing or sequence of editor interactions with gopls (or at least they're originally reported this way). For example: "when I open a package and then create a new file, I lose diagnostics for existing files". These conditions are often hard to reproduce, and to isolate as either a gopls bug or a bug in the editor. Right now we're relying on govim integration tests to catch these regressions, but it's important to also have a testing framework that can exercise this functionality in-process. As a starting point this CL adds test fakes that implement a high level API for scripting editor interactions. A fake workspace can be used to sandbox file operations; a fake editor provides an interface for text editing operations; a fake LSP client can be used to connect the fake editor to a gopls instance. Some tests are added to the lsprpc package to demonstrate the API. The primary goal of these fakes should be to simulate an client that complies to the LSP spec. Put another way: if we have a bug report that we can't reproduce with our regression tests, it should either be a bug in our test fakes or a bug in the LSP client originating the report. I did my best to comply with the spec in this implementation, but it will certainly develop as we write more tests. We will also need to add to the editor API in the future for testing more language features. Updates golang/go#36879 Updates golang/go#34111 Change-Id: Ib81188683a7066184b8a254275ed5525191a2d68 Reviewed-on: https://go-review.googlesource.com/c/tools/+/217598 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-02-02 10:53:30 -07:00
buf.content = content
buf.version++
e.buffers[path] = buf
// A simple heuristic: if there is only one edit, send it incrementally.
// Otherwise, send the entire content.
if len(edits) == 1 {
evts = append(evts, edits[0].toProtocolChangeEvent())
} else {
evts = append(evts, protocol.TextDocumentContentChangeEvent{
Text: buf.text(),
})
}
internal/lsp/fake: add fakes for testing editor interaction A lot of bug reports originating from LSP clients are related to either the timing or sequence of editor interactions with gopls (or at least they're originally reported this way). For example: "when I open a package and then create a new file, I lose diagnostics for existing files". These conditions are often hard to reproduce, and to isolate as either a gopls bug or a bug in the editor. Right now we're relying on govim integration tests to catch these regressions, but it's important to also have a testing framework that can exercise this functionality in-process. As a starting point this CL adds test fakes that implement a high level API for scripting editor interactions. A fake workspace can be used to sandbox file operations; a fake editor provides an interface for text editing operations; a fake LSP client can be used to connect the fake editor to a gopls instance. Some tests are added to the lsprpc package to demonstrate the API. The primary goal of these fakes should be to simulate an client that complies to the LSP spec. Put another way: if we have a bug report that we can't reproduce with our regression tests, it should either be a bug in our test fakes or a bug in the LSP client originating the report. I did my best to comply with the spec in this implementation, but it will certainly develop as we write more tests. We will also need to add to the editor API in the future for testing more language features. Updates golang/go#36879 Updates golang/go#34111 Change-Id: Ib81188683a7066184b8a254275ed5525191a2d68 Reviewed-on: https://go-review.googlesource.com/c/tools/+/217598 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-02-02 10:53:30 -07:00
params := &protocol.DidChangeTextDocumentParams{
TextDocument: protocol.VersionedTextDocumentIdentifier{
Version: float64(buf.version),
TextDocumentIdentifier: protocol.TextDocumentIdentifier{
URI: e.ws.URI(buf.path),
},
},
ContentChanges: evts,
}
if e.server != nil {
if err := e.server.DidChange(ctx, params); err != nil {
return fmt.Errorf("DidChange: %v", err)
}
}
return nil
internal/lsp/fake: add fakes for testing editor interaction A lot of bug reports originating from LSP clients are related to either the timing or sequence of editor interactions with gopls (or at least they're originally reported this way). For example: "when I open a package and then create a new file, I lose diagnostics for existing files". These conditions are often hard to reproduce, and to isolate as either a gopls bug or a bug in the editor. Right now we're relying on govim integration tests to catch these regressions, but it's important to also have a testing framework that can exercise this functionality in-process. As a starting point this CL adds test fakes that implement a high level API for scripting editor interactions. A fake workspace can be used to sandbox file operations; a fake editor provides an interface for text editing operations; a fake LSP client can be used to connect the fake editor to a gopls instance. Some tests are added to the lsprpc package to demonstrate the API. The primary goal of these fakes should be to simulate an client that complies to the LSP spec. Put another way: if we have a bug report that we can't reproduce with our regression tests, it should either be a bug in our test fakes or a bug in the LSP client originating the report. I did my best to comply with the spec in this implementation, but it will certainly develop as we write more tests. We will also need to add to the editor API in the future for testing more language features. Updates golang/go#36879 Updates golang/go#34111 Change-Id: Ib81188683a7066184b8a254275ed5525191a2d68 Reviewed-on: https://go-review.googlesource.com/c/tools/+/217598 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-02-02 10:53:30 -07:00
}
// GoToDefinition jumps to the definition of the symbol at the given position
// in an open buffer.
func (e *Editor) GoToDefinition(ctx context.Context, path string, pos Pos) (string, Pos, error) {
if err := e.checkBufferPosition(path, pos); err != nil {
return "", Pos{}, err
}
params := &protocol.DefinitionParams{}
params.TextDocument.URI = e.ws.URI(path)
params.Position = pos.toProtocolPosition()
resp, err := e.server.Definition(ctx, params)
if err != nil {
return "", Pos{}, fmt.Errorf("definition: %v", err)
}
if len(resp) == 0 {
return "", Pos{}, nil
}
newPath := e.ws.URIToPath(resp[0].URI)
newPos := fromProtocolPosition(resp[0].Range.Start)
if err := e.OpenFile(ctx, newPath); err != nil {
return "", Pos{}, fmt.Errorf("OpenFile: %v", err)
}
return newPath, newPos, nil
}
// OrganizeImports requests and performs the source.organizeImports codeAction.
func (e *Editor) OrganizeImports(ctx context.Context, path string) error {
if e.server == nil {
return nil
}
params := &protocol.CodeActionParams{}
params.TextDocument.URI = e.ws.URI(path)
actions, err := e.server.CodeAction(ctx, params)
if err != nil {
return fmt.Errorf("textDocument/codeAction: %v", err)
}
e.mu.Lock()
defer e.mu.Unlock()
for _, action := range actions {
if action.Kind == protocol.SourceOrganizeImports {
for _, change := range action.Edit.DocumentChanges {
path := e.ws.URIToPath(change.TextDocument.URI)
if float64(e.buffers[path].version) != change.TextDocument.Version {
// Skip edits for old versions.
continue
}
edits := convertEdits(change.Edits)
if err := e.editBufferLocked(ctx, path, edits); err != nil {
return fmt.Errorf("editing buffer %q: %v", path, err)
}
}
}
}
return nil
}
func convertEdits(protocolEdits []protocol.TextEdit) []Edit {
var edits []Edit
for _, lspEdit := range protocolEdits {
edits = append(edits, fromProtocolTextEdit(lspEdit))
}
return edits
}
// FormatBuffer gofmts a Go file.
func (e *Editor) FormatBuffer(ctx context.Context, path string) error {
if e.server == nil {
return nil
}
// Because textDocument/formatting has no versions, we must block while
// performing formatting.
e.mu.Lock()
defer e.mu.Unlock()
params := &protocol.DocumentFormattingParams{}
params.TextDocument.URI = e.ws.URI(path)
resp, err := e.server.Formatting(ctx, params)
if err != nil {
return fmt.Errorf("textDocument/formatting: %v", err)
}
edits := convertEdits(resp)
return e.editBufferLocked(ctx, path, edits)
}
func (e *Editor) checkBufferPosition(path string, pos Pos) error {
e.mu.Lock()
defer e.mu.Unlock()
buf, ok := e.buffers[path]
if !ok {
return fmt.Errorf("buffer %q is not open", path)
}
if !inText(pos, buf.content) {
return fmt.Errorf("position %v is invalid in buffer %q", pos, path)
}
return nil
}