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

Issue:
Client is unable to parse hierachy of symbols returned by some language servers
- Did you upgrade to latest plugin version?
*yes*
- Did you upgrade to/compile latest binary? Run shell command
  `bin/languageclient --version` to get its version number.
*languageclient 0.1.154 b4de07929d6c03b49595902d44e9f8b29e0882b*
- (Neovim users only) Did you check output of `:checkhealth LanguageClient`?
 *yes*
- Did you check [troubleshooting]?
 *yes*

[troubleshooting]: https://github.com/autozimu/LanguageClient-neovim/blob/next/INSTALL.md#6-troubleshooting

## Describe the bug

This plugin doesn't implement the Language Server Protocol 3.14 spec version. Some language servers (like rust-analyzer) return a hierarchy of symbols as a response to the `documentSymbol` request, and this client is unable to parse it.

## Environment
- Language server link and version: https://github.com/rust-analyzer/rust-analyzer/commit/e6cb06d2850b9b9c38c0c13d6948ab39adcd652e

[min-vimrc.vim]: https://github.com/autozimu/LanguageClient-neovim/blob/next/min-vimrc.vim

## To Reproduce
1. Use latest client with latest rust-analyzer server.
2. send `documentSymbol` request.
3. Observe `[LC]: missing field: 'location'` message in `neovim` statusline.

## Current behavior
The client can't parse the updated `documentSymbol` response of some (spec-compliant) language servers.

## Expected behavior
It should be able to parse the response.

## Screenshots
n.a.

## Additional context
I'll have a fix ready reasonably soon.


Discussion / hints:

Fix patch:
diff --git a/src/language_server_protocol.rs b/src/language_server_protocol.rs
index cb137265e..c9c703707 100644
--- a/src/language_server_protocol.rs
+++ b/src/language_server_protocol.rs
@@ -1223,34 +1223,87 @@ impl LanguageClient {
             return Ok(result);
         }
 
