clangSerialization
clangTooling
clangToolingCore
+ clangToolingRefactor
${LLVM_PTHREAD_LIB}
)
{"triggerCharacters", {"(", ","}},
}},
{"definitionProvider", true},
+ {"renameProvider", true},
{"executeCommandProvider",
json::obj{
{"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}},
}
}
+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);
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);
#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"
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) {
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");
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.
}
};
-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,
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;
+}
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);
{"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;
+}
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
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);
}
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,
# CHECK-NEXT: "clangd.applyFix"\r
# CHECK-NEXT: ]\r
# CHECK-NEXT: },\r
+# CHECK-NEXT: "renameProvider": true,\r
# CHECK-NEXT: "signatureHelpProvider": {\r
# CHECK-NEXT: "triggerCharacters": [\r
# CHECK-NEXT: "(",\r
# CHECK-NEXT: "clangd.applyFix"\r
# CHECK-NEXT: ]\r
# CHECK-NEXT: },\r
+# CHECK-NEXT: "renameProvider": true,\r
# CHECK-NEXT: "signatureHelpProvider": {\r
# CHECK-NEXT: "triggerCharacters": [\r
# CHECK-NEXT: "(",\r
--- /dev/null
+# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s\r
+# It is absolutely vital that this file has CRLF line endings.\r
+#\r
+Content-Length: 125\r
+\r
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}\r
+\r
+Content-Length: 150\r
+\r
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///foo.cpp","languageId":"cpp","version":1,"text":"int foo;"}}}\r
+\r
+Content-Length: 159\r
+\r
+{"jsonrpc":"2.0","id":1,"method":"textDocument/rename","params":{"textDocument":{"uri":"file:///foo.cpp"},"position":{"line":0,"character":5},"newName":"bar"}}\r
+# CHECK: "id": 1,\r
+# CHECK-NEXT: "jsonrpc": "2.0",\r
+# CHECK-NEXT: "result": {\r
+# CHECK-NEXT: "changes": {\r
+# CHECK-NEXT: "file:///foo.cpp": [\r
+# CHECK-NEXT: {\r
+# CHECK-NEXT: "newText": "bar",\r
+# CHECK-NEXT: "range": {\r
+# CHECK-NEXT: "end": {\r
+# CHECK-NEXT: "character": 7\r
+# CHECK-NEXT: "line": 0\r
+# CHECK-NEXT: },\r
+# CHECK-NEXT: "start": {\r
+# CHECK-NEXT: "character": 4\r
+# CHECK-NEXT: "line": 0\r
+# CHECK-NEXT: }\r
+# CHECK-NEXT: }\r
+# CHECK-NEXT: }\r
+# CHECK-NEXT: ]\r
+# CHECK-NEXT: }\r
+# CHECK-NEXT: }\r
+Content-Length: 159\r
+\r
+{"jsonrpc":"2.0","id":2,"method":"textDocument/rename","params":{"textDocument":{"uri":"file:///foo.cpp"},"position":{"line":0,"character":2},"newName":"bar"}}\r
+# CHECK: "error": {\r
+# CHECK-NEXT: "code": -32603,\r
+# CHECK-NEXT: "message": "clang diagnostic"\r
+# CHECK-NEXT: },\r
+# CHECK-NEXT: "id": 2,\r
+# CHECK-NEXT: "jsonrpc": "2.0"\r
+Content-Length: 44\r
+\r
+{"jsonrpc":"2.0","id":3,"method":"shutdown"}\r
+Content-Length: 33\r
+\r
+{"jsonrpc":"2.0":"method":"exit"}\r