Skip to content

Commit

Permalink
langserver: Implement textDocument/hover (#294)
Browse files Browse the repository at this point in the history
* Bump hcl-lang to latest revision

* langserver: Implement textDocument/hover
  • Loading branch information
radeksimko authored Nov 13, 2020
1 parent bf37e29 commit c477bf9
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 4 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/google/go-cmp v0.5.1
github.com/hashicorp/go-multierror v1.1.0
github.com/hashicorp/go-version v1.2.1
github.com/hashicorp/hcl-lang v0.0.0-20201112193825-eb220f038552
github.com/hashicorp/hcl-lang v0.0.0-20201113080530-b0668b270b47
github.com/hashicorp/hcl/v2 v2.6.0
github.com/hashicorp/terraform-exec v0.11.1-0.20201007122305-ea2094d52cb5
github.com/hashicorp/terraform-json v0.6.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl-lang v0.0.0-20201110071249-4e412924f52b h1:EjnMRaTQlomBMNRQfyWoLEg9IdqxeN1R2mb3ZZetCBs=
github.com/hashicorp/hcl-lang v0.0.0-20201110071249-4e412924f52b/go.mod h1:vd3BPEDWrYMAgAnB0MRlBdZknrpUXf8Jk2PNaHIbwhg=
github.com/hashicorp/hcl-lang v0.0.0-20201112193825-eb220f038552 h1:JA1B6R+7yNbBxwZsOFy50EWfWKVQWTjAp5QZsSxnZbA=
github.com/hashicorp/hcl-lang v0.0.0-20201112193825-eb220f038552/go.mod h1:vd3BPEDWrYMAgAnB0MRlBdZknrpUXf8Jk2PNaHIbwhg=
github.com/hashicorp/hcl-lang v0.0.0-20201113080530-b0668b270b47 h1:n3STOLqEwfs4QWgzV0cjg4sZDSKVM4IwK38ZDVa+ljM=
github.com/hashicorp/hcl-lang v0.0.0-20201113080530-b0668b270b47/go.mod h1:vd3BPEDWrYMAgAnB0MRlBdZknrpUXf8Jk2PNaHIbwhg=
github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90=
github.com/hashicorp/hcl/v2 v2.6.0 h1:3krZOfGY6SziUXa6H9PJU6TyohHn7I+ARYnhbeNBz+o=
github.com/hashicorp/hcl/v2 v2.6.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY=
Expand Down
26 changes: 26 additions & 0 deletions internal/lsp/hover.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package lsp

import (
"github.com/hashicorp/hcl-lang/lang"
"github.com/hashicorp/terraform-ls/internal/mdplain"
"github.com/sourcegraph/go-lsp"
)

func HoverData(data *lang.HoverData, cc lsp.TextDocumentClientCapabilities) lsp.Hover {
mdSupported := cc.Hover != nil &&
len(cc.Hover.ContentFormat) > 0 &&
cc.Hover.ContentFormat[0] == "markdown"

value := data.Content.Value
if data.Content.Kind == lang.MarkdownKind && !mdSupported {
value = mdplain.Clean(value)
}

content := lsp.RawMarkedString(value)
rng := HCLRangeToLSP(data.Range)

return lsp.Hover{
Contents: []lsp.MarkedString{content},
Range: &rng,
}
}
1 change: 0 additions & 1 deletion langserver/handlers/complete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ func TestCompletion_withoutInitialization(t *testing.T) {

func TestCompletion_withValidData(t *testing.T) {
tmpDir := TempDir(t)
t.Logf("will init at %s", tmpDir.Dir())
InitPluginCache(t, tmpDir.Dir())

var testSchema tfjson.ProviderSchemas
Expand Down
1 change: 1 addition & 0 deletions langserver/handlers/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func initializeResponse(t *testing.T, commandPrefix string) string {
"openClose": true,
"change": 2
},
"hoverProvider": true,
"completionProvider": {},
"documentSymbolProvider":true,
"documentFormattingProvider":true,
Expand Down
62 changes: 62 additions & 0 deletions langserver/handlers/hover.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package handlers

import (
"context"

lsctx "github.com/hashicorp/terraform-ls/internal/context"
ilsp "github.com/hashicorp/terraform-ls/internal/lsp"
lsp "github.com/sourcegraph/go-lsp"
)

func (h *logHandler) TextDocumentHover(ctx context.Context, params lsp.TextDocumentPositionParams) (lsp.Hover, error) {
var data lsp.Hover

fs, err := lsctx.DocumentStorage(ctx)
if err != nil {
return data, err
}

cc, err := lsctx.ClientCapabilities(ctx)
if err != nil {
return data, err
}

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

file, err := fs.GetDocument(ilsp.FileHandlerFromDocumentURI(params.TextDocument.URI))
if err != nil {
return data, err
}

rm, err := rmf.RootModuleByPath(file.Dir())
if err != nil {
return data, err
}

schema, err := rmf.SchemaForPath(file.Dir())
if err != nil {
return data, err
}

d, err := rm.DecoderWithSchema(schema)
if err != nil {
return data, err
}

fPos, err := ilsp.FilePositionFromDocumentPosition(params, file)
if err != nil {
return data, err
}

h.logger.Printf("Looking for hover data at %q -> %#v", file.Filename(), fPos.Position())
hoverData, err := d.HoverAtPos(file.Filename(), fPos.Position())
h.logger.Printf("received hover data: %#v", data)
if err != nil {
return data, err
}

return ilsp.HoverData(hoverData, cc.TextDocument), nil
}
129 changes: 129 additions & 0 deletions langserver/handlers/hover_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package handlers

import (
"encoding/json"
"fmt"
"testing"

"github.com/hashicorp/go-version"
tfjson "github.com/hashicorp/terraform-json"
"github.com/hashicorp/terraform-ls/internal/terraform/exec"
"github.com/hashicorp/terraform-ls/internal/terraform/rootmodule"
"github.com/hashicorp/terraform-ls/langserver"
"github.com/hashicorp/terraform-ls/langserver/session"
"github.com/stretchr/testify/mock"
)

func TestHover_withoutInitialization(t *testing.T) {
ls := langserver.NewLangServerMock(t, NewMockSession(nil))
stop := ls.Start(t)
defer stop()

ls.CallAndExpectError(t, &langserver.CallRequest{
Method: "textDocument/hover",
ReqParams: fmt.Sprintf(`{
"textDocument": {
"uri": "%s/main.tf"
},
"position": {
"character": 0,
"line": 1
}
}`, TempDir(t).URI())}, session.SessionNotInitialized.Err())
}

func TestHover_withValidData(t *testing.T) {
tmpDir := TempDir(t)
InitPluginCache(t, tmpDir.Dir())

var testSchema tfjson.ProviderSchemas
err := json.Unmarshal([]byte(testSchemaOutput), &testSchema)
if err != nil {
t.Fatal(err)
}

ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{
RootModules: map[string]*rootmodule.RootModuleMock{
tmpDir.Dir(): {
TfExecFactory: exec.NewMockExecutor([]*mock.Call{
{
Method: "Version",
Repeatability: 1,
Arguments: []interface{}{
mock.AnythingOfType(""),
},
ReturnArguments: []interface{}{
version.Must(version.NewVersion("0.12.0")),
nil,
},
},
{
Method: "GetExecPath",
Repeatability: 1,
ReturnArguments: []interface{}{
"",
},
},
{
Method: "ProviderSchemas",
Repeatability: 1,
Arguments: []interface{}{
mock.AnythingOfType(""),
},
ReturnArguments: []interface{}{
&testSchema,
nil,
},
},
}),
},
}}))
stop := ls.Start(t)
defer stop()

ls.Call(t, &langserver.CallRequest{
Method: "initialize",
ReqParams: fmt.Sprintf(`{
"capabilities": {},
"rootUri": %q,
"processId": 12345
}`, TempDir(t).URI())})
ls.Notify(t, &langserver.CallRequest{
Method: "initialized",
ReqParams: "{}",
})
ls.Call(t, &langserver.CallRequest{
Method: "textDocument/didOpen",
ReqParams: fmt.Sprintf(`{
"textDocument": {
"version": 0,
"languageId": "terraform",
"text": "provider \"test\" {\n\n}\n",
"uri": "%s/main.tf"
}
}`, TempDir(t).URI())})

ls.CallAndExpectResponse(t, &langserver.CallRequest{
Method: "textDocument/hover",
ReqParams: fmt.Sprintf(`{
"textDocument": {
"uri": "%s/main.tf"
},
"position": {
"character": 3,
"line": 0
}
}`, TempDir(t).URI())}, `{
"jsonrpc": "2.0",
"id": 3,
"result": {
"contents": [
"provider Block\n\nA provider block is used to specify a provider configuration"
],
"range": {
"start": { "line":0, "character":0 },
"end": { "line":0, "character":8 }
}
}
}`)
}
1 change: 1 addition & 0 deletions langserver/handlers/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParam
CompletionProvider: &lsp.CompletionOptions{
ResolveProvider: false,
},
HoverProvider: true,
DocumentFormattingProvider: true,
DocumentSymbolProvider: true,
},
Expand Down
12 changes: 12 additions & 0 deletions langserver/handlers/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,18 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {

return handle(ctx, req, lh.TextDocumentComplete)
},
"textDocument/hover": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) {
err := session.CheckInitializationIsConfirmed()
if err != nil {
return nil, err
}

ctx = lsctx.WithDocumentStorage(ctx, fs)
ctx = lsctx.WithClientCapabilities(ctx, cc)
ctx = lsctx.WithRootModuleFinder(ctx, svc.modMgr)

return handle(ctx, req, lh.TextDocumentHover)
},
"textDocument/formatting": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) {
err := session.CheckInitializationIsConfirmed()
if err != nil {
Expand Down

0 comments on commit c477bf9

Please sign in to comment.