Repository: autozimu/LanguageClient-neovim (language: rust)

Issue:
Running code action automatically when there is only one
When I run code action I get an FZF window of choices. In the case where there is only one, is there a way to execute it immediately? Adding an extra `<CR>` in the binding doesn't work.


Discussion / hints:
There's nothing there yet, but I commented on this a few days ago here https://github.com/autozimu/LanguageClient-neovim/issues/854#issuecomment-732363961.

I'll try and see if I can find a nice way to enable this, although it's likely going to be a new function or a modification to one existing function instead of it just selecting it when there's only one. I oppose to the idea of calling the action when there's only one for two reasons: the first reason is that as a user I would like to know what exactly was ran, the second reason is that adding a function that you can call from mappings enables you the call specific actions even without explicitly making the initial code actions call, and I guess that's desirable.
Sure, being able to explicitly call a specific code action would also work and be better.

Fix patch:
diff --git a/autoload/LanguageClient.vim b/autoload/LanguageClient.vim
index 3ec1d6f26..1f0d8e8d3 100644
--- a/autoload/LanguageClient.vim
+++ b/autoload/LanguageClient.vim
@@ -1049,6 +1049,20 @@ function! LanguageClient#textDocument_codeAction(...) abort
   call s:do_codeAction('n', a:000)
 endfunction
 
+function! LanguageClient#executeCodeAction(kind, ...) abort
+  let l:Callback = get(a:000, 1, v:null)
+  let l:params = {
+              \ 'filename': LSP#filename(),
+              \ 'line': LSP#line(),
+              \ 'character': LSP#character(),
+              \ 'handle': s:IsFalse(l:Callback),
+              \ 'range': LSP#range('n'),
+              \ 'kind': a:kind,
+              \ }
+  call extend(l:params, get(a:000, 0, {}))
+  return LanguageClient#Call('languageClient/executeCodeAction', l:params, l:Callback)
+endfunction
+
 function! LanguageClient#textDocument_completion(...) abort
     " Note: do not add 'text' as it might be huge.
     let l:params = {
diff --git a/doc/LanguageClient.txt b/doc/LanguageClient.txt
index 82128f8d3..7e50b2abc 100644
--- a/doc/LanguageClient.txt
+++ b/doc/LanguageClient.txt
@@ -1025,6 +1025,21 @@ Signature: LanguageClient#textDocument_switchSourceHeader(...)
 
 Calls clangd's `textDocument/switchSourceHeader` extension request.
 
+*LanguageClient#executeCodeAction*
+Signature: LanguageClient#executeCodeAction(kind, ...)
+
+Tries to execute the code action with the give kind under the cursor position.
+The action will only be executed if there is exactly one code action with that
+kind under the cursor.
+
+This function can be used to create commands for common LSP actions, such as
+`source.organizeImports`.  To do so you can create a command like this:
+
+  command! OrganizeImports call LanguageClient#executeCodeAction('source.organizeImports')
+
+Note that this only works for code actions, not commands, and only in normal
+mode.
+
 ==============================================================================
 5. Mappings                                           *LanguageClientMappings*
 
diff --git a/src/language_server_protocol.rs b/src/language_server_protocol.rs
index ef43a940e..3d8ed9bef 100644
--- a/src/language_server_protocol.rs
+++ b/src/language_server_protocol.rs
@@ -1239,8 +1239,7 @@ impl LanguageClient {
         Ok(result)
     }
 
-    #[tracing::instrument(level = "info", skip(self))]
-    pub fn text_document_code_action(&self, params: &Value) -> Result<Value> {
+    pub fn get_code_actions(&self, params: &Value) -> Result<Value> {
         self.text_document_did_change(params)?;
         let filename = self.vim()?.get_filename(params)?;
         let language_id = self.vim()?.get_language_id(&filename, params)?;
@@ -1276,6 +1275,40 @@ impl LanguageClient {
             },
         )?;
 
+        Ok(result)
+    }
+
+    #[tracing::instrument(level = "info", skip(self))]
+    pub fn execute_code_action(&self, params: &Value) -> Result<Value> {
+        let result = self.get_code_actions(params)?;
+        let response = <Option<CodeActionResponse>>::deserialize(&result)?;
+        let response: CodeActionResponse = response.unwrap_or_default();
+        let kind: String =
+            try_get("kind", params)?.ok_or_else(|| anyhow!("Missing kind parameter"))?;
+        let action_kind = CodeActionKind::from(kind.clone());
+        let actions: Vec<CodeActionOrCommand> = response.into_iter().filter(|a| matches!(a,
+            CodeActionOrCommand::CodeAction(action) if action.kind.is_some() && action.kind.as_ref().unwrap() == &action_kind)
+        ).collect();
+        if actions.len() > 1 {
+            return Err(anyhow!("Too many code actions found with kind {}", kind));
+        }
+        if actions.len() == 0 {
+            return Err(anyhow!("No code actions found with kind {}", kind));
+        }
+
+        match actions.first().cloned() {
+            Some(CodeActionOrCommand::CodeAction(action)) => {
+                self.handle_code_action_selection(&[action], 0)?
+            }
+            _ => return Err(anyhow!("No code actions found with kind {}", kind)),
+        }
+
+        Ok(result)
+    }
+
+    #[tracing::instrument(level = "info", skip(self))]
+    pub fn text_document_code_action(&self, params: &Value) -> Result<Value> {
+        let result = self.get_code_actions(params)?;
         let response = <Option<CodeActionResponse>>::deserialize(&result)?;
         let response = response.unwrap_or_default();
 
diff --git a/src/rpchandler.rs b/src/rpchandler.rs
index 717955e12..caace4233 100644
--- a/src/rpchandler.rs
+++ b/src/rpchandler.rs
@@ -120,6 +120,7 @@ impl LanguageClient {
             REQUEST_CODE_LENS_ACTION => self.handle_code_lens_action(&params),
             REQUEST_SEMANTIC_SCOPES => self.semantic_scopes(&params),
             REQUEST_SHOW_SEMANTIC_HL_SYMBOLS => self.semantic_highlight_symbols(&params),
+            REQUEST_EXECUTE_CODE_ACTION => self.execute_code_action(&params),
 
             clangd::request::SwitchSourceHeader::METHOD => {
                 self.text_document_switch_source_header(&params)
diff --git a/src/types.rs b/src/types.rs
index e98cba9e0..9a721034f 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -64,6 +64,7 @@ pub const REQUEST_CODE_LENS_ACTION: &str = "LanguageClient/handleCodeLensAction"
 pub const REQUEST_SEMANTIC_SCOPES: &str = "languageClient/semanticScopes";
 pub const REQUEST_SHOW_SEMANTIC_HL_SYMBOLS: &str = "languageClient/showSemanticHighlightSymbols";
 pub const REQUEST_CLASS_FILE_CONTENTS: &str = "java/classFileContents";
+pub const REQUEST_EXECUTE_CODE_ACTION: &str = "languageClient/executeCodeAction";
 
 pub const NOTIFICATION_HANDLE_BUF_NEW_FILE: &str = "languageClient/handleBufNewFile";
 pub const NOTIFICATION_HANDLE_BUF_ENTER: &str = "languageClient/handleBufEnter";


Test patch: