Skip to content

Commit 0f0cbcc

Browse files
committed
[clangd] Extend the rename API.
several changes: - return a structure result in rename API; - prepareRename now returns more information (main-file occurrences); - remove the duplicated detecting-touch-identifier code in prepareRename (which is implemented in rename API); Differential Revision: https://reviews.llvm.org/D88634
1 parent fa59135 commit 0f0cbcc

File tree

10 files changed

+159
-86
lines changed

10 files changed

+159
-86
lines changed

clang-tools-extra/clangd/ClangdLSPServer.cpp

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -793,8 +793,13 @@ void ClangdLSPServer::onWorkspaceSymbol(
793793

794794
void ClangdLSPServer::onPrepareRename(const TextDocumentPositionParams &Params,
795795
Callback<llvm::Optional<Range>> Reply) {
796-
Server->prepareRename(Params.textDocument.uri.file(), Params.position,
797-
Opts.Rename, std::move(Reply));
796+
Server->prepareRename(
797+
Params.textDocument.uri.file(), Params.position, Opts.Rename,
798+
[Reply = std::move(Reply)](llvm::Expected<RenameResult> Result) mutable {
799+
if (!Result)
800+
return Reply(Result.takeError());
801+
return Reply(std::move(Result->Target));
802+
});
798803
}
799804

800805
void ClangdLSPServer::onRename(const RenameParams &Params,
@@ -806,14 +811,14 @@ void ClangdLSPServer::onRename(const RenameParams &Params,
806811
Server->rename(
807812
File, Params.position, Params.newName, Opts.Rename,
808813
[File, Params, Reply = std::move(Reply),
809-
this](llvm::Expected<FileEdits> Edits) mutable {
810-
if (!Edits)
811-
return Reply(Edits.takeError());
812-
if (auto Err = validateEdits(DraftMgr, *Edits))
814+
this](llvm::Expected<RenameResult> R) mutable {
815+
if (!R)
816+
return Reply(R.takeError());
817+
if (auto Err = validateEdits(DraftMgr, R->GlobalChanges))
813818
return Reply(std::move(Err));
814819
WorkspaceEdit Result;
815820
Result.changes.emplace();
816-
for (const auto &Rep : *Edits) {
821+
for (const auto &Rep : R->GlobalChanges) {
817822
(*Result.changes)[URI::createFile(Rep.first()).toString()] =
818823
Rep.second.asTextEdits();
819824
}

clang-tools-extra/clangd/ClangdServer.cpp

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -400,46 +400,35 @@ void ClangdServer::formatOnType(PathRef File, llvm::StringRef Code,
400400

401401
void ClangdServer::prepareRename(PathRef File, Position Pos,
402402
const RenameOptions &RenameOpts,
403-
Callback<llvm::Optional<Range>> CB) {
403+
Callback<RenameResult> CB) {
404404
auto Action = [Pos, File = File.str(), CB = std::move(CB), RenameOpts,
405405
this](llvm::Expected<InputsAndAST> InpAST) mutable {
406406
if (!InpAST)
407407
return CB(InpAST.takeError());
408-
auto &AST = InpAST->AST;
409-
const auto &SM = AST.getSourceManager();
410-
auto Loc = sourceLocationInMainFile(SM, Pos);
411-
if (!Loc)
412-
return CB(Loc.takeError());
413-
const auto *TouchingIdentifier =
414-
spelledIdentifierTouching(*Loc, AST.getTokens());
415-
if (!TouchingIdentifier)
416-
return CB(llvm::None); // no rename on non-identifiers.
417-
418-
auto Range = halfOpenToRange(
419-
SM, CharSourceRange::getCharRange(TouchingIdentifier->location(),
420-
TouchingIdentifier->endLocation()));
421-
422-
if (RenameOpts.AllowCrossFile)
423-
// FIXME: we now assume cross-file rename always succeeds, revisit this.
424-
return CB(Range);
425-
426-
// Performing the local rename isn't substantially more expensive than
427-
// doing an AST-based check, so we just rename and throw away the results.
428-
auto Changes = clangd::rename({Pos, "dummy", AST, File, Index, RenameOpts,
429-
/*GetDirtyBuffer=*/nullptr});
430-
if (!Changes) {
408+
// prepareRename is latency-sensitive:
409+
// - for single-file rename, performing rename isn't substantially more
410+
// expensive than doing an AST-based check (the index is used to see if
411+
// the rename is complete);
412+
// - for cross-file rename, we deliberately pass a nullptr index to save
413+
// the cost, thus the result may be incomplete as it only contains
414+
// main-file occurrences;
415+
auto Results = clangd::rename({Pos, /*NewName*/ "", InpAST->AST, File,
416+
RenameOpts.AllowCrossFile ? nullptr : Index,
417+
RenameOpts});
418+
if (!Results) {
431419
// LSP says to return null on failure, but that will result in a generic
432420
// failure message. If we send an LSP error response, clients can surface
433421
// the message to users (VSCode does).
434-
return CB(Changes.takeError());
422+
return CB(Results.takeError());
435423
}
436-
return CB(Range);
424+
return CB(*Results);
437425
};
438426
WorkScheduler.runWithAST("PrepareRename", File, std::move(Action));
439427
}
440428

441429
void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName,
442-
const RenameOptions &Opts, Callback<FileEdits> CB) {
430+
const RenameOptions &Opts,
431+
Callback<RenameResult> CB) {
443432
// A snapshot of all file dirty buffers.
444433
llvm::StringMap<std::string> Snapshot = WorkScheduler.getAllFileContents();
445434
auto Action = [File = File.str(), NewName = NewName.str(), Pos, Opts,
@@ -457,24 +446,24 @@ void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName,
457446
return llvm::None;
458447
return It->second;
459448
};
460-
auto Edits = clangd::rename(
449+
auto R = clangd::rename(
461450
{Pos, NewName, InpAST->AST, File, Index, Opts, GetDirtyBuffer});
462-
if (!Edits)
463-
return CB(Edits.takeError());
451+
if (!R)
452+
return CB(R.takeError());
464453

465454
if (Opts.WantFormat) {
466455
auto Style = getFormatStyleForFile(File, InpAST->Inputs.Contents,
467456
*InpAST->Inputs.TFS);
468457
llvm::Error Err = llvm::Error::success();
469-
for (auto &E : *Edits)
458+
for (auto &E : R->GlobalChanges)
470459
Err =
471460
llvm::joinErrors(reformatEdit(E.getValue(), Style), std::move(Err));
472461

473462
if (Err)
474463
return CB(std::move(Err));
475464
}
476-
RenameFiles.record(Edits->size());
477-
return CB(std::move(*Edits));
465+
RenameFiles.record(R->GlobalChanges.size());
466+
return CB(*R);
478467
};
479468
WorkScheduler.runWithAST("Rename", File, std::move(Action));
480469
}

clang-tools-extra/clangd/ClangdServer.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,17 +273,20 @@ class ClangdServer {
273273
StringRef TriggerText, Callback<std::vector<TextEdit>> CB);
274274

275275
/// Test the validity of a rename operation.
276+
///
277+
/// The returned result describes edits in the main-file only (all
278+
/// occurrences of the renamed symbol are simply deleted.
276279
void prepareRename(PathRef File, Position Pos,
277280
const RenameOptions &RenameOpts,
278-
Callback<llvm::Optional<Range>> CB);
281+
Callback<RenameResult> CB);
279282

280283
/// Rename all occurrences of the symbol at the \p Pos in \p File to
281284
/// \p NewName.
282285
/// If WantFormat is false, the final TextEdit will be not formatted,
283286
/// embedders could use this method to get all occurrences of the symbol (e.g.
284287
/// highlighting them in prepare stage).
285288
void rename(PathRef File, Position Pos, llvm::StringRef NewName,
286-
const RenameOptions &Opts, Callback<FileEdits> CB);
289+
const RenameOptions &Opts, Callback<RenameResult> CB);
287290

288291
struct TweakRef {
289292
std::string ID; /// ID to pass for applyTweak.

clang-tools-extra/clangd/SourceCode.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ struct Edit {
181181
tooling::Replacements Replacements;
182182
std::string InitialCode;
183183

184+
Edit() = default;
185+
184186
Edit(llvm::StringRef Code, tooling::Replacements Reps)
185187
: Replacements(std::move(Reps)), InitialCode(Code) {}
186188

clang-tools-extra/clangd/refactor/Rename.cpp

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,6 @@ llvm::Optional<ReasonToReject> renameable(const NamedDecl &RenameDecl,
182182
}
183183

184184
assert(CrossFile);
185-
if (!Index)
186-
return ReasonToReject::NoIndexProvided;
187185

188186
// FIXME: Renaming virtual methods requires to rename all overridens in
189187
// subclasses, our index doesn't have this information.
@@ -427,7 +425,7 @@ void findNearMiss(
427425

428426
} // namespace
429427

430-
llvm::Expected<FileEdits> rename(const RenameInputs &RInputs) {
428+
llvm::Expected<RenameResult> rename(const RenameInputs &RInputs) {
431429
trace::Span Tracer("Rename flow");
432430
const auto &Opts = RInputs.Opts;
433431
ParsedAST &AST = RInputs.AST;
@@ -456,9 +454,13 @@ llvm::Expected<FileEdits> rename(const RenameInputs &RInputs) {
456454
return Loc.takeError();
457455
const syntax::Token *IdentifierToken =
458456
spelledIdentifierTouching(*Loc, AST.getTokens());
457+
459458
// Renames should only triggered on identifiers.
460459
if (!IdentifierToken)
461460
return makeError(ReasonToReject::NoSymbolFound);
461+
Range CurrentIdentifier = halfOpenToRange(
462+
SM, CharSourceRange::getCharRange(IdentifierToken->location(),
463+
IdentifierToken->endLocation()));
462464
// FIXME: Renaming macros is not supported yet, the macro-handling code should
463465
// be moved to rename tooling library.
464466
if (locateMacroAt(*IdentifierToken, AST.getPreprocessor()))
@@ -489,32 +491,40 @@ llvm::Expected<FileEdits> rename(const RenameInputs &RInputs) {
489491
auto MainFileRenameEdit = renameWithinFile(AST, RenameDecl, RInputs.NewName);
490492
if (!MainFileRenameEdit)
491493
return MainFileRenameEdit.takeError();
494+
RenameResult Result;
495+
Result.Target = CurrentIdentifier;
496+
Edit MainFileEdits = Edit(MainFileCode, std::move(*MainFileRenameEdit));
497+
llvm::for_each(MainFileEdits.asTextEdits(), [&Result](const TextEdit &TE) {
498+
Result.LocalChanges.push_back(TE.range);
499+
});
492500

493501
// return the main file edit if this is a within-file rename or the symbol
494502
// being renamed is function local.
495503
if (!Opts.AllowCrossFile || RenameDecl.getParentFunctionOrMethod()) {
496-
return FileEdits(
497-
{std::make_pair(RInputs.MainFilePath,
498-
Edit{MainFileCode, std::move(*MainFileRenameEdit)})});
504+
Result.GlobalChanges = FileEdits(
505+
{std::make_pair(RInputs.MainFilePath, std::move(MainFileEdits))});
506+
return Result;
499507
}
500508

501-
FileEdits Results;
502-
// Renameable safely guards us that at this point we are renaming a local
503-
// symbol if we don't have index.
504-
if (RInputs.Index) {
505-
auto OtherFilesEdits = renameOutsideFile(
506-
RenameDecl, RInputs.MainFilePath, RInputs.NewName, *RInputs.Index,
507-
Opts.LimitFiles == 0 ? std::numeric_limits<size_t>::max()
508-
: Opts.LimitFiles,
509-
GetFileContent);
510-
if (!OtherFilesEdits)
511-
return OtherFilesEdits.takeError();
512-
Results = std::move(*OtherFilesEdits);
509+
// If the index is nullptr, we don't know the completeness of the result, so
510+
// we don't populate the field GlobalChanges.
511+
if (!RInputs.Index) {
512+
assert(Result.GlobalChanges.empty() && Opts.AllowCrossFile);
513+
return Result;
513514
}
515+
516+
auto OtherFilesEdits = renameOutsideFile(
517+
RenameDecl, RInputs.MainFilePath, RInputs.NewName, *RInputs.Index,
518+
Opts.LimitFiles == 0 ? std::numeric_limits<size_t>::max()
519+
: Opts.LimitFiles,
520+
GetFileContent);
521+
if (!OtherFilesEdits)
522+
return OtherFilesEdits.takeError();
523+
Result.GlobalChanges = *OtherFilesEdits;
514524
// Attach the rename edits for the main file.
515-
Results.try_emplace(RInputs.MainFilePath, MainFileCode,
516-
std::move(*MainFileRenameEdit));
517-
return Results;
525+
Result.GlobalChanges.try_emplace(RInputs.MainFilePath,
526+
std::move(MainFileEdits));
527+
return Result;
518528
}
519529

520530
llvm::Expected<Edit> buildRenameEdit(llvm::StringRef AbsFilePath,

clang-tools-extra/clangd/refactor/Rename.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,20 @@ struct RenameInputs {
5555
DirtyBufferGetter GetDirtyBuffer = nullptr;
5656
};
5757

58+
struct RenameResult {
59+
// The range of the symbol that the user can attempt to rename.
60+
Range Target;
61+
// Rename occurrences for the current main file.
62+
std::vector<Range> LocalChanges;
63+
// Complete edits for the rename, including LocalChanges.
64+
// If the full set of changes is unknown, this field is empty.
65+
FileEdits GlobalChanges;
66+
};
67+
5868
/// Renames all occurrences of the symbol. The result edits are unformatted.
5969
/// If AllowCrossFile is false, returns an error if rename a symbol that's used
6070
/// in another file (per the index).
61-
llvm::Expected<FileEdits> rename(const RenameInputs &RInputs);
71+
llvm::Expected<RenameResult> rename(const RenameInputs &RInputs);
6272

6373
/// Generates rename edits that replaces all given occurrences with the
6474
/// NewName.

clang-tools-extra/clangd/test/rename.test

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@
2121
# CHECK-NEXT: }
2222
---
2323
{"jsonrpc":"2.0","id":2,"method":"textDocument/prepareRename","params":{"textDocument":{"uri":"test:///foo.cpp"},"position":{"line":0,"character":2}}}
24-
# CHECK: "id": 2,
25-
# CHECK-NEXT: "jsonrpc": "2.0",
26-
# CHECK-NEXT: "result": null
24+
# CHECK: "error": {
25+
# CHECK-NEXT: "code": -32001,
26+
# CHECK-NEXT: "message": "Cannot rename symbol: there is no symbol at the given location"
27+
# CHECK-NEXT: },
28+
# CHECK-NEXT: "id": 2,
29+
# CHECK-NEXT: "jsonrpc": "2.0"
2730
---
2831
{"jsonrpc":"2.0","id":4,"method":"textDocument/rename","params":{"textDocument":{"uri":"test:///foo.cpp"},"position":{"line":0,"character":2},"newName":"bar"}}
2932
# CHECK: "error": {

0 commit comments

Comments
 (0)