[clang-tools-extra] r317322 - [clangd] Handle clangd.applyFix server-side

Marc-Andre Laperle via cfe-commits cfe-commits at lists.llvm.org
Fri Nov 3 06:39:16 PDT 2017


Author: malaperle
Date: Fri Nov  3 06:39:15 2017
New Revision: 317322

URL: http://llvm.org/viewvc/llvm-project?rev=317322&view=rev
Log:
[clangd] Handle clangd.applyFix server-side

Summary:
When the user selects a fix-it (or any code action with commands), it is
possible to let the client forward the selected command to the server.
When the clangd.applyFix command is handled on the server, it can send a
workspace/applyEdit request to the client. This has the advantage that
the client doesn't explicitly have to know how to handle
clangd.applyFix. Therefore, the code to handle clangd.applyFix in the VS
Code extension (and any other Clangd client) is not required anymore.

Reviewers: ilya-biryukov, sammccall, Nebiroth, hokein

Reviewed By: hokein

Subscribers: ioeric, hokein, rwols, puremourning, bkramer, ilya-biryukov

Tags: #clang-tools-extra

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

Added:
    clang-tools-extra/trunk/test/clangd/execute-command.test
Modified:
    clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp
    clang-tools-extra/trunk/clangd/ClangdLSPServer.h
    clang-tools-extra/trunk/clangd/JSONRPCDispatcher.cpp
    clang-tools-extra/trunk/clangd/JSONRPCDispatcher.h
    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/fixits.test
    clang-tools-extra/trunk/test/clangd/initialize-params-invalid.test
    clang-tools-extra/trunk/test/clangd/initialize-params.test

Modified: clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp?rev=317322&r1=317321&r2=317322&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp (original)
+++ clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp Fri Nov  3 06:39:15 2017
@@ -10,27 +10,25 @@
 #include "ClangdLSPServer.h"
 #include "JSONRPCDispatcher.h"
 
+#include "llvm/Support/FormatVariadic.h"
+
 using namespace clang::clangd;
 using namespace clang;
 
 namespace {
 
-std::string
+std::vector<TextEdit>
 replacementsToEdits(StringRef Code,
                     const std::vector<tooling::Replacement> &Replacements) {
+  std::vector<TextEdit> Edits;
   // Turn the replacements into the format specified by the Language Server
-  // Protocol. Fuse them into one big JSON array.
-  std::string Edits;
+  // Protocol.
   for (auto &R : Replacements) {
     Range ReplacementRange = {
         offsetToPosition(Code, R.getOffset()),
         offsetToPosition(Code, R.getOffset() + R.getLength())};
-    TextEdit TE = {ReplacementRange, R.getReplacementText()};
-    Edits += TextEdit::unparse(TE);
-    Edits += ',';
+    Edits.push_back({ReplacementRange, R.getReplacementText()});
   }
-  if (!Edits.empty())
-    Edits.pop_back();
 
   return Edits;
 }
@@ -47,7 +45,9 @@ void ClangdLSPServer::onInitialize(Ctx C
           "codeActionProvider": true,
           "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
           "signatureHelpProvider": {"triggerCharacters": ["(",","]},
-          "definitionProvider": true
+          "definitionProvider": true,
+          "executeCommandProvider": {"commands": [")" +
+      ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND + R"("]}
         }})");
   if (Params.rootUri && !Params.rootUri->file.empty())
     Server.setRootPath(Params.rootUri->file);
@@ -84,6 +84,34 @@ void ClangdLSPServer::onFileEvent(Ctx C,
   Server.onFileEvent(Params);
 }
 
