Skip to content

Commit d88a227

Browse files
authored
Merge pull request #402 from docker/volume-bind-mount-fs-suggestions
Suggest local files and folders for volume bind mounts
2 parents 21297de + 8d3c959 commit d88a227

File tree

5 files changed

+379
-13
lines changed

5 files changed

+379
-13
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+
- provide local file and folder name suggestions when modifying simple strings for service volumes ([#376](https://github.com/docker/docker-language-server/issues/376))
12+
713
### Fixed
814

915
- Compose

e2e-tests/initialize_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,10 @@ func createGuaranteedInitializeResult() protocol.InitializeResult {
9696
syncKind := protocol.TextDocumentSyncKindFull
9797
return protocol.InitializeResult{
9898
Capabilities: protocol.ServerCapabilities{
99-
CodeActionProvider: protocol.CodeActionOptions{},
100-
CompletionProvider: &protocol.CompletionOptions{},
99+
CodeActionProvider: protocol.CodeActionOptions{},
100+
CompletionProvider: &protocol.CompletionOptions{
101+
TriggerCharacters: []string{"/"},
102+
},
101103
DefinitionProvider: protocol.DefinitionOptions{},
102104
DocumentHighlightProvider: &protocol.DocumentHighlightOptions{},
103105
DocumentLinkProvider: &protocol.DocumentLinkOptions{},

internal/compose/completion.go

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package compose
33
import (
44
"context"
55
"fmt"
6+
"os"
67
"path/filepath"
78
"slices"
89
"strings"
@@ -213,7 +214,8 @@ func Completion(ctx context.Context, params *protocol.CompletionParams, manager
213214
whitespaceLine := currentLineTrimmed == ""
214215
line := int(lspLine) + 1
215216
path := constructCompletionNodePath(file, line)
216-
wordPrefix := protocol.UInteger(len(prefix(lines[lspLine], character-1)))
217+
prefixContent := prefix(lines[lspLine], character-1)
218+
prefixLength := protocol.UInteger(len(prefixContent))
217219
if len(path) == 0 {
218220
if topLevelNodeOffset != -1 && params.Position.Character != uint32(topLevelNodeOffset) {
219221
return nil, nil
@@ -222,7 +224,7 @@ func Completion(ctx context.Context, params *protocol.CompletionParams, manager
222224
} else if len(path) == 1 {
223225
if path[0].Key.GetToken().Value == "include" {
224226
schema := schemaProperties()["include"].Items.(*jsonschema.Schema)
225-
items := createSchemaItems(params, schema.Ref.OneOf[1].Properties, lines, lspLine, whitespaceLine, wordPrefix, file, manager, documentPath, path)
227+
items := createSchemaItems(params, schema.Ref.OneOf[1].Properties, lines, lspLine, whitespaceLine, prefixLength, file, manager, documentPath, path)
226228
return processItems(items, whitespaceLine), nil
227229
}
228230
return nil, nil
@@ -231,30 +233,41 @@ func Completion(ctx context.Context, params *protocol.CompletionParams, manager
231233
}
232234

233235
path, nodeProps, arrayAttributes := nodeProperties(path, line, character)
234-
dependencies := dependencyCompletionItems(file, documentPath, path, params, wordPrefix)
236+
dependencies := dependencyCompletionItems(file, documentPath, path, params, prefixLength)
235237
if len(dependencies) > 0 {
236238
return &protocol.CompletionList{Items: dependencies}, nil
237239
}
238-
items, stop := buildTargetCompletionItems(params, manager, path, documentPath, wordPrefix)
240+
items, stop := buildTargetCompletionItems(params, manager, path, documentPath, prefixLength)
239241
if stop {
240242
return &protocol.CompletionList{Items: items}, nil
241243
}
244+
folderStructureItems := folderStructureCompletionItems(documentPath, path, removeQuote(prefixContent))
245+
if len(folderStructureItems) > 0 {
246+
return processItems(folderStructureItems, whitespaceLine && arrayAttributes), nil
247+
}
242248

243-
items = namedDependencyCompletionItems(file, path, "configs", "configs", params, wordPrefix)
249+
items = namedDependencyCompletionItems(file, path, "configs", "configs", params, prefixLength)
244250
if len(items) == 0 {
245-
items = namedDependencyCompletionItems(file, path, "secrets", "secrets", params, wordPrefix)
251+
items = namedDependencyCompletionItems(file, path, "secrets", "secrets", params, prefixLength)
246252
}
247253
if len(items) == 0 {
248-
items = volumeDependencyCompletionItems(file, path, params, wordPrefix)
254+
items = volumeDependencyCompletionItems(file, path, params, prefixLength)
249255
}
250-
schemaItems := createSchemaItems(params, nodeProps, lines, lspLine, whitespaceLine && arrayAttributes, wordPrefix, file, manager, documentPath, path)
256+
schemaItems := createSchemaItems(params, nodeProps, lines, lspLine, whitespaceLine && arrayAttributes, prefixLength, file, manager, documentPath, path)
251257
items = append(items, schemaItems...)
252258
if len(items) == 0 {
253259
return nil, nil
254260
}
255261
return processItems(items, whitespaceLine && arrayAttributes), nil
256262
}
257263

264+
func removeQuote(prefix string) string {
265+
if len(prefix) > 0 && (prefix[0] == 34 || prefix[0] == 39) {
266+
return prefix[1:]
267+
}
268+
return prefix
269+
}
270+
258271
func createEnumItems(schema *jsonschema.Schema, params *protocol.CompletionParams, wordPrefixLength protocol.UInteger) []protocol.CompletionItem {
259272
items := []protocol.CompletionItem{}
260273
for _, value := range schema.Enum.Values {
@@ -382,6 +395,54 @@ func modifyTextEdit(file *ast.File, manager *document.Manager, documentPath docu
382395
return edit
383396
}
384397

398+
func folderStructureCompletionItems(documentPath document.DocumentPath, path []*ast.MappingValueNode, prefix string) []protocol.CompletionItem {
399+
folder := directoryForPrefix(documentPath, path, prefix)
400+
if folder != "" {
401+
items := []protocol.CompletionItem{}
402+
entries, _ := os.ReadDir(folder)
403+
for _, entry := range entries {
404+
item := protocol.CompletionItem{Label: entry.Name()}
405+
if entry.IsDir() {
406+
item.Kind = types.CreateCompletionItemKindPointer(protocol.CompletionItemKindFolder)
407+
} else {
408+
item.Kind = types.CreateCompletionItemKindPointer(protocol.CompletionItemKindFile)
409+
}
410+
items = append(items, item)
411+
}
412+
return items
413+
}
414+
return nil
415+
}
416+
417+
func directoryForPrefix(documentPath document.DocumentPath, path []*ast.MappingValueNode, prefix string) string {
418+
if len(path) == 3 && path[2].Key.GetToken().Value == "volumes" {
419+
if strings.HasPrefix(prefix, "./") {
420+
_, folder := types.Concatenate(documentPath.Folder, prefix[0:strings.LastIndex(prefix, "/")], documentPath.WSLDollarSignHost)
421+
return folder
422+
}
423+
} else if len(path) == 4 && path[2].Key.GetToken().Value == "volumes" && path[3].Key.GetToken().Value == "source" {
424+
if volumes, ok := path[2].Value.(*ast.SequenceNode); ok {
425+
for _, node := range volumes.Values {
426+
if volume, ok := node.(*ast.MappingNode); ok {
427+
if slices.Contains(volume.Values, path[3]) {
428+
for _, property := range volume.Values {
429+
if property.Key.GetToken().Value == "type" && property.Value.GetToken().Value == "bind" {
430+
if strings.HasPrefix(prefix, "./") {
431+
_, folder := types.Concatenate(documentPath.Folder, prefix[0:strings.LastIndex(prefix, "/")], documentPath.WSLDollarSignHost)
432+
return folder
433+
}
434+
return documentPath.Folder
435+
}
436+
}
437+
return ""
438+
}
439+
}
440+
}
441+
}
442+
}
443+
return ""
444+
}
445+
385446
func findDependencies(file *ast.File, dependencyType string) []string {
386447
services := []string{}
387448
for _, documentNode := range file.Docs {

0 commit comments

Comments
 (0)