diff --git a/go.mod b/go.mod index c0bcdfa..01f7a6c 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/creack/pty v1.1.9 // indirect github.com/davecgh/go-spew v1.1.1 github.com/go-errors/errors v1.0.1 // indirect + github.com/go-language-server/uri v0.2.0 github.com/gogo/protobuf v1.2.1 // indirect github.com/gruntwork-io/terragrunt v0.21.13 github.com/hashicorp/go-hclog v0.9.0 // indirect diff --git a/go.sum b/go.sum index 5857758..550d0b7 100644 --- a/go.sum +++ b/go.sum @@ -109,6 +109,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-language-server/uri v0.2.0 h1:zkFlKR0EYmCg3076hwVbM9/+/Ugr8vMQvQQgoS2JpTo= +github.com/go-language-server/uri v0.2.0/go.mod h1:1wEq7lT5PmX5uljJuQJeRxdW06jr5VfA1aR1FKe2/gc= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= @@ -137,6 +139,8 @@ github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.2-0.20190829225427-b1c9c4891a65 h1:B3yqxlLHBEoav+FDQM8ph7IIRA6AhQ70w119k3hoT2Y= +github.com/google/go-cmp v0.3.2-0.20190829225427-b1c9c4891a65/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -473,6 +477,7 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522 h1:bhOzK9QyoD0ogCnFro1m2mz41+Ib0oOhfJnBp5MR4K4= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= diff --git a/langserver/complete.go b/langserver/complete.go index 57dbef4..cbcf2bc 100644 --- a/langserver/complete.go +++ b/langserver/complete.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/lang" lsp "github.com/sourcegraph/go-lsp" - "net/url" "path/filepath" "reflect" "regexp" @@ -25,16 +24,19 @@ func TextDocumentComplete(ctx context.Context, vs lsp.CompletionParams) (lsp.Com //log.Println(tfstructs.Clients) parser := configs.NewParser(memfs.MemFs) - fileURL := strings.Replace(string(vs.TextDocument.URI), "file://", "", 1) + uri, err := absolutePath(string(vs.TextDocument.URI)) + if err != nil { + return lsp.CompletionList{}, err + } + fileURL := uri.Filename() - decodedFileURL, _ := url.QueryUnescape(fileURL) - fileDir := filepath.Dir(decodedFileURL) + fileDir := filepath.Dir(fileURL) res, _ := filepath.Glob(fileDir + "/*.tf") var file *configs.File var resultFiles []*configs.File for _, v := range res { - if strings.ToLower(decodedFileURL) == strings.ToLower(v) { + if strings.ToLower(fileURL) == strings.ToLower(v) { continue } diff --git a/langserver/did_change.go b/langserver/did_change.go index d5cbf48..2d48941 100644 --- a/langserver/did_change.go +++ b/langserver/did_change.go @@ -4,7 +4,6 @@ import ( "context" "github.com/juliosueiras/terraform-lsp/tfstructs" lsp "github.com/sourcegraph/go-lsp" - "strings" ) func TextDocumentDidChange(ctx context.Context, vs lsp.DidChangeTextDocumentParams) error { @@ -12,7 +11,12 @@ func TextDocumentDidChange(ctx context.Context, vs lsp.DidChangeTextDocumentPara tempFile.Seek(0, 0) tempFile.Write([]byte(vs.ContentChanges[0].Text)) - fileURL := strings.Replace(string(vs.TextDocument.URI), "file://", "", 1) + uri, err := absolutePath(string(vs.TextDocument.URI)) + if err != nil { + return err + } + fileURL := uri.Filename() + DiagsFiles[fileURL] = tfstructs.GetDiagnostics(tempFile.Name(), fileURL) TextDocumentPublishDiagnostics(Server, ctx, lsp.PublishDiagnosticsParams{ diff --git a/langserver/did_open.go b/langserver/did_open.go index bc36ec5..7eae969 100644 --- a/langserver/did_open.go +++ b/langserver/did_open.go @@ -4,11 +4,15 @@ import ( "context" "github.com/juliosueiras/terraform-lsp/tfstructs" lsp "github.com/sourcegraph/go-lsp" - "strings" ) func TextDocumentDidOpen(ctx context.Context, vs lsp.DidOpenTextDocumentParams) error { - fileURL := strings.Replace(string(vs.TextDocument.URI), "file://", "", 1) + uri, err := absolutePath(string(vs.TextDocument.URI)) + if err != nil { + return err + } + fileURL := uri.Filename() + DiagsFiles[fileURL] = tfstructs.GetDiagnostics(fileURL, fileURL) TextDocumentPublishDiagnostics(Server, ctx, lsp.PublishDiagnosticsParams{ diff --git a/langserver/document_link.go b/langserver/document_link.go new file mode 100644 index 0000000..5cfb4fc --- /dev/null +++ b/langserver/document_link.go @@ -0,0 +1,43 @@ +package langserver + +import ( + "context" + lsp "github.com/sourcegraph/go-lsp" + log "github.com/sirupsen/logrus" +) + +type documentLinkParams struct { + TextDocument lsp.TextDocumentItem `json:"textDocument"` +} + +type DocumentLink struct { + Range lsp.Range `json:"range"` + + /** + * The uri this link points to. If missing a resolve request is sent later. + */ + Target string `json:"target"` + + Tooltip string `json:"tooltip"` +} + +func TextDocumentDocumentLink(ctx context.Context, vs documentLinkParams) ([]DocumentLink, error) { + log.Info(vs) + + return []DocumentLink{ + DocumentLink{ + Range: lsp.Range{ + Start: lsp.Position{ + Line: 1, + Character: 1, + }, + End: lsp.Position{ + Line: 1, + Character: 10, + }, + }, + Target: "https://github.com", + Tooltip: "https://github.com", + }, + }, nil +} diff --git a/langserver/initialize.go b/langserver/initialize.go index 252db73..d7628fd 100644 --- a/langserver/initialize.go +++ b/langserver/initialize.go @@ -8,7 +8,22 @@ import ( "github.com/spf13/afero" ) -func Initialize(ctx context.Context, vs lsp.InitializeParams) (lsp.InitializeResult, error) { +type DocumentLinkOptions struct { + ResolveProvider bool `json:"resolveProvider,omitempty"` +} + +type ExtendedServerCapabilities struct { + TextDocumentSync *lsp.TextDocumentSyncOptionsOrKind `json:"textDocumentSync,omitempty"` + CompletionProvider *lsp.CompletionOptions `json:"completionProvider,omitempty"` + HoverProvider bool `json:"hoverProvider,omitempty"` + DocumentLinkProvider *DocumentLinkOptions `json:"documentLinkProvider,omitempty"` +} + +type ExtendedInitializeResult struct { + Capabilities ExtendedServerCapabilities `json:"capabilities"` +} + +func Initialize(ctx context.Context, vs lsp.InitializeParams) (ExtendedInitializeResult, error) { file, err := afero.TempFile(memfs.MemFs, "", "tf-lsp-") if err != nil { log.Fatal(err) @@ -16,8 +31,8 @@ func Initialize(ctx context.Context, vs lsp.InitializeParams) (lsp.InitializeRes //defer os.Remove(file.Name()) tempFile = file - return lsp.InitializeResult{ - Capabilities: lsp.ServerCapabilities{ + return ExtendedInitializeResult{ + Capabilities: ExtendedServerCapabilities{ TextDocumentSync: &lsp.TextDocumentSyncOptionsOrKind{ Options: &lsp.TextDocumentSyncOptions{ OpenClose: true, @@ -29,6 +44,9 @@ func Initialize(ctx context.Context, vs lsp.InitializeParams) (lsp.InitializeRes TriggerCharacters: []string{"."}, }, HoverProvider: false, + DocumentLinkProvider: &DocumentLinkOptions{ + ResolveProvider: false, + }, }, }, nil } diff --git a/langserver/main.go b/langserver/main.go index 374aa3d..aabbbfb 100644 --- a/langserver/main.go +++ b/langserver/main.go @@ -12,6 +12,7 @@ func CreateServer() *jrpc2.Server { "textDocument/didChange": handler.New(TextDocumentDidChange), "textDocument/didOpen": handler.New(TextDocumentDidOpen), "textDocument/didClose": handler.New(TextDocumentDidClose), + "textDocument/documentLink": handler.New(TextDocumentDocumentLink), //"textDocument/hover": handler.New(TextDocumentHover), //"textDocument/references": handler.New(TextDocumentReferences), //"textDocument/codeLens": handler.New(TextDocumentCodeLens), diff --git a/langserver/uri.go b/langserver/uri.go new file mode 100644 index 0000000..0a00cf2 --- /dev/null +++ b/langserver/uri.go @@ -0,0 +1,9 @@ +package langserver + +import ( + "github.com/go-language-server/uri" +) + +func absolutePath(in string) (uri.URI, error) { + return uri.Parse(string(in)) +} diff --git a/langserver/uri_test.go b/langserver/uri_test.go new file mode 100644 index 0000000..b5f6f1d --- /dev/null +++ b/langserver/uri_test.go @@ -0,0 +1,33 @@ +package langserver + +import ( + "testing" +) + +func TestAbsolutePath(t *testing.T) { + tests := []struct { + path string + expected string + }{ + { + path: "file:///C:/Users/acmeuser/git/tf-test/main.tf", + expected: "C:/Users/acmeuser/git/tf-test/main.tf", + }, + { + path: "file:///etc/fstab", + expected: "/etc/fstab", + }, + } + + for _, test := range tests { + uri, err := absolutePath(test.path) + if err != nil { + t.Errorf("Parsing path '%s' resulted in error: %s", test.path, err) + } + res := uri.Filename() + + if res != test.expected { + t.Errorf("Unexpected result. Got '%s', Expected '%s'", res, test.expected) + } + } +} diff --git a/tfstructs/diags.go b/tfstructs/diags.go index f867fed..4af2453 100644 --- a/tfstructs/diags.go +++ b/tfstructs/diags.go @@ -6,9 +6,6 @@ import ( v2 "github.com/hashicorp/hcl/v2" "github.com/hashicorp/terraform/configs" "github.com/zclconf/go-cty/cty" - "net/url" - "strings" - "unicode/utf8" //"github.com/juliosueiras/terraform-lsp/helper" terragruntConfig "github.com/gruntwork-io/terragrunt/config" terragruntOptions "github.com/gruntwork-io/terragrunt/options" @@ -26,16 +23,6 @@ func GetDiagnostics(fileName string, originalFile string) []lsp.Diagnostic { result := make([]lsp.Diagnostic, 0) originalFileName := originalFile - originalFileNameDecoded, _ := url.QueryUnescape(originalFileName) - - if strings.Contains(originalFileNameDecoded, ":/") { - s, i := utf8.DecodeRuneInString("/") - if []rune(originalFileNameDecoded)[0] == s { - // https://stackoverflow.com/questions/48798588/how-do-you-remove-the-first-character-of-a-string - originalFileNameDecoded = originalFileNameDecoded[i:] - } - } - if exist, _ := afero.Exists(memfs.MemFs, fileName); !exist { return result } @@ -133,15 +120,8 @@ func GetDiagnostics(fileName string, originalFile string) []lsp.Diagnostic { resourceTypes[v.Type][v.Name] = cty.DynamicVal } - if strings.Contains(originalFileNameDecoded, "\\") { - s, i := utf8.DecodeRuneInString("\\") - if []rune(originalFileNameDecoded)[0] == s { - // https://stackoverflow.com/questions/48798588/how-do-you-remove-the-first-character-of-a-string - originalFileNameDecoded = originalFileNameDecoded[i:] - } - } + targetDir := filepath.Dir(originalFileName) - targetDir := filepath.Dir(originalFileNameDecoded) resultedDir := "" searchLevel := 4 for dir := targetDir; dir != "" && searchLevel != 0; dir = filepath.Dir(dir) { @@ -154,8 +134,8 @@ func GetDiagnostics(fileName string, originalFile string) []lsp.Diagnostic { variables := map[string]cty.Value{ "path": cty.ObjectVal(map[string]cty.Value{ - "cwd": cty.StringVal(filepath.Dir(originalFileNameDecoded)), - "module": cty.StringVal(filepath.Dir(originalFileNameDecoded)), + "cwd": cty.StringVal(filepath.Dir(originalFileName)), + "module": cty.StringVal(filepath.Dir(originalFileName)), "root": cty.StringVal(resultedDir), }), "var": cty.DynamicVal, // Need to check for undefined vars @@ -219,7 +199,7 @@ func GetDiagnostics(fileName string, originalFile string) []lsp.Diagnostic { } for _, local := range cfg.Locals { - diags := GetLocalsForDiags(*local, filepath.Dir(originalFileNameDecoded), variables) + diags := GetLocalsForDiags(*local, filepath.Dir(originalFileName), variables) if diags != nil { for _, diag := range diags { @@ -269,7 +249,7 @@ func GetDiagnostics(fileName string, originalFile string) []lsp.Diagnostic { for _, v := range cfg.ProviderConfigs { providerType := v.Name - tfSchema := GetProviderSchemaForDiags(providerType, v.Config, filepath.Dir(originalFileNameDecoded), variables) + tfSchema := GetProviderSchemaForDiags(providerType, v.Config, filepath.Dir(originalFileName), variables) if tfSchema != nil { for _, diag := range tfSchema.Diags { @@ -316,7 +296,7 @@ func GetDiagnostics(fileName string, originalFile string) []lsp.Diagnostic { providerType = v.ProviderConfigRef.Name } - tfSchema := GetResourceSchemaForDiags(resourceType, v.Config, filepath.Dir(originalFileNameDecoded), providerType, variables) + tfSchema := GetResourceSchemaForDiags(resourceType, v.Config, filepath.Dir(originalFileName), providerType, variables) if tfSchema != nil { for _, diag := range tfSchema.Diags { @@ -362,7 +342,7 @@ func GetDiagnostics(fileName string, originalFile string) []lsp.Diagnostic { providerType = v.ProviderConfigRef.Name } - tfSchema := GetDataSourceSchemaForDiags(resourceType, v.Config, filepath.Dir(originalFileNameDecoded), providerType, variables) + tfSchema := GetDataSourceSchemaForDiags(resourceType, v.Config, filepath.Dir(originalFileName), providerType, variables) if tfSchema != nil { for _, diag := range tfSchema.Diags { diff --git a/tfstructs/provider.go b/tfstructs/provider.go index c1673ef..31284d5 100644 --- a/tfstructs/provider.go +++ b/tfstructs/provider.go @@ -3,7 +3,6 @@ package tfstructs import ( "fmt" - "unicode/utf8" log "github.com/sirupsen/logrus" "go/build" "io/ioutil" @@ -189,14 +188,6 @@ func pluginDirs(targetDir string) ([]string, error) { searchLevel := 4 - if strings.Contains(targetDir, "\\") { - s, i := utf8.DecodeRuneInString("\\") - if []rune(targetDir)[0] == s { - // https://stackoverflow.com/questions/48798588/how-do-you-remove-the-first-character-of-a-string - targetDir = targetDir[i:] - } - } - for dir := targetDir; dir != "" && searchLevel != 0; dir = filepath.Dir(dir) { log.Debug("[DEBUG] search .terraform dir in %s", dir) diff --git a/tfstructs/vars.go b/tfstructs/vars.go index e2b054e..a617670 100644 --- a/tfstructs/vars.go +++ b/tfstructs/vars.go @@ -1,9 +1,6 @@ package tfstructs import ( - "unicode/utf8" - "strings" - "net/url" "fmt" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" @@ -29,16 +26,9 @@ type GetVarAttributeRequest struct { func GetVarAttributeCompletion(request GetVarAttributeRequest) []lsp.CompletionItem { scope := lang.Scope{} - fileDir, _ := url.QueryUnescape(request.FileDir) - if strings.Contains(fileDir, "\\") { - s, i := utf8.DecodeRuneInString("\\") - if []rune(fileDir)[0] == s { - // https://stackoverflow.com/questions/48798588/how-do-you-remove-the-first-character-of-a-string - fileDir = fileDir[i:] - } - } - targetDir := filepath.Dir(fileDir) + targetDir := request.FileDir + resultedDir := "" searchLevel := 4 for dir := targetDir; dir != "" && searchLevel != 0; dir = filepath.Dir(dir) { @@ -49,13 +39,12 @@ func GetVarAttributeCompletion(request GetVarAttributeRequest) []lsp.CompletionI searchLevel -= 1 } - helper.DumpLog(fileDir) helper.DumpLog(targetDir) variables := map[string]cty.Value{ "path": cty.ObjectVal(map[string]cty.Value{ - "cwd": cty.StringVal(fileDir), - "module": cty.StringVal(fileDir), + "cwd": cty.StringVal(request.FileDir), + "module": cty.StringVal(request.FileDir), "root": cty.StringVal(resultedDir), }), "var": cty.DynamicVal, // Need to check for undefined vars