From 76b6a585b2c5d9bfb192f04782bb6f3d2a7ba465 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Wed, 4 May 2022 09:39:30 -0400 Subject: [PATCH 1/8] Subscribe to updates of module.calls data Adds support for a client to register a command that will refresh clide side data based on changes the server has detected in the workspace. This specifically adds a new command to refresh the module calls shown client side. The existing Module hook system is used to detect changes in the workspace and notify the client that data has changed. --- internal/langserver/handlers/initialize.go | 16 ++++++++++------ internal/langserver/handlers/service.go | 5 +++++ internal/protocol/experimental.go | 10 ++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/internal/langserver/handlers/initialize.go b/internal/langserver/handlers/initialize.go index c312cc070..853d6a922 100644 --- a/internal/langserver/handlers/initialize.go +++ b/internal/langserver/handlers/initialize.go @@ -46,18 +46,22 @@ func (svc *service) Initialize(ctx context.Context, params lsp.InitializeParams) } } + expServerCaps := lsp.ExperimentalServerCapabilities{} + if _, ok := expClientCaps.ShowReferencesCommandId(); ok { - serverCaps.Capabilities.Experimental = lsp.ExperimentalServerCapabilities{ - ReferenceCountCodeLens: true, - } + expServerCaps.ReferenceCountCodeLens = true properties["experimentalCapabilities.referenceCountCodeLens"] = true } if _, ok := expClientCaps.RefreshModuleProvidersCommandId(); ok { - serverCaps.Capabilities.Experimental = lsp.ExperimentalServerCapabilities{ - RefreshModuleProviders: true, - } + expServerCaps.RefreshModuleProviders = true properties["experimentalCapabilities.refreshModuleProviders"] = true } + if _, ok := expClientCaps.RefreshModuleProvidersCommandId(); ok { + expServerCaps.RefreshModuleCalls = true + properties["experimentalCapabilities.refreshModuleProviders"] = true + } + + serverCaps.Capabilities.Experimental = expServerCaps err = ilsp.SetClientCapabilities(ctx, &clientCaps) if err != nil { diff --git a/internal/langserver/handlers/service.go b/internal/langserver/handlers/service.go index 1faff807f..eb89245e8 100644 --- a/internal/langserver/handlers/service.go +++ b/internal/langserver/handlers/service.go @@ -447,6 +447,11 @@ func (svc *service) configureSessionDependencies(ctx context.Context, cfgOpts *s refreshModuleProviders(svc.sessCtx, svc.server, svc.logger, commandId)) } + if commandId, ok := lsp.ExperimentalClientCapabilities(cc.Experimental).RefreshModuleCallsCommandId(); ok { + svc.stateStore.Modules.ChangeHooks = append(svc.stateStore.Modules.ChangeHooks, + refreshModuleProviders(svc.sessCtx, svc.server, svc.logger, commandId)) + } + if cc.Workspace.SemanticTokens.RefreshSupport { svc.stateStore.Modules.ChangeHooks = append(svc.stateStore.Modules.ChangeHooks, refreshSemanticTokens(ctx, svc.srvCtx, svc.logger)) diff --git a/internal/protocol/experimental.go b/internal/protocol/experimental.go index 84fd06d06..865a96be2 100644 --- a/internal/protocol/experimental.go +++ b/internal/protocol/experimental.go @@ -3,6 +3,7 @@ package protocol type ExperimentalServerCapabilities struct { ReferenceCountCodeLens bool `json:"referenceCountCodeLens"` RefreshModuleProviders bool `json:"refreshModuleProviders"` + RefreshModuleCalls bool `json:"refreshModuleCalls"` } type ExpClientCapabilities map[string]interface{} @@ -32,6 +33,15 @@ func (cc ExpClientCapabilities) RefreshModuleProvidersCommandId() (string, bool) return cmdId, ok } +func (cc ExpClientCapabilities) RefreshModuleCallsCommandId() (string, bool) { + if cc == nil { + return "", false + } + + cmdId, ok := cc["refereshModuleCallsCommandId"].(string) + return cmdId, ok +} + func (cc ExpClientCapabilities) TelemetryVersion() (int, bool) { if cc == nil { return 0, false From 08c74e01f07f3de099223291555f1da4d50e571c Mon Sep 17 00:00:00 2001 From: James Pogran Date: Wed, 4 May 2022 10:06:59 -0400 Subject: [PATCH 2/8] fix test --- internal/langserver/handlers/handlers_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/langserver/handlers/handlers_test.go b/internal/langserver/handlers/handlers_test.go index 7eaca80f2..8675958e4 100644 --- a/internal/langserver/handlers/handlers_test.go +++ b/internal/langserver/handlers/handlers_test.go @@ -71,6 +71,11 @@ func initializeResponse(t *testing.T, commandPrefix string) string { "supported": true, "changeNotifications": "workspace/didChangeWorkspaceFolders" } + }, + "experimental": { + "referenceCountCodeLens": false, + "refreshModuleProviders": false, + "refreshModuleCalls": false } }, "serverInfo": { From 7e39b480b07f45aa11c74f820941d2bb2d5e04f7 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Wed, 4 May 2022 11:35:17 -0400 Subject: [PATCH 3/8] Apply suggestions from code review Co-authored-by: Radek Simko --- internal/langserver/handlers/initialize.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/langserver/handlers/initialize.go b/internal/langserver/handlers/initialize.go index 853d6a922..04eb90653 100644 --- a/internal/langserver/handlers/initialize.go +++ b/internal/langserver/handlers/initialize.go @@ -56,9 +56,9 @@ func (svc *service) Initialize(ctx context.Context, params lsp.InitializeParams) expServerCaps.RefreshModuleProviders = true properties["experimentalCapabilities.refreshModuleProviders"] = true } - if _, ok := expClientCaps.RefreshModuleProvidersCommandId(); ok { + if _, ok := expClientCaps.RefreshModuleCallsCommandId(); ok { expServerCaps.RefreshModuleCalls = true - properties["experimentalCapabilities.refreshModuleProviders"] = true + properties["experimentalCapabilities.refreshModuleCalls"] = true } serverCaps.Capabilities.Experimental = expServerCaps From ace516062b672d65521bbbb221281ac0121bcf9b Mon Sep 17 00:00:00 2001 From: James Pogran Date: Wed, 4 May 2022 11:36:59 -0400 Subject: [PATCH 4/8] rename refersh method to callClientCommand --- internal/langserver/handlers/hooks_module.go | 2 +- internal/langserver/handlers/service.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/langserver/handlers/hooks_module.go b/internal/langserver/handlers/hooks_module.go index 95b341b6e..d236ec531 100644 --- a/internal/langserver/handlers/hooks_module.go +++ b/internal/langserver/handlers/hooks_module.go @@ -153,7 +153,7 @@ func updateDiagnostics(ctx context.Context, notifier *diagnostics.Notifier) stat } } -func refreshModuleProviders(ctx context.Context, clientRequester session.ClientCaller, logger *log.Logger, commandId string) state.ModuleChangeHook { +func callClientCommand(ctx context.Context, clientRequester session.ClientCaller, logger *log.Logger, commandId string) state.ModuleChangeHook { return func(oldMod, newMod *state.Module) { _, err := clientRequester.Callback(ctx, commandId, nil) if err != nil { diff --git a/internal/langserver/handlers/service.go b/internal/langserver/handlers/service.go index eb89245e8..770f02cd1 100644 --- a/internal/langserver/handlers/service.go +++ b/internal/langserver/handlers/service.go @@ -444,12 +444,12 @@ func (svc *service) configureSessionDependencies(ctx context.Context, cfgOpts *s if commandId, ok := lsp.ExperimentalClientCapabilities(cc.Experimental).RefreshModuleProvidersCommandId(); ok { svc.stateStore.Modules.ChangeHooks = append(svc.stateStore.Modules.ChangeHooks, - refreshModuleProviders(svc.sessCtx, svc.server, svc.logger, commandId)) + callClientCommand(svc.sessCtx, svc.server, svc.logger, commandId)) } if commandId, ok := lsp.ExperimentalClientCapabilities(cc.Experimental).RefreshModuleCallsCommandId(); ok { svc.stateStore.Modules.ChangeHooks = append(svc.stateStore.Modules.ChangeHooks, - refreshModuleProviders(svc.sessCtx, svc.server, svc.logger, commandId)) + callClientCommand(svc.sessCtx, svc.server, svc.logger, commandId)) } if cc.Workspace.SemanticTokens.RefreshSupport { From a22615cc827d41e37965e412a50d33a88d4aadf0 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Thu, 5 May 2022 09:47:55 -0400 Subject: [PATCH 5/8] more renames --- internal/langserver/handlers/hooks_module.go | 9 ++++++++- internal/protocol/experimental.go | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/internal/langserver/handlers/hooks_module.go b/internal/langserver/handlers/hooks_module.go index d236ec531..be4cda5de 100644 --- a/internal/langserver/handlers/hooks_module.go +++ b/internal/langserver/handlers/hooks_module.go @@ -155,9 +155,16 @@ func updateDiagnostics(ctx context.Context, notifier *diagnostics.Notifier) stat func callClientCommand(ctx context.Context, clientRequester session.ClientCaller, logger *log.Logger, commandId string) state.ModuleChangeHook { return func(oldMod, newMod *state.Module) { + if oldMod == nil { + return + } + if newMod == nil { + return + } + _, err := clientRequester.Callback(ctx, commandId, nil) if err != nil { - logger.Printf("Error refreshing %s: %s", newMod.Path, err) + logger.Printf("Error calling %s for %s: %s", commandId, newMod.Path, err) } } } diff --git a/internal/protocol/experimental.go b/internal/protocol/experimental.go index 865a96be2..07cd12a60 100644 --- a/internal/protocol/experimental.go +++ b/internal/protocol/experimental.go @@ -29,7 +29,7 @@ func (cc ExpClientCapabilities) RefreshModuleProvidersCommandId() (string, bool) return "", false } - cmdId, ok := cc["refereshModuleProvidersCommandId"].(string) + cmdId, ok := cc["refreshModuleProvidersCommandId"].(string) return cmdId, ok } @@ -38,7 +38,7 @@ func (cc ExpClientCapabilities) RefreshModuleCallsCommandId() (string, bool) { return "", false } - cmdId, ok := cc["refereshModuleCallsCommandId"].(string) + cmdId, ok := cc["refreshModuleCallsCommandId"].(string) return cmdId, ok } From 2bca1cfe7ec48e8243667755d4f29b23b6b61f40 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Thu, 5 May 2022 09:54:22 -0400 Subject: [PATCH 6/8] defend against nil --- internal/langserver/handlers/hooks_module.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/langserver/handlers/hooks_module.go b/internal/langserver/handlers/hooks_module.go index be4cda5de..148384422 100644 --- a/internal/langserver/handlers/hooks_module.go +++ b/internal/langserver/handlers/hooks_module.go @@ -155,16 +155,16 @@ func updateDiagnostics(ctx context.Context, notifier *diagnostics.Notifier) stat func callClientCommand(ctx context.Context, clientRequester session.ClientCaller, logger *log.Logger, commandId string) state.ModuleChangeHook { return func(oldMod, newMod *state.Module) { - if oldMod == nil { - return - } - if newMod == nil { - return + var modPath string + if oldMod != nil { + modPath = oldMod.Path + } else { + modPath = newMod.Path } _, err := clientRequester.Callback(ctx, commandId, nil) if err != nil { - logger.Printf("Error calling %s for %s: %s", commandId, newMod.Path, err) + logger.Printf("Error calling %s for %s: %s", commandId, modPath, err) } } } From 8f19acf80269692c6d5a688e41bbfc8edf7e2ac8 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Thu, 5 May 2022 10:00:12 -0400 Subject: [PATCH 7/8] some documentation about wiring up new client side commands --- docs/commands.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/docs/commands.md b/docs/commands.md index 867200bb4..a4f608085 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -184,3 +184,53 @@ installed version. } } ``` + +# Client Side Commands + +## How to add an Experimental Capability + +Add a new entry to the ExperimentalServerCapabilities struct in `internal/protocol/expertimental.go:6`: + +```go +type ExperimentalServerCapabilities struct { + ReferenceCountCodeLens bool `json:"referenceCountCodeLens"` + RefreshModuleProviders bool `json:"refreshModuleProviders"` + RefreshModuleCalls bool `json:"refreshModuleCalls"` +} +``` + +> Note the casing in the mapstructure field compared to the field name. + +Add a new method to retrieve the client side command id in `internal/protocol/expertimental.go`: + +```go +func (cc ExpClientCapabilities) NewItemHereCommandId() (string, bool) { + if cc == nil { + return "", false + } + + cmdId, ok := cc["newItemHereCommandId"].(string) + return cmdId, ok +} +``` + +> Note that the command ID matches the experimental capabilities struct in expertimental.go` + +Add a new stanza to `internal/langServer/handlers/initialize.go:63` to pull the command ID and register the capability: + +```go +if _, ok := expClientCaps.NewItemHereCommandId(); ok { + expServerCaps.NewItemHere = true + properties["experimentalCapabilities.newItemHere"] = true +} +``` + +> Note the casing in the proprties hash. + +Finally, register the command handler in `internal/langServer/handlers/service.go:454`: + +```go +if commandId, ok := lsp.ExperimentalClientCapabilities(cc.Experimental).NewItemHereCommandId(); ok { + // do something with commandId here +} +``` From 87179d77f82aabe001fafd9155227b59d42b030c Mon Sep 17 00:00:00 2001 From: James Pogran Date: Mon, 9 May 2022 11:32:32 -0400 Subject: [PATCH 8/8] movee documentation about wiring up new client side commands to stand alone document --- docs/commands.md | 50 ----------------------------------------- docs/language-server.md | 49 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 50 deletions(-) create mode 100644 docs/language-server.md diff --git a/docs/commands.md b/docs/commands.md index a4f608085..867200bb4 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -184,53 +184,3 @@ installed version. } } ``` - -# Client Side Commands - -## How to add an Experimental Capability - -Add a new entry to the ExperimentalServerCapabilities struct in `internal/protocol/expertimental.go:6`: - -```go -type ExperimentalServerCapabilities struct { - ReferenceCountCodeLens bool `json:"referenceCountCodeLens"` - RefreshModuleProviders bool `json:"refreshModuleProviders"` - RefreshModuleCalls bool `json:"refreshModuleCalls"` -} -``` - -> Note the casing in the mapstructure field compared to the field name. - -Add a new method to retrieve the client side command id in `internal/protocol/expertimental.go`: - -```go -func (cc ExpClientCapabilities) NewItemHereCommandId() (string, bool) { - if cc == nil { - return "", false - } - - cmdId, ok := cc["newItemHereCommandId"].(string) - return cmdId, ok -} -``` - -> Note that the command ID matches the experimental capabilities struct in expertimental.go` - -Add a new stanza to `internal/langServer/handlers/initialize.go:63` to pull the command ID and register the capability: - -```go -if _, ok := expClientCaps.NewItemHereCommandId(); ok { - expServerCaps.NewItemHere = true - properties["experimentalCapabilities.newItemHere"] = true -} -``` - -> Note the casing in the proprties hash. - -Finally, register the command handler in `internal/langServer/handlers/service.go:454`: - -```go -if commandId, ok := lsp.ExperimentalClientCapabilities(cc.Experimental).NewItemHereCommandId(); ok { - // do something with commandId here -} -``` diff --git a/docs/language-server.md b/docs/language-server.md new file mode 100644 index 000000000..b75cb7313 --- /dev/null +++ b/docs/language-server.md @@ -0,0 +1,49 @@ +# Language Server Implementation Notes + +## How to add an Experimental Capability + +Add a new entry to the ExperimentalServerCapabilities struct in `internal/protocol/expertimental.go:6`: + +```go +type ExperimentalServerCapabilities struct { + ReferenceCountCodeLens bool `json:"referenceCountCodeLens"` + RefreshModuleProviders bool `json:"refreshModuleProviders"` + RefreshModuleCalls bool `json:"refreshModuleCalls"` +} +``` + +> Note the casing in the mapstructure field compared to the field name. + +Add a new method to retrieve the client side command id in `internal/protocol/expertimental.go`: + +```go +func (cc ExpClientCapabilities) NewItemHereCommandId() (string, bool) { + if cc == nil { + return "", false + } + + cmdId, ok := cc["newItemHereCommandId"].(string) + return cmdId, ok +} +``` + +> Note that the command ID matches the experimental capabilities struct in expertimental.go` + +Add a new stanza to `internal/langServer/handlers/initialize.go:63` to pull the command ID and register the capability: + +```go +if _, ok := expClientCaps.NewItemHereCommandId(); ok { + expServerCaps.NewItemHere = true + properties["experimentalCapabilities.newItemHere"] = true +} +``` + +> Note the casing in the proprties hash. + +Finally, register the command handler in `internal/langServer/handlers/service.go:454`: + +```go +if commandId, ok := lsp.ExperimentalClientCapabilities(cc.Experimental).NewItemHereCommandId(); ok { + // do something with commandId here +} +```