[clang-tools-extra] r296636 - [clangd] Add support for FixIts.

Benjamin Kramer via cfe-commits cfe-commits at lists.llvm.org
Wed Mar 1 08:16:29 PST 2017


Author: d0k
Date: Wed Mar  1 10:16:29 2017
New Revision: 296636

URL: http://llvm.org/viewvc/llvm-project?rev=296636&view=rev
Log:
[clangd] Add support for FixIts.

Summary:
This uses CodeActions to show 'apply fix' actions when code actions are
requested for a location. The actions themselves make use of a
clangd.applyFix command which has to be implemented on the editor side. I
included an implementation for vscode.

This also adds a -run-synchronously flag which runs everything on the main
thread. This is useful for testing.

Reviewers: krasimir

Subscribers: cfe-commits

Differential Revision: https://reviews.llvm.org/D30498

Added:
    clang-tools-extra/trunk/test/clangd/fixits.test
Modified:
    clang-tools-extra/trunk/clangd/ASTManager.cpp
    clang-tools-extra/trunk/clangd/ASTManager.h
    clang-tools-extra/trunk/clangd/ClangDMain.cpp
    clang-tools-extra/trunk/clangd/Protocol.cpp
    clang-tools-extra/trunk/clangd/Protocol.h
    clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp
    clang-tools-extra/trunk/clangd/ProtocolHandlers.h
    clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/extension.ts
    clang-tools-extra/trunk/test/clangd/formatting.test

Modified: clang-tools-extra/trunk/clangd/ASTManager.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ASTManager.cpp?rev=296636&r1=296635&r2=296636&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ASTManager.cpp (original)
+++ clang-tools-extra/trunk/clangd/ASTManager.cpp Wed Mar  1 10:16:29 2017
@@ -54,8 +54,9 @@ static int getSeverity(DiagnosticsEngine
   llvm_unreachable("Unknown diagnostic level!");
 }
 
-ASTManager::ASTManager(JSONOutput &Output, DocumentStore &Store)
-    : Output(Output), Store(Store),
+ASTManager::ASTManager(JSONOutput &Output, DocumentStore &Store,
+                       bool RunSynchronously)
+    : Output(Output), Store(Store), RunSynchronously(RunSynchronously),
       PCHs(std::make_shared<PCHContainerOperations>()),
       ClangWorker([this]() { runWorker(); }) {}
 
@@ -67,9 +68,8 @@ void ASTManager::runWorker() {
       std::unique_lock<std::mutex> Lock(RequestLock);
       // Check if there's another request pending. We keep parsing until
       // our one-element queue is empty.
-      ClangRequestCV.wait(Lock, [this] {
-        return !RequestQueue.empty() || Done;
-      });
+      ClangRequestCV.wait(Lock,
+                          [this] { return !RequestQueue.empty() || Done; });
 
       if (RequestQueue.empty() && Done)
         return;
@@ -78,49 +78,67 @@ void ASTManager::runWorker() {
       RequestQueue.pop_back();
     } // unlock.
 
-    auto &Unit = ASTs[File]; // Only one thread can access this at a time.
+    parseFileAndPublishDiagnostics(File);
+  }
+}
 
