ilya-biryukov updated this revision to Diff 181309.
ilya-biryukov added a comment.

- Put more AST-centric information into ActionInputs
- Restructure and refactor the code


Repository:
  rCTE Clang Tools Extra

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D56267/new/

https://reviews.llvm.org/D56267

Files:
  clangd/CMakeLists.txt
  clangd/ClangdLSPServer.cpp
  clangd/ClangdServer.cpp
  clangd/ClangdServer.h
  clangd/CodeActions.cpp
  clangd/CodeActions.h
  clangd/Protocol.cpp
  clangd/Protocol.h
  clangd/SourceCode.cpp
  clangd/SourceCode.h
  clangd/refactor/ActionProvider.cpp
  clangd/refactor/ActionProvider.h
  test/clangd/initialize-params.test

Index: test/clangd/initialize-params.test
===================================================================
--- test/clangd/initialize-params.test
+++ test/clangd/initialize-params.test
@@ -25,7 +25,8 @@
 # CHECK-NEXT:      "documentSymbolProvider": true,
 # CHECK-NEXT:      "executeCommandProvider": {
 # CHECK-NEXT:        "commands": [
-# CHECK-NEXT:          "clangd.applyFix"
+# CHECK-NEXT:          "clangd.applyFix",
+# CHECK-NEXT:          "clangd.applyCodeAction"
 # CHECK-NEXT:        ]
 # CHECK-NEXT:      },
 # CHECK-NEXT:      "hoverProvider": true,
