Skip to content

Commit cc1667d

Browse files
committed
Support highlighting the short form syntax for services in depends_on
Signed-off-by: Remy Suen <[email protected]>
1 parent 14ebce9 commit cc1667d

File tree

4 files changed

+241
-0
lines changed

4 files changed

+241
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ All notable changes to the Docker Language Server will be documented in this fil
1414
- suggest dependent volume names for the `volumes` attribute ([#133](https://github.com/docker/docker-language-server/issues/133))
1515
- suggest dependent config names for the `configs` attribute ([#134](https://github.com/docker/docker-language-server/issues/134))
1616
- suggest dependent secret names for the `secrets` attribute ([#135](https://github.com/docker/docker-language-server/issues/135))
17+
- textDocument/documentHighlight
18+
- support highlighting the short form `depends_on` syntax for services ([#70](https://github.com/docker/docker-language-server/issues/70))
1719

1820
### Fixed
1921

internal/compose/documentHighlight.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package compose
2+
3+
import (
4+
"github.com/docker/docker-language-server/internal/pkg/document"
5+
"github.com/docker/docker-language-server/internal/tliron/glsp/protocol"
6+
"github.com/goccy/go-yaml/ast"
7+
"github.com/goccy/go-yaml/token"
8+
)
9+
10+
func serviceReferences(node *ast.MappingValueNode, dependencyAttributeName string) []*token.Token {
11+
if servicesNode, ok := node.Value.(*ast.MappingNode); ok {
12+
tokens := []*token.Token{}
13+
for _, serviceNode := range servicesNode.Values {
14+
if serviceAttributes, ok := serviceNode.Value.(*ast.MappingNode); ok {
15+
for _, attributeNode := range serviceAttributes.Values {
16+
if attributeNode.Key.GetToken().Value == dependencyAttributeName {
17+
if sequenceNode, ok := attributeNode.Value.(*ast.SequenceNode); ok {
18+
for _, service := range sequenceNode.Values {
19+
tokens = append(tokens, service.GetToken())
20+
}
21+
}
22+
}
23+
}
24+
}
25+
}
26+
return tokens
27+
}
28+
return nil
29+
}
30+
31+
func declarations(node *ast.MappingValueNode, dependencyType string) []*token.Token {
32+
if s, ok := node.Key.(*ast.StringNode); ok && s.Value == dependencyType {
33+
if servicesNode, ok := node.Value.(*ast.MappingNode); ok {
34+
tokens := []*token.Token{}
35+
for _, serviceNode := range servicesNode.Values {
36+
tokens = append(tokens, serviceNode.Key.GetToken())
37+
}
38+
return tokens
39+
}
40+
}
41+
return nil
42+
}
43+
44+
func DocumentHighlight(doc document.ComposeDocument, position protocol.Position) ([]protocol.DocumentHighlight, error) {
45+
file := doc.File()
46+
if file == nil || len(file.Docs) == 0 {
47+
return nil, nil
48+
}
49+
50+
line := int(position.Line) + 1
51+
character := int(position.Character) + 1
52+
if mappingNode, ok := file.Docs[0].Body.(*ast.MappingNode); ok {
53+
for _, node := range mappingNode.Values {
54+
if s, ok := node.Key.(*ast.StringNode); ok {
55+
switch s.Value {
56+
case "services":
57+
refs := serviceReferences(node, "depends_on")
58+
decls := declarations(node, "services")
59+
highlights := highlightServiceReferences(refs, decls, line, character)
60+
if len(highlights) > 0 {
61+
return highlights, nil
62+
}
63+
}
64+
}
65+
}
66+
}
67+
return nil, nil
68+
}
69+
70+
func highlightServiceReferences(refs, decls []*token.Token, line, character int) []protocol.DocumentHighlight {
71+
var match *token.Token
72+
for _, reference := range refs {
73+
if inToken(reference, line, character) {
74+
match = reference
75+
break
76+
}
77+
}
78+
79+
if match == nil {
80+
for _, declaration := range decls {
81+
if inToken(declaration, line, character) {
82+
match = declaration
83+
break
84+
}
85+
}
86+
}
87+
88+
if match != nil {
89+
highlights := []protocol.DocumentHighlight{}
90+
for _, reference := range refs {
91+
if reference.Value == match.Value {
92+
highlights = append(highlights, documentHighlightFromToken(reference, protocol.DocumentHighlightKindRead))
93+
}
94+
}
95+
96+
for _, declaration := range decls {
97+
if declaration.Value == match.Value {
98+
highlights = append(highlights, documentHighlightFromToken(declaration, protocol.DocumentHighlightKindWrite))
99+
}
100+
}
101+
return highlights
102+
}
103+
return nil
104+
}
105+
106+
func documentHighlightFromToken(t *token.Token, kind protocol.DocumentHighlightKind) protocol.DocumentHighlight {
107+
return documentHighlight(
108+
protocol.UInteger(t.Position.Line)-1,
109+
protocol.UInteger(t.Position.Column)-1,
110+
protocol.UInteger(t.Position.Line)-1,
111+
protocol.UInteger(t.Position.Column+len(t.Value))-1,
112+
kind,
113+
)
114+
}
115+
116+
func documentHighlight(startLine, startCharacter, endLine, endCharacter protocol.UInteger, kind protocol.DocumentHighlightKind) protocol.DocumentHighlight {
117+
return protocol.DocumentHighlight{
118+
Kind: &kind,
119+
Range: protocol.Range{
120+
Start: protocol.Position{
121+
Line: startLine,
122+
Character: startCharacter,
123+
},
124+
End: protocol.Position{
125+
Line: endLine,
126+
Character: endCharacter,
127+
},
128+
},
129+
}
130+
}
131+
132+
func inToken(t *token.Token, line, character int) bool {
133+
return t.Position.Line == line && t.Position.Column <= character && character <= t.Position.Column+len(t.Value)
134+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package compose
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
"testing"
9+
10+
"github.com/docker/docker-language-server/internal/pkg/document"
11+
"github.com/docker/docker-language-server/internal/tliron/glsp/protocol"
12+
"github.com/stretchr/testify/require"
13+
"go.lsp.dev/uri"
14+
)
15+
16+
func TestDocumentHighlight_Services(t *testing.T) {
17+
testCases := []struct {
18+
name string
19+
content string
20+
line protocol.UInteger
21+
character protocol.UInteger
22+
ranges []protocol.DocumentHighlight
23+
}{
24+
{
25+
name: "write highlight on a service",
26+
content: `
27+
services:
28+
test:`,
29+
line: 2,
30+
character: 4,
31+
ranges: []protocol.DocumentHighlight{
32+
documentHighlight(2, 2, 2, 6, protocol.DocumentHighlightKindWrite),
33+
},
34+
},
35+
{
36+
name: "read highlight on an undefined service array item",
37+
content: `
38+
services:
39+
test:
40+
depends_on:
41+
- test2`,
42+
line: 4,
43+
character: 10,
44+
ranges: []protocol.DocumentHighlight{
45+
documentHighlight(4, 8, 4, 13, protocol.DocumentHighlightKindRead),
46+
},
47+
},
48+
{
49+
name: "cursor not on anything meaningful",
50+
content: `
51+
services:
52+
test:
53+
depends_on:
54+
- test2
55+
- test2`,
56+
line: 3,
57+
character: 9,
58+
ranges: nil,
59+
},
60+
{
61+
name: "read highlight on an undefined service array item, duplicated",
62+
content: `
63+
services:
64+
test:
65+
depends_on:
66+
- test2
67+
- test2`,
68+
line: 4,
69+
character: 10,
70+
ranges: []protocol.DocumentHighlight{
71+
documentHighlight(4, 8, 4, 13, protocol.DocumentHighlightKindRead),
72+
documentHighlight(5, 8, 5, 13, protocol.DocumentHighlightKindRead),
73+
},
74+
},
75+
{
76+
name: "read/write highlight on a service array item",
77+
content: `
78+
services:
79+
test:
80+
depends_on:
81+
- test2
82+
test2:`,
83+
line: 5,
84+
character: 5,
85+
ranges: []protocol.DocumentHighlight{
86+
documentHighlight(4, 8, 4, 13, protocol.DocumentHighlightKindRead),
87+
documentHighlight(5, 2, 5, 7, protocol.DocumentHighlightKindWrite),
88+
},
89+
},
90+
}
91+
92+
composeFileURI := fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(filepath.Join(os.TempDir(), "compose.yaml")), "/"))
93+
u := uri.URI(composeFileURI)
94+
for _, tc := range testCases {
95+
t.Run(tc.name, func(t *testing.T) {
96+
doc := document.NewComposeDocument(u, 1, []byte(tc.content))
97+
ranges, err := DocumentHighlight(doc, protocol.Position{Line: tc.line, Character: tc.character})
98+
require.NoError(t, err)
99+
require.Equal(t, tc.ranges, ranges)
100+
})
101+
}
102+
}

internal/pkg/server/documentHighlight.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package server
22

33
import (
44
"github.com/docker/docker-language-server/internal/bake/hcl"
5+
"github.com/docker/docker-language-server/internal/compose"
56
"github.com/docker/docker-language-server/internal/pkg/document"
67
"github.com/docker/docker-language-server/internal/tliron/glsp"
78
"github.com/docker/docker-language-server/internal/tliron/glsp/protocol"
@@ -16,6 +17,8 @@ func (s *Server) TextDocumentDocumentHighlight(ctx *glsp.Context, params *protoc
1617
defer doc.Close()
1718
if doc.LanguageIdentifier() == protocol.DockerBakeLanguage {
1819
return hcl.DocumentHighlight(doc.(document.BakeHCLDocument), params.Position)
20+
} else if doc.LanguageIdentifier() == protocol.DockerComposeLanguage {
21+
return compose.DocumentHighlight(doc.(document.ComposeDocument), params.Position)
1922
}
2023
return nil, nil
2124
}

0 commit comments

Comments
 (0)