-    if (!Unit) {
-      Unit = createASTUnitForFile(File, this->Store);
-    } else {
-      // Do a reparse if this wasn't the first parse.
-      // FIXME: This might have the wrong working directory if it changed in the
-      // meantime.
-      Unit->Reparse(PCHs, getRemappedFiles(this->Store));
-    }
+void ASTManager::parseFileAndPublishDiagnostics(StringRef File) {
+  auto &Unit = ASTs[File]; // Only one thread can access this at a time.
 
-    if (!Unit)
-      continue;
+  if (!Unit) {
+    Unit = createASTUnitForFile(File, this->Store);
+  } else {
+    // Do a reparse if this wasn't the first parse.
+    // FIXME: This might have the wrong working directory if it changed in the
+    // meantime.
+    Unit->Reparse(PCHs, getRemappedFiles(this->Store));
+  }
+
+  if (!Unit)
+    return;
 
-    // Send the diagnotics to the editor.
-    // FIXME: If the diagnostic comes from a different file, do we want to
-    // show them all? Right now we drop everything not coming from the
-    // main file.
-    // FIXME: Send FixIts to the editor.
-    std::string Diagnostics;
-    for (ASTUnit::stored_diag_iterator D = Unit->stored_diag_begin(),
-                                       DEnd = Unit->stored_diag_end();
-         D != DEnd; ++D) {
-      if (!D->getLocation().isValid() ||
-          !D->getLocation().getManager().isInMainFile(D->getLocation()))
-        continue;
-      Position P;
-      P.line = D->getLocation().getSpellingLineNumber() - 1;
-      P.character = D->getLocation().getSpellingColumnNumber();
-      Range R = {P, P};
-      Diagnostics +=
-          R"({"range":)" + Range::unparse(R) +
-          R"(,"severity":)" + std::to_string(getSeverity(D->getLevel())) +
-          R"(,"message":")" + llvm::yaml::escape(D->getMessage()) +
-          R"("},)";
+  // Send the diagnotics to the editor.
+  // FIXME: If the diagnostic comes from a different file, do we want to
+  // show them all? Right now we drop everything not coming from the
+  // main file.
+  std::string Diagnostics;
+  DiagnosticToReplacementMap LocalFixIts; // Temporary storage
+  for (ASTUnit::stored_diag_iterator D = Unit->stored_diag_begin(),
+                                     DEnd = Unit->stored_diag_end();
+       D != DEnd; ++D) {
+    if (!D->getLocation().isValid() ||
+        !D->getLocation().getManager().isInMainFile(D->getLocation()))
+      continue;
+    Position P;
+    P.line = D->getLocation().getSpellingLineNumber() - 1;
+    P.character = D->getLocation().getSpellingColumnNumber();
+    Range R = {P, P};
+    Diagnostics +=
+        R"({"range":)" + Range::unparse(R) +
+        R"(,"severity":)" + std::to_string(getSeverity(D->getLevel())) +
+        R"(,"message":")" + llvm::yaml::escape(D->getMessage()) +
+        R"("},)";
+
+    // We convert to Replacements to become independent of the SourceManager.
+    clangd::Diagnostic Diag = {R, getSeverity(D->getLevel()), D->getMessage()};
+    auto &FixItsForDiagnostic = LocalFixIts[Diag];
+    for (const FixItHint &Fix : D->getFixIts()) {
+      FixItsForDiagnostic.push_back(clang::tooling::Replacement(
+          Unit->getSourceManager(), Fix.RemoveRange, Fix.CodeToInsert));
     }
+  }
 
-    if (!Diagnostics.empty())
-      Diagnostics.pop_back(); // Drop trailing comma.
-    Output.writeMessage(
-        R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" +
-        File + R"(","diagnostics":[)" + Diagnostics + R"(]}})");
+  // Put FixIts into place.
+  {
+    std::lock_guard<std::mutex> Guard(FixItLock);
+    FixIts = std::move(LocalFixIts);
   }
+
+  if (!Diagnostics.empty())
+    Diagnostics.pop_back(); // Drop trailing comma.
+  Output.writeMessage(
+      R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" +
+      File + R"(","diagnostics":[)" + Diagnostics + R"(]}})");
 }
 
 ASTManager::~ASTManager() {
@@ -134,6 +152,10 @@ ASTManager::~ASTManager() {
 }
 
 void ASTManager::onDocumentAdd(StringRef Uri) {
+  if (RunSynchronously) {
+    parseFileAndPublishDiagnostics(Uri);
+    return;
+  }
   std::lock_guard<std::mutex> Guard(RequestLock);
   // Currently we discard all pending requests and just enqueue the latest one.
   RequestQueue.clear();
@@ -201,3 +223,12 @@ ASTManager::createASTUnitForFile(StringR
       /*IncludeBriefCommentsInCodeCompletion=*/true,
       /*AllowPCHWithCompilerErrors=*/true));
 }
