[clangd] Add rename support.
Summary:
Make clangd handle "textDocument/rename" request. The rename
functionality comes from the "local-rename" sub-tool of clang-refactor.
Currently clangd only supports local rename (only symbol occurrences in
the main file will be renamed).
Reviewers: sammccall, ilya-biryukov
Reviewed By: sammccall
Subscribers: cfe-commits, ioeric, arphaman, mgorny
Differential Revision: https://reviews.llvm.org/D39676
llvm-svn: 317780
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index ba9336f..df85bca 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -9,6 +9,8 @@
#include "ClangdServer.h"
#include "clang/Format/Format.h"
+#include "clang/Tooling/Refactoring/RefactoringResultConsumer.h"
+#include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Tooling/CompilationDatabase.h"
@@ -51,6 +53,28 @@
return CompilerInvocation::GetResourcesPath("clangd", (void *)&Dummy);
}
+class RefactoringResultCollector final
+ : public tooling::RefactoringResultConsumer {
+public:
+ void handleError(llvm::Error Err) override {
+ assert(!Result.hasValue());
+ // FIXME: figure out a way to return better message for DiagnosticError.
+ // clangd uses llvm::toString to convert the Err to string, however, for
+ // DiagnosticError, only "clang diagnostic" will be generated.
+ Result = std::move(Err);
+ }
+
+ // Using the handle(SymbolOccurrences) from parent class.
+ using tooling::RefactoringResultConsumer::handle;
+
+ void handle(tooling::AtomicChanges SourceReplacements) override {
+ assert(!Result.hasValue());
+ Result = std::move(SourceReplacements);
+ }
+
+ Optional<Expected<tooling::AtomicChanges>> Result;
+};
+
} // namespace
size_t clangd::positionToOffset(StringRef Code, Position P) {
@@ -333,6 +357,54 @@
return formatCode(Code, File, {tooling::Range(PreviousLBracePos, Len)});
}
+Expected<std::vector<tooling::Replacement>>
+ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName) {
+ std::string Code = getDocument(File);
+ std::shared_ptr<CppFile> Resources = Units.getFile(File);
+ RefactoringResultCollector ResultCollector;
+ Resources->getAST().get()->runUnderLock([&](ParsedAST *AST) {
+ const SourceManager &SourceMgr = AST->getASTContext().getSourceManager();
+ const FileEntry *FE =
+ SourceMgr.getFileEntryForID(SourceMgr.getMainFileID());
+ if (!FE)
+ return;
+ SourceLocation SourceLocationBeg =
+ clangd::getBeginningOfIdentifier(*AST, Pos, FE);
+ tooling::RefactoringRuleContext Context(
+ AST->getASTContext().getSourceManager());
+ Context.setASTContext(AST->getASTContext());
+ auto Rename = clang::tooling::RenameOccurrences::initiate(
+ Context, SourceRange(SourceLocationBeg), NewName.str());
+ if (!Rename) {
+ ResultCollector.Result = Rename.takeError();
+ return;
+ }
+ Rename->invoke(ResultCollector, Context);
+ });
+ assert(ResultCollector.Result.hasValue());
+ if (!ResultCollector.Result.getValue())
+ return ResultCollector.Result->takeError();
+
+ std::vector<tooling::Replacement> Replacements;
+ for (const tooling::AtomicChange &Change : ResultCollector.Result->get()) {
+ tooling::Replacements ChangeReps = Change.getReplacements();
+ for (const auto &Rep : ChangeReps) {
+ // FIXME: Right now we only support renaming the main file, so we drop
+ // replacements not for the main file. In the future, we might consider to
+ // support:
+ // * rename in any included header
+ // * rename only in the "main" header
+ // * provide an error if there are symbols we won't rename (e.g.
+ // std::vector)
+ // * rename globally in project
+ // * rename in open files
+ if (Rep.getFilePath() == File)
+ Replacements.push_back(Rep);
+ }
+ }
+ return Replacements;
+}
+
std::string ClangdServer::getDocument(PathRef File) {
auto draft = DraftMgr.getDraft(File);
assert(draft.Draft && "File is not tracked, cannot get contents");