From d54ab99795a8a21a9523e829960d1691d263d21f Mon Sep 17 00:00:00 2001
From: Alex Pilon <apilon@hashicorp.com>
Date: Thu, 22 Oct 2020 21:07:47 -0400
Subject: [PATCH 01/10] Ensure command names are unique to avoid language
 protocol collisions

---
 langserver/handlers/execute_command.go        | 18 +++++--
 .../execute_command_rootmodules_test.go       | 14 +++---
 langserver/handlers/handlers_test.go          | 50 ++++++++++++-------
 langserver/handlers/initialize.go             |  2 +-
 4 files changed, 52 insertions(+), 32 deletions(-)

diff --git a/langserver/handlers/execute_command.go b/langserver/handlers/execute_command.go
index 76eb2d47..505f5c90 100644
--- a/langserver/handlers/execute_command.go
+++ b/langserver/handlers/execute_command.go
@@ -7,9 +7,13 @@ import (
 	"strings"
 
 	"github.com/creachadair/jrpc2/code"
+	lsctx "github.com/hashicorp/terraform-ls/internal/context"
+	ilsp "github.com/hashicorp/terraform-ls/internal/lsp"
 	lsp "github.com/sourcegraph/go-lsp"
 )
 
+const commandPrefix = "terraform-ls."
+
 type executeCommandHandler func(context.Context, commandArgs) (interface{}, error)
 type executeCommandHandlers map[string]executeCommandHandler
 
@@ -17,16 +21,20 @@ var handlers = executeCommandHandlers{
 	"rootmodules": executeCommandRootModulesHandler,
 }
 
-func (h executeCommandHandlers) Names() []string {
-	var names []string
+func (h executeCommandHandlers) Names(suffix string) []string {
+	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]
+	rootDir, _ := lsctx.RootDirectory(ctx)
+	fh := ilsp.FileHandlerFromDirPath(rootDir)
+	name := strings.TrimPrefix(params.Command, commandPrefix)
+	name = strings.TrimSuffix(name, "."+fh.URI())
+	handler, ok := handlers[name]
 	if !ok {
 		return nil, fmt.Errorf("%w: command handler not found for %q", code.MethodNotFound.Err(), params.Command)
 	}
diff --git a/langserver/handlers/execute_command_rootmodules_test.go b/langserver/handlers/execute_command_rootmodules_test.go
index 42926db7..48c4bdf8 100644
--- a/langserver/handlers/execute_command_rootmodules_test.go
+++ b/langserver/handlers/execute_command_rootmodules_test.go
@@ -50,9 +50,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.%s"
+	}`, tmpDir.URI())}, code.InvalidParams.Err())
 }
 
 func TestLangServer_workspaceExecuteCommand_rootmodules_basic(t *testing.T) {
@@ -95,9 +95,9 @@ 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.%s",
 		"arguments": ["uri=%s"] 
-	}`, testFileURI)}, fmt.Sprintf(`{
+	}`, tmpDir.URI(), testFileURI)}, fmt.Sprintf(`{
 		"jsonrpc": "2.0",
 		"id": 3,
 		"result": {
@@ -158,9 +158,9 @@ 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.%s",
 		"arguments": ["uri=%s"] 
-	}`, module.URI())}, fmt.Sprintf(`{
+	}`, root.URI(), module.URI())}, fmt.Sprintf(`{
 		"jsonrpc": "2.0",
 		"id": 2,
 		"result": {
diff --git a/langserver/handlers/handlers_test.go b/langserver/handlers/handlers_test.go
index 8900420e..973566ac 100644
--- a/langserver/handlers/handlers_test.go
+++ b/langserver/handlers/handlers_test.go
@@ -1,6 +1,7 @@
 package handlers
 
 import (
+	"encoding/json"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -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, rootUri string) string {
+	jsonArray, err := json.Marshal(handlers.Names("." + rootUri))
+	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()
@@ -49,7 +59,7 @@ func TestInitalizeAndShutdown(t *testing.T) {
 	    "capabilities": {},
 	    "rootUri": %q,
 	    "processId": 12345
-	}`, TempDir(t).URI())}, initializeResponse)
+	}`, tmpDir.URI())}, initializeResponse(t, tmpDir.URI()))
 	ls.CallAndExpectResponse(t, &langserver.CallRequest{
 		Method: "shutdown", ReqParams: `{}`},
 		`{
@@ -60,6 +70,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()},
@@ -74,7 +86,7 @@ func TestEOF(t *testing.T) {
 	    "capabilities": {},
 	    "rootUri": %q,
 	    "processId": 12345
-	}`, TempDir(t).URI())}, initializeResponse)
+	}`, tmpDir.URI())}, initializeResponse(t, tmpDir.URI()))
 
 	ls.CloseClientStdout(t)
 
diff --git a/langserver/handlers/initialize.go b/langserver/handlers/initialize.go
index 5a812b28..88ae7224 100644
--- a/langserver/handlers/initialize.go
+++ b/langserver/handlers/initialize.go
@@ -29,7 +29,7 @@ func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParam
 			DocumentFormattingProvider: true,
 			DocumentSymbolProvider:     true,
 			ExecuteCommandProvider: &lsp.ExecuteCommandOptions{
-				Commands: handlers.Names(),
+				Commands: handlers.Names("." + string(params.RootURI)),
 			},
 		},
 	}

From d76e60926dcc7cd914b192893708275e897476c3 Mon Sep 17 00:00:00 2001
From: Alex Pilon <apilon@hashicorp.com>
Date: Tue, 27 Oct 2020 20:23:29 -0400
Subject: [PATCH 02/10] Refactor codebase to support server ID

---
 internal/context/context.go                   | 23 ++++++++++++++++
 internal/settings/settings.go                 |  2 ++
 langserver/handlers/execute_command.go        |  9 ++++---
 .../execute_command_rootmodules_test.go       | 27 ++++++++++++-------
 langserver/handlers/handlers_test.go          | 18 ++++++++-----
 langserver/handlers/initialize.go             | 13 ++++++---
 langserver/handlers/service.go                |  4 ++-
 7 files changed, 73 insertions(+), 23 deletions(-)

