summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--clang-tools-extra/clangd/CMakeLists.txt1
-rw-r--r--clang-tools-extra/clangd/ClangdLSPServer.cpp17
-rw-r--r--clang-tools-extra/clangd/ClangdLSPServer.h1
-rw-r--r--clang-tools-extra/clangd/ClangdServer.cpp72
-rw-r--r--clang-tools-extra/clangd/ClangdServer.h4
-rw-r--r--clang-tools-extra/clangd/ClangdUnit.cpp78
-rw-r--r--clang-tools-extra/clangd/ClangdUnit.h4
-rw-r--r--clang-tools-extra/clangd/Protocol.cpp48
-rw-r--r--clang-tools-extra/clangd/Protocol.h14
-rw-r--r--clang-tools-extra/clangd/ProtocolHandlers.cpp1
-rw-r--r--clang-tools-extra/clangd/ProtocolHandlers.h1
-rw-r--r--clang-tools-extra/test/clangd/initialize-params-invalid.test1
-rw-r--r--clang-tools-extra/test/clangd/initialize-params.test1
-rw-r--r--clang-tools-extra/test/clangd/rename.test50
14 files changed, 255 insertions, 38 deletions
diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt
index a81da615a36..72305e4208b 100644
--- a/clang-tools-extra/clangd/CMakeLists.txt
+++ b/clang-tools-extra/clangd/CMakeLists.txt
@@ -27,6 +27,7 @@ add_clang_library(clangDaemon
clangSerialization
clangTooling
clangToolingCore
+ clangToolingRefactor
${LLVM_PTHREAD_LIB}
)
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index aa73e737f22..b751c91eb51 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -57,6 +57,7 @@ void ClangdLSPServer::onInitialize(Ctx C, InitializeParams &Params) {
{"triggerCharacters", {"(", ","}},
}},
{"definitionProvider", true},
+ {"renameProvider", true},
{"executeCommandProvider",
json::obj{
{"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}},
@@ -127,6 +128,22 @@ void ClangdLSPServer::onCommand(Ctx C, ExecuteCommandParams &Params) {
}
}
+void ClangdLSPServer::onRename(Ctx C, RenameParams &Params) {
+ auto File = Params.textDocument.uri.file;
+ auto Replacements = Server.rename(File, Params.position, Params.newName);
+ if (!Replacements) {
+ C.replyError(
+ ErrorCode::InternalError,
+ llvm::toString(Replacements.takeError()));
+ return;
+ }
+ std::string Code = Server.getDocument(File);
+ std::vector<TextEdit> Edits = replacementsToEdits(Code, *Replacements);
+ WorkspaceEdit WE;
+ WE.changes = {{llvm::yaml::escape(Params.textDocument.uri.uri), Edits}};
+ C.reply(WorkspaceEdit::unparse(WE));
+}
+
void ClangdLSPServer::onDocumentDidClose(Ctx C,
DidCloseTextDocumentParams &Params) {
Server.removeDocument(Params.textDocument.uri.file);
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h
index 22f73cb5874..c2e494ddb84 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -70,6 +70,7 @@ private:
void onSwitchSourceHeader(Ctx C, TextDocumentIdentifier &Params) override;
void onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) override;
void onCommand(Ctx C, ExecuteCommandParams &Params) override;
+ void onRename(Ctx C, RenameParams &Parames) override;
std::vector<clang::tooling::Replacement>
getFixIts(StringRef File, const clangd::Diagnostic &D);
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index ba9336fd36b..df85bca9a22 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 @@ std::string getStandardResourceDir() {
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 @@ std::vector<tooling::Replacement> ClangdServer::formatOnType(PathRef File,
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");
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 1eddfd9e07b..eb8964bf595 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -290,6 +290,10 @@ public:
std::vector<tooling::Replacement> formatFile(PathRef File);
/// Run formatting after a character was typed at \p Pos in \p File.
std::vector<tooling::Replacement> formatOnType(PathRef File, Position Pos);
+ /// Rename all occurrences of the symbol at the \p Pos in \p File to
+ /// \p NewName.
+ Expected<std::vector<tooling::Replacement>> rename(PathRef File, Position Pos,
+ llvm::StringRef NewName);
/// Gets current document contents for \p File. \p File must point to a
/// currently tracked file.
diff --git a/clang-tools-extra/clangd/ClangdUnit.cpp b/clang-tools-extra/clangd/ClangdUnit.cpp
index aeeb0e7709b..fa261654450 100644
--- a/clang-tools-extra/clangd/ClangdUnit.cpp
+++ b/clang-tools-extra/clangd/ClangdUnit.cpp
@@ -1007,44 +1007,6 @@ private:
}
};
-SourceLocation getBeginningOfIdentifier(ParsedAST &Unit, const Position &Pos,
- const FileEntry *FE) {
- // The language server protocol uses zero-based line and column numbers.
- // Clang uses one-based numbers.
-
- const ASTContext &AST = Unit.getASTContext();
- const SourceManager &SourceMgr = AST.getSourceManager();
-
- SourceLocation InputLocation =
- getMacroArgExpandedLocation(SourceMgr, FE, Pos);
- if (Pos.character == 0) {
- return InputLocation;
- }
-
- // This handle cases where the position is in the middle of a token or right
- // after the end of a token. In theory we could just use GetBeginningOfToken
- // to find the start of the token at the input position, but this doesn't
- // work when right after the end, i.e. foo|.
- // So try to go back by one and see if we're still inside the an identifier
- // token. If so, Take the beginning of this token.
- // (It should be the same identifier because you can't have two adjacent
- // identifiers without another token in between.)
- SourceLocation PeekBeforeLocation = getMacroArgExpandedLocation(
- SourceMgr, FE, Position{Pos.line, Pos.character - 1});
- Token Result;
- if (Lexer::getRawToken(PeekBeforeLocation, Result, SourceMgr,
- AST.getLangOpts(), false)) {
- // getRawToken failed, just use InputLocation.
- return InputLocation;
- }
-
- if (Result.is(tok::raw_identifier)) {
- return Lexer::GetBeginningOfToken(PeekBeforeLocation, SourceMgr,
- AST.getLangOpts());
- }
-
- return InputLocation;
-}
} // namespace
std::vector<Location> clangd::findDefinitions(ParsedAST &AST, Position Pos,
@@ -1436,3 +1398,43 @@ CppFile::RebuildGuard::~RebuildGuard() {
Lock.unlock();
File.RebuildCond.notify_all();
}
+
+SourceLocation clangd::getBeginningOfIdentifier(ParsedAST &Unit,
+ const Position &Pos,
+ const FileEntry *FE) {
+ // The language server protocol uses zero-based line and column numbers.
+ // Clang uses one-based numbers.
+
+ const ASTContext &AST = Unit.getASTContext();
+ const SourceManager &SourceMgr = AST.getSourceManager();
+
+ SourceLocation InputLocation =
+ getMacroArgExpandedLocation(SourceMgr, FE, Pos);
+ if (Pos.character == 0) {
+ return InputLocation;
+ }
+
+ // This handle cases where the position is in the middle of a token or right
+ // after the end of a token. In theory we could just use GetBeginningOfToken
+ // to find the start of the token at the input position, but this doesn't
+ // work when right after the end, i.e. foo|.
+ // So try to go back by one and see if we're still inside the an identifier
+ // token. If so, Take the beginning of this token.
+ // (It should be the same identifier because you can't have two adjacent
+ // identifiers without another token in between.)
+ SourceLocation PeekBeforeLocation = getMacroArgExpandedLocation(
+ SourceMgr, FE, Position{Pos.line, Pos.character - 1});
+ Token Result;
+ if (Lexer::getRawToken(PeekBeforeLocation, Result, SourceMgr,
+ AST.getLangOpts(), false)) {
+ // getRawToken failed, just use InputLocation.
+ return InputLocation;
+ }
+
+ if (Result.is(tok::raw_identifier)) {
+ return Lexer::GetBeginningOfToken(PeekBeforeLocation, SourceMgr,
+ AST.getLangOpts());
+ }
+
+ return InputLocation;
+}
diff --git a/clang-tools-extra/clangd/ClangdUnit.h b/clang-tools-extra/clangd/ClangdUnit.h
index 88c5b4d57f7..55085d85560 100644
--- a/clang-tools-extra/clangd/ClangdUnit.h
+++ b/clang-tools-extra/clangd/ClangdUnit.h
@@ -304,6 +304,10 @@ SignatureHelp signatureHelp(PathRef FileName,
std::shared_ptr<PCHContainerOperations> PCHs,
clangd::Logger &Logger);
+/// Get the beginning SourceLocation at a specified \p Pos.
+SourceLocation getBeginningOfIdentifier(ParsedAST &Unit, const Position &Pos,
+ const FileEntry *FE);
+
/// Get definition of symbol at a specified \p Pos.
std::vector<Location> findDefinitions(ParsedAST &AST, Position Pos,
clangd::Logger &Logger);
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index 8fa23bfda5b..7d531341759 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -1073,3 +1073,51 @@ json::Expr SignatureHelp::unparse(const SignatureHelp &SH) {
{"signatures", json::ary(SH.signatures)},
};
}
+
+llvm::Optional<RenameParams>
+RenameParams::parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger) {
+ RenameParams Result;
+ for (auto &NextKeyValue : *Params) {
+ auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+ if (!KeyString)
+ return llvm::None;
+
+ llvm::SmallString<10> KeyStorage;
+ StringRef KeyValue = KeyString->getValue(KeyStorage);
+
+ if (KeyValue == "textDocument") {
+ auto *Value =
+ dyn_cast_or_null<llvm::yaml::MappingNode>(NextKeyValue.getValue());
+ if (!Value)
+ continue;
+ auto *Map = dyn_cast<llvm::yaml::MappingNode>(Value);
+ if (!Map)
+ return llvm::None;
+ auto Parsed = TextDocumentIdentifier::parse(Map, Logger);
+ if (!Parsed)
+ return llvm::None;
+ Result.textDocument = std::move(*Parsed);
+ } else if (KeyValue == "position") {
+ auto *Value =
+ dyn_cast_or_null<llvm::yaml::MappingNode>(NextKeyValue.getValue());
+ if (!Value)
+ continue;
+ auto Parsed = Position::parse(Value, Logger);
+ if (!Parsed)
+ return llvm::None;
+ Result.position = std::move(*Parsed);
+ } else if (KeyValue == "newName") {
+ auto *Value = NextKeyValue.getValue();
+ if (!Value)
+ continue;
+ auto *Node = dyn_cast<llvm::yaml::ScalarNode>(Value);
+ if (!Node)
+ return llvm::None;
+ llvm::SmallString<10> Storage;
+ Result.newName = Node->getValue(Storage);
+ } else {
+ logIgnoredField(KeyValue, Logger);
+ }
+ }
+ return Result;
+}
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index a53f72f984e..8c1f1a59307 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -589,6 +589,20 @@ struct SignatureHelp {
static json::Expr unparse(const SignatureHelp &);
};
+struct RenameParams {
+ /// The document that was opened.
+ TextDocumentIdentifier textDocument;
+
+ /// The position at which this request was sent.
+ Position position;
+
+ /// The new name of the symbol.
+ std::string newName;
+
+ static llvm::Optional<RenameParams> parse(llvm::yaml::MappingNode *Params,
+ clangd::Logger &Logger);
+};
+
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/ProtocolHandlers.cpp b/clang-tools-extra/clangd/ProtocolHandlers.cpp
index 4ca6ee0b6b7..5ee191e7626 100644
--- a/clang-tools-extra/clangd/ProtocolHandlers.cpp
+++ b/clang-tools-extra/clangd/ProtocolHandlers.cpp
@@ -71,6 +71,7 @@ void clangd::registerCallbackHandlers(JSONRPCDispatcher &Dispatcher,
Register("textDocument/definition", &ProtocolCallbacks::onGoToDefinition);
Register("textDocument/switchSourceHeader",
&ProtocolCallbacks::onSwitchSourceHeader);
+ Register("textDocument/rename", &ProtocolCallbacks::onRename);
Register("workspace/didChangeWatchedFiles", &ProtocolCallbacks::onFileEvent);
Register("workspace/executeCommand", &ProtocolCallbacks::onCommand);
}
diff --git a/clang-tools-extra/clangd/ProtocolHandlers.h b/clang-tools-extra/clangd/ProtocolHandlers.h
index f82bd29142b..37d325692ab 100644
--- a/clang-tools-extra/clangd/ProtocolHandlers.h
+++ b/clang-tools-extra/clangd/ProtocolHandlers.h
@@ -53,6 +53,7 @@ public:
virtual void onSwitchSourceHeader(Ctx C, TextDocumentIdentifier &Params) = 0;
virtual void onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) = 0;
virtual void onCommand(Ctx C, ExecuteCommandParams &Params) = 0;
+ virtual void onRename(Ctx C, RenameParams &Parames) = 0;
};
void registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out,
diff --git a/clang-tools-extra/test/clangd/initialize-params-invalid.test b/clang-tools-extra/test/clangd/initialize-params-invalid.test
index 9f9c69d14b4..1a5f1710877 100644
--- a/clang-tools-extra/test/clangd/initialize-params-invalid.test
+++ b/clang-tools-extra/test/clangd/initialize-params-invalid.test
@@ -30,6 +30,7 @@ Content-Length: 142
# CHECK-NEXT: "clangd.applyFix"
# CHECK-NEXT: ]
# CHECK-NEXT: },
+# CHECK-NEXT: "renameProvider": true,
# CHECK-NEXT: "signatureHelpProvider": {
# CHECK-NEXT: "triggerCharacters": [
# CHECK-NEXT: "(",
diff --git a/clang-tools-extra/test/clangd/initialize-params.test b/clang-tools-extra/test/clangd/initialize-params.test
index 60fc05ec069..73a1576584d 100644
--- a/clang-tools-extra/test/clangd/initialize-params.test
+++ b/clang-tools-extra/test/clangd/initialize-params.test
@@ -30,6 +30,7 @@ Content-Length: 143
# CHECK-NEXT: "clangd.applyFix"
# CHECK-NEXT: ]
# CHECK-NEXT: },
+# CHECK-NEXT: "renameProvider": true,
# CHECK-NEXT: "signatureHelpProvider": {
# CHECK-NEXT: "triggerCharacters": [
# CHECK-NEXT: "(",
diff --git a/clang-tools-extra/test/clangd/rename.test b/clang-tools-extra/test/clangd/rename.test
new file mode 100644
index 00000000000..fd887a14362
--- /dev/null
+++ b/clang-tools-extra/test/clangd/rename.test
@@ -0,0 +1,50 @@
+# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s
+# It is absolutely vital that this file has CRLF line endings.
+#
+Content-Length: 125
+
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
+
+Content-Length: 150
+
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///foo.cpp","languageId":"cpp","version":1,"text":"int foo;"}}}
+
+Content-Length: 159
+
+{"jsonrpc":"2.0","id":1,"method":"textDocument/rename","params":{"textDocument":{"uri":"file:///foo.cpp"},"position":{"line":0,"character":5},"newName":"bar"}}
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": {
+# CHECK-NEXT: "changes": {
+# CHECK-NEXT: "file:///foo.cpp": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": "bar",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 7
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 4
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+Content-Length: 159
+
+{"jsonrpc":"2.0","id":2,"method":"textDocument/rename","params":{"textDocument":{"uri":"file:///foo.cpp"},"position":{"line":0,"character":2},"newName":"bar"}}
+# CHECK: "error": {
+# CHECK-NEXT: "code": -32603,
+# CHECK-NEXT: "message": "clang diagnostic"
+# CHECK-NEXT: },
+# CHECK-NEXT: "id": 2,
+# CHECK-NEXT: "jsonrpc": "2.0"
+Content-Length: 44
+
+{"jsonrpc":"2.0","id":3,"method":"shutdown"}
+Content-Length: 33
+
+{"jsonrpc":"2.0":"method":"exit"}
OpenPOWER on IntegriCloud