diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -92,6 +92,8 @@ void onCommand(const ExecuteCommandParams &, Callback); void onWorkspaceSymbol(const WorkspaceSymbolParams &, Callback>); + void onPrepareRename(const TextDocumentPositionParams &, + Callback>); void onRename(const RenameParams &, Callback); void onHover(const TextDocumentPositionParams &, Callback>); diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -390,6 +390,8 @@ }}, {"typeHierarchyProvider", true}, }}}}; + if (Params.capabilities.RenamePrepareSupport) + Result["renameProvider"] = llvm::json::Object{{"prepareProvider", true}}; if (NegotiatedOffsetEncoding) Result["offsetEncoding"] = *NegotiatedOffsetEncoding; Reply(std::move(Result)); @@ -525,6 +527,25 @@ std::move(Reply))); } +void ClangdLSPServer::onPrepareRename(const TextDocumentPositionParams &Params, + Callback> Reply) { + Server->prepareRename( + Params.textDocument.uri.file(), Params.position, + Bind( + [](decltype(Reply) Reply, + llvm::Expected> R) { + if (!R) + return Reply(R.takeError()); + if (!*R) + return Reply(llvm::None); + + Range Result; + Result = (*R)->NameRange; + Reply(std::move(Result)); + }, + std::move(Reply))); +} + void ClangdLSPServer::onRename(const RenameParams &Params, Callback Reply) { Path File = Params.textDocument.uri.file(); @@ -960,6 +981,7 @@ MsgHandler->bind("textDocument/declaration", &ClangdLSPServer::onGoToDeclaration); MsgHandler->bind("textDocument/references", &ClangdLSPServer::onReference); MsgHandler->bind("textDocument/switchSourceHeader", &ClangdLSPServer::onSwitchSourceHeader); + MsgHandler->bind("textDocument/prepareRename", &ClangdLSPServer::onPrepareRename); MsgHandler->bind("textDocument/rename", &ClangdLSPServer::onRename); MsgHandler->bind("textDocument/hover", &ClangdLSPServer::onHover); MsgHandler->bind("textDocument/documentSymbol", &ClangdLSPServer::onDocumentSymbol); diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -23,6 +23,7 @@ #include "index/Background.h" #include "index/FileIndex.h" #include "index/Index.h" +#include "refactor/Rename.h" #include "refactor/Tweak.h" #include "clang/Tooling/CompilationDatabase.h" #include "clang/Tooling/Core/Replacement.h" @@ -219,6 +220,10 @@ PathRef File, Position Pos, StringRef TriggerText); + /// Test the validity of a rename operation. + void prepareRename(PathRef File, Position Pos, + Callback> CB); + /// Rename all occurrences of the symbol at the \p Pos in \p File to /// \p NewName. void rename(PathRef File, Position Pos, llvm::StringRef NewName, diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -270,6 +270,17 @@ return Result; } +void ClangdServer::prepareRename(PathRef File, Position Pos, + Callback> CB) { + auto Action = [Pos](Callback> CB, + llvm::Expected InpAST) { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::prepareRenameAt(InpAST->AST, Pos)); + }; + WorkScheduler.runWithAST("PrepareRename", File, Bind(Action, std::move(CB))); +} + void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName, Callback> CB) { auto Action = [Pos](Path File, std::string NewName, diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -406,6 +406,10 @@ /// The content format that should be used for Hover requests. MarkupKind HoverContentFormat = MarkupKind::PlainText; + + /// The client supports testing for validity of rename operations + /// before execution. + bool RenamePrepareSupport = false; }; bool fromJSON(const llvm::json::Value &, ClientCapabilities &); diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -339,6 +339,10 @@ if (!fromJSON(*OffsetEncoding, *R.offsetEncoding)) return false; } + if (auto *Rename = O->getObject("rename")) { + if (auto RenameSupport = Rename->getBoolean("prepareSupport")) + R.RenamePrepareSupport = *RenameSupport; + } return true; } diff --git a/clang-tools-extra/clangd/refactor/Rename.h b/clang-tools-extra/clangd/refactor/Rename.h --- a/clang-tools-extra/clangd/refactor/Rename.h +++ b/clang-tools-extra/clangd/refactor/Rename.h @@ -16,6 +16,20 @@ namespace clang { namespace clangd { +// Contains information about the symbol being renamed. +struct PrepareRename { + /// The unqualified symbol name. + std::string Name; + /// The range of the symbol at the Pos; + Range NameRange; + /// Whether the symbol is local (e.g. function-local). + bool IsLocal; +}; + +/// Test the validity of a rename operation at a specified \Pos. +/// Returns null if the rename operation is not valid. +llvm::Optional prepareRenameAt(ParsedAST &AST, Position Pos); + /// Renames all occurrences of the symbol at \p Pos to \p NewName. /// Occurrences outside the current file are not modified. llvm::Expected renameWithinFile(ParsedAST &AST, diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -7,8 +7,10 @@ //===----------------------------------------------------------------------===// #include "refactor/Rename.h" +#include "AST.h" #include "clang/Tooling/Refactoring/RefactoringResultConsumer.h" #include "clang/Tooling/Refactoring/Rename/RenamingAction.h" +#include "clang/Tooling/Refactoring/Rename/USRFinder.h" namespace clang { namespace clangd { @@ -84,5 +86,28 @@ return FilteredChanges; } +static Range getTokenRange(SourceLocation Loc, const ASTContext &Ctx) { + SourceLocation End = Lexer::getLocForEndOfToken( + Loc, 0, Ctx.getSourceManager(), Ctx.getLangOpts()); + return halfOpenToRange(Ctx.getSourceManager(), + CharSourceRange::getCharRange(Loc, End)); +} + +llvm::Optional prepareRenameAt(ParsedAST &AST, Position Pos) { + ASTContext &ASTCtx = AST.getASTContext(); + SourceLocation SourceLocationBeg = clangd::getBeginningOfIdentifier( + AST, Pos, AST.getSourceManager().getMainFileID()); + const NamedDecl *D = + clang::tooling::getNamedDeclAt(ASTCtx, SourceLocationBeg); + if (!D) + return llvm::None; + PrepareRename Result; + Result.Name = printName(ASTCtx, *D); + Result.NameRange = getTokenRange(SourceLocationBeg, ASTCtx); + /// FIXME: we should include the TU-scoped symbols (e.g. static function). + Result.IsLocal = D->getParentFunctionOrMethod(); + return Result; +} + } // namespace clangd } // namespace clang