+void ClangdLSPServer::onCommand(Ctx C, ExecuteCommandParams &Params) {
+  if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND &&
+      Params.workspaceEdit) {
+    // The flow for "apply-fix" :
+    // 1. We publish a diagnostic, including fixits
+    // 2. The user clicks on the diagnostic, the editor asks us for code actions
+    // 3. We send code actions, with the fixit embedded as context
+    // 4. The user selects the fixit, the editor asks us to apply it
+    // 5. We unwrap the changes and send them back to the editor
+    // 6. The editor applies the changes (applyEdit), and sends us a reply (but
+    // we ignore it)
+
+    ApplyWorkspaceEditParams ApplyEdit;
+    ApplyEdit.edit = *Params.workspaceEdit;
+    C.reply("\"Fix applied.\"");
+    // We don't need the response so id == 1 is OK.
+    // Ideally, we would wait for the response and if there is no error, we
+    // would reply success/failure to the original RPC.
+    C.call("workspace/applyEdit", ApplyWorkspaceEditParams::unparse(ApplyEdit));
+  } 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
+    // more commands are added, this will be here has a safe guard.
+    C.replyError(
+        1, llvm::formatv("Unsupported command \"{0}\".", Params.command).str());
+  }
+}
+
 void ClangdLSPServer::onDocumentDidClose(Ctx C,
                                          DidCloseTextDocumentParams &Params) {
   Server.removeDocument(Params.textDocument.uri.file);
@@ -93,26 +121,27 @@ void ClangdLSPServer::onDocumentOnTypeFo
     Ctx C, DocumentOnTypeFormattingParams &Params) {
   auto File = Params.textDocument.uri.file;
   std::string Code = Server.getDocument(File);
-  std::string Edits =
-      replacementsToEdits(Code, Server.formatOnType(File, Params.position));
-  C.reply("[" + Edits + "]");
+  std::string Edits = TextEdit::unparse(
+      replacementsToEdits(Code, Server.formatOnType(File, Params.position)));
+  C.reply(Edits);
 }
 
 void ClangdLSPServer::onDocumentRangeFormatting(
     Ctx C, DocumentRangeFormattingParams &Params) {
   auto File = Params.textDocument.uri.file;
   std::string Code = Server.getDocument(File);
-  std::string Edits =
-      replacementsToEdits(Code, Server.formatRange(File, Params.range));
-  C.reply("[" + Edits + "]");
+  std::string Edits = TextEdit::unparse(
+      replacementsToEdits(Code, Server.formatRange(File, Params.range)));
+  C.reply(Edits);
 }
 
 void ClangdLSPServer::onDocumentFormatting(Ctx C,
                                            DocumentFormattingParams &Params) {
   auto File = Params.textDocument.uri.file;
   std::string Code = Server.getDocument(File);
-  std::string Edits = replacementsToEdits(Code, Server.formatFile(File));
-  C.reply("[" + Edits + "]");
+  std::string Edits =
+      TextEdit::unparse(replacementsToEdits(Code, Server.formatFile(File)));
+  C.reply(Edits);
 }
 
 void ClangdLSPServer::onCodeAction(Ctx C, CodeActionParams &Params) {
@@ -123,15 +152,16 @@ void ClangdLSPServer::onCodeAction(Ctx C
   for (Diagnostic &D : Params.context.diagnostics) {
     std::vector<clang::tooling::Replacement> Fixes =
         getFixIts(Params.textDocument.uri.file, D);
-    std::string Edits = replacementsToEdits(Code, Fixes);
+    auto Edits = replacementsToEdits(Code, Fixes);
+    WorkspaceEdit WE;
+    WE.changes = {{llvm::yaml::escape(Params.textDocument.uri.uri), Edits}};
 
     if (!Edits.empty())
       Commands +=
           R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
-          R"('", "command": "clangd.applyFix", "arguments": [")" +
-          llvm::yaml::escape(Params.textDocument.uri.uri) +
-          R"(", [)" + Edits +
-          R"(]]},)";
+          R"('", "command": ")" +
+          ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND +
+          R"(", "arguments": [)" + WorkspaceEdit::unparse(WE) + R"(]},)";
   }
   if (!Commands.empty())
     Commands.pop_back();

Modified: clang-tools-extra/trunk/clangd/ClangdLSPServer.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdLSPServer.h?rev=317322&r1=317321&r2=317322&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdLSPServer.h (original)
+++ clang-tools-extra/trunk/clangd/ClangdLSPServer.h Fri Nov  3 06:39:15 2017
@@ -69,6 +69,7 @@ private:
   void onGoToDefinition(Ctx C, TextDocumentPositionParams &Params) override;
   void onSwitchSourceHeader(Ctx C, TextDocumentIdentifier &Params) override;
   void onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) override;
+  void onCommand(Ctx C, ExecuteCommandParams &Params) override;
 
   std::vector<clang::tooling::Replacement>
   getFixIts(StringRef File, const clangd::Diagnostic &D);

Modified: clang-tools-extra/trunk/clangd/JSONRPCDispatcher.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/JSONRPCDispatcher.cpp?rev=317322&r1=317321&r2=317322&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/JSONRPCDispatcher.cpp (original)
+++ clang-tools-extra/trunk/clangd/JSONRPCDispatcher.cpp Fri Nov  3 06:39:15 2017
@@ -66,6 +66,13 @@ void RequestContext::replyError(int code
   }
 }
 