Index: clangd/refactor/ActionProvider.h
===================================================================
--- /dev/null
+++ clangd/refactor/ActionProvider.h
@@ -0,0 +1,115 @@
+//===--- ActionProvider.h ----------------------------------------*- C++-*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+// The code actions are small refactoring-like actions that run over the AST and
+// produce the set of edits as a result. They are local, i.e. they should take
+// the current editor context, e.g. the cursor position and selection into
+// account.
+// The actions are executed in two stages:
+//   - Stage 1 should check whether the action is available in a current
+//     context. It should be cheap and fast to compute as it is executed for all
+//     available actions on every client request, which happen quite frequently.
+//   - Stage 2 is performed after stage 1 and can be more expensive to compute.
+//     It is performed when the user actually chooses the action.
+// To avoid extra round-trips and AST reads, actions can also produce results on
+// stage 1 in cases when the actions are fast to compute.
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_REFACTOR_ACTIONS_ACTIONPROVIDER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_REFACTOR_ACTIONS_ACTIONPROVIDER_H
+
+#include "ClangdUnit.h"
+#include "Protocol.h"
+#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/StringRef.h"
+namespace clang {
+namespace clangd {
+
+class PreparedAction;
+struct ActionInputs;
+
+/// Base interface for writing a code action.
+class ActionProvider {
+public:
+  virtual ~ActionProvider() = default;
+
+  /// Run the first stage of the action. The non-None result indicates that the
+  /// action is available and should be shown to the user. Returns None if the
+  /// action is not available.
+  /// This function should be fast, if the action requires non-trivial work it
+  /// should be moved into the second stage, i.e. the continuation function
+  /// provided in the resulting PreparedAction.
+  virtual llvm::Optional<PreparedAction>
+  prepare(const ActionInputs &Inputs) = 0;
+};
+
+/// A context used by the actions to identify
+struct ActionInputs {
+  static llvm::Optional<ActionInputs> create(llvm::StringRef File,
+                                             llvm::StringRef Code,
+                                             ParsedAST &AST, Range Selection);
+
+  /// The path of an active document the action was invoked in.
+  llvm::StringRef File;
+  /// The text of the active document.
+  llvm::StringRef Code;
+  /// Parsed AST of the active file.
+  ParsedAST &AST;
+  /// A location of the cursor in the editor.
+  SourceLocation Cursor;
+  // FIXME: add selection when there are checks relying on it.
+  // FIXME: provide a way to get sources and ASTs for other files.
+  // FIXME: cache some commonly required information (e.g. AST nodes under
+  //        cursor) to avoid redundant AST visit in every action.
+};
+
+using ActionID = size_t;
+/// Result of executing a first stage of the action. If computing the resulting
+/// WorkspaceEdit is fast, the actions should produce it right away.
+/// For non-trivial actions, a continuation function to compute the resulting
+/// edits should be provided instead. It is expected that PreparedAction is
+/// consumed immediately when created, so continuations can reference the AST,
+/// so it's not safe to store the PreparedAction for long spans of time.
+class PreparedAction {
+public:
+  PreparedAction(std::string Title,
+                 llvm::unique_function<llvm::Expected<tooling::Replacements>()>
+                     FinishAction);
+  PreparedAction(std::string Title, tooling::Replacements Result);
+
+  /// An id of the action that can be used to re-run it with
+  /// CodeActions::prepare().
+  ActionID id() const { return ID; }
+  /// A title of the action for display in the UI.
+  llvm::StringRef title() const { return Title; }
+  /// Whether the results are immediately available. If this function returns
+  /// false, computeEdits() won't fail and is very cheap to compute.
+  bool needsMoreWork() const { return static_cast<bool>(FinishAction); }
+  /// Consume the action to compute the resulting edits. This function can be
+  /// expensive if needsMoreWork() returns true.
+  llvm::Expected<tooling::Replacements> computeEdits() && {
+    if (FinishAction)
+      return FinishAction();
+    return std::move(*Result);
+  }
+
+private:
+  void setID(ActionID V) { ID = V; } // only available to CodeActions.
+  friend class CodeActions;
+
+  ActionID ID = std::numeric_limits<ActionID>::max(); // set by CodeActions().
+  std::string Title;                                  /// For display in the UI.
+  // Exactly one of FinishAction and Result will be set.
+  llvm::unique_function<llvm::Expected<tooling::Replacements>()> FinishAction;
+  llvm::Optional<tooling::Replacements> Result;
+};
+
+} // namespace clangd
+} // namespace clang
+
+#endif
Index: clangd/refactor/ActionProvider.cpp
===================================================================
--- /dev/null
+++ clangd/refactor/ActionProvider.cpp
@@ -0,0 +1,40 @@
+//===--- ActionProvider.cpp --------------------------------------*- C++-*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+#include "ActionProvider.h"
+
+namespace clang {
+namespace clangd {
+PreparedAction::PreparedAction(
+    std::string Title,
+    llvm::unique_function<llvm::Expected<tooling::Replacements>()> FinishAction)
+    : Title(std::move(Title)), FinishAction(std::move(FinishAction)) {
+  assert(this->FinishAction);
+}
+
+PreparedAction::PreparedAction(std::string Title, tooling::Replacements Result)
+    : Title(std::move(Title)), Result(std::move(Result)) {}
+
+llvm::Optional<ActionInputs> ActionInputs::create(llvm::StringRef File,
+                                                  llvm::StringRef Code,
+                                                  ParsedAST &AST,
+                                                  Range Selection) {
+
+  auto &Ctx = AST.getASTContext();
+  // Approximate the cursor position as the start of the selection. The LSP does
+  // not provide a separate field for this.
+  auto Cursor = positionToSourceLoc(Ctx.getSourceManager(), Selection.start);
+  if (!Cursor) {
+    llvm::consumeError(Cursor.takeError());
+    return llvm::None;
+  }
+  return ActionInputs{File, Code, AST, *Cursor};
+}
+
+} // namespace clangd
+} // namespace clang
\ No newline at end of file
Index: clangd/SourceCode.h
===================================================================
--- clangd/SourceCode.h
+++ clangd/SourceCode.h
@@ -55,6 +55,11 @@
 /// FIXME: This should return an error if the location is invalid.
 Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc);
 
+/// Return the file location, corresponding to \p P. Note that one should take
+/// care to avoid comparing the result with expansion locations.
+llvm::Expected<SourceLocation> positionToSourceLoc(const SourceManager &SM,
+                                                   Position P);
+
 // Converts a half-open clang source range to an LSP range.
 // Note that clang also uses closed source ranges, which this can't handle!
 Range halfOpenToRange(const SourceManager &SM, CharSourceRange R);
