From cb0eebe13a7ad5202f64bb43dbcbd08125566391 Mon Sep 17 00:00:00 2001 From: kmichaelk <130953568+kmichaelk@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:35:57 +0300 Subject: [PATCH] Improve autocompletion - Fields autocompletion - Function named parameters autocompletion - Composite Literals properties autocompletion - Local variables autocompletion Collect params to completion items directly --- internal/lsp/completion.go | 269 ++++++++++++++++++++++-- internal/lsp/completion_test.go | 357 ++++++++++++++++++++++++++++++++ ttcn3/tree.go | 20 +- 3 files changed, 632 insertions(+), 14 deletions(-) diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go index bf54a67d..5a868db8 100644 --- a/internal/lsp/completion.go +++ b/internal/lsp/completion.go @@ -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 ) @@ -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 } @@ -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 } @@ -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): @@ -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 @@ -740,6 +768,162 @@ func CompletePortTypes(suite *Suite, mname string, sortPref string) []protocol.C return ret } +func CollectParams(n syntax.Node) []protocol.CompletionItem { + var list []protocol.CompletionItem + + switch n := n.(type) { + case *syntax.FormalPars: + for _, par := range n.List { + list = append(list, protocol.CompletionItem{Label: par.Name.String(), Detail: syntax.Name(par.Type)}) + } + case *syntax.StructTypeDecl: + for _, field := range n.Fields { + list = append(list, protocol.CompletionItem{Label: field.Name.String(), Detail: syntax.Name(field.Type)}) + } + } + + return list +} + +func CompleteParams(tgt syntax.Node, cur int, sortPref string, completeAssign bool) []protocol.CompletionItem { + var insertText string + if completeAssign { + insertText = "%s := ${1:}" + } else { + insertText = "%s" + } + + items := CollectParams(tgt) + if len(items) > cur { + items = items[cur:] + } + for i := range items { + items[i].Documentation = "" + items[i].Kind = protocol.EnumMemberCompletion + items[i].SortText = sortPref + items[i].Label + items[i].InsertText = fmt.Sprintf(insertText, items[i].Label) + items[i].InsertTextFormat = protocol.SnippetTextFormat + } + + return items +} + +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() { @@ -794,6 +978,41 @@ 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.Declarator: + name := n.Name.String() + ret = append(ret, protocol.CompletionItem{ + Label: name, + Kind: protocol.VariableCompletion, + SortText: sortPref + name, + }) + default: + return true + } + return false + }) + + return ret +} + func behaviourInfos(tree *ttcn3.Tree, kind syntax.Kind) []*BehaviourInfo { var ret []*BehaviourInfo mname := "" @@ -873,6 +1092,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 { diff --git a/internal/lsp/completion_test.go b/internal/lsp/completion_test.go index b3d88c10..f75cf512 100644 --- a/internal/lsp/completion_test.go +++ b/internal/lsp/completion_test.go @@ -1132,3 +1132,360 @@ func TestSyntaxErrorProvokingInvalidPos(t *testing.T) { assert.Equal(t, pos, 203) } */ + +func TestSelectorComplWithStructTypeDecl(t *testing.T) { + list := testCompletion(t, `module Test + { + type record Structure + { + integer firstField, + AnotherStructure secondField, + YetAnotherOne thirdField + } + + function f1() { + var template Structure st := {}; + st.¶ + } + }`) + + assert.Equal(t, []protocol.CompletionItem{ + {Label: "firstField", Kind: protocol.FieldCompletion, Detail: "integer", Documentation: ""}, + {Label: "secondField", Kind: protocol.FieldCompletion, Detail: "AnotherStructure", Documentation: ""}, + {Label: "thirdField", Kind: protocol.FieldCompletion, Detail: "YetAnotherOne", Documentation: ""}}, + list) +} + +func TestDeepSelectorComplWithStructTypeDecl(t *testing.T) { + list := testCompletion(t, `module Test + { + type record YetAnotherOne + { + integer i + } + + type record AnotherStructure + { + YetAnotherOne singleField + } + + type record Structure + { + integer firstField, + AnotherStructure secondField, + YetAnotherOne thirdField + } + + function f1() { + var template Structure st := {}; + st.secondField.¶ + } + }`) + + assert.Equal(t, []protocol.CompletionItem{ + {Label: "singleField", Kind: protocol.FieldCompletion, Detail: "YetAnotherOne", Documentation: ""}}, + list) +} + +func TestSelectorComplWithStructTypeDeclPartialInput(t *testing.T) { + list := testCompletion(t, `module Test + { + type record Structure + { + integer firstField, + AnotherStructure secondField, + YetAnotherOne thirdField + } + + function f1() { + var template Structure st := {}; + st.f¶ + } + }`) + + assert.Equal(t, []protocol.CompletionItem{ + {Label: "firstField", Kind: protocol.FieldCompletion, Detail: "integer", Documentation: ""}, + {Label: "secondField", Kind: protocol.FieldCompletion, Detail: "AnotherStructure", Documentation: ""}, + {Label: "thirdField", Kind: protocol.FieldCompletion, Detail: "YetAnotherOne", Documentation: ""}}, + list) +} + +func TestNestedSelectorComplWithStructTypeDeclPartialInput(t *testing.T) { + list := testCompletion(t, `module Test + { + type record YetAnotherOne + { + integer i + } + + type record AnotherStructure + { + YetAnotherOne singleField + } + + type record Structure + { + integer firstField, + AnotherStructure secondField, + YetAnotherOne thirdField + } + + function f1() { + var template Structure st := {}; + st.secondField.s¶ + } + }`) + + assert.Equal(t, []protocol.CompletionItem{ + {Label: "singleField", Kind: protocol.FieldCompletion, Detail: "YetAnotherOne", Documentation: ""}}, + list) +} + +func TestNamedParamsCompletion(t *testing.T) { + list := testCompletion(t, `module Test + { + function fn(integer intParam, Structure structParam, integer intParam2) {} + + function f1() { + fn(i¶) + } + }`) + + assert.Equal(t, []protocol.CompletionItem{ + {Label: "intParam", Kind: protocol.EnumMemberCompletion, Detail: "integer", Documentation: "", InsertText: "intParam := ${1:}", + InsertTextFormat: protocol.SnippetTextFormat, SortText: " 1intParam"}, + {Label: "structParam", Kind: protocol.EnumMemberCompletion, Detail: "Structure", Documentation: "", InsertText: "structParam := ${1:}", + InsertTextFormat: protocol.SnippetTextFormat, SortText: " 1structParam"}, + {Label: "intParam2", Kind: protocol.EnumMemberCompletion, Detail: "integer", Documentation: "", InsertText: "intParam2 := ${1:}", + InsertTextFormat: protocol.SnippetTextFormat, SortText: " 1intParam2"}}, + list) +} + +func TestNamedParamsInsideAssignCompletion(t *testing.T) { + list := testCompletion(t, `module Test + { + function fn(integer intParam, Structure structParam, integer intParam2) {} + + function f1() { + fn(i¶ := 42) + } + }`) + + assert.Equal(t, []protocol.CompletionItem{ + {Label: "intParam", Kind: protocol.EnumMemberCompletion, Detail: "integer", Documentation: "", InsertText: "intParam", + InsertTextFormat: protocol.SnippetTextFormat, SortText: " 1intParam"}, + {Label: "structParam", Kind: protocol.EnumMemberCompletion, Detail: "Structure", Documentation: "", InsertText: "structParam", + InsertTextFormat: protocol.SnippetTextFormat, SortText: " 1structParam"}, + {Label: "intParam2", Kind: protocol.EnumMemberCompletion, Detail: "integer", Documentation: "", InsertText: "intParam2", + InsertTextFormat: protocol.SnippetTextFormat, SortText: " 1intParam2"}}, + list) +} + +func TestNamedParamsAfterAssignCompletion(t *testing.T) { + list := testCompletion(t, `module Test + { + function fn(integer intParam, Structure structParam, integer intParam2) {} + + function f1() { + fn(intParam := ¶) + } + }`) + + assert.Equal(t, []protocol.CompletionItem(nil), + list) +} + +func TestNamedParamsInCompositeLiteralCompletion(t *testing.T) { + list := testCompletion(t, `module Test + { + type record Structure + { + integer firstField, + AnotherStructure secondField, + YetAnotherOne thirdField + } + + function f1() { + var template Structure st := { + s¶ + }; + } + }`) + + assert.Equal(t, []protocol.CompletionItem{ + {Label: "firstField", Kind: protocol.EnumMemberCompletion, Detail: "integer", Documentation: "", InsertText: "firstField := ${1:}", + InsertTextFormat: protocol.SnippetTextFormat, SortText: " 1firstField"}, + {Label: "secondField", Kind: protocol.EnumMemberCompletion, Detail: "AnotherStructure", Documentation: "", InsertText: "secondField := ${1:}", + InsertTextFormat: protocol.SnippetTextFormat, SortText: " 1secondField"}, + {Label: "thirdField", Kind: protocol.EnumMemberCompletion, Detail: "YetAnotherOne", Documentation: "", InsertText: "thirdField := ${1:}", + InsertTextFormat: protocol.SnippetTextFormat, SortText: " 1thirdField"}, + {Label: "st", Kind: protocol.VariableCompletion, SortText: " 2st"}}, + list) +} + +func TestNamedParamsInCompositeLiteralInsideAssignCompletion(t *testing.T) { + list := testCompletion(t, `module Test + { + type record Structure + { + integer firstField, + AnotherStructure secondField, + YetAnotherOne thirdField + } + + function f1() { + var template Structure st := { + s¶ := 42 + }; + } + }`) + + assert.Equal(t, []protocol.CompletionItem{ + {Label: "firstField", Kind: protocol.EnumMemberCompletion, Detail: "integer", Documentation: "", InsertText: "firstField", + InsertTextFormat: protocol.SnippetTextFormat, SortText: " 1firstField"}, + {Label: "secondField", Kind: protocol.EnumMemberCompletion, Detail: "AnotherStructure", Documentation: "", InsertText: "secondField", + InsertTextFormat: protocol.SnippetTextFormat, SortText: " 1secondField"}, + {Label: "thirdField", Kind: protocol.EnumMemberCompletion, Detail: "YetAnotherOne", Documentation: "", InsertText: "thirdField", + InsertTextFormat: protocol.SnippetTextFormat, SortText: " 1thirdField"}, + {Label: "st", Kind: protocol.VariableCompletion, SortText: " 2st"}}, + list) +} + +func TestNamedParamsInCompositeLiteralAfterAssignCompletion(t *testing.T) { + list := testCompletion(t, `module Test + { + type record Structure + { + integer firstField, + AnotherStructure secondField, + YetAnotherOne thirdField + } + + function f1() { + var template Structure st := { + firstField := s¶ + }; + } + }`) + + assert.Equal(t, []protocol.CompletionItem{ + {Label: "st", Kind: protocol.VariableCompletion, SortText: " 2st"}}, + list) +} + +func TestRecordItemSelectorCompletion(t *testing.T) { + list := testCompletion(t, `module Test + { + type record Structure + { + integer firstField, + AnotherStructure secondField, + YetAnotherOne thirdField + } + + type record of Structure RoStructure; + + function f1() { + var template RoStructure roSt := {}; + roSt[i].f¶; + } + }`) + + assert.Equal(t, []protocol.CompletionItem{ + {Label: "firstField", Kind: protocol.FieldCompletion, Detail: "integer", Documentation: ""}, + {Label: "secondField", Kind: protocol.FieldCompletion, Detail: "AnotherStructure", Documentation: ""}, + {Label: "thirdField", Kind: protocol.FieldCompletion, Detail: "YetAnotherOne", Documentation: ""}}, + list) +} + +func TestRecordItemSelectorNotDeepEnoughCompletion(t *testing.T) { + list := testCompletion(t, `module Test + { + type record Structure + { + integer firstField, + AnotherStructure secondField, + YetAnotherOne thirdField + } + + type record of Structure RoStructure; + + function f1() { + var template RoStructure roSt := {}; + roSt.f¶; + } + }`) + + assert.Equal(t, []protocol.CompletionItem(nil), list) +} + +func TestRecordItemSelectorTooDeepCompletion(t *testing.T) { + list := testCompletion(t, `module Test + { + type record Structure + { + integer firstField, + AnotherStructure secondField, + YetAnotherOne thirdField + } + + type record of Structure RoStructure; + + function f1() { + var template RoStructure roSt := {}; + roSt[i][j].f¶; + } + }`) + + assert.Equal(t, []protocol.CompletionItem(nil), list) +} + +func TestRecordItemSelectorMultidimensionalCompletion(t *testing.T) { + list := testCompletion(t, `module Test + { + type record Structure + { + integer firstField, + AnotherStructure secondField, + YetAnotherOne thirdField + } + + type record of Structure RoStructure; + type record of RoStructure RoRoStructure; + type record of RoRoStructure RoRoRoStructure; + + function f1() { + var template RoRoRoStructure roRoRoSt := {}; + roRoRoSt[i][j][k].f¶; + } + }`) + + assert.Equal(t, []protocol.CompletionItem{ + {Label: "firstField", Kind: protocol.FieldCompletion, Detail: "integer", Documentation: ""}, + {Label: "secondField", Kind: protocol.FieldCompletion, Detail: "AnotherStructure", Documentation: ""}, + {Label: "thirdField", Kind: protocol.FieldCompletion, Detail: "YetAnotherOne", Documentation: ""}}, + list) +} + +func TestRecordItemSelectorMultidimensionalNotDeepEnoughCompletion(t *testing.T) { + list := testCompletion(t, `module Test + { + type record Structure + { + integer firstField, + AnotherStructure secondField, + YetAnotherOne thirdField + } + + type record of Structure RoStructure; + type record of RoStructure RoRoStructure; + type record of RoRoStructure RoRoRoStructure; + + function f1() { + var template RoRoRoStructure roRoRoSt := {}; + roRoRoSt[i][j].f¶; + } + }`) + + assert.Equal(t, []protocol.CompletionItem(nil), list) +} diff --git a/ttcn3/tree.go b/ttcn3/tree.go index b4b8598b..a9c0940b 100644 --- a/ttcn3/tree.go +++ b/ttcn3/tree.go @@ -288,6 +288,10 @@ func (t *Tree) LookupWithDB(n syntax.Expr, db *DB) []*Node { } +func (t *Tree) TypeOf(n syntax.Node, db *DB) []*Node { + return newFinder(db).typeOf(&Node{Node: n, Tree: t}) +} + func newFinder(db *DB) *finder { return &finder{DB: db, cache: make(map[syntax.Node][]*Node)} } @@ -492,6 +496,7 @@ func (f *finder) typeOf(def *Node) []*Node { var result []*Node q := []*Node{def} + var prev syntax.Node = nil for len(q) > 0 { @@ -505,6 +510,9 @@ func (f *finder) typeOf(def *Node) []*Node { case *syntax.BinaryExpr: q = append(q, &Node{Node: n.X, Tree: def.Tree}) + case *syntax.Declarator: + q = append(q, &Node{Node: def.ParentOf(n), Tree: def.Tree}) + case *syntax.TemplateDecl: q = append(q, &Node{Node: n.Type, Tree: def.Tree}) @@ -532,11 +540,19 @@ func (f *finder) typeOf(def *Node) []*Node { q = append(q, &Node{Node: n.Return.Type, Tree: def.Tree}) } + case *syntax.IndexExpr: + q = append(q, f.lookup(n, def.Tree)...) + case syntax.Expr: q = append(q, f.lookup(n, def.Tree)...) case *syntax.RefSpec: - q = append(q, f.lookup(n.X, def.Tree)...) + r := f.lookup(n.X, def.Tree) + for _, v := range r { + if v.Node != prev { + q = append(q, v) + } + } case *syntax.BehaviourSpec, *syntax.BehaviourTypeDecl, @@ -552,6 +568,8 @@ func (f *finder) typeOf(def *Node) []*Node { *syntax.MapTypeDecl: result = append(result, &Node{Node: n, Tree: def.Tree}) } + + prev = def.Node } return result }