Skip to content

Commit 601ce2a

Browse files
authored
LSP fix triggers for autocomplete (#4126)
1 parent 6fe2e10 commit 601ce2a

File tree

2 files changed

+85
-10
lines changed

2 files changed

+85
-10
lines changed

private/buf/buflsp/completion.go

Lines changed: 84 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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

817892
func 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.

private/buf/buflsp/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ func (s *server) Initialize(
122122
},
123123
CompletionProvider: &protocol.CompletionOptions{
124124
ResolveProvider: true,
125-
TriggerCharacters: []string{" ", ".", "(", "\"", "/"},
125+
TriggerCharacters: []string{".", "\"", "/"},
126126
},
127127
DefinitionProvider: &protocol.DefinitionOptions{},
128128
TypeDefinitionProvider: &protocol.TypeDefinitionOptions{},

0 commit comments

Comments
 (0)