Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unique command names #279

Merged
merged 10 commits into from
Oct 30, 2020
23 changes: 23 additions & 0 deletions internal/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var (
ctxRootModuleLoader = &contextKey{"root module loader"}
ctxRootDir = &contextKey{"root directory"}
ctxDiags = &contextKey{"diagnostics"}
ctxServerID = &contextKey{"server id"}
appilon marked this conversation as resolved.
Show resolved Hide resolved
)

func missingContextErr(ctxKey *contextKey) *MissingContextErr {
Expand Down Expand Up @@ -190,6 +191,28 @@ func RootDirectory(ctx context.Context) (string, bool) {
return *rootDir, true
}

func WithServerID(ctx context.Context, id *string) context.Context {
return context.WithValue(ctx, ctxServerID, id)
}

func SetServerID(ctx context.Context, id string) error {
serverID, ok := ctx.Value(ctxServerID).(*string)
if !ok {
return missingContextErr(ctxServerID)
}

*serverID = id
return nil
}

func ServerID(ctx context.Context) (string, bool) {
serverID, ok := ctx.Value(ctxServerID).(*string)
if !ok {
return "", false
}
return *serverID, true
}

func WithRootModuleWalker(ctx context.Context, w *rootmodule.Walker) context.Context {
return context.WithValue(ctx, ctxRootModuleWalker, w)
}
Expand Down
2 changes: 2 additions & 0 deletions internal/settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package settings

import (
"fmt"

"github.com/mitchellh/mapstructure"
)

type Options struct {
// RootModulePaths describes a list of absolute paths to root modules
RootModulePaths []string `mapstructure:"rootModulePaths"`
ExcludeModulePaths []string `mapstructure:"excludeModulePaths"`
ID string `mapstructure:"id"`
appilon marked this conversation as resolved.
Show resolved Hide resolved

// TODO: Need to check for conflict with CLI flags
// TerraformExecPath string
Expand Down
19 changes: 14 additions & 5 deletions langserver/handlers/execute_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,35 @@ import (
"strings"

"github.com/creachadair/jrpc2/code"
lsctx "github.com/hashicorp/terraform-ls/internal/context"
lsp "github.com/sourcegraph/go-lsp"
)

const commandPrefix = "terraform-ls."

type executeCommandHandler func(context.Context, commandArgs) (interface{}, error)
type executeCommandHandlers map[string]executeCommandHandler

var handlers = executeCommandHandlers{
"rootmodules": executeCommandRootModulesHandler,
}

func (h executeCommandHandlers) Names() []string {
var names []string
func (h executeCommandHandlers) Names(suffix string) []string {
if suffix != "" && !strings.HasPrefix(suffix, ".") {
suffix = "." + suffix
}
var fullnames []string
for name := range h {
names = append(names, name)
fullnames = append(fullnames, commandPrefix+name+suffix)
}
return names
return fullnames
}

func (lh *logHandler) WorkspaceExecuteCommand(ctx context.Context, params lsp.ExecuteCommandParams) (interface{}, error) {
handler, ok := handlers[params.Command]
serverID, _ := lsctx.ServerID(ctx)
name := strings.TrimPrefix(params.Command, commandPrefix)
name = strings.TrimSuffix(name, "."+serverID)
handler, ok := handlers[name]
appilon marked this conversation as resolved.
Show resolved Hide resolved
if !ok {
return nil, fmt.Errorf("%w: command handler not found for %q", code.MethodNotFound.Err(), params.Command)
}
Expand Down
25 changes: 17 additions & 8 deletions langserver/handlers/execute_command_rootmodules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ func TestLangServer_workspaceExecuteCommand_rootmodules_argumentError(t *testing
ReqParams: fmt.Sprintf(`{
"capabilities": {},
"rootUri": %q,
"processId": 12345
"processId": 12345,
"initializationOptions": {
"id": "1"
}
}`, tmpDir.URI())})
ls.Notify(t, &langserver.CallRequest{
Method: "initialized",
Expand All @@ -50,9 +53,9 @@ func TestLangServer_workspaceExecuteCommand_rootmodules_argumentError(t *testing

ls.CallAndExpectError(t, &langserver.CallRequest{
Method: "workspace/executeCommand",
ReqParams: `{
"command": "rootmodules"
}`}, code.InvalidParams.Err())
ReqParams: fmt.Sprintf(`{
"command": "terraform-ls.rootmodules.1"
}`)}, code.InvalidParams.Err())
}

func TestLangServer_workspaceExecuteCommand_rootmodules_basic(t *testing.T) {
Expand All @@ -75,7 +78,10 @@ func TestLangServer_workspaceExecuteCommand_rootmodules_basic(t *testing.T) {
ReqParams: fmt.Sprintf(`{
"capabilities": {},
"rootUri": %q,
"processId": 12345
"processId": 12345,
"initializationOptions": {
"id": "1"
}
}`, tmpDir.URI())})
ls.Notify(t, &langserver.CallRequest{
Method: "initialized",
Expand All @@ -95,7 +101,7 @@ func TestLangServer_workspaceExecuteCommand_rootmodules_basic(t *testing.T) {
ls.CallAndExpectResponse(t, &langserver.CallRequest{
Method: "workspace/executeCommand",
ReqParams: fmt.Sprintf(`{
"command": "rootmodules",
"command": "terraform-ls.rootmodules.1",
"arguments": ["uri=%s"]
}`, testFileURI)}, fmt.Sprintf(`{
"jsonrpc": "2.0",
Expand Down Expand Up @@ -146,7 +152,10 @@ func TestLangServer_workspaceExecuteCommand_rootmodules_multiple(t *testing.T) {
ReqParams: fmt.Sprintf(`{
"capabilities": {},
"rootUri": %q,
"processId": 12345
"processId": 12345,
"initializationOptions": {
"id": "1"
}
}`, root.URI())})
ls.Notify(t, &langserver.CallRequest{
Method: "initialized",
Expand All @@ -158,7 +167,7 @@ func TestLangServer_workspaceExecuteCommand_rootmodules_multiple(t *testing.T) {
ls.CallAndExpectResponse(t, &langserver.CallRequest{
Method: "workspace/executeCommand",
ReqParams: fmt.Sprintf(`{
"command": "rootmodules",
"command": "terraform-ls.rootmodules.1",
"arguments": ["uri=%s"]
}`, module.URI())}, fmt.Sprintf(`{
"jsonrpc": "2.0",
Expand Down
60 changes: 39 additions & 21 deletions langserver/handlers/handlers_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package handlers

import (
"encoding/json"
"fmt"
"os"
"path/filepath"
Expand All @@ -16,29 +17,38 @@ import (
"github.com/stretchr/testify/mock"
)

const initializeResponse = `{
"jsonrpc": "2.0",
"id": 1,
"result": {
"capabilities": {
"textDocumentSync": {
"openClose": true,
"change": 2
},
"completionProvider": {},
"documentSymbolProvider":true,
"documentFormattingProvider":true,
"executeCommandProvider": {
"commands": ["rootmodules"]
func initializeResponse(t *testing.T, commandSuffix string) string {
appilon marked this conversation as resolved.
Show resolved Hide resolved
jsonArray, err := json.Marshal(handlers.Names("." + commandSuffix))
if err != nil {
t.Fatal(err)
}

return fmt.Sprintf(`{
"jsonrpc": "2.0",
"id": 1,
"result": {
"capabilities": {
"textDocumentSync": {
"openClose": true,
"change": 2
},
"completionProvider": {},
"documentSymbolProvider":true,
"documentFormattingProvider":true,
"executeCommandProvider": {
"commands": %s
}
}
}
}
}`
}`, string(jsonArray))
}

func TestInitalizeAndShutdown(t *testing.T) {
tmpDir := TempDir(t)

ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{
RootModules: map[string]*rootmodule.RootModuleMock{
TempDir(t).Dir(): {TfExecFactory: validTfMockCalls()},
tmpDir.Dir(): {TfExecFactory: validTfMockCalls()},
}}))
stop := ls.Start(t)
defer stop()
Expand All @@ -48,8 +58,11 @@ func TestInitalizeAndShutdown(t *testing.T) {
ReqParams: fmt.Sprintf(`{
"capabilities": {},
"rootUri": %q,
"processId": 12345
}`, TempDir(t).URI())}, initializeResponse)
"processId": 12345,
"initializationOptions": {
"id": "1"
}
}`, tmpDir.URI())}, initializeResponse(t, "1"))
ls.CallAndExpectResponse(t, &langserver.CallRequest{
Method: "shutdown", ReqParams: `{}`},
`{
Expand All @@ -60,6 +73,8 @@ func TestInitalizeAndShutdown(t *testing.T) {
}

func TestEOF(t *testing.T) {
tmpDir := TempDir(t)

ms := newMockSession(&MockSessionInput{
RootModules: map[string]*rootmodule.RootModuleMock{
TempDir(t).Dir(): {TfExecFactory: validTfMockCalls()},
Expand All @@ -73,8 +88,11 @@ func TestEOF(t *testing.T) {
ReqParams: fmt.Sprintf(`{
"capabilities": {},
"rootUri": %q,
"processId": 12345
}`, TempDir(t).URI())}, initializeResponse)
"processId": 12345,
"initializationOptions": {
"id": "1"
}
}`, tmpDir.URI())}, initializeResponse(t, "1"))

ls.CloseClientStdout(t)

Expand Down
13 changes: 10 additions & 3 deletions langserver/handlers/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParam
},
DocumentFormattingProvider: true,
DocumentSymbolProvider: true,
ExecuteCommandProvider: &lsp.ExecuteCommandOptions{
Commands: handlers.Names(),
},
},
}

Expand Down Expand Up @@ -77,6 +74,16 @@ func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParam
if err != nil {
return serverCaps, err
}

// set server ID
err = lsctx.SetServerID(ctx, out.Options.ID)
if err != nil {
return serverCaps, err
}
// apply suffix to executeCommand handler names
serverCaps.Capabilities.ExecuteCommandProvider = &lsp.ExecuteCommandOptions{
Commands: handlers.Names(out.Options.ID),
}
appilon marked this conversation as resolved.
Show resolved Hide resolved
if len(out.UnusedKeys) > 0 {
jrpc2.PushNotify(ctx, "window/showMessage", &lsp.ShowMessageParams{
Type: lsp.MTWarning,
Expand Down
4 changes: 3 additions & 1 deletion langserver/handlers/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
diags := diagnostics.NewNotifier(svc.sessCtx, svc.logger)

rootDir := ""
serverID := ""

m := map[string]rpch.Func{
"initialize": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) {
Expand All @@ -157,6 +158,7 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
ctx = lsctx.WithWatcher(ctx, ww)
ctx = lsctx.WithRootModuleWalker(ctx, svc.walker)
ctx = lsctx.WithRootDirectory(ctx, &rootDir)
ctx = lsctx.WithServerID(ctx, &serverID)
ctx = lsctx.WithRootModuleManager(ctx, svc.modMgr)
ctx = lsctx.WithRootModuleLoader(ctx, rmLoader)

Expand Down Expand Up @@ -240,7 +242,7 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
return nil, err
}

ctx = lsctx.WithRootDirectory(ctx, &rootDir)
ctx = lsctx.WithServerID(ctx, &serverID)
ctx = lsctx.WithRootModuleCandidateFinder(ctx, svc.modMgr)
ctx = lsctx.WithRootModuleWalker(ctx, svc.walker)

Expand Down