Skip to content

Commit

Permalink
Improve autocompletion
Browse files Browse the repository at this point in the history
- Fields autocompletion
- Function named parameters autocompletion
- Composite Literals properties autocompletion
- Local variables autocompletion
  • Loading branch information
kmichaelk committed Sep 13, 2024
1 parent 90d2d70 commit dcc5db0
Show file tree
Hide file tree
Showing 3 changed files with 530 additions and 14 deletions.
284 changes: 271 additions & 13 deletions internal/lsp/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type BehavAttrib int

// The list of behaviours.
const (
NONE BehavAttrib = iota //neither return nor runs on spec
NONE BehavAttrib = iota // neither return nor runs on spec
WITH_RETURN // only retrurn spec
WITH_RUNSON // only runs on spec
)
Expand Down Expand Up @@ -87,6 +87,7 @@ func (s *Server) completion(ctx context.Context, params *protocol.CompletionPara
if len(suites) == 0 {
return nil, fmt.Errorf("no suite found for file %q", string(params.TextDocument.URI))
}

return &protocol.CompletionList{IsIncomplete: false, Items: Complete(suites[0], pos, nodeStack, defaultModuleId)}, nil
}

Expand Down Expand Up @@ -149,8 +150,10 @@ func Complete(suite *Suite, pos int, nodes []syntax.Node, ownModName string) []p
l := len(nodes)
switch {
case isBehaviourBodyScope(nodes):
prevNode := nodes[l-2]

modName := ""
if n, ok := nodes[l-2].(*syntax.SelectorExpr); ok {
if n, ok := prevNode.(*syntax.SelectorExpr); ok {
if n.X == nil {
return nil
}
Expand All @@ -170,16 +173,36 @@ func Complete(suite *Suite, pos int, nodes []syntax.Node, ownModName string) []p

if modName != "" {
file, err := suite.FindModule(modName)
if err != nil {
log.Debugf("module %s not found\n", modName)
return nil
if err == nil {
tree := ttcn3.ParseFile(file)
return CompleteBehaviours(tree, kinds, attrs, modName, " 1")
}
tree := ttcn3.ParseFile(file)
return CompleteBehaviours(tree, kinds, attrs, modName, " 1")

expr := prevNode.(*syntax.SelectorExpr)
var tgt syntax.Expr
if expr.Sel != nil && expr.Sel.LastTok().Pos() < pos {
tgt = expr
} else {
tgt = expr.X
}
if n, ok := tgt.(*syntax.SelectorExpr); ok && pos == tgt.End() {
tgt = n.X
}
return CompleteSelectorExpr(suite, ownModName, tgt)
} else if n, ok := prevNode.(*syntax.IndexExpr); ok {
return CompleteSelectorExpr(suite, ownModName, n)
}
list = CompleteAllBehaviours(suite, kinds, attrs, ownModName)

if isArgListScope(nodes, pos) {
list = CompleteArgNames(suite, ownModName, nodes[l-2].(syntax.Expr), " 1")
if n, ok := nodes[l-2].(*syntax.BinaryExpr); ok && pos > n.Op.FirstTok().End() {
return list
}
}
list = append(list, CompleteAllLocalVariables(nodes, " 2")...)
list = append(list, CompleteAllBehaviours(suite, kinds, attrs, ownModName)...)
list = append(list, CompletePredefinedFunctions()...)
list = append(list, CompleteAllModules(suite, ownModName, " 3")...)
list = append(list, CompleteAllModules(suite, ownModName, " 4")...)
return list

case isControlBodyScope(nodes):
Expand Down Expand Up @@ -483,10 +506,15 @@ func CompleteBehaviours(tree *ttcn3.Tree, kinds []syntax.Kind, attribs []BehavAt
}
}
if isSelected {
ret = append(ret, protocol.CompletionItem{Label: v.Label + "()",
InsertText: insertText,
Kind: protocol.FunctionCompletion, SortText: sortPref + v.Label,
Detail: v.Signature, Documentation: v.Documentation, InsertTextFormat: v.TextFormat})
ret = append(ret, protocol.CompletionItem{
Label: v.Label + "()",
InsertText: insertText,
Kind: protocol.FunctionCompletion,
SortText: sortPref + v.Label,
Detail: v.Signature,
Documentation: v.Documentation,
InsertTextFormat: v.TextFormat,
})
}
}
return ret
Expand Down Expand Up @@ -740,6 +768,171 @@ func CompletePortTypes(suite *Suite, mname string, sortPref string) []protocol.C
return ret
}

type completionItemData struct {
Name string
Detail string
}

func CollectParamsNames(tgt syntax.Node) []*completionItemData {
var list []*completionItemData

switch tgt := tgt.(type) {
case *syntax.FormalPars:
for _, par := range tgt.List {
list = append(list, &completionItemData{Name: par.Name.String(), Detail: syntax.Name(par.Type)})
}
case *syntax.StructTypeDecl:
for _, field := range tgt.Fields {
list = append(list, &completionItemData{Name: field.Name.String(), Detail: syntax.Name(field.Type)})
}
}

return list
}

func CompleteParams(tgt syntax.Node, cur int, sortPref string, completeAssign bool) []protocol.CompletionItem {
var list []protocol.CompletionItem

var insertText string
if completeAssign {
insertText = "%s := ${1:}"
} else {
insertText = "%s"
}

items := CollectParamsNames(tgt)
for i := cur; i < len(items); i++ {
item := items[i]
list = append(list, protocol.CompletionItem{
Label: item.Name,
Detail: item.Detail,
Documentation: "",
Kind: protocol.EnumMemberCompletion,
SortText: sortPref + item.Name,
InsertText: fmt.Sprintf(insertText, item.Name),
InsertTextFormat: protocol.SnippetTextFormat,
})
}

return list
}

func CompleteArgNames(suite *Suite, mname string, n syntax.Expr, sortPref string) []protocol.CompletionItem {
file, err := suite.FindModule(mname)
if err != nil {
log.Debugf("module %s not found\n", mname)
return nil
}
tree := ttcn3.ParseFile(file)

completeAssignOperator := true
if _, ok := n.(*syntax.BinaryExpr); ok {
n = tree.ParentOf(n).(syntax.Expr)
completeAssignOperator = false
}

if _, ok := n.(*syntax.ParenExpr); ok {
n = tree.ParentOf(n).(syntax.Expr)
}

switch n := n.(type) {
case *syntax.CallExpr:
if defs := tree.LookupWithDB(n.Fun, suite.DB); len(defs) > 0 {
def := defs[0]
if params := getDeclarationParams(def.Node); params != nil {
return CompleteParams(params, len(n.Args.List)-1, sortPref, completeAssignOperator)
}
}
case *syntax.CompositeLiteral:
defs := tree.TypeOf(n, suite.DB)
if len(defs) > 0 {
def := defs[0]
n := def.Node
if _, ok := n.(*syntax.ListSpec); ok {
n, _ = ExtractActualType(def.Tree, suite.DB, n, 0)
}
return CompleteParams(n, 0, sortPref, completeAssignOperator)
}
}

return nil
}

func CompleteFields(suite *Suite, tree *ttcn3.Tree, wtype syntax.Node, depth int) []protocol.CompletionItem {
var ret []protocol.CompletionItem

typ, typeDepth := ExtractActualType(tree, suite.DB, wtype, 0)

switch n := typ.(type) {
case *syntax.StructTypeDecl:
if typeDepth == depth {
for _, field := range n.Fields {
ret = append(ret, protocol.CompletionItem{
Label: field.Name.String(),
Kind: protocol.FieldCompletion,
Detail: syntax.Name(field.Type),
Documentation: "",
})
}
}
}

return ret
}

func ExtractActualType(tree *ttcn3.Tree, db *ttcn3.DB, t syntax.Node, depth int) (syntax.Node, int) {
q := []*ttcn3.Node{{Node: t, Tree: tree}}

for len(q) > 0 {
c := q[0]
q = q[1:]

switch n := c.Node.(type) {
case *syntax.RefSpec:
if defs := tree.TypeOf(n, db); len(defs) > 0 {
def := defs[0]
if def.Node != t {
q = append(q, def)
continue
}
}
case *syntax.ListSpec:
q = append(q, &ttcn3.Node{Node: n.ElemType, Tree: tree})
depth++
default:
return n, depth
}
}

return nil, -1
}

func CompleteSelectorExpr(suite *Suite, mname string, expr syntax.Expr) []protocol.CompletionItem {
file, err := suite.FindModule(mname)
if err != nil {
log.Debugf("module %s not found\n", mname)
return nil
}

depth := 0
for {
if n, ok := expr.(*syntax.IndexExpr); ok {
expr = n.X
depth++
continue
}
break
}

tree := ttcn3.ParseFile(file)
defs := tree.TypeOf(expr, suite.DB)
if len(defs) == 0 {
return nil
}

return CompleteFields(suite, tree, defs[0].Node, depth)
}

func CompleteAllComponentTypes(suite *Suite, sortPref string) []protocol.CompletionItem {
var ret []protocol.CompletionItem
for _, f := range suite.Files() {
Expand Down Expand Up @@ -794,6 +987,47 @@ func CompleteAllValueDecls(suite *Suite, kind syntax.Kind) []protocol.Completion
return ret
}

func CompleteAllLocalVariables(nodes []syntax.Node, sortPref string) []protocol.CompletionItem {
var ret []protocol.CompletionItem

var funcDecl *syntax.FuncDecl = nil
for _, n := range nodes {
if n, ok := n.(*syntax.FuncDecl); ok {
funcDecl = n
break
}
}
if funcDecl == nil {
return nil
}

funcDecl.Inspect(func(n syntax.Node) bool {
if n == nil {
return false
}
switch n := n.(type) {
case *syntax.BlockStmt:
return true
case *syntax.DeclStmt:
return true
case *syntax.ValueDecl:
return true
case *syntax.Declarator:
name := n.Name.String()
ret = append(ret, protocol.CompletionItem{
Label: name,
Kind: protocol.VariableCompletion,
SortText: sortPref + name,
})
default:
return false
}
return false
})

return ret
}

func behaviourInfos(tree *ttcn3.Tree, kind syntax.Kind) []*BehaviourInfo {
var ret []*BehaviourInfo
mname := ""
Expand Down Expand Up @@ -873,6 +1107,30 @@ func baseName(name string) string {
return name
}

func isArgListScope(nodes []syntax.Node, pos int) bool {
l := len(nodes)
off := 2

if n, ok := nodes[l-off].(*syntax.BinaryExpr); ok {
if n.Op.FirstTok().Pos() < pos {
return false
}
if n.Op.String() == ":=" {
off++
}
}

if _, ok := nodes[l-off].(*syntax.ParenExpr); ok {
if _, ok := nodes[l-off-1].(*syntax.CallExpr); ok {
return true
}
} else if _, ok := nodes[l-off].(*syntax.CompositeLiteral); ok {
return true
}

return false
}

func isBehaviourBodyScope(nodes []syntax.Node) bool {
insideBehav := false
for _, node := range nodes {
Expand Down
Loading

0 comments on commit dcc5db0

Please sign in to comment.