Skip to content

Commit 69f6b68

Browse files
committed
Prefer to return LocationLinks if the client supports it
Originally, we returned LocationLinks only in select cases. However, this can mean that the client may not provide the correct UI affordance if hyphens are involved so pivoting to always returning LocationLinks will help ensure the client presents the user with the accurate UX. Signed-off-by: Remy Suen <[email protected]>
1 parent 532b1ff commit 69f6b68

File tree

3 files changed

+670
-141
lines changed

3 files changed

+670
-141
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ All notable changes to the Docker Language Server will be documented in this fil
1010
- textDocument/publishDiagnostics
1111
- introduce a setting to ignore certain diagnostics to not duplicate the ones from the Dockerfile Language Server
1212

13+
### Fixed
14+
15+
- Docker Bake
16+
- textDocument/definition
17+
- always return LocationLinks to help disambiguate word boundaries for clients ([#31](https://github.com/docker/docker-language-server/issues/31))
18+
1319
## 0.1.0 - 2025-03-31
1420

1521
### Added

internal/bake/hcl/definition.go

Lines changed: 142 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -89,21 +89,30 @@ func Definition(ctx context.Context, definitionLinkSupport bool, manager *docume
8989

9090
for _, attribute := range body.Attributes {
9191
if isInsideRange(attribute.NameRange, position) {
92-
return []protocol.Location{
93-
{
94-
Range: protocol.Range{
95-
Start: protocol.Position{
96-
Line: uint32(attribute.NameRange.Start.Line) - 1,
97-
Character: uint32(attribute.NameRange.Start.Column) - 1,
98-
},
99-
End: protocol.Position{
100-
Line: uint32(attribute.NameRange.End.Line) - 1,
101-
Character: uint32(attribute.NameRange.End.Column) - 1,
102-
},
92+
return createDefinitionResult(
93+
definitionLinkSupport,
94+
protocol.Range{
95+
Start: protocol.Position{
96+
Line: uint32(attribute.NameRange.Start.Line) - 1,
97+
Character: uint32(attribute.NameRange.Start.Column) - 1,
98+
},
99+
End: protocol.Position{
100+
Line: uint32(attribute.NameRange.End.Line) - 1,
101+
Character: uint32(attribute.NameRange.End.Column) - 1,
102+
},
103+
},
104+
&protocol.Range{
105+
Start: protocol.Position{
106+
Line: uint32(attribute.NameRange.Start.Line) - 1,
107+
Character: uint32(attribute.NameRange.Start.Column) - 1,
108+
},
109+
End: protocol.Position{
110+
Line: uint32(attribute.NameRange.End.Line) - 1,
111+
Character: uint32(attribute.NameRange.End.Column) - 1,
103112
},
104-
URI: string(documentURI),
105113
},
106-
}, nil
114+
string(documentURI),
115+
), nil
107116
}
108117

109118
if isInsideRange(attribute.SrcRange, position) {
@@ -126,7 +135,18 @@ func ResolveAttributeValue(ctx context.Context, definitionLinkSupport bool, mana
126135
(sourceBlock.Type == "group" && attribute.Name == "targets") {
127136
value, _ := templateExpr.Value(&hcl.EvalContext{})
128137
target := value.AsString()
129-
return CalculateBlockLocation(input, body, documentURI, "target", target, false)
138+
templateExprRange := templateExpr.Range()
139+
sourceRange := hcl.Range{
140+
Start: hcl.Pos{
141+
Line: templateExprRange.Start.Line,
142+
Column: templateExprRange.Start.Column + 1,
143+
},
144+
End: hcl.Pos{
145+
Line: templateExprRange.End.Line,
146+
Column: templateExprRange.End.Column - 1,
147+
},
148+
}
149+
return CalculateBlockLocation(definitionLinkSupport, input, body, documentURI, sourceRange, "target", target, false)
130150
}
131151
}
132152
}
@@ -158,32 +178,30 @@ func ResolveExpression(ctx context.Context, definitionLinkSupport bool, manager
158178
for _, child := range nodes {
159179
if strings.EqualFold(child.Value, "FROM") {
160180
if child.Next != nil && child.Next.Next != nil && strings.EqualFold(child.Next.Next.Value, "AS") && child.Next.Next.Next != nil && child.Next.Next.Next.Value == target {
161-
targetRange := protocol.Range{
162-
Start: protocol.Position{Line: uint32(child.StartLine) - 1, Character: 0},
163-
End: protocol.Position{Line: uint32(child.EndLine) - 1, Character: uint32(len(lines[child.EndLine-1]))},
164-
}
165-
166-
linkURI := protocol.URI(fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(dockerfilePath), "/")))
167-
if !definitionLinkSupport {
168-
return []protocol.Location{
169-
{
170-
Range: targetRange,
171-
URI: linkURI,
181+
return createDefinitionResult(
182+
definitionLinkSupport,
183+
protocol.Range{
184+
Start: protocol.Position{
185+
Line: uint32(child.StartLine) - 1,
186+
Character: 0,
172187
},
173-
}
174-
}
175-
176-
return []protocol.LocationLink{
177-
{
178-
OriginSelectionRange: &protocol.Range{
179-
Start: protocol.Position{Line: uint32(literalValueExpr.Range().Start.Line) - 1, Character: uint32(literalValueExpr.Range().Start.Column) - 1},
180-
End: protocol.Position{Line: uint32(literalValueExpr.Range().End.Line) - 1, Character: uint32(uint32(literalValueExpr.Range().End.Column) - 1)},
188+
End: protocol.Position{
189+
Line: uint32(child.EndLine) - 1,
190+
Character: uint32(len(lines[child.EndLine-1])),
181191
},
182-
TargetRange: targetRange,
183-
TargetSelectionRange: targetRange,
184-
TargetURI: linkURI,
185192
},
186-
}
193+
&protocol.Range{
194+
Start: protocol.Position{
195+
Line: uint32(literalValueExpr.Range().Start.Line) - 1,
196+
Character: uint32(literalValueExpr.Range().Start.Column) - 1,
197+
},
198+
End: protocol.Position{
199+
Line: uint32(literalValueExpr.Range().End.Line) - 1,
200+
Character: uint32(uint32(literalValueExpr.Range().End.Column) - 1),
201+
},
202+
},
203+
protocol.URI(fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(dockerfilePath), "/"))),
204+
)
187205
}
188206
}
189207
}
@@ -225,15 +243,29 @@ func ResolveExpression(ctx context.Context, definitionLinkSupport bool, manager
225243
}
226244

227245
if value == arg {
228-
return []protocol.Location{
229-
{
230-
Range: protocol.Range{
231-
Start: protocol.Position{Line: uint32(node.StartLine) - 1, Character: 0},
232-
End: protocol.Position{Line: uint32(node.EndLine) - 1, Character: uint32(len(lines[node.EndLine-1]))},
233-
},
234-
URI: protocol.URI(fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(dockerfilePath), "/"))),
246+
originSelectionRange := protocol.Range{
247+
Start: protocol.Position{
248+
Line: uint32(item.KeyExpr.Range().Start.Line) - 1,
249+
Character: uint32(item.KeyExpr.Range().Start.Column) - 1,
235250
},
251+
End: protocol.Position{
252+
Line: uint32(item.KeyExpr.Range().End.Line) - 1,
253+
Character: uint32(item.KeyExpr.Range().End.Column) - 1,
254+
},
255+
}
256+
if LiteralValue(item.KeyExpr) {
257+
originSelectionRange.Start.Character = originSelectionRange.Start.Character + 1
258+
originSelectionRange.End.Character = originSelectionRange.End.Character - 1
236259
}
260+
return createDefinitionResult(
261+
definitionLinkSupport,
262+
protocol.Range{
263+
Start: protocol.Position{Line: uint32(node.StartLine) - 1, Character: 0},
264+
End: protocol.Position{Line: uint32(node.EndLine) - 1, Character: uint32(len(lines[node.EndLine-1]))},
265+
},
266+
&originSelectionRange,
267+
protocol.URI(fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(dockerfilePath), "/"))),
268+
)
237269
}
238270
child = child.Next
239271
}
@@ -278,7 +310,7 @@ func ResolveExpression(ctx context.Context, definitionLinkSupport bool, manager
278310

279311
if _, ok := expression.(*hclsyntax.ScopeTraversalExpr); ok {
280312
name := string(input[expression.Range().Start.Byte:expression.Range().End.Byte])
281-
return CalculateBlockLocation(input, body, documentURI, "variable", name, true)
313+
return CalculateBlockLocation(definitionLinkSupport, input, body, documentURI, expression.Range(), "variable", name, true)
282314
}
283315

284316
if templateWrapExpr, ok := expression.(*hclsyntax.TemplateWrapExpr); ok {
@@ -287,7 +319,7 @@ func ResolveExpression(ctx context.Context, definitionLinkSupport bool, manager
287319

288320
if functionCallExpr, ok := expression.(*hclsyntax.FunctionCallExpr); ok {
289321
if isInsideRange(functionCallExpr.NameRange, position) {
290-
return CalculateBlockLocation(input, body, documentURI, "function", functionCallExpr.Name, true)
322+
return CalculateBlockLocation(definitionLinkSupport, input, body, documentURI, functionCallExpr.NameRange, "function", functionCallExpr.Name, true)
291323
}
292324

293325
for _, arg := range functionCallExpr.Args {
@@ -303,7 +335,7 @@ func ResolveExpression(ctx context.Context, definitionLinkSupport bool, manager
303335
// returns it. If variable is true then it will also look at the
304336
// top-level attributes of the HCL file and resolve to those if the
305337
// names match.
306-
func CalculateBlockLocation(input []byte, body *hclsyntax.Body, documentURI uri.URI, blockName, name string, variable bool) any {
338+
func CalculateBlockLocation(definitionLinkSupport bool, input []byte, body *hclsyntax.Body, documentURI uri.URI, sourceRange hcl.Range, blockName, name string, variable bool) any {
307339
for _, b := range body.Blocks {
308340
if b.Type == blockName && b.Labels[0] == name {
309341
startCharacter := uint32(b.LabelRanges[0].Start.Column)
@@ -315,42 +347,80 @@ func CalculateBlockLocation(input []byte, body *hclsyntax.Body, documentURI uri.
315347
startCharacter--
316348
endCharacter--
317349
}
318-
return []protocol.Location{
319-
{
320-
Range: protocol.Range{
321-
Start: protocol.Position{
322-
Line: uint32(b.LabelRanges[0].Start.Line) - 1,
323-
Character: startCharacter,
324-
},
325-
End: protocol.Position{
326-
Line: uint32(b.LabelRanges[0].End.Line) - 1,
327-
Character: endCharacter,
328-
},
350+
return createDefinitionResult(
351+
definitionLinkSupport,
352+
protocol.Range{
353+
Start: protocol.Position{
354+
Line: uint32(b.LabelRanges[0].Start.Line) - 1,
355+
Character: startCharacter,
356+
},
357+
End: protocol.Position{
358+
Line: uint32(b.LabelRanges[0].End.Line) - 1,
359+
Character: endCharacter,
329360
},
330-
URI: string(documentURI),
331361
},
332-
}
362+
&protocol.Range{
363+
Start: protocol.Position{
364+
Line: uint32(sourceRange.Start.Line) - 1,
365+
Character: uint32(sourceRange.Start.Column) - 1,
366+
},
367+
End: protocol.Position{
368+
Line: uint32(sourceRange.End.Line) - 1,
369+
Character: uint32(sourceRange.End.Column) - 1,
370+
},
371+
},
372+
string(documentURI),
373+
)
333374
}
334375
}
335376

336377
if attribute, ok := body.Attributes[name]; ok && variable {
378+
return createDefinitionResult(
379+
definitionLinkSupport,
380+
protocol.Range{
381+
Start: protocol.Position{
382+
Line: uint32(attribute.NameRange.Start.Line) - 1,
383+
Character: uint32(attribute.NameRange.Start.Column) - 1,
384+
},
385+
End: protocol.Position{
386+
Line: uint32(attribute.NameRange.End.Line) - 1,
387+
Character: uint32(attribute.NameRange.End.Column) - 1,
388+
},
389+
},
390+
&protocol.Range{
391+
Start: protocol.Position{
392+
Line: uint32(sourceRange.Start.Line) - 1,
393+
Character: uint32(sourceRange.Start.Column) - 1,
394+
},
395+
End: protocol.Position{
396+
Line: uint32(sourceRange.End.Line) - 1,
397+
Character: uint32(sourceRange.End.Column) - 1,
398+
},
399+
},
400+
string(documentURI),
401+
)
402+
}
403+
return nil
404+
}
405+
406+
func createDefinitionResult(definitionLinkSupport bool, targetRange protocol.Range, originSelectionRange *protocol.Range, linkURI protocol.URI) any {
407+
if !definitionLinkSupport {
337408
return []protocol.Location{
338409
{
339-
Range: protocol.Range{
340-
Start: protocol.Position{
341-
Line: uint32(attribute.NameRange.Start.Line) - 1,
342-
Character: uint32(attribute.NameRange.Start.Column) - 1,
343-
},
344-
End: protocol.Position{
345-
Line: uint32(attribute.NameRange.End.Line) - 1,
346-
Character: uint32(attribute.NameRange.End.Column) - 1,
347-
},
348-
},
349-
URI: string(documentURI),
410+
Range: targetRange,
411+
URI: linkURI,
350412
},
351413
}
352414
}
353-
return nil
415+
416+
return []protocol.LocationLink{
417+
{
418+
OriginSelectionRange: originSelectionRange,
419+
TargetRange: targetRange,
420+
TargetSelectionRange: targetRange,
421+
TargetURI: linkURI,
422+
},
423+
}
354424
}
355425

356426
func ParseDockerfile(dockerfilePath string) ([]byte, *parser.Result, error) {

0 commit comments

Comments
 (0)