diff --git a/internal/jsonrpc2/jsonrpc2.go b/internal/jsonrpc2/jsonrpc2.go index fd38f643..e465d3ab 100644 --- a/internal/jsonrpc2/jsonrpc2.go +++ b/internal/jsonrpc2/jsonrpc2.go @@ -29,15 +29,15 @@ type Conn struct { pendingMu sync.Mutex // protects the pending map pending map[ID]chan *Response handlingMu sync.Mutex // protects the handling map - handling map[ID]context.CancelFunc + handling map[ID]handling } // Handler is an option you can pass to NewConn to handle incoming requests. -// If the request returns true from IsNotify then the Handler should not return a -// result or error, otherwise it should handle the Request and return either -// an encoded result, or an error. -// Handlers must be concurrency-safe. -type Handler = func(context.Context, *Conn, *Request) (interface{}, *Error) +// If the request returns false from IsNotify then the Handler must eventually +// call Reply on the Conn with the supplied request. +// Handlers are called synchronously, they should pass the work off to a go +// routine if they are going to take a long time. +type Handler func(context.Context, *Conn, *Request) // Canceler is an option you can pass to NewConn which is invoked for // cancelled outgoing requests. @@ -46,7 +46,7 @@ type Handler = func(context.Context, *Conn, *Request) (interface{}, *Error) // It is okay to use the connection to send notifications, but the context will // be in the cancelled state, so you must do it with the background context // instead. -type Canceler = func(context.Context, *Conn, *Request) +type Canceler func(context.Context, *Conn, *Request) // NewErrorf builds a Error struct for the suppied message and code. // If args is not empty, message and args will be passed to Sprintf. @@ -64,7 +64,7 @@ func NewConn(ctx context.Context, s Stream, options ...interface{}) *Conn { stream: s, done: make(chan struct{}), pending: make(map[ID]chan *Response), - handling: make(map[ID]context.CancelFunc), + handling: make(map[ID]handling), } for _, opt := range options { switch opt := opt.(type) { @@ -89,8 +89,10 @@ func NewConn(ctx context.Context, s Stream, options ...interface{}) *Conn { } if conn.handle == nil { // the default handler reports a method error - conn.handle = func(ctx context.Context, c *Conn, r *Request) (interface{}, *Error) { - return nil, NewErrorf(CodeMethodNotFound, "method %q not found", r.Method) + 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 { @@ -126,10 +128,10 @@ func (c *Conn) Wait(ctx context.Context) error { // to propagate the cancel. func (c *Conn) Cancel(id ID) { c.handlingMu.Lock() - cancel := c.handling[id] + handling, found := c.handling[id] c.handlingMu.Unlock() - if cancel != nil { - cancel() + if found { + handling.cancel() } } @@ -215,6 +217,59 @@ func (c *Conn) Call(ctx context.Context, method string, params, result interface } } +// Reply sends a reply to the given request. +// It is an error to call this if request was not a call. +// You must call this exactly once for any given request. +// If err is set then result will be ignored. +func (c *Conn) Reply(ctx context.Context, req *Request, result interface{}, err error) error { + if req.IsNotify() { + return fmt.Errorf("reply not invoked with a valid call") + } + c.handlingMu.Lock() + handling, found := c.handling[*req.ID] + if found { + delete(c.handling, *req.ID) + } + c.handlingMu.Unlock() + if !found { + return fmt.Errorf("not a call in progress: %v", req.ID) + } + + elapsed := time.Since(handling.start) + var raw *json.RawMessage + if err == nil { + raw, err = marshalToRaw(result) + } + response := &Response{ + Result: raw, + ID: req.ID, + } + if err != nil { + if callErr, ok := err.(*Error); ok { + response.Error = callErr + } else { + response.Error = NewErrorf(0, "%s", err) + } + } + data, err := json.Marshal(response) + if err != nil { + return err + } + c.log(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 + return err + } + return nil +} + +type handling struct { + request *Request + cancel context.CancelFunc + start time.Time +} + // combined has all the fields of both Request and Response. // We can decode this and then work out which it is. type combined struct { @@ -230,7 +285,6 @@ type combined struct { // 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 { - ctx, cancelRun := context.WithCancel(ctx) for { // get the data for a message data, err := c.stream.Read(ctx) @@ -258,57 +312,19 @@ func (c *Conn) run(ctx context.Context) error { if request.IsNotify() { c.log(Receive, request.ID, -1, request.Method, request.Params, nil) // we have a Notify, forward to the handler in a go routine - go func() { - if _, err := c.handle(ctx, c, request); err != nil { - // notify produced an error, we can't forward it to the other side - // because there is no id, so we just log it - c.log(Receive, nil, -1, request.Method, nil, err) - } - }() + c.handle(ctx, c, request) } else { // we have a Call, forward to the handler in another go routine reqCtx, cancelReq := context.WithCancel(ctx) c.handlingMu.Lock() - c.handling[*request.ID] = cancelReq + c.handling[*request.ID] = handling{ + request: request, + cancel: cancelReq, + start: time.Now(), + } c.handlingMu.Unlock() - go func() { - defer func() { - // clean up the cancel handler on the way out - c.handlingMu.Lock() - delete(c.handling, *request.ID) - c.handlingMu.Unlock() - cancelReq() - }() - c.log(Receive, request.ID, -1, request.Method, request.Params, nil) - before := time.Now() - resp, callErr := c.handle(reqCtx, c, request) - elapsed := time.Since(before) - var result *json.RawMessage - if result, err = marshalToRaw(resp); err != nil { - callErr = &Error{Message: err.Error()} - } - response := &Response{ - Result: result, - Error: callErr, - ID: request.ID, - } - data, err := json.Marshal(response) - if err != nil { - // failure to marshal leaves the call without a response - // possibly we could attempt to respond with a different message - // but we can probably rely on timeouts instead - c.log(Send, request.ID, elapsed, request.Method, nil, NewErrorf(0, "%s", err)) - return - } - c.log(Send, response.ID, elapsed, request.Method, response.Result, response.Error) - if err = c.stream.Write(ctx, data); err != nil { - // if a stream write fails, we really need to shut down the whole - // stream and return from the run - c.log(Send, request.ID, elapsed, request.Method, nil, NewErrorf(0, "%s", err)) - cancelRun() - return - } - }() + c.log(Receive, request.ID, -1, request.Method, request.Params, nil) + c.handle(reqCtx, c, request) } case msg.ID != nil: // we have a response, get the pending entry from the map diff --git a/internal/jsonrpc2/jsonrpc2_test.go b/internal/jsonrpc2/jsonrpc2_test.go index 2556083e..264200b0 100644 --- a/internal/jsonrpc2/jsonrpc2_test.go +++ b/internal/jsonrpc2/jsonrpc2_test.go @@ -102,7 +102,7 @@ func prepare(ctx context.Context, t *testing.T, withHeaders bool) (*testHandler, } else { h.stream = jsonrpc2.NewStream(h.reader, h.writer) } - args := []interface{}{handle} + args := []interface{}{jsonrpc2.Handler(handle)} if *logRPC { args = append(args, jsonrpc2.Log) } @@ -128,32 +128,36 @@ type testHandler struct { *jsonrpc2.Conn } -func handle(ctx context.Context, c *jsonrpc2.Conn, r *jsonrpc2.Request) (interface{}, *jsonrpc2.Error) { +func handle(ctx context.Context, c *jsonrpc2.Conn, r *jsonrpc2.Request) { switch r.Method { case "no_args": if r.Params != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params") + c.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params")) + return } - return true, nil + c.Reply(ctx, r, true, nil) case "one_string": var v string if err := json.Unmarshal(*r.Params, &v); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()) + c.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error())) + return } - return "got:" + v, nil + c.Reply(ctx, r, "got:"+v, nil) case "one_number": var v int if err := json.Unmarshal(*r.Params, &v); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()) + c.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error())) + return } - return fmt.Sprintf("got:%d", v), nil + c.Reply(ctx, r, fmt.Sprintf("got:%d", v), nil) case "join": var v []string if err := json.Unmarshal(*r.Params, &v); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()) + c.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error())) + return } - return path.Join(v...), nil + c.Reply(ctx, r, path.Join(v...), nil) default: - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not found", r.Method) + c.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not found", r.Method)) } } diff --git a/internal/lsp/protocol/client.go b/internal/lsp/protocol/client.go index 1e713289..d16eef2b 100644 --- a/internal/lsp/protocol/client.go +++ b/internal/lsp/protocol/client.go @@ -25,121 +25,103 @@ type Client interface { } func clientHandler(client Client) jsonrpc2.Handler { - return func(ctx context.Context, conn *jsonrpc2.Conn, r *jsonrpc2.Request) (interface{}, *jsonrpc2.Error) { + return func(ctx context.Context, conn *jsonrpc2.Conn, r *jsonrpc2.Request) { switch r.Method { case "$/cancelRequest": var params CancelParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } conn.Cancel(params.ID) - return nil, nil case "window/showMessage": var params ShowMessageParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } - if err := client.ShowMessage(ctx, ¶ms); err != nil { - return nil, toJSONError(err) - } - return nil, nil + unhandledError(client.ShowMessage(ctx, ¶ms)) case "window/showMessageRequest": var params ShowMessageRequestParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := client.ShowMessageRequest(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "window/logMessage": var params LogMessageParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } - if err := client.LogMessage(ctx, ¶ms); err != nil { - return nil, toJSONError(err) - } - return nil, nil + unhandledError(client.LogMessage(ctx, ¶ms)) case "telemetry/event": var params interface{} if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } - if err := client.Telemetry(ctx, ¶ms); err != nil { - return nil, toJSONError(err) - } - return nil, nil + unhandledError(client.Telemetry(ctx, ¶ms)) case "client/registerCapability": var params RegistrationParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } - if err := client.RegisterCapability(ctx, ¶ms); err != nil { - return nil, toJSONError(err) - } - return nil, nil + unhandledError(client.RegisterCapability(ctx, ¶ms)) case "client/unregisterCapability": var params UnregistrationParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } - if err := client.UnregisterCapability(ctx, ¶ms); err != nil { - return nil, toJSONError(err) - } - return nil, nil + unhandledError(client.UnregisterCapability(ctx, ¶ms)) case "workspace/workspaceFolders": if r.Params != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params") + conn.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params")) + return } resp, err := client.WorkspaceFolders(ctx) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "workspace/configuration": var params ConfigurationParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := client.Configuration(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "workspace/applyEdit": var params ApplyWorkspaceEditParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := client.ApplyEdit(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/publishDiagnostics": var params PublishDiagnosticsParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } - if err := client.PublishDiagnostics(ctx, ¶ms); err != nil { - return nil, toJSONError(err) - } - return nil, nil + unhandledError(client.PublishDiagnostics(ctx, ¶ms)) default: - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not found", r.Method) + if r.IsNotify() { + conn.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not found", r.Method)) + } } } } diff --git a/internal/lsp/protocol/protocol.go b/internal/lsp/protocol/protocol.go index 27682502..5d11c3c8 100644 --- a/internal/lsp/protocol/protocol.go +++ b/internal/lsp/protocol/protocol.go @@ -6,6 +6,7 @@ package protocol import ( "context" + "log" "golang.org/x/tools/internal/jsonrpc2" ) @@ -15,20 +16,34 @@ func canceller(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) } func RunClient(ctx context.Context, stream jsonrpc2.Stream, client Client, opts ...interface{}) (*jsonrpc2.Conn, Server) { - opts = append([]interface{}{clientHandler(client), canceller}, opts...) + opts = append([]interface{}{clientHandler(client), jsonrpc2.Canceler(canceller)}, opts...) conn := jsonrpc2.NewConn(ctx, stream, opts...) 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), canceller}, opts...) + opts = append([]interface{}{serverHandler(server), jsonrpc2.Canceler(canceller)}, opts...) conn := jsonrpc2.NewConn(ctx, stream, opts...) return conn, &clientDispatcher{Conn: conn} } -func toJSONError(err error) *jsonrpc2.Error { - if jsonError, ok := err.(*jsonrpc2.Error); ok { - return jsonError +func sendParseError(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request, err error) { + if _, ok := err.(*jsonrpc2.Error); !ok { + err = jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) } - return jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + unhandledError(conn.Reply(ctx, req, nil, err)) +} + +// unhandledError is used in places where an error may occur that cannot be handled. +// This occurs in things like rpc handlers that are a notify, where we cannot +// reply to the caller, or in a call when we are actually attempting to reply. +// In these cases, there is nothing we can do with the error except log it, so +// we do that in this function, and the presence of this function acts as a +// useful reminder of why we are effectively dropping the error and also a +// good place to hook in when debugging those kinds of errors. +func unhandledError(err error) { + if err == nil { + return + } + log.Printf("%v", err) } diff --git a/internal/lsp/protocol/server.go b/internal/lsp/protocol/server.go index 22694f15..548812d8 100644 --- a/internal/lsp/protocol/server.go +++ b/internal/lsp/protocol/server.go @@ -52,411 +52,339 @@ type Server interface { } func serverHandler(server Server) jsonrpc2.Handler { - return func(ctx context.Context, conn *jsonrpc2.Conn, r *jsonrpc2.Request) (interface{}, *jsonrpc2.Error) { + return func(ctx context.Context, conn *jsonrpc2.Conn, r *jsonrpc2.Request) { switch r.Method { case "initialize": var params InitializeParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.Initialize(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "initialized": var params InitializedParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } - if err := server.Initialized(ctx, ¶ms); err != nil { - return nil, toJSONError(err) - } - return nil, nil + unhandledError(server.Initialized(ctx, ¶ms)) case "shutdown": if r.Params != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params") + conn.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params")) + return } - if err := server.Shutdown(ctx); err != nil { - return nil, toJSONError(err) - } - return nil, nil + unhandledError(server.Shutdown(ctx)) case "exit": if r.Params != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params") + conn.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params")) + return } - if err := server.Exit(ctx); err != nil { - return nil, toJSONError(err) - } - return nil, nil + unhandledError(server.Exit(ctx)) case "$/cancelRequest": var params CancelParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } conn.Cancel(params.ID) - return nil, nil case "workspace/didChangeWorkspaceFolders": var params DidChangeWorkspaceFoldersParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } - if err := server.DidChangeWorkspaceFolders(ctx, ¶ms); err != nil { - return nil, toJSONError(err) - } - return nil, nil + unhandledError(server.DidChangeWorkspaceFolders(ctx, ¶ms)) case "workspace/didChangeConfiguration": var params DidChangeConfigurationParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } - if err := server.DidChangeConfiguration(ctx, ¶ms); err != nil { - return nil, toJSONError(err) - } - return nil, nil + unhandledError(server.DidChangeConfiguration(ctx, ¶ms)) case "workspace/didChangeWatchedFiles": var params DidChangeWatchedFilesParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } - if err := server.DidChangeWatchedFiles(ctx, ¶ms); err != nil { - return nil, toJSONError(err) - } - return nil, nil + unhandledError(server.DidChangeWatchedFiles(ctx, ¶ms)) case "workspace/symbol": var params WorkspaceSymbolParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.Symbols(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "workspace/executeCommand": var params ExecuteCommandParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.ExecuteCommand(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/didOpen": var params DidOpenTextDocumentParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } - if err := server.DidOpen(ctx, ¶ms); err != nil { - return nil, toJSONError(err) - } - return nil, nil + unhandledError(server.DidOpen(ctx, ¶ms)) case "textDocument/didChange": var params DidChangeTextDocumentParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } - if err := server.DidChange(ctx, ¶ms); err != nil { - return nil, toJSONError(err) - } - return nil, nil + unhandledError(server.DidChange(ctx, ¶ms)) case "textDocument/willSave": var params WillSaveTextDocumentParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } - if err := server.WillSave(ctx, ¶ms); err != nil { - return nil, toJSONError(err) - } - return nil, nil + unhandledError(server.WillSave(ctx, ¶ms)) case "textDocument/willSaveWaitUntil": var params WillSaveTextDocumentParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.WillSaveWaitUntil(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/didSave": var params DidSaveTextDocumentParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } - if err := server.DidSave(ctx, ¶ms); err != nil { - return nil, toJSONError(err) - } - return nil, nil + unhandledError(server.DidSave(ctx, ¶ms)) case "textDocument/didClose": var params DidCloseTextDocumentParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } - if err := server.DidClose(ctx, ¶ms); err != nil { - return nil, toJSONError(err) - } - return nil, nil + unhandledError(server.DidClose(ctx, ¶ms)) case "textDocument/completion": var params CompletionParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.Completion(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "completionItem/resolve": var params CompletionItem if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.CompletionResolve(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/hover": var params TextDocumentPositionParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.Hover(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/signatureHelp": var params TextDocumentPositionParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.SignatureHelp(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/definition": var params TextDocumentPositionParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.Definition(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/typeDefinition": var params TextDocumentPositionParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.TypeDefinition(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/implementation": var params TextDocumentPositionParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.Implementation(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/references": var params ReferenceParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.References(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/documentHighlight": var params TextDocumentPositionParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.DocumentHighlight(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/documentSymbol": var params DocumentSymbolParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.DocumentSymbol(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/codeAction": var params CodeActionParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.CodeAction(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/codeLens": var params CodeLensParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.CodeLens(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "codeLens/resolve": var params CodeLens if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.CodeLensResolve(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/documentLink": var params DocumentLinkParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.DocumentLink(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "documentLink/resolve": var params DocumentLink if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.DocumentLinkResolve(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/documentColor": var params DocumentColorParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.DocumentColor(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/colorPresentation": var params ColorPresentationParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.ColorPresentation(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/formatting": var params DocumentFormattingParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.Formatting(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/rangeFormatting": var params DocumentRangeFormattingParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.RangeFormatting(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/onTypeFormatting": var params DocumentOnTypeFormattingParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.OnTypeFormatting(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/rename": var params RenameParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.Rename(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) case "textDocument/foldingRanges": var params FoldingRangeRequestParam if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err) + sendParseError(ctx, conn, r, err) + return } resp, err := server.FoldingRanges(ctx, ¶ms) - if err != nil { - return nil, toJSONError(err) - } - return resp, nil + unhandledError(conn.Reply(ctx, r, resp, err)) default: - return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not found", r.Method) + if r.IsNotify() { + conn.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not found", r.Method)) + } } } }