diff --git a/private/buf/buflsp/completion.go b/private/buf/buflsp/completion.go index a1295f9279..355d39d43a 100644 --- a/private/buf/buflsp/completion.go +++ b/private/buf/buflsp/completion.go @@ -444,6 +444,7 @@ func completionItemsForDef(ctx context.Context, file *file, declPath []ast.DeclA tokenSpan, offset, ), + rpcRequestResponseSnippetCompletionItem(parentDef, tokenSpan), ) } } else if showReturnKeyword { @@ -867,6 +868,50 @@ func keywordToCompletionItem( } } +// rpcRequestResponseSnippetCompletionItem returns a completion item for the common RPC pattern. +// This generates an RPC method declaration with Request/Response message types. +func rpcRequestResponseSnippetCompletionItem( + parentDef ast.DeclDef, + tokenSpan source.Span, +) iter.Seq[protocol.CompletionItem] { + return func(yield func(protocol.CompletionItem) bool) { + // Create expanded span from cursor (offset) to end of service + serviceEndOffset := parentDef.Span().End + expandedSpan := source.Span{ + File: tokenSpan.File, + Start: tokenSpan.End, + End: serviceEndOffset, + } + body := expandedSpan.Text() + + // The snippet uses ${1:Name} for linked placeholders. + // Because everything is in one TextEdit, placeholders work across RPC and messages. + var sb strings.Builder + sb.WriteString("rpc ${1:Name}(${1:Name}Request) returns (${1:Name}Response);") + sb.WriteString(body) + sb.WriteString("\n\nmessage ${1:Name}Request {}\n\nmessage ${1:Name}Response {}") + + yield(protocol.CompletionItem{ + Label: "rpc Name(NameRequest) returns (NameResponse)", + Kind: protocol.CompletionItemKindSnippet, + InsertTextFormat: protocol.InsertTextFormatSnippet, + InsertTextMode: protocol.InsertTextModeAsIs, + Detail: "Insert RPC method with Request and Response messages", + Documentation: "Creates an RPC method declaration and corresponding Request/Response message types", + TextEdit: &protocol.TextEdit{ + Range: reportSpanToProtocolRange(tokenSpan), + NewText: sb.String(), + }, + AdditionalTextEdits: []protocol.TextEdit{ + { + Range: reportSpanToProtocolRange(expandedSpan), + NewText: "", // Delete from cursor to end of service + }, + }, + }) + } +} + // typeReferencesToCompletionItems returns completion items for user-defined types (messages, enums, etc). func typeReferencesToCompletionItems( current *file,