Index: clangd/SourceCode.cpp
===================================================================
--- clangd/SourceCode.cpp
+++ clangd/SourceCode.cpp
@@ -142,6 +142,16 @@
   return P;
 }
 
+llvm::Expected<SourceLocation> positionToSourceLoc(const SourceManager &SM,
+                                                   Position P) {
+  llvm::StringRef Code = SM.getBuffer(SM.getMainFileID())->getBuffer();
+  auto Offset =
+      positionToOffset(Code, P, /*AllowColumnBeyondLineLength=*/false);
+  if (!Offset)
+    return Offset.takeError();
+  return SM.getLocForStartOfFile(SM.getMainFileID()).getLocWithOffset(*Offset);
+}
+
 Range halfOpenToRange(const SourceManager &SM, CharSourceRange R) {
   // Clang is 1-based, LSP uses 0-based indexes.
   Position Begin = sourceLocToPosition(SM, R.getBegin());
Index: clangd/Protocol.h
===================================================================
--- clangd/Protocol.h
+++ clangd/Protocol.h
@@ -632,6 +632,14 @@
 bool fromJSON(const llvm::json::Value &, WorkspaceEdit &);
 llvm::json::Value toJSON(const WorkspaceEdit &WE);
 
+struct CodeActionArgs {
+  std::string file;
+  int64_t actionId;
+  Range selection;
+};
+bool fromJSON(const llvm::json::Value &, CodeActionArgs &);
+llvm::json::Value toJSON(const CodeActionArgs &A);
+
 /// Exact commands are not specified in the protocol so we define the
 /// ones supported by Clangd here. The protocol specifies the command arguments
 /// to be "any[]" but to make this safer and more manageable, each command we
@@ -643,12 +651,15 @@
 struct ExecuteCommandParams {
   // Command to apply fix-its. Uses WorkspaceEdit as argument.
   const static llvm::StringLiteral CLANGD_APPLY_FIX_COMMAND;
+  // Command to apply the code action. Uses action id as the argument.
+  const static llvm::StringLiteral CLANGD_APPLY_CODE_ACTION;
 
   /// The command identifier, e.g. CLANGD_APPLY_FIX_COMMAND
   std::string command;
 
   // Arguments
   llvm::Optional<WorkspaceEdit> workspaceEdit;
+  llvm::Optional<CodeActionArgs> codeActionArgs;
 };
 bool fromJSON(const llvm::json::Value &, ExecuteCommandParams &);
 
@@ -670,6 +681,7 @@
   /// Used to filter code actions.
   llvm::Optional<std::string> kind;
   const static llvm::StringLiteral QUICKFIX_KIND;
+  const static llvm::StringLiteral REFACTOR_KIND;
 
   /// The diagnostics that this code action resolves.
   llvm::Optional<std::vector<Diagnostic>> diagnostics;
Index: clangd/Protocol.cpp
===================================================================
--- clangd/Protocol.cpp
+++ clangd/Protocol.cpp
@@ -422,6 +422,9 @@
 
 const llvm::StringLiteral ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND =
     "clangd.applyFix";
+const llvm::StringLiteral ExecuteCommandParams::CLANGD_APPLY_CODE_ACTION =
+    "clangd.applyCodeAction";
+
 bool fromJSON(const llvm::json::Value &Params, ExecuteCommandParams &R) {
   llvm::json::ObjectMapper O(Params);
   if (!O || !O.map("command", R.command))
@@ -432,6 +435,9 @@
     return Args && Args->size() == 1 &&
            fromJSON(Args->front(), R.workspaceEdit);
   }
+  if (R.command == ExecuteCommandParams::CLANGD_APPLY_CODE_ACTION)
+    return Args && Args->size() == 1 &&
+           fromJSON(Args->front(), R.codeActionArgs);
   return false; // Unrecognized command.
 }
 
@@ -498,10 +504,13 @@
   auto Cmd = llvm::json::Object{{"title", C.title}, {"command", C.command}};
   if (C.workspaceEdit)
     Cmd["arguments"] = {*C.workspaceEdit};
