sammccall created this revision.
sammccall added a reviewer: ilya-biryukov.
Herald added subscribers: cfe-commits, kadircet, arphaman, jkorous, MaskRay, 
mgorny.
Herald added a project: clang.

This introduces a few new concepts:

- tweaks have an Intent (they don't all advertise as refactorings)
- tweaks may produce messages (for ShowMessage notification). Generalized 
Replacements -> Effect.
- tweaks (and other features) may be hidden (clangd -hidden-features flag). We 
may choose to promote these one day. I'm not sure they're worth their own 
feature flags though.

Verified it in vim-clangd (not yet open source), curious if the UI is ok in 
VSCode.


Repository:
  rCTE Clang Tools Extra

https://reviews.llvm.org/D62538

Files:
  clangd/ClangdLSPServer.cpp
  clangd/ClangdServer.cpp
  clangd/ClangdServer.h
  clangd/Protocol.cpp
  clangd/Protocol.h
  clangd/refactor/Tweak.h
  clangd/refactor/tweaks/CMakeLists.txt
  clangd/refactor/tweaks/DumpAST.cpp
  clangd/refactor/tweaks/RawStringLiteral.cpp
  clangd/refactor/tweaks/SwapIfBranches.cpp
  clangd/tool/ClangdMain.cpp
  clangd/unittests/TweakTests.cpp

Index: clangd/unittests/TweakTests.cpp
===================================================================
--- clangd/unittests/TweakTests.cpp
+++ clangd/unittests/TweakTests.cpp
@@ -16,12 +16,12 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Testing/Support/Error.h"
+#include "gmock/gmock-matchers.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include <cassert>
 
 using llvm::Failed;
-using llvm::HasValue;
 using llvm::Succeeded;
 
 namespace clang {
@@ -76,7 +76,8 @@
 void checkNotAvailable(StringRef ID, llvm::StringRef Input) {
   return checkAvailable(ID, Input, /*Available=*/false);
 }
-llvm::Expected<std::string> apply(StringRef ID, llvm::StringRef Input) {
+
+llvm::Expected<Tweak::Effect> apply(StringRef ID, llvm::StringRef Input) {
   Annotations Code(Input);
   Range SelectionRng;
   if (Code.points().size() != 0) {
@@ -98,15 +99,30 @@
   auto T = prepareTweak(ID, S);
   if (!T)
     return T.takeError();
-  auto Replacements = (*T)->apply(S);
-  if (!Replacements)
-    return Replacements.takeError();
-  return applyAllReplacements(Code.code(), *Replacements);
+  return (*T)->apply(S);
+}
+
+llvm::Expected<std::string> applyEdit(StringRef ID, llvm::StringRef Input) {
+  auto Effect = apply(ID, Input);
+  if (!Effect)
+    return Effect.takeError();
+  if (!Effect->ApplyEdit)
+    return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                   "No replacements");
+  Annotations Code(Input);
+  return applyAllReplacements(Code.code(), *Effect->ApplyEdit);
+}
+
+std::string getMessage(StringRef ID, llvm::StringRef Input) {
+  auto Effect = apply(ID, Input);
+  if (!Effect)
+    return "error: " + llvm::toString(Effect.takeError());
+  return Effect->ShowMessage.getValueOr("no message produced!");
 }
 
 void checkTransform(llvm::StringRef ID, llvm::StringRef Input,
                     std::string Output) {
-  auto Result = apply(ID, Input);
+  auto Result = applyEdit(ID, Input);
   ASSERT_TRUE(bool(Result)) << llvm::toString(Result.takeError()) << Input;
   EXPECT_EQ(Output, std::string(*Result)) << Input;
 }
@@ -217,6 +233,37 @@
   checkTransform(ID, Input, Output);
 }
 
+TEST(TweakTest, DumpAST) {
+  llvm::StringLiteral ID = "DumpAST";
+
+  checkAvailable(ID, "^int f^oo() { re^turn 2 ^+ 2; }");
+  checkNotAvailable(ID, "/*c^omment*/ int foo() return 2 ^ + 2; }");
+
+  const char *Input = "int x = 2 ^+ 2;";
+  const char *Output = R"(BinaryOperator.*'\+'.*
+.*IntegerLiteral.*'int' 2.*
+.*IntegerLiteral.*'int' 2.*)";
+  EXPECT_THAT(getMessage(ID, Input), ::testing::MatchesRegex(Output));
+}
+
+TEST(TweakTest, ShowSelectionTree) {
+  llvm::StringLiteral ID = "ShowSelectionTree";
+
+  checkAvailable(ID, "^int f^oo() { re^turn 2 ^+ 2; }");
+  checkNotAvailable(ID, "/*c^omment*/ int foo() return 2 ^ + 2; }");
+
+  const char *Input = "int fcall(int); int x = fca[[ll(2 +]]2);";
+  const char *Output = R"(TranslationUnitDecl 
+  VarDecl int x = fcall(2 + 2)
+   .CallExpr fcall(2 + 2)
+      ImplicitCastExpr fcall
+       .DeclRefExpr fcall
+     .BinaryOperator 2 + 2
+       *IntegerLiteral 2
+)";
+  EXPECT_EQ(Output, getMessage(ID, Input));
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
Index: clangd/tool/ClangdMain.cpp
===================================================================
--- clangd/tool/ClangdMain.cpp
+++ clangd/tool/ClangdMain.cpp
@@ -268,6 +268,11 @@
                                     "Always used text-based completion")),
         llvm::cl::init(CodeCompleteOptions().RunParser), llvm::cl::Hidden);
 
