@@ -15,6 +15,12 @@ import (
1515 "github.com/santhosh-tekuri/jsonschema/v6"
1616)
1717
18+ type completionItemText struct {
19+ label string
20+ newText string
21+ documentation string
22+ }
23+
1824func prefix (line string , character int ) string {
1925 sb := strings.Builder {}
2026 for i := range character {
@@ -101,7 +107,7 @@ func Completion(ctx context.Context, params *protocol.CompletionParams, manager
101107 items = namedDependencyCompletionItems (file , path , "secrets" , "secrets" , params , protocol .UInteger (len (wordPrefix )))
102108 }
103109 isArray := array (lines [lspLine ], character - 1 )
104- nodeProps , arrayAttributes := nodeProperties (path , line , character )
110+ path , nodeProps , arrayAttributes := nodeProperties (path , line , character )
105111 if isArray != arrayAttributes {
106112 return nil , nil
107113 }
@@ -184,6 +190,7 @@ func Completion(ctx context.Context, params *protocol.CompletionParams, manager
184190 },
185191 }
186192 }
193+ item .TextEdit = modifyTextEdit (manager , u , item .TextEdit .(protocol.TextEdit ), attributeName , path )
187194 items = append (items , item )
188195 }
189196 }
@@ -196,6 +203,54 @@ func Completion(ctx context.Context, params *protocol.CompletionParams, manager
196203 return & protocol.CompletionList {Items : items }, nil
197204}
198205
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+
199254func findDependencies (file * ast.File , dependencyType string ) []string {
200255 services := []string {}
201256 for _ , documentNode := range file .Docs {
@@ -216,27 +271,18 @@ func findDependencies(file *ast.File, dependencyType string) []string {
216271 return services
217272}
218273
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 {
220275 _ , nodes := document .OpenDockerfile (context .Background (), manager , dockerfilePath )
221- items := []protocol. CompletionItem {}
276+ items := []completionItemText {}
222277 for _ , child := range nodes {
223278 if strings .EqualFold (child .Value , "FROM" ) {
224279 if child .Next != nil && child .Next .Next != nil && strings .EqualFold (child .Next .Next .Value , "AS" ) && child .Next .Next .Next != nil {
225280 buildStage := child .Next .Next .Next .Value
226281 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 ,
240286 })
241287 }
242288 }
@@ -247,19 +293,52 @@ func findBuildStages(params *protocol.CompletionParams, manager *document.Manage
247293
248294func buildTargetCompletionItems (params * protocol.CompletionParams , manager * document.Manager , path []* ast.MappingValueNode , u * url.URL , prefixLength protocol.UInteger ) ([]protocol.CompletionItem , bool ) {
249295 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+ }
257315 }
258316 }
259317 }
260318 return nil , false
261319}
262320
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+
263342func dependencyCompletionItems (file * ast.File , path []* ast.MappingValueNode , params * protocol.CompletionParams , prefixLength protocol.UInteger ) []protocol.CompletionItem {
264343 dependency := map [string ]string {
265344 "depends_on" : "services" ,
0 commit comments