+  if (C.codeActionArgs)
+    Cmd["arguments"] = {*C.codeActionArgs};
   return std::move(Cmd);
 }
 
 const llvm::StringLiteral CodeAction::QUICKFIX_KIND = "quickfix";
+const llvm::StringLiteral CodeAction::REFACTOR_KIND = "refactor";
 
 llvm::json::Value toJSON(const CodeAction &CA) {
   auto CodeAction = llvm::json::Object{{"title", CA.title}};
@@ -545,6 +554,17 @@
   return llvm::json::Object{{"changes", std::move(FileChanges)}};
 }
 
+bool fromJSON(const llvm::json::Value &Params, CodeActionArgs &A) {
+  llvm::json::ObjectMapper O(Params);
+  return O && O.map("actionId", A.actionId) &&
+         O.map("selection", A.selection) && O.map("file", A.file);
+}
+
+llvm::json::Value toJSON(const CodeActionArgs &A) {
+  return llvm::json::Object{
+      {"actionId", A.actionId}, {"selection", A.selection}, {"file", A.file}};
+}
+
 llvm::json::Value toJSON(const ApplyWorkspaceEditParams &Params) {
   return llvm::json::Object{{"edit", Params.edit}};
 }
Index: clangd/CodeActions.h
===================================================================
--- /dev/null
+++ clangd/CodeActions.h
@@ -0,0 +1,43 @@
+//===--- CodeActions.h -------------------------------------------*- C++-*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+// A registry and of all code actions available to clangd and the main entry
+// point used by ClangdServer to serve those.
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODEACTIONS_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODEACTIONS_H
+
+#include "refactor/ActionProvider.h"
+
+namespace clang {
+namespace clangd {
+
+/// A registry of code actions available in clangd and the main entry point for
+/// running the actions.
+class CodeActions {
+public:
+  CodeActions();
+
+  /// Produces a list of all actions available in the specified context.
+  std::vector<PreparedAction> prepareAll(const ActionInputs &Inputs) const;
+
+  /// Prepares an action with a specified \p ID for the final execution. This
+  /// method is used to finish executing an action requested by a user in the
+  /// UI.
+  llvm::Optional<PreparedAction> prepare(ActionID ID,
+                                         const ActionInputs &Inputs) const;
+
+private:
+  std::vector<std::unique_ptr<ActionProvider>> Actions;
+};
+
+} // namespace clangd
+} // namespace clang
+
+#endif
\ No newline at end of file
Index: clangd/CodeActions.cpp
===================================================================
--- /dev/null
+++ clangd/CodeActions.cpp
@@ -0,0 +1,48 @@
+//===--- CodeActions.cpp -----------------------------------------*- C++-*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+#include "CodeActions.h"
+#include "Logger.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/FormatVariadic.h"
+#include <algorithm>
+
+using namespace llvm;
+
+namespace clang {
+namespace clangd {
+
+CodeActions::CodeActions() {
+  // FIXME: provide a registry of the providers.
+  // FIXME: initialize code actions here.
+}
+
+std::vector<PreparedAction>
+CodeActions::prepareAll(const ActionInputs &Inputs) const {
+  std::vector<PreparedAction> Available;
+  for (size_t I = 0; I < Actions.size(); ++I) {
+    auto A = Actions[I]->prepare(Inputs);
+    if (!A)
+      continue;
+    A->setID(I);
+    Available.push_back(std::move(*A));
+  }
+  return Available;
+}
+
+llvm::Optional<PreparedAction>
+CodeActions::prepare(ActionID ID, const ActionInputs &Inputs) const {
+  if (Actions.size() <= ID) {
+    elog("id of the code action ({0}) is invalid", ID);
+    return llvm::None;
+  }
+  return Actions[ID]->prepare(Inputs);
+}
+
+} // namespace clangd
+} // namespace clang
\ No newline at end of file
Index: clangd/ClangdServer.h
===================================================================
--- clangd/ClangdServer.h
+++ clangd/ClangdServer.h
@@ -12,6 +12,7 @@
 
 #include "Cancellation.h"
 #include "ClangdUnit.h"
