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

Issue:
Switch between source/header
## Is your feature request related to a problem? Please describe.

In C++ often need to switch between header and source file. Clang implements [Switch between source/header extension](https://clangd.llvm.org/extensions.html#switch-between-sourceheader).

## Describe the solution you'd like

I suggest to add a support for this extension as a separate plugin.

## Additional context

For example, asycomplete.vim have the [following plugin](https://github.com/micchy326/vim-lsp-clangd-switch).


Discussion / hints:
I opened #1072 a few days ago where I propose (as an alternative to that PR) to create a set of extensions for LCN and make it so that it's easier to extend functionality specific to some language server.
I would argue that it's better to wait for a reply on the extension topic on the PR I merged, as to not introduce any breaking changes if we were to move to an extension paradigm later on. But if that doesn't happen, we can always bake those clang specific requests into the client.

Fix patch:
diff --git a/autoload/LanguageClient.vim b/autoload/LanguageClient.vim
index 22ec7205f..0ad4de34e 100644
--- a/autoload/LanguageClient.vim
+++ b/autoload/LanguageClient.vim
@@ -808,6 +808,15 @@ function! LanguageClient#findLocations(...) abort
     return LanguageClient#Call('languageClient/findLocations', l:params, l:Callback)
 endfunction
 
+function! LanguageClient#textDocument_switchSourceHeader(...) abort
+    let l:Callback = get(a:000, 1, v:null)
+    let l:params = {
+                \ 'filename': LSP#filename(),
+                \ }
+    call extend(l:params, get(a:000, 0, {}))
+    return LanguageClient#Call('textDocument/switchSourceHeader', l:params, l:Callback)
+endfunction
+
 function! LanguageClient#textDocument_definition(...) abort
     let l:params = {
                 \ 'method': 'textDocument/definition',
diff --git a/doc/LanguageClient.txt b/doc/LanguageClient.txt
index 97ff70786..59d15ff5a 100644
--- a/doc/LanguageClient.txt
+++ b/doc/LanguageClient.txt
@@ -974,6 +974,10 @@ Signature: LanguageClient#diagnosticsPrevious()
 
 Moves the cursor to the previous diagnostic in the buffer, relative to the current cursor position.
 
+*LanguageClient#textDocument_switchSourceHeader*
+Signature: LanguageClient#textDocument_switchSourceHeader(...)
+
+Calls clangd's `textDocument/switchSourceHeader` extension request.
 
 ==============================================================================
 5. Mappings                                           *LanguageClientMappings*
diff --git a/plugin/LanguageClient.vim b/plugin/LanguageClient.vim
index 89b0ed0ba..9095f6c09 100644
--- a/plugin/LanguageClient.vim
+++ b/plugin/LanguageClient.vim
@@ -146,6 +146,10 @@ function! LanguageClient_explainErrorAtPoint(...)
     return call('LanguageClient#explainErrorAtPoint', a:000)
 endfunction
 
+function! LanguageClient_textDocument_switchSourceHeader(...)
+    return call('LanguageClient#textDocument_switchSourceHeader', a:000)
+endfunction
+
 command! -nargs=* LanguageClientStart :call LanguageClient#startServer(<f-args>)
 command! LanguageClientStop :call LanguageClient#exit()
 
diff --git a/src/extensions/clangd.rs b/src/extensions/clangd.rs
new file mode 100644
index 000000000..c80ecead8
--- /dev/null
+++ b/src/extensions/clangd.rs
@@ -0,0 +1,27 @@
+use crate::{language_client::LanguageClient, utils::ToUrl};
+use anyhow::Result;
+use jsonrpc_core::Value;
+use lsp_types::TextDocumentIdentifier;
+
+pub mod request {
+    pub const SWITCH_SOURCE_HEADER: &str = "textDocument/switchSourceHeader";
+}
+
+impl LanguageClient {
+    pub fn text_document_switch_source_header(&self, params: &Value) -> Result<Value> {
+        let filename = self.vim()?.get_filename(params)?;
+        let language_id = self.vim()?.get_language_id(&filename, &Value::Null)?;
+        let params = TextDocumentIdentifier {
+            uri: filename.to_url()?,
+        };
+
+        let response: String = self
+            .get_client(&Some(language_id))?
+            .call(request::SWITCH_SOURCE_HEADER, params)?;
+
+        let path = std::path::Path::new(&response);
+        self.vim()?.edit(&None, path)?;
+
+        Ok(Value::Null)
+    }
+}
diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs
index 716cf880a..a5a9f9803 100644
--- a/src/extensions/mod.rs
+++ b/src/extensions/mod.rs
@@ -1,3 +1,4 @@
+pub mod clangd;
 pub mod gopls;
 pub mod java;
 pub mod rust_analyzer;
diff --git a/src/rpchandler.rs b/src/rpchandler.rs
index f5d58a728..9ebe1bd7f 100644
--- a/src/rpchandler.rs
+++ b/src/rpchandler.rs
@@ -1,3 +1,4 @@
+use crate::extensions::clangd;
 use crate::{language_client::LanguageClient, language_server_protocol::Direction, types::*};
 use anyhow::{anyhow, Result};
 use log::*;
@@ -118,6 +119,10 @@ impl LanguageClient {
             REQUEST_SEMANTIC_SCOPES => self.semantic_scopes(&params),
             REQUEST_SHOW_SEMANTIC_HL_SYMBOLS => self.semantic_highlight_symbols(&params),
 
+            clangd::request::SWITCH_SOURCE_HEADER => {
+                self.text_document_switch_source_header(&params)
+            }
+
             _ => {
                 let language_id_target = if language_id.is_some() {
                     // Message from language server. No handler found.


Test patch: