Skip to content

Commit 66f7c98

Browse files
authored
Merge pull request #408 from docker/compose-include-file-completion
Add file structure completion for the include node
2 parents ef8177f + 37e9dd5 commit 66f7c98

File tree

3 files changed

+178
-31
lines changed

3 files changed

+178
-31
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ All notable changes to the Docker Language Server will be documented in this fil
1919
- `label_file` of a service ([#403](https://github.com/docker/docker-language-server/issues/403))
2020
- `file` attribute of a config ([#403](https://github.com/docker/docker-language-server/issues/403))
2121
- `file` attribute of a secret ([#403](https://github.com/docker/docker-language-server/issues/403))
22+
- string items of include objects ([#403](https://github.com/docker/docker-language-server/issues/403))
23+
- `env_file` attribute of include objects ([#403](https://github.com/docker/docker-language-server/issues/403))
24+
- `path` attribute of include objects ([#403](https://github.com/docker/docker-language-server/issues/403))
2225

2326
### Fixed
2427

internal/compose/completion.go

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ func Completion(ctx context.Context, params *protocol.CompletionParams, manager
225225
if path[0].Key.GetToken().Value == "include" {
226226
schema := schemaProperties()["include"].Items.(*jsonschema.Schema)
227227
items := createSchemaItems(params, schema.Ref.OneOf[1].Properties, lines, lspLine, whitespaceLine, prefixLength, file, manager, documentPath, path)
228+
items = append(items, folderStructureCompletionItems(documentPath, path, removeQuote(prefixContent))...)
228229
return processItems(items, whitespaceLine), nil
229230
}
230231
return nil, nil
@@ -363,10 +364,11 @@ func processItems(items []protocol.CompletionItem, arrayPrefix bool) *protocol.C
363364
})
364365
if arrayPrefix {
365366
for i := range items {
366-
edit := items[i].TextEdit.(protocol.TextEdit)
367-
items[i].TextEdit = protocol.TextEdit{
368-
NewText: fmt.Sprintf("%v%v", "- ", edit.NewText),
369-
Range: edit.Range,
367+
if edit, ok := items[i].TextEdit.(protocol.TextEdit); ok {
368+
items[i].TextEdit = protocol.TextEdit{
369+
NewText: fmt.Sprintf("%v%v", "- ", edit.NewText),
370+
Range: edit.Range,
371+
}
370372
}
371373
}
372374
}
@@ -417,7 +419,25 @@ func folderStructureCompletionItems(documentPath document.DocumentPath, path []*
417419
}
418420

419421
func directoryForNode(documentPath document.DocumentPath, path []*ast.MappingValueNode, prefix string) (folder string, hideFiles bool) {
420-
if len(path) == 3 {
422+
if len(path) == 1 && path[0].Key.GetToken().Value == "include" {
423+
return directoryForPrefix(documentPath, prefix, documentPath.Folder, false), false
424+
} else if len(path) == 2 {
425+
// include:
426+
// - env_file: ...
427+
// - env_file:
428+
// - ...
429+
// - path: ...
430+
// - path:
431+
// - ...
432+
if path[0].Key.GetToken().Value == "include" {
433+
if path[1].Key.GetToken().Value == "env_file" {
434+
return directoryForPrefix(documentPath, prefix, documentPath.Folder, false), false
435+
}
436+
if path[1].Key.GetToken().Value == "path" {
437+
return directoryForPrefix(documentPath, prefix, documentPath.Folder, false), false
438+
}
439+
}
440+
} else if len(path) == 3 {
421441
switch path[0].Key.GetToken().Value {
422442
case "services":
423443
// services:

internal/compose/completion_test.go

Lines changed: 150 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2835,7 +2835,12 @@ services:
28352835
},
28362836
}
28372837

2838-
composeFileURI := fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(filepath.Join(os.TempDir(), "compose.yaml")), "/"))
2838+
dir, err := os.MkdirTemp(os.TempDir(), fmt.Sprintf("%v-%v", t.Name(), time.Now().UnixMilli()))
2839+
require.NoError(t, err)
2840+
t.Cleanup(func() {
2841+
require.NoError(t, os.RemoveAll(dir))
2842+
})
2843+
composeFileURI := fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(filepath.Join(dir, "compose.yaml")), "/"))
28392844

28402845
for _, tc := range testCases {
28412846
t.Run(tc.name, func(t *testing.T) {
@@ -4967,31 +4972,6 @@ services:
49674972
}
49684973

49694974
func TestCompletion_FileStructure(t *testing.T) {
4970-
dir, err := os.MkdirTemp(os.TempDir(), fmt.Sprintf("%v-%v", t.Name(), time.Now().UnixMilli()))
4971-
require.NoError(t, err)
4972-
t.Cleanup(func() {
4973-
require.NoError(t, os.RemoveAll(dir))
4974-
})
4975-
4976-
fileStructure := []struct {
4977-
name string
4978-
isDir bool
4979-
}{
4980-
{name: "a.txt", isDir: false},
4981-
{name: "b", isDir: true},
4982-
{name: "folder", isDir: true},
4983-
{name: "folder/subfile.txt", isDir: false},
4984-
}
4985-
for _, entry := range fileStructure {
4986-
if entry.isDir {
4987-
require.NoError(t, os.Mkdir(filepath.Join(dir, entry.name), 0755))
4988-
} else {
4989-
f, err := os.Create(filepath.Join(dir, entry.name))
4990-
require.NoError(t, err)
4991-
require.NoError(t, f.Close())
4992-
}
4993-
}
4994-
49954975
testCases := []struct {
49964976
name string
49974977
content string
@@ -5116,6 +5096,44 @@ secrets:
51165096
line: 3,
51175097
character: 10,
51185098
},
5099+
{
5100+
name: "include - env_file attribute",
5101+
content: `
5102+
include:
5103+
- env_file: `,
5104+
hideFiles: false,
5105+
line: 2,
5106+
character: 14,
5107+
},
5108+
{
5109+
name: "include - env_file attribute's string array items",
5110+
content: `
5111+
include:
5112+
- env_file:
5113+
- `,
5114+
hideFiles: false,
5115+
line: 3,
5116+
character: 6,
5117+
},
5118+
{
5119+
name: "include - path attribute",
5120+
content: `
5121+
include:
5122+
- path: `,
5123+
hideFiles: false,
5124+
line: 2,
5125+
character: 10,
5126+
},
5127+
{
5128+
name: "include - path attribute's string array items",
5129+
content: `
5130+
include:
5131+
- path:
5132+
- `,
5133+
hideFiles: false,
5134+
line: 3,
5135+
character: 6,
5136+
},
51195137
}
51205138

51215139
setups := []struct {
@@ -5207,6 +5225,7 @@ secrets:
52075225
},
52085226
}
52095227

5228+
dir := createFileStructure(t)
52105229
composeFileURI := fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(filepath.Join(dir, "compose.yaml")), "/"))
52115230

52125231
for _, tc := range testCases {
@@ -5231,6 +5250,83 @@ secrets:
52315250
}
52325251
}
52335252

5253+
func TestCompletion_FileStructureMerged(t *testing.T) {
5254+
testCases := []struct {
5255+
name string
5256+
content string
5257+
line uint32
5258+
character uint32
5259+
list *protocol.CompletionList
5260+
}{
5261+
{
5262+
name: "include array item suggests attributes and file structure",
5263+
content: `
5264+
include:
5265+
- `,
5266+
line: 2,
5267+
character: 4,
5268+
list: &protocol.CompletionList{
5269+
Items: []protocol.CompletionItem{
5270+
{
5271+
Label: "a.txt",
5272+
Kind: types.CreateCompletionItemKindPointer(protocol.CompletionItemKindFile),
5273+
},
5274+
{
5275+
Label: "b",
5276+
Kind: types.CreateCompletionItemKindPointer(protocol.CompletionItemKindFolder),
5277+
},
5278+
{
5279+
Label: "env_file",
5280+
Detail: types.CreateStringPointer("array or string"),
5281+
Documentation: "Either a single string or a list of strings.",
5282+
TextEdit: textEdit("env_file:", 2, 4, 0),
5283+
InsertTextMode: types.CreateInsertTextModePointer(protocol.InsertTextModeAsIs),
5284+
InsertTextFormat: types.CreateInsertTextFormatPointer(protocol.InsertTextFormatSnippet),
5285+
},
5286+
{
5287+
Label: "folder",
5288+
Kind: types.CreateCompletionItemKindPointer(protocol.CompletionItemKindFolder),
5289+
},
5290+
{
5291+
Label: "path",
5292+
Detail: types.CreateStringPointer("array or string"),
5293+
Documentation: "Either a single string or a list of strings.",
5294+
TextEdit: textEdit("path:", 2, 4, 0),
5295+
InsertTextMode: types.CreateInsertTextModePointer(protocol.InsertTextModeAsIs),
5296+
InsertTextFormat: types.CreateInsertTextFormatPointer(protocol.InsertTextFormatSnippet),
5297+
},
5298+
{
5299+
Label: "project_directory",
5300+
Detail: types.CreateStringPointer("string"),
5301+
Documentation: "Path to resolve relative paths set in the Compose file",
5302+
TextEdit: textEdit("project_directory: ", 2, 4, 0),
5303+
InsertTextMode: types.CreateInsertTextModePointer(protocol.InsertTextModeAsIs),
5304+
InsertTextFormat: types.CreateInsertTextFormatPointer(protocol.InsertTextFormatSnippet),
5305+
},
5306+
},
5307+
},
5308+
},
5309+
}
5310+
5311+
dir := createFileStructure(t)
5312+
composeFileURI := fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(filepath.Join(dir, "compose.yaml")), "/"))
5313+
5314+
for _, tc := range testCases {
5315+
t.Run(tc.name, func(t *testing.T) {
5316+
manager := document.NewDocumentManager()
5317+
doc := document.NewComposeDocument(manager, uri.URI(composeFileURI), 1, []byte(tc.content))
5318+
list, err := Completion(context.Background(), &protocol.CompletionParams{
5319+
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
5320+
TextDocument: protocol.TextDocumentIdentifier{URI: composeFileURI},
5321+
Position: protocol.Position{Line: tc.line, Character: tc.character},
5322+
},
5323+
}, manager, doc)
5324+
require.NoError(t, err)
5325+
require.Equal(t, tc.list, list)
5326+
})
5327+
}
5328+
}
5329+
52345330
func textEdit(newText string, line, character, prefixLength protocol.UInteger) protocol.TextEdit {
52355331
return protocol.TextEdit{
52365332
NewText: newText,
@@ -5246,3 +5342,31 @@ func textEdit(newText string, line, character, prefixLength protocol.UInteger) p
52465342
},
52475343
}
52485344
}
5345+
5346+
func createFileStructure(t *testing.T) string {
5347+
dir, err := os.MkdirTemp(os.TempDir(), fmt.Sprintf("%v-%v", t.Name(), time.Now().UnixMilli()))
5348+
require.NoError(t, err)
5349+
t.Cleanup(func() {
5350+
require.NoError(t, os.RemoveAll(dir))
5351+
})
5352+
5353+
fileStructure := []struct {
5354+
name string
5355+
isDir bool
5356+
}{
5357+
{name: "a.txt", isDir: false},
5358+
{name: "b", isDir: true},
5359+
{name: "folder", isDir: true},
5360+
{name: "folder/subfile.txt", isDir: false},
5361+
}
5362+
for _, entry := range fileStructure {
5363+
if entry.isDir {
5364+
require.NoError(t, os.Mkdir(filepath.Join(dir, entry.name), 0755))
5365+
} else {
5366+
f, err := os.Create(filepath.Join(dir, entry.name))
5367+
require.NoError(t, err)
5368+
require.NoError(t, f.Close())
5369+
}
5370+
}
5371+
return dir
5372+
}

0 commit comments

Comments
 (0)