Skip to content

Commit

Permalink
Merge pull request #706 from moosq/master
Browse files Browse the repository at this point in the history
Show links to possible map calls on hover
  • Loading branch information
moosq authored Oct 11, 2023
2 parents 5e6aaa8 + 6cc2408 commit 472f4c4
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 20 deletions.
2 changes: 1 addition & 1 deletion internal/lsp/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ func Complete(suite *Suite, pos int, nodes []syntax.Node, ownModName string) []p
return list
}
func CompletePredefinedFunctions() []protocol.CompletionItem {
var ret []protocol.CompletionItem
ret := make([]protocol.CompletionItem, 0, len(PredefinedFunctions))
for _, v := range PredefinedFunctions {
markup := protocol.MarkupContent{Kind: "markdown", Value: v.Documentation}
ret = append(ret, protocol.CompletionItem{
Expand Down
8 changes: 6 additions & 2 deletions internal/lsp/general.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ import (
errors "golang.org/x/xerrors"
)

type PlainTextHover struct{}
type MarkdownHover struct{}
type PlainTextHover struct {
mapOrConnectRefs string
}
type MarkdownHover struct {
mapOrConnectLinks string
}

func (s *Server) registerSemanticTokensIfNoDynReg() *protocol.SemanticTokensRegistrationOptions {
if s.clientCapability.HasDynRegForSemTok {
Expand Down
74 changes: 66 additions & 8 deletions internal/lsp/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,52 @@ import (
"github.com/nokia/ntt/ttcn3/syntax"
)

// removeDuplicateNodes
// NOTE: this function is a workaround for a scoping problem which
// occurs inside `tree.LookupWithDB`.
// following test is provoking the situation:
// TestPlainTextHoverForPortDefFromDecl (hover_test.go)
func removeDuplicateNodes(nodes []*ttcn3.Node) []*ttcn3.Node {
uNodes := make(map[*syntax.Ident]bool, len(nodes))
res := make([]*ttcn3.Node, 0, len(nodes))
for _, n := range nodes {
if _, ok := uNodes[n.Ident]; !ok {
uNodes[n.Ident] = true
res = append(res, n)
}
}
return res
}

func (md *MarkdownHover) Print(sign string, comment string, posRef string) protocol.MarkupContent {
// make line breaks conform to markdown spec
comment = strings.ReplaceAll(comment, "\n", " \n")
return protocol.MarkupContent{Kind: "markdown", Value: "```typescript\n" + string(sign) + "\n```\n - - -\n" + comment + "\n - - -\n" + posRef}
res := "```typescript\n" + string(sign) + "\n```\n"
if len(comment) > 0 {
res += " - - -\n" + comment
}
if len(md.mapOrConnectLinks) > 0 {
res += "_possible map / connect statements_\n - - -\n" + md.mapOrConnectLinks
}
if len(posRef) > 0 {
res += "\n - - -\n" + posRef
}
return protocol.MarkupContent{Kind: "markdown", Value: res}
}

func (md *MarkdownHover) LinkForNode(def *ttcn3.Node) string {
p := syntax.Begin(def.Node)
return fmt.Sprintf("[module %s](%s#L%dC%d)", def.ModuleOf(def.Node).Name.String(), def.Filename(), p.Line, p.Column)
}

func (md *MarkdownHover) GatherMapOrConnectLinks(fileName string, line uint32, uri protocol.DocumentURI) {
md.mapOrConnectLinks += fmt.Sprintf("[%s:%d](%s#L%d) \n", fileName, line+1, uri, line+1)
}

func (md *MarkdownHover) Reset() {
md.mapOrConnectLinks = ""
}

func (pt *PlainTextHover) Print(sign string, comment string, posRef string) protocol.MarkupContent {
val := sign
if len(val) > 0 && val[len(val)-1] != '\n' {
Expand All @@ -36,6 +71,9 @@ func (pt *PlainTextHover) Print(sign string, comment string, posRef string) prot
val += "\n"
}
}
if len(pt.mapOrConnectRefs) > 0 {
val += "possible map / connect statements\n_________________________________\n" + pt.mapOrConnectRefs
}
if len(posRef) > 0 {
val += strings.Repeat("_", len(posRef)+2) + "\n" + posRef
}
Expand All @@ -46,6 +84,14 @@ func (pt *PlainTextHover) LinkForNode(def *ttcn3.Node) string {
return fmt.Sprintf("[module %s]", def.ModuleOf(def.Node).Name.String())
}

func (pt *PlainTextHover) GatherMapOrConnectLinks(fileName string, line uint32, uri protocol.DocumentURI) {
pt.mapOrConnectRefs += fmt.Sprintf("%s:%d\n", fileName, line+1)
}

func (pt *PlainTextHover) Reset() {
pt.mapOrConnectRefs = ""
}

func getSignature(def *ttcn3.Node) string {
var sig bytes.Buffer
var prefix = ""
Expand All @@ -54,18 +100,18 @@ func getSignature(def *ttcn3.Node) string {
switch node := def.Node.(type) {
case *syntax.FuncDecl:
sig.WriteString(node.Kind.String() + " " + node.Name.String())
sig.Write(content[node.Params.Pos() : node.Params.End()])
sig.Write(content[node.Params.Pos():node.Params.End()])
if node.RunsOn != nil {
sig.WriteString("\n ")
sig.Write(content[node.RunsOn.Pos() : node.RunsOn.End()])
sig.Write(content[node.RunsOn.Pos():node.RunsOn.End()])
}
if node.System != nil {
sig.WriteString("\n ")
sig.Write(content[node.System.Pos() : node.System.End()])
sig.Write(content[node.System.Pos():node.System.End()])
}
if node.Return != nil {
sig.WriteString("\n ")
sig.Write(content[node.Return.Pos() : node.Return.End()])
sig.Write(content[node.Return.Pos():node.Return.End()])
}
case *syntax.ValueDecl, *syntax.TemplateDecl, *syntax.FormalPar, *syntax.StructTypeDecl, *syntax.ComponentTypeDecl, *syntax.EnumTypeDecl, *syntax.PortTypeDecl:
sig.Write(content[def.Node.Pos()-1 : def.Node.End()])
Expand Down Expand Up @@ -105,14 +151,26 @@ func ProcessHover(params *protocol.HoverParams, db *ttcn3.DB, capability HoverCo
return nil, nil
}

for _, def := range tree.LookupWithDB(x, db) {
for _, def := range removeDuplicateNodes(tree.LookupWithDB(x, db)) {
defFound = true

comment = syntax.Doc(def.Node)
signature = getSignature(def)
if tree.Root != def.Root {
posRef = capability.LinkForNode(def)
}

if firstTok := def.Node.FirstTok(); firstTok == nil {
continue
} else {
if node, ok := def.Node.(*syntax.ValueDecl); ok {
if node.Kind.Kind() == syntax.PORT {
locations := FindMapConnectStatementForPortIdMatchingTheName(db, syntax.Name(x))
for _, l := range locations {
capability.GatherMapOrConnectLinks(l.URI.SpanURI().Filename(), l.Range.Start.Line, l.URI)
}
}
}
}
}
if !defFound {
// look for predefined functions
Expand All @@ -128,6 +186,6 @@ func ProcessHover(params *protocol.HoverParams, db *ttcn3.DB, capability HoverCo

hoverContents := capability.Print(signature, comment, posRef)
hover := &protocol.Hover{Contents: hoverContents}

capability.Reset()
return hover, nil
}
69 changes: 60 additions & 9 deletions internal/lsp/hover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/nokia/ntt/internal/fs"
"github.com/nokia/ntt/internal/lsp"
"github.com/nokia/ntt/internal/lsp/protocol"
"github.com/nokia/ntt/project"
"github.com/nokia/ntt/ttcn3"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -54,20 +55,71 @@ func TestMarkdownHoverForFunction(t *testing.T) {
" runs on Component\n" +
" system System\n" +
" return integer\n" +
"```\n" +
" - - -\n" +
"\n" +
" - - -\n"
"```\n"

assert.Equal(t, expected, actual.Contents.Value)
}

func TestPlainTextHoverForPortDefFromDecl(t *testing.T) {
actual := testHover(t, `
module Test {
type component Component {
port P p1;
}
function myfunc(integer x)
runs on Component
{
map(system:p1, mtc:p1);
p1.receive;
}
}`,
protocol.Position{Line: 3, Character: 24},
&lsp.PlainTextHover{})

expected :=
" port P p1\n" +
"possible map / connect statements\n" +
"_________________________________\n" +
"/TestPlainTextHoverForPortDefFromDecl.ttcn3:9\n"

assert.Equal(t, expected, actual.Contents.Value)
}

func TestPlainTextHoverForPortDefFromUsage(t *testing.T) {
actual := testHover(t, `
module Test {
type component Component {
port P p1;
}
function myfunc(integer x)
runs on Component
{
map(system:p1, mtc:p1);
p1.receive;
}
}`,
protocol.Position{Line: 9, Character: 16},
&lsp.PlainTextHover{})

expected :=
" port P p1\n" +
"possible map / connect statements\n" +
"_________________________________\n" +
"/TestPlainTextHoverForPortDefFromUsage.ttcn3:9\n"

assert.Equal(t, expected, actual.Contents.Value)
}

func testHover(t *testing.T, text string, position protocol.Position, capability lsp.HoverContentProvider) *protocol.Hover {
t.Helper()

file := fmt.Sprintf("%s.ttcn3", t.Name())
suite := &lsp.Suite{
Config: &project.Config{},
DB: &ttcn3.DB{},
}
file := fmt.Sprintf("file://%s.ttcn3", t.Name())
fs.SetContent(file, []byte(text))

suite.Config.Sources = append(suite.Config.Sources, file)
suite.DB.Index(suite.Config.Sources...)
params := protocol.HoverParams{
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
Position: position,
Expand All @@ -77,8 +129,7 @@ func testHover(t *testing.T, text string, position protocol.Position, capability
},
}

hover, err := lsp.ProcessHover(&params, &ttcn3.DB{}, capability)
hover, err := lsp.ProcessHover(&params, suite.DB, capability)
assert.Equal(t, err, nil)

return hover
}
79 changes: 79 additions & 0 deletions internal/lsp/map_connector_finder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package lsp

import (
"sort"

"github.com/nokia/ntt/internal/lsp/protocol"
"github.com/nokia/ntt/ttcn3"
"github.com/nokia/ntt/ttcn3/syntax"
)

func isDuplicate(list []protocol.Location, loc protocol.Location) bool {
for _, l := range list {
if l == loc {
return true
}
}
return false
}
func invokedFromMapOrConnect(n syntax.Node, syn *ttcn3.Tree, list []protocol.Location) []protocol.Location {
p := n

for {
p = syn.ParentOf(p)
if p == nil {
break
}
node, ok := p.(*syntax.CallExpr)
if !ok {
continue
}
idNode, ok := node.Fun.(*syntax.Ident)
if !ok {
continue
}
if idNode.String() == "map" || idNode.String() == "connect" {
loc := location(syntax.Span{Begin: syn.Position(node.Pos()), End: syn.Position(node.End()), Filename: syn.Filename()})
if !isDuplicate(list, loc) {
list = append(list, loc)
}
break
}
}
return list
}

func findMapConnectStatementForPortIdMatchingTheNameFromFile(file string, idName string) []protocol.Location {
list := make([]protocol.Location, 0, 4)
syn := ttcn3.ParseFile(file)
syn.Root.Inspect(func(n syntax.Node) bool {
if n == nil {
// called on node exit
return false
}

switch node := n.(type) {
case *syntax.Ident:
if idName == node.String() {
list = invokedFromMapOrConnect(n, syn, list)
}
return false
default:
return true
}
})
return list
}

func FindMapConnectStatementForPortIdMatchingTheName(db *ttcn3.DB, name string) []protocol.Location {
candidates := make([]string, 0, len(db.Uses))
locs := make([]protocol.Location, 0, len(db.Uses))
for file := range db.Uses[name] {
candidates = append(candidates, file)
}
sort.Strings(candidates)
for _, file := range candidates {
locs = append(locs, findMapConnectStatementForPortIdMatchingTheNameFromFile(file, name)...)
}
return locs
}
Loading

0 comments on commit 472f4c4

Please sign in to comment.