Repository: rust-analyzer/rust-analyzer
Pull request #4167 (2020-04-26 23:05:24)

Filter out code actions if unsupported by the client and advertise our capabilities
This PR does three things:
1. If the client does not support `CodeActionKind` this will filter the results and only send `Command[]` back.
2. Correctly advertises to the client that the server supports `CodeActionKind`. This may cause clients to not request code actions if they are checking for the provider to be `true` (or implement LSP < 3.8) in the caps but I will fix that in a followup PR.
3. Marks most CodeActions as <strike>"refactor" so that they show up in the menu in vscode.</strike>`""`.

Part of #144
#4147 
#2833  


Patch:
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs
index e22ab8402e..c0d3209262 100644
--- a/crates/rust-analyzer/src/caps.rs
+++ b/crates/rust-analyzer/src/caps.rs
@@ -3,13 +3,13 @@
 use crate::semantic_tokens;
 
 use lsp_types::{
-    CallHierarchyServerCapability, CodeActionProviderCapability, CodeLensOptions,
-    CompletionOptions, DocumentOnTypeFormattingOptions, FoldingRangeProviderCapability,
-    ImplementationProviderCapability, RenameOptions, RenameProviderCapability, SaveOptions,
-    SelectionRangeProviderCapability, SemanticTokensDocumentProvider, SemanticTokensLegend,
-    SemanticTokensOptions, ServerCapabilities, SignatureHelpOptions, TextDocumentSyncCapability,
-    TextDocumentSyncKind, TextDocumentSyncOptions, TypeDefinitionProviderCapability,
-    WorkDoneProgressOptions,
+    CallHierarchyServerCapability, CodeActionOptions, CodeActionProviderCapability,
+    CodeLensOptions, CompletionOptions, DocumentOnTypeFormattingOptions,
+    FoldingRangeProviderCapability, ImplementationProviderCapability, RenameOptions,
+    RenameProviderCapability, SaveOptions, SelectionRangeProviderCapability,
+    SemanticTokensDocumentProvider, SemanticTokensLegend, SemanticTokensOptions,
+    ServerCapabilities, SignatureHelpOptions, TextDocumentSyncCapability, TextDocumentSyncKind,
+    TextDocumentSyncOptions, TypeDefinitionProviderCapability, WorkDoneProgressOptions,
 };
 
 pub fn server_capabilities() -> ServerCapabilities {
@@ -40,7 +40,20 @@ pub fn server_capabilities() -> ServerCapabilities {
         document_highlight_provider: Some(true),
         document_symbol_provider: Some(true),
         workspace_symbol_provider: Some(true),
-        code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
+        code_action_provider: Some(CodeActionProviderCapability::Options(CodeActionOptions {
+            // Advertise support for all built-in CodeActionKinds
+            code_action_kinds: Some(vec![
+                String::new(),
+                lsp_types::code_action_kind::QUICKFIX.to_string(),
+                lsp_types::code_action_kind::REFACTOR.to_string(),
+                lsp_types::code_action_kind::REFACTOR_EXTRACT.to_string(),
+                lsp_types::code_action_kind::REFACTOR_INLINE.to_string(),
+                lsp_types::code_action_kind::REFACTOR_REWRITE.to_string(),
+                lsp_types::code_action_kind::SOURCE.to_string(),
+                lsp_types::code_action_kind::SOURCE_ORGANIZE_IMPORTS.to_string(),
+            ]),
+            work_done_progress_options: Default::default(),
+        })),
         code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
         document_formatting_provider: Some(true),
         document_range_formatting_provider: None,
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 74a63e32a3..177da94cc9 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -70,6 +70,7 @@ pub struct ClientCapsConfig {
     pub location_link: bool,
     pub line_folding_only: bool,
     pub hierarchical_symbols: bool,
+    pub code_action_literals: bool,
 }
 
 impl Default for Config {
@@ -221,6 +222,11 @@ impl Config {
         {
             self.client_caps.hierarchical_symbols = value
         }
+        if let Some(value) =
+            caps.code_action.as_ref().and_then(|it| Some(it.code_action_literal_support.is_some()))
+        {
+            self.client_caps.code_action_literals = value;
+        }
         self.completion.allow_snippets(false);
         if let Some(completion) = &caps.completion {
             if let Some(completion_item) = &completion.completion_item {
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index 8db2dfa0c8..0f623949ec 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -19,8 +19,7 @@ use lsp_types::{
     TextEdit, Url, WorkspaceEdit,
 };
 use ra_ide::{
-    Assist, AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind,
-    SearchScope,
+    Assist, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, SearchScope,
 };
 use ra_prof::profile;
 use ra_syntax::{AstNode, SyntaxKind, TextRange, TextSize};
@@ -702,15 +701,9 @@ fn create_single_code_action(assist: Assist, world: &WorldSnapshot) -> Result<Co
         arguments: Some(vec![arg]),
     };
 
-    let kind = match assist.id {
-        AssistId("introduce_variable") => Some("refactor.extract.variable".to_string()),
-        AssistId("add_custom_impl") => Some("refactor.rewrite.add_custom_impl".to_string()),
-        _ => None,
-    };
-
     Ok(CodeAction {
         title,
-        kind,
+        kind: Some(String::new()),
         diagnostics: None,
         edit: None,
         command: Some(command),
@@ -812,6 +805,23 @@ pub fn handle_code_action(
         }
     }
 
+    // If the client only supports commands then filter the list
+    // and remove and actions that depend on edits.
+    if !world.config.client_caps.code_action_literals {
+        // FIXME: use drain_filter once it hits stable.
+        res = res
+            .into_iter()
+            .filter_map(|it| match it {
+                cmd @ lsp_types::CodeActionOrCommand::Command(_) => Some(cmd),
+                lsp_types::CodeActionOrCommand::CodeAction(action) => match action.command {
+                    Some(cmd) if action.edit.is_none() => {
+                        Some(lsp_types::CodeActionOrCommand::Command(cmd))
+                    }
+                    _ => None,
+                },
+            })
+            .collect();
+    }
     Ok(Some(res))
 }
 


Test patch:
diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs
index e4fe3411aa..8d47ee4f64 100644
--- a/crates/rust-analyzer/tests/heavy_tests/support.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/support.rs
@@ -77,7 +77,11 @@ impl<'a> Project<'a> {
         let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect();
 
         let mut config = Config {
-            client_caps: ClientCapsConfig { location_link: true, ..Default::default() },
+            client_caps: ClientCapsConfig {
+                location_link: true,
+                code_action_literals: true,
+                ..Default::default()
+            },
             with_sysroot: self.with_sysroot,
             ..Config::default()
         };