+#include "CodeActions.h"
 #include "CodeComplete.h"
 #include "FSProvider.h"
 #include "Function.h"
@@ -23,6 +24,7 @@
 #include "index/Index.h"
 #include "clang/Tooling/CompilationDatabase.h"
 #include "clang/Tooling/Core/Replacement.h"
+#include "llvm/ADT/FunctionExtras.h"
 #include "llvm/ADT/IntrusiveRefCntPtr.h"
 #include "llvm/ADT/Optional.h"
 #include "llvm/ADT/StringRef.h"
@@ -202,6 +204,17 @@
   void rename(PathRef File, Position Pos, llvm::StringRef NewName,
               Callback<std::vector<tooling::Replacement>> CB);
 
+  /// Enumerate the code actions available to the user at a specified point.
+  /// This runs the first stage of all the code actions known to clangd, see
+  /// CodeActions.h on distinction between the stages of the actions.
+  void enumerateCodeActions(PathRef File, Range R,
+                            Callback<std::vector<PreparedAction>> CB);
+
+  /// Apply the previously precomputed code action. This will always fully
+  /// execute the action.
+  void applyCodeAction(PathRef File, Range R, ActionID ID,
+                       Callback<tooling::Replacements> CB);
+
   /// Only for testing purposes.
   /// Waits until all requests to worker thread are finished and dumps AST for
   /// \p File. \p File must be in the list of added documents.
@@ -245,6 +258,7 @@
   const FileSystemProvider &FSProvider;
 
   Path ResourceDir;
+  CodeActions Actions;
   // The index used to look up symbols. This could be:
   //   - null (all index functionality is optional)
   //   - the dynamic index owned by ClangdServer (DynamicIdx)
Index: clangd/ClangdServer.cpp
===================================================================
--- clangd/ClangdServer.cpp
+++ clangd/ClangdServer.cpp
@@ -8,6 +8,7 @@
 //===-------------------------------------------------------------------===//
 
 #include "ClangdServer.h"
+#include "CodeActions.h"
 #include "CodeComplete.h"
 #include "FindSymbols.h"
 #include "Headers.h"
@@ -21,6 +22,7 @@
 #include "clang/Frontend/CompilerInvocation.h"
 #include "clang/Lex/Preprocessor.h"
 #include "clang/Tooling/CompilationDatabase.h"
+#include "clang/Tooling/Core/Replacement.h"
 #include "clang/Tooling/Refactoring/RefactoringResultConsumer.h"
 #include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
 #include "llvm/ADT/ArrayRef.h"
@@ -28,6 +30,7 @@
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Errc.h"
+#include "llvm/Support/Error.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/raw_ostream.h"
@@ -322,6 +325,48 @@
       "Rename", File, Bind(Action, File.str(), NewName.str(), std::move(CB)));
 }
 