diff --git a/internal/context/context.go b/internal/context/context.go
index 8e08f864..06ae1095 100644
--- a/internal/context/context.go
+++ b/internal/context/context.go
@@ -35,6 +35,7 @@ var (
 	ctxRootModuleLoader  = &contextKey{"root module loader"}
 	ctxRootDir           = &contextKey{"root directory"}
 	ctxDiags             = &contextKey{"diagnostics"}
+	ctxServerID          = &contextKey{"servier id"}
 )
 
 func missingContextErr(ctxKey *contextKey) *MissingContextErr {
@@ -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)
 }
diff --git a/internal/settings/settings.go b/internal/settings/settings.go
index 68662ed1..a5304402 100644
--- a/internal/settings/settings.go
+++ b/internal/settings/settings.go
@@ -2,6 +2,7 @@ package settings
 
 import (
 	"fmt"
+
 	"github.com/mitchellh/mapstructure"
 )
 
@@ -9,6 +10,7 @@ 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"`
 
 	// TODO: Need to check for conflict with CLI flags
 	// TerraformExecPath string
diff --git a/langserver/handlers/execute_command.go b/langserver/handlers/execute_command.go
index 505f5c90..e0d3cdb8 100644
--- a/langserver/handlers/execute_command.go
+++ b/langserver/handlers/execute_command.go
@@ -8,7 +8,6 @@ import (
 
 	"github.com/creachadair/jrpc2/code"
 	lsctx "github.com/hashicorp/terraform-ls/internal/context"
-	ilsp "github.com/hashicorp/terraform-ls/internal/lsp"
 	lsp "github.com/sourcegraph/go-lsp"
 )
 
@@ -22,6 +21,9 @@ var handlers = executeCommandHandlers{
 }
 
 func (h executeCommandHandlers) Names(suffix string) []string {
+	if suffix != "" && !strings.HasPrefix(suffix, ".") {
+		suffix = "." + suffix
+	}
 	var fullnames []string
 	for name := range h {
 		fullnames = append(fullnames, commandPrefix+name+suffix)
@@ -30,10 +32,9 @@ func (h executeCommandHandlers) Names(suffix string) []string {
 }
 
 func (lh *logHandler) WorkspaceExecuteCommand(ctx context.Context, params lsp.ExecuteCommandParams) (interface{}, error) {
-	rootDir, _ := lsctx.RootDirectory(ctx)
-	fh := ilsp.FileHandlerFromDirPath(rootDir)
+	serverID, _ := lsctx.ServerID(ctx)
 	name := strings.TrimPrefix(params.Command, commandPrefix)
-	name = strings.TrimSuffix(name, "."+fh.URI())
+	name = strings.TrimSuffix(name, "."+serverID)
 	handler, ok := handlers[name]
 	if !ok {
 		return nil, fmt.Errorf("%w: command handler not found for %q", code.MethodNotFound.Err(), params.Command)
diff --git a/langserver/handlers/execute_command_rootmodules_test.go b/langserver/handlers/execute_command_rootmodules_test.go
index 48c4bdf8..baeb96b5 100644
--- a/langserver/handlers/execute_command_rootmodules_test.go
+++ b/langserver/handlers/execute_command_rootmodules_test.go
@@ -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",
@@ -51,8 +54,8 @@ func TestLangServer_workspaceExecuteCommand_rootmodules_argumentError(t *testing
 	ls.CallAndExpectError(t, &langserver.CallRequest{
 		Method: "workspace/executeCommand",
 		ReqParams: fmt.Sprintf(`{
-		"command": "terraform-ls.rootmodules.%s"
-	}`, tmpDir.URI())}, code.InvalidParams.Err())
+		"command": "terraform-ls.rootmodules.1"
+	}`)}, code.InvalidParams.Err())
 }
 
 func TestLangServer_workspaceExecuteCommand_rootmodules_basic(t *testing.T) {
@@ -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",
@@ -95,9 +101,9 @@ func TestLangServer_workspaceExecuteCommand_rootmodules_basic(t *testing.T) {
 	ls.CallAndExpectResponse(t, &langserver.CallRequest{
 		Method: "workspace/executeCommand",
 		ReqParams: fmt.Sprintf(`{
-		"command": "terraform-ls.rootmodules.%s",
+		"command": "terraform-ls.rootmodules.1",
 		"arguments": ["uri=%s"] 
-	}`, tmpDir.URI(), testFileURI)}, fmt.Sprintf(`{
+	}`, testFileURI)}, fmt.Sprintf(`{
 		"jsonrpc": "2.0",
 		"id": 3,
 		"result": {
@@ -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",
@@ -158,9 +167,9 @@ func TestLangServer_workspaceExecuteCommand_rootmodules_multiple(t *testing.T) {
 	ls.CallAndExpectResponse(t, &langserver.CallRequest{
 		Method: "workspace/executeCommand",
 		ReqParams: fmt.Sprintf(`{
-		"command": "terraform-ls.rootmodules.%s",
+		"command": "terraform-ls.rootmodules.1",
 		"arguments": ["uri=%s"] 
-	}`, root.URI(), module.URI())}, fmt.Sprintf(`{
+	}`, module.URI())}, fmt.Sprintf(`{
 		"jsonrpc": "2.0",
 		"id": 2,
 		"result": {
diff --git a/langserver/handlers/handlers_test.go b/langserver/handlers/handlers_test.go
index 973566ac..05720f3b 100644
--- a/langserver/handlers/handlers_test.go
+++ b/langserver/handlers/handlers_test.go
@@ -17,8 +17,8 @@ import (
 	"github.com/stretchr/testify/mock"
 )
 
-func initializeResponse(t *testing.T, rootUri string) string {
-	jsonArray, err := json.Marshal(handlers.Names("." + rootUri))
+func initializeResponse(t *testing.T, commandSuffix string) string {
+	jsonArray, err := json.Marshal(handlers.Names("." + commandSuffix))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -58,8 +58,11 @@ func TestInitalizeAndShutdown(t *testing.T) {
 		ReqParams: fmt.Sprintf(`{
 	    "capabilities": {},
 	    "rootUri": %q,
-	    "processId": 12345
-	}`, tmpDir.URI())}, initializeResponse(t, tmpDir.URI()))
+		"processId": 12345,
+		"initializationOptions": {
+			"id": "1"
+		}
+	}`, tmpDir.URI())}, initializeResponse(t, "1"))
 	ls.CallAndExpectResponse(t, &langserver.CallRequest{
 		Method: "shutdown", ReqParams: `{}`},
 		`{
@@ -85,8 +88,11 @@ func TestEOF(t *testing.T) {
 		ReqParams: fmt.Sprintf(`{
 	    "capabilities": {},
 	    "rootUri": %q,
-	    "processId": 12345
-	}`, tmpDir.URI())}, initializeResponse(t, tmpDir.URI()))
+		"processId": 12345,
+		"initializationOptions": {
+			"id": "1"
+		}
+	}`, tmpDir.URI())}, initializeResponse(t, "1"))
 
 	ls.CloseClientStdout(t)
 
diff --git a/langserver/handlers/initialize.go b/langserver/handlers/initialize.go
index 88ae7224..24b7688e 100644
--- a/langserver/handlers/initialize.go
+++ b/langserver/handlers/initialize.go
@@ -28,9 +28,6 @@ func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParam
 			},
 			DocumentFormattingProvider: true,
 			DocumentSymbolProvider:     true,
-			ExecuteCommandProvider: &lsp.ExecuteCommandOptions{
-				Commands: handlers.Names("." + string(params.RootURI)),
-			},
 		},
 	}
 