+static llvm::cl::opt<bool> HiddenFeatures(
+    "hidden-features",
+    llvm::cl::desc("Enable hidden features mostly useful to clangd developers"),
+    llvm::cl::init(false), llvm::cl::Hidden);
+
 namespace {
 
 /// \brief Supports a test URI scheme with relaxed constraints for lit tests.
@@ -465,6 +470,7 @@
   }
   Opts.StaticIndex = StaticIdx.get();
   Opts.AsyncThreadsCount = WorkerThreadsCount;
+  Opts.HiddenFeatures = HiddenFeatures;
 
   clangd::CodeCompleteOptions CCOpts;
   CCOpts.IncludeIneligibleResults = IncludeIneligibleResults;
Index: clangd/refactor/tweaks/SwapIfBranches.cpp
===================================================================
--- clangd/refactor/tweaks/SwapIfBranches.cpp
+++ clangd/refactor/tweaks/SwapIfBranches.cpp
@@ -37,8 +37,9 @@
   const char *id() const override final;
 
   bool prepare(const Selection &Inputs) override;
-  Expected<tooling::Replacements> apply(const Selection &Inputs) override;
-  std::string title() const override;
+  Expected<Effect> apply(const Selection &Inputs) override;
+  std::string title() const override { return "Swap if branches"; }
+  Intent intent() const override { return Refactor; }
 
 private:
   const IfStmt *If = nullptr;
@@ -60,7 +61,7 @@
          dyn_cast_or_null<CompoundStmt>(If->getElse());
 }
 
-Expected<tooling::Replacements> SwapIfBranches::apply(const Selection &Inputs) {
+Expected<Tweak::Effect> SwapIfBranches::apply(const Selection &Inputs) {
   auto &Ctx = Inputs.AST.getASTContext();
   auto &SrcMgr = Ctx.getSourceManager();
 
@@ -89,11 +90,9 @@
                                                  ElseRng->getBegin(),
                                                  ElseCode.size(), ThenCode)))
     return std::move(Err);
-  return Result;
+  return Effect::applyEdit(Result);
 }
 
-std::string SwapIfBranches::title() const { return "Swap if branches"; }
-
 } // namespace
 } // namespace clangd
 } // namespace clang
Index: clangd/refactor/tweaks/RawStringLiteral.cpp
===================================================================
--- clangd/refactor/tweaks/RawStringLiteral.cpp
+++ clangd/refactor/tweaks/RawStringLiteral.cpp
@@ -39,8 +39,9 @@
   const char *id() const override final;
 
   bool prepare(const Selection &Inputs) override;
-  Expected<tooling::Replacements> apply(const Selection &Inputs) override;
-  std::string title() const override;
+  Expected<Effect> apply(const Selection &Inputs) override;
+  std::string title() const override { return "Convert to raw string"; }
+  Intent intent() const override { return Refactor; }
 
 private:
   const clang::StringLiteral *Str = nullptr;
@@ -87,16 +88,13 @@
          needsRaw(Str->getBytes()) && canBeRaw(Str->getBytes());
 }
 