+void ClangdServer::enumerateCodeActions(
+    PathRef File, Range Selection, Callback<std::vector<PreparedAction>> CB) {
+  auto Action = [this, Selection](decltype(CB) CB, std::string File,
+                                  Expected<InputsAndAST> InpAST) {
+    if (!InpAST)
+      return CB(InpAST.takeError());
+
+    auto Inputs = ActionInputs::create(File, InpAST->Inputs.Contents,
+                                       InpAST->AST, Selection);
+    if (!Inputs)
+      return CB(llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                        "could not create action context"));
+    CB(Actions.prepareAll(*Inputs));
+  };
+
+  WorkScheduler.runWithAST("EnumerateCodeActions", File,
+                           Bind(Action, std::move(CB), File.str()));
+}
+
+void ClangdServer::applyCodeAction(PathRef File, Range R, ActionID ID,
+                                   Callback<tooling::Replacements> CB) {
+  auto Action = [this, ID, R](decltype(CB) CB, std::string File,
+                              Expected<InputsAndAST> InpAST) {
+    if (!InpAST)
+      return CB(InpAST.takeError());
+
+    auto Inputs =
+        ActionInputs::create(File, InpAST->Inputs.Contents, InpAST->AST, R);
+    if (!Inputs)
+      return CB(llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                        "could not create action context"));
+
+    auto A = Actions.prepare(ID, *Inputs);
+    if (!A)
+      return CB(llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                        "failed to recreate the code action"));
+    return CB(std::move(*A).computeEdits());
+  };
+  WorkScheduler.runWithAST("ApplyCodeAction", File,
+                           Bind(Action, std::move(CB), File.str()));
+}
+
 void ClangdServer::dumpAST(PathRef File,
                            llvm::unique_function<void(std::string)> Callback) {
   auto Action = [](decltype(Callback) Callback,
Index: clangd/ClangdLSPServer.cpp
===================================================================
--- clangd/ClangdLSPServer.cpp
+++ clangd/ClangdLSPServer.cpp
@@ -9,11 +9,14 @@
 
 #include "ClangdLSPServer.h"
 #include "Diagnostics.h"
+#include "Protocol.h"
 #include "SourceCode.h"
 #include "Trace.h"
 #include "URI.h"
+#include "clang/Tooling/Core/Replacement.h"
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/Support/Errc.h"
+#include "llvm/Support/Error.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/ScopedPrinter.h"
@@ -338,7 +341,9 @@
             {"referencesProvider", true},
             {"executeCommandProvider",
              llvm::json::Object{
-                 {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}},
+                 {"commands",
+                  {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND,
+                   ExecuteCommandParams::CLANGD_APPLY_CODE_ACTION}},
              }},
         }}}});
 }
