Skip to content

Commit f581dd8

Browse files
authored
Implement textDocument/rename and textDocument/prepareRename for LSP (#4216)
1 parent 63d9a41 commit f581dd8

File tree

4 files changed

+97
-3
lines changed

4 files changed

+97
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## [Unreleased]
44

5-
- No changes yet.
5+
- Add `textDocument/rename` and `textDocument/prepareRename` support for `buf lsp serve`.
66

77
## [v1.61.0] - 2025-11-25
88

private/buf/buflsp/file.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -588,10 +588,12 @@ func (f *file) irToSymbols(irSymbol ir.Symbol) ([]*symbol, []*symbol) {
588588
resolved = append(resolved, method)
589589

590590
input, _ := irSymbol.AsMethod().Input()
591+
// Method input must be a single message type.
592+
inputAST := irSymbol.AsMethod().AST().AsMethod().Signature.Inputs().At(0)
591593
inputSym := &symbol{
592594
ir: irSymbol,
593595
file: f,
594-
span: irSymbol.AsMethod().AST().AsMethod().Signature.Inputs().Span(),
596+
span: inputAST.RemovePrefixes().Span(), // We always strip prefixes in case of streaming.
595597
kind: &reference{
596598
def: input.AST(), // Only messages can be method inputs and outputs
597599
fullName: input.FullName(),
@@ -600,10 +602,12 @@ func (f *file) irToSymbols(irSymbol ir.Symbol) ([]*symbol, []*symbol) {
600602
unresolved = append(unresolved, inputSym)
601603

602604
output, _ := irSymbol.AsMethod().Output()
605+
// Method output must be a single message type.
606+
outputAST := irSymbol.AsMethod().AST().AsMethod().Signature.Outputs().At(0)
603607
outputSym := &symbol{
604608
ir: irSymbol,
605609
file: f,
606-
span: irSymbol.AsMethod().AST().AsMethod().Signature.Outputs().Span(),
610+
span: outputAST.RemovePrefixes().Span(), // We always strip prefixes in case of streaming.
607611
kind: &reference{
608612
def: output.AST(), // Only messages can be method inputs and outputs
609613
fullName: output.FullName(),

private/buf/buflsp/server.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ func (s *server) Initialize(
152152
DocumentFormattingProvider: true,
153153
HoverProvider: true,
154154
ReferencesProvider: &protocol.ReferenceOptions{},
155+
RenameProvider: &protocol.RenameOptions{
156+
PrepareProvider: true,
157+
},
155158
SemanticTokensProvider: &SemanticTokensOptions{
156159
Legend: SemanticTokensLegend{
157160
TokenTypes: semanticTypeLegend,
@@ -559,6 +562,38 @@ func (s *server) DocumentSymbol(ctx context.Context, params *protocol.DocumentSy
559562
return anyResults, nil
560563
}
561564

565+
// PrepareRename is the entry point for checking workspace wide renaming of a symbol.
566+
//
567+
// If a symbol can be renamed, PrepareRename will return the range for the rename. Returning
568+
// an empty range indicates that the requested position cannot be renamed and the client
569+
// will handle providing feedback to the user.
570+
//
571+
// Supported symbol types for renaming are [referenceable], [static], and [reference].
572+
func (s *server) PrepareRename(ctx context.Context, params *protocol.PrepareRenameParams) (*protocol.Range, error) {
573+
symbol := s.getSymbol(ctx, params.TextDocument.URI, params.Position)
574+
if symbol == nil {
575+
return nil, nil
576+
}
577+
switch symbol.kind.(type) {
578+
case *referenceable, *static, *reference:
579+
rnge := reportSpanToProtocolRange(symbol.span)
580+
return &rnge, nil
581+
}
582+
return nil, nil
583+
}
584+
585+
// Rename is the entry point for workspace wide renaming of a symbol.
586+
func (s *server) Rename(
587+
ctx context.Context,
588+
params *protocol.RenameParams,
589+
) (*protocol.WorkspaceEdit, error) {
590+
symbol := s.getSymbol(ctx, params.TextDocument.URI, params.Position)
591+
if symbol == nil {
592+
return nil, nil
593+
}
594+
return symbol.Rename(params.NewName)
595+
}
596+
562597
// getSymbol is a helper function that gets the *[symbol] for the given [protocol.URI] and
563598
// [protocol.Position].
564599
func (s *server) getSymbol(

private/buf/buflsp/symbol.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,61 @@ func (s *symbol) GetSymbolInformation() protocol.SymbolInformation {
307307
}
308308
}
309309

310+
// Rename returns the [protocol.WorkspaceEdit] for renaming the symbol.
311+
func (s *symbol) Rename(newName string) (*protocol.WorkspaceEdit, error) {
312+
var edits protocol.WorkspaceEdit
313+
switch s.kind.(type) {
314+
case *referenceable:
315+
changes, err := renameChangesForReferenceableSymbol(s, newName)
316+
if err != nil {
317+
return nil, err
318+
}
319+
edits.Changes = changes
320+
case *static:
321+
edits.Changes = map[protocol.DocumentURI][]protocol.TextEdit{
322+
s.file.uri: {{
323+
Range: reportSpanToProtocolRange(s.span),
324+
NewText: newName,
325+
}},
326+
}
327+
case *reference:
328+
// For references, we attempt to rename the definition symbol, if resolved. This would
329+
// include this reference symbol.
330+
if s.def != nil {
331+
changes, err := renameChangesForReferenceableSymbol(s.def, newName)
332+
if err != nil {
333+
return nil, err
334+
}
335+
edits.Changes = changes
336+
}
337+
}
338+
// All other symbol types (options, imports, built-ins, and tags) cannot be renamed.
339+
return &edits, nil
340+
}
341+
342+
// renameChangesForReferenceableSymbol is a helper for getting all rename changes for the
343+
// given referenceable symbol.
344+
func renameChangesForReferenceableSymbol(s *symbol, newName string) (map[protocol.DocumentURI][]protocol.TextEdit, error) {
345+
// At minimum, we would rename the symbol itself.
346+
changes := map[protocol.DocumentURI][]protocol.TextEdit{
347+
s.file.uri: {{
348+
Range: reportSpanToProtocolRange(s.span),
349+
NewText: newName,
350+
}},
351+
}
352+
if def, ok := s.def.kind.(*referenceable); ok {
353+
for _, reference := range def.references {
354+
changes[reference.file.uri] = append(changes[reference.file.uri], protocol.TextEdit{
355+
Range: reportSpanToProtocolRange(reference.span),
356+
NewText: newName,
357+
})
358+
}
359+
} else {
360+
return nil, fmt.Errorf("attempting to rename a non-referenceble symbol as a referenceable symbol: %v", s)
361+
}
362+
return changes, nil
363+
}
364+
310365
func protowireTypeForPredeclared(name predeclared.Name) protowire.Type {
311366
switch name {
312367
case predeclared.Bool, predeclared.Int32, predeclared.Int64, predeclared.UInt32,

0 commit comments

Comments
 (0)