-        let symbols: Vec<SymbolInformation> = serde_json::from_value(result.clone())?;
+        let syms: <lsp::request::DocumentSymbolRequest as lsp::request::Request>::Result =
+            serde_json::from_value(result.clone())?;
+
         let title = format!("[LC]: symbols for {}", filename);
 
         let selectionUI = self.get(|state| state.selectionUI)?;
         let selectionUI_autoOpen = self.get(|state| state.selectionUI_autoOpen)?;
         match selectionUI {
             SelectionUI::FZF => {
-                let source: Vec<_> = symbols
-                    .iter()
-                    .map(|sym| {
-                        let start = sym.location.range.start;
-                        format!(
-                            "{}:{}:\t{}\t\t{:?}",
-                            start.line + 1,
-                            start.character + 1,
-                            sym.name,
-                            sym.kind
-                        )
-                    })
-                    .collect();
+                let symbols = match syms {
+                    Some(lsp::DocumentSymbolResponse::Flat(flat)) => flat
+                        .iter()
+                        .map(|sym| {
+                            let start = sym.location.range.start;
+                            format!(
+                                "{}:{}:\t{}\t\t{:?}",
+                                start.line + 1,
+                                start.character + 1,
+                                sym.name,
+                                sym.kind
+                            )
+                        })
+                        .collect(),
+                    Some(lsp::DocumentSymbolResponse::Nested(nested)) => {
+                        let mut symbols = Vec::new();
+
+                        fn walk_document_symbol(
+                            buffer: &mut Vec<String>,
+                            parent: Option<&str>,
+                            ds: &lsp::DocumentSymbol,
+                        ) {
+                            let start = ds.selection_range.start;
+
+                            let name = if let Some(parent) = parent {
+                                format!("{}::{}", parent, ds.name)
+                            } else {
+                                ds.name.clone()
+                            };
+
+                            let n = format!(
+                                "{}:{}:\t{}\t\t{:?}",
+                                start.line + 1,
+                                start.character + 1,
+                                name,
+                                ds.kind
+                            );
+
+                            buffer.push(n);
+
+                            if let Some(children) = &ds.children {
+                                for child in children {
+                                    walk_document_symbol(buffer, Some(&ds.name), child);
+                                }
+                            }
+                        }
+
+                        for ds in &nested {
+                            walk_document_symbol(&mut symbols, None, ds);
+                        }
+
+                        symbols
+                    }
+                    _ => Vec::new(),
+                };
 
                 self.vim()?.rpcclient.notify(
                     "s:FZF",
-                    json!([source, format!("s:{}", NOTIFICATION__FZFSinkLocation)]),
+                    json!([symbols, format!("s:{}", NOTIFICATION__FZFSinkLocation)]),
                 )?;
             }
             SelectionUI::Quickfix => {
-                let list: Fallible<Vec<_>> = symbols.iter().map(QuickfixEntry::from_lsp).collect();
+                let list = match syms {
+                    Some(lsp::DocumentSymbolResponse::Flat(flat)) => {
+                        flat.iter().map(QuickfixEntry::from_lsp).collect()
+                    }
+                    Some(lsp::DocumentSymbolResponse::Nested(nested)) => {
+                        <(Vec<QuickfixEntry>)>::from_lsp(&nested)
+                    }
+                    _ => Ok(Vec::new()),
+                };
+
                 let list = list?;
                 self.vim()?.setqflist(&list, " ", &title)?;
                 if selectionUI_autoOpen {
@@ -1260,7 +1313,16 @@ impl LanguageClient {
                     .echo("Document symbols populated to quickfix list.")?;
             }
             SelectionUI::LocationList => {
-                let list: Fallible<Vec<_>> = symbols.iter().map(QuickfixEntry::from_lsp).collect();
+                let list = match syms {
+                    Some(lsp::DocumentSymbolResponse::Flat(flat)) => {
+                        flat.iter().map(QuickfixEntry::from_lsp).collect()
+                    }
+                    Some(lsp::DocumentSymbolResponse::Nested(nested)) => {
+                        <(Vec<QuickfixEntry>)>::from_lsp(&nested)
+                    }
+                    _ => Ok(Vec::new()),
+                };
+
                 let list = list?;
                 self.vim()?.setloclist(&list, " ", &title)?;
                 if selectionUI_autoOpen {
diff --git a/src/types.rs b/src/types.rs
index 14891f20b..81bb4bbf6 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -1045,6 +1045,53 @@ impl FromLSP<SymbolInformation> for QuickfixEntry {
     }
 }
 
+impl FromLSP<Vec<lsp::SymbolInformation>> for Vec<QuickfixEntry> {
+    fn from_lsp(symbols: &Vec<lsp::SymbolInformation>) -> Fallible<Self> {
+        symbols.iter().map(QuickfixEntry::from_lsp).collect()
+    }
+}
+
+impl FromLSP<Vec<lsp::DocumentSymbol>> for Vec<QuickfixEntry> {
+    fn from_lsp(document_symbols: &Vec<lsp::DocumentSymbol>) -> Fallible<Self> {
+        let mut symbols = Vec::new();
+
+        fn walk_document_symbol(
+            buffer: &mut Vec<QuickfixEntry>,
+            parent: Option<&str>,
+            ds: &lsp::DocumentSymbol,
+        ) {
+            let start = ds.selection_range.start;
+
+            let name = if let Some(parent) = parent {
+                format!("{}::{}", parent, ds.name)
+            } else {
+                ds.name.clone()
+            };
+
+            buffer.push(QuickfixEntry {
+                filename: "".to_string(),
+                lnum: start.line + 1,
+                col: Some(start.character + 1),
+                text: Some(name),
+                nr: None,
+                typ: None,
+            });
+
+            if let Some(children) = &ds.children {
+                for child in children {
+                    walk_document_symbol(buffer, Some(&ds.name), child);
+                }
+            }
+        }
+
+        for ds in document_symbols {
+            walk_document_symbol(&mut symbols, None, ds);
+        }
+
+        Ok(symbols)
+    }
+}
+
 #[derive(Debug, Serialize, Deserialize)]
 #[serde(untagged)]
 pub enum RawMessage {


Test patch: