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 +} +``` 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": { diff --git a/internal/langserver/handlers/hooks_module.go b/internal/langserver/handlers/hooks_module.go index 95b341b6e..148384422 100644 --- a/internal/langserver/handlers/hooks_module.go +++ b/internal/langserver/handlers/hooks_module.go @@ -153,11 +153,18 @@ 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) { + 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 refreshing %s: %s", newMod.Path, err) + logger.Printf("Error calling %s for %s: %s", commandId, modPath, err) } } } diff --git a/internal/langserver/handlers/initialize.go b/internal/langserver/handlers/initialize.go index c312cc070..04eb90653 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.RefreshModuleCallsCommandId(); ok { + expServerCaps.RefreshModuleCalls = true + properties["experimentalCapabilities.refreshModuleCalls"] = 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..770f02cd1 100644 --- a/internal/langserver/handlers/service.go +++ b/internal/langserver/handlers/service.go @@ -444,7 +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, + callClientCommand(svc.sessCtx, svc.server, svc.logger, commandId)) } if cc.Workspace.SemanticTokens.RefreshSupport { diff --git a/internal/protocol/experimental.go b/internal/protocol/experimental.go index 84fd06d06..07cd12a60 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{} @@ -28,7 +29,16 @@ func (cc ExpClientCapabilities) RefreshModuleProvidersCommandId() (string, bool) return "", false } - cmdId, ok := cc["refereshModuleProvidersCommandId"].(string) + cmdId, ok := cc["refreshModuleProvidersCommandId"].(string) + return cmdId, ok +} + +func (cc ExpClientCapabilities) RefreshModuleCallsCommandId() (string, bool) { + if cc == nil { + return "", false + } + + cmdId, ok := cc["refreshModuleCallsCommandId"].(string) return cmdId, ok }