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

settings: Support relative paths to root modules #246

Merged
merged 1 commit into from
Jul 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion docs/SETTINGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ The language server supports the following configuration options:
## `rootModulePaths` (`[]string`)

This allows overriding automatic root module discovery by passing a static list
of absolute paths to root modules (i.e. folders with `*.tf` files
of absolute or relative paths to root modules (i.e. folders with `*.tf` files
which have been `terraform init`-ed).

Relative paths are resolved relative to the directory opened in the editor.

Path separators are converted automatically to the match separators
of the target platform (e.g. `\` on Windows, or `/` on Unix),
symlinks are followed and trailing slashes automatically removed.

## How to pass settings

The server expects static settings to be passed as part of LSP `initialize` call,
Expand Down
14 changes: 1 addition & 13 deletions internal/settings/settings.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
package settings

import (
"fmt"
"path/filepath"

"github.com/hashicorp/go-multierror"
"github.com/mitchellh/mapstructure"
)

Expand All @@ -19,15 +15,7 @@ type Options struct {
}

func (o *Options) Validate() error {
var result *multierror.Error

for _, p := range o.RootModulePaths {
if !filepath.IsAbs(p) {
result = multierror.Append(result, fmt.Errorf("%q is not an absolute path", p))
}
}

return result.ErrorOrNil()
return nil
}

type DecodedOptions struct {
Expand Down
12 changes: 0 additions & 12 deletions internal/settings/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,3 @@ func TestDecodeOptions_success(t *testing.T) {
t.Fatalf("options mismatch: %s", diff)
}
}

func TestDecodedOptions_Validate(t *testing.T) {
opts := &Options{
RootModulePaths: []string{
"./relative/path",
},
}
err := opts.Validate()
if err == nil {
t.Fatal("expected relative path to fail validation")
}
}
42 changes: 27 additions & 15 deletions langserver/handlers/did_open.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package handlers
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/creachadair/jrpc2"
Expand Down Expand Up @@ -36,31 +36,32 @@ func (lh *logHandler) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpe
}

rootDir, _ := lsctx.RootDirectory(ctx)
dir := relativePath(rootDir, f.Dir())

readableDir := humanReadablePath(rootDir, f.Dir())
candidates := cf.RootModuleCandidatesByPath(f.Dir())

if walker.IsWalking() {
// avoid raising false warnings if walker hasn't finished yet
lh.logger.Printf("walker has not finished walking yet, data may be inaccurate for %s", f.FullPath())
} else if len(candidates) == 0 {
// TODO: Only notify once per f.Dir() per session
msg := fmt.Sprintf("No root module found for %s."+
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"+
" and reload your editor.", dir)
" and reload your editor.", readableDir)
return jrpc2.ServerPush(ctx, "window/showMessage", lsp.ShowMessageParams{
Type: lsp.MTWarning,
Message: msg,
})
}
if len(candidates) > 1 {
candidateDir := humanReadablePath(rootDir, candidates[0].Path())

msg := fmt.Sprintf("Alternative root modules found for %s (%s), picked: %s."+
" You can try setting paths to root modules explicitly in settings.",
dir, candidatePaths(rootDir, candidates[1:]),
relativePath(rootDir, candidates[0].Path()))
readableDir, candidatePaths(rootDir, candidates[1:]),
candidateDir)
return jrpc2.ServerPush(ctx, "window/showMessage", lsp.ShowMessageParams{
Type: lsp.MTWarning,
Message: msg,
Expand All @@ -73,17 +74,28 @@ func (lh *logHandler) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpe
func candidatePaths(rootDir string, candidates []rootmodule.RootModule) string {
paths := make([]string, len(candidates))
for i, rm := range candidates {
// This helps displaying shorter, but still relevant paths
paths[i] = relativePath(rootDir, rm.Path())
paths[i] = humanReadablePath(rootDir, rm.Path())
}
return strings.Join(paths, ", ")
}

func relativePath(rootDir string, path string) string {
trimmed := strings.TrimPrefix(
strings.TrimPrefix(path, rootDir), string(os.PathSeparator))
if trimmed == "" {
return "."
// humanReadablePath helps displaying shorter, but still relevant paths
func humanReadablePath(rootDir, path string) string {
if rootDir == "" {
return path
}

// absolute paths can be too long for UI/messages,
// so we just display relative to root dir
relDir, err := filepath.Rel(rootDir, path)
if err != nil {
return path
}

if relDir == "." {
// Name of the root dir is more helpful than "."
return filepath.Base(rootDir)
}
return trimmed

return relDir
}
17 changes: 17 additions & 0 deletions langserver/handlers/did_open_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package handlers

import (
"fmt"
"os"
"path/filepath"
"testing"

"github.com/hashicorp/terraform-ls/langserver"
Expand All @@ -24,3 +26,18 @@ func TestLangServer_didOpenWithoutInitialization(t *testing.T) {
}
}`, TempDir(t).URI())}, session.SessionNotInitialized.Err())
}

func TestHumanReadablePath(t *testing.T) {
fh := TempDir(t)

err := os.Mkdir(filepath.Join(fh.Dir(), "testDir"), os.ModeDir)
if err != nil {
t.Fatal(err)
}

expectedPath := "testDir"
path := humanReadablePath(fh.Dir(), "testDir")
if path != expectedPath {
t.Fatalf("expected %q, given: %q", expectedPath, path)
}
}
24 changes: 19 additions & 5 deletions langserver/handlers/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package handlers
import (
"context"
"fmt"
"path/filepath"

"github.com/creachadair/jrpc2"
lsctx "github.com/hashicorp/terraform-ls/internal/context"
Expand Down Expand Up @@ -36,7 +37,8 @@ func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParam
return serverCaps, fmt.Errorf("URI %q is not valid", params.RootURI)
}

err := lsctx.SetRootDirectory(ctx, fh.FullPath())
rootDir := fh.FullPath()
err := lsctx.SetRootDirectory(ctx, rootDir)
if err != nil {
return serverCaps, err
}
Expand Down Expand Up @@ -80,7 +82,11 @@ func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParam
// Static user-provided paths take precedence over dynamic discovery
if len(cfgOpts.RootModulePaths) > 0 {
lh.logger.Printf("Attempting to add %d static root module paths", len(cfgOpts.RootModulePaths))
for _, rmPath := range cfgOpts.RootModulePaths {
for _, rawPath := range cfgOpts.RootModulePaths {
rmPath, err := resolvePath(rootDir, rawPath)
if err != nil {
return serverCaps, err
}
rm, err := addAndLoadRootModule(rmPath)
if err != nil {
return serverCaps, err
Expand All @@ -106,9 +112,8 @@ func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParam

// Walker runs asynchronously so we're intentionally *not*
// passing the request context here
ctx = context.Background()

err = walker.StartWalking(ctx, fh.Dir(), func(ctx context.Context, dir string) error {
bCtx := context.Background()
err = walker.StartWalking(bCtx, fh.Dir(), func(ctx context.Context, dir string) error {
lh.logger.Printf("Adding root module: %s", dir)
rm, err := rmm.AddAndStartLoadingRootModule(ctx, dir)
if err != nil {
Expand All @@ -127,3 +132,12 @@ func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParam

return serverCaps, err
}

func resolvePath(rootDir, rawPath string) (string, error) {
if filepath.IsAbs(rawPath) {
return filepath.EvalSymlinks(rawPath)
}

path := filepath.Join(rootDir, rawPath)
return filepath.EvalSymlinks(path)
}