-Expected<tooling::Replacements>
-RawStringLiteral::apply(const Selection &Inputs) {
-  return tooling::Replacements(
+Expected<Tweak::Effect> RawStringLiteral::apply(const Selection &Inputs) {
+  return Effect::applyEdit(tooling::Replacements(
       tooling::Replacement(Inputs.AST.getASTContext().getSourceManager(), Str,
                            ("R\"(" + Str->getBytes() + ")\"").str(),
-                           Inputs.AST.getASTContext().getLangOpts()));
+                           Inputs.AST.getASTContext().getLangOpts())));
 }
 
-std::string RawStringLiteral::title() const { return "Convert to raw string"; }
-
 } // namespace
 } // namespace clangd
 } // namespace clang
Index: clangd/refactor/tweaks/DumpAST.cpp
===================================================================
--- /dev/null
+++ clangd/refactor/tweaks/DumpAST.cpp
@@ -0,0 +1,97 @@
+//===--- DumpAST.cpp ---------------------------------------------*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#include "refactor/Tweak.h"
+#include "clang/AST/ASTTypeTraits.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/ScopedPrinter.h"
+#include "llvm/Support/raw_ostream.h"
+
+namespace clang {
+namespace clangd {
+namespace {
+
+/// Dumps the AST of the selected node.
+/// Input:
+///   fcall("foo");
+///   ^^^^^
+/// Message:
+///   CallExpr
+///   |-DeclRefExpr fcall
+///   `-StringLiteral "foo"
+class DumpAST : public Tweak {
+public:
+  const char *id() const override final;
+
+  bool prepare(const Selection &Inputs) override {
+    for (auto N = Inputs.ASTSelection.commonAncestor(); N && !Node;
+         N = N->Parent)
+      if (dumpable(N->ASTNode))
+        Node = N->ASTNode;
+    return Node.hasValue();
+  }
+  Expected<Effect> apply(const Selection &Inputs) override;
+  std::string title() const override {
+    return llvm::formatv("Dump {0} AST", Node->getNodeKind().asStringRef());
+  }
+  Intent intent() const override { return Info; }
+  bool hidden() const override { return true; }
+
+private:
+  static bool dumpable(const ast_type_traits::DynTypedNode &N) {
+    // Sadly not all node types can be dumped, and there's no API to check.
+    // See DynTypedNode::dump().
+    return N.get<Decl>() || N.get<Stmt>() || N.get<Type>();
+  }
+
+  llvm::Optional<ast_type_traits::DynTypedNode> Node;
+};
+REGISTER_TWEAK(DumpAST)
+
+llvm::Expected<Tweak::Effect> DumpAST::apply(const Selection &Inputs) {
+  std::string Str;
+  llvm::raw_string_ostream OS(Str);
+  Node->dump(OS, Inputs.AST.getASTContext().getSourceManager());
+  return Effect::showMessage(std::move(OS.str()));
+}
+
+/// Dumps the SelectionTree.
+/// Input:
+/// int fcall(int);
+/// void foo() {
+///   fcall(2 + 2);
+///     ^^^^^
+/// }
+/// Message:
+/// TranslationUnitDecl
+///   FunctionDecl void foo()
+///     CompoundStmt {}
+///      .CallExpr fcall(2 + 2)
+///        ImplicitCastExpr fcall
+///         .DeclRefExpr fcall
+///        BinaryOperator 2 + 2
+///          *IntegerLiteral 2
+class ShowSelectionTree : public Tweak {
+  const char *id() const override final;
+
+  bool prepare(const Selection &Inputs) override {
+    return Inputs.ASTSelection.root() != nullptr;
+  }
+  Expected<Effect> apply(const Selection &Inputs) override {
+    return Effect::showMessage(llvm::to_string(Inputs.ASTSelection));
+  }
+  std::string title() const override {
+    return llvm::formatv("Show selection tree");
+  }
+  Intent intent() const override { return Info; }
+  bool hidden() const override { return true; }
+};
+REGISTER_TWEAK(ShowSelectionTree);
+
+} // namespace
+} // namespace clangd
+} // namespace clang
Index: clangd/refactor/tweaks/CMakeLists.txt
===================================================================
--- clangd/refactor/tweaks/CMakeLists.txt
+++ clangd/refactor/tweaks/CMakeLists.txt
@@ -12,6 +12,7 @@
 # $<TARGET_OBJECTS:obj.clangDaemonTweaks> to a list of sources, see
 # clangd/tool/CMakeLists.txt for an example.
 add_clang_library(clangDaemonTweaks OBJECT
+  DumpAST.cpp
   RawStringLiteral.cpp
   SwapIfBranches.cpp
 
Index: clangd/refactor/Tweak.h
===================================================================
--- clangd/refactor/Tweak.h
+++ clangd/refactor/Tweak.h
@@ -5,9 +5,9 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 //===----------------------------------------------------------------------===//
-// Tweaks 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.
+// Tweaks are small actions that run over the AST and produce edits, messages
+// etc 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
@@ -47,10 +47,36 @@
     ParsedAST &AST;
     /// A location of the cursor in the editor.
     SourceLocation Cursor;
-    // The AST nodes that were selected.
+    /// The AST nodes that were selected.
     SelectionTree ASTSelection;
     // FIXME: provide a way to get sources and ASTs for other files.
   };
