@@ -94,7 +94,7 @@ func completionItemsForDeclPath(ctx context.Context, file *file, declPath []ast.
9494 case ast .DeclKindDef :
9595 return completionItemsForDef (ctx , file , declPath , decl .AsDef (), position )
9696 case ast .DeclKindSyntax :
97- return completionItemsForSyntax (ctx , file , decl .AsSyntax ())
97+ return completionItemsForSyntax (ctx , file , decl .AsSyntax (), position )
9898 case ast .DeclKindPackage :
9999 return completionItemsForPackage (ctx , file , decl .AsPackage ())
100100 case ast .DeclKindImport :
@@ -106,13 +106,68 @@ func completionItemsForDeclPath(ctx context.Context, file *file, declPath []ast.
106106}
107107
108108// completionItemsForSyntax returns the completion items for the files syntax.
109- func completionItemsForSyntax (ctx context.Context , file * file , syntaxDecl ast.DeclSyntax ) []protocol.CompletionItem {
109+ func completionItemsForSyntax (ctx context.Context , file * file , syntaxDecl ast.DeclSyntax , position protocol. Position ) []protocol.CompletionItem {
110110 file .lsp .logger .DebugContext (ctx , "completion: syntax declaration" , slog .Bool ("is_edition" , syntaxDecl .IsEdition ()))
111111
112- var prefix string
112+ positionLocation := file .file .InverseLocation (int (position .Line )+ 1 , int (position .Character )+ 1 , positionalEncoding )
113+ offset := positionLocation .Offset
114+
115+ valueSpan := syntaxDecl .Value ().Span ()
116+ start , end := valueSpan .Start , valueSpan .End
117+ valueText := valueSpan .Text ()
118+
119+ // Break on newlines to split an unintended capture.
120+ if index := strings .IndexByte (valueText , '\n' ); index != - 1 {
121+ end = start + index
122+ valueText = valueText [:index ]
123+ }
124+
125+ if start > offset || offset > end {
126+ file .lsp .logger .DebugContext (
127+ ctx , "completion: syntax outside value" ,
128+ slog .String ("value" , valueSpan .Text ()),
129+ slog .Int ("start" , start ),
130+ slog .Int ("offset" , offset ),
131+ slog .Int ("end" , end ),
132+ slog .Int ("line" , int (position .Line )),
133+ slog .Int ("character" , int (position .Character )),
134+ )
135+ return nil // outside value
136+ }
137+
138+ index := offset - start
139+ prefix := valueText [:index ]
140+ suffix := valueText [index :]
141+
142+ var additionalTextEdits []protocol.TextEdit
113143 if syntaxDecl .Equals ().IsZero () {
114- prefix += "= "
144+ valueRange := reportSpanToProtocolRange (valueSpan )
145+ additionalTextEdits = append (additionalTextEdits , protocol.TextEdit {
146+ NewText : "= " ,
147+ // Insert before value.
148+ Range : protocol.Range {
149+ Start : valueRange .Start ,
150+ End : valueRange .Start ,
151+ },
152+ })
153+ }
154+ if syntaxDecl .Semicolon ().IsZero () {
155+ additionalTextEdits = append (additionalTextEdits , protocol.TextEdit {
156+ NewText : ";" ,
157+ // End of line.
158+ Range : protocol.Range {
159+ Start : protocol.Position {
160+ Line : position .Line ,
161+ Character : math .MaxInt32 - 1 ,
162+ },
163+ End : protocol.Position {
164+ Line : position .Line ,
165+ Character : math .MaxUint32 ,
166+ },
167+ },
168+ })
115169 }
170+
116171 var syntaxes iter.Seq [syntax.Syntax ]
117172 if syntaxDecl .IsEdition () {
118173 syntaxes = syntax .Editions ()
@@ -124,9 +179,27 @@ func completionItemsForSyntax(ctx context.Context, file *file, syntaxDecl ast.De
124179 }
125180 var items []protocol.CompletionItem
126181 for syntax := range syntaxes {
182+ suggest := fmt .Sprintf ("%q" , syntax )
183+ if ! strings .HasPrefix (suggest , prefix ) || ! strings .HasSuffix (suggest , suffix ) {
184+ file .lsp .logger .Debug ("completion: skipping on prefix/suffix" ,
185+ slog .String ("value" , valueSpan .Text ()),
186+ slog .String ("suggest" , suggest ),
187+ slog .String ("prefix" , prefix ),
188+ slog .String ("suffix" , suffix ),
189+ )
190+ continue
191+ }
127192 items = append (items , protocol.CompletionItem {
128- Label : fmt . Sprintf ( "%s%q;" , prefix , syntax ),
193+ Label : syntax . String ( ),
129194 Kind : protocol .CompletionItemKindValue ,
195+ TextEdit : & protocol.TextEdit {
196+ Range : protocol.Range {
197+ Start : position ,
198+ End : position ,
199+ },
200+ NewText : suggest [len (prefix ) : len (suggest )- len (suffix )],
201+ },
202+ AdditionalTextEdits : additionalTextEdits ,
130203 })
131204 }
132205 return items
@@ -319,7 +392,7 @@ func completionItemsForField(ctx context.Context, file *file, declPath []ast.Dec
319392 }
320393
321394 typeSpan := def .Type ().Span ()
322- if offsetInSpan (typeSpan , offset ) {
395+ if ! offsetInSpan (typeSpan , offset ) {
323396 file .lsp .logger .DebugContext (
324397 ctx , "completion: field outside definition" ,
325398 slog .String ("kind" , parent .Kind ().String ()),
@@ -641,7 +714,9 @@ func typeReferencesToCompletionItems(
641714 packagePrefix := string (current .ir .Package ()) + "."
642715 return func (yield func (protocol.CompletionItem ) bool ) {
643716 editRange := reportSpanToProtocolRange (span )
644- prefix , suffix := splitSpan (span , offset )
717+ prefix , _ := splitSpan (span , offset )
718+ // Prefix filter on the trigger character '.', if present.
719+ prefix = prefix [:strings .LastIndexByte (prefix , '.' )+ 1 ]
645720 for _ , symbol := range fileSymbolTypesIter {
646721 // We only support types in this completion instance, and not scalar values, which leaves us
647722 // with messages and enums.
@@ -660,7 +735,7 @@ func typeReferencesToCompletionItems(
660735 } else if strings .HasPrefix (label , packagePrefix ) {
661736 label = label [len (packagePrefix ):]
662737 }
663- if ! strings .HasPrefix (label , prefix ) || ! strings . HasSuffix ( label , suffix ) {
738+ if ! strings .HasPrefix (label , prefix ) {
664739 continue
665740 }
666741 var isDeprecated bool
@@ -815,7 +890,7 @@ func splitSpan(span report.Span, offset int) (prefix string, suffix string) {
815890}
816891
817892func offsetInSpan (span report.Span , offset int ) bool {
818- return span .Start > offset || offset > span .End
893+ return span .Start <= offset && offset <= span .End // End is inclusive for completions_
819894}
820895
821896// isNewlineOrEndOfSpan returns true if this offset is separated be a newline or at the end of the span.
0 commit comments