+
+llvm::ArrayRef<clang::tooling::Replacement>
+ASTManager::getFixIts(const clangd::Diagnostic &D) {
+  std::lock_guard<std::mutex> Guard(FixItLock);
+  auto I = FixIts.find(D);
+  if (I != FixIts.end())
+    return I->second;
+  return llvm::None;
+}

Modified: clang-tools-extra/trunk/clangd/ASTManager.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ASTManager.h?rev=296636&r1=296635&r2=296636&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ASTManager.h (original)
+++ clang-tools-extra/trunk/clangd/ASTManager.h Wed Mar  1 10:16:29 2017
@@ -12,6 +12,8 @@
 
 #include "DocumentStore.h"
 #include "JSONRPCDispatcher.h"
+#include "Protocol.h"
+#include "clang/Tooling/Core/Replacement.h"
 #include "llvm/ADT/IntrusiveRefCntPtr.h"
 #include <condition_variable>
 #include <deque>
@@ -29,17 +31,27 @@ namespace clangd {
 
 class ASTManager : public DocumentStoreListener {
 public:
-  ASTManager(JSONOutput &Output, DocumentStore &Store);
+  ASTManager(JSONOutput &Output, DocumentStore &Store, bool RunSynchronously);
   ~ASTManager() override;
 
   void onDocumentAdd(StringRef Uri) override;
   // FIXME: Implement onDocumentRemove
   // FIXME: Implement codeComplete
 
+  /// Get the fixes associated with a certain diagnostic as replacements.
+  llvm::ArrayRef<clang::tooling::Replacement>
+  getFixIts(const clangd::Diagnostic &D);
+
+  DocumentStore &getStore() const { return Store; }
+
 private:
   JSONOutput &Output;
   DocumentStore &Store;
 
+  // Set to true if requests should block instead of being processed
+  // asynchronously.
+  bool RunSynchronously;
+
   /// Loads a compilation database for URI. May return nullptr if it fails. The
   /// database is cached for subsequent accesses.
   clang::tooling::CompilationDatabase *
@@ -50,6 +62,7 @@ private:
   createASTUnitForFile(StringRef Uri, const DocumentStore &Docs);
 
   void runWorker();
+  void parseFileAndPublishDiagnostics(StringRef File);
 
   /// Clang objects.
   llvm::StringMap<std::unique_ptr<clang::ASTUnit>> ASTs;
@@ -57,6 +70,11 @@ private:
       CompilationDatabases;
   std::shared_ptr<clang::PCHContainerOperations> PCHs;
 
+  typedef std::map<clangd::Diagnostic, std::vector<clang::tooling::Replacement>>
+      DiagnosticToReplacementMap;
+  DiagnosticToReplacementMap FixIts;
+  std::mutex FixItLock;
+
   /// Queue of requests.
   std::deque<std::string> RequestQueue;
   /// Setting Done to true will make the worker thread terminate.

Modified: clang-tools-extra/trunk/clangd/ClangDMain.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangDMain.cpp?rev=296636&r1=296635&r2=296636&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangDMain.cpp (original)
+++ clang-tools-extra/trunk/clangd/ClangDMain.cpp Wed Mar  1 10:16:29 2017
@@ -11,13 +11,20 @@
 #include "DocumentStore.h"
 #include "JSONRPCDispatcher.h"
 #include "ProtocolHandlers.h"
+#include "llvm/Support/CommandLine.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/Program.h"
 #include <iostream>
 #include <string>
 using namespace clang::clangd;
 
+static llvm::cl::opt<bool>
+    RunSynchronously("run-synchronously",
+                     llvm::cl::desc("parse on main thread"),
+                     llvm::cl::init(false), llvm::cl::Hidden);
+
 int main(int argc, char *argv[]) {
+  llvm::cl::ParseCommandLineOptions(argc, argv, "clangd");
   llvm::raw_ostream &Outs = llvm::outs();
   llvm::raw_ostream &Logs = llvm::errs();
   JSONOutput Out(Outs, Logs);
@@ -28,14 +35,14 @@ int main(int argc, char *argv[]) {
   // Set up a document store and intialize all the method handlers for JSONRPC
   // dispatching.
   DocumentStore Store;
-  ASTManager AST(Out, Store);
+  ASTManager AST(Out, Store, RunSynchronously);
   Store.addListener(&AST);
   JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
   Dispatcher.registerHandler("initialize",
                              llvm::make_unique<InitializeHandler>(Out));
   auto ShutdownPtr = llvm::make_unique<ShutdownHandler>(Out);
   auto *ShutdownHandler = ShutdownPtr.get();
-  Dispatcher.registerHandler("shutdown",std::move(ShutdownPtr));
+  Dispatcher.registerHandler("shutdown", std::move(ShutdownPtr));
   Dispatcher.registerHandler(
       "textDocument/didOpen",
       llvm::make_unique<TextDocumentDidOpenHandler>(Out, Store));
@@ -52,6 +59,8 @@ int main(int argc, char *argv[]) {
   Dispatcher.registerHandler(
       "textDocument/formatting",
       llvm::make_unique<TextDocumentFormattingHandler>(Out, Store));
+  Dispatcher.registerHandler("textDocument/codeAction",
+                             llvm::make_unique<CodeActionHandler>(Out, AST));
 
   while (std::cin.good()) {
     // A Language Server Protocol message starts with a HTTP header, delimited

Modified: clang-tools-extra/trunk/clangd/Protocol.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Protocol.cpp?rev=296636&r1=296635&r2=296636&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/Protocol.cpp (original)
+++ clang-tools-extra/trunk/clangd/Protocol.cpp Wed Mar  1 10:16:29 2017
@@ -457,3 +457,116 @@ DocumentFormattingParams::parse(llvm::ya
   }
   return Result;
 }
+
+llvm::Optional<Diagnostic> Diagnostic::parse(llvm::yaml::MappingNode *Params) {
+  Diagnostic 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);
+
+    llvm::SmallString<10> Storage;
+    if (KeyValue == "range") {
+      auto *Value =
+          dyn_cast_or_null<llvm::yaml::MappingNode>(NextKeyValue.getValue());
+      if (!Value)
+        return llvm::None;
+      auto Parsed = Range::parse(Value);
+      if (!Parsed)
+        return llvm::None;
+      Result.range = std::move(*Parsed);
+    } else if (KeyValue == "severity") {
+      auto *Value =
+          dyn_cast_or_null<llvm::yaml::ScalarNode>(NextKeyValue.getValue());
+      if (!Value)
+        return llvm::None;
+      long long Val;
+      if (llvm::getAsSignedInteger(Value->getValue(Storage), 0, Val))
+        return llvm::None;
+      Result.severity = Val;
+    } else if (KeyValue == "message") {
+      auto *Value =
+          dyn_cast_or_null<llvm::yaml::ScalarNode>(NextKeyValue.getValue());
+      if (!Value)
+        return llvm::None;
+      Result.message = Value->getValue(Storage);
+    } else {
+      return llvm::None;
+    }
+  }
+  return Result;
+}
+
+llvm::Optional<CodeActionContext>
+CodeActionContext::parse(llvm::yaml::MappingNode *Params) {
+  CodeActionContext 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);
+    auto *Value = NextKeyValue.getValue();
+
+    llvm::SmallString<10> Storage;
+    if (KeyValue == "diagnostics") {
+      auto *Seq = dyn_cast<llvm::yaml::SequenceNode>(Value);
+      if (!Seq)
+        return llvm::None;
+      for (auto &Item : *Seq) {
+        auto *I = dyn_cast<llvm::yaml::MappingNode>(&Item);
+        if (!I)
+          return llvm::None;
+        auto Parsed = Diagnostic::parse(I);
+        if (!Parsed)
+          return llvm::None;
+        Result.diagnostics.push_back(std::move(*Parsed));
+      }
+    } else {
+      return llvm::None;
+    }
+  }
+  return Result;
+}
+
+llvm::Optional<CodeActionParams>
+CodeActionParams::parse(llvm::yaml::MappingNode *Params) {
+  CodeActionParams 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);
+    auto *Value =
+        dyn_cast_or_null<llvm::yaml::MappingNode>(NextKeyValue.getValue());
+    if (!Value)
+      return llvm::None;
+
+    llvm::SmallString<10> Storage;
+    if (KeyValue == "textDocument") {
+      auto Parsed = TextDocumentIdentifier::parse(Value);
+      if (!Parsed)
+        return llvm::None;
+      Result.textDocument = std::move(*Parsed);
+    } else if (KeyValue == "range") {
+      auto Parsed = Range::parse(Value);
+      if (!Parsed)
+        return llvm::None;
+      Result.range = std::move(*Parsed);
+    } else if (KeyValue == "context") {
+      auto Parsed = CodeActionContext::parse(Value);
+      if (!Parsed)
+        return llvm::None;
+      Result.context = std::move(*Parsed);
+    } else {
+      return llvm::None;
+    }
+  }
+  return Result;
+}