+
+  /// Output of a tweak.
+  enum Intent {
+    /// Apply changes that preserve the behavior of the code.
+    Refactor,
+    /// Provide information to the user.
+    Info,
+  };
+  struct Effect {
+    /// A message to be displayed to the user.
+    llvm::Optional<std::string> ShowMessage;
+    /// An edit to apply to the input file.
+    llvm::Optional<tooling::Replacements> ApplyEdit;
+
+    static Effect applyEdit(tooling::Replacements R) {
+      Effect E;
+      E.ApplyEdit = std::move(R);
+      return E;
+    }
+    static Effect showMessage(StringRef S) {
+      Effect E;
+      E.ShowMessage = S;
+      return E;
+    }
+  };
+
   virtual ~Tweak() = default;
   /// A unique id of the action, it is always equal to the name of the class
   /// defining the Tweak. Definition is provided automatically by
@@ -63,13 +89,19 @@
   /// should be moved into 'apply'.
   /// Returns true iff the action is available and apply() can be called on it.
   virtual bool prepare(const Selection &Sel) = 0;
-  /// Run the second stage of the action that would produce the actual changes.
+  /// Run the second stage of the action that would produce the actual effect.
   /// EXPECTS: prepare() was called and returned true.
-  virtual Expected<tooling::Replacements> apply(const Selection &Sel) = 0;
+  virtual Expected<Effect> apply(const Selection &Sel) = 0;
+
   /// A one-line title of the action that should be shown to the users in the
   /// UI.
   /// EXPECTS: prepare() was called and returned true.
   virtual std::string title() const = 0;
+  /// Describes what kind of action this is.
+  /// EXPECTS: prepare() was called and returned true.
+  virtual Intent intent() const = 0;
+  /// Is this a 'hidden' tweak, which are off by default.
+  virtual bool hidden() const { return false; }
 };
 
 // All tweaks must be registered in the .cpp file next to their definition.
Index: clangd/Protocol.h
===================================================================
--- clangd/Protocol.h
+++ clangd/Protocol.h
@@ -463,6 +463,28 @@
 };
 bool fromJSON(const llvm::json::Value &, InitializeParams &);
 
+enum class MessageType {
+  /// An error message.
+  Error = 1,
+  /// A warning message.
+  Warning = 1,
+  /// An information message.
+  Info = 1,
+  /// A log message.
+  Log = 1,
+};
+llvm::json::Value toJSON(const MessageType &);
+
+/// The show message notification is sent from a server to a client to ask the
+/// client to display a particular message in the user interface.
+struct ShowMessageParams {
+  /// The message type.
+  MessageType type = MessageType::Info;
+  /// The actual message.
+  std::string message;
+};
+llvm::json::Value toJSON(const ShowMessageParams &);
+
 struct DidOpenTextDocumentParams {
   /// The document that was opened.
   TextDocumentItem textDocument;
@@ -731,6 +753,7 @@
   llvm::Optional<std::string> kind;
   const static llvm::StringLiteral QUICKFIX_KIND;
   const static llvm::StringLiteral REFACTOR_KIND;
+  const static llvm::StringLiteral INFO_KIND;
 
   /// The diagnostics that this code action resolves.
   llvm::Optional<std::vector<Diagnostic>> diagnostics;
Index: clangd/Protocol.cpp
===================================================================
--- clangd/Protocol.cpp
+++ clangd/Protocol.cpp
@@ -338,6 +338,14 @@
   return true;
 }
 