@@ -400,7 +405,7 @@
 
 void ClangdLSPServer::onCommand(const ExecuteCommandParams &Params,
                                 Callback<llvm::json::Value> Reply) {
-  auto ApplyEdit = [&](WorkspaceEdit WE) {
+  auto ApplyEdit = [this](WorkspaceEdit WE) {
     ApplyWorkspaceEditParams Edit;
     Edit.edit = std::move(WE);
     // Ideally, we would wait for the response and if there is no error, we
@@ -420,6 +425,33 @@
 
     Reply("Fix applied.");
     ApplyEdit(*Params.workspaceEdit);
+  } else if (Params.command == ExecuteCommandParams::CLANGD_APPLY_CODE_ACTION &&
+             Params.codeActionArgs) {
+    auto Code = DraftMgr.getDraft(Params.codeActionArgs->file);
+    if (!Code)
+      return Reply(llvm::createStringError(
+          llvm::inconvertibleErrorCode(),
+          "trying to apply a code action for a non-added file"));
+
+    auto Action = [ApplyEdit](decltype(Reply) Reply, std::string File,
+                              std::string Code,
+                              Expected<tooling::Replacements> R) {
+      if (!R)
+        return Reply(R.takeError());
+
+      WorkspaceEdit WE;
+      WE.changes.emplace();
+      (*WE.changes)[URI::createFile(File).toString()] =
+          replacementsToEdits(Code, *R);
+
+      Reply("Fix applied.");
+      ApplyEdit(std::move(WE));
+    };
+    Server->applyCodeAction(
+        Params.codeActionArgs->file, Params.codeActionArgs->selection,
+        Params.codeActionArgs->actionId,
+        Bind(Action, std::move(Reply), Params.codeActionArgs->file,
+             std::move(*Code)));
   } else {
     // We should not get here because ExecuteCommandParams would not have
     // parsed in the first place and this handler should not be called. But if
@@ -601,28 +633,79 @@
 
 void ClangdLSPServer::onCodeAction(const CodeActionParams &Params,
                                    Callback<llvm::json::Value> Reply) {
-  auto Code = DraftMgr.getDraft(Params.textDocument.uri.file());
+  StringRef File = Params.textDocument.uri.file();
+  auto Code = DraftMgr.getDraft(File);
   if (!Code)
     return Reply(llvm::make_error<LSPError>(
         "onCodeAction called for non-added file", ErrorCode::InvalidParams));
   // We provide a code action for Fixes on the specified diagnostics.
-  std::vector<CodeAction> Actions;
+  std::vector<CodeAction> FixIts;
   for (const Diagnostic &D : Params.context.diagnostics) {
-    for (auto &F : getFixes(Params.textDocument.uri.file(), D)) {
-      Actions.push_back(toCodeAction(F, Params.textDocument.uri));
-      Actions.back().diagnostics = {D};
+    for (auto &F : getFixes(File, D)) {
+      FixIts.push_back(toCodeAction(F, Params.textDocument.uri));
+      FixIts.back().diagnostics = {D};
     }
   }
 
-  if (SupportsCodeAction)
-    Reply(llvm::json::Array(Actions));
-  else {
-    std::vector<Command> Commands;
-    for (const auto &Action : Actions)
-      if (auto Command = asCommand(Action))
-        Commands.push_back(std::move(*Command));
-    Reply(llvm::json::Array(Commands));
-  }
+  // Now enumerate the semantic code actions.
+  auto ConsumeActions =
+      [this](decltype(Reply) Reply, std::string File, std::string Code, Range R,
+             std::vector<CodeAction> FixIts,
+             llvm::Expected<std::vector<PreparedAction>> SemActions) {
+        if (!SemActions) {
+          elog("error while getting semantic code actions: {0}",
+               SemActions.takeError());
+          return Reply(llvm::json::Array(FixIts));
+        }
+
+        std::vector<CodeAction> Actions = std::move(FixIts);
+        auto toCodeAction = [&](PreparedAction &&A) -> CodeAction {
+          CodeAction CA;
+          CA.title = A.title();
+          CA.kind = CodeAction::REFACTOR_KIND;
+          if (!A.needsMoreWork()) {
+            // This is an instant action, so we reply with a command to directly
+            // apply the edit that the action has already produced.
+            auto R = cantFail(std::move(A).computeEdits());
+
+            CA.edit.emplace();
+            CA.edit->changes.emplace();
+            (*CA.edit->changes)[URI::createFile(File).toString()] =
+                replacementsToEdits(Code, R);
+            return CA;
+          }
+
+          // This action requires an expensive second stage, we only run it if
+          // the user actually chooses the action. So we reply with a command to
+          // run the full action when requested.
+          CA.command.emplace();
+          CA.command->title = A.title();
+          CA.command->command = Command::CLANGD_APPLY_CODE_ACTION;
+          CA.command->codeActionArgs.emplace();
+          CA.command->codeActionArgs->file = File;
+          CA.command->codeActionArgs->actionId = A.id();
+          CA.command->codeActionArgs->selection = R;
+          return CA;
+        };
+
+        Actions.reserve(Actions.size() + SemActions->size());
+        for (PreparedAction &A : *SemActions)
+          Actions.push_back(toCodeAction(std::move(A)));
+
+        if (SupportsCodeAction)
+          return Reply(llvm::json::Array(Actions));
+        std::vector<Command> Commands;
+        for (const auto &Action : Actions) {
+          if (auto Command = asCommand(Action))
+            Commands.push_back(std::move(*Command));
+        }
+        return Reply(llvm::json::Array(Commands));
+      };
+
+  Server->enumerateCodeActions(File, Params.range,
+                               Bind(ConsumeActions, std::move(Reply),
+                                    File.str(), std::move(*Code), Params.range,
+                                    std::move(FixIts)));
 }
 
 void ClangdLSPServer::onCompletion(const CompletionParams &Params,
Index: clangd/CMakeLists.txt
===================================================================
--- clangd/CMakeLists.txt
+++ clangd/CMakeLists.txt
@@ -18,6 +18,7 @@
   ClangdLSPServer.cpp
   ClangdServer.cpp
   ClangdUnit.cpp
+  CodeActions.cpp
   CodeComplete.cpp
   CodeCompletionStrings.cpp
   Compiler.cpp
@@ -62,6 +63,8 @@
   index/dex/PostingList.cpp
   index/dex/Trigram.cpp
 
+  refactor/ActionProvider.cpp
+
   LINK_LIBS
   clangAST
   clangASTMatchers
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to