Modified: clang-tools-extra/trunk/clangd/Protocol.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Protocol.h?rev=296636&r1=296635&r2=296636&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/Protocol.h (original)
+++ clang-tools-extra/trunk/clangd/Protocol.h Wed Mar  1 10:16:29 2017
@@ -44,6 +44,15 @@ struct Position {
   /// Character offset on a line in a document (zero-based).
   int character;
 
+  friend bool operator==(const Position &LHS, const Position &RHS) {
+    return std::tie(LHS.line, LHS.character) ==
+           std::tie(RHS.line, RHS.character);
+  }
+  friend bool operator<(const Position &LHS, const Position &RHS) {
+    return std::tie(LHS.line, LHS.character) <
+           std::tie(RHS.line, RHS.character);
+  }
+
   static llvm::Optional<Position> parse(llvm::yaml::MappingNode *Params);
   static std::string unparse(const Position &P);
 };
@@ -55,6 +64,13 @@ struct Range {
   /// The range's end position.
   Position end;
 
+  friend bool operator==(const Range &LHS, const Range &RHS) {
+    return std::tie(LHS.start, LHS.end) == std::tie(RHS.start, RHS.end);
+  }
+  friend bool operator<(const Range &LHS, const Range &RHS) {
+    return std::tie(LHS.start, LHS.end) < std::tie(RHS.start, RHS.end);
+  }
+
   static llvm::Optional<Range> parse(llvm::yaml::MappingNode *Params);
   static std::string unparse(const Range &P);
 };
@@ -172,6 +188,51 @@ struct DocumentFormattingParams {
   parse(llvm::yaml::MappingNode *Params);
 };
 
+struct Diagnostic {
+  /// The range at which the message applies.
+  Range range;
+
+  /// The diagnostic's severity. Can be omitted. If omitted it is up to the
+  /// client to interpret diagnostics as error, warning, info or hint.
+  int severity;
+
+  /// The diagnostic's message.
+  std::string message;
+
+  friend bool operator==(const Diagnostic &LHS, const Diagnostic &RHS) {
+    return std::tie(LHS.range, LHS.severity, LHS.message) ==
+           std::tie(RHS.range, RHS.severity, RHS.message);
+  }
+  friend bool operator<(const Diagnostic &LHS, const Diagnostic &RHS) {
+    return std::tie(LHS.range, LHS.severity, LHS.message) <
+           std::tie(RHS.range, RHS.severity, RHS.message);
+  }
+
+  static llvm::Optional<Diagnostic> parse(llvm::yaml::MappingNode *Params);
+};
+
+struct CodeActionContext {
+  /// An array of diagnostics.
+  std::vector<Diagnostic> diagnostics;
+
+  static llvm::Optional<CodeActionContext>
+  parse(llvm::yaml::MappingNode *Params);
+};
+
+struct CodeActionParams {
+  /// The document in which the command was invoked.
+  TextDocumentIdentifier textDocument;
+
+  /// The range for which the command was invoked.
+  Range range;
+
+  /// Context carrying additional information.
+  CodeActionContext context;
+
+  static llvm::Optional<CodeActionParams>
+  parse(llvm::yaml::MappingNode *Params);
+};
+
 } // namespace clangd
 } // namespace clang
 

Modified: clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp?rev=296636&r1=296635&r2=296636&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp (original)
+++ clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp Wed Mar  1 10:16:29 2017
@@ -8,6 +8,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "ProtocolHandlers.h"
+#include "ASTManager.h"
 #include "DocumentStore.h"
 #include "clang/Format/Format.h"
 using namespace clang;