+llvm::json::Value toJSON(const MessageType &R) {
+  return static_cast<int64_t>(R);
+}
+
+llvm::json::Value toJSON(const ShowMessageParams &R) {
+  return llvm::json::Object{{"type", R.type}, {"message", R.message}};
+}
+
 bool fromJSON(const llvm::json::Value &Params, DidOpenTextDocumentParams &R) {
   llvm::json::ObjectMapper O(Params);
   return O && O.map("textDocument", R.textDocument);
@@ -587,6 +595,7 @@
 
 const llvm::StringLiteral CodeAction::QUICKFIX_KIND = "quickfix";
 const llvm::StringLiteral CodeAction::REFACTOR_KIND = "refactor";
+const llvm::StringLiteral CodeAction::INFO_KIND = "info";
 
 llvm::json::Value toJSON(const CodeAction &CA) {
   auto CodeAction = llvm::json::Object{{"title", CA.title}};
Index: clangd/ClangdServer.h
===================================================================
--- clangd/ClangdServer.h
+++ clangd/ClangdServer.h
@@ -123,6 +123,10 @@
         std::chrono::milliseconds(500);
 
     bool SuggestMissingIncludes = false;
+
+    /// Enable hidden features mostly useful to clangd developers.
+    /// e.g. tweaks to dump the AST.
+    bool HiddenFeatures = false;
   };
   // Sensible default options for use in tests.
   // Features like indexing must be enabled if desired.
@@ -226,6 +230,7 @@
   struct TweakRef {
     std::string ID;    /// ID to pass for applyTweak.
     std::string Title; /// A single-line message to show in the UI.
+    Tweak::Intent Intent;
   };
   /// Enumerate the code tweaks available to the user at a specified point.
   void enumerateTweaks(PathRef File, Range Sel,
@@ -233,7 +238,7 @@
 
   /// Apply the code tweak with a specified \p ID.
   void applyTweak(PathRef File, Range Sel, StringRef ID,
-                  Callback<tooling::Replacements> CB);
+                  Callback<Tweak::Effect> CB);
 
   /// Only for testing purposes.
   /// Waits until all requests to worker thread are finished and dumps AST for
@@ -290,6 +295,7 @@
   // If this is true, suggest include insertion fixes for diagnostic errors that
   // can be caused by missing includes (e.g. member access in incomplete type).
   bool SuggestMissingIncludes = false;
+  bool EnableHiddenFeatures = false;
 
   // GUARDED_BY(CachedCompletionFuzzyFindRequestMutex)
   llvm::StringMap<llvm::Optional<FuzzyFindRequest>>
Index: clangd/ClangdServer.cpp
===================================================================
--- clangd/ClangdServer.cpp
+++ clangd/ClangdServer.cpp
@@ -92,6 +92,7 @@
                      : nullptr),
       GetClangTidyOptions(Opts.GetClangTidyOptions),
       SuggestMissingIncludes(Opts.SuggestMissingIncludes),
+      EnableHiddenFeatures(Opts.HiddenFeatures),
       WorkspaceRoot(Opts.WorkspaceRoot),
       // Pass a callback into `WorkScheduler` to extract symbols from a newly
       // parsed file and rebuild the file index synchronously each time an AST
