x/tools/gopls: add support for $/progress functionality

This CL adds support for sending progress notifications through $/progress
calls as well as being able to cancel them through window/workDoneProgress/cancel.
This feature is only supported in clients running LSP 3.15 and therefore the initialize
request will check for client capabilities for its progress support.

Updates golang/go#37680

Change-Id: Iff8c016694746a9dd553e5cc49444df7afcc21f5
Reviewed-on: https://go-review.googlesource.com/c/tools/+/222981
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
This commit is contained in:
Marwan Sulaiman 2020-03-12 16:31:00 -04:00 committed by Rebecca Stambler
parent bd88ce9755
commit 3e76bee198
9 changed files with 100 additions and 41 deletions

View File

@ -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 {

View File

@ -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) {

View File

@ -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) }()

View File

@ -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, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
if err := h.client.Progress(ctx, &params); 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, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
err := h.client.WorkDoneProgressCreate(ctx, &params)
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, &params); 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
}

View File

@ -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"`
}

View File

@ -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, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
if err := h.server.Progress(ctx, &params); err != nil {
event.Error(ctx, "", err)
}
return true
case "$/setTraceNotification": // notif
var params SetTraceParams
if err := json.Unmarshal(*r.Params, &params); 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, &params); err != nil {
sendParseError(ctx, r, err)
return true
}
err := h.server.WorkDoneProgressCreate(ctx, &params)
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, &params); 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 {

View File

@ -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}}`);

View File

@ -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)
}

View File

@ -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)
}