@@ -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),
+	}
 	if len(out.UnusedKeys) > 0 {
 		jrpc2.PushNotify(ctx, "window/showMessage", &lsp.ShowMessageParams{
 			Type:    lsp.MTWarning,
diff --git a/langserver/handlers/service.go b/langserver/handlers/service.go
index 8d0fcb79..3b65fd74 100644
--- a/langserver/handlers/service.go
+++ b/langserver/handlers/service.go
@@ -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) {
@@ -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)
 
@@ -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)
 

From 646ed62e760c44570e9fc4cec4e69efa05365d9b Mon Sep 17 00:00:00 2001
From: appilon <apilon@hashicorp.com>
Date: Tue, 27 Oct 2020 20:31:25 -0400
Subject: [PATCH 03/10] Update internal/context/context.go

Co-authored-by: Audrey Eschright <audrey@hashicorp.com>
---
 internal/context/context.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/internal/context/context.go b/internal/context/context.go
index 06ae1095..85e3e39f 100644
--- a/internal/context/context.go
+++ b/internal/context/context.go
@@ -35,7 +35,7 @@ var (
 	ctxRootModuleLoader  = &contextKey{"root module loader"}
 	ctxRootDir           = &contextKey{"root directory"}
 	ctxDiags             = &contextKey{"diagnostics"}
-	ctxServerID          = &contextKey{"servier id"}
+	ctxServerID          = &contextKey{"server id"}
 )
 
 func missingContextErr(ctxKey *contextKey) *MissingContextErr {

From d121e3aa4f3e6161b46e78b4262d41b965d09ba7 Mon Sep 17 00:00:00 2001
From: Alex Pilon <apilon@hashicorp.com>
Date: Thu, 29 Oct 2020 20:07:34 -0400
Subject: [PATCH 04/10] Refactor command prefix and handler init

---
 internal/context/context.go                   | 24 ++----------
 internal/settings/settings.go                 |  2 +-
 langserver/handlers/execute_command.go        | 37 +++++++++++--------
 .../execute_command_rootmodules_test.go       | 27 +++++---------
 langserver/handlers/handlers_test.go          | 18 +++------
 langserver/handlers/initialize.go             |  9 +----
 langserver/handlers/service.go                |  3 --
 7 files changed, 43 insertions(+), 77 deletions(-)

diff --git a/internal/context/context.go b/internal/context/context.go
index 85e3e39f..d72846e8 100644
--- a/internal/context/context.go
+++ b/internal/context/context.go
@@ -35,7 +35,7 @@ var (
 	ctxRootModuleLoader  = &contextKey{"root module loader"}
 	ctxRootDir           = &contextKey{"root directory"}
 	ctxDiags             = &contextKey{"diagnostics"}
-	ctxServerID          = &contextKey{"server id"}
+	ctxCommandPrefix     = &contextKey{"command prefix"}
 )
 
 func missingContextErr(ctxKey *contextKey) *MissingContextErr {
@@ -191,26 +191,8 @@ 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 WithCommandPrefix(ctx context.Context, prefix *string) context.Context {
+	return context.WithValue(ctx, ctxCommandPrefix, prefix)
 }
 
 func WithRootModuleWalker(ctx context.Context, w *rootmodule.Walker) context.Context {
diff --git a/internal/settings/settings.go b/internal/settings/settings.go
index a5304402..85e43c82 100644
--- a/internal/settings/settings.go
+++ b/internal/settings/settings.go
@@ -10,7 +10,7 @@ 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"`
+	CommandPrefix      string   `mapstructure:"commandPrefix"`
 
 	// TODO: Need to check for conflict with CLI flags
 	// TerraformExecPath string
diff --git a/langserver/handlers/execute_command.go b/langserver/handlers/execute_command.go
index e0d3cdb8..6538fed8 100644
--- a/langserver/handlers/execute_command.go
+++ b/langserver/handlers/execute_command.go
@@ -7,35 +7,42 @@ 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,
+const langServerPrefix = "terraform-ls."
+
+var commandPrefix string
+var handlers = make(executeCommandHandlers)
+
+func prefix(name string) string {
+	prefix := langServerPrefix
+	if commandPrefix != "" {
+		prefix = commandPrefix + "." + langServerPrefix
+	}
+	return prefix + name
 }
 
-func (h executeCommandHandlers) Names(suffix string) []string {
-	if suffix != "" && !strings.HasPrefix(suffix, ".") {
-		suffix = "." + suffix
+func (h executeCommandHandlers) Init(p string) executeCommandHandlers {
+	if len(h) == 0 {
+		commandPrefix = p
+		h[prefix("rootmodules")] = executeCommandRootModulesHandler
 	}
-	var fullnames []string
+	return h
+}
+
+func (h executeCommandHandlers) Names() (names []string) {
 	for name := range h {
-		fullnames = append(fullnames, commandPrefix+name+suffix)
+		names = append(names, name)
 	}
-	return fullnames
+	return names
 }
 
 func (lh *logHandler) WorkspaceExecuteCommand(ctx context.Context, params lsp.ExecuteCommandParams) (interface{}, error) {
-	serverID, _ := lsctx.ServerID(ctx)
-	name := strings.TrimPrefix(params.Command, commandPrefix)
-	name = strings.TrimSuffix(name, "."+serverID)
-	handler, ok := handlers[name]
+	handler, ok := handlers[params.Command]
 	if !ok {
 		return nil, fmt.Errorf("%w: command handler not found for %q", code.MethodNotFound.Err(), params.Command)
 	}
diff --git a/langserver/handlers/execute_command_rootmodules_test.go b/langserver/handlers/execute_command_rootmodules_test.go
index baeb96b5..5382b91e 100644
--- a/langserver/handlers/execute_command_rootmodules_test.go
+++ b/langserver/handlers/execute_command_rootmodules_test.go
@@ -31,10 +31,7 @@ func TestLangServer_workspaceExecuteCommand_rootmodules_argumentError(t *testing
 		ReqParams: fmt.Sprintf(`{
 	    "capabilities": {},
 	    "rootUri": %q,
-		"processId": 12345,
-		"initializationOptions": {
-			"id": "1"
-		}
+		"processId": 12345
 	}`, tmpDir.URI())})
 	ls.Notify(t, &langserver.CallRequest{
 		Method:    "initialized",
@@ -54,8 +51,8 @@ func TestLangServer_workspaceExecuteCommand_rootmodules_argumentError(t *testing
 	ls.CallAndExpectError(t, &langserver.CallRequest{
 		Method: "workspace/executeCommand",
 		ReqParams: fmt.Sprintf(`{
-		"command": "terraform-ls.rootmodules.1"
-	}`)}, code.InvalidParams.Err())
+		"command": %q
+	}`, prefix("rootmodules"))}, code.InvalidParams.Err())
 }
 
 func TestLangServer_workspaceExecuteCommand_rootmodules_basic(t *testing.T) {
@@ -78,10 +75,7 @@ func TestLangServer_workspaceExecuteCommand_rootmodules_basic(t *testing.T) {
 		ReqParams: fmt.Sprintf(`{
 	    "capabilities": {},
 	    "rootUri": %q,
-		"processId": 12345,
-		"initializationOptions": {
-			"id": "1"
-		}
+		"processId": 12345
 	}`, tmpDir.URI())})
 	ls.Notify(t, &langserver.CallRequest{
 		Method:    "initialized",
@@ -101,9 +95,9 @@ func TestLangServer_workspaceExecuteCommand_rootmodules_basic(t *testing.T) {
 	ls.CallAndExpectResponse(t, &langserver.CallRequest{
 		Method: "workspace/executeCommand",
 		ReqParams: fmt.Sprintf(`{
-		"command": "terraform-ls.rootmodules.1",
+		"command": %q,
 		"arguments": ["uri=%s"] 
-	}`, testFileURI)}, fmt.Sprintf(`{
+	}`, prefix("rootmodules"), testFileURI)}, fmt.Sprintf(`{
 		"jsonrpc": "2.0",
 		"id": 3,
 		"result": {
@@ -152,10 +146,7 @@ func TestLangServer_workspaceExecuteCommand_rootmodules_multiple(t *testing.T) {
 		ReqParams: fmt.Sprintf(`{
 	    "capabilities": {},
 	    "rootUri": %q,
-		"processId": 12345,
-		"initializationOptions": {
-			"id": "1"
-		}
+		"processId": 12345
 	}`, root.URI())})
 	ls.Notify(t, &langserver.CallRequest{
 		Method:    "initialized",
@@ -167,9 +158,9 @@ func TestLangServer_workspaceExecuteCommand_rootmodules_multiple(t *testing.T) {
 	ls.CallAndExpectResponse(t, &langserver.CallRequest{
 		Method: "workspace/executeCommand",
 		ReqParams: fmt.Sprintf(`{
-		"command": "terraform-ls.rootmodules.1",
+		"command": %q,
 		"arguments": ["uri=%s"] 
-	}`, module.URI())}, fmt.Sprintf(`{
+	}`, prefix("rootmodules"), module.URI())}, fmt.Sprintf(`{
 		"jsonrpc": "2.0",
 		"id": 2,
 		"result": {
diff --git a/langserver/handlers/handlers_test.go b/langserver/handlers/handlers_test.go
index 05720f3b..1cf0677f 100644
--- a/langserver/handlers/handlers_test.go
+++ b/langserver/handlers/handlers_test.go
@@ -17,8 +17,8 @@ import (
 	"github.com/stretchr/testify/mock"
 )
 
-func initializeResponse(t *testing.T, commandSuffix string) string {
-	jsonArray, err := json.Marshal(handlers.Names("." + commandSuffix))
+func initializeResponse(t *testing.T, prefix string) string {
+	jsonArray, err := json.Marshal(handlers.Init(prefix).Names())
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -58,11 +58,8 @@ func TestInitalizeAndShutdown(t *testing.T) {
 		ReqParams: fmt.Sprintf(`{
 	    "capabilities": {},
 	    "rootUri": %q,
-		"processId": 12345,
-		"initializationOptions": {
-			"id": "1"
-		}
-	}`, tmpDir.URI())}, initializeResponse(t, "1"))
+		"processId": 12345
+	}`, tmpDir.URI())}, initializeResponse(t, ""))
 	ls.CallAndExpectResponse(t, &langserver.CallRequest{
 		Method: "shutdown", ReqParams: `{}`},
 		`{
@@ -88,11 +85,8 @@ func TestEOF(t *testing.T) {
 		ReqParams: fmt.Sprintf(`{
 	    "capabilities": {},
 	    "rootUri": %q,
-		"processId": 12345,
-		"initializationOptions": {
-			"id": "1"
-		}
-	}`, tmpDir.URI())}, initializeResponse(t, "1"))
+		"processId": 12345
+	}`, tmpDir.URI())}, initializeResponse(t, ""))
 
 	ls.CloseClientStdout(t)
 
diff --git a/langserver/handlers/initialize.go b/langserver/handlers/initialize.go
index 24b7688e..4dc71650 100644
--- a/langserver/handlers/initialize.go
+++ b/langserver/handlers/initialize.go
@@ -75,14 +75,9 @@ func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParam
 		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
+	// apply prefix to executeCommand handler names
 	serverCaps.Capabilities.ExecuteCommandProvider = &lsp.ExecuteCommandOptions{
-		Commands: handlers.Names(out.Options.ID),
+		Commands: handlers.Init(out.Options.CommandPrefix).Names(),
 	}
 	if len(out.UnusedKeys) > 0 {
 		jrpc2.PushNotify(ctx, "window/showMessage", &lsp.ShowMessageParams{
diff --git a/langserver/handlers/service.go b/langserver/handlers/service.go
index 3b65fd74..f192c5c1 100644
--- a/langserver/handlers/service.go
+++ b/langserver/handlers/service.go
@@ -145,7 +145,6 @@ 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) {
@@ -158,7 +157,6 @@ 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)
 
@@ -242,7 +240,6 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
 				return nil, err
 			}
 
-			ctx = lsctx.WithServerID(ctx, &serverID)
 			ctx = lsctx.WithRootModuleCandidateFinder(ctx, svc.modMgr)
 			ctx = lsctx.WithRootModuleWalker(ctx, svc.walker)
 

From e1fd836cc5844f65a081b88f779b7cefaa9b179e Mon Sep 17 00:00:00 2001
From: Alex Pilon <apilon@hashicorp.com>
Date: Thu, 29 Oct 2020 20:36:08 -0400
Subject: [PATCH 05/10] Fix tests to not use commanPrefix

Add explicit test with commandPrefix
Add doc on new setting
---
 docs/SETTINGS.md                     | 20 ++++++++++++++++++++
 langserver/handlers/handlers_test.go | 22 ++++++++++++++++++++++
 2 files changed, 42 insertions(+)

diff --git a/docs/SETTINGS.md b/docs/SETTINGS.md
index 53409f18..453b2d55 100644
--- a/docs/SETTINGS.md
+++ b/docs/SETTINGS.md
@@ -30,6 +30,26 @@ of the target platform (e.g. `\` on Windows, or `/` on Unix),
 symlinks are followed, trailing slashes automatically removed,
 and `~` is replaced with your home directory.
 
+## `commandPrefix`
+
+Some clients such as VS code keep a global registry of commands published by language
+servers, and the names must be unique, even between terraform-ls instances. Setting
+this allows multiple servers to run side by side, albeit the client is now responsible
+for routing commands to the correct server. Users should not need to worry about
+this, the frontend client extension should manage it.
+
+The prefix will be applied to the front of the command name, which already contains
+a `terraform-ls` prefix.
+
+`commandPrefix.terraform-ls.commandName`
+
+Or if left empty
+
+`terraform-ls.commandName`
+
+This setting should be deprecated once the language server supports multiple workspaces,
+as this arises in VS code because a server instance is started per VS code workspace.
+
 ## How to pass settings
 
 The server expects static settings to be passed as part of LSP `initialize` call,
diff --git a/langserver/handlers/handlers_test.go b/langserver/handlers/handlers_test.go
index 1cf0677f..3ae43821 100644
--- a/langserver/handlers/handlers_test.go
+++ b/langserver/handlers/handlers_test.go
@@ -69,6 +69,28 @@ func TestInitalizeAndShutdown(t *testing.T) {
 	}`)
 }
 
+func TestInitalizeWithCommandPrefix(t *testing.T) {
+	tmpDir := TempDir(t)
+
+	ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{
+		RootModules: map[string]*rootmodule.RootModuleMock{
+			tmpDir.Dir(): {TfExecFactory: validTfMockCalls()},
+		}}))
+	stop := ls.Start(t)
+	defer stop()
+
+	ls.CallAndExpectResponse(t, &langserver.CallRequest{
+		Method: "initialize",
+		ReqParams: fmt.Sprintf(`{
+	    "capabilities": {},
+	    "rootUri": %q,
+		"processId": 12345,
+		"initializationOptions": {
+			"commandPrefix": "1"
+		}
+	}`, tmpDir.URI())}, initializeResponse(t, "1"))
+}
+
 func TestEOF(t *testing.T) {
 	tmpDir := TempDir(t)
 

From 44daa5ec6f846eae6ca139b218680ed56928e35c Mon Sep 17 00:00:00 2001
From: Alex Pilon <apilon@hashicorp.com>
Date: Thu, 29 Oct 2020 20:39:14 -0400
Subject: [PATCH 06/10] Remove context commandPrefix

---
 internal/context/context.go | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/internal/context/context.go b/internal/context/context.go
index d72846e8..8e08f864 100644
--- a/internal/context/context.go
+++ b/internal/context/context.go
@@ -35,7 +35,6 @@ var (
 	ctxRootModuleLoader  = &contextKey{"root module loader"}
 	ctxRootDir           = &contextKey{"root directory"}
 	ctxDiags             = &contextKey{"diagnostics"}
-	ctxCommandPrefix     = &contextKey{"command prefix"}
 )
 
 func missingContextErr(ctxKey *contextKey) *MissingContextErr {
@@ -191,10 +190,6 @@ func RootDirectory(ctx context.Context) (string, bool) {
 	return *rootDir, true
 }
 
-func WithCommandPrefix(ctx context.Context, prefix *string) context.Context {
-	return context.WithValue(ctx, ctxCommandPrefix, prefix)
-}
-
 func WithRootModuleWalker(ctx context.Context, w *rootmodule.Walker) context.Context {
 	return context.WithValue(ctx, ctxRootModuleWalker, w)
 }

From 04d2c218a015988f0bad248c68ce5db2346598bf Mon Sep 17 00:00:00 2001
From: Alex Pilon <apilon@hashicorp.com>
Date: Thu, 29 Oct 2020 21:10:45 -0400
Subject: [PATCH 07/10] Fix clearing of global map between tests

---
 langserver/handlers/execute_command.go |  6 ++----
 langserver/handlers/handlers_test.go   | 16 +++++++++++-----
 2 files changed, 13 insertions(+), 9 deletions(-)

diff --git a/langserver/handlers/execute_command.go b/langserver/handlers/execute_command.go
index 6538fed8..e18e3299 100644
--- a/langserver/handlers/execute_command.go
+++ b/langserver/handlers/execute_command.go
@@ -27,10 +27,8 @@ func prefix(name string) string {
 }
 
 func (h executeCommandHandlers) Init(p string) executeCommandHandlers {
-	if len(h) == 0 {
-		commandPrefix = p
-		h[prefix("rootmodules")] = executeCommandRootModulesHandler
-	}
+	commandPrefix = p
+	h[prefix("rootmodules")] = executeCommandRootModulesHandler
 	return h
 }
 
diff --git a/langserver/handlers/handlers_test.go b/langserver/handlers/handlers_test.go
index 3ae43821..0f8cd4e6 100644
--- a/langserver/handlers/handlers_test.go
+++ b/langserver/handlers/handlers_test.go
@@ -17,8 +17,8 @@ import (
 	"github.com/stretchr/testify/mock"
 )
 
-func initializeResponse(t *testing.T, prefix string) string {
-	jsonArray, err := json.Marshal(handlers.Init(prefix).Names())
+func initializeResponse(t *testing.T) string {
+	jsonArray, err := json.Marshal(handlers.Names())
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -44,6 +44,8 @@ func initializeResponse(t *testing.T, prefix string) string {
 }
 
 func TestInitalizeAndShutdown(t *testing.T) {
+	handlers = make(executeCommandHandlers)
+	handlers.Init("")
 	tmpDir := TempDir(t)
 
 	ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{
@@ -59,7 +61,7 @@ func TestInitalizeAndShutdown(t *testing.T) {
 	    "capabilities": {},
 	    "rootUri": %q,
 		"processId": 12345
-	}`, tmpDir.URI())}, initializeResponse(t, ""))
+	}`, tmpDir.URI())}, initializeResponse(t))
 	ls.CallAndExpectResponse(t, &langserver.CallRequest{
 		Method: "shutdown", ReqParams: `{}`},
 		`{
@@ -70,6 +72,8 @@ func TestInitalizeAndShutdown(t *testing.T) {
 }
 
 func TestInitalizeWithCommandPrefix(t *testing.T) {
+	handlers = make(executeCommandHandlers)
+	handlers.Init("1")
 	tmpDir := TempDir(t)
 
 	ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{
@@ -88,10 +92,12 @@ func TestInitalizeWithCommandPrefix(t *testing.T) {
 		"initializationOptions": {
 			"commandPrefix": "1"
 		}
-	}`, tmpDir.URI())}, initializeResponse(t, "1"))
+	}`, tmpDir.URI())}, initializeResponse(t))
 }
 
 func TestEOF(t *testing.T) {
+	handlers = make(executeCommandHandlers)
+	handlers.Init("")
 	tmpDir := TempDir(t)
 
 	ms := newMockSession(&MockSessionInput{
@@ -108,7 +114,7 @@ func TestEOF(t *testing.T) {
 	    "capabilities": {},
 	    "rootUri": %q,
 		"processId": 12345
-	}`, tmpDir.URI())}, initializeResponse(t, ""))
+	}`, tmpDir.URI())}, initializeResponse(t))
 
 	ls.CloseClientStdout(t)
 

From 44b927359602fc6fd43a224be685d42dc4c7dd55 Mon Sep 17 00:00:00 2001
From: appilon <apilon@hashicorp.com>
Date: Fri, 30 Oct 2020 10:04:54 -0400
Subject: [PATCH 08/10] Apply suggestions from code review

Co-authored-by: Radek Simko <radek.simko@gmail.com>
---
 docs/SETTINGS.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/SETTINGS.md b/docs/SETTINGS.md
index 453b2d55..064daeff 100644
--- a/docs/SETTINGS.md
+++ b/docs/SETTINGS.md
@@ -32,7 +32,7 @@ and `~` is replaced with your home directory.
 
 ## `commandPrefix`
 
-Some clients such as VS code keep a global registry of commands published by language
+Some clients such as VS Code keep a global registry of commands published by language
 servers, and the names must be unique, even between terraform-ls instances. Setting
 this allows multiple servers to run side by side, albeit the client is now responsible
 for routing commands to the correct server. Users should not need to worry about
@@ -48,7 +48,7 @@ Or if left empty
 `terraform-ls.commandName`
 
 This setting should be deprecated once the language server supports multiple workspaces,
-as this arises in VS code because a server instance is started per VS code workspace.
+as this arises in VS code because a server instance is started per VS Code workspace.
 
 ## How to pass settings
 

From 2d3eaa49832db5537f18178cc57924b9bfbc333a Mon Sep 17 00:00:00 2001
From: Alex Pilon <apilon@hashicorp.com>
Date: Fri, 30 Oct 2020 11:52:33 -0400
Subject: [PATCH 09/10] Make session safe command prefix

---
 internal/context/context.go            | 23 ++++++++++++++++
 langserver/handlers/execute_command.go | 37 +++++++++++++++-----------
 langserver/handlers/handlers_test.go   | 16 ++++-------
 langserver/handlers/initialize.go      |  5 +++-
 langserver/handlers/service.go         |  3 +++
 5 files changed, 56 insertions(+), 28 deletions(-)

diff --git a/internal/context/context.go b/internal/context/context.go
index 8e08f864..35eab216 100644
--- a/internal/context/context.go
+++ b/internal/context/context.go
@@ -34,6 +34,7 @@ var (
 	ctxRootModuleWalker  = &contextKey{"root module walker"}
 	ctxRootModuleLoader  = &contextKey{"root module loader"}
 	ctxRootDir           = &contextKey{"root directory"}
+	ctxCommandPrefix     = &contextKey{"command prefix"}
 	ctxDiags             = &contextKey{"diagnostics"}
 )
 
@@ -190,6 +191,28 @@ func RootDirectory(ctx context.Context) (string, bool) {
 	return *rootDir, true
 }
 
+func WithCommandPrefix(ctx context.Context, prefix *string) context.Context {
+	return context.WithValue(ctx, ctxCommandPrefix, prefix)
+}
+
+func SetCommandPrefix(ctx context.Context, prefix string) error {
+	commandPrefix, ok := ctx.Value(ctxCommandPrefix).(*string)
+	if !ok {
+		return missingContextErr(ctxCommandPrefix)
+	}
+
+	*commandPrefix = prefix
+	return nil
+}
+
+func CommandPrefix(ctx context.Context) (string, bool) {
+	commandPrefix, ok := ctx.Value(ctxCommandPrefix).(*string)
+	if !ok {
+		return "", false
+	}
+	return *commandPrefix, true
+}
+
 func WithRootModuleWalker(ctx context.Context, w *rootmodule.Walker) context.Context {
 	return context.WithValue(ctx, ctxRootModuleWalker, w)
 }
diff --git a/langserver/handlers/execute_command.go b/langserver/handlers/execute_command.go
index e18e3299..fb61b046 100644
--- a/langserver/handlers/execute_command.go
+++ b/langserver/handlers/execute_command.go
@@ -7,6 +7,7 @@ import (
 	"strings"
 
 	"github.com/creachadair/jrpc2/code"
+	lsctx "github.com/hashicorp/terraform-ls/internal/context"
 	lsp "github.com/sourcegraph/go-lsp"
 )
 
@@ -15,32 +16,36 @@ type executeCommandHandlers map[string]executeCommandHandler
 
 const langServerPrefix = "terraform-ls."
 
-var commandPrefix string
-var handlers = make(executeCommandHandlers)
-
-func prefix(name string) string {
-	prefix := langServerPrefix
-	if commandPrefix != "" {
-		prefix = commandPrefix + "." + langServerPrefix
-	}
-	return prefix + name
+var handlers = executeCommandHandlers{
+	prefix("rootmodules"): executeCommandRootModulesHandler,
 }
 
-func (h executeCommandHandlers) Init(p string) executeCommandHandlers {
-	commandPrefix = p
-	h[prefix("rootmodules")] = executeCommandRootModulesHandler
-	return h
+func prefix(name string) string {
+	return langServerPrefix + name
 }
 
-func (h executeCommandHandlers) Names() (names []string) {
+func (h executeCommandHandlers) Names(commandPrefix string) (names []string) {
+	if commandPrefix != "" {
+		commandPrefix += "."
+	}
 	for name := range h {
-		names = append(names, name)
+		names = append(names, commandPrefix+name)
 	}
 	return names
 }
 
+func (h executeCommandHandlers) Get(name, commandPrefix string) (executeCommandHandler, bool) {
+	if commandPrefix != "" {
+		commandPrefix += "."
+	}
+	name = strings.TrimPrefix(name, commandPrefix)
+	handler, ok := h[name]
+	return handler, ok
+}
+
 func (lh *logHandler) WorkspaceExecuteCommand(ctx context.Context, params lsp.ExecuteCommandParams) (interface{}, error) {
-	handler, ok := handlers[params.Command]
+	commandPrefix, _ := lsctx.CommandPrefix(ctx)
+	handler, ok := handlers.Get(params.Command, commandPrefix)
 	if !ok {
 		return nil, fmt.Errorf("%w: command handler not found for %q", code.MethodNotFound.Err(), params.Command)
 	}
diff --git a/langserver/handlers/handlers_test.go b/langserver/handlers/handlers_test.go
index 0f8cd4e6..71c3ced2 100644
--- a/langserver/handlers/handlers_test.go
+++ b/langserver/handlers/handlers_test.go
@@ -17,8 +17,8 @@ import (
 	"github.com/stretchr/testify/mock"
 )
 
-func initializeResponse(t *testing.T) string {
-	jsonArray, err := json.Marshal(handlers.Names())
+func initializeResponse(t *testing.T, commandPrefix string) string {
+	jsonArray, err := json.Marshal(handlers.Names(commandPrefix))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -44,8 +44,6 @@ func initializeResponse(t *testing.T) string {
 }
 
 func TestInitalizeAndShutdown(t *testing.T) {
-	handlers = make(executeCommandHandlers)
-	handlers.Init("")
 	tmpDir := TempDir(t)
 
 	ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{
@@ -61,7 +59,7 @@ func TestInitalizeAndShutdown(t *testing.T) {
 	    "capabilities": {},
 	    "rootUri": %q,
 		"processId": 12345
-	}`, tmpDir.URI())}, initializeResponse(t))
+	}`, tmpDir.URI())}, initializeResponse(t, ""))
 	ls.CallAndExpectResponse(t, &langserver.CallRequest{
 		Method: "shutdown", ReqParams: `{}`},
 		`{
@@ -72,8 +70,6 @@ func TestInitalizeAndShutdown(t *testing.T) {
 }
 
 func TestInitalizeWithCommandPrefix(t *testing.T) {
-	handlers = make(executeCommandHandlers)
-	handlers.Init("1")
 	tmpDir := TempDir(t)
 
 	ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{
@@ -92,12 +88,10 @@ func TestInitalizeWithCommandPrefix(t *testing.T) {
 		"initializationOptions": {
 			"commandPrefix": "1"
 		}
-	}`, tmpDir.URI())}, initializeResponse(t))
+	}`, tmpDir.URI())}, initializeResponse(t, "1"))
 }
 
 func TestEOF(t *testing.T) {
-	handlers = make(executeCommandHandlers)
-	handlers.Init("")
 	tmpDir := TempDir(t)
 
 	ms := newMockSession(&MockSessionInput{
@@ -114,7 +108,7 @@ func TestEOF(t *testing.T) {
 	    "capabilities": {},
 	    "rootUri": %q,
 		"processId": 12345
-	}`, tmpDir.URI())}, initializeResponse(t))
+	}`, tmpDir.URI())}, initializeResponse(t, ""))
 
 	ls.CloseClientStdout(t)
 
