diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go index 1537c85652..09519e1e3a 100644 --- a/internal/lsp/cmd/cmd.go +++ b/internal/lsp/cmd/cmd.go @@ -387,6 +387,14 @@ func (c *cmdClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishD return nil } +func (c *cmdClient) Progress(context.Context, *protocol.ProgressParams) error { + return nil +} + +func (c *cmdClient) WorkDoneProgressCreate(context.Context, *protocol.WorkDoneProgressCreateParams) error { + return nil +} + func (c *cmdClient) getFile(ctx context.Context, uri span.URI) *cmdFile { file, found := c.files[uri] if !found || file.err != nil { diff --git a/internal/lsp/fake/client.go b/internal/lsp/fake/client.go index 81336e3a2d..394718b98c 100644 --- a/internal/lsp/fake/client.go +++ b/internal/lsp/fake/client.go @@ -90,6 +90,14 @@ func (c *Client) UnregisterCapability(context.Context, *protocol.UnregistrationP return nil } +func (c *Client) Progress(context.Context, *protocol.ProgressParams) error { + return nil +} + +func (c *Client) WorkDoneProgressCreate(context.Context, *protocol.WorkDoneProgressCreateParams) error { + return nil +} + // ApplyEdit applies edits sent from the server. Note that as of writing gopls // doesn't use this feature, so it is untested. func (c *Client) ApplyEdit(ctx context.Context, params *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResponse, error) { diff --git a/internal/lsp/general.go b/internal/lsp/general.go index f2b735dcdc..c8d40d2d81 100644 --- a/internal/lsp/general.go +++ b/internal/lsp/general.go @@ -29,6 +29,9 @@ func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitializ s.state = serverInitializing s.stateMu.Unlock() + s.supportsWorkDoneProgress = params.Capabilities.Window.WorkDoneProgress + s.inProgress = map[string]func(){} + options := s.session.Options() defer func() { s.session.SetOptions(options) }() diff --git a/internal/lsp/protocol/tsclient.go b/internal/lsp/protocol/tsclient.go index 149e48eea5..cb5a01d124 100644 --- a/internal/lsp/protocol/tsclient.go +++ b/internal/lsp/protocol/tsclient.go @@ -21,8 +21,10 @@ type Client interface { LogMessage(context.Context, *LogMessageParams) error Event(context.Context, *interface{}) error PublishDiagnostics(context.Context, *PublishDiagnosticsParams) error + Progress(context.Context, *ProgressParams) error WorkspaceFolders(context.Context) ([]WorkspaceFolder /*WorkspaceFolder[] | null*/, error) Configuration(context.Context, *ParamConfiguration) ([]interface{}, error) + WorkDoneProgressCreate(context.Context, *WorkDoneProgressCreateParams) error RegisterCapability(context.Context, *RegistrationParams) error UnregisterCapability(context.Context, *UnregistrationParams) error ShowMessageRequest(context.Context, *ShowMessageRequestParams) (*MessageActionItem /*MessageActionItem | null*/, error) @@ -79,6 +81,16 @@ func (h clientHandler) Deliver(ctx context.Context, r *jsonrpc2.Request, deliver event.Error(ctx, "", err) } return true + case "$/progress": // notif + var params ProgressParams + if err := json.Unmarshal(*r.Params, ¶ms); err != nil { + sendParseError(ctx, r, err) + return true + } + if err := h.client.Progress(ctx, ¶ms); err != nil { + event.Error(ctx, "", err) + } + return true case "workspace/workspaceFolders": // req if r.Params != nil { r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params")) @@ -100,6 +112,17 @@ func (h clientHandler) Deliver(ctx context.Context, r *jsonrpc2.Request, deliver event.Error(ctx, "", err) } return true + case "window/workDoneProgress/create": // req + var params WorkDoneProgressCreateParams + if err := json.Unmarshal(*r.Params, ¶ms); err != nil { + sendParseError(ctx, r, err) + return true + } + err := h.client.WorkDoneProgressCreate(ctx, ¶ms) + if err := r.Reply(ctx, nil, err); err != nil { + event.Error(ctx, "", err) + } + return true case "client/registerCapability": // req var params RegistrationParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { @@ -169,6 +192,10 @@ func (s *clientDispatcher) Event(ctx context.Context, params *interface{}) error func (s *clientDispatcher) PublishDiagnostics(ctx context.Context, params *PublishDiagnosticsParams) error { return s.Conn.Notify(ctx, "textDocument/publishDiagnostics", params) } + +func (s *clientDispatcher) Progress(ctx context.Context, params *ProgressParams) error { + return s.Conn.Notify(ctx, "$/progress", params) +} func (s *clientDispatcher) WorkspaceFolders(ctx context.Context) ([]WorkspaceFolder /*WorkspaceFolder[] | null*/, error) { var result []WorkspaceFolder /*WorkspaceFolder[] | null*/ if err := s.Conn.Call(ctx, "workspace/workspaceFolders", nil, &result); err != nil { @@ -185,6 +212,10 @@ func (s *clientDispatcher) Configuration(ctx context.Context, params *ParamConfi return result, nil } +func (s *clientDispatcher) WorkDoneProgressCreate(ctx context.Context, params *WorkDoneProgressCreateParams) error { + return s.Conn.Call(ctx, "window/workDoneProgress/create", params, nil) // Call, not Notify +} + func (s *clientDispatcher) RegisterCapability(ctx context.Context, params *RegistrationParams) error { return s.Conn.Call(ctx, "client/registerCapability", params, nil) // Call, not Notify } diff --git a/internal/lsp/protocol/tsprotocol.go b/internal/lsp/protocol/tsprotocol.go index 7436a7d338..139ad988c9 100644 --- a/internal/lsp/protocol/tsprotocol.go +++ b/internal/lsp/protocol/tsprotocol.go @@ -4060,3 +4060,27 @@ type WorkspaceFoldersGn struct { */ ChangeNotifications string/*string | boolean*/ `json:"changeNotifications,omitempty"` } + +// The following types are defined by +// the protocol but are not yet auto generated +// TODO: generate progress types from here: https://github.com/microsoft/vscode-languageserver-node/blob/master/protocol/src/protocol.progress.ts + +type WorkDoneProgressBegin struct { + Kind string `json:"kind,omitempty"` + Title string `json:"title,omitempty"` + Cancellable bool `json:"cancellable,omitempty"` + Message string `json:"message,omitempty"` + Percentage int `json:"percentage,omitempty"` +} + +type WorkDoneProgressReport struct { + Kind string `json:"kind,omitempty"` + Cancellable bool `json:"cancellable,omitempty"` + Message string `json:"message,omitempty"` + Percentage int `json:"percentage,omitempty"` +} + +type WorkDoneProgressEnd struct { + Kind string `json:"kind,omitempty"` + Message string `json:"message,omitempty"` +} diff --git a/internal/lsp/protocol/tsserver.go b/internal/lsp/protocol/tsserver.go index 54b395e6a6..e44d558210 100644 --- a/internal/lsp/protocol/tsserver.go +++ b/internal/lsp/protocol/tsserver.go @@ -28,7 +28,6 @@ type Server interface { DidSave(context.Context, *DidSaveTextDocumentParams) error WillSave(context.Context, *WillSaveTextDocumentParams) error DidChangeWatchedFiles(context.Context, *DidChangeWatchedFilesParams) error - Progress(context.Context, *ProgressParams) error SetTraceNotification(context.Context, *SetTraceParams) error LogTraceNotification(context.Context, *LogTraceParams) error Implementation(context.Context, *ImplementationParams) (Definition /*Definition | DefinitionLink[] | null*/, error) @@ -38,7 +37,6 @@ type Server interface { FoldingRange(context.Context, *FoldingRangeParams) ([]FoldingRange /*FoldingRange[] | null*/, error) Declaration(context.Context, *DeclarationParams) (Declaration /*Declaration | DeclarationLink[] | null*/, error) SelectionRange(context.Context, *SelectionRangeParams) ([]SelectionRange /*SelectionRange[] | null*/, error) - WorkDoneProgressCreate(context.Context, *WorkDoneProgressCreateParams) error Initialize(context.Context, *ParamInitialize) (*InitializeResult, error) Shutdown(context.Context) error WillSaveWaitUntil(context.Context, *WillSaveTextDocumentParams) ([]TextEdit /*TextEdit[] | null*/, error) @@ -186,16 +184,6 @@ func (h serverHandler) Deliver(ctx context.Context, r *jsonrpc2.Request, deliver event.Error(ctx, "", err) } return true - case "$/progress": // notif - var params ProgressParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - if err := h.server.Progress(ctx, ¶ms); err != nil { - event.Error(ctx, "", err) - } - return true case "$/setTraceNotification": // notif var params SetTraceParams if err := json.Unmarshal(*r.Params, ¶ms); err != nil { @@ -293,17 +281,6 @@ func (h serverHandler) Deliver(ctx context.Context, r *jsonrpc2.Request, deliver event.Error(ctx, "", err) } return true - case "window/workDoneProgress/create": // req - var params WorkDoneProgressCreateParams - if err := json.Unmarshal(*r.Params, ¶ms); err != nil { - sendParseError(ctx, r, err) - return true - } - err := h.server.WorkDoneProgressCreate(ctx, ¶ms) - if err := r.Reply(ctx, nil, err); err != nil { - event.Error(ctx, "", err) - } - return true case "initialize": // req var params ParamInitialize if err := json.Unmarshal(*r.Params, ¶ms); err != nil { @@ -685,10 +662,6 @@ func (s *serverDispatcher) DidChangeWatchedFiles(ctx context.Context, params *Di return s.Conn.Notify(ctx, "workspace/didChangeWatchedFiles", params) } -func (s *serverDispatcher) Progress(ctx context.Context, params *ProgressParams) error { - return s.Conn.Notify(ctx, "$/progress", params) -} - func (s *serverDispatcher) SetTraceNotification(ctx context.Context, params *SetTraceParams) error { return s.Conn.Notify(ctx, "$/setTraceNotification", params) } @@ -752,10 +725,6 @@ func (s *serverDispatcher) SelectionRange(ctx context.Context, params *Selection return result, nil } -func (s *serverDispatcher) WorkDoneProgressCreate(ctx context.Context, params *WorkDoneProgressCreateParams) error { - return s.Conn.Call(ctx, "window/workDoneProgress/create", params, nil) // Call, not Notify -} - func (s *serverDispatcher) Initialize(ctx context.Context, params *ParamInitialize) (*InitializeResult, error) { var result *InitializeResult if err := s.Conn.Call(ctx, "initialize", params, &result); err != nil { diff --git a/internal/lsp/protocol/typescript/code.ts b/internal/lsp/protocol/typescript/code.ts index 0f6b2b4080..4caa9f68ab 100644 --- a/internal/lsp/protocol/typescript/code.ts +++ b/internal/lsp/protocol/typescript/code.ts @@ -142,6 +142,8 @@ function setReceives() { receives.set('workspace/configuration', 'client'); receives.set('workspace/applyEdit', 'client'); receives.set('textDocument/publishDiagnostics', 'client'); + receives.set('window/workDoneProgress/create', 'client'); + receives.set('$/progress', 'client'); // a small check receives.forEach((_, k) => { if (!req.get(k) && !not.get(k)) throw new Error(`missing ${k}}`); diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 010e313c1b..b8e997bb7b 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -15,6 +15,7 @@ import ( "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/span" + errors "golang.org/x/xerrors" ) const concurrentAnalyses = 1 @@ -78,6 +79,12 @@ type Server struct { // diagnosticsSema limits the concurrency of diagnostics runs, which can be expensive. diagnosticsSema chan struct{} + + // supportsWorkDoneProgress is set in the initializeRequest + // to determine if the client can support progress notifications + supportsWorkDoneProgress bool + inProgressMu sync.Mutex + inProgress map[string]func() } // sentDiagnostics is used to cache diagnostics that have been sent for a given file. @@ -132,6 +139,21 @@ func (s *Server) nonstandardRequest(ctx context.Context, method string, params i return nil, notImplemented(method) } +func (s *Server) workDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error { + token, ok := params.Token.(string) + if !ok { + return errors.Errorf("expected params.Token to be string but got %T", params.Token) + } + s.inProgressMu.Lock() + defer s.inProgressMu.Unlock() + cancel, ok := s.inProgress[token] + if !ok { + return errors.Errorf("token %q not found in progress", token) + } + cancel() + return nil +} + func notImplemented(method string) *jsonrpc2.Error { return jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not yet implemented", method) } diff --git a/internal/lsp/server_gen.go b/internal/lsp/server_gen.go index e02467e180..ad971f4f80 100644 --- a/internal/lsp/server_gen.go +++ b/internal/lsp/server_gen.go @@ -136,10 +136,6 @@ func (s *Server) PrepareRename(ctx context.Context, params *protocol.PrepareRena return s.prepareRename(ctx, params) } -func (s *Server) Progress(context.Context, *protocol.ProgressParams) error { - return notImplemented("Progress") -} - func (s *Server) RangeFormatting(context.Context, *protocol.DocumentRangeFormattingParams) ([]protocol.TextEdit, error) { return nil, notImplemented("RangeFormatting") } @@ -208,10 +204,6 @@ func (s *Server) WillSaveWaitUntil(context.Context, *protocol.WillSaveTextDocume return nil, notImplemented("WillSaveWaitUntil") } -func (s *Server) WorkDoneProgressCancel(context.Context, *protocol.WorkDoneProgressCancelParams) error { - return notImplemented("WorkDoneProgressCancel") -} - -func (s *Server) WorkDoneProgressCreate(context.Context, *protocol.WorkDoneProgressCreateParams) error { - return notImplemented("WorkDoneProgressCreate") +func (s *Server) WorkDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error { + return s.workDoneProgressCancel(ctx, params) }