From be50c97ddda2c0101e921d2367a497e8bb72d68d Mon Sep 17 00:00:00 2001 From: Martin Asquino Date: Fri, 9 Oct 2020 17:43:16 +0100 Subject: [PATCH 1/2] Add support for diagnostics display funcref for more flexible integration --- doc/LanguageClient.txt | 45 +++++++++++++++++++++++++++++++++ src/language_server_protocol.rs | 13 ++++++++++ src/types.rs | 2 ++ 3 files changed, 60 insertions(+) diff --git a/doc/LanguageClient.txt b/doc/LanguageClient.txt index 59d15ff5a..9b8aa5896 100644 --- a/doc/LanguageClient.txt +++ b/doc/LanguageClient.txt @@ -635,6 +635,51 @@ Highlight group to be used for code lens. Default: 'Comment' +2.42 g:LanguageClient_diagnosticsDisplayFuncref *g:LanguageClient_diagnosticsDisplayFuncref* + +If set, LanguageClient-neovim will call this function instead of setting the diagnostics signs. This +is useful to delegate the display of diagnostics to other engines. The function is called with two +arguments, the first one is the file name of which the diagnostics correspond to, and the seconds one +is the list of diagnostics for said file. Those diagnostics are as specified in the LSP specification. + +For example, if you wanted to use `dense-analysis/ale` to display diagnostics instead of this plugin +you could use something like this: + +``` +function! g:DisplayDiagnostics(filename, diagnostics) abort + let s:diagnostics = [] + + for d in a:diagnostics + let s:severity = 'I' + if d.severity == 1 + let s:severity = 'E' + elseif d.severity == 2 + let s:severity = 'W' + endif + + call add(s:diagnostics, { + \ "filename": a:filename, + \ "text": d.message, + \ "lnum": d.range.start.line + 1, + \ "end_lnum": d.range.end.line + 1, + \ "col": d.range.end.character, + \ "end_col": d.range.end.character, + \ "type": s:severity, + \ }) + endfor + + call ale#other_source#ShowResults(bufnr('%'), 'LanguageClientNeovim', s:diagnostics) +endfunction + +let g:LanguageClient_diagnosticsDisplayFuncref = 'g:DisplayDiagnostics' +``` + +Keep in mind that to complete the integration between `ale` and `LanguageClient-neovim` you need to +add `LanguageClientNeovim` (or the name of the linter you used in the call to ShowResults) to the list +of linters to be used in `ale`. + +Default: v:null + ============================================================================== 3. Commands *LanguageClientCommands* diff --git a/src/language_server_protocol.rs b/src/language_server_protocol.rs index c38a81006..58def10ab 100644 --- a/src/language_server_protocol.rs +++ b/src/language_server_protocol.rs @@ -165,6 +165,7 @@ impl LanguageClient { #[allow(clippy::type_complexity)] let ( + diagnostics_display_funcref, diagnostics_signs_max, diagnostics_max_severity, diagnostics_ignore_sources, @@ -180,6 +181,7 @@ impl LanguageClient { enable_extensions, code_lens_hl_group, ): ( + Option, Option, String, Vec, @@ -196,6 +198,7 @@ impl LanguageClient { String, ) = self.vim()?.eval( [ + "get(g:, 'LanguageClient_diagnosticsDisplayFuncref', v:null)", "get(g:, 'LanguageClient_diagnosticsSignsMax', v:null)", "get(g:, 'LanguageClient_diagnosticsMaxSeverity', 'Hint')", "get(g:, 'LanguageClient_diagnosticsIgnoreSources', [])", @@ -328,6 +331,8 @@ impl LanguageClient { state.preferred_markup_kind = preferred_markup_kind; state.enable_extensions = enable_extensions; state.code_lens_hl_group = code_lens_hl_group; + state.diagnostics_display_funcref = diagnostics_display_funcref; + Ok(()) })?; @@ -2470,6 +2475,14 @@ impl LanguageClient { } } + // if a diagnostics display funcref has been configured then call that function and return + if let Some(funcref) = self.get(|state| state.diagnostics_display_funcref.clone())? { + self.vim()? + .rpcclient + .notify(funcref, json!([filename, diagnostics]))?; + return Ok(()); + } + let current_filename: String = self.vim()?.get_filename(&Value::Null)?; if filename != current_filename.canonicalize() { return Ok(()); diff --git a/src/types.rs b/src/types.rs index 7b15aea89..37d6d8cc4 100644 --- a/src/types.rs +++ b/src/types.rs @@ -211,6 +211,7 @@ pub struct State { pub hide_virtual_texts_on_insert: bool, pub echo_project_root: bool, pub enable_extensions: Option>, + pub diagnostics_display_funcref: Option, pub server_stderr: Option, pub logger: Logger, @@ -296,6 +297,7 @@ impl State { preferred_markup_kind: None, enable_extensions: None, code_lens_hl_group: "Comment".into(), + diagnostics_display_funcref: None, logger, }) From 33e94d986f49423275401cb888ef4d905ffa59e5 Mon Sep 17 00:00:00 2001 From: Martin Asquino Date: Mon, 19 Oct 2020 22:23:29 +0100 Subject: [PATCH 2/2] Support funcref in LanguageClient_diagnosticsList --- autoload/LanguageClient.vim | 12 ++- doc/LanguageClient.txt | 89 ++++++++------- src/language_server_protocol.rs | 185 +++++++++++++++++++------------- src/types.rs | 25 +++-- src/vim.rs | 57 ++++++++++ 5 files changed, 233 insertions(+), 135 deletions(-) diff --git a/autoload/LanguageClient.vim b/autoload/LanguageClient.vim index 0ad4de34e..ca032c734 100644 --- a/autoload/LanguageClient.vim +++ b/autoload/LanguageClient.vim @@ -117,6 +117,14 @@ function! s:hasSnippetSupport() abort return 0 endfunction +function! s:getStringOrFuncref(name, default) abort + if type(get(g:, a:name, a:default)) is s:TYPE.funcref + return string(get(g:, a:name, a:default)) + else + return get(g:, a:name, a:default) + endif +endfunction + function! s:getSelectionUI() abort if type(get(g:, 'LanguageClient_selectionUI', v:null)) is s:TYPE.funcref return 'funcref' @@ -1236,11 +1244,11 @@ function! LanguageClient#handleVimLeavePre() abort endtry endfunction -function! s:LanguageClient_FZFSinkLocation(line) abort +function! g:LanguageClient_FZFSinkLocation(line) abort return LanguageClient#Notify('LanguageClient_FZFSinkLocation', [a:line]) endfunction -function! LanguageClient_FZFSinkCommand(selection) abort +function! g:LanguageClient_FZFSinkCommand(selection) abort return LanguageClient#Notify('LanguageClient_FZFSinkCommand', { \ 'selection': a:selection, \ }) diff --git a/doc/LanguageClient.txt b/doc/LanguageClient.txt index 9b8aa5896..24c0ad6a5 100644 --- a/doc/LanguageClient.txt +++ b/doc/LanguageClient.txt @@ -207,7 +207,49 @@ Valid options: "off" | "messages" | "verbose" List used to fill diagnostic messages. Default: "Quickfix" -Valid options: "Quickfix" | "Location" | "Disabled" +Valid options: "Quickfix" | "Location" | "Disabled" | |Funcref| + +If you use a |Funcref|, the referenced function should have two arguments +(filename, diagnostics). filename is the name of the file of which the +diagnostics correspond to, and diagnostics is the list of diagnostics for said +file. Those diagnostics are as specified in the LSP specification. + +For example, if you wanted to use `dense-analysis/ale` to display diagnostics +instead of this plugin you could use something like this: + +``` +function! DisplayDiagnostics(filename, diagnostics) abort + let s:diagnostics = [] + + for d in a:diagnostics + let s:severity = 'I' + if d.severity == 1 + let s:severity = 'E' + elseif d.severity == 2 + let s:severity = 'W' + endif + + call add(s:diagnostics, { + \ "filename": a:filename, + \ "text": d.message, + \ "lnum": d.range.start.line + 1, + \ "end_lnum": d.range.end.line + 1, + \ "col": d.range.end.character, + \ "end_col": d.range.end.character, + \ "type": s:severity, + \ }) + endfor + + call ale#other_source#ShowResults(bufnr('%'), 'LanguageClientNeovim', s:diagnostics) +endfunction + +let g:LanguageClient_diagnosticsDisplayFuncref = function('DisplayDiagnostics') +``` + +Keep in mind that to complete the integration between `ale` and +`LanguageClient-neovim` you need to add `LanguageClientNeovim` (or the name of +the linter you used in the call to ShowResults) to the list of linters to be +used in `ale`. 2.10 g:LanguageClient_diagnosticsEnable *g:LanguageClient_diagnosticsEnable* @@ -635,51 +677,6 @@ Highlight group to be used for code lens. Default: 'Comment' -2.42 g:LanguageClient_diagnosticsDisplayFuncref *g:LanguageClient_diagnosticsDisplayFuncref* - -If set, LanguageClient-neovim will call this function instead of setting the diagnostics signs. This -is useful to delegate the display of diagnostics to other engines. The function is called with two -arguments, the first one is the file name of which the diagnostics correspond to, and the seconds one -is the list of diagnostics for said file. Those diagnostics are as specified in the LSP specification. - -For example, if you wanted to use `dense-analysis/ale` to display diagnostics instead of this plugin -you could use something like this: - -``` -function! g:DisplayDiagnostics(filename, diagnostics) abort - let s:diagnostics = [] - - for d in a:diagnostics - let s:severity = 'I' - if d.severity == 1 - let s:severity = 'E' - elseif d.severity == 2 - let s:severity = 'W' - endif - - call add(s:diagnostics, { - \ "filename": a:filename, - \ "text": d.message, - \ "lnum": d.range.start.line + 1, - \ "end_lnum": d.range.end.line + 1, - \ "col": d.range.end.character, - \ "end_col": d.range.end.character, - \ "type": s:severity, - \ }) - endfor - - call ale#other_source#ShowResults(bufnr('%'), 'LanguageClientNeovim', s:diagnostics) -endfunction - -let g:LanguageClient_diagnosticsDisplayFuncref = 'g:DisplayDiagnostics' -``` - -Keep in mind that to complete the integration between `ale` and `LanguageClient-neovim` you need to -add `LanguageClientNeovim` (or the name of the linter you used in the call to ShowResults) to the list -of linters to be used in `ale`. - -Default: v:null - ============================================================================== 3. Commands *LanguageClientCommands* diff --git a/src/language_server_protocol.rs b/src/language_server_protocol.rs index 58def10ab..7b2b7c80f 100644 --- a/src/language_server_protocol.rs +++ b/src/language_server_protocol.rs @@ -1,6 +1,6 @@ -use crate::extensions::java; -use crate::language_client::LanguageClient; use crate::vim::{try_get, Mode}; +use crate::{extensions::java, vim::Funcref}; +use crate::{language_client::LanguageClient, viewport::Viewport}; use crate::{ rpcclient::RpcClient, types::*, @@ -127,7 +127,7 @@ impl LanguageClient { ): ( u64, HashMap>, - Option, + Option>, Option, Vec, u64, @@ -135,7 +135,7 @@ impl LanguageClient { Option, Option, u64, - Option, + Option>, Value, String, Option, @@ -145,7 +145,7 @@ impl LanguageClient { [ "!!get(g:, 'LanguageClient_autoStart', 1)", "s:GetVar('LanguageClient_serverCommands', {})", - "s:getSelectionUI()", + "s:getStringOrFuncref('LanguageClient_selectionUI', v:null)", "get(g:, 'LanguageClient_trace', v:null)", "map(s:ToList(get(g:, 'LanguageClient_settingsPath', '.vim/settings.json')), 'expand(v:val)')", "!!get(g:, 'LanguageClient_loadSettings', 1)", @@ -153,7 +153,7 @@ impl LanguageClient { "get(g:, 'LanguageClient_changeThrottle', v:null)", "get(g:, 'LanguageClient_waitOutputTimeout', v:null)", "!!get(g:, 'LanguageClient_diagnosticsEnable', 1)", - "get(g:, 'LanguageClient_diagnosticsList', 'Quickfix')", + "s:getStringOrFuncref('LanguageClient_diagnosticsList', 'Quickfix')", "get(g:, 'LanguageClient_diagnosticsDisplay', {})", "get(g:, 'LanguageClient_windowLogMessageLevel', 'Warning')", "get(g:, 'LanguageClient_hoverPreview', 'Auto')", @@ -165,7 +165,6 @@ impl LanguageClient { #[allow(clippy::type_complexity)] let ( - diagnostics_display_funcref, diagnostics_signs_max, diagnostics_max_severity, diagnostics_ignore_sources, @@ -181,7 +180,6 @@ impl LanguageClient { enable_extensions, code_lens_hl_group, ): ( - Option, Option, String, Vec, @@ -198,7 +196,6 @@ impl LanguageClient { String, ) = self.vim()?.eval( [ - "get(g:, 'LanguageClient_diagnosticsDisplayFuncref', v:null)", "get(g:, 'LanguageClient_diagnosticsSignsMax', v:null)", "get(g:, 'LanguageClient_diagnosticsMaxSeverity', 'Hint')", "get(g:, 'LanguageClient_diagnosticsIgnoreSources', [])", @@ -232,12 +229,12 @@ impl LanguageClient { None => Some(TraceOption::default()), }; - let selection_ui = if let Some(s) = selection_ui { - SelectionUI::from_str(&s)? - } else if self.vim()?.eval::<_, i64>("get(g:, 'loaded_fzf')")? == 1 { - SelectionUI::Funcref + let selection_ui = if let Some(Either::Right(s)) = selection_ui { + Either::Right(SelectionUI::from_str(&s)?) + } else if let Some(Either::Left(s)) = selection_ui { + Either::Left(s) } else { - SelectionUI::default() + Either::Right(SelectionUI::default()) }; let change_throttle = change_throttle.map(|t| Duration::from_millis((t * 1000.0) as u64)); @@ -246,10 +243,12 @@ impl LanguageClient { let diagnostics_enable = diagnostics_enable == 1; - let diagnostics_list = if let Some(s) = diagnostics_list { - DiagnosticsList::from_str(&s)? + let diagnostics_list = if let Some(Either::Right(s)) = diagnostics_list { + Either::Right(DiagnosticsList::from_str(&s)?) + } else if let Some(Either::Left(s)) = diagnostics_list { + Either::Left(s) } else { - DiagnosticsList::Disabled + Either::Right(DiagnosticsList::Disabled) }; let window_log_level = match window_log_message_level.to_ascii_uppercase().as_str() { @@ -331,7 +330,6 @@ impl LanguageClient { state.preferred_markup_kind = preferred_markup_kind; state.enable_extensions = enable_extensions; state.code_lens_hl_group = code_lens_hl_group; - state.diagnostics_display_funcref = diagnostics_display_funcref; Ok(()) })?; @@ -686,15 +684,18 @@ impl LanguageClient { .collect(); let title = "[LC]: diagnostics"; - let diagnostics_list = self.get(|state| state.diagnostics_list)?; + let diagnostics_list = self.get(|state| state.diagnostics_list.clone())?; match diagnostics_list { - DiagnosticsList::Quickfix => { - self.vim()?.setqflist(&qflist, "r", title)?; - } - DiagnosticsList::Location => { - self.vim()?.setloclist(&qflist, "r", title)?; - } - DiagnosticsList::Disabled => {} + Either::Left(_) => {} + Either::Right(dl) => match dl { + DiagnosticsList::Quickfix => { + self.vim()?.setqflist(&qflist, "r", title)?; + } + DiagnosticsList::Location => { + self.vim()?.setloclist(&qflist, "r", title)?; + } + DiagnosticsList::Disabled => {} + }, } Ok(()) @@ -1890,14 +1891,21 @@ impl LanguageClient { .map(|it| ListItem::string_item(it, self, &cwd)) .collect(); - match self.get(|state| state.selection_ui)? { - SelectionUI::Funcref => { + match self.get(|state| state.selection_ui.clone())? { + Either::Left(f) => { + self.vim()? + .rpcclient + .notify(f, json!([actions?, NOTIFICATION_FZF_SINK_COMMAND]))?; + } + // this exists purely for compatibility purposes, we should consider dropping this at + // some point and letting the user set up the FZF integration via a funcref + Either::Right(SelectionUI::FZF) => { self.vim()?.rpcclient.notify( "s:selectionUI_funcref", json!([actions?, NOTIFICATION_FZF_SINK_COMMAND]), )?; } - SelectionUI::Quickfix | SelectionUI::LocationList => { + Either::Right(SelectionUI::Quickfix) | Either::Right(SelectionUI::LocationList) => { let mut actions: Vec = actions? .iter_mut() .enumerate() @@ -1922,11 +1930,25 @@ impl LanguageClient { where T: ListItem, { - let selection_ui = self.get(|state| state.selection_ui)?; + let selection_ui = self.get(|state| state.selection_ui.clone())?; let selection_ui_auto_open = self.get(|state| state.selection_ui_auto_open)?; match selection_ui { - SelectionUI::Funcref => { + Either::Left(f) => { + let cwd: String = self.vim()?.eval("getcwd()")?; + let source: Result> = items + .iter() + .map(|it| ListItem::string_item(it, self, &cwd)) + .collect(); + let source = source?; + + self.vim()? + .rpcclient + .notify(f, json!([source, NOTIFICATION_FZF_SINK_LOCATION]))?; + } + // this exists purely for compatibility purposes, we should consider dropping this at + // some point and letting the user set up the FZF integration via a funcref + Either::Right(SelectionUI::FZF) => { let cwd: String = self.vim()?.eval("getcwd()")?; let source: Result> = items .iter() @@ -1936,10 +1958,10 @@ impl LanguageClient { self.vim()?.rpcclient.notify( "s:selectionUI_funcref", - json!([source, format!("s:{}", NOTIFICATION_FZF_SINK_LOCATION)]), + json!([source, NOTIFICATION_FZF_SINK_LOCATION]), )?; } - SelectionUI::Quickfix => { + Either::Right(SelectionUI::Quickfix) => { let list: Result> = items .iter() .map(|it| ListItem::quickfix_item(it, self)) @@ -1951,7 +1973,7 @@ impl LanguageClient { } self.vim()?.echo("Populated quickfix list.")?; } - SelectionUI::LocationList => { + Either::Right(SelectionUI::LocationList) => { let list: Result> = items .iter() .map(|it| ListItem::quickfix_item(it, self)) @@ -2476,10 +2498,11 @@ impl LanguageClient { } // if a diagnostics display funcref has been configured then call that function and return - if let Some(funcref) = self.get(|state| state.diagnostics_display_funcref.clone())? { + if let Either::Left(funcref) = self.get(|state| state.diagnostics_list.clone())? { self.vim()? .rpcclient .notify(funcref, json!([filename, diagnostics]))?; + self.draw_virtual_texts(&Value::Null)?; return Ok(()); } @@ -3153,50 +3176,12 @@ impl LanguageClient { Ok(()) } - pub fn handle_cursor_moved(&self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION_HANDLE_CURSOR_MOVED); - let filename = self.vim()?.get_filename(params)?; - let language_id = self.vim()?.get_language_id(&filename, params)?; - let line = self.vim()?.get_position(params)?.line; - if !self.get(|state| state.server_commands.contains_key(&language_id))? { - return Ok(()); - } - if !self.get(|state| state.diagnostics.contains_key(&filename))? - && !self.get(|state| state.code_lens.contains_key(&filename))? - { - return Ok(()); - } - - if line != self.get(|state| state.last_cursor_line)? { - let message = self.get(|state| { - state - .line_diagnostics - .get(&(filename.clone(), line)) - .cloned() - .unwrap_or_default() - })?; - - if message != self.get(|state| state.last_line_diagnostic.clone())? { - self.vim()?.echo_ellipsis(&message)?; - self.update(|state| { - state.last_line_diagnostic = message; - Ok(()) - })?; - } - - self.update(|state| { - state.last_cursor_line = line; - Ok(()) - })?; - } - - let viewport = self.vim()?.get_viewport(params)?; - + fn update_signs(&self, filename: &str, viewport: &Viewport) -> Result<()> { // use the most severe diagnostic of each line as the sign let signs_next: Vec<_> = self.update(|state| { let mut diagnostics = state .diagnostics - .entry(filename.clone()) + .entry(filename.to_string()) .or_default() .iter() .filter(|diag| viewport.overlaps(diag.range)) @@ -3217,7 +3202,7 @@ impl LanguageClient { self.update(|state| { let signs_prev: Vec<_> = state .signs - .entry(filename.clone()) + .entry(filename.to_string()) .or_default() .iter() .map(|(_, sign)| sign.clone()) @@ -3237,7 +3222,7 @@ impl LanguageClient { } } - let signs = state.signs.entry(filename.clone()).or_default(); + let signs = state.signs.entry(filename.to_string()).or_default(); // signs might be deleted AND added in the same line to change severity, // so deletions must be before additions for sign in &signs_to_delete { @@ -3252,6 +3237,52 @@ impl LanguageClient { Ok(()) })?; + Ok(()) + } + + pub fn handle_cursor_moved(&self, params: &Value) -> Result<()> { + info!("Begin {}", NOTIFICATION_HANDLE_CURSOR_MOVED); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let line = self.vim()?.get_position(params)?.line; + if !self.get(|state| state.server_commands.contains_key(&language_id))? { + return Ok(()); + } + if !self.get(|state| state.diagnostics.contains_key(&filename))? + && !self.get(|state| state.code_lens.contains_key(&filename))? + { + return Ok(()); + } + + if line != self.get(|state| state.last_cursor_line)? { + let message = self.get(|state| { + state + .line_diagnostics + .get(&(filename.clone(), line)) + .cloned() + .unwrap_or_default() + })?; + + if message != self.get(|state| state.last_line_diagnostic.clone())? { + self.vim()?.echo_ellipsis(&message)?; + self.update(|state| { + state.last_line_diagnostic = message; + Ok(()) + })?; + } + + self.update(|state| { + state.last_cursor_line = line; + Ok(()) + })?; + } + + let viewport = self.vim()?.get_viewport(params)?; + // skip updating signs if a funcref is being used for diagnostics display + if let Either::Right(_not_a_funcref) = self.get(|state| state.diagnostics_list.clone())? { + self.update_signs(&filename, &viewport)?; + } + let highlights: Vec<_> = self.update(|state| { Ok(state .highlights diff --git a/src/types.rs b/src/types.rs index 37d6d8cc4..f28201905 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,3 @@ -use crate::logger::Logger; use crate::rpcclient::RpcClient; use crate::sign::Sign; use crate::{ @@ -7,6 +6,7 @@ use crate::{ vim::Vim, watcher::FSWatch, }; +use crate::{logger::Logger, vim::Funcref}; use anyhow::{anyhow, Result}; use jsonrpc_core::Params; use log::*; @@ -107,6 +107,13 @@ pub type LanguageId = Option; /// Buffer id/handle. pub type Bufnr = i64; +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Either { + Left(L), + Right(R), +} + #[derive(Debug, Serialize, Deserialize)] pub enum Message { MethodCall(LanguageId, jsonrpc_core::MethodCall), @@ -188,11 +195,11 @@ pub struct State { pub semantic_highlight_maps: HashMap>, pub semantic_scope_separator: String, pub auto_start: bool, - pub selection_ui: SelectionUI, + pub selection_ui: Either, pub selection_ui_auto_open: bool, pub trace: Option, pub diagnostics_enable: bool, - pub diagnostics_list: DiagnosticsList, + pub diagnostics_list: Either, pub diagnostics_display: HashMap, pub diagnostics_signs_max: Option, pub diagnostics_max_severity: DiagnosticSeverity, @@ -211,7 +218,6 @@ pub struct State { pub hide_virtual_texts_on_insert: bool, pub echo_project_root: bool, pub enable_extensions: Option>, - pub diagnostics_display_funcref: Option, pub server_stderr: Option, pub logger: Logger, @@ -271,11 +277,11 @@ impl State { semantic_highlight_maps: HashMap::new(), semantic_scope_separator: ":".into(), auto_start: true, - selection_ui: SelectionUI::LocationList, + selection_ui: Either::Right(SelectionUI::LocationList), selection_ui_auto_open: true, trace: None, diagnostics_enable: true, - diagnostics_list: DiagnosticsList::Quickfix, + diagnostics_list: Either::Right(DiagnosticsList::Quickfix), diagnostics_display: DiagnosticsDisplay::default(), diagnostics_signs_max: None, diagnostics_max_severity: DiagnosticSeverity::Hint, @@ -297,7 +303,6 @@ impl State { preferred_markup_kind: None, enable_extensions: None, code_lens_hl_group: "Comment".into(), - diagnostics_display_funcref: None, logger, }) @@ -306,7 +311,7 @@ impl State { #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum SelectionUI { - Funcref, + FZF, Quickfix, LocationList, } @@ -322,7 +327,7 @@ impl FromStr for SelectionUI { fn from_str(s: &str) -> Result { match s.to_ascii_uppercase().as_str() { - "FUNCREF" | "FZF" => Ok(SelectionUI::Funcref), + "FZF" => Ok(SelectionUI::FZF), "QUICKFIX" => Ok(SelectionUI::Quickfix), "LOCATIONLIST" | "LOCATION-LIST" => Ok(SelectionUI::LocationList), _ => Err(anyhow!( @@ -376,7 +381,7 @@ impl FromStr for HoverPreviewOption { } } -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum DiagnosticsList { Quickfix, Location, diff --git a/src/vim.rs b/src/vim.rs index 1c054a126..966f0483b 100644 --- a/src/vim.rs +++ b/src/vim.rs @@ -23,6 +23,44 @@ pub fn try_get(key: &str, params: &Value) -> Result for Funcref { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + +impl serde::ser::Serialize for Funcref { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(format!("function('{}')", self.0).as_str()) + } +} + +impl<'de> serde::de::Deserialize<'de> for Funcref { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + let s: String = serde::de::Deserialize::deserialize(deserializer)?; + let re = regex::Regex::new(r"function\('(\w+)'\)").unwrap(); + if let Some(captures) = re.captures(&s) { + if captures.len() > 1 { + let funcref = captures[1].to_string(); + return Ok(Funcref(funcref)); + } + } + + return Err(serde::de::Error::custom( + "Expected funcref to start with 'function'", + )); + } +} + #[derive(PartialEq)] pub enum Mode { Normal, @@ -253,3 +291,22 @@ impl Vim { ) } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_deserialize_funcref() { + let a = r#""function('funcname')""#; + let funcref: Funcref = serde_json::from_str(a).unwrap(); + assert_eq!(funcref, Funcref("funcname".into())); + } + + #[test] + fn test_serialize_funcref() { + let actual = serde_json::to_string(&Funcref("funcname".into())).unwrap(); + let expect = r#""function('funcname')""#; + assert_eq!(expect, actual); + } +}