@@ -58,18 +59,9 @@ static Position offsetToPosition(StringR
   return {Lines, Cols};
 }
 
-static std::string formatCode(StringRef Code, StringRef Filename,
-                              ArrayRef<tooling::Range> Ranges, StringRef ID) {
-  // Call clang-format.
-  // FIXME: Don't ignore style.
-  format::FormatStyle Style = format::getLLVMStyle();
-  // On windows FileManager doesn't like file://. Just strip it, clang-format
-  // doesn't need it.
-  Filename.consume_front("file://");
-  tooling::Replacements Replacements =
-      format::reformat(Style, Code, Ranges, Filename);
-
-  // Now turn the replacements into the format specified by the Language Server
+template <typename T>
+static std::string replacementsToEdits(StringRef Code, const T &Replacements) {
+  // Turn the replacements into the format specified by the Language Server
   // Protocol. Fuse them into one big JSON array.
   std::string Edits;
   for (auto &R : Replacements) {
@@ -83,6 +75,21 @@ static std::string formatCode(StringRef
   if (!Edits.empty())
     Edits.pop_back();
 
+  return Edits;
+}
+
+static std::string formatCode(StringRef Code, StringRef Filename,
+                              ArrayRef<tooling::Range> Ranges, StringRef ID) {
+  // Call clang-format.
+  // FIXME: Don't ignore style.
+  format::FormatStyle Style = format::getLLVMStyle();
+  // On windows FileManager doesn't like file://. Just strip it, clang-format
+  // doesn't need it.
+  Filename.consume_front("file://");
+  tooling::Replacements Replacements =
+      format::reformat(Style, Code, Ranges, Filename);
+
+  std::string Edits = replacementsToEdits(Code, Replacements);
   return R"({"jsonrpc":"2.0","id":)" + ID.str() +
          R"(,"result":[)" + Edits + R"(]})";
 }
@@ -138,3 +145,36 @@ void TextDocumentFormattingHandler::hand
   writeMessage(formatCode(Code, DFP->textDocument.uri,
                           {clang::tooling::Range(0, Code.size())}, ID));
 }
+
+void CodeActionHandler::handleMethod(llvm::yaml::MappingNode *Params,
+                                     StringRef ID) {
+  auto CAP = CodeActionParams::parse(Params);
+  if (!CAP) {
+    Output.log("Failed to decode CodeActionParams!\n");
+    return;
+  }
+
+  // We provide a code action for each diagnostic at the requested location
+  // which has FixIts available.
+  std::string Code = AST.getStore().getDocument(CAP->textDocument.uri);
+  std::string Commands;
+  for (Diagnostic &D : CAP->context.diagnostics) {
+    std::vector<clang::tooling::Replacement> Fixes = AST.getFixIts(D);
+    std::string Edits = replacementsToEdits(Code, Fixes);
+
+    if (!Edits.empty())
+      Commands +=
+          R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
+          R"('", "command": "clangd.applyFix", "arguments": [")" +
+          llvm::yaml::escape(CAP->textDocument.uri) +
+          R"(", [)" + Edits +
+          R"(]]},)";
+  }
+  if (!Commands.empty())
+    Commands.pop_back();
+
+  writeMessage(
+      R"({"jsonrpc":"2.0","id":)" + ID.str() +
+      R"(, "result": [)" + Commands +
+      R"(]})");
+}

Modified: clang-tools-extra/trunk/clangd/ProtocolHandlers.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ProtocolHandlers.h?rev=296636&r1=296635&r2=296636&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ProtocolHandlers.h (original)
+++ clang-tools-extra/trunk/clangd/ProtocolHandlers.h Wed Mar  1 10:16:29 2017
@@ -22,6 +22,7 @@
 
 namespace clang {
 namespace clangd {
+class ASTManager;
 class DocumentStore;
 
 struct InitializeHandler : Handler {
@@ -34,7 +35,8 @@ struct InitializeHandler : Handler {
           "textDocumentSync": 1,
           "documentFormattingProvider": true,
           "documentRangeFormattingProvider": true,
-          "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]}
+          "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
+          "codeActionProvider": true
         }}})");
   }
 };
@@ -102,6 +104,16 @@ private:
   DocumentStore &Store;
 };
 
