Skip to content

Commit

Permalink
Expose terraform.init as a command + implement progress reporting (ha…
Browse files Browse the repository at this point in the history
  • Loading branch information
radeksimko authored Dec 2, 2020
1 parent e9f301d commit 586ff89
Show file tree
Hide file tree
Showing 16 changed files with 424 additions and 30 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/fsnotify/fsnotify v1.4.9
github.com/gammazero/workerpool v1.0.0
github.com/google/go-cmp v0.5.1
github.com/google/uuid v1.1.2
github.com/hashicorp/go-multierror v1.1.0
github.com/hashicorp/go-version v1.2.1
github.com/hashicorp/hcl-lang v0.0.0-20201116081236-948e43712a65
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
Expand Down
13 changes: 13 additions & 0 deletions internal/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var (
ctxCommandPrefix = &contextKey{"command prefix"}
ctxDiags = &contextKey{"diagnostics"}
ctxLsVersion = &contextKey{"language server version"}
ctxProgressToken = &contextKey{"progress token"}
)

func missingContextErr(ctxKey *contextKey) *MissingContextErr {
Expand Down Expand Up @@ -249,3 +250,15 @@ func LanguageServerVersion(ctx context.Context) (string, bool) {
}
return version, true
}

func WithProgressToken(ctx context.Context, pt lsp.ProgressToken) context.Context {
return context.WithValue(ctx, ctxProgressToken, pt)
}

func ProgressToken(ctx context.Context) (lsp.ProgressToken, bool) {
pt, ok := ctx.Value(ctxProgressToken).(lsp.ProgressToken)
if !ok {
return "", false
}
return pt, true
}
6 changes: 6 additions & 0 deletions internal/langserver/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"context"
"sort"
"strings"
)

Expand All @@ -21,6 +22,11 @@ func (h Handlers) Names(commandPrefix string) (names []string) {
for name := range h {
names = append(names, commandPrefix+name)
}

sort.SliceStable(names, func(i, j int) bool {
return names[i] < names[j]
})

return names
}

Expand Down
56 changes: 56 additions & 0 deletions internal/langserver/handlers/command/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package command

import (
"context"
"fmt"

"github.com/creachadair/jrpc2/code"
lsctx "github.com/hashicorp/terraform-ls/internal/context"
"github.com/hashicorp/terraform-ls/internal/langserver/cmd"
ilsp "github.com/hashicorp/terraform-ls/internal/lsp"
lsp "github.com/hashicorp/terraform-ls/internal/protocol"
)

func TerraformInitHandler(ctx context.Context, args cmd.CommandArgs) (interface{}, error) {
fileUri, ok := args.GetString("uri")
if !ok || fileUri == "" {
return nil, fmt.Errorf("%w: expected uri argument to be set", code.InvalidParams.Err())
}

fh := ilsp.FileHandlerFromDocumentURI(lsp.DocumentURI(fileUri))

cf, err := lsctx.RootModuleFinder(ctx)
if err != nil {
return nil, err
}

rm, err := cf.RootModuleByPath(fh.Dir())
if err != nil {
return nil, err
}

progressBegin(ctx, "Initializing")
defer func() {
progressEnd(ctx, "Finished")
}()

progressReport(ctx, "Running terraform init ...")
err = rm.ExecuteTerraformInit(ctx)
if err != nil {
return nil, err
}

progressReport(ctx, "Detecting paths to watch ...")
paths := rm.PathsToWatch()

w, err := lsctx.Watcher(ctx)
if err != nil {
return nil, err
}
err = w.AddPaths(paths)
if err != nil {
return nil, fmt.Errorf("failed to add watch for dir (%s): %+v", fh.Dir(), err)
}

return nil, nil
}
54 changes: 54 additions & 0 deletions internal/langserver/handlers/command/progress.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package command

import (
"context"

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

func progressBegin(ctx context.Context, title string) error {
token, ok := lsctx.ProgressToken(ctx)
if !ok {
return nil
}

return jrpc2.PushNotify(ctx, "$/progress", lsp.ProgressParams{
Token: token,
Value: lsp.WorkDoneProgressBegin{
Kind: "begin",
Title: title,
},
})
}

func progressReport(ctx context.Context, message string) error {
token, ok := lsctx.ProgressToken(ctx)
if !ok {
return nil
}

return jrpc2.PushNotify(ctx, "$/progress", lsp.ProgressParams{
Token: token,
Value: lsp.WorkDoneProgressReport{
Kind: "report",
Message: message,
},
})
}

func progressEnd(ctx context.Context, message string) error {
token, ok := lsctx.ProgressToken(ctx)
if !ok {
return nil
}

return jrpc2.PushNotify(ctx, "$/progress", lsp.ProgressParams{
Token: token,
Value: lsp.WorkDoneProgressEnd{
Kind: "end",
Message: message,
},
})
}
58 changes: 39 additions & 19 deletions internal/langserver/handlers/did_open.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import (
"strings"

"github.com/creachadair/jrpc2"
"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/handlers/command"
ilsp "github.com/hashicorp/terraform-ls/internal/lsp"
lsp "github.com/hashicorp/terraform-ls/internal/protocol"
"github.com/hashicorp/terraform-ls/internal/terraform/rootmodule"
"github.com/hashicorp/terraform-ls/internal/watcher"
)

func (lh *logHandler) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpenTextDocumentParams) error {
Expand All @@ -37,11 +39,6 @@ func (lh *logHandler) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpe
return err
}

w, err := lsctx.Watcher(ctx)
if err != nil {
return err
}

rootDir, _ := lsctx.RootDirectory(ctx)
readableDir := humanReadablePath(rootDir, f.Dir())

Expand Down Expand Up @@ -83,7 +80,7 @@ func (lh *logHandler) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpe
} else if len(candidates) == 0 {
// TODO: Only notify once per f.Dir() per session
go func() {
err := askInitForEmptyRootModule(ctx, w, rootDir, f.Dir())
err := askInitForEmptyRootModule(ctx, rootDir, f)
if err != nil {
jrpc2.PushNotify(ctx, "window/showMessage", lsp.ShowMessageParams{
Type: lsp.Error,
Expand Down Expand Up @@ -138,12 +135,12 @@ func humanReadablePath(rootDir, path string) string {
return relDir
}

func askInitForEmptyRootModule(ctx context.Context, w watcher.Watcher, rootDir, dir string) error {
func askInitForEmptyRootModule(ctx context.Context, rootDir string, dh ilsp.DirHandler) error {
msg := fmt.Sprintf("No root module found for %q."+
" Functionality may be limited."+
// Unfortunately we can't be any more specific wrt where
// because we don't gather "init-able folders" in any way
" You may need to run terraform init.", humanReadablePath(rootDir, dir))
" You may need to run terraform init.", humanReadablePath(rootDir, dh.Dir()))
title := "terraform init"
resp, err := jrpc2.PushCall(ctx, "window/showMessageRequest", lsp.ShowMessageRequestParams{
Type: lsp.Info,
Expand All @@ -162,21 +159,44 @@ func askInitForEmptyRootModule(ctx context.Context, w watcher.Watcher, rootDir,
return fmt.Errorf("unmarshal MessageActionItem: %+v", err)
}
if action.Title == title {
rmm, err := lsctx.RootModuleManager(ctx)
ctx, err := initiateProgress(ctx)
if err != nil {
return err
}

rm, err := rmm.InitAndUpdateRootModule(ctx, dir)
if err != nil {
return fmt.Errorf("failed to init root module %+v", err)
}

paths := rm.PathsToWatch()
err = w.AddPaths(paths)
_, err = command.TerraformInitHandler(ctx, cmd.CommandArgs{
"uri": dh.URI(),
})
if err != nil {
return fmt.Errorf("failed to add watch for dir (%s): %+v", dir, err)
return fmt.Errorf("Initialization failed: %w", err)
}
return nil
}
return nil
}

func initiateProgress(ctx context.Context) (context.Context, error) {
cc, err := lsctx.ClientCapabilities(ctx)
if err != nil {
return ctx, err
}

if !cc.Window.WorkDoneProgress {
// server-side reporting not supported
return ctx, nil
}

id, err := uuid.NewUUID()
if err != nil {
return nil, err
}
token := lsp.ProgressToken(id.String())

_, err = jrpc2.PushCall(ctx, "window/workDoneProgress/create", lsp.WorkDoneProgressCreateParams{
Token: token,
})
if err == nil {
return lsctx.WithProgressToken(ctx, token), nil
}

return ctx, err
}
9 changes: 8 additions & 1 deletion internal/langserver/handlers/execute_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import (
)

var handlers = cmd.Handlers{
cmd.Name("rootmodules"): command.RootModulesHandler,
cmd.Name("rootmodules"): command.RootModulesHandler,
cmd.Name("terraform.init"): command.TerraformInitHandler,
}

func (lh *logHandler) WorkspaceExecuteCommand(ctx context.Context, params lsp.ExecuteCommandParams) (interface{}, error) {
Expand All @@ -28,5 +29,11 @@ func (lh *logHandler) WorkspaceExecuteCommand(ctx context.Context, params lsp.Ex
if !ok {
return nil, fmt.Errorf("%w: command handler not found for %q", code.MethodNotFound.Err(), params.Command)
}

pt, ok := params.WorkDoneToken.(lsp.ProgressToken)
if ok {
ctx = lsctx.WithProgressToken(ctx, pt)
}

return handler(ctx, cmd.ParseCommandArgs(params.Arguments))
}
Loading

0 comments on commit 586ff89

Please sign in to comment.