@@ -297,16 +298,19 @@
 
 void ClangdServer::enumerateTweaks(PathRef File, Range Sel,
                                    Callback<std::vector<TweakRef>> CB) {
-  auto Action = [Sel](decltype(CB) CB, std::string File,
-                      Expected<InputsAndAST> InpAST) {
+  auto Action = [this, Sel](decltype(CB) CB, std::string File,
+                            Expected<InputsAndAST> InpAST) {
     if (!InpAST)
       return CB(InpAST.takeError());
     auto Selection = tweakSelection(Sel, *InpAST);
     if (!Selection)
       return CB(Selection.takeError());
     std::vector<TweakRef> Res;
-    for (auto &T : prepareTweaks(*Selection))
-      Res.push_back({T->id(), T->title()});
+    for (auto &T : prepareTweaks(*Selection)) {
+      if (T->hidden() && !EnableHiddenFeatures)
+        continue;
+      Res.push_back({T->id(), T->title(), T->intent()});
+    }
     CB(std::move(Res));
   };
 
@@ -315,7 +319,7 @@
 }
 
 void ClangdServer::applyTweak(PathRef File, Range Sel, StringRef TweakID,
-                              Callback<tooling::Replacements> CB) {
+                              Callback<Tweak::Effect> CB) {
   auto Action = [Sel](decltype(CB) CB, std::string File, std::string TweakID,
                       Expected<InputsAndAST> InpAST) {
     if (!InpAST)
@@ -326,15 +330,19 @@
     auto A = prepareTweak(TweakID, *Selection);
     if (!A)
       return CB(A.takeError());
-    auto RawReplacements = (*A)->apply(*Selection);
-    if (!RawReplacements)
-      return CB(RawReplacements.takeError());
-    // FIXME: this function has I/O operations (find .clang-format file), figure
-    // out a way to cache the format style.
-    auto Style = getFormatStyleForFile(File, InpAST->Inputs.Contents,
-                                       InpAST->Inputs.FS.get());
-    return CB(
-        cleanupAndFormat(InpAST->Inputs.Contents, *RawReplacements, Style));
+    auto Effect = (*A)->apply(*Selection);
+    if (!Effect)
+      return CB(Effect.takeError());
+    if (Effect->ApplyEdit) {
+      // FIXME: this function has I/O operations (find .clang-format file),
+      // figure out a way to cache the format style.
+      auto Style = getFormatStyleForFile(File, InpAST->Inputs.Contents,
+                                         InpAST->Inputs.FS.get());
+      if (auto Formatted = cleanupAndFormat(InpAST->Inputs.Contents,
+                                            *Effect->ApplyEdit, Style))
+        Effect->ApplyEdit = std::move(*Formatted);
+    }
+    return CB(std::move(*Effect));
   };
   WorkScheduler.runWithAST(
       "ApplyTweak", File,
Index: clangd/ClangdLSPServer.cpp
===================================================================
--- clangd/ClangdLSPServer.cpp
+++ clangd/ClangdLSPServer.cpp
@@ -12,6 +12,7 @@
 #include "SourceCode.h"
 #include "Trace.h"
 #include "URI.h"
+#include "refactor/Tweak.h"
 #include "clang/Tooling/Core/Replacement.h"
 #include "llvm/ADT/Optional.h"
 #include "llvm/ADT/ScopeExit.h"
@@ -40,7 +41,14 @@
                         Range Selection) {
   CodeAction CA;
   CA.title = T.Title;
-  CA.kind = CodeAction::REFACTOR_KIND;
+  switch (T.Intent) {
+  case Tweak::Refactor:
+    CA.kind = CodeAction::REFACTOR_KIND;
+    break;
+  case Tweak::Info:
+    CA.kind = CodeAction::INFO_KIND;
+    break;
+  }
   // This tweak may have an expensive second stage, we only run it if the user
   // actually chooses it in the UI. We reply with a command that would run the
   // corresponding tweak.
@@ -487,18 +495,25 @@
           llvm::inconvertibleErrorCode(),
           "trying to apply a code action for a non-added file"));
 
-    auto Action = [ApplyEdit](decltype(Reply) Reply, URIForFile File,
-                              std::string Code,
-                              llvm::Expected<tooling::Replacements> R) {
+    auto Action = [this, ApplyEdit](decltype(Reply) Reply, URIForFile File,
+                                    std::string Code,
+                                    llvm::Expected<Tweak::Effect> R) {
       if (!R)
         return Reply(R.takeError());
 
-      WorkspaceEdit WE;
-      WE.changes.emplace();
-      (*WE.changes)[File.uri()] = replacementsToEdits(Code, *R);
-
-      Reply("Fix applied.");
-      ApplyEdit(std::move(WE));
+      if (R->ApplyEdit) {
+        WorkspaceEdit WE;
+        WE.changes.emplace();
+        (*WE.changes)[File.uri()] = replacementsToEdits(Code, *R->ApplyEdit);
+        ApplyEdit(std::move(WE));
+      }
+      if (R->ShowMessage) {
+        ShowMessageParams Msg;
+        Msg.message = *R->ShowMessage;
+        Msg.type = MessageType::Info;
+        notify("window/showMessage", Msg);
+      }
+      Reply("Tweak applied.");
     };
     Server->applyTweak(Params.tweakArgs->file.file(),
                        Params.tweakArgs->selection, Params.tweakArgs->tweakID,
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to