+struct CodeActionHandler : Handler {
+  CodeActionHandler(JSONOutput &Output, ASTManager &AST)
+      : Handler(Output), AST(AST) {}
+
+  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override;
+
+private:
+  ASTManager &AST;
+};
+
 } // namespace clangd
 } // namespace clang
 

Modified: clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/extension.ts
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/extension.ts?rev=296636&r1=296635&r2=296636&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/extension.ts (original)
+++ clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/extension.ts Wed Mar  1 10:16:29 2017
@@ -18,9 +18,25 @@ export function activate(context: vscode
 
     const clangdClient = new vscodelc.LanguageClient('Clang Language Server', serverOptions, clientOptions);
 
+    function applyTextEdits(uri: string, edits: vscodelc.TextEdit[]) {
+        let textEditor = vscode.window.activeTextEditor;
+
+        if (textEditor && textEditor.document.uri.toString() === uri) {
+            textEditor.edit(mutator => {
+                for (const edit of edits) {
+                    mutator.replace(vscodelc.Protocol2Code.asRange(edit.range), edit.newText);
+                }
+            }).then((success) => {
+                if (!success) {
+                    vscode.window.showErrorMessage('Failed to apply fixes to the document.');
+                }
+            });
+        }
+    }
+
     console.log('Clang Language Server is now active!');
 
     const disposable = clangdClient.start();
 
-    context.subscriptions.push(disposable);
+    context.subscriptions.push(disposable, vscode.commands.registerCommand('clangd.applyFix', applyTextEdits));
 }
\ No newline at end of file

Added: clang-tools-extra/trunk/test/clangd/fixits.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/fixits.test?rev=296636&view=auto
==============================================================================
--- clang-tools-extra/trunk/test/clangd/fixits.test (added)
+++ clang-tools-extra/trunk/test/clangd/fixits.test Wed Mar  1 10:16:29 2017
@@ -0,0 +1,22 @@
+# 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: 180
+
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///foo.c","languageId":"c","version":1,"text":"int main(int i, char **a) { if (i = 2) {}}"}}}
+#
+# CHECK: {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///foo.c","diagnostics":[{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":2,"message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}}
+#
+Content-Length: 746
+
+ {"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":2,"message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}}}
+#
+# CHECK: {"jsonrpc":"2.0","id":2, "result": [{"title":"Apply FixIt 'place parentheses around the assignment to silence this warning'", "command": "clangd.applyFix", "arguments": ["file:///foo.c", [{"range": {"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 32}}, "newText": "("},{"range": {"start": {"line": 0, "character": 37}, "end": {"line": 0, "character": 37}}, "newText": ")"}]]},{"title":"Apply FixIt 'use '==' to turn this assignment into an equality comparison'", "command": "clangd.applyFix", "arguments": ["file:///foo.c", [{"range": {"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}}, "newText": "=="}]]}]
+#
+Content-Length: 44
+
+{"jsonrpc":"2.0","id":3,"method":"shutdown"}

Modified: clang-tools-extra/trunk/test/clangd/formatting.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/formatting.test?rev=296636&r1=296635&r2=296636&view=diff
==============================================================================
--- clang-tools-extra/trunk/test/clangd/formatting.test (original)
+++ clang-tools-extra/trunk/test/clangd/formatting.test Wed Mar  1 10:16:29 2017
@@ -4,12 +4,13 @@
 Content-Length: 125
 
 {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
-# CHECK: Content-Length: 294
+# CHECK: Content-Length: 332
 # CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{
 # CHECK:   "textDocumentSync": 1,
 # CHECK:   "documentFormattingProvider": true,
 # CHECK:   "documentRangeFormattingProvider": true,
-# CHECK:   "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]}
+# CHECK:   "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
+# CHECK:   "codeActionProvider": true
 # CHECK: }}}
 #
 Content-Length: 193




More information about the cfe-commits mailing list