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

Issue:
Code actions for ranges
## Is your feature request related to a problem? Please describe.

The code action request supports a range, however currently as far as I can tell LanguageClient-neovim can only send code action requests where the range is set to the current cursor position, and not where the range is set according to the whole current selection.

## Describe the solution you'd like

Code action sends selection range properly.

## Additional context

An example range code action is this introduce variable from rust-analyzer: https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/user/assists.md#introduce_variable



Discussion / hints:

Fix patch:
diff --git a/autoload/LSP.vim b/autoload/LSP.vim
index 1c02cb56..9cdadb22 100644
--- a/autoload/LSP.vim
+++ b/autoload/LSP.vim
@@ -52,8 +52,46 @@ function! LSP#viewport() abort
 endfunction
 
 function! LSP#position() abort
+  return s:get_position()
+endfunction
+
+function! LSP#range(mode) abort
+  if a:mode ==# 'v'
+    return s:get_visual_selection()
+  endif
+
+  return {
+    \ 'start': s:get_position(),
+    \ 'end': s:get_position(),
+    \ }
+endfunction
+
+function! s:get_position() abort
 	return {
 		\ 'line': LSP#line(),
 		\ 'character': LSP#character(),
 		\ }
 endfunction
+
+function! s:get_visual_selection() abort
+    let [line_start, column_start] = getpos("'<")[1:2]
+    let [line_end, column_end] = getpos("'>")[1:2]
+    let lines = getline(line_start, line_end)
+    if len(lines) == 0
+        echohl Error | echom '[LC] No lines found in range.' | echohl None
+        return ''
+    endif
+    let lines[-1] = lines[-1][: column_end - 2]
+    let lines[0] = lines[0][column_start - 1:]
+
+    return {
+      \ 'start': {
+        \ 'line': line_start - 1,
+        \ 'character': column_start - 1,
+        \ },
+      \ 'end': {
+        \ 'line': line_end - 1,
+        \ 'character': column_end - 1,
+        \ }
+      \ }
+endfunction
diff --git a/autoload/LanguageClient.vim b/autoload/LanguageClient.vim
index 21d40fe5..20fa6b41 100644
--- a/autoload/LanguageClient.vim
+++ b/autoload/LanguageClient.vim
@@ -861,18 +861,27 @@ function! LanguageClient#textDocument_codeLens(...) abort
     return LanguageClient#Call('textDocument/codeLens', l:params, l:Callback)
 endfunction
 
-function! LanguageClient#textDocument_codeAction(...) abort
-    let l:Callback = get(a:000, 1, v:null)
+function! s:do_codeAction(mode, ...) abort
+    let l:Callback = get(a:000, 2, v:null)
     let l:params = {
                 \ 'filename': LSP#filename(),
                 \ 'line': LSP#line(),
                 \ 'character': LSP#character(),
                 \ 'handle': s:IsFalse(l:Callback),
+                \ 'range': LSP#range(a:mode),
                 \ }
-    call extend(l:params, get(a:000, 0, {}))
+    call extend(l:params, get(a:000, 1, {}))
     return LanguageClient#Call('textDocument/codeAction', l:params, l:Callback)
 endfunction
 
+function! LanguageClient#textDocument_visualCodeAction(...) abort
+  call s:do_codeAction('v', a:000)
+endfunction
+
+function! LanguageClient#textDocument_codeAction(...) abort
+  call s:do_codeAction('n', a:000)
+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 367b50da..3693d7cf 100644
--- a/doc/LanguageClient.txt
+++ b/doc/LanguageClient.txt
@@ -678,6 +678,12 @@ prompt, selecting one of the entry will then goto the reference location.
 
 For Denite users, a source with name 'references' is provided.
 
+*LanguageClient#textDocument_visualCodeAction()*
+*LanguageClient_textDocument_visualCodeAction()*
+Signature: LanguageClient#textDocument_visualCodeAction(...)
+
+Show code actions at current visual selection.
+
 *LanguageClient#textDocument_codeAction()*
 *LanguageClient_textDocument_codeAction()*
 Signature: LanguageClient#textDocument_codeAction(...)
diff --git a/plugin/LanguageClient.vim b/plugin/LanguageClient.vim
index 54cc1db9..724f80ad 100644
--- a/plugin/LanguageClient.vim
+++ b/plugin/LanguageClient.vim
@@ -35,7 +35,7 @@ function! LanguageClient_textDocument_references(...)
 endfunction
 
 function! LanguageClient_textDocument_codeAction(...)
-    return call('LanguageClient#textDocument_codeAction', a:000)
+    return call('LanguageClient#textDocument_codeAction', 'n', a:000)
 endfunction
 
 function! LanguageClient_textDocument_codeLens(...)
diff --git a/src/language_server_protocol.rs b/src/language_server_protocol.rs
index fe3318ae..3908b512 100644
--- a/src/language_server_protocol.rs
+++ b/src/language_server_protocol.rs
@@ -1551,7 +1551,7 @@ impl LanguageClient {
         info!("Begin {}", lsp::request::CodeActionRequest::METHOD);
         let filename = self.vim()?.get_filename(params)?;
         let languageId = self.vim()?.get_languageId(&filename, params)?;
-        let position = self.vim()?.get_position(params)?;
+        let range: Range = serde_json::from_value(params["range"].clone())?;
 
         // Unify filename.
         let filename = filename.canonicalize();
@@ -1562,7 +1562,7 @@ impl LanguageClient {
                 .get(&filename)
                 .unwrap_or(&vec![])
                 .iter()
-                .filter(|dn| position >= dn.range.start && position < dn.range.end)
+                .filter(|dn| range.start >= dn.range.start && range.start < dn.range.end)
                 .cloned()
                 .collect()
         })?;
@@ -1573,10 +1573,7 @@ impl LanguageClient {
                 text_document: TextDocumentIdentifier {
                     uri: filename.to_url()?,
                 },
-                range: Range {
-                    start: position,
-                    end: position,
-                },
+                range,
                 context: CodeActionContext {
                     diagnostics,
                     only: None,


Test patch: