Skip to content

Commit 40411aa

Browse files
committed
Suggest attributes from the include top level node
The structure of the include object is different from the other top level nodes in the Compose file so they were not handled by initial implementation. Signed-off-by: Remy Suen <remy.suen@docker.com>
1 parent affe5cb commit 40411aa

File tree

3 files changed

+260
-50
lines changed

3 files changed

+260
-50
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to the Docker Language Server will be documented in this fil
44

55
## [Unreleased]
66

7+
### Added
8+
9+
- Compose
10+
- textDocument/completion
11+
- add support for suggesting `include` properties ([#316](https://github.com/docker/docker-language-server/issues/316))
12+
713
### Fixed
814

915
- Compose

internal/compose/completion.go

Lines changed: 65 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ var textEditModifiers = []textEditModifier{buildTargetModifier, serviceSuggestio
126126

127127
func prefix(line string, character int) string {
128128
sb := strings.Builder{}
129+
sb.Grow(character)
129130
for i := range character {
130131
if unicode.IsSpace(rune(line[i])) {
131132
sb.Reset()
@@ -136,13 +137,14 @@ func prefix(line string, character int) string {
136137
return sb.String()
137138
}
138139

139-
func createSpacing(line string, whitespaceLine, arrayAttributes bool) string {
140-
if whitespaceLine && arrayAttributes {
140+
func createSpacing(line string, character int, arrayAttributes bool) string {
141+
if arrayAttributes {
141142
// 2 more for the attribute, then 2 more for the array offset = 4 total
142-
return strings.Repeat(" ", len(line)+4)
143+
return strings.Repeat(" ", character+4)
143144
}
144145
sb := strings.Builder{}
145-
for i := range line {
146+
sb.Grow(character + 2)
147+
for i := range character {
146148
if unicode.IsSpace(rune(line[i])) || line[i] == '-' {
147149
sb.WriteString(" ")
148150
}
@@ -201,75 +203,87 @@ func Completion(ctx context.Context, params *protocol.CompletionParams, manager
201203
return nil, nil
202204
}
203205

206+
character := int(params.Position.Character) + 1
207+
if len(lines[lspLine]) < character-1 {
208+
return nil, nil
209+
}
204210
whitespaceLine := currentLineTrimmed == ""
205211
line := int(lspLine) + 1
206-
character := int(params.Position.Character) + 1
207212
path := constructCompletionNodePath(file, line)
213+
wordPrefix := protocol.UInteger(len(prefix(lines[lspLine], character-1)))
208214
if len(path) == 0 {
209215
if topLevelNodeOffset != -1 && params.Position.Character != uint32(topLevelNodeOffset) {
210216
return nil, nil
211217
}
212218
return &protocol.CompletionList{Items: createTopLevelItems()}, nil
213219
} else if len(path) == 1 {
220+
if path[0].Key.GetToken().Value == "include" {
221+
schema := schemaProperties()["include"].Items.(*jsonschema.Schema)
222+
items := createSchemaItems(params, schema.Ref.OneOf[1].Properties, lines, lspLine, whitespaceLine, wordPrefix, file, manager, u, path)
223+
return processItems(items, whitespaceLine), nil
224+
}
214225
return nil, nil
215226
} else if path[1].Key.GetToken().Position.Column >= character {
216227
return nil, nil
217-
} else if len(lines[lspLine]) < character-1 {
218-
return nil, nil
219228
}
220229

221-
wordPrefix := prefix(lines[lspLine], character-1)
222230
path, nodeProps, arrayAttributes := nodeProperties(path, line, character)
223-
dependencies := dependencyCompletionItems(file, u, path, params, protocol.UInteger(len(wordPrefix)))
231+
dependencies := dependencyCompletionItems(file, u, path, params, wordPrefix)
224232
if len(dependencies) > 0 {
225233
return &protocol.CompletionList{Items: dependencies}, nil
226234
}
227-
items, stop := buildTargetCompletionItems(params, manager, path, u, protocol.UInteger(len(wordPrefix)))
235+
items, stop := buildTargetCompletionItems(params, manager, path, u, wordPrefix)
228236
if stop {
229237
return &protocol.CompletionList{Items: items}, nil
230238
}
231239

232-
items = namedDependencyCompletionItems(file, path, "configs", "configs", params, protocol.UInteger(len(wordPrefix)))
240+
items = namedDependencyCompletionItems(file, path, "configs", "configs", params, wordPrefix)
233241
if len(items) == 0 {
234-
items = namedDependencyCompletionItems(file, path, "secrets", "secrets", params, protocol.UInteger(len(wordPrefix)))
242+
items = namedDependencyCompletionItems(file, path, "secrets", "secrets", params, wordPrefix)
235243
}
236244
if len(items) == 0 {
237-
items = volumeDependencyCompletionItems(file, path, params, protocol.UInteger(len(wordPrefix)))
245+
items = volumeDependencyCompletionItems(file, path, params, wordPrefix)
238246
}
247+
schemaItems := createSchemaItems(params, nodeProps, lines, lspLine, whitespaceLine && arrayAttributes, wordPrefix, file, manager, u, path)
248+
items = append(items, schemaItems...)
249+
if len(items) == 0 {
250+
return nil, nil
251+
}
252+
return processItems(items, whitespaceLine && arrayAttributes), nil
253+
}
254+
255+
func createEnumItems(schema *jsonschema.Schema, params *protocol.CompletionParams, wordPrefixLength protocol.UInteger) []protocol.CompletionItem {
256+
items := []protocol.CompletionItem{}
257+
for _, value := range schema.Enum.Values {
258+
enumValue := value.(string)
259+
item := protocol.CompletionItem{
260+
Label: enumValue,
261+
Documentation: schema.Description,
262+
Detail: extractDetail(schema),
263+
TextEdit: protocol.TextEdit{
264+
NewText: enumValue,
265+
Range: protocol.Range{
266+
Start: protocol.Position{
267+
Line: params.Position.Line,
268+
Character: params.Position.Character - wordPrefixLength,
269+
},
270+
End: params.Position,
271+
},
272+
},
273+
}
274+
items = append(items, item)
275+
}
276+
return items
277+
}
278+
279+
func createSchemaItems(params *protocol.CompletionParams, nodeProps any, lines []string, lspLine int, whitespacePrefixedArrayAttribute bool, wordPrefixLength protocol.UInteger, file *ast.File, manager *document.Manager, u *url.URL, path []*ast.MappingValueNode) []protocol.CompletionItem {
280+
items := []protocol.CompletionItem{}
239281
if schema, ok := nodeProps.(*jsonschema.Schema); ok {
240282
if schema.Enum != nil {
241-
for _, value := range schema.Enum.Values {
242-
enumValue := value.(string)
243-
item := protocol.CompletionItem{
244-
Label: enumValue,
245-
Documentation: schema.Description,
246-
Detail: extractDetail(schema),
247-
TextEdit: protocol.TextEdit{
248-
NewText: enumValue,
249-
Range: protocol.Range{
250-
Start: protocol.Position{
251-
Line: params.Position.Line,
252-
Character: params.Position.Character - protocol.UInteger(len(wordPrefix)),
253-
},
254-
End: params.Position,
255-
},
256-
},
257-
}
258-
items = append(items, item)
259-
}
283+
return createEnumItems(schema, params, wordPrefixLength)
260284
}
261285
} else if properties, ok := nodeProps.(map[string]*jsonschema.Schema); ok {
262-
sb := strings.Builder{}
263-
for i := range lines[lspLine] {
264-
if unicode.IsSpace(rune(lines[lspLine][i])) || lines[lspLine][i] == '-' {
265-
sb.WriteString(" ")
266-
}
267-
}
268-
sb.WriteString(" ")
269-
if whitespaceLine && arrayAttributes {
270-
sb.WriteString(" ")
271-
}
272-
spacing := createSpacing(lines[lspLine], whitespaceLine, arrayAttributes)
286+
spacing := createSpacing(lines[lspLine], int(params.Position.Character), whitespacePrefixedArrayAttribute)
273287
for attributeName, schema := range properties {
274288
item := protocol.CompletionItem{
275289
Detail: extractDetail(schema),
@@ -279,7 +293,7 @@ func Completion(ctx context.Context, params *protocol.CompletionParams, manager
279293
Range: protocol.Range{
280294
Start: protocol.Position{
281295
Line: params.Position.Line,
282-
Character: params.Position.Character - protocol.UInteger(len(wordPrefix)),
296+
Character: params.Position.Character - protocol.UInteger(wordPrefixLength),
283297
},
284298
End: params.Position,
285299
},
@@ -314,7 +328,7 @@ func Completion(ctx context.Context, params *protocol.CompletionParams, manager
314328
Range: protocol.Range{
315329
Start: protocol.Position{
316330
Line: params.Position.Line,
317-
Character: params.Position.Character - protocol.UInteger(len(wordPrefix)),
331+
Character: params.Position.Character - protocol.UInteger(wordPrefixLength),
318332
},
319333
End: params.Position,
320334
},
@@ -324,13 +338,14 @@ func Completion(ctx context.Context, params *protocol.CompletionParams, manager
324338
items = append(items, item)
325339
}
326340
}
327-
if len(items) == 0 {
328-
return nil, nil
329-
}
341+
return items
342+
}
343+
344+
func processItems(items []protocol.CompletionItem, arrayPrefix bool) *protocol.CompletionList {
330345
slices.SortFunc(items, func(a, b protocol.CompletionItem) int {
331346
return strings.Compare(a.Label, b.Label)
332347
})
333-
if whitespaceLine && arrayAttributes {
348+
if arrayPrefix {
334349
for i := range items {
335350
edit := items[i].TextEdit.(protocol.TextEdit)
336351
items[i].TextEdit = protocol.TextEdit{
@@ -339,7 +354,7 @@ func Completion(ctx context.Context, params *protocol.CompletionParams, manager
339354
}
340355
}
341356
}
342-
return &protocol.CompletionList{Items: items}, nil
357+
return &protocol.CompletionList{Items: items}
343358
}
344359

345360
func createChoiceSnippetText(itemTexts []completionItemText) string {

0 commit comments

Comments
 (0)