diff --git a/langserver/handlers/initialize.go b/langserver/handlers/initialize.go
index 4dc71650..84c6efab 100644
--- a/langserver/handlers/initialize.go
+++ b/langserver/handlers/initialize.go
@@ -75,10 +75,13 @@ func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParam
 		return serverCaps, err
 	}
 
+	// set commandPrefix for session
+	lsctx.SetCommandPrefix(ctx, out.Options.CommandPrefix)
 	// apply prefix to executeCommand handler names
 	serverCaps.Capabilities.ExecuteCommandProvider = &lsp.ExecuteCommandOptions{
-		Commands: handlers.Init(out.Options.CommandPrefix).Names(),
+		Commands: handlers.Names(out.Options.CommandPrefix),
 	}
+
 	if len(out.UnusedKeys) > 0 {
 		jrpc2.PushNotify(ctx, "window/showMessage", &lsp.ShowMessageParams{
 			Type:    lsp.MTWarning,
diff --git a/langserver/handlers/service.go b/langserver/handlers/service.go
index f192c5c1..f77c78a5 100644
--- a/langserver/handlers/service.go
+++ b/langserver/handlers/service.go
@@ -145,6 +145,7 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
 	diags := diagnostics.NewNotifier(svc.sessCtx, svc.logger)
 
 	rootDir := ""
+	commandPrefix := ""
 
 	m := map[string]rpch.Func{
 		"initialize": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) {
@@ -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.WithCommandPrefix(ctx, &commandPrefix)
 			ctx = lsctx.WithRootModuleManager(ctx, svc.modMgr)
 			ctx = lsctx.WithRootModuleLoader(ctx, rmLoader)
 
@@ -240,6 +242,7 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
 				return nil, err
 			}
 
+			ctx = lsctx.WithCommandPrefix(ctx, &commandPrefix)
 			ctx = lsctx.WithRootModuleCandidateFinder(ctx, svc.modMgr)
 			ctx = lsctx.WithRootModuleWalker(ctx, svc.walker)
 

From ed25c1b24fc107f67879c71fd8fff0daf132ed61 Mon Sep 17 00:00:00 2001
From: Alex Pilon <apilon@hashicorp.com>
Date: Fri, 30 Oct 2020 12:25:56 -0400
Subject: [PATCH 10/10] adjust func name

---
 langserver/handlers/execute_command.go                  | 4 ++--
 langserver/handlers/execute_command_rootmodules_test.go | 6 +++---
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/langserver/handlers/execute_command.go b/langserver/handlers/execute_command.go
index fb61b046..a11c00ca 100644
--- a/langserver/handlers/execute_command.go
+++ b/langserver/handlers/execute_command.go
@@ -17,10 +17,10 @@ type executeCommandHandlers map[string]executeCommandHandler
 const langServerPrefix = "terraform-ls."
 
 var handlers = executeCommandHandlers{
-	prefix("rootmodules"): executeCommandRootModulesHandler,
+	prefixCommandName("rootmodules"): executeCommandRootModulesHandler,
 }
 
-func prefix(name string) string {
+func prefixCommandName(name string) string {
 	return langServerPrefix + name
 }
 
diff --git a/langserver/handlers/execute_command_rootmodules_test.go b/langserver/handlers/execute_command_rootmodules_test.go
index 5382b91e..6d96c907 100644
--- a/langserver/handlers/execute_command_rootmodules_test.go
+++ b/langserver/handlers/execute_command_rootmodules_test.go
@@ -52,7 +52,7 @@ func TestLangServer_workspaceExecuteCommand_rootmodules_argumentError(t *testing
 		Method: "workspace/executeCommand",
 		ReqParams: fmt.Sprintf(`{
 		"command": %q
-	}`, prefix("rootmodules"))}, code.InvalidParams.Err())
+	}`, prefixCommandName("rootmodules"))}, code.InvalidParams.Err())
 }
 
 func TestLangServer_workspaceExecuteCommand_rootmodules_basic(t *testing.T) {
@@ -97,7 +97,7 @@ func TestLangServer_workspaceExecuteCommand_rootmodules_basic(t *testing.T) {
 		ReqParams: fmt.Sprintf(`{
 		"command": %q,
 		"arguments": ["uri=%s"] 
-	}`, prefix("rootmodules"), testFileURI)}, fmt.Sprintf(`{
+	}`, prefixCommandName("rootmodules"), testFileURI)}, fmt.Sprintf(`{
 		"jsonrpc": "2.0",
 		"id": 3,
 		"result": {
@@ -160,7 +160,7 @@ func TestLangServer_workspaceExecuteCommand_rootmodules_multiple(t *testing.T) {
 		ReqParams: fmt.Sprintf(`{
 		"command": %q,
 		"arguments": ["uri=%s"] 
-	}`, prefix("rootmodules"), module.URI())}, fmt.Sprintf(`{
+	}`, prefixCommandName("rootmodules"), module.URI())}, fmt.Sprintf(`{
 		"jsonrpc": "2.0",
 		"id": 2,
 		"result": {