+void RequestContext::call(StringRef Method, StringRef Params) {
+  // FIXME: Generate/Increment IDs for every request so that we can get proper
+  // replies once we need to.
+  Out.writeMessage(llvm::Twine(R"({"jsonrpc":"2.0","id":1,"method":")" +
+                               Method + R"(","params":)" + Params + R"(})"));
+}
+
 void JSONRPCDispatcher::registerHandler(StringRef Method, Handler H) {
   assert(!Handlers.count(Method) && "Handler already registered!");
   Handlers[Method] = std::move(H);

Modified: clang-tools-extra/trunk/clangd/JSONRPCDispatcher.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/JSONRPCDispatcher.h?rev=317322&r1=317321&r2=317322&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/JSONRPCDispatcher.h (original)
+++ clang-tools-extra/trunk/clangd/JSONRPCDispatcher.h Fri Nov  3 06:39:15 2017
@@ -58,6 +58,8 @@ public:
   void reply(const Twine &Result);
   /// Sends an error response to the client, and logs it.
   void replyError(int code, const llvm::StringRef &Message);
+  /// Sends a request to the client.
+  void call(llvm::StringRef Method, StringRef Params);
 
 private:
   JSONOutput &Out;

Modified: clang-tools-extra/trunk/clangd/Protocol.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Protocol.cpp?rev=317322&r1=317321&r2=317322&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/Protocol.cpp (original)
+++ clang-tools-extra/trunk/clangd/Protocol.cpp Fri Nov  3 06:39:15 2017
@@ -287,6 +287,19 @@ std::string TextEdit::unparse(const Text
   return Result;
 }
 
+std::string TextEdit::unparse(const std::vector<TextEdit> &TextEdits) {
+  // Fuse all edits into one big JSON array.
+  std::string Edits;
+  for (auto &TE : TextEdits) {
+    Edits += TextEdit::unparse(TE);
+    Edits += ',';
+  }
+  if (!Edits.empty())
+    Edits.pop_back();
+
+  return "[" + Edits + "]";
+}
+
 namespace {
 TraceLevel getTraceLevel(llvm::StringRef TraceLevelStr,
                          clangd::Logger &Logger) {
@@ -846,6 +859,153 @@ CodeActionParams::parse(llvm::yaml::Mapp
   return Result;
 }
 
+llvm::Optional<std::map<std::string, std::vector<TextEdit>>>
+parseWorkspaceEditChange(llvm::yaml::MappingNode *Params,
+                         clangd::Logger &Logger) {
+  std::map<std::string, std::vector<TextEdit>> 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 (Result.count(KeyValue)) {
+      logIgnoredField(KeyValue, Logger);
+      continue;
+    }
+
+    auto *Value =
+        dyn_cast_or_null<llvm::yaml::SequenceNode>(NextKeyValue.getValue());
+    if (!Value)
+      return llvm::None;
+    for (auto &Item : *Value) {
+      auto *ItemValue = dyn_cast_or_null<llvm::yaml::MappingNode>(&Item);
+      if (!ItemValue)
+        return llvm::None;
+      auto Parsed = TextEdit::parse(ItemValue, Logger);
+      if (!Parsed)
+        return llvm::None;
+
+      Result[KeyValue].push_back(*Parsed);
+    }
+  }
+
+  return Result;
+}
+
+llvm::Optional<WorkspaceEdit>
+WorkspaceEdit::parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger) {
+  WorkspaceEdit 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 == "changes") {
+      auto *Value =
+          dyn_cast_or_null<llvm::yaml::MappingNode>(NextKeyValue.getValue());
+      if (!Value)
+        return llvm::None;
+      auto Parsed = parseWorkspaceEditChange(Value, Logger);
+      if (!Parsed)
+        return llvm::None;
+      Result.changes = std::move(*Parsed);
+    } else {
+      logIgnoredField(KeyValue, Logger);
+    }
+  }
+  return Result;
+}
+
+const std::string ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND =
+    "clangd.applyFix";
+
+llvm::Optional<ExecuteCommandParams>
+ExecuteCommandParams::parse(llvm::yaml::MappingNode *Params,
+                            clangd::Logger &Logger) {
+  ExecuteCommandParams Result;
+  // Depending on which "command" we parse, we will use this function to parse
+  // the command "arguments".
+  std::function<bool(llvm::yaml::MappingNode * Params)> ArgParser = nullptr;
+
+  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);
+
+    // Note that "commands" has to be parsed before "arguments" for this to
+    // work properly.
+    if (KeyValue == "command") {
+      auto *ScalarValue =
+          dyn_cast_or_null<llvm::yaml::ScalarNode>(NextKeyValue.getValue());
+      if (!ScalarValue)
+        return llvm::None;
+      llvm::SmallString<10> Storage;
+      Result.command = ScalarValue->getValue(Storage);
+      if (Result.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND) {
+        ArgParser = [&Result, &Logger](llvm::yaml::MappingNode *Params) {
+          auto WE = WorkspaceEdit::parse(Params, Logger);
+          if (WE)
+            Result.workspaceEdit = WE;
+          return WE.hasValue();
+        };
+      } else {
+        return llvm::None;
+      }
+    } else if (KeyValue == "arguments") {
+      auto *Value = NextKeyValue.getValue();
+      auto *Seq = dyn_cast<llvm::yaml::SequenceNode>(Value);
+      if (!Seq)
+        return llvm::None;
+      for (auto &Item : *Seq) {
+        auto *ItemValue = dyn_cast_or_null<llvm::yaml::MappingNode>(&Item);
+        if (!ItemValue || !ArgParser)
+          return llvm::None;
+        if (!ArgParser(ItemValue))
+          return llvm::None;
+      }
+    } else {
+      logIgnoredField(KeyValue, Logger);
+    }
+  }
+  if (Result.command.empty())
+    return llvm::None;
+
+  return Result;
+}
+
+std::string WorkspaceEdit::unparse(const WorkspaceEdit &WE) {
+  std::string Changes;
+  for (auto &Change : *WE.changes) {
+    Changes += llvm::formatv(R"("{0}": {1})", Change.first,
+                             TextEdit::unparse(Change.second));
+    Changes += ',';
+  }
+  if (!Changes.empty())
+    Changes.pop_back();
+
+  std::string Result;
+  llvm::raw_string_ostream(Result)
+      << llvm::format(R"({"changes": {%s}})", Changes.c_str());
+  return Result;
+}
+
+std::string
+ApplyWorkspaceEditParams::unparse(const ApplyWorkspaceEditParams &Params) {
+  std::string Result;
+  llvm::raw_string_ostream(Result) << llvm::format(
+      R"({"edit": %s})", WorkspaceEdit::unparse(Params.edit).c_str());
+  return Result;
+}
+
 llvm::Optional<TextDocumentPositionParams>
 TextDocumentPositionParams::parse(llvm::yaml::MappingNode *Params,
                                   clangd::Logger &Logger) {

Modified: clang-tools-extra/trunk/clangd/Protocol.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Protocol.h?rev=317322&r1=317321&r2=317322&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/Protocol.h (original)
+++ clang-tools-extra/trunk/clangd/Protocol.h Fri Nov  3 06:39:15 2017
@@ -141,6 +141,7 @@ struct TextEdit {
   static llvm::Optional<TextEdit> parse(llvm::yaml::MappingNode *Params,
                                         clangd::Logger &Logger);
   static std::string unparse(const TextEdit &P);
+  static std::string unparse(const std::vector<TextEdit> &TextEdits);
 };
 
 struct TextDocumentItem {
@@ -382,6 +383,46 @@ struct CodeActionParams {
                                                 clangd::Logger &Logger);
 };
 
+struct WorkspaceEdit {
+  /// Holds changes to existing resources.
+  llvm::Optional<std::map<std::string, std::vector<TextEdit>>> changes;
+
+  /// Note: "documentChanges" is not currently used because currently there is
+  /// no support for versioned edits.
+
+  static llvm::Optional<WorkspaceEdit> parse(llvm::yaml::MappingNode *Params,
+                                             clangd::Logger &Logger);
+  static std::string unparse(const WorkspaceEdit &WE);
+};
+
+/// 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
+/// handle maps to a certain llvm::Optional of some struct to contain its
+/// arguments. Different commands could reuse the same llvm::Optional as
+/// arguments but a command that needs different arguments would simply add a
+/// new llvm::Optional and not use any other ones. In practice this means only
+/// one argument type will be parsed and set.
+struct ExecuteCommandParams {
+  // Command to apply fix-its. Uses WorkspaceEdit as argument.
+  const static std::string CLANGD_APPLY_FIX_COMMAND;
+
+  /// The command identifier, e.g. CLANGD_APPLY_FIX_COMMAND
+  std::string command;
+
+  // Arguments
+
+  llvm::Optional<WorkspaceEdit> workspaceEdit;
+
+  static llvm::Optional<ExecuteCommandParams>
+  parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger);
+};
+
+struct ApplyWorkspaceEditParams {
+  WorkspaceEdit edit;
+  static std::string unparse(const ApplyWorkspaceEditParams &Params);
+};
+
 struct TextDocumentPositionParams {
   /// The text document.
   TextDocumentIdentifier textDocument;

Modified: clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp?rev=317322&r1=317321&r2=317322&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp (original)
+++ clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp Fri Nov  3 06:39:15 2017
@@ -72,4 +72,5 @@ void clangd::registerCallbackHandlers(JS
   Register("textDocument/switchSourceHeader",
            &ProtocolCallbacks::onSwitchSourceHeader);
   Register("workspace/didChangeWatchedFiles", &ProtocolCallbacks::onFileEvent);
+  Register("workspace/executeCommand", &ProtocolCallbacks::onCommand);
 }

Modified: clang-tools-extra/trunk/clangd/ProtocolHandlers.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ProtocolHandlers.h?rev=317322&r1=317321&r2=317322&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ProtocolHandlers.h (original)
+++ clang-tools-extra/trunk/clangd/ProtocolHandlers.h Fri Nov  3 06:39:15 2017
@@ -52,6 +52,7 @@ public:
   virtual void onGoToDefinition(Ctx C, TextDocumentPositionParams &Params) = 0;
   virtual void onSwitchSourceHeader(Ctx C, TextDocumentIdentifier &Params) = 0;
   virtual void onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) = 0;
