Skip to content

Commit

Permalink
WIP: any expr
Browse files Browse the repository at this point in the history
  • Loading branch information
dbanck committed Feb 14, 2023
1 parent f657464 commit 045d9c0
Showing 1 changed file with 189 additions and 4 deletions.
193 changes: 189 additions & 4 deletions decoder/expr_any.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ package decoder

import (
"context"
"fmt"
"log"
"sort"
"strings"

"github.com/hashicorp/hcl-lang/lang"
"github.com/hashicorp/hcl-lang/reference"
"github.com/hashicorp/hcl-lang/schema"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
)

type Any struct {
Expand All @@ -16,17 +21,157 @@ type Any struct {
}

func (a Any) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candidate {
// TODO
return nil
log.Printf("expression: %#v", a.expr)
if a.expr == nil || isEmptyExpression(a.expr) {
editRange := hcl.Range{
Filename: a.expr.Range().Filename,
Start: pos,
End: pos,
}

candidates := make([]lang.Candidate, 0)
candidates = append(candidates, a.matchingFunctions("", editRange)...) // TODO? create a function call expr
candidates = append(candidates, newExpression(a.pathCtx, a.expr, schema.Reference{OfType: a.cons.OfType}).CompletionAtPos(ctx, pos)...)

return candidates
}

switch e := a.expr.(type) {
case *hclsyntax.FunctionCallExpr:
if e.NameRange.ContainsPos(pos) {
prefixLen := pos.Byte - e.NameRange.Start.Byte
prefix := e.Name[0:prefixLen]
editRange := e.Range()
return a.matchingFunctions(prefix, editRange)
}

f, ok := a.pathCtx.Functions[e.Name]
if ok {
parensRange := hcl.RangeBetween(e.OpenParenRange, e.CloseParenRange)
if parensRange.ContainsPos(pos) {
fParamsLength := len(f.Params)
// Function accepts no parameters
if fParamsLength == 0 && f.VarParam == nil {
return []lang.Candidate{}
}

var activeParameterIdx int
var found bool
activeParameterIdx, found = searchActiveParameterByArgs(e.Args, pos)
if !found {
activeParameterIdx, found = searchActiveParameterByBytes(a.pathCtx, e.OpenParenRange, pos)
}
if !found {
return []lang.Candidate{}
}

// arg := e.Args[activeParameterIdx]
// check the length

// In case of a variadic parameter there might be more
// arguments supplied to the function than parameters
// of the signature
if activeParameterIdx >= fParamsLength {
// If we're not dealing with a variadic parameter, we
// can't suggest anything and the user is supplying too
// many arguments to the function
if f.VarParam == nil {
return []lang.Candidate{}
}

// TODO! decide when to use empty expr
return newExpression(
a.pathCtx,
newEmptyExpressionAtPos(a.expr.Range().Filename, pos),
schema.AnyExpression{OfType: f.VarParam.Type},
).CompletionAtPos(ctx, pos)
}

if fParamsLength > 0 {
activeParameter := f.Params[activeParameterIdx]

return newExpression(
a.pathCtx,
newEmptyExpressionAtPos(a.expr.Range().Filename, pos),
schema.AnyExpression{OfType: activeParameter.Type},
).CompletionAtPos(ctx, pos)
}
}
}

// TODO? handle TypeDeclarationExpr -- probably not

return []lang.Candidate{}
}

return []lang.Candidate{}
}

func searchActiveParameterByArgs(args []hclsyntax.Expression, pos hcl.Pos) (int, bool) {
for i, v := range args {
if v.Range().ContainsPos(pos) {
return i, true
}
}

return 0, false
}

func searchActiveParameterByBytes(pathCtx *PathContext, openParenRange hcl.Range, pos hcl.Pos) (int, bool) {
f, ok := pathCtx.Files[openParenRange.Filename]
if !ok {
return 0, false
}

rangeToPos := hcl.Range{
Filename: openParenRange.Filename,
Start: openParenRange.Start,
End: pos,
}

if !rangeToPos.CanSliceBytes(f.Bytes) {
return 0, false
}

b := rangeToPos.SliceBytes(f.Bytes)
commasUntilPos := strings.Count(string(b), ",")

return commasUntilPos, true
}

func (a Any) HoverAtPos(ctx context.Context, pos hcl.Pos) *lang.HoverData {
// TODO
switch e := a.expr.(type) {
case *hclsyntax.FunctionCallExpr:
f, ok := a.pathCtx.Functions[e.Name]
if ok {
return &lang.HoverData{
// We could use a code block here, but the HCL highlighting of
// type names conflicts with the naming of function parameters
// e.g. a parameter with the name `list``
Content: lang.Markdown(fmt.Sprintf("`%s(%s) %s`\n\n%s", e.Name, f.ParameterSignature(), f.ReturnType.FriendlyName(), f.Description)),
Range: a.expr.Range(),
}
}
}

return nil
}

func (a Any) SemanticTokens(ctx context.Context) []lang.SemanticToken {
// TODO
switch e := a.expr.(type) {
case *hclsyntax.FunctionCallExpr:
_, ok := a.pathCtx.Functions[e.Name]
if ok {
return []lang.SemanticToken{
{
Type: lang.TokenAttrName,
Modifiers: []lang.SemanticTokenModifier{},
// Range: eType.Range(),
},
}
}
}

return nil
}

Expand All @@ -39,3 +184,43 @@ func (a Any) ReferenceTargets(ctx context.Context, addr lang.Address, addrCtx Ad
// TODO
return nil
}

func (a Any) matchingFunctions(prefix string, editRange hcl.Range) []lang.Candidate {
candidates := make([]lang.Candidate, 0)

for name, f := range a.pathCtx.Functions {
// Only suggest functions that have a matching return type
// TODO? what about dynamic
if a.cons.OfType != f.ReturnType {
continue
}

candidates = append(candidates, lang.Candidate{
Label: name,
Detail: fmt.Sprintf("%s(%s) %s", name, f.ParameterSignature(), f.ReturnType.FriendlyName()),
Kind: lang.FunctionCandidateKind,
Description: lang.Markdown(f.Description),
TextEdit: lang.TextEdit{
NewText: fmt.Sprintf("%s()", name),
Snippet: fmt.Sprintf("%s(${0})", name), // TODO? improve with parameter information
Range: editRange,
},
})
}

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

return candidates
}

func (a Any) matchingTraversal(editRange hcl.Range) []lang.Candidate {
candidates := make([]lang.Candidate, 0)

if a.pathCtx.ReferenceTargets == nil {
return candidates
}

return candidates
}

0 comments on commit 045d9c0

Please sign in to comment.