[clangd] Support CodeActionParams.only
authorSam McCall <sam.mccall@gmail.com>
Fri, 9 Oct 2020 13:17:26 +0000 (15:17 +0200)
committerSam McCall <sam.mccall@gmail.com>
Thu, 29 Oct 2020 08:44:08 +0000 (09:44 +0100)
Differential Revision: https://reviews.llvm.org/D89126

clang-tools-extra/clangd/ClangdLSPServer.cpp
clang-tools-extra/clangd/Protocol.cpp
clang-tools-extra/clangd/Protocol.h
clang-tools-extra/clangd/test/code-action-request.test

index 99c2465..3164b6c 100644 (file)
@@ -993,12 +993,24 @@ void ClangdLSPServer::onCodeAction(const CodeActionParams &Params,
   if (!Code)
     return Reply(llvm::make_error<LSPError>(
         "onCodeAction called for non-added file", ErrorCode::InvalidParams));
+
+  // Checks whether a particular CodeActionKind is included in the response.
+  auto KindAllowed = [Only(Params.context.only)](llvm::StringRef Kind) {
+    if (Only.empty())
+      return true;
+    return llvm::any_of(Only, [&](llvm::StringRef Base) {
+      return Kind.consume_front(Base) && (Kind.empty() || Kind.startswith("."));
+    });
+  };
+
   // We provide a code action for Fixes on the specified diagnostics.
   std::vector<CodeAction> FixIts;
-  for (const Diagnostic &D : Params.context.diagnostics) {
-    for (auto &F : getFixes(File.file(), D)) {
-      FixIts.push_back(toCodeAction(F, Params.textDocument.uri));
-      FixIts.back().diagnostics = {D};
+  if (KindAllowed(CodeAction::QUICKFIX_KIND)) {
+    for (const Diagnostic &D : Params.context.diagnostics) {
+      for (auto &F : getFixes(File.file(), D)) {
+        FixIts.push_back(toCodeAction(F, Params.textDocument.uri));
+        FixIts.back().diagnostics = {D};
+      }
     }
   }
 
@@ -1038,14 +1050,10 @@ void ClangdLSPServer::onCodeAction(const CodeActionParams &Params,
         }
         return Reply(llvm::json::Array(Commands));
       };
-
   Server->enumerateTweaks(
       File.file(), Params.range,
-      [&](const Tweak &T) {
-        if (!Opts.TweakFilter(T))
-          return false;
-        // FIXME: also consider CodeActionContext.only
-        return true;
+      [this, KindAllowed(std::move(KindAllowed))](const Tweak &T) {
+        return Opts.TweakFilter(T) && KindAllowed(T.kind());
       },
       std::move(ConsumeActions));
 }
index 0103a06..d11307d 100644 (file)
@@ -599,7 +599,10 @@ llvm::json::Value toJSON(const PublishDiagnosticsParams &PDP) {
 bool fromJSON(const llvm::json::Value &Params, CodeActionContext &R,
               llvm::json::Path P) {
   llvm::json::ObjectMapper O(Params, P);
-  return O && O.map("diagnostics", R.diagnostics);
+  if (!O || !O.map("diagnostics", R.diagnostics))
+    return false;
+  O.map("only", R.only);
+  return true;
 }
 
 llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diagnostic &D) {
index 165a4a8..f846acc 100644 (file)
@@ -863,8 +863,19 @@ struct PublishDiagnosticsParams {
 llvm::json::Value toJSON(const PublishDiagnosticsParams &);
 
 struct CodeActionContext {
-  /// An array of diagnostics.
+  /// An array of diagnostics known on the client side overlapping the range
+  /// provided to the `textDocument/codeAction` request. They are provided so
+  /// that the server knows which errors are currently presented to the user for
+  /// the given range. There is no guarantee that these accurately reflect the
+  /// error state of the resource. The primary parameter to compute code actions
+  /// is the provided range.
   std::vector<Diagnostic> diagnostics;
+
+  /// Requested kind of actions to return.
+  ///
+  /// Actions not of this kind are filtered out by the client before being
+  /// shown. So servers can omit computing them.
+  std::vector<std::string> only;
 };
 bool fromJSON(const llvm::json::Value &, CodeActionContext &, llvm::json::Path);
 
index 78e90ce..f16f779 100644 (file)
 # CHECK-NEXT:    }
 # CHECK-NEXT:  ]
 ---
+{
+  "jsonrpc": "2.0",
+  "id": 2,
+  "method": "textDocument/codeAction",
+  "params": {
+    "textDocument": { "uri": "test:///main.cpp" },
+        "range": {
+            "start": {"line": 0, "character": 0},
+            "end": {"line": 0, "character": 4}
+        },
+        "context": {
+            "diagnostics": [],
+            "only": ["quickfix"]
+        }
+    }
+}
+#      CHECK:  "id": 2,
+# CHECK-NEXT:  "jsonrpc": "2.0",
+# CHECK-NEXT:  "result": []
+---
+{
+  "jsonrpc": "2.0",
+  "id": 3,
+  "method": "textDocument/codeAction",
+  "params": {
+    "textDocument": { "uri": "test:///main.cpp" },
+        "range": {
+            "start": {"line": 0, "character": 0},
+            "end": {"line": 0, "character": 4}
+        },
+        "context": {
+            "diagnostics": [],
+            "only": ["refactor"]
+        }
+    }
+}
+#      CHECK:  "id": 3,
+# CHECK-NEXT:  "jsonrpc": "2.0",
+# CHECK-NEXT:  "result": [
+# CHECK-NEXT:    {
+---
 {"jsonrpc":"2.0","id":4,"method":"workspace/executeCommand","params":{"command":"clangd.applyTweak","arguments":[{"file":"test:///main.cpp","selection":{"end":{"character":4,"line":0},"start":{"character":0,"line":0}},"tweakID":"ExpandAutoType"}]}}
 #      CHECK:    "newText": "int",
 # CHECK-NEXT:    "range": {
 # CHECK-NEXT:      }
 # CHECK-NEXT:    }
 ---
-{"jsonrpc":"2.0","id":4,"method":"shutdown"}
+{"jsonrpc":"2.0","id":5,"method":"shutdown"}
 ---
 {"jsonrpc":"2.0","method":"exit"}
 ---