@@ -15,6 +15,12 @@ import (
15
15
"github.com/santhosh-tekuri/jsonschema/v6"
16
16
)
17
17
18
+ type completionItemText struct {
19
+ label string
20
+ newText string
21
+ documentation string
22
+ }
23
+
18
24
func prefix (line string , character int ) string {
19
25
sb := strings.Builder {}
20
26
for i := range character {
@@ -101,7 +107,7 @@ func Completion(ctx context.Context, params *protocol.CompletionParams, manager
101
107
items = namedDependencyCompletionItems (file , path , "secrets" , "secrets" , params , protocol .UInteger (len (wordPrefix )))
102
108
}
103
109
isArray := array (lines [lspLine ], character - 1 )
104
- nodeProps , arrayAttributes := nodeProperties (path , line , character )
110
+ path , nodeProps , arrayAttributes := nodeProperties (path , line , character )
105
111
if isArray != arrayAttributes {
106
112
return nil , nil
107
113
}
@@ -184,6 +190,7 @@ func Completion(ctx context.Context, params *protocol.CompletionParams, manager
184
190
},
185
191
}
186
192
}
193
+ item .TextEdit = modifyTextEdit (manager , u , item .TextEdit .(protocol.TextEdit ), attributeName , path )
187
194
items = append (items , item )
188
195
}
189
196
}
@@ -196,6 +203,54 @@ func Completion(ctx context.Context, params *protocol.CompletionParams, manager
196
203
return & protocol.CompletionList {Items : items }, nil
197
204
}
198
205
206
+ func createChoiceSnippetText (itemTexts []completionItemText ) string {
207
+ sb := strings.Builder {}
208
+ sb .WriteString ("${1|" )
209
+ for i , stage := range itemTexts {
210
+ sb .WriteString (stage .newText )
211
+ if i != len (itemTexts )- 1 {
212
+ sb .WriteString ("," )
213
+ }
214
+ }
215
+ sb .WriteString ("|}" )
216
+ return sb .String ()
217
+ }
218
+
219
+ func modifyTextEdit (manager * document.Manager , u * url.URL , edit protocol.TextEdit , attributeName string , path []* ast.MappingValueNode ) protocol.TextEdit {
220
+ if attributeName == "target" && len (path ) == 3 && path [2 ].Key .GetToken ().Value == "build" {
221
+ if _ , ok := path [2 ].Value .(* ast.NullNode ); ok {
222
+ dockerfilePath , err := types .LocalDockerfile (u )
223
+ if err == nil {
224
+ stages := findBuildStages (manager , dockerfilePath , "" )
225
+ if len (stages ) > 0 {
226
+ edit .NewText = fmt .Sprintf ("%v%v" , edit .NewText , createChoiceSnippetText (stages ))
227
+ return edit
228
+ }
229
+ }
230
+ } else if mappingNode , ok := path [2 ].Value .(* ast.MappingNode ); ok {
231
+ dockerfileAttributePath := "Dockerfile"
232
+ for _ , buildAttribute := range mappingNode .Values {
233
+ switch buildAttribute .Key .GetToken ().Value {
234
+ case "dockerfile_inline" :
235
+ return edit
236
+ case "dockerfile" :
237
+ dockerfileAttributePath = buildAttribute .Value .GetToken ().Value
238
+ }
239
+ }
240
+
241
+ dockerfilePath , err := types .AbsolutePath (u , dockerfileAttributePath )
242
+ if err == nil {
243
+ stages := findBuildStages (manager , dockerfilePath , "" )
244
+ if len (stages ) > 0 {
245
+ edit .NewText = fmt .Sprintf ("%v%v" , edit .NewText , createChoiceSnippetText (stages ))
246
+ return edit
247
+ }
248
+ }
249
+ }
250
+ }
251
+ return edit
252
+ }
253
+
199
254
func findDependencies (file * ast.File , dependencyType string ) []string {
200
255
services := []string {}
201
256
for _ , documentNode := range file .Docs {
@@ -216,27 +271,18 @@ func findDependencies(file *ast.File, dependencyType string) []string {
216
271
return services
217
272
}
218
273
219
- func findBuildStages (params * protocol. CompletionParams , manager * document.Manager , dockerfilePath , prefix string , prefixLength protocol. UInteger ) []protocol. CompletionItem {
274
+ func findBuildStages (manager * document.Manager , dockerfilePath , prefix string ) []completionItemText {
220
275
_ , nodes := document .OpenDockerfile (context .Background (), manager , dockerfilePath )
221
- items := []protocol. CompletionItem {}
276
+ items := []completionItemText {}
222
277
for _ , child := range nodes {
223
278
if strings .EqualFold (child .Value , "FROM" ) {
224
279
if child .Next != nil && child .Next .Next != nil && strings .EqualFold (child .Next .Next .Value , "AS" ) && child .Next .Next .Next != nil {
225
280
buildStage := child .Next .Next .Next .Value
226
281
if strings .HasPrefix (buildStage , prefix ) {
227
- items = append (items , protocol.CompletionItem {
228
- Label : buildStage ,
229
- Documentation : child .Next .Value ,
230
- TextEdit : protocol.TextEdit {
231
- NewText : buildStage ,
232
- Range : protocol.Range {
233
- Start : protocol.Position {
234
- Line : params .Position .Line ,
235
- Character : params .Position .Character - prefixLength ,
236
- },
237
- End : params .Position ,
238
- },
239
- },
282
+ items = append (items , completionItemText {
283
+ label : buildStage ,
284
+ documentation : child .Next .Value ,
285
+ newText : buildStage ,
240
286
})
241
287
}
242
288
}
@@ -247,19 +293,52 @@ func findBuildStages(params *protocol.CompletionParams, manager *document.Manage
247
293
248
294
func buildTargetCompletionItems (params * protocol.CompletionParams , manager * document.Manager , path []* ast.MappingValueNode , u * url.URL , prefixLength protocol.UInteger ) ([]protocol.CompletionItem , bool ) {
249
295
if len (path ) == 4 && path [2 ].Key .GetToken ().Value == "build" && path [3 ].Key .GetToken ().Value == "target" {
250
- dockerfilePath , err := types .LocalDockerfile (u )
251
- if err == nil {
252
- if _ , ok := path [3 ].Value .(* ast.NullNode ); ok {
253
- return findBuildStages (params , manager , dockerfilePath , "" , prefixLength ), true
254
- } else if prefix , ok := path [3 ].Value .(* ast.StringNode ); ok {
255
- offset := int (params .Position .Character ) - path [3 ].Value .GetToken ().Position .Column + 1
256
- return findBuildStages (params , manager , dockerfilePath , prefix .Value [0 :offset ], prefixLength ), true
296
+ if mappingNode , ok := path [2 ].Value .(* ast.MappingNode ); ok {
297
+ dockerfileAttributePath := "Dockerfile"
298
+ for _ , buildAttribute := range mappingNode .Values {
299
+ switch buildAttribute .Key .GetToken ().Value {
300
+ case "dockerfile_inline" :
301
+ return nil , true
302
+ case "dockerfile" :
303
+ dockerfileAttributePath = buildAttribute .Value .GetToken ().Value
304
+ }
305
+ }
306
+
307
+ dockerfilePath , err := types .AbsolutePath (u , dockerfileAttributePath )
308
+ if err == nil {
309
+ if _ , ok := path [3 ].Value .(* ast.NullNode ); ok {
310
+ return createBuildStageItems (params , manager , dockerfilePath , "" , prefixLength ), true
311
+ } else if prefix , ok := path [3 ].Value .(* ast.StringNode ); ok {
312
+ offset := int (params .Position .Character ) - path [3 ].Value .GetToken ().Position .Column + 1
313
+ return createBuildStageItems (params , manager , dockerfilePath , prefix .Value [0 :offset ], prefixLength ), true
314
+ }
257
315
}
258
316
}
259
317
}
260
318
return nil , false
261
319
}
262
320
321
+ func createBuildStageItems (params * protocol.CompletionParams , manager * document.Manager , dockerfilePath , prefix string , prefixLength protocol.UInteger ) []protocol.CompletionItem {
322
+ items := []protocol.CompletionItem {}
323
+ for _ , itemText := range findBuildStages (manager , dockerfilePath , prefix ) {
324
+ items = append (items , protocol.CompletionItem {
325
+ Label : itemText .label ,
326
+ Documentation : itemText .documentation ,
327
+ TextEdit : protocol.TextEdit {
328
+ NewText : itemText .newText ,
329
+ Range : protocol.Range {
330
+ Start : protocol.Position {
331
+ Line : params .Position .Line ,
332
+ Character : params .Position .Character - prefixLength ,
333
+ },
334
+ End : params .Position ,
335
+ },
336
+ },
337
+ })
338
+ }
339
+ return items
340
+ }
341
+
263
342
func dependencyCompletionItems (file * ast.File , path []* ast.MappingValueNode , params * protocol.CompletionParams , prefixLength protocol.UInteger ) []protocol.CompletionItem {
264
343
dependency := map [string ]string {
265
344
"depends_on" : "services" ,
0 commit comments