internal/lsp/fake: fix some messiness around client hooks

While writing the fake editor, I added some state tracking without using
it (log messages, events etc). We have since duplicated this logic in
the regtest package using client hooks.

Fix two messy aspects of this:
 - remove the state tracking in the editor
 - pass in the client hooks when connecting, so that they may be used
   without locking, and so that we do not miss any hooks that may fire
   during session initialization.

Change-Id: I24c17a28e9cfa4fca32b7ddd17c7bf01cbb12e0f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/232744
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Rob Findley 2020-05-07 12:25:33 -04:00 committed by Robert Findley
parent 88bf40a80d
commit b8469989bc
4 changed files with 41 additions and 94 deletions

View File

@ -10,59 +10,25 @@ import (
"golang.org/x/tools/internal/lsp/protocol"
)
// ClientHooks are called to handle the corresponding client LSP method.
type ClientHooks struct {
OnLogMessage func(context.Context, *protocol.LogMessageParams) error
OnDiagnostics func(context.Context, *protocol.PublishDiagnosticsParams) error
OnWorkDoneProgressCreate func(context.Context, *protocol.WorkDoneProgressCreateParams) error
OnProgress func(context.Context, *protocol.ProgressParams) error
OnShowMessage func(context.Context, *protocol.ShowMessageParams) error
}
// Client is an adapter that converts an *Editor into an LSP Client. It mosly
// delegates functionality to hooks that can be configured by tests.
type Client struct {
*Editor
// Hooks for testing. Add additional hooks here as needed for testing.
onLogMessage func(context.Context, *protocol.LogMessageParams) error
onDiagnostics func(context.Context, *protocol.PublishDiagnosticsParams) error
onWorkDoneProgressCreate func(context.Context, *protocol.WorkDoneProgressCreateParams) error
onProgress func(context.Context, *protocol.ProgressParams) error
onShowMessage func(context.Context, *protocol.ShowMessageParams) error
}
// OnShowMessage sets the hook to run when the editor receives a showMessage notification
func (c *Client) OnShowMessage(hook func(context.Context, *protocol.ShowMessageParams) error) {
c.mu.Lock()
c.onShowMessage = hook
c.mu.Unlock()
}
// OnLogMessage sets the hook to run when the editor receives a log message.
func (c *Client) OnLogMessage(hook func(context.Context, *protocol.LogMessageParams) error) {
c.mu.Lock()
c.onLogMessage = hook
c.mu.Unlock()
}
// OnDiagnostics sets the hook to run when the editor receives diagnostics
// published from the language server.
func (c *Client) OnDiagnostics(hook func(context.Context, *protocol.PublishDiagnosticsParams) error) {
c.mu.Lock()
c.onDiagnostics = hook
c.mu.Unlock()
}
func (c *Client) OnWorkDoneProgressCreate(hook func(context.Context, *protocol.WorkDoneProgressCreateParams) error) {
c.mu.Lock()
c.onWorkDoneProgressCreate = hook
c.mu.Unlock()
}
func (c *Client) OnProgress(hook func(context.Context, *protocol.ProgressParams) error) {
c.mu.Lock()
c.onProgress = hook
c.mu.Unlock()
editor *Editor
hooks ClientHooks
}
func (c *Client) ShowMessage(ctx context.Context, params *protocol.ShowMessageParams) error {
c.mu.Lock()
c.lastMessage = params
c.mu.Unlock()
if c.onShowMessage != nil {
return c.onShowMessage(ctx, params)
if c.hooks.OnShowMessage != nil {
return c.hooks.OnShowMessage(ctx, params)
}
return nil
}
@ -72,30 +38,19 @@ func (c *Client) ShowMessageRequest(ctx context.Context, params *protocol.ShowMe
}
func (c *Client) LogMessage(ctx context.Context, params *protocol.LogMessageParams) error {
c.mu.Lock()
c.logs = append(c.logs, params)
onLogMessage := c.onLogMessage
c.mu.Unlock()
if onLogMessage != nil {
return onLogMessage(ctx, params)
if c.hooks.OnLogMessage != nil {
return c.hooks.OnLogMessage(ctx, params)
}
return nil
}
func (c *Client) Event(ctx context.Context, event *interface{}) error {
c.mu.Lock()
c.events = append(c.events, event)
c.mu.Unlock()
return nil
}
func (c *Client) PublishDiagnostics(ctx context.Context, params *protocol.PublishDiagnosticsParams) error {
c.mu.Lock()
c.diagnostics = params
onPublishDiagnostics := c.onDiagnostics
c.mu.Unlock()
if onPublishDiagnostics != nil {
return onPublishDiagnostics(ctx, params)
if c.hooks.OnDiagnostics != nil {
return c.hooks.OnDiagnostics(ctx, params)
}
return nil
}
@ -110,7 +65,7 @@ func (c *Client) Configuration(_ context.Context, p *protocol.ParamConfiguration
if item.Section != "gopls" {
continue
}
results[i] = c.configuration()
results[i] = c.editor.configuration()
}
return results, nil
}
@ -124,21 +79,15 @@ func (c *Client) UnregisterCapability(context.Context, *protocol.UnregistrationP
}
func (c *Client) Progress(ctx context.Context, params *protocol.ProgressParams) error {
c.mu.Lock()
onProgress := c.onProgress
c.mu.Unlock()
if onProgress != nil {
return onProgress(ctx, params)
if c.hooks.OnProgress != nil {
return c.hooks.OnProgress(ctx, params)
}
return nil
}
func (c *Client) WorkDoneProgressCreate(ctx context.Context, params *protocol.WorkDoneProgressCreateParams) error {
c.mu.Lock()
onCreate := c.onWorkDoneProgressCreate
c.mu.Unlock()
if onCreate != nil {
return onCreate(ctx, params)
if c.hooks.OnWorkDoneProgressCreate != nil {
return c.hooks.OnWorkDoneProgressCreate(ctx, params)
}
return nil
}
@ -150,9 +99,9 @@ func (c *Client) ApplyEdit(ctx context.Context, params *protocol.ApplyWorkspaceE
return &protocol.ApplyWorkspaceEditResponse{FailureReason: "Edit.Changes is unsupported"}, nil
}
for _, change := range params.Edit.DocumentChanges {
path := c.sandbox.Workdir.URIToPath(change.TextDocument.URI)
path := c.editor.sandbox.Workdir.URIToPath(change.TextDocument.URI)
edits := convertEdits(change.Edits)
c.EditBuffer(ctx, path, edits)
c.editor.EditBuffer(ctx, path, edits)
}
return &protocol.ApplyWorkspaceEditResponse{Applied: true}, nil
}

View File

@ -32,11 +32,7 @@ type Editor struct {
// locking.
mu sync.Mutex
// Editor state.
buffers map[string]buffer
lastMessage *protocol.ShowMessageParams
logs []*protocol.LogMessageParams
diagnostics *protocol.PublishDiagnosticsParams
events []interface{}
buffers map[string]buffer
// Capabilities / Options
serverCapabilities protocol.ServerCapabilities
}
@ -80,9 +76,9 @@ func NewEditor(ws *Sandbox, config EditorConfig) *Editor {
//
// It returns the editor, so that it may be called as follows:
// editor, err := NewEditor(s).Connect(ctx, conn)
func (e *Editor) Connect(ctx context.Context, conn *jsonrpc2.Conn) (*Editor, error) {
func (e *Editor) Connect(ctx context.Context, conn *jsonrpc2.Conn, hooks ClientHooks) (*Editor, error) {
e.server = protocol.ServerDispatcher(conn)
e.client = &Client{Editor: e}
e.client = &Client{editor: e, hooks: hooks}
go conn.Run(ctx,
protocol.Handlers(
protocol.ClientHandler(e.client,

View File

@ -218,13 +218,13 @@ func TestDebugInfoLifecycle(t *testing.T) {
tsForwarder := servertest.NewPipeServer(clientCtx, forwarder)
conn1 := tsForwarder.Connect(clientCtx)
ed1, err := fake.NewEditor(sb, fake.EditorConfig{}).Connect(clientCtx, conn1)
ed1, err := fake.NewEditor(sb, fake.EditorConfig{}).Connect(clientCtx, conn1, fake.ClientHooks{})
if err != nil {
t.Fatal(err)
}
defer ed1.Shutdown(clientCtx)
conn2 := tsBackend.Connect(baseCtx)
ed2, err := fake.NewEditor(sb, fake.EditorConfig{}).Connect(baseCtx, conn2)
ed2, err := fake.NewEditor(sb, fake.EditorConfig{}).Connect(baseCtx, conn2, fake.ClientHooks{})
if err != nil {
t.Fatal(err)
}

View File

@ -105,15 +105,10 @@ type condition struct {
func NewEnv(ctx context.Context, t *testing.T, scratch *fake.Sandbox, ts servertest.Connector, editorConfig fake.EditorConfig) *Env {
t.Helper()
conn := ts.Connect(ctx)
editor, err := fake.NewEditor(scratch, editorConfig).Connect(ctx, conn)
if err != nil {
t.Fatal(err)
}
env := &Env{
T: t,
Ctx: ctx,
Sandbox: scratch,
Editor: editor,
Server: ts,
Conn: conn,
state: State{
@ -123,11 +118,18 @@ func NewEnv(ctx context.Context, t *testing.T, scratch *fake.Sandbox, ts servert
},
waiters: make(map[int]*condition),
}
env.Editor.Client().OnDiagnostics(env.onDiagnostics)
env.Editor.Client().OnLogMessage(env.onLogMessage)
env.Editor.Client().OnWorkDoneProgressCreate(env.onWorkDoneProgressCreate)
env.Editor.Client().OnProgress(env.onProgress)
env.Editor.Client().OnShowMessage(env.onShowMessage)
hooks := fake.ClientHooks{
OnDiagnostics: env.onDiagnostics,
OnLogMessage: env.onLogMessage,
OnWorkDoneProgressCreate: env.onWorkDoneProgressCreate,
OnProgress: env.onProgress,
OnShowMessage: env.onShowMessage,
}
editor, err := fake.NewEditor(scratch, editorConfig).Connect(ctx, conn, hooks)
if err != nil {
t.Fatal(err)
}
env.Editor = editor
return env
}