From a8f40b3f4d73b4863d04b4ad6b0337d3a78f5eaa Mon Sep 17 00:00:00 2001 From: Ian Cottrell Date: Thu, 28 Mar 2019 21:06:01 -0400 Subject: [PATCH] internal/jsonrpc2: split main loop from construction to fix race This changes the basic API of a jsonrpc2 connection to run the read loop as a method rather than in a go routine launched in the NewConn. This allows the handler to be created and bound between construction and the read loop starting, which fixes the race. Fixes golang/go#30091 Change-Id: I8201175affe431819cf473e5194d70c019f58425 Reviewed-on: https://go-review.googlesource.com/c/tools/+/170003 Run-TryBot: Ian Cottrell Reviewed-by: Rebecca Stambler --- internal/jsonrpc2/jsonrpc2.go | 101 ++++++++----------------- internal/jsonrpc2/jsonrpc2_test.go | 60 +++++++-------- internal/lsp/cmd/cmd.go | 12 ++- internal/lsp/cmd/serve.go | 12 ++- internal/lsp/diagnostics.go | 4 +- internal/lsp/lsp_test.go | 12 +-- internal/lsp/protocol/protocol.go | 14 ++-- internal/lsp/server.go | 116 +++++++++++++++-------------- 8 files changed, 151 insertions(+), 180 deletions(-) diff --git a/internal/jsonrpc2/jsonrpc2.go b/internal/jsonrpc2/jsonrpc2.go index e465d3ab440..5c30829286b 100644 --- a/internal/jsonrpc2/jsonrpc2.go +++ b/internal/jsonrpc2/jsonrpc2.go @@ -19,13 +19,12 @@ import ( // Conn is a JSON RPC 2 client server connection. // Conn is bidirectional; it does not have a designated server or client end. type Conn struct { - handle Handler - cancel Canceler - log Logger + seq int64 // must only be accessed using atomic operations + Handler Handler + Canceler Canceler + Logger Logger stream Stream - done chan struct{} err error - seq int64 // must only be accessed using atomic operations pendingMu sync.Mutex // protects the pending map pending map[ID]chan *Response handlingMu sync.Mutex // protects the handling map @@ -57,70 +56,27 @@ func NewErrorf(code int64, format string, args ...interface{}) *Error { } } -// NewConn creates a new connection object that reads and writes messages from -// the supplied stream and dispatches incoming messages to the supplied handler. -func NewConn(ctx context.Context, s Stream, options ...interface{}) *Conn { +// NewConn creates a new connection object around the supplied stream. +// You must call Run for the connection to be active. +func NewConn(s Stream) *Conn { conn := &Conn{ stream: s, - done: make(chan struct{}), pending: make(map[ID]chan *Response), handling: make(map[ID]handling), } - for _, opt := range options { - switch opt := opt.(type) { - case Handler: - if conn.handle != nil { - panic("Duplicate Handler function in options list") - } - conn.handle = opt - case Canceler: - if conn.cancel != nil { - panic("Duplicate Canceler function in options list") - } - conn.cancel = opt - case Logger: - if conn.log != nil { - panic("Duplicate Logger function in options list") - } - conn.log = opt - default: - panic(fmt.Errorf("Unknown option type %T in options list", opt)) + // the default handler reports a method error + conn.Handler = func(ctx context.Context, c *Conn, r *Request) { + if r.IsNotify() { + c.Reply(ctx, r, nil, NewErrorf(CodeMethodNotFound, "method %q not found", r.Method)) } } - if conn.handle == nil { - // the default handler reports a method error - conn.handle = func(ctx context.Context, c *Conn, r *Request) { - if r.IsNotify() { - c.Reply(ctx, r, nil, NewErrorf(CodeMethodNotFound, "method %q not found", r.Method)) - } - } - } - if conn.cancel == nil { - // the default canceller does nothing - conn.cancel = func(context.Context, *Conn, *Request) {} - } - if conn.log == nil { - // the default logger does nothing - conn.log = func(Direction, *ID, time.Duration, string, *json.RawMessage, *Error) {} - } - go func() { - conn.err = conn.run(ctx) - close(conn.done) - }() + // the default canceller does nothing + conn.Canceler = func(context.Context, *Conn, *Request) {} + // the default logger does nothing + conn.Logger = func(Direction, *ID, time.Duration, string, *json.RawMessage, *Error) {} return conn } -// Wait blocks until the connection is terminated, and returns any error that -// cause the termination. -func (c *Conn) Wait(ctx context.Context) error { - select { - case <-c.done: - return c.err - case <-ctx.Done(): - return ctx.Err() - } -} - // Cancel cancels a pending Call on the server side. // The call is identified by its id. // JSON RPC 2 does not specify a cancel message, so cancellation support is not @@ -151,7 +107,7 @@ func (c *Conn) Notify(ctx context.Context, method string, params interface{}) er if err != nil { return fmt.Errorf("marshalling notify request: %v", err) } - c.log(Send, nil, -1, request.Method, request.Params, nil) + c.Logger(Send, nil, -1, request.Method, request.Params, nil) return c.stream.Write(ctx, data) } @@ -189,7 +145,7 @@ func (c *Conn) Call(ctx context.Context, method string, params, result interface }() // now we are ready to send before := time.Now() - c.log(Send, request.ID, -1, request.Method, request.Params, nil) + c.Logger(Send, request.ID, -1, request.Method, request.Params, nil) if err := c.stream.Write(ctx, data); err != nil { // sending failed, we will never get a response, so don't leave it pending return err @@ -198,7 +154,7 @@ func (c *Conn) Call(ctx context.Context, method string, params, result interface select { case response := <-rchan: elapsed := time.Since(before) - c.log(Send, response.ID, elapsed, request.Method, response.Result, response.Error) + c.Logger(Send, response.ID, elapsed, request.Method, response.Result, response.Error) // is it an error response? if response.Error != nil { return response.Error @@ -212,7 +168,7 @@ func (c *Conn) Call(ctx context.Context, method string, params, result interface return nil case <-ctx.Done(): // allow the handler to propagate the cancel - c.cancel(ctx, c, request) + c.Canceler(ctx, c, request) return ctx.Err() } } @@ -255,7 +211,7 @@ func (c *Conn) Reply(ctx context.Context, req *Request, result interface{}, err if err != nil { return err } - c.log(Send, response.ID, elapsed, req.Method, response.Result, response.Error) + c.Logger(Send, response.ID, elapsed, req.Method, response.Result, response.Error) if err = c.stream.Write(ctx, data); err != nil { // TODO(iancottrell): if a stream write fails, we really need to shut down // the whole stream @@ -281,10 +237,11 @@ type combined struct { Error *Error `json:"error,omitempty"` } -// Run starts a read loop on the supplied reader. +// Run blocks until the connection is terminated, and returns any error that +// caused the termination. // It must be called exactly once for each Conn. // It returns only when the reader is closed or there is an error in the stream. -func (c *Conn) run(ctx context.Context) error { +func (c *Conn) Run(ctx context.Context) error { for { // get the data for a message data, err := c.stream.Read(ctx) @@ -297,7 +254,7 @@ func (c *Conn) run(ctx context.Context) error { if err := json.Unmarshal(data, msg); err != nil { // a badly formed message arrived, log it and continue // we trust the stream to have isolated the error to just this message - c.log(Receive, nil, -1, "", nil, NewErrorf(0, "unmarshal failed: %v", err)) + c.Logger(Receive, nil, -1, "", nil, NewErrorf(0, "unmarshal failed: %v", err)) continue } // work out which kind of message we have @@ -310,9 +267,9 @@ func (c *Conn) run(ctx context.Context) error { ID: msg.ID, } if request.IsNotify() { - c.log(Receive, request.ID, -1, request.Method, request.Params, nil) + c.Logger(Receive, request.ID, -1, request.Method, request.Params, nil) // we have a Notify, forward to the handler in a go routine - c.handle(ctx, c, request) + c.Handler(ctx, c, request) } else { // we have a Call, forward to the handler in another go routine reqCtx, cancelReq := context.WithCancel(ctx) @@ -323,8 +280,8 @@ func (c *Conn) run(ctx context.Context) error { start: time.Now(), } c.handlingMu.Unlock() - c.log(Receive, request.ID, -1, request.Method, request.Params, nil) - c.handle(reqCtx, c, request) + c.Logger(Receive, request.ID, -1, request.Method, request.Params, nil) + c.Handler(reqCtx, c, request) } case msg.ID != nil: // we have a response, get the pending entry from the map @@ -343,7 +300,7 @@ func (c *Conn) run(ctx context.Context) error { rchan <- response close(rchan) default: - c.log(Receive, nil, -1, "", nil, NewErrorf(0, "message not a call, notify or response, ignoring")) + c.Logger(Receive, nil, -1, "", nil, NewErrorf(0, "message not a call, notify or response, ignoring")) } } } diff --git a/internal/jsonrpc2/jsonrpc2_test.go b/internal/jsonrpc2/jsonrpc2_test.go index 264200b05a8..5b4e0fb8574 100644 --- a/internal/jsonrpc2/jsonrpc2_test.go +++ b/internal/jsonrpc2/jsonrpc2_test.go @@ -90,42 +90,36 @@ func TestHeaderCall(t *testing.T) { } } -func prepare(ctx context.Context, t *testing.T, withHeaders bool) (*testHandler, *testHandler) { - a := &testHandler{t: t} - b := &testHandler{t: t} - a.reader, b.writer = io.Pipe() - b.reader, a.writer = io.Pipe() - for _, h := range []*testHandler{a, b} { - h := h - if withHeaders { - h.stream = jsonrpc2.NewHeaderStream(h.reader, h.writer) - } else { - h.stream = jsonrpc2.NewStream(h.reader, h.writer) - } - args := []interface{}{jsonrpc2.Handler(handle)} - if *logRPC { - args = append(args, jsonrpc2.Log) - } - h.Conn = jsonrpc2.NewConn(ctx, h.stream, args...) - go func() { - defer func() { - h.reader.Close() - h.writer.Close() - }() - if err := h.Conn.Wait(ctx); err != nil { - t.Fatalf("Stream failed: %v", err) - } - }() - } +func prepare(ctx context.Context, t *testing.T, withHeaders bool) (*jsonrpc2.Conn, *jsonrpc2.Conn) { + aR, bW := io.Pipe() + bR, aW := io.Pipe() + a := run(ctx, t, withHeaders, aR, aW) + b := run(ctx, t, withHeaders, bR, bW) return a, b } -type testHandler struct { - t *testing.T - reader *io.PipeReader - writer *io.PipeWriter - stream jsonrpc2.Stream - *jsonrpc2.Conn +func run(ctx context.Context, t *testing.T, withHeaders bool, r io.ReadCloser, w io.WriteCloser) *jsonrpc2.Conn { + var stream jsonrpc2.Stream + if withHeaders { + stream = jsonrpc2.NewHeaderStream(r, w) + } else { + stream = jsonrpc2.NewStream(r, w) + } + conn := jsonrpc2.NewConn(stream) + conn.Handler = handle + if *logRPC { + conn.Logger = jsonrpc2.Log + } + go func() { + defer func() { + r.Close() + w.Close() + }() + if err := conn.Run(ctx); err != nil { + t.Fatalf("Stream failed: %v", err) + } + }() + return conn } func handle(ctx context.Context, c *jsonrpc2.Conn, r *jsonrpc2.Request) { diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go index e664cd114e6..6ac65cc8234 100644 --- a/internal/lsp/cmd/cmd.go +++ b/internal/lsp/cmd/cmd.go @@ -121,22 +121,26 @@ func (app *Application) connect(ctx context.Context, client protocol.Client) (pr var server protocol.Server switch app.Remote { case "": - server = lsp.NewServer(client) + server = lsp.NewClientServer(client) case "internal": cr, sw, _ := os.Pipe() sr, cw, _ := os.Pipe() - _, server = protocol.RunClient(ctx, jsonrpc2.NewHeaderStream(cr, cw), client) - go lsp.RunServer(ctx, jsonrpc2.NewHeaderStream(sr, sw)) + var jc *jsonrpc2.Conn + jc, server = protocol.NewClient(jsonrpc2.NewHeaderStream(cr, cw), client) + go jc.Run(ctx) + go lsp.NewServer(jsonrpc2.NewHeaderStream(sr, sw)).Run(ctx) default: conn, err := net.Dial("tcp", app.Remote) if err != nil { return nil, err } stream := jsonrpc2.NewHeaderStream(conn, conn) - _, server = protocol.RunClient(ctx, stream, client) + var jc *jsonrpc2.Conn + jc, server = protocol.NewClient(stream, client) if err != nil { return nil, err } + go jc.Run(ctx) } params := &protocol.InitializeParams{} params.RootURI = string(span.FileURI(app.Config.Dir)) diff --git a/internal/lsp/cmd/serve.go b/internal/lsp/cmd/serve.go index 2bf79f79c91..afa333ac1e7 100644 --- a/internal/lsp/cmd/serve.go +++ b/internal/lsp/cmd/serve.go @@ -119,14 +119,20 @@ func (s *Serve) Run(ctx context.Context, args ...string) error { fmt.Fprintf(out, "%s", outx.String()) } // For debugging purposes only. + run := func(srv *lsp.Server) { + srv.Conn.Logger = logger + go srv.Conn.Run(ctx) + } if s.Address != "" { - return lsp.RunServerOnAddress(ctx, s.Address, logger) + return lsp.RunServerOnAddress(ctx, s.Address, run) } if s.Port != 0 { - return lsp.RunServerOnPort(ctx, s.Port, logger) + return lsp.RunServerOnPort(ctx, s.Port, run) } stream := jsonrpc2.NewHeaderStream(os.Stdin, os.Stdout) - return lsp.RunServer(ctx, stream, logger) + srv := lsp.NewServer(stream) + srv.Conn.Logger = logger + return srv.Conn.Run(ctx) } func (s *Serve) forward() error { diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go index f7838e95377..bef6dccd46e 100644 --- a/internal/lsp/diagnostics.go +++ b/internal/lsp/diagnostics.go @@ -13,7 +13,7 @@ import ( "golang.org/x/tools/internal/span" ) -func (s *server) cacheAndDiagnose(ctx context.Context, uri span.URI, content string) error { +func (s *Server) cacheAndDiagnose(ctx context.Context, uri span.URI, content string) error { if err := s.setContent(ctx, uri, []byte(content)); err != nil { return err } @@ -43,7 +43,7 @@ func (s *server) cacheAndDiagnose(ctx context.Context, uri span.URI, content str return nil } -func (s *server) setContent(ctx context.Context, uri span.URI, content []byte) error { +func (s *Server) setContent(ctx context.Context, uri span.URI, content []byte) error { return s.view.SetContent(ctx, uri, content) } diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go index b1e46fc4ec7..618847070bf 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -70,7 +70,7 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) { return parser.ParseFile(fset, filename, src, parser.AllErrors|parser.ParseComments) } - s := &server{ + s := &Server{ view: cache.NewView("lsp_test", span.FileURI(cfg.Dir), &cfg), } // Do a first pass to collect special markers for completion. @@ -285,7 +285,7 @@ Failed: return msg.String() } -func (c completions) test(t *testing.T, exported *packagestest.Exported, s *server, items completionItems) { +func (c completions) test(t *testing.T, exported *packagestest.Exported, s *Server, items completionItems) { for src, itemList := range c { var want []protocol.CompletionItem for _, pos := range itemList { @@ -406,7 +406,7 @@ Failed: return msg.String() } -func (f formats) test(t *testing.T, s *server) { +func (f formats) test(t *testing.T, s *Server) { ctx := context.Background() for filename, gofmted := range f { uri := span.FileURI(filename) @@ -444,7 +444,7 @@ func (f formats) collect(pos token.Position) { f[pos.Filename] = stdout.String() } -func (d definitions) test(t *testing.T, s *server, typ bool) { +func (d definitions) test(t *testing.T, s *Server, typ bool) { for src, target := range d { params := &protocol.TextDocumentPositionParams{ TextDocument: protocol.TextDocumentIdentifier{ @@ -495,7 +495,7 @@ func (h highlights) collect(e *packagestest.Exported, fset *token.FileSet, name h[name] = append(h[name], loc) } -func (h highlights) test(t *testing.T, s *server) { +func (h highlights) test(t *testing.T, s *Server) { for name, locations := range h { params := &protocol.TextDocumentPositionParams{ TextDocument: protocol.TextDocumentIdentifier{ @@ -547,7 +547,7 @@ func (s symbols) collect(e *packagestest.Exported, fset *token.FileSet, name str }) } -func (s symbols) test(t *testing.T, server *server) { +func (s symbols) test(t *testing.T, server *Server) { for uri, expectedSymbols := range s { params := &protocol.DocumentSymbolParams{ TextDocument: protocol.TextDocumentIdentifier{ diff --git a/internal/lsp/protocol/protocol.go b/internal/lsp/protocol/protocol.go index 5d11c3c8950..e5737f8b573 100644 --- a/internal/lsp/protocol/protocol.go +++ b/internal/lsp/protocol/protocol.go @@ -15,15 +15,17 @@ func canceller(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) conn.Notify(context.Background(), "$/cancelRequest", &CancelParams{ID: *req.ID}) } -func RunClient(ctx context.Context, stream jsonrpc2.Stream, client Client, opts ...interface{}) (*jsonrpc2.Conn, Server) { - opts = append([]interface{}{clientHandler(client), jsonrpc2.Canceler(canceller)}, opts...) - conn := jsonrpc2.NewConn(ctx, stream, opts...) +func NewClient(stream jsonrpc2.Stream, client Client) (*jsonrpc2.Conn, Server) { + conn := jsonrpc2.NewConn(stream) + conn.Handler = clientHandler(client) + conn.Canceler = jsonrpc2.Canceler(canceller) return conn, &serverDispatcher{Conn: conn} } -func RunServer(ctx context.Context, stream jsonrpc2.Stream, server Server, opts ...interface{}) (*jsonrpc2.Conn, Client) { - opts = append([]interface{}{serverHandler(server), jsonrpc2.Canceler(canceller)}, opts...) - conn := jsonrpc2.NewConn(ctx, stream, opts...) +func NewServer(stream jsonrpc2.Stream, server Server) (*jsonrpc2.Conn, Client) { + conn := jsonrpc2.NewConn(stream) + conn.Handler = serverHandler(server) + conn.Canceler = jsonrpc2.Canceler(canceller) return conn, &clientDispatcher{Conn: conn} } diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 9f5dc34569c..384e4ca52e2 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -25,32 +25,33 @@ import ( "golang.org/x/tools/internal/span" ) -// NewServer -func NewServer(client protocol.Client) protocol.Server { - return &server{ +// NewClientServer +func NewClientServer(client protocol.Client) *Server { + return &Server{ client: client, configured: make(chan struct{}), } } -// RunServer starts an LSP server on the supplied stream, and waits until the +// NewServer starts an LSP server on the supplied stream, and waits until the // stream is closed. -func RunServer(ctx context.Context, stream jsonrpc2.Stream, opts ...interface{}) error { - s := NewServer(nil).(*server) - conn, client := protocol.RunServer(ctx, stream, s, opts...) - s.client = client - return conn.Wait(ctx) +func NewServer(stream jsonrpc2.Stream) *Server { + s := &Server{ + configured: make(chan struct{}), + } + s.Conn, s.client = protocol.NewServer(stream, s) + return s } // RunServerOnPort starts an LSP server on the given port and does not exit. // This function exists for debugging purposes. -func RunServerOnPort(ctx context.Context, port int, opts ...interface{}) error { - return RunServerOnAddress(ctx, fmt.Sprintf(":%v", port)) +func RunServerOnPort(ctx context.Context, port int, h func(s *Server)) error { + return RunServerOnAddress(ctx, fmt.Sprintf(":%v", port), h) } // RunServerOnPort starts an LSP server on the given port and does not exit. // This function exists for debugging purposes. -func RunServerOnAddress(ctx context.Context, addr string, opts ...interface{}) error { +func RunServerOnAddress(ctx context.Context, addr string, h func(s *Server)) error { ln, err := net.Listen("tcp", addr) if err != nil { return err @@ -61,11 +62,14 @@ func RunServerOnAddress(ctx context.Context, addr string, opts ...interface{}) e return err } stream := jsonrpc2.NewHeaderStream(conn, conn) - go RunServer(ctx, stream, opts...) + s := NewServer(stream) + h(s) + go s.Run(ctx) } } -type server struct { +type Server struct { + Conn *jsonrpc2.Conn client protocol.Client initializedMu sync.Mutex @@ -81,7 +85,11 @@ type server struct { configured chan struct{} } -func (s *server) Initialize(ctx context.Context, params *protocol.InitializeParams) (*protocol.InitializeResult, error) { +func (s *Server) Run(ctx context.Context) error { + return s.Conn.Run(ctx) +} + +func (s *Server) Initialize(ctx context.Context, params *protocol.InitializeParams) (*protocol.InitializeResult, error) { s.initializedMu.Lock() defer s.initializedMu.Unlock() if s.initialized { @@ -151,7 +159,7 @@ func (s *server) Initialize(ctx context.Context, params *protocol.InitializePara }, nil } -func (s *server) Initialized(ctx context.Context, params *protocol.InitializedParams) error { +func (s *Server) Initialized(ctx context.Context, params *protocol.InitializedParams) error { go func() { // we hae to do this in a go routine to unblock the jsonrpc processor // but we also have to block all calls to packages.Load until this is done @@ -179,7 +187,7 @@ func (s *server) Initialized(ctx context.Context, params *protocol.InitializedPa return nil } -func (s *server) Shutdown(context.Context) error { +func (s *Server) Shutdown(context.Context) error { s.initializedMu.Lock() defer s.initializedMu.Unlock() if !s.initialized { @@ -189,7 +197,7 @@ func (s *server) Shutdown(context.Context) error { return nil } -func (s *server) Exit(ctx context.Context) error { +func (s *Server) Exit(ctx context.Context) error { if s.initialized { os.Exit(1) } @@ -197,31 +205,31 @@ func (s *server) Exit(ctx context.Context) error { return nil } -func (s *server) DidChangeWorkspaceFolders(context.Context, *protocol.DidChangeWorkspaceFoldersParams) error { +func (s *Server) DidChangeWorkspaceFolders(context.Context, *protocol.DidChangeWorkspaceFoldersParams) error { return notImplemented("DidChangeWorkspaceFolders") } -func (s *server) DidChangeConfiguration(context.Context, *protocol.DidChangeConfigurationParams) error { +func (s *Server) DidChangeConfiguration(context.Context, *protocol.DidChangeConfigurationParams) error { return notImplemented("DidChangeConfiguration") } -func (s *server) DidChangeWatchedFiles(context.Context, *protocol.DidChangeWatchedFilesParams) error { +func (s *Server) DidChangeWatchedFiles(context.Context, *protocol.DidChangeWatchedFilesParams) error { return notImplemented("DidChangeWatchedFiles") } -func (s *server) Symbols(context.Context, *protocol.WorkspaceSymbolParams) ([]protocol.SymbolInformation, error) { +func (s *Server) Symbols(context.Context, *protocol.WorkspaceSymbolParams) ([]protocol.SymbolInformation, error) { return nil, notImplemented("Symbols") } -func (s *server) ExecuteCommand(context.Context, *protocol.ExecuteCommandParams) (interface{}, error) { +func (s *Server) ExecuteCommand(context.Context, *protocol.ExecuteCommandParams) (interface{}, error) { return nil, notImplemented("ExecuteCommand") } -func (s *server) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error { +func (s *Server) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error { return s.cacheAndDiagnose(ctx, span.NewURI(params.TextDocument.URI), params.TextDocument.Text) } -func (s *server) applyChanges(ctx context.Context, params *protocol.DidChangeTextDocumentParams) (string, error) { +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] @@ -257,7 +265,7 @@ func (s *server) applyChanges(ctx context.Context, params *protocol.DidChangeTex return string(content), nil } -func (s *server) DidChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error { +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") } @@ -281,24 +289,24 @@ func (s *server) DidChange(ctx context.Context, params *protocol.DidChangeTextDo return s.cacheAndDiagnose(ctx, span.NewURI(params.TextDocument.URI), text) } -func (s *server) WillSave(context.Context, *protocol.WillSaveTextDocumentParams) error { +func (s *Server) WillSave(context.Context, *protocol.WillSaveTextDocumentParams) error { return notImplemented("WillSave") } -func (s *server) WillSaveWaitUntil(context.Context, *protocol.WillSaveTextDocumentParams) ([]protocol.TextEdit, error) { +func (s *Server) WillSaveWaitUntil(context.Context, *protocol.WillSaveTextDocumentParams) ([]protocol.TextEdit, error) { return nil, notImplemented("WillSaveWaitUntil") } -func (s *server) DidSave(context.Context, *protocol.DidSaveTextDocumentParams) error { +func (s *Server) DidSave(context.Context, *protocol.DidSaveTextDocumentParams) error { return nil // ignore } -func (s *server) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error { +func (s *Server) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error { s.setContent(ctx, span.NewURI(params.TextDocument.URI), nil) return nil } -func (s *server) Completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) { +func (s *Server) Completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) { f, m, err := newColumnMap(ctx, s.view, span.NewURI(params.TextDocument.URI)) if err != nil { return nil, err @@ -321,11 +329,11 @@ func (s *server) Completion(ctx context.Context, params *protocol.CompletionPara }, nil } -func (s *server) CompletionResolve(context.Context, *protocol.CompletionItem) (*protocol.CompletionItem, error) { +func (s *Server) CompletionResolve(context.Context, *protocol.CompletionItem) (*protocol.CompletionItem, error) { return nil, notImplemented("CompletionResolve") } -func (s *server) Hover(ctx context.Context, params *protocol.TextDocumentPositionParams) (*protocol.Hover, error) { +func (s *Server) Hover(ctx context.Context, params *protocol.TextDocumentPositionParams) (*protocol.Hover, error) { f, m, err := newColumnMap(ctx, s.view, span.NewURI(params.TextDocument.URI)) if err != nil { return nil, err @@ -364,7 +372,7 @@ func (s *server) Hover(ctx context.Context, params *protocol.TextDocumentPositio }, nil } -func (s *server) SignatureHelp(ctx context.Context, params *protocol.TextDocumentPositionParams) (*protocol.SignatureHelp, error) { +func (s *Server) SignatureHelp(ctx context.Context, params *protocol.TextDocumentPositionParams) (*protocol.SignatureHelp, error) { f, m, err := newColumnMap(ctx, s.view, span.NewURI(params.TextDocument.URI)) if err != nil { return nil, err @@ -384,7 +392,7 @@ func (s *server) SignatureHelp(ctx context.Context, params *protocol.TextDocumen return toProtocolSignatureHelp(info), nil } -func (s *server) Definition(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.Location, error) { +func (s *Server) Definition(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.Location, error) { f, m, err := newColumnMap(ctx, s.view, span.NewURI(params.TextDocument.URI)) if err != nil { return nil, err @@ -416,7 +424,7 @@ func (s *server) Definition(ctx context.Context, params *protocol.TextDocumentPo return []protocol.Location{loc}, nil } -func (s *server) TypeDefinition(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.Location, error) { +func (s *Server) TypeDefinition(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.Location, error) { f, m, err := newColumnMap(ctx, s.view, span.NewURI(params.TextDocument.URI)) if err != nil { return nil, err @@ -448,15 +456,15 @@ func (s *server) TypeDefinition(ctx context.Context, params *protocol.TextDocume return []protocol.Location{loc}, nil } -func (s *server) Implementation(context.Context, *protocol.TextDocumentPositionParams) ([]protocol.Location, error) { +func (s *Server) Implementation(context.Context, *protocol.TextDocumentPositionParams) ([]protocol.Location, error) { return nil, notImplemented("Implementation") } -func (s *server) References(context.Context, *protocol.ReferenceParams) ([]protocol.Location, error) { +func (s *Server) References(context.Context, *protocol.ReferenceParams) ([]protocol.Location, error) { return nil, notImplemented("References") } -func (s *server) DocumentHighlight(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.DocumentHighlight, error) { +func (s *Server) DocumentHighlight(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.DocumentHighlight, error) { f, m, err := newColumnMap(ctx, s.view, span.NewURI(params.TextDocument.URI)) if err != nil { return nil, err @@ -476,7 +484,7 @@ func (s *server) DocumentHighlight(ctx context.Context, params *protocol.TextDoc return toProtocolHighlight(m, spans), nil } -func (s *server) DocumentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) ([]protocol.DocumentSymbol, error) { +func (s *Server) DocumentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) ([]protocol.DocumentSymbol, error) { f, m, err := newColumnMap(ctx, s.view, span.NewURI(params.TextDocument.URI)) if err != nil { return nil, err @@ -485,7 +493,7 @@ func (s *server) DocumentSymbol(ctx context.Context, params *protocol.DocumentSy return toProtocolDocumentSymbols(m, symbols), nil } -func (s *server) CodeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) { +func (s *Server) CodeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) { _, m, err := newColumnMap(ctx, s.view, span.NewURI(params.TextDocument.URI)) if err != nil { return nil, err @@ -511,36 +519,36 @@ func (s *server) CodeAction(ctx context.Context, params *protocol.CodeActionPara }, nil } -func (s *server) CodeLens(context.Context, *protocol.CodeLensParams) ([]protocol.CodeLens, error) { +func (s *Server) CodeLens(context.Context, *protocol.CodeLensParams) ([]protocol.CodeLens, error) { return nil, nil // ignore } -func (s *server) CodeLensResolve(context.Context, *protocol.CodeLens) (*protocol.CodeLens, error) { +func (s *Server) CodeLensResolve(context.Context, *protocol.CodeLens) (*protocol.CodeLens, error) { return nil, notImplemented("CodeLensResolve") } -func (s *server) DocumentLink(context.Context, *protocol.DocumentLinkParams) ([]protocol.DocumentLink, error) { +func (s *Server) DocumentLink(context.Context, *protocol.DocumentLinkParams) ([]protocol.DocumentLink, error) { return nil, nil // ignore } -func (s *server) DocumentLinkResolve(context.Context, *protocol.DocumentLink) (*protocol.DocumentLink, error) { +func (s *Server) DocumentLinkResolve(context.Context, *protocol.DocumentLink) (*protocol.DocumentLink, error) { return nil, notImplemented("DocumentLinkResolve") } -func (s *server) DocumentColor(context.Context, *protocol.DocumentColorParams) ([]protocol.ColorInformation, error) { +func (s *Server) DocumentColor(context.Context, *protocol.DocumentColorParams) ([]protocol.ColorInformation, error) { return nil, notImplemented("DocumentColor") } -func (s *server) ColorPresentation(context.Context, *protocol.ColorPresentationParams) ([]protocol.ColorPresentation, error) { +func (s *Server) ColorPresentation(context.Context, *protocol.ColorPresentationParams) ([]protocol.ColorPresentation, error) { return nil, notImplemented("ColorPresentation") } -func (s *server) Formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) { +func (s *Server) Formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) { spn := span.New(span.URI(params.TextDocument.URI), span.Point{}, span.Point{}) return formatRange(ctx, s.view, spn) } -func (s *server) RangeFormatting(ctx context.Context, params *protocol.DocumentRangeFormattingParams) ([]protocol.TextEdit, error) { +func (s *Server) RangeFormatting(ctx context.Context, params *protocol.DocumentRangeFormattingParams) ([]protocol.TextEdit, error) { _, m, err := newColumnMap(ctx, s.view, span.NewURI(params.TextDocument.URI)) if err != nil { return nil, err @@ -552,26 +560,26 @@ func (s *server) RangeFormatting(ctx context.Context, params *protocol.DocumentR return formatRange(ctx, s.view, spn) } -func (s *server) OnTypeFormatting(context.Context, *protocol.DocumentOnTypeFormattingParams) ([]protocol.TextEdit, error) { +func (s *Server) OnTypeFormatting(context.Context, *protocol.DocumentOnTypeFormattingParams) ([]protocol.TextEdit, error) { return nil, notImplemented("OnTypeFormatting") } -func (s *server) Rename(context.Context, *protocol.RenameParams) ([]protocol.WorkspaceEdit, error) { +func (s *Server) Rename(context.Context, *protocol.RenameParams) ([]protocol.WorkspaceEdit, error) { return nil, notImplemented("Rename") } -func (s *server) FoldingRanges(context.Context, *protocol.FoldingRangeParams) ([]protocol.FoldingRange, error) { +func (s *Server) FoldingRanges(context.Context, *protocol.FoldingRangeParams) ([]protocol.FoldingRange, error) { return nil, notImplemented("FoldingRanges") } -func (s *server) Error(err error) { +func (s *Server) Error(err error) { s.client.LogMessage(context.Background(), &protocol.LogMessageParams{ Type: protocol.Error, Message: fmt.Sprint(err), }) } -func (s *server) processConfig(config interface{}) error { +func (s *Server) processConfig(config interface{}) error { //TODO: we should probably store and process more of the config c, ok := config.(map[string]interface{}) if !ok {