diff --git a/internal/langserver/errors/errors.go b/internal/langserver/errors/errors.go new file mode 100644 index 000000000..45ed08bf7 --- /dev/null +++ b/internal/langserver/errors/errors.go @@ -0,0 +1,15 @@ +package errors + +import ( + e "errors" + + "github.com/hashicorp/terraform-ls/internal/terraform/module" +) + +func EnrichTfExecError(err error) error { + if module.IsTerraformNotFound(err) { + return e.New("Terraform (CLI) is required. " + + "Please install Terraform or make it available in $PATH") + } + return err +} diff --git a/internal/langserver/handlers/command/init.go b/internal/langserver/handlers/command/init.go index 6714f50e1..dabbec4d4 100644 --- a/internal/langserver/handlers/command/init.go +++ b/internal/langserver/handlers/command/init.go @@ -7,6 +7,7 @@ import ( "github.com/creachadair/jrpc2/code" lsctx "github.com/hashicorp/terraform-ls/internal/context" "github.com/hashicorp/terraform-ls/internal/langserver/cmd" + "github.com/hashicorp/terraform-ls/internal/langserver/errors" "github.com/hashicorp/terraform-ls/internal/langserver/progress" ilsp "github.com/hashicorp/terraform-ls/internal/lsp" lsp "github.com/hashicorp/terraform-ls/internal/protocol" @@ -40,7 +41,7 @@ func TerraformInitHandler(ctx context.Context, args cmd.CommandArgs) (interface{ tfExec, err := module.TerraformExecutorForModule(ctx, mod) if err != nil { - return nil, err + return nil, errors.EnrichTfExecError(err) } progress.Begin(ctx, "Initializing") diff --git a/internal/langserver/handlers/command/validate.go b/internal/langserver/handlers/command/validate.go index 46efe0001..cee335434 100644 --- a/internal/langserver/handlers/command/validate.go +++ b/internal/langserver/handlers/command/validate.go @@ -8,6 +8,7 @@ import ( lsctx "github.com/hashicorp/terraform-ls/internal/context" "github.com/hashicorp/terraform-ls/internal/langserver/cmd" "github.com/hashicorp/terraform-ls/internal/langserver/diagnostics" + "github.com/hashicorp/terraform-ls/internal/langserver/errors" "github.com/hashicorp/terraform-ls/internal/langserver/progress" ilsp "github.com/hashicorp/terraform-ls/internal/lsp" lsp "github.com/hashicorp/terraform-ls/internal/protocol" @@ -41,7 +42,7 @@ func TerraformValidateHandler(ctx context.Context, args cmd.CommandArgs) (interf tfExec, err := module.TerraformExecutorForModule(ctx, mod) if err != nil { - return nil, err + return nil, errors.EnrichTfExecError(err) } notifier, err := lsctx.Diagnostics(ctx) diff --git a/internal/langserver/handlers/did_open.go b/internal/langserver/handlers/did_open.go index 8c89c6142..54ee15bad 100644 --- a/internal/langserver/handlers/did_open.go +++ b/internal/langserver/handlers/did_open.go @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" lsctx "github.com/hashicorp/terraform-ls/internal/context" "github.com/hashicorp/terraform-ls/internal/langserver/cmd" + "github.com/hashicorp/terraform-ls/internal/langserver/errors" "github.com/hashicorp/terraform-ls/internal/langserver/handlers/command" ilsp "github.com/hashicorp/terraform-ls/internal/lsp" lsp "github.com/hashicorp/terraform-ls/internal/protocol" @@ -89,9 +90,16 @@ func (lh *logHandler) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpe return err } + _, tfExecErr := module.TerraformExecPath(ctx, mod) + if walker.IsWalking() || mod.ProviderSchemaState() == module.OpStateLoading { // avoid raising false warnings if operations are still in-flight lh.logger.Printf("walker has not finished walking yet, data may be inaccurate for %s", f.FullPath()) + } else if tfExecErr != nil { + jrpc2.PushNotify(ctx, "window/showMessage", lsp.ShowMessageParams{ + Type: lsp.Error, + Message: errors.EnrichTfExecError(tfExecErr).Error(), + }) } else if len(sources) == 0 { // TODO: Only notify once per f.Dir() per session dh := ilsp.FileHandlerFromDirPath(f.Dir()) diff --git a/internal/langserver/handlers/formatting.go b/internal/langserver/handlers/formatting.go index 40032501d..e449371af 100644 --- a/internal/langserver/handlers/formatting.go +++ b/internal/langserver/handlers/formatting.go @@ -5,6 +5,7 @@ import ( lsctx "github.com/hashicorp/terraform-ls/internal/context" "github.com/hashicorp/terraform-ls/internal/hcl" + "github.com/hashicorp/terraform-ls/internal/langserver/errors" ilsp "github.com/hashicorp/terraform-ls/internal/lsp" lsp "github.com/hashicorp/terraform-ls/internal/protocol" "github.com/hashicorp/terraform-ls/internal/terraform/module" @@ -32,7 +33,7 @@ func (h *logHandler) TextDocumentFormatting(ctx context.Context, params lsp.Docu tfExec, err := module.TerraformExecutorForModule(ctx, mod) if err != nil { - return edits, err + return edits, errors.EnrichTfExecError(err) } file, err := fs.GetDocument(fh) diff --git a/internal/terraform/module/errors.go b/internal/terraform/module/errors.go index dd2de9ca1..fa57b130a 100644 --- a/internal/terraform/module/errors.go +++ b/internal/terraform/module/errors.go @@ -22,3 +22,17 @@ func IsModuleNotFound(err error) bool { _, ok := err.(*ModuleNotFoundErr) return ok } + +type NoTerraformExecPathErr struct{} + +func (NoTerraformExecPathErr) Error() string { + return "No exec path provided for terraform" +} + +func IsTerraformNotFound(err error) bool { + if err == nil { + return false + } + _, ok := err.(NoTerraformExecPathErr) + return ok +} diff --git a/internal/terraform/module/terraform_executor.go b/internal/terraform/module/terraform_executor.go index 87d4b9aaf..60fa72a9a 100644 --- a/internal/terraform/module/terraform_executor.go +++ b/internal/terraform/module/terraform_executor.go @@ -13,24 +13,17 @@ func TerraformExecutorForModule(ctx context.Context, mod Module) (exec.Terraform return nil, fmt.Errorf("no terraform executor provided") } - var tfExec exec.TerraformExecutor - var err error + execPath, err := TerraformExecPath(ctx, mod) + if err != nil { + return nil, err + } - opts, ok := exec.ExecutorOptsFromContext(ctx) - if ok && opts.ExecPath != "" { - tfExec, err = newExecutor(mod.Path(), opts.ExecPath) - if err != nil { - return nil, err - } - } else if mod.TerraformExecPath() != "" { - tfExec, err = newExecutor(mod.Path(), mod.TerraformExecPath()) - if err != nil { - return nil, err - } - } else { - return nil, fmt.Errorf("no exec path provided for terraform") + tfExec, err := newExecutor(mod.Path(), execPath) + if err != nil { + return nil, err } + opts, ok := exec.ExecutorOptsFromContext(ctx) if ok && opts.ExecLogPath != "" { tfExec.SetExecLogPath(opts.ExecLogPath) } @@ -40,3 +33,14 @@ func TerraformExecutorForModule(ctx context.Context, mod Module) (exec.Terraform return tfExec, nil } + +func TerraformExecPath(ctx context.Context, mod Module) (string, error) { + opts, ok := exec.ExecutorOptsFromContext(ctx) + if ok && opts.ExecPath != "" { + return opts.ExecPath, nil + } else if mod.TerraformExecPath() != "" { + return mod.TerraformExecPath(), nil + } else { + return "", NoTerraformExecPathErr{} + } +}