Skip to content

Commit 5e0d5cb

Browse files
committed
Add LSP debug logs and resolve alias definitions by anchor
1 parent 3d2eb6e commit 5e0d5cb

4 files changed

Lines changed: 189 additions & 75 deletions

File tree

internal/lsp/handlers.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,19 @@ func (h *definitionHandler) findMixinsDefinition(doc *string, params *lsp.Defini
6868

6969
filename := h.parser.extractFilenameFromMixins(doc, params.Position)
7070
if filename == "" {
71+
h.parser.log.Debugf("no mixin filename resolved at %s:%d:%d", path, params.Position.Line, params.Position.Character)
7172
return nil, nil
7273
}
7374

7475
absFilename := replacePathFilename(path, filename)
7576

7677
if !util.FileExists(absFilename) {
78+
h.parser.log.Debugf("mixin target does not exist: %s", absFilename)
7779
return nil, nil
7880
}
7981

82+
h.parser.log.Debugf("resolved mixin definition %q -> %s", filename, absFilename)
83+
8084
return []lsp.Location{
8185
{
8286
URI: pathToURI(absFilename),
@@ -86,16 +90,27 @@ func (h *definitionHandler) findMixinsDefinition(doc *string, params *lsp.Defini
8690
}
8791

8892
func (h *definitionHandler) findCommandDefinition(doc *string, params *lsp.DefinitionParams) (any, error) {
93+
path := normalizePath(params.TextDocument.URI)
8994
commandName := h.parser.extractCommandReference(doc, params.Position)
9095
if commandName == "" {
96+
h.parser.log.Debugf("no command reference resolved at %s:%d:%d", path, params.Position.Line, params.Position.Character)
9197
return nil, nil
9298
}
9399

94100
command := h.parser.findCommand(doc, commandName)
95101
if command == nil {
102+
h.parser.log.Debugf("command reference %q did not match any local command", commandName)
96103
return nil, nil
97104
}
98105

106+
h.parser.log.Debugf(
107+
"resolved command definition %q -> %s:%d:%d",
108+
commandName,
109+
path,
110+
command.position.Line,
111+
command.position.Character,
112+
)
113+
99114
// TODO: theoretically we can have multiple commands with the same name if we have mixins
100115
return []lsp.Location{
101116
{
@@ -154,13 +169,22 @@ func (s *lspServer) textDocumentDefinition(context *glsp.Context, params *lsp.De
154169
doc := s.storage.GetDocument(params.TextDocument.URI)
155170

156171
p := newParser(s.log)
157-
158-
switch p.getPositionType(doc, params.Position) {
172+
positionType := p.getPositionType(doc, params.Position)
173+
s.log.Debugf(
174+
"definition request uri=%s line=%d char=%d type=%s",
175+
normalizePath(params.TextDocument.URI),
176+
params.Position.Line,
177+
params.Position.Character,
178+
positionType,
179+
)
180+
181+
switch positionType {
159182
case PositionTypeMixins:
160183
return definitionHandler.findMixinsDefinition(doc, params)
161184
case PositionTypeDepends, PositionTypeCommandAlias:
162185
return definitionHandler.findCommandDefinition(doc, params)
163186
default:
187+
s.log.Debugf("definition request ignored: unsupported cursor position")
164188
return nil, nil
165189
}
166190
}

internal/lsp/server.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package lsp
33
import (
44
"context"
55

6+
"github.com/lets-cli/lets/internal/env"
67
"github.com/tliron/commonlog"
78
_ "github.com/tliron/commonlog/simple"
89
lsp "github.com/tliron/glsp/protocol_3_16"
@@ -24,8 +25,22 @@ func (s *lspServer) Run() error {
2425
return s.server.RunStdio()
2526
}
2627

28+
func lspLogVerbosity() (verbosity int) {
29+
verbosity = 1
30+
31+
defer func() {
32+
_ = recover()
33+
}()
34+
35+
if env.DebugLevel() > 0 {
36+
verbosity = 2
37+
}
38+
39+
return verbosity
40+
}
41+
2742
func Run(ctx context.Context, version string) error {
28-
commonlog.Configure(1, nil)
43+
commonlog.Configure(lspLogVerbosity(), nil)
2944

3045
logger := commonlog.GetLogger(lsName)
3146
logger.Infof("Lets LSP server starting %s", version)

internal/lsp/treesitter.go

Lines changed: 120 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ const (
1818
PositionTypeNone
1919
)
2020

21+
func (p PositionType) String() string {
22+
switch p {
23+
case PositionTypeMixins:
24+
return "mixins"
25+
case PositionTypeDepends:
26+
return "depends"
27+
case PositionTypeCommandAlias:
28+
return "command_alias"
29+
default:
30+
return "none"
31+
}
32+
}
33+
2134
var yamlLanguage = grammars.YamlLanguage()
2235

2336
func isCursorWithinNode(node *ts.Node, pos lsp.Position) bool {
@@ -58,6 +71,41 @@ func parseYAMLDocument(document *string) (*ts.Tree, []byte, error) {
5871
return tree, docBytes, nil
5972
}
6073

74+
func executeYAMLQuery(document *string, queryText string, visit func(capture ts.QueryCapture, docBytes []byte) bool) bool {
75+
tree, docBytes, err := parseYAMLDocument(document)
76+
if err != nil {
77+
return false
78+
}
79+
defer tree.Release()
80+
81+
query, err := ts.NewQuery(queryText, yamlLanguage)
82+
if err != nil {
83+
return false
84+
}
85+
86+
root := tree.RootNode()
87+
if root == nil {
88+
return false
89+
}
90+
91+
matches := query.Exec(root, yamlLanguage, docBytes)
92+
93+
for {
94+
match, ok := matches.NextMatch()
95+
if !ok {
96+
break
97+
}
98+
99+
for _, capture := range match.Captures {
100+
if visit(capture, docBytes) {
101+
return true
102+
}
103+
}
104+
}
105+
106+
return false
107+
}
108+
61109
func getLine(document *string, line uint32) string {
62110
lines := strings.Split(*document, "\n")
63111
if line >= uint32(len(lines)) {
@@ -233,44 +281,15 @@ func (p *parser) inDependsPosition(document *string, position lsp.Position) bool
233281
}
234282

235283
func (p *parser) inCommandAliasPosition(document *string, position lsp.Position) bool {
236-
tree, docBytes, err := parseYAMLDocument(document)
237-
if err != nil {
238-
return false
239-
}
240-
defer tree.Release()
241-
242-
query, err := ts.NewQuery(`
284+
return executeYAMLQuery(document, `
243285
(block_mapping_pair
244286
key: (flow_node) @keymerge
245287
value: (flow_node(alias) @alias)
246288
(#eq? @keymerge "<<")
247289
)
248-
`, yamlLanguage)
249-
if err != nil {
250-
return false
251-
}
252-
253-
root := tree.RootNode()
254-
if root == nil {
255-
return false
256-
}
257-
258-
matches := query.Exec(root, yamlLanguage, docBytes)
259-
260-
for {
261-
match, ok := matches.NextMatch()
262-
if !ok {
263-
break
264-
}
265-
266-
for _, capture := range match.Captures {
267-
if capture.Name == "alias" && isCursorWithinNode(capture.Node, position) {
268-
return true
269-
}
270-
}
271-
}
272-
273-
return false
290+
`, func(capture ts.QueryCapture, _ []byte) bool {
291+
return capture.Name == "alias" && isCursorWithinNode(capture.Node, position)
292+
})
274293
}
275294

276295
func (p *parser) extractFilenameFromMixins(document *string, position lsp.Position) string {
@@ -321,20 +340,22 @@ func (p *parser) extractFilenameFromMixins(document *string, position lsp.Positi
321340

322341
func (p *parser) extractCommandReference(document *string, position lsp.Position) string {
323342
if commandName := p.extractDependsCommandReference(document, position); commandName != "" {
343+
p.log.Debugf("resolved command reference from depends: %q", commandName)
324344
return commandName
325345
}
326346

327-
return p.extractAliasCommandReference(document, position)
347+
commandName := p.extractAliasCommandReference(document, position)
348+
if commandName != "" {
349+
p.log.Debugf("resolved command reference from alias: %q", commandName)
350+
}
351+
352+
return commandName
328353
}
329354

330355
func (p *parser) extractDependsCommandReference(document *string, position lsp.Position) string {
331-
tree, docBytes, err := parseYAMLDocument(document)
332-
if err != nil {
333-
return ""
334-
}
335-
defer tree.Release()
356+
var commandName string
336357

337-
query, err := ts.NewQuery(`
358+
executeYAMLQuery(document, `
338359
(block_mapping_pair
339360
key: (flow_node) @keydepends
340361
value: [
@@ -352,35 +373,52 @@ func (p *parser) extractDependsCommandReference(document *string, position lsp.P
352373
]
353374
(#eq? @keydepends "depends")
354375
)
355-
`, yamlLanguage)
356-
if err != nil {
357-
return ""
358-
}
376+
`, func(capture ts.QueryCapture, docBytes []byte) bool {
377+
if capture.Name == "reference" && isCursorWithinNode(capture.Node, position) {
378+
commandName = capture.Node.Text(docBytes)
379+
return true
380+
}
359381

360-
root := tree.RootNode()
361-
if root == nil {
362-
return ""
363-
}
382+
return false
383+
})
364384

365-
matches := query.Exec(root, yamlLanguage, docBytes)
385+
return commandName
386+
}
366387

367-
for {
368-
match, ok := matches.NextMatch()
369-
if !ok {
370-
break
371-
}
388+
func (p *parser) extractAliasCommandReference(document *string, position lsp.Position) string {
389+
var anchorName string
372390

373-
for _, capture := range match.Captures {
374-
if capture.Name == "reference" && isCursorWithinNode(capture.Node, position) {
375-
return capture.Node.Text(docBytes)
376-
}
391+
executeYAMLQuery(document, `
392+
(block_mapping_pair
393+
key: (flow_node) @keymerge
394+
value: (flow_node(alias) @reference)
395+
(#eq? @keymerge "<<")
396+
)
397+
`, func(capture ts.QueryCapture, docBytes []byte) bool {
398+
if capture.Name == "reference" && isCursorWithinNode(capture.Node, position) {
399+
anchorName = strings.TrimPrefix(capture.Node.Text(docBytes), "*")
400+
return true
377401
}
402+
403+
return false
404+
})
405+
406+
if anchorName == "" {
407+
return ""
378408
}
379409

380-
return ""
410+
commandName := p.findCommandNameByAnchor(document, anchorName)
411+
if commandName == "" {
412+
p.log.Debugf("alias anchor %q did not match any local command anchor", anchorName)
413+
return ""
414+
}
415+
416+
p.log.Debugf("resolved alias anchor %q to command %q", anchorName, commandName)
417+
418+
return commandName
381419
}
382420

383-
func (p *parser) extractAliasCommandReference(document *string, position lsp.Position) string {
421+
func (p *parser) findCommandNameByAnchor(document *string, anchorName string) string {
384422
tree, docBytes, err := parseYAMLDocument(document)
385423
if err != nil {
386424
return ""
@@ -389,9 +427,17 @@ func (p *parser) extractAliasCommandReference(document *string, position lsp.Pos
389427

390428
query, err := ts.NewQuery(`
391429
(block_mapping_pair
392-
key: (flow_node) @keymerge
393-
value: (flow_node(alias) @reference)
394-
(#eq? @keymerge "<<")
430+
key: (flow_node(plain_scalar(string_scalar)) @commands)
431+
value: (block_node
432+
(block_mapping
433+
(block_mapping_pair
434+
key: (flow_node
435+
(plain_scalar
436+
(string_scalar)) @cmd_key)
437+
value: (block_node
438+
(anchor
439+
(anchor_name) @anchor_name)))))
440+
(#eq? @commands "commands")
395441
)
396442
`, yamlLanguage)
397443
if err != nil {
@@ -411,12 +457,20 @@ func (p *parser) extractAliasCommandReference(document *string, position lsp.Pos
411457
break
412458
}
413459

460+
var commandName string
461+
var matchedAnchor string
462+
414463
for _, capture := range match.Captures {
415-
if capture.Name != "reference" || !isCursorWithinNode(capture.Node, position) {
416-
continue
464+
switch capture.Name {
465+
case "cmd_key":
466+
commandName = capture.Node.Text(docBytes)
467+
case "anchor_name":
468+
matchedAnchor = capture.Node.Text(docBytes)
417469
}
470+
}
418471

419-
return strings.TrimPrefix(capture.Node.Text(docBytes), "*")
472+
if matchedAnchor == anchorName {
473+
return commandName
420474
}
421475
}
422476

0 commit comments

Comments
 (0)