+  virtual void onCommand(Ctx C, ExecuteCommandParams &Params) = 0;
 };
 
 void registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out,

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=317322&r1=317321&r2=317322&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 Fri Nov  3 06:39:15 2017
@@ -40,27 +40,7 @@ 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;
-
-        // FIXME: vscode expects that uri will be percent encoded
-        if (textEditor && textEditor.document.uri.toString(true) === uri) {
-            textEditor.edit(mutator => {
-                for (const edit of edits) {
-                    mutator.replace(clangdClient.protocol2CodeConverter.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, vscode.commands.registerCommand('clangd.applyFix', applyTextEdits));
 }

Added: clang-tools-extra/trunk/test/clangd/execute-command.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/execute-command.test?rev=317322&view=auto
==============================================================================
--- clang-tools-extra/trunk/test/clangd/execute-command.test (added)
+++ clang-tools-extra/trunk/test/clangd/execute-command.test Fri Nov  3 06:39:15 2017
@@ -0,0 +1,67 @@
+# 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: 72
+
+{"jsonrpc":"2.0","id":3,"method":"workspace/executeCommand","params":{}}
+# No command name
+Content-Length: 85
+
+{"jsonrpc":"2.0","id":4,"method":"workspace/executeCommand","params":{"command": {}}}
+# Invalid, non-scalar command name
+Content-Length: 345
+
+{"jsonrpc":"2.0","id":5,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","custom":"foo", "arguments":[{"changes":{"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":")"}]}}]}}
+Content-Length: 117
+
+{"jsonrpc":"2.0","id":6,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":"foo"}}
+# Arguments not a sequence.
+Content-Length: 93
+
+{"jsonrpc":"2.0","id":7,"method":"workspace/executeCommand","params":{"command":"mycommand"}}
+# Unknown command.
+Content-Length: 132
+
+{"jsonrpc":"2.0","id":8,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","custom":"foo", "arguments":[""]}}
+# ApplyFix argument not a mapping node.
+Content-Length: 345
+
+{"jsonrpc":"2.0","id":9,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"custom":"foo", "changes":{"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":")"}]}}]}}
+# Custom field in WorkspaceEdit
+Content-Length: 132
+
+{"jsonrpc":"2.0","id":10,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":"foo"}]}}
+# changes in WorkspaceEdit with no mapping node
+Content-Length: 346
+
+{"jsonrpc":"2.0","id":11,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":{"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":")"}], "custom":"foo"}}]}}
+# Custom field in WorkspaceEditChange
+Content-Length: 150
+
+{"jsonrpc":"2.0","id":12,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":{"file:///foo.c":"bar"}}]}}
+# No sequence node for TextEdits
+Content-Length: 149
+
+{"jsonrpc":"2.0","id":13,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":{"file:///foo.c":[""]}}]}}
+# No mapping node for TextEdit
+Content-Length: 265
+
+{"jsonrpc":"2.0","id":14,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":{"file:///foo.c":[{"range":{"start":{"line":0,"character":32},"end":{"line":0,"character":32}},"newText":"("},{"range":"","newText":")"}]}}]}}
+# TextEdit not decoded
+Content-Length: 345
+
+{"jsonrpc":"2.0","id":9,"method":"workspace/executeCommand","params":{"arguments":[{"custom":"foo", "changes":{"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":")"}]}}],"command":"clangd.applyFix"}}
+# Command name after arguments
+Content-Length: 44
+
+{"jsonrpc":"2.0","id":3,"method":"shutdown"}

Modified: clang-tools-extra/trunk/test/clangd/fixits.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/fixits.test?rev=317322&r1=317321&r2=317322&view=diff
==============================================================================
--- clang-tools-extra/trunk/test/clangd/fixits.test (original)
+++ clang-tools-extra/trunk/test/clangd/fixits.test Fri Nov  3 06:39:15 2017
@@ -13,16 +13,21 @@ Content-Length: 180
 #
 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"}]}}}
+{"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": "=="}]]}]
+# CHECK: {"jsonrpc":"2.0","id":2,"result":[{"title":"Apply FixIt 'place parentheses around the assignment to silence this warning'", "command": "clangd.applyFix", "arguments": [{"changes": {"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": [{"changes": {"file:///foo.c": [{"range": {"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}}, "newText": "=="}]}}]}]}
 #
 Content-Length: 771
 
 {"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,"code":"1","source":"foo","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"}]}}}
 # Make sure unused "code" and "source" fields ignored gracefully
-# 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": "=="}]]}]
+# CHECK: {"jsonrpc":"2.0","id":2,"result":[{"title":"Apply FixIt 'place parentheses around the assignment to silence this warning'", "command": "clangd.applyFix", "arguments": [{"changes": {"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": [{"changes": {"file:///foo.c": [{"range": {"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}}, "newText": "=="}]}}]}]}
 #
+Content-Length: 329
+
+{"jsonrpc":"2.0","id":3,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":{"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":")"}]}}]}}
+# CHECK: {"jsonrpc":"2.0","id":3,"result":"Fix applied."}
+# CHECK: {"jsonrpc":"2.0","id":1,"method":"workspace/applyEdit","params":{"edit": {"changes": {"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": ")"}]}}}}
 Content-Length: 44
 
 {"jsonrpc":"2.0","id":3,"method":"shutdown"}

Modified: clang-tools-extra/trunk/test/clangd/initialize-params-invalid.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/initialize-params-invalid.test?rev=317322&r1=317321&r2=317322&view=diff
==============================================================================
--- clang-tools-extra/trunk/test/clangd/initialize-params-invalid.test (original)
+++ clang-tools-extra/trunk/test/clangd/initialize-params-invalid.test Fri Nov  3 06:39:15 2017
@@ -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: 535
+# CHECK: Content-Length: 606
 # CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{
 # CHECK:   "textDocumentSync": 1,
 # CHECK:   "documentFormattingProvider": true,
@@ -14,7 +14,8 @@ Content-Length: 142
 # CHECK:   "codeActionProvider": true,
 # CHECK:   "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
 # CHECK:   "signatureHelpProvider": {"triggerCharacters": ["(",","]},
-# CHECK:   "definitionProvider": true
+# CHECK:   "definitionProvider": true,
+# CHECK:   "executeCommandProvider": {"commands": ["clangd.applyFix"]}
 # CHECK: }}}
 #
 Content-Length: 44

Modified: clang-tools-extra/trunk/test/clangd/initialize-params.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/initialize-params.test?rev=317322&r1=317321&r2=317322&view=diff
==============================================================================
--- clang-tools-extra/trunk/test/clangd/initialize-params.test (original)
+++ clang-tools-extra/trunk/test/clangd/initialize-params.test Fri Nov  3 06:39:15 2017
@@ -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: 535
+# CHECK: Content-Length: 606
 # CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{
 # CHECK:   "textDocumentSync": 1,
 # CHECK:   "documentFormattingProvider": true,
@@ -14,7 +14,8 @@ Content-Length: 143
 # CHECK:   "codeActionProvider": true,
 # CHECK:   "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
 # CHECK:   "signatureHelpProvider": {"triggerCharacters": ["(",","]},
-# CHECK:   "definitionProvider": true
+# CHECK:   "definitionProvider": true,
+# CHECK:   "executeCommandProvider": {"commands": ["clangd.applyFix"]}
 # CHECK: }}}
 #
 Content-Length: 44




More information about the cfe-commits mailing list