Skip to content

Commit

Permalink
Symbols Support (#265)
Browse files Browse the repository at this point in the history
* PoC

* Plumbed data through interfaces and calculated proper range

* Remove unneeded line

* Fix initialize expected response

* comment
  • Loading branch information
appilon authored Sep 1, 2020
1 parent efad24d commit a2cdcae
Show file tree
Hide file tree
Showing 13 changed files with 201 additions and 29 deletions.
29 changes: 29 additions & 0 deletions internal/hcl/hcl.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ func (pb *parsedBlock) Tokens() hclsyntax.Tokens {
return pb.tokens
}

func (pb *parsedBlock) Range() hcl.Range {
return hcl.Range{
Filename: pb.tokens[0].Range.Filename,
Start: pb.tokens[0].Range.Start,
End: pb.tokens[len(pb.tokens)-1].Range.End,
}
}

func (pb *parsedBlock) TokenAtPosition(pos hcl.Pos) (hclsyntax.Token, error) {
for _, t := range pb.tokens {
if rangeContainsOffset(t.Range, pos.Byte) {
Expand Down Expand Up @@ -120,6 +128,27 @@ func (f *file) BlockAtPosition(pos hcl.Pos) (TokenizedBlock, error) {
return nil, &NoBlockFoundErr{pos}
}

func (f *file) Blocks() ([]TokenizedBlock, error) {
var blocks []TokenizedBlock

pf, err := f.parse()
if err != nil {
return blocks, err
}

body, ok := pf.Body.(*hclsyntax.Body)
if !ok {
return blocks, fmt.Errorf("unexpected body type (%T)", body)
}

for _, block := range body.Blocks {
dt := definitionTokens(tokensInRange(pf.Tokens, block.Range()))
blocks = append(blocks, &parsedBlock{dt})
}

return blocks, nil
}

func (f *file) TokenAtPosition(pos hcl.Pos) (hclsyntax.Token, error) {
pf, _ := f.parse()

Expand Down
2 changes: 2 additions & 0 deletions internal/hcl/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ type TokenizedFile interface {
BlockAtPosition(hcl.Pos) (TokenizedBlock, error)
TokenAtPosition(hcl.Pos) (hclsyntax.Token, error)
PosInBlock(hcl.Pos) bool
Blocks() ([]TokenizedBlock, error)
}

type TokenizedBlock interface {
TokenAtPosition(hcl.Pos) (hclsyntax.Token, error)
Tokens() hclsyntax.Tokens
Range() hcl.Range
}
2 changes: 1 addition & 1 deletion internal/lsp/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,6 @@ func textEdit(te lang.TextEdit, pos hcl.Pos) *lsp.TextEdit {

return &lsp.TextEdit{
NewText: te.NewText(),
Range: hclRangeToLSP(*rng),
Range: HCLRangeToLSP(*rng),
}
}
4 changes: 2 additions & 2 deletions internal/lsp/file_change.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ func TextEdits(changes filesystem.DocumentChanges) []lsp.TextEdit {

for i, change := range changes {
edits[i] = lsp.TextEdit{
Range: hclRangeToLSP(change.Range()),
Range: HCLRangeToLSP(change.Range()),
NewText: change.Text(),
}
}

return edits
}

func hclRangeToLSP(hclRng hcl.Range) lsp.Range {
func HCLRangeToLSP(hclRng hcl.Range) lsp.Range {
return lsp.Range{
Start: lsp.Position{
Character: hclRng.Start.Column - 1,
Expand Down
56 changes: 30 additions & 26 deletions internal/terraform/lang/datasource_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,13 @@ type datasourceBlock struct {
providerRefs addrs.ProviderReferences
}

func (r *datasourceBlock) Type() string {
return r.Labels()[0].Value
func (d *datasourceBlock) Type() string {
return d.Labels()[0].Value
}

func (r *datasourceBlock) Name() string {
firstLabel := r.Labels()[0].Value
secondLabel := r.Labels()[1].Value
func (d *datasourceBlock) Name() string {
firstLabel := d.Labels()[0].Value
secondLabel := d.Labels()[1].Value

if firstLabel == "" && secondLabel == "" {
return "<unknown>"
Expand All @@ -82,39 +82,43 @@ func (r *datasourceBlock) Name() string {
return fmt.Sprintf("%s.%s", firstLabel, secondLabel)
}

func (r *datasourceBlock) Labels() []*ParsedLabel {
if r.labels != nil {
return r.labels
func (d *datasourceBlock) Labels() []*ParsedLabel {
if d.labels != nil {
return d.labels
}

labels := ParseLabels(r.tBlock, r.labelSchema)
r.labels = labels
labels := ParseLabels(d.tBlock, d.labelSchema)
d.labels = labels

return r.labels
return d.labels
}

func (r *datasourceBlock) BlockType() string {
func (d *datasourceBlock) BlockType() string {
return "data"
}

func (r *datasourceBlock) CompletionCandidatesAtPos(pos hcl.Pos) (CompletionCandidates, error) {
if r.sr == nil {
return nil, &noSchemaReaderErr{r.BlockType()}
func (d *datasourceBlock) Range() hcl.Range {
return d.tBlock.Range()
}

func (d *datasourceBlock) CompletionCandidatesAtPos(pos hcl.Pos) (CompletionCandidates, error) {
if d.sr == nil {
return nil, &noSchemaReaderErr{d.BlockType()}
}

// We ignore diags as we assume incomplete (invalid) configuration
hclBlock, _ := hclsyntax.ParseBlockFromTokens(r.tBlock.Tokens())
hclBlock, _ := hclsyntax.ParseBlockFromTokens(d.tBlock.Tokens())

if PosInLabels(hclBlock, pos) {
dataSources, err := r.sr.DataSources()
dataSources, err := d.sr.DataSources()
if err != nil {
return nil, err
}

cl := &completableLabels{
logger: r.logger,
parsedLabels: r.Labels(),
tBlock: r.tBlock,
logger: d.logger,
parsedLabels: d.Labels(),
tBlock: d.tBlock,
labels: labelCandidates{
"type": dataSourceCandidates(dataSources),
},
Expand All @@ -123,25 +127,25 @@ func (r *datasourceBlock) CompletionCandidatesAtPos(pos hcl.Pos) (CompletionCand
return cl.completionCandidatesAtPos(pos)
}

lRef, err := parseProviderRef(hclBlock.Body.Attributes, r.Type())
lRef, err := parseProviderRef(hclBlock.Body.Attributes, d.Type())
if err != nil {
return nil, err
}

pAddr, err := lookupProviderAddress(r.providerRefs, lRef)
pAddr, err := lookupProviderAddress(d.providerRefs, lRef)
if err != nil {
return nil, err
}

rSchema, err := r.sr.DataSourceSchema(pAddr, r.Type())
rSchema, err := d.sr.DataSourceSchema(pAddr, d.Type())
if err != nil {
return nil, err
}
cb := &completableBlock{
logger: r.logger,
parsedLabels: r.Labels(),
logger: d.logger,
parsedLabels: d.Labels(),
schema: rSchema.Block,
tBlock: r.tBlock,
tBlock: d.tBlock,
}
return cb.completionCandidatesAtPos(pos)
}
Expand Down
22 changes: 22 additions & 0 deletions internal/terraform/lang/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,28 @@ func (p *parser) BlockTypeCandidates(file ihcl.TokenizedFile, pos hcl.Pos) Compl
return list
}

func (p *parser) Blocks(file ihcl.TokenizedFile) ([]ConfigBlock, error) {
blocks, err := file.Blocks()
if err != nil {
return nil, err
}

var cfgBlocks []ConfigBlock

for _, block := range blocks {
cfgBlock, err := p.ParseBlockFromTokens(block)
if err != nil {
// do not error out if parsing failed, continue to parse
// blocks that are supported
p.logger.Printf("parsing config block failed: %s", err)
continue
}
cfgBlocks = append(cfgBlocks, cfgBlock)
}

return cfgBlocks, nil
}

type completableBlockType struct {
TypeName string
LabelSchema LabelSchema
Expand Down
4 changes: 4 additions & 0 deletions internal/terraform/lang/provider_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ func (p *providerBlock) BlockType() string {
return "provider"
}

func (p *providerBlock) Range() hcl.Range {
return p.tBlock.Range()
}

func (p *providerBlock) CompletionCandidatesAtPos(pos hcl.Pos) (CompletionCandidates, error) {
if p.sr == nil {
return nil, &noSchemaReaderErr{p.BlockType()}
Expand Down
4 changes: 4 additions & 0 deletions internal/terraform/lang/resource_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ func (r *resourceBlock) BlockType() string {
return "resource"
}

func (r *resourceBlock) Range() hcl.Range {
return r.tBlock.Range()
}

func (r *resourceBlock) CompletionCandidatesAtPos(pos hcl.Pos) (CompletionCandidates, error) {
if r.sr == nil {
return nil, &noSchemaReaderErr{r.BlockType()}
Expand Down
2 changes: 2 additions & 0 deletions internal/terraform/lang/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Parser interface {
SetProviderReferences(addrs.ProviderReferences)
BlockTypeCandidates(ihcl.TokenizedFile, hcl.Pos) CompletionCandidates
CompletionCandidatesAtPos(ihcl.TokenizedFile, hcl.Pos) (CompletionCandidates, error)
Blocks(ihcl.TokenizedFile) ([]ConfigBlock, error)
}

// ConfigBlock implements an abstraction above HCL block
Expand All @@ -28,6 +29,7 @@ type ConfigBlock interface {
Name() string
BlockType() string
Labels() []*ParsedLabel
Range() hcl.Range
}

// Block represents a decoded HCL block (by a Parser)
Expand Down
2 changes: 2 additions & 0 deletions langserver/handlers/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func TestInitalizeAndShutdown(t *testing.T) {
"change": 2
},
"completionProvider": {},
"documentSymbolProvider":true,
"documentFormattingProvider":true
}
}
Expand Down Expand Up @@ -75,6 +76,7 @@ func TestEOF(t *testing.T) {
"change": 2
},
"completionProvider": {},
"documentSymbolProvider":true,
"documentFormattingProvider":true
}
}
Expand Down
1 change: 1 addition & 0 deletions langserver/handlers/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParam
ResolveProvider: false,
},
DocumentFormattingProvider: true,
DocumentSymbolProvider: true,
},
}

Expand Down
11 changes: 11 additions & 0 deletions langserver/handlers/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,17 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) {
ctx = lsctx.WithDocumentStorage(ctx, fs)
return handle(ctx, req, TextDocumentDidClose)
},
"textDocument/documentSymbol": 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.WithParserFinder(ctx, svc.modMgr)

return handle(ctx, req, lh.TextDocumentSymbol)
},
"textDocument/completion": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) {
err := session.CheckInitializationIsConfirmed()
if err != nil {
Expand Down
91 changes: 91 additions & 0 deletions langserver/handlers/symbol.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package handlers

import (
"context"
"fmt"
"time"

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

func (h *logHandler) TextDocumentSymbol(ctx context.Context, params lsp.DocumentSymbolParams) ([]lsp.SymbolInformation, error) {
var symbols []lsp.SymbolInformation

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

pf, err := lsctx.ParserFinder(ctx)
if err != nil {
return symbols, err
}

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

text, err := file.Text()
if err != nil {
return symbols, err
}

hclFile := ihcl.NewFile(file, text)

// TODO: block until it's available <-pf.ParserLoadingDone()
// requires https://github.com/hashicorp/terraform-ls/issues/8
// TODO: replace crude wait/timeout loop
var isParserLoaded bool
var elapsed time.Duration
sleepFor := 10 * time.Millisecond
maxWait := 3 * time.Second
for !isParserLoaded {
time.Sleep(sleepFor)
elapsed += sleepFor
if elapsed >= maxWait {
return symbols, fmt.Errorf("parser is not available yet for %s", file.Dir())
}
var err error
isParserLoaded, err = pf.IsParserLoaded(file.Dir())
if err != nil {
return symbols, err
}
}

p, err := pf.ParserForDir(file.Dir())
if err != nil {
return symbols, fmt.Errorf("finding compatible parser failed: %w", err)
}

blocks, err := p.Blocks(hclFile)
if err != nil {
return symbols, err
}

for _, block := range blocks {
symbols = append(symbols, lsp.SymbolInformation{
Name: symbolName(block),
Kind: lsp.SKStruct, // We don't have a great SymbolKind match for blocks
Location: lsp.Location{
Range: ilsp.HCLRangeToLSP(block.Range()),
URI: params.TextDocument.URI,
},
})
}

return symbols, nil

}

func symbolName(b lang.ConfigBlock) string {
name := b.BlockType()
for _, l := range b.Labels() {
name += "." + l.Value
}
return name
}

0 comments on commit a2cdcae

Please sign in to comment.