hokein created this revision.
Herald added a subscriber: mgorny.

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).


https://reviews.llvm.org/D39676

Files:
  clangd/CMakeLists.txt
  clangd/ClangdLSPServer.cpp
  clangd/ClangdLSPServer.h
  clangd/ClangdServer.cpp
  clangd/ClangdServer.h
  clangd/ClangdUnit.cpp
  clangd/ClangdUnit.h
  clangd/Protocol.cpp
  clangd/Protocol.h
  clangd/ProtocolHandlers.cpp
  clangd/ProtocolHandlers.h
  test/clangd/initialize-params-invalid.test
  test/clangd/initialize-params.test
  test/clangd/rename.test

Index: test/clangd/rename.test
===================================================================
--- /dev/null
+++ test/clangd/rename.test
@@ -0,0 +1,28 @@
+# RUN: clangd -run-synchronously < %s | FileCheck %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: 186
+
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///foo.cpp","languageId":"cpp","version":1,"text":"int foo;"}}}
+#
+# CHECK: {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///foo.cpp","diagnostics":[]}}
+#
+
+Content-Length: 159
+
+{"jsonrpc":"2.0","id":2,"method":"textDocument/rename","params":{"textDocument":{"uri":"file:///foo.cpp"},"position":{"line":0,"character":5},"newName":"bar"}}
+#
+# CHECK: {"jsonrpc":"2.0","id":2,"result":{"changes": {"file:///foo.cpp": [{"range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 7}}, "newText": "bar"}]}}}
+#
+
+Content-Length: 44
+
+{"jsonrpc":"2.0","id":3,"method":"shutdown"}
+# CHECK: {"jsonrpc":"2.0","id":3,"result":null}
+Content-Length: 33
+
+{"jsonrpc":"2.0":"method":"exit"}
Index: test/clangd/initialize-params.test
===================================================================
--- test/clangd/initialize-params.test
+++ test/clangd/initialize-params.test
@@ -5,7 +5,7 @@
 Content-Length: 143
 
 {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootUri":"file:///path/to/workspace","capabilities":{},"trace":"off"}}
-# CHECK: Content-Length: 606
+# CHECK: Content-Length: 640
 # CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{
 # CHECK:   "textDocumentSync": 1,
 # CHECK:   "documentFormattingProvider": true,
@@ -15,6 +15,7 @@
 # CHECK:   "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
 # CHECK:   "signatureHelpProvider": {"triggerCharacters": ["(",","]},
 # CHECK:   "definitionProvider": true,
+# CHECK:   "renameProvider": true,
 # CHECK:   "executeCommandProvider": {"commands": ["clangd.applyFix"]}
 # CHECK: }}}
 #
Index: test/clangd/initialize-params-invalid.test
===================================================================
--- test/clangd/initialize-params-invalid.test
+++ test/clangd/initialize-params-invalid.test
@@ -5,7 +5,7 @@
 Content-Length: 142
 
 {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":"","rootUri":"file:///path/to/workspace","capabilities":{},"trace":"off"}}
-# CHECK: Content-Length: 606
+# CHECK: Content-Length: 640
 # CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{
 # CHECK:   "textDocumentSync": 1,
 # CHECK:   "documentFormattingProvider": true,
@@ -15,6 +15,7 @@
 # CHECK:   "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
 # CHECK:   "signatureHelpProvider": {"triggerCharacters": ["(",","]},
 # CHECK:   "definitionProvider": true,
+# CHECK:   "renameProvider": true,
 # CHECK:   "executeCommandProvider": {"commands": ["clangd.applyFix"]}
 # CHECK: }}}
 #
Index: clangd/ProtocolHandlers.h
===================================================================
--- clangd/ProtocolHandlers.h
+++ clangd/ProtocolHandlers.h
@@ -53,6 +53,7 @@
   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,
Index: clangd/ProtocolHandlers.cpp
===================================================================
--- clangd/ProtocolHandlers.cpp
+++ clangd/ProtocolHandlers.cpp
@@ -71,6 +71,7 @@
   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);
 }
Index: clangd/Protocol.h
===================================================================
--- clangd/Protocol.h
+++ clangd/Protocol.h
@@ -572,6 +572,20 @@
   static std::string 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
 
Index: clangd/Protocol.cpp
===================================================================
--- clangd/Protocol.cpp
+++ clangd/Protocol.cpp
@@ -1133,3 +1133,51 @@
   Result.push_back('}');
   return Result;
 }
+
+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;
+}
Index: clangd/ClangdUnit.h
===================================================================
--- clangd/ClangdUnit.h
+++ clangd/ClangdUnit.h
@@ -304,6 +304,10 @@
                             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);
Index: clangd/ClangdUnit.cpp
===================================================================
--- clangd/ClangdUnit.cpp
+++ clangd/ClangdUnit.cpp
@@ -1001,44 +1001,6 @@
   }
 };
 
-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,
@@ -1430,3 +1392,48 @@
   Lock.unlock();
   File.RebuildCond.notify_all();
 }
+
+namespace clang {
+namespace clangd {
+
+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 clangd
+} // namespace clang
Index: clangd/ClangdServer.h
===================================================================
--- clangd/ClangdServer.h
+++ clangd/ClangdServer.h
@@ -290,6 +290,10 @@
   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.
+  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.
Index: clangd/ClangdServer.cpp
===================================================================
--- clangd/ClangdServer.cpp
+++ 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"
@@ -333,6 +335,53 @@
   return formatCode(Code, File, {tooling::Range(PreviousLBracePos, Len)});
 }
 
+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);
+  class RenameResultCollector final
+      : public tooling::RefactoringResultConsumer {
+  public:
+    void handleError(llvm::Error Err) override {}
+
+    void handle(tooling::SymbolOccurrences) override {}
+
+    void handle(tooling::AtomicChanges SourceReplacements) override {
+      Result = std::move(SourceReplacements);
+    }
+
+    tooling::AtomicChanges Result;
+  };
+  RenameResultCollector 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)
+       rename->invoke(ResultCollector, Context);
+  });
+  std::vector<tooling::Replacement> Replacements;
+  for (const tooling::AtomicChange &Change : ResultCollector.Result) {
+    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.
+      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");
Index: clangd/ClangdLSPServer.h
===================================================================
--- clangd/ClangdLSPServer.h
+++ clangd/ClangdLSPServer.h
@@ -70,6 +70,7 @@
   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);
Index: clangd/ClangdLSPServer.cpp
===================================================================
--- clangd/ClangdLSPServer.cpp
+++ clangd/ClangdLSPServer.cpp
@@ -46,6 +46,7 @@
           "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
           "signatureHelpProvider": {"triggerCharacters": ["(",","]},
           "definitionProvider": true,
+          "renameProvider": true,
           "executeCommandProvider": {"commands": [")" +
       ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND + R"("]}
         }})");
@@ -112,6 +113,17 @@
   }
 }
 
+void ClangdLSPServer::onRename(Ctx C, RenameParams &Params) {
+  auto File = Params.textDocument.uri.file;
+  std::vector<tooling::Replacement> Replacements =
+      Server.Rename(File, Params.position, Params.newName);
+  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);
Index: clangd/CMakeLists.txt
===================================================================
--- clangd/CMakeLists.txt
+++ clangd/CMakeLists.txt
@@ -26,6 +26,7 @@
   clangSerialization
   clangTooling
   clangToolingCore
+  clangToolingRefactor
   ${LLVM_PTHREAD_LIB}
   )
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to