diff --git a/Cargo.lock b/Cargo.lock index 94ab47f3..b111bbfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -460,6 +460,8 @@ dependencies = [ "serde_json", "shellexpand", "thiserror", + "tracing", + "tracing-log", "url", ] @@ -729,6 +731,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pin-project-lite" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" + [[package]] name = "proc-macro2" version = "1.0.19" @@ -982,6 +990,50 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" +[[package]] +name = "tracing" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0987850db3733619253fe60e17cb59b82d37c7e6c0236bb81e4d6b87c879f27" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e0ccfc3378da0cce270c946b676a376943f5cd16aeba64568e7939806f4ada" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-log" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0f8c7178e13481ff6765bd169b33e8d554c5d2bbede5e32c356194be02b9b9" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + [[package]] name = "traitobject" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 25d4a82f..d1ff2f99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,3 +40,5 @@ derivative = "2.1.1" anyhow = "1.0.32" thiserror = "1.0.20" lazy_static = "1.4.0" +tracing = { version = "0.1", features = ["log", "log-always"] } +tracing-log = "0.1.1" diff --git a/autoload/LanguageClient.vim b/autoload/LanguageClient.vim index e13ed24d..fec29d4d 100644 --- a/autoload/LanguageClient.vim +++ b/autoload/LanguageClient.vim @@ -783,7 +783,6 @@ function! LanguageClient#textDocument_hover(...) abort let l:Callback = get(a:000, 1, v:null) let l:params = { \ 'filename': LSP#filename(), - \ 'text': LSP#text(), \ 'line': LSP#line(), \ 'character': LSP#character(), \ 'handle': s:IsFalse(l:Callback), @@ -846,7 +845,6 @@ function! LanguageClient#textDocument_references(...) abort let l:Callback = get(a:000, 1, v:null) let l:params = { \ 'filename': LSP#filename(), - \ 'text': LSP#text(), \ 'line': LSP#line(), \ 'character': LSP#character(), \ 'includeDeclaration': v:true, @@ -860,7 +858,6 @@ endfunction function! LanguageClient#textDocument_rename(...) abort let l:params = { \ 'filename': LSP#filename(), - \ 'text': LSP#text(), \ 'line': LSP#line(), \ 'character': LSP#character(), \ 'cword': expand(''), @@ -875,7 +872,6 @@ function! LanguageClient#textDocument_documentSymbol(...) abort let l:Callback = get(a:000, 1, v:null) let l:params = { \ 'filename': LSP#filename(), - \ 'text': LSP#text(), \ 'handle': s:IsFalse(l:Callback), \ } call extend(l:params, get(a:000, 0, {})) @@ -886,7 +882,6 @@ function! LanguageClient#workspace_symbol(...) abort let l:Callback = get(a:000, 2, v:null) let l:params = { \ 'filename': LSP#filename(), - \ 'text': LSP#text(), \ 'query': get(a:000, 0, ''), \ 'handle': s:IsFalse(l:Callback), \ } @@ -898,7 +893,6 @@ function! LanguageClient#textDocument_codeLens(...) abort let l:Callback = get(a:000, 1, v:null) let l:params = { \ 'filename': LSP#filename(), - \ 'text': LSP#text(), \ 'line': LSP#line(), \ 'character': LSP#character(), \ 'handle': s:IsFalse(l:Callback), @@ -1023,7 +1017,6 @@ function! LanguageClient#textDocument_documentHighlight(...) abort let l:Callback = get(a:000, 1, v:null) let l:params = { \ 'filename': LSP#filename(), - \ 'text': LSP#text(), \ 'line': LSP#line(), \ 'character': LSP#character(), \ 'handle': s:IsFalse(l:Callback), diff --git a/doc/LanguageClient.txt b/doc/LanguageClient.txt index 91457d91..7947afde 100644 --- a/doc/LanguageClient.txt +++ b/doc/LanguageClient.txt @@ -657,6 +657,20 @@ margin, set this setting to 0. Example: < Default: 1 +2.43 g:LanguageClient_restartOnCrash *g:LanguageClient_restartOnCrash* + +If enabled, the client will attempt to restart the server on the event that it +unexpectedly crashes. + +Default: 1 + +2.44 g:LanguageClient_maxRestartRetries *g:LanguageClient_maxRestartRetries* + +Max number of times to attempt to recover from a server crash. Each language +handles its own count independently. + +Default: 5 + ============================================================================== 3. Commands *LanguageClientCommands* @@ -1094,6 +1108,11 @@ This event is triggered when diagnostics changed. Triggered after textDocument/didOpen notification is sent to language server. +6.5 LanguageServerCrashed +*LanguageServerCrashed* + +This event is triggered when a language server unexpectedly quits. + ============================================================================== 7. License *LanguageClientLicense* diff --git a/src/language_server_protocol.rs b/src/language_server_protocol.rs index b65868e6..1daf11e2 100644 --- a/src/language_server_protocol.rs +++ b/src/language_server_protocol.rs @@ -17,29 +17,29 @@ use glob::glob; use itertools::Itertools; use jsonrpc_core::Value; use log::{debug, error, info, warn}; -use lsp_types::request::Request; -use lsp_types::{notification::Notification, PublishDiagnosticsClientCapabilities}; use lsp_types::{ - ApplyWorkspaceEditParams, ApplyWorkspaceEditResponse, ClientCapabilities, ClientInfo, - CodeAction, CodeActionCapability, CodeActionContext, CodeActionKind, - CodeActionKindLiteralSupport, CodeActionLiteralSupport, CodeActionOrCommand, CodeActionParams, - CodeActionResponse, CodeLens, Command, CompletionCapability, CompletionItem, - CompletionItemCapability, CompletionResponse, CompletionTextEdit, Diagnostic, - DiagnosticSeverity, DidChangeConfigurationParams, DidChangeTextDocumentParams, - DidChangeWatchedFilesParams, DidChangeWatchedFilesRegistrationOptions, - DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, - DocumentChangeOperation, DocumentChanges, DocumentFormattingParams, DocumentHighlight, - DocumentHighlightKind, DocumentRangeFormattingParams, DocumentSymbolParams, + notification::Notification, request::Request, ApplyWorkspaceEditParams, + ApplyWorkspaceEditResponse, ClientCapabilities, ClientInfo, CodeAction, CodeActionCapability, + CodeActionContext, CodeActionKind, CodeActionKindLiteralSupport, CodeActionLiteralSupport, + CodeActionOrCommand, CodeActionParams, CodeActionResponse, CodeLens, Command, + CompletionCapability, CompletionItem, CompletionItemCapability, CompletionResponse, + CompletionTextEdit, Diagnostic, DiagnosticSeverity, DidChangeConfigurationParams, + DidChangeTextDocumentParams, DidChangeWatchedFilesParams, + DidChangeWatchedFilesRegistrationOptions, DidCloseTextDocumentParams, + DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentChangeOperation, DocumentChanges, + DocumentFormattingParams, DocumentHighlight, DocumentHighlightKind, + DocumentRangeFormattingParams, DocumentSymbolParams, DocumentSymbolResponse, ExecuteCommandParams, FormattingOptions, GenericCapability, GotoCapability, GotoDefinitionResponse, Hover, HoverCapability, InitializeParams, InitializeResult, InitializedParams, Location, LogMessageParams, MarkupKind, MessageType, NumberOrString, ParameterInformation, ParameterInformationSettings, PartialResultParams, Position, - ProgressParams, ProgressParamsValue, PublishDiagnosticsParams, Range, ReferenceContext, - RegistrationParams, RenameParams, ResourceOp, SemanticHighlightingClientCapability, - SemanticHighlightingParams, ShowMessageParams, ShowMessageRequestParams, SignatureHelp, - SignatureHelpCapability, SignatureInformationSettings, SymbolInformation, - TextDocumentClientCapabilities, TextDocumentContentChangeEvent, TextDocumentIdentifier, - TextDocumentItem, TextDocumentPositionParams, TextEdit, TraceOption, UnregistrationParams, + ProgressParams, ProgressParamsValue, PublishDiagnosticsClientCapabilities, + PublishDiagnosticsParams, Range, ReferenceContext, RegistrationParams, RenameParams, + ResourceOp, SemanticHighlightingClientCapability, SemanticHighlightingParams, + ShowMessageParams, ShowMessageRequestParams, SignatureHelp, SignatureHelpCapability, + SignatureInformationSettings, SymbolInformation, TextDocumentClientCapabilities, + TextDocumentContentChangeEvent, TextDocumentIdentifier, TextDocumentItem, + TextDocumentPositionParams, TextEdit, TraceOption, UnregistrationParams, VersionedTextDocumentIdentifier, WorkDoneProgress, WorkDoneProgressParams, WorkspaceClientCapabilities, WorkspaceEdit, WorkspaceSymbolParams, }; @@ -90,8 +90,8 @@ impl LanguageClient { } /////// Utils /////// + #[tracing::instrument(level = "info", skip(self))] fn sync_settings(&self) -> Result<()> { - info!("Begin sync settings"); let (logging_file, logging_level, server_stderr): ( Option, log::LevelFilter, @@ -214,6 +214,15 @@ impl LanguageClient { .as_ref(), )?; + #[allow(clippy::type_complexity)] + let (restart_on_crash, max_restart_retries): (u8, u8) = self.vim()?.eval( + [ + "get(g:, 'LanguageClient_restartOnCrash', 1)", + "get(g:, 'LanguageClient_maxRestartRetries', 5)", + ] + .as_ref(), + )?; + // vimscript use 1 for true, 0 for false. let auto_start = auto_start == 1; let selection_ui_auto_open = selection_ui_auto_open == 1; @@ -328,6 +337,9 @@ impl LanguageClient { state.preferred_markup_kind = preferred_markup_kind; state.enable_extensions = enable_extensions; state.code_lens_hl_group = code_lens_hl_group; + state.max_restart_retries = max_restart_retries; + state.restart_on_crash = restart_on_crash == 1; + Ok(()) })?; @@ -335,7 +347,6 @@ impl LanguageClient { self.update_semantic_highlight_tables(&language_id)?; } - info!("End sync settings"); Ok(()) } @@ -396,8 +407,8 @@ impl LanguageClient { Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn apply_workspace_edit(&self, edit: &WorkspaceEdit) -> Result<()> { - debug!("Begin apply WorkspaceEdit: {:?}", edit); let mut filename = self.vim()?.get_filename(&Value::Null)?; let mut position = self.vim()?.get_position(&Value::Null)?; @@ -446,16 +457,11 @@ impl LanguageClient { self.edit(&None, &filename)?; self.vim()? .cursor(position.line + 1, position.character + 1)?; - debug!("End apply WorkspaceEdit"); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn text_document_document_highlight(&self, params: &Value) -> Result { - self.text_document_did_change(params)?; - info!( - "Begin {}", - lsp_types::request::DocumentHighlightRequest::METHOD - ); let filename = self.vim()?.get_filename(&Value::Null)?; let language_id = self.vim()?.get_language_id(&filename, &Value::Null)?; let position = self.vim()?.get_position(&Value::Null)?; @@ -474,8 +480,7 @@ impl LanguageClient { return Ok(result); } - let document_highlight: Option> = - serde_json::from_value(result.clone())?; + let document_highlight = >>::deserialize(&result)?; if let Some(document_highlight) = document_highlight { let document_highlight_display = self.get(|state| state.document_highlight_display.clone())?; @@ -550,16 +555,11 @@ impl LanguageClient { })?; } - info!( - "End {}", - lsp_types::request::DocumentHighlightRequest::METHOD - ); Ok(result) } + #[tracing::instrument(level = "info", skip(self))] pub fn clear_document_highlight(&self, _params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION_CLEAR_DOCUMENT_HL); - // The following code needs to be inside the critical section as a whole to update // everything correctly and not leave hanging highlights. self.update(|state| { @@ -573,17 +573,16 @@ impl LanguageClient { Ok(()) })?; - info!("End {}", NOTIFICATION_CLEAR_DOCUMENT_HL); Ok(()) } - fn apply_text_edits>( + #[tracing::instrument(level = "info", skip(self))] + fn apply_text_edits + std::fmt::Debug>( &self, path: P, edits: &[TextEdit], position: Position, ) -> Result { - debug!("Begin apply TextEdits: {:?}", edits); if edits.is_empty() { return Ok(position); } @@ -618,7 +617,6 @@ impl LanguageClient { .command(format!("{},{}d", lines.len() + 1, lines_len_prev))?; } self.vim()?.rpcclient.notify("setline", json!([1, lines]))?; - debug!("End apply TextEdits"); Ok(position) } @@ -848,14 +846,14 @@ impl LanguageClient { Ok(()) } + #[tracing::instrument(level = "info", skip(self))] fn register_cm_source(&self, language_id: &str, result: &Value) -> Result<()> { - info!("Begin register NCM source"); let exists_cm_register: u64 = self.vim()?.eval("exists('g:cm_matcher')")?; if exists_cm_register == 0 { return Ok(()); } - let result: InitializeResult = serde_json::from_value(result.clone())?; + let result = InitializeResult::deserialize(result)?; if result.capabilities.completion_provider.is_none() { return Ok(()); } @@ -885,18 +883,17 @@ impl LanguageClient { "cm_refresh": REQUEST_NCM_REFRESH, }]), )?; - info!("End register NCM source"); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] fn register_ncm2_source(&self, language_id: &str, result: &Value) -> Result<()> { - info!("Begin register NCM2 source"); let exists_ncm2: u64 = self.vim()?.eval("exists('g:ncm2_loaded')")?; if exists_ncm2 == 0 { return Ok(()); } - let result: InitializeResult = serde_json::from_value(result.clone())?; + let result = InitializeResult::deserialize(result)?; if result.capabilities.completion_provider.is_none() { return Ok(()); } @@ -926,13 +923,12 @@ impl LanguageClient { "on_complete": REQUEST_NCM2_ON_COMPLETE, }]), )?; - info!("End register NCM2 source"); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] fn parse_semantic_scopes(&self, language_id: &str, result: &Value) -> Result<()> { - info!("Begin parse Semantic Scopes"); - let result: InitializeResult = serde_json::from_value(result.clone())?; + let result = InitializeResult::deserialize(result)?; if let Some(capability) = result.capabilities.semantic_highlighting { self.update(|state| { @@ -943,15 +939,14 @@ impl LanguageClient { })?; } - info!("End parse Semantic Scopes"); Ok(()) } /// Build the Semantic Highlight Lookup Table of /// /// ScopeIndex -> Option + #[tracing::instrument(level = "info", skip(self))] fn update_semantic_highlight_tables(&self, language_id: &str) -> Result<()> { - info!("Begin updateSemanticHighlightTables"); let (opt_scopes, opt_hl_map, scope_separator) = self.get(|state| { ( state.semantic_scopes.get(language_id).cloned(), @@ -1001,16 +996,15 @@ impl LanguageClient { Ok(()) })?; } - info!("End updateSemanticHighlightTables"); Ok(()) } pub fn get_line(&self, path: impl AsRef, line: u64) -> Result { - let value = self.vim()?.rpcclient.call( + let value: Value = self.vim()?.rpcclient.call( "getbufline", json!([path.as_ref().to_string_lossy(), line + 1]), )?; - let mut texts: Vec = serde_json::from_value(value)?; + let mut texts = >::deserialize(value)?; let mut text = texts.pop().unwrap_or_default(); if text.is_empty() { @@ -1054,9 +1048,8 @@ impl LanguageClient { } } + #[tracing::instrument(level = "info", skip(self))] fn cleanup(&self, language_id: &str) -> Result<()> { - info!("Begin cleanup"); - let root = self.get(|state| { state .roots @@ -1107,7 +1100,6 @@ impl LanguageClient { .rpcclient .notify("s:ExecuteAutocmd", "LanguageClientStopped")?; - info!("End cleanup"); Ok(()) } @@ -1138,8 +1130,8 @@ impl LanguageClient { /////// LSP /////// + #[tracing::instrument(level = "info", skip(self))] fn initialize(&self, params: &Value) -> Result { - info!("Begin {}", lsp_types::request::Initialize::METHOD); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; let has_snippet_support: i8 = try_get("hasSnippetSupport", params)? @@ -1281,8 +1273,6 @@ impl LanguageClient { Ok(()) })?; - info!("End {}", lsp_types::request::Initialize::METHOD); - if let Err(e) = self.register_cm_source(&language_id, &result) { let message = format!("LanguageClient: failed to register as NCM source: {}", e); error!("{}\n{:?}", message, e); @@ -1302,8 +1292,8 @@ impl LanguageClient { Ok(result) } + #[tracing::instrument(level = "info", skip(self))] fn initialized(&self, params: &Value) -> Result<()> { - info!("Begin {}", lsp_types::notification::Initialized::METHOD); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; self.update_semantic_highlight_tables(&language_id)?; @@ -1311,13 +1301,11 @@ impl LanguageClient { lsp_types::notification::Initialized::METHOD, InitializedParams {}, )?; - info!("End {}", lsp_types::notification::Initialized::METHOD); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn text_document_hover(&self, params: &Value) -> Result { - self.text_document_did_change(params)?; - info!("Begin {}", lsp_types::request::HoverRequest::METHOD); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; let position = self.vim()?.get_position(params)?; @@ -1351,16 +1339,14 @@ impl LanguageClient { } } - info!("End {}", lsp_types::request::HoverRequest::METHOD); Ok(result) } /// Generic find locations, e.g, definitions, references. + #[tracing::instrument(level = "info", skip(self))] pub fn find_locations(&self, params: &Value) -> Result { - self.text_document_did_change(params)?; let method: String = try_get("method", params)?.ok_or_else(|| anyhow!("method not found in request!"))?; - info!("Begin {}", method); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; let position = self.vim()?.get_position(params)?; @@ -1417,13 +1403,11 @@ impl LanguageClient { } } - info!("End {}", method); Ok(result) } + #[tracing::instrument(level = "info", skip(self))] pub fn text_document_rename(&self, params: &Value) -> Result { - self.text_document_did_change(params)?; - info!("Begin {}", lsp_types::request::Rename::METHOD); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; let position = self.vim()?.get_position(params)?; @@ -1462,20 +1446,14 @@ impl LanguageClient { return Ok(result); } - let edit: WorkspaceEdit = serde_json::from_value(result.clone())?; + let edit = WorkspaceEdit::deserialize(&result)?; self.apply_workspace_edit(&edit)?; - info!("End {}", lsp_types::request::Rename::METHOD); Ok(result) } + #[tracing::instrument(level = "info", skip(self))] pub fn text_document_document_symbol(&self, params: &Value) -> Result { - self.text_document_did_change(params)?; - info!( - "Begin {}", - lsp_types::request::DocumentSymbolRequest::METHOD - ); - let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; @@ -1494,14 +1472,14 @@ impl LanguageClient { return Ok(result); } - let syms = ::Result::deserialize(&result)?; + let syms = >::deserialize(&result)?; let title = format!("[LC]: symbols for {}", filename); match syms { - Some(lsp_types::DocumentSymbolResponse::Flat(flat)) => { + Some(DocumentSymbolResponse::Flat(flat)) => { self.present_list(&title, &flat)?; } - Some(lsp_types::DocumentSymbolResponse::Nested(nested)) => { + Some(DocumentSymbolResponse::Nested(nested)) => { let mut symbols = Vec::new(); fn walk_document_symbol( @@ -1533,16 +1511,14 @@ impl LanguageClient { _ => (), }; - info!("End {}", lsp_types::request::DocumentSymbolRequest::METHOD); Ok(result) } + #[tracing::instrument(level = "info", skip(self))] pub fn text_document_code_action(&self, params: &Value) -> Result { - self.text_document_did_change(params)?; - info!("Begin {}", lsp_types::request::CodeActionRequest::METHOD); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; - let range: Range = serde_json::from_value(params["range"].clone())?; + let range = Range::deserialize(¶ms["range"])?; // Unify filename. let filename = filename.canonicalize(); @@ -1607,7 +1583,6 @@ impl LanguageClient { self.handle_code_action_selection(&actions, idx) })?; - info!("End {}", lsp_types::request::CodeActionRequest::METHOD); Ok(result) } @@ -1640,9 +1615,8 @@ impl LanguageClient { Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn text_document_completion(&self, params: &Value) -> Result { - info!("Begin {}", lsp_types::request::Completion::METHOD); - let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; let position = self.vim()?.get_position(params)?; @@ -1661,13 +1635,11 @@ impl LanguageClient { return Ok(result); } - info!("End {}", lsp_types::request::Completion::METHOD); Ok(result) } + #[tracing::instrument(level = "info", skip(self))] pub fn text_document_signature_help(&self, params: &Value) -> Result { - self.text_document_did_change(params)?; - info!("Begin {}", lsp_types::request::SignatureHelpRequest::METHOD); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; let position = self.vim()?.get_position(params)?; @@ -1689,7 +1661,7 @@ impl LanguageClient { return Ok(result); } - let help: SignatureHelp = serde_json::from_value(result)?; + let help = SignatureHelp::deserialize(result)?; if help.signatures.is_empty() { return Ok(Value::Null); } @@ -1726,13 +1698,11 @@ impl LanguageClient { self.vim()?.echo(&active_signature.label)?; } - info!("End {}", lsp_types::request::SignatureHelpRequest::METHOD); Ok(Value::Null) } + #[tracing::instrument(level = "info", skip(self))] pub fn text_document_references(&self, params: &Value) -> Result { - info!("Begin {}", lsp_types::request::References::METHOD); - let include_declaration: bool = try_get("includeDeclaration", params)?.unwrap_or(true); // TODO: cleanup. let params = json!({ @@ -1743,14 +1713,11 @@ impl LanguageClient { }) .combine(params); let result = self.find_locations(¶ms)?; - - info!("End {}", lsp_types::request::References::METHOD); Ok(result) } + #[tracing::instrument(level = "info", skip(self))] pub fn text_document_formatting(&self, params: &Value) -> Result { - self.text_document_did_change(params)?; - info!("Begin {}", lsp_types::request::Formatting::METHOD); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; @@ -1776,20 +1743,18 @@ impl LanguageClient { return Ok(result); } - let text_edits: Option> = serde_json::from_value(result.clone())?; + let text_edits = >>::deserialize(&result)?; let text_edits = text_edits.unwrap_or_default(); let edit = lsp_types::WorkspaceEdit { changes: Some(hashmap! {filename.to_url()? => text_edits}), document_changes: None, }; self.apply_workspace_edit(&edit)?; - info!("End {}", lsp_types::request::Formatting::METHOD); Ok(result) } + #[tracing::instrument(level = "info", skip(self))] pub fn text_document_range_formatting(&self, params: &Value) -> Result { - self.text_document_did_change(params)?; - info!("Begin {}", lsp_types::request::RangeFormatting::METHOD); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; let start_line = try_get("range_start_line", params)? @@ -1829,23 +1794,18 @@ impl LanguageClient { return Ok(result); } - let text_edits: Option> = serde_json::from_value(result.clone())?; + let text_edits = >>::deserialize(&result)?; let text_edits = text_edits.unwrap_or_default(); let edit = lsp_types::WorkspaceEdit { changes: Some(hashmap! {filename.to_url()? => text_edits}), document_changes: None, }; self.apply_workspace_edit(&edit)?; - info!("End {}", lsp_types::request::RangeFormatting::METHOD); Ok(result) } + #[tracing::instrument(level = "info", skip(self))] pub fn completion_item_resolve(&self, params: &Value) -> Result { - self.text_document_did_change(params)?; - info!( - "Begin {}", - lsp_types::request::ResolveCompletionItem::METHOD - ); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; let completion_item: CompletionItem = try_get("completionItem", params)? @@ -1865,7 +1825,6 @@ impl LanguageClient { warn!("{}", msg); self.vim()?.echowarn(&msg)?; - info!("End {}", lsp_types::request::ResolveCompletionItem::METHOD); Ok(Value::Null) } @@ -1963,9 +1922,8 @@ impl LanguageClient { Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn workspace_symbol(&self, params: &Value) -> Result { - self.text_document_did_change(params)?; - info!("Begin {}", lsp_types::request::WorkspaceSymbol::METHOD); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; @@ -1987,13 +1945,11 @@ impl LanguageClient { let title = "[LC]: workspace symbols"; self.present_list(title, &symbols)?; - - info!("End {}", lsp_types::request::WorkspaceSymbol::METHOD); Ok(result) } + #[tracing::instrument(level = "info", skip(self))] pub fn workspace_execute_command(&self, params: &Value) -> Result { - info!("Begin {}", lsp_types::request::ExecuteCommand::METHOD); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; let command: String = @@ -2008,28 +1964,19 @@ impl LanguageClient { work_done_progress_params: WorkDoneProgressParams::default(), }, )?; - info!("End {}", lsp_types::request::ExecuteCommand::METHOD); Ok(result) } + #[tracing::instrument(level = "info", skip(self))] pub fn workspace_apply_edit(&self, params: &Value) -> Result { - info!("Begin {}", lsp_types::request::ApplyWorkspaceEdit::METHOD); - let params = ApplyWorkspaceEditParams::deserialize(params)?; self.apply_workspace_edit(¶ms.edit)?; - - info!("End {}", lsp_types::request::ApplyWorkspaceEdit::METHOD); - Ok(serde_json::to_value(ApplyWorkspaceEditResponse { applied: true, })?) } pub fn workspace_did_change_configuration(&self, params: &Value) -> Result<()> { - info!( - "Begin {}", - lsp_types::notification::DidChangeConfiguration::METHOD - ); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; let settings: Value = try_get("settings", params)?.unwrap_or_default(); @@ -2038,10 +1985,6 @@ impl LanguageClient { lsp_types::notification::DidChangeConfiguration::METHOD, DidChangeConfigurationParams { settings }, )?; - info!( - "End {}", - lsp_types::notification::DidChangeConfiguration::METHOD - ); Ok(()) } @@ -2108,6 +2051,7 @@ impl LanguageClient { Ok(Value::Null) } + #[tracing::instrument(level = "info", skip(self))] pub fn progress(&self, params: &Value) -> Result<()> { let params = ProgressParams::deserialize(params)?; let message = match params.value { @@ -2138,6 +2082,7 @@ impl LanguageClient { Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn text_document_code_lens(&self, params: &Value) -> Result { let use_virtual_text = self.get(|state| state.use_virtual_text.clone())?; if UseVirtualText::No == use_virtual_text || UseVirtualText::Diagnostics == use_virtual_text @@ -2154,7 +2099,6 @@ impl LanguageClient { let capabilities = initialize_result.capabilities.clone(); if let Some(code_lens_provider) = capabilities.code_lens_provider { - info!("Begin {}", lsp_types::request::CodeLensRequest::METHOD); let client = self.get_client(&Some(language_id))?; let input = lsp_types::CodeLensParams { text_document: TextDocumentIdentifier { @@ -2166,7 +2110,7 @@ impl LanguageClient { let results: Value = client.call(lsp_types::request::CodeLensRequest::METHOD, &input)?; - let code_lens: Option> = serde_json::from_value(results)?; + let code_lens = >>::deserialize(results)?; let mut code_lens: Vec = code_lens.unwrap_or_default(); if code_lens_provider.resolve_provider.unwrap_or_default() { @@ -2188,13 +2132,6 @@ impl LanguageClient { state.code_lens.insert(filename.to_owned(), code_lens); Ok(Value::Null) })?; - - info!("End {}", lsp_types::request::CodeLensRequest::METHOD); - } else { - info!( - "CodeLens not supported. Skipping {}", - lsp_types::request::CodeLensRequest::METHOD - ); } } @@ -2203,14 +2140,11 @@ impl LanguageClient { Ok(Value::Null) } + #[tracing::instrument(level = "info", skip(self))] pub fn text_document_did_open(&self, params: &Value) -> Result<()> { - info!( - "Begin {}", - lsp_types::notification::DidOpenTextDocument::METHOD - ); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; - let text = self.vim()?.get_text(&filename)?; + let text = self.vim()?.get_text(&filename, params)?; let set_omnifunc: bool = self .vim()? .eval("s:GetVar('LanguageClient_setOmnifunc', v:true)")?; @@ -2248,18 +2182,11 @@ impl LanguageClient { self.text_document_code_lens(params)?; - info!( - "End {}", - lsp_types::notification::DidOpenTextDocument::METHOD - ); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn text_document_did_change(&self, params: &Value) -> Result<()> { - info!( - "Begin {}", - lsp_types::notification::DidChangeTextDocument::METHOD - ); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; if !self.get(|state| state.text_documents.contains_key(&filename))? { @@ -2267,7 +2194,7 @@ impl LanguageClient { return self.text_document_did_open(params); } - let text = self.vim()?.get_text(&filename)?.join("\n"); + let text = self.vim()?.get_text(&filename, params)?.join("\n"); let text_state = self.get(|state| { state .text_documents @@ -2276,7 +2203,6 @@ impl LanguageClient { .unwrap_or_default() })?; if text == text_state { - info!("Texts equal. Skipping didChange."); return Ok(()); } @@ -2317,18 +2243,11 @@ impl LanguageClient { self.text_document_code_lens(params)?; - info!( - "End {}", - lsp_types::notification::DidChangeTextDocument::METHOD - ); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn text_document_did_save(&self, params: &Value) -> Result<()> { - info!( - "Begin {}", - lsp_types::notification::DidSaveTextDocument::METHOD - ); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; if !self.get(|state| state.server_commands.contains_key(&language_id))? { @@ -2347,18 +2266,11 @@ impl LanguageClient { self.draw_virtual_texts(params)?; - info!( - "End {}", - lsp_types::notification::DidSaveTextDocument::METHOD - ); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn text_document_did_close(&self, params: &Value) -> Result<()> { - info!( - "Begin {}", - lsp_types::notification::DidCloseTextDocument::METHOD - ); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; @@ -2370,18 +2282,11 @@ impl LanguageClient { }, }, )?; - info!( - "End {}", - lsp_types::notification::DidCloseTextDocument::METHOD - ); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn text_document_publish_diagnostics(&self, params: &Value) -> Result<()> { - info!( - "Begin {}", - lsp_types::notification::PublishDiagnostics::METHOD - ); let params = PublishDiagnosticsParams::deserialize(params)?; if !self.get(|state| state.diagnostics_enable)? { return Ok(()); @@ -2496,18 +2401,11 @@ impl LanguageClient { .rpcclient .notify("s:ExecuteAutocmd", "LanguageClientDiagnosticsChanged")?; - info!( - "End {}", - lsp_types::notification::PublishDiagnostics::METHOD - ); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn text_document_semantic_highlight(&self, params: &Value) -> Result<()> { - info!( - "Begin {}", - lsp_types::notification::SemanticHighlighting::METHOD - ); let mut params = SemanticHighlightingParams::deserialize(params)?; // TODO: Do we need to handle the versioning of the file? @@ -2714,17 +2612,13 @@ impl LanguageClient { })?; } - info!( - "End {}", - lsp_types::notification::SemanticHighlighting::METHOD - ); Ok(()) } // logs a message to with the specified level to the log file if the threshold is below the // message's level. + #[tracing::instrument(level = "info", skip(self))] pub fn window_log_message(&self, params: &Value) -> Result<()> { - info!("Begin {}", lsp_types::notification::LogMessage::METHOD); let params = LogMessageParams::deserialize(params)?; let threshold = self.get(|state| state.window_log_message_level)?; if params.typ.to_int()? > threshold.to_int()? { @@ -2738,23 +2632,21 @@ impl LanguageClient { MessageType::Log => debug!("{}", params.message), }; - info!("End {}", lsp_types::notification::LogMessage::METHOD); Ok(()) } // shows the given message in vim. + #[tracing::instrument(level = "info", skip(self))] pub fn window_show_message(&self, params: &Value) -> Result<()> { - info!("Begin {}", lsp_types::notification::ShowMessage::METHOD); let params = ShowMessageParams::deserialize(params)?; let msg = format!("[{:?}] {}", params.typ, params.message); self.vim()?.echomsg(&msg)?; - info!("End {}", lsp_types::notification::ShowMessage::METHOD); Ok(()) } // TODO: change this to use the show_acions method + #[tracing::instrument(level = "info", skip(self))] pub fn window_show_message_request(&self, params: &Value) -> Result { - info!("Begin {}", lsp_types::request::ShowMessageRequest::METHOD); let mut v = Value::Null; let msg_params = ShowMessageRequestParams::deserialize(params)?; let msg = format!("[{:?}] {}", msg_params.typ, msg_params.message); @@ -2777,18 +2669,18 @@ impl LanguageClient { } } - info!("End {}", lsp_types::request::ShowMessageRequest::METHOD); Ok(v) } + #[tracing::instrument(level = "info", skip(self))] pub fn client_register_capability(&self, language_id: &str, params: &Value) -> Result { - info!("Begin {}", lsp_types::request::RegisterCapability::METHOD); let params = RegistrationParams::deserialize(params)?; for r in ¶ms.registrations { match r.method.as_str() { lsp_types::notification::DidChangeWatchedFiles::METHOD => { - let opt: DidChangeWatchedFilesRegistrationOptions = - serde_json::from_value(r.register_options.clone().unwrap_or_default())?; + let opt = DidChangeWatchedFilesRegistrationOptions::deserialize( + r.register_options.as_ref().unwrap_or(&Value::Null), + )?; if !self.get(|state| state.watchers.contains_key(language_id))? { let (watcher_tx, watcher_rx) = mpsc::channel(); // TODO: configurable duration. @@ -2837,12 +2729,11 @@ impl LanguageClient { state.registrations.extend(params.registrations); Ok(()) })?; - info!("End {}", lsp_types::request::RegisterCapability::METHOD); Ok(Value::Null) } + #[tracing::instrument(level = "info", skip(self))] pub fn client_unregister_capability(&self, language_id: &str, params: &Value) -> Result { - info!("Begin {}", lsp_types::request::UnregisterCapability::METHOD); let params = UnregistrationParams::deserialize(params)?; let mut regs_removed = vec![]; for r in ¶ms.unregisterations { @@ -2859,8 +2750,9 @@ impl LanguageClient { for r in ®s_removed { match r.method.as_str() { lsp_types::notification::DidChangeWatchedFiles::METHOD => { - let opt: DidChangeWatchedFilesRegistrationOptions = - serde_json::from_value(r.register_options.clone().unwrap_or_default())?; + let opt = DidChangeWatchedFilesRegistrationOptions::deserialize( + r.register_options.as_ref().unwrap_or(&Value::Null), + )?; self.update(|state| { if let Some(ref mut watcher) = state.watchers.get_mut(language_id) { for w in opt.watchers { @@ -2876,12 +2768,11 @@ impl LanguageClient { } } - info!("End {}", lsp_types::request::UnregisterCapability::METHOD); Ok(Value::Null) } + #[tracing::instrument(level = "info", skip(self))] pub fn exit(&self, params: &Value) -> Result<()> { - info!("Begin {}", lsp_types::notification::Exit::METHOD); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; @@ -2899,30 +2790,27 @@ impl LanguageClient { if let Err(err) = self.cleanup(&language_id) { error!("Error: {:?}", err); } - info!("End {}", lsp_types::notification::Exit::METHOD); Ok(()) } /////// Extensions by this plugin /////// + #[tracing::instrument(level = "info", skip(self))] pub fn get_state(&self, _params: &Value) -> Result { - info!("Begin {}", REQUEST_GET_STATE); let s = self.get(|state| serde_json::to_string(state))??; - info!("End {}", REQUEST_GET_STATE); Ok(Value::String(s)) } + #[tracing::instrument(level = "info", skip(self))] pub fn is_alive(&self, params: &Value) -> Result { - info!("Begin {}", REQUEST_IS_ALIVE); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; let is_alive = self.get(|state| state.clients.contains_key(&Some(language_id.clone())))?; - info!("End {}", REQUEST_IS_ALIVE); Ok(Value::Bool(is_alive)) } + #[tracing::instrument(level = "info", skip(self))] pub fn register_server_commands(&self, params: &Value) -> Result { - info!("Begin {}", REQUEST_REGISTER_SERVER_COMMANDS); let commands = HashMap::>::deserialize(params)?; self.update(|state| { state.server_commands.extend(commands); @@ -2933,36 +2821,33 @@ impl LanguageClient { serde_json::to_string(&self.get(|state| state.server_commands.clone())?)? ); self.vim()?.command(&exp)?; - info!("End {}", REQUEST_REGISTER_SERVER_COMMANDS); Ok(Value::Null) } + #[tracing::instrument(level = "info", skip(self))] pub fn set_logging_level(&self, params: &Value) -> Result { - info!("Begin {}", REQUEST_SET_LOGGING_LEVEL); let logging_level = try_get("loggingLevel", params)?.ok_or_else(|| anyhow!("loggingLevel not found!"))?; self.update(|state| { state.logger.set_level(logging_level)?; Ok(()) })?; - info!("End {}", REQUEST_SET_LOGGING_LEVEL); Ok(Value::Null) } + #[tracing::instrument(level = "info", skip(self))] pub fn set_diagnostics_list(&self, params: &Value) -> Result { - info!("Begin {}", REQUEST_SET_DIAGNOSTICS_LIST); let diagnostics_list = try_get("diagnosticsList", params)? .ok_or_else(|| anyhow!("diagnosticsList not found!"))?; self.update(|state| { state.diagnostics_list = diagnostics_list; Ok(()) })?; - info!("End {}", REQUEST_SET_DIAGNOSTICS_LIST); Ok(Value::Null) } + #[tracing::instrument(level = "info", skip(self))] pub fn register_handlers(&self, params: &Value) -> Result { - info!("Begin {}", REQUEST_REGISTER_HANDLERS); let handlers: Result> = params .as_object() .ok_or_else(|| anyhow!("Invalid arguments!"))? @@ -2984,14 +2869,13 @@ impl LanguageClient { state.user_handlers.extend(handlers); Ok(()) })?; - info!("End {}", REQUEST_REGISTER_HANDLERS); Ok(Value::Null) } + #[tracing::instrument(level = "info", skip(self))] pub fn omnicomplete(&self, params: &Value) -> Result { - info!("Begin {}", REQUEST_OMNI_COMPLETE); let result = self.text_document_completion(params)?; - let result: Option = serde_json::from_value(result)?; + let result = >::deserialize(result)?; let result = result.unwrap_or_else(|| CompletionResponse::Array(vec![])); let matches = match result { CompletionResponse::Array(arr) => arr, @@ -3005,12 +2889,11 @@ impl LanguageClient { .map(|item| VimCompleteItem::from_lsp(item, complete_position)) .collect(); let matches = matches?; - info!("End {}", REQUEST_OMNI_COMPLETE); Ok(serde_json::to_value(matches)?) } + #[tracing::instrument(level = "info", skip(self))] pub fn handle_buf_new_file(&self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION_HANDLE_BUF_NEW_FILE); if self.vim()?.get_filename(params)?.is_empty() { return Ok(()); } @@ -3026,12 +2909,11 @@ impl LanguageClient { } } - info!("End {}", NOTIFICATION_HANDLE_BUF_NEW_FILE); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn handle_buf_enter(&self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION_HANDLE_BUF_ENTER); if self.vim()?.get_filename(params)?.is_empty() { return Ok(()); } @@ -3048,12 +2930,11 @@ impl LanguageClient { .rpcclient .notify("setbufvar", json!([filename, VIM_IS_SERVER_RUNNING, 0]))?; } - info!("End {}", NOTIFICATION_HANDLE_BUF_ENTER); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn handle_file_type(&self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION_HANDLE_FILE_TYPE); if self.vim()?.get_filename(params)?.is_empty() { return Ok(()); } @@ -3083,12 +2964,11 @@ impl LanguageClient { } } - info!("End {}", NOTIFICATION_HANDLE_FILE_TYPE); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn handle_text_changed(&self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION_HANDLE_TEXT_CHANGED); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; if !self.get(|state| state.server_commands.contains_key(&language_id))? { @@ -3111,19 +2991,17 @@ impl LanguageClient { } self.text_document_did_change(params)?; - info!("End {}", NOTIFICATION_HANDLE_TEXT_CHANGED); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn handle_buf_write_post(&self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION_HANDLE_BUF_WRITE_POST); self.text_document_did_save(params)?; - info!("End {}", NOTIFICATION_HANDLE_BUF_WRITE_POST); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn handle_buf_delete(&self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION_HANDLE_BUF_WRITE_POST); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; if !self.get(|state| state.server_commands.contains_key(&language_id))? { @@ -3138,12 +3016,11 @@ impl LanguageClient { Ok(()) })?; self.text_document_did_close(params)?; - info!("End {}", NOTIFICATION_HANDLE_BUF_WRITE_POST); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] 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; @@ -3294,7 +3171,6 @@ impl LanguageClient { self.draw_virtual_texts(¶ms)?; - info!("End {}", NOTIFICATION_HANDLE_CURSOR_MOVED); Ok(()) } @@ -3466,8 +3342,8 @@ impl LanguageClient { .cursor(position.line + 1, position.character + 1) } + #[tracing::instrument(level = "info", skip(self))] pub fn fzf_sink_location(&self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION_FZF_SINK_LOCATION); let params = match params { Value::Array(ref arr) => Value::Array(arr.clone()), _ => { @@ -3475,7 +3351,7 @@ impl LanguageClient { } }; - let lines: Vec = serde_json::from_value(params.clone())?; + let lines = >::deserialize(¶ms)?; if lines.is_empty() { anyhow!("No selection!"); } @@ -3511,12 +3387,11 @@ impl LanguageClient { self.edit(&None, &filename)?; self.vim()?.cursor(line + 1, character + 1)?; - info!("End {}", NOTIFICATION_FZF_SINK_LOCATION); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn fzf_sink_command(&self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION_FZF_SINK_COMMAND); let selection: String = try_get("selection", params)?.ok_or_else(|| anyhow!("selection not found!"))?; let tokens: Vec<&str> = selection.splitn(2, ": ").collect(); @@ -3538,7 +3413,6 @@ impl LanguageClient { None => return Err(anyhow!("Action not stashed, please try again")), }; - info!("End {}", NOTIFICATION_FZF_SINK_COMMAND); Ok(()) } @@ -3616,10 +3490,9 @@ impl LanguageClient { } } + #[tracing::instrument(level = "info", skip(self))] pub fn ncm_refresh(&self, params: &Value) -> Result { - info!("Begin {}", REQUEST_NCM_REFRESH); - let params: NCMRefreshParams = - serde_json::from_value(jsonrpc_core::to_value(params.clone())?)?; + let params = NCMRefreshParams::deserialize(params)?; let NCMRefreshParams { info, ctx } = params; if ctx.typed.is_empty() { return Ok(Value::Null); @@ -3636,7 +3509,7 @@ impl LanguageClient { "character": character, "handle": false, }))?; - let result: Option = serde_json::from_value(result)?; + let result = >::deserialize(result)?; let result = result.unwrap_or_else(|| CompletionResponse::Array(vec![])); let is_incomplete = match result { CompletionResponse::Array(_) => false, @@ -3654,17 +3527,13 @@ impl LanguageClient { "cm#complete", json!([info.name, ctx, ctx.startcol, matches, is_incomplete]), )?; - info!("End {}", REQUEST_NCM_REFRESH); Ok(Value::Null) } + #[tracing::instrument(level = "info", skip(self))] pub fn ncm2_on_complete(&self, params: &Value) -> Result { - info!("Begin {}", REQUEST_NCM2_ON_COMPLETE); - - let orig_ctx: Value = serde_json::from_value(jsonrpc_core::to_value(params.clone())?)?; - let orig_ctx = &orig_ctx["ctx"]; - - let ctx: NCM2Context = serde_json::from_value(orig_ctx.clone())?; + let orig_ctx = ¶ms["ctx"]; + let ctx = NCM2Context::deserialize(orig_ctx)?; if ctx.typed.is_empty() { return Ok(Value::Null); } @@ -3682,7 +3551,7 @@ impl LanguageClient { let is_incomplete; let matches; if let Ok(ref value) = result { - let completion = serde_json::from_value(value.clone())?; + let completion = CompletionResponse::deserialize(value)?; is_incomplete = match completion { CompletionResponse::List(ref list) => list.is_incomplete, _ => false, @@ -3703,12 +3572,11 @@ impl LanguageClient { "ncm2#complete", json!([orig_ctx, ctx.startccol, matches, is_incomplete]), )?; - info!("End {}", REQUEST_NCM2_ON_COMPLETE); result } + #[tracing::instrument(level = "info", skip(self))] pub fn explain_error_at_point(&self, params: &Value) -> Result { - info!("Begin {}", REQUEST_EXPLAIN_ERROR_AT_POINT); let filename = self.vim()?.get_filename(params)?; let position = self.vim()?.get_position(params)?; let diag = self.get(|state| { @@ -3763,33 +3631,29 @@ impl LanguageClient { } self.preview(explanation.as_str())?; - - info!("End {}", REQUEST_EXPLAIN_ERROR_AT_POINT); Ok(Value::Null) } // Extensions by language servers. + #[tracing::instrument(level = "info", skip(self))] pub fn language_status(&self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION_LANGUAGE_STATUS); let params = LanguageStatusParams::deserialize(params)?; let msg = format!("{} {}", params.typee, params.message); self.vim()?.echomsg(&msg)?; - info!("End {}", NOTIFICATION_LANGUAGE_STATUS); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn rust_handle_begin_build(&self, _params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION_RUST_BEGIN_BUILD); self.vim()?.command(vec![ format!("let {}=1", VIM_SERVER_STATUS), format!("let {}='Rust: build begin'", VIM_SERVER_STATUS_MESSAGE), ])?; - info!("End {}", NOTIFICATION_RUST_BEGIN_BUILD); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn rust_handle_diagnostics_begin(&self, _params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION_RUST_DIAGNOSTICS_BEGIN); self.vim()?.command(vec![ format!("let {}=1", VIM_SERVER_STATUS), format!( @@ -3797,22 +3661,20 @@ impl LanguageClient { VIM_SERVER_STATUS_MESSAGE ), ])?; - info!("End {}", NOTIFICATION_RUST_DIAGNOSTICS_BEGIN); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn rust_handle_diagnostics_end(&self, _params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION_RUST_DIAGNOSTICS_END); self.vim()?.command(vec![ format!("let {}=0", VIM_SERVER_STATUS), format!("let {}='Rust: diagnostics end'", VIM_SERVER_STATUS_MESSAGE), ])?; - info!("End {}", NOTIFICATION_RUST_DIAGNOSTICS_END); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn window_progress(&self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION_WINDOW_PROGRESS); let params = WindowProgressParams::deserialize(params)?; let done = params.done.unwrap_or(false); @@ -3843,12 +3705,11 @@ impl LanguageClient { &escape_single_quote(buf) ), ])?; - info!("End {}", NOTIFICATION_WINDOW_PROGRESS); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn start_server(&self, params: &Value) -> Result { - info!("Begin {}", REQUEST_START_SERVER); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; let cmdargs: Vec = try_get("cmdargs", params)?.unwrap_or_default(); @@ -3972,12 +3833,20 @@ impl LanguageClient { (child_id, reader, writer) }; + let lcn = self.clone(); + let on_server_crash = move |language_id: &LanguageId| -> () { + if let Err(err) = lcn.on_server_crash(language_id) { + error!("Restart attempt failed: {}", err); + } + }; + let client = RpcClient::new( Some(language_id.clone()), reader, writer, child_id, self.get(|state| state.tx.clone())?, + on_server_crash, )?; self.update(|state| { state @@ -3986,8 +3855,6 @@ impl LanguageClient { Ok(()) })?; - info!("End {}", REQUEST_START_SERVER); - if self.get(|state| state.clients.len())? == 2 { self.define_signs()?; } @@ -4010,7 +3877,6 @@ impl LanguageClient { .notify("setbufvar", json!([filename, VIM_IS_SERVER_RUNNING, 1]))?; self.text_document_did_open(¶ms)?; - self.text_document_did_change(¶ms)?; self.vim()? .rpcclient @@ -4018,6 +3884,55 @@ impl LanguageClient { Ok(Value::Null) } + fn on_server_crash(&self, language_id: &LanguageId) -> Result<()> { + if language_id.is_none() { + return Ok(()); + } + + self.vim()? + .rpcclient + .notify("s:ExecuteAutocmd", "LanguageServerCrashed")?; + let filename = self.vim()?.get_filename(&Value::Null)?; + self.vim()? + .rpcclient + .notify("setbufvar", json!([filename, VIM_IS_SERVER_RUNNING, 0]))?; + + let restart_on_crash = self.get(|state| state.restart_on_crash)?; + if !restart_on_crash { + return Ok(()); + } + + let max_restart_retries = self.get(|state| state.max_restart_retries)?; + let mut restarts = + self.get(|state| state.restarts.get(language_id).cloned().unwrap_or_default())?; + restarts += 1; + + self.update(|state| { + let mut restarts = restarts; + if restarts > max_restart_retries { + restarts = 0; + }; + + state.clients.remove(language_id); + state.restarts.insert(language_id.clone(), restarts); + Ok(()) + })?; + + if restarts > max_restart_retries { + self.vim()?.echoerr(format!( + "Server for {} restarted too many times, not retrying any more.", + language_id.clone().unwrap() + ))?; + return Ok(()); + } + + self.vim()?.echoerr("Server crashed, restarting client")?; + std::thread::sleep(Duration::from_millis(300 * (restarts as u64).pow(2))); + self.start_server(&json!({"languageId": language_id.clone().unwrap()}))?; + + Ok(()) + } + pub fn handle_server_exited(&self, params: &Value) -> Result<()> { let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; @@ -4083,11 +3998,8 @@ impl LanguageClient { Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn workspace_did_change_watched_files(&self, params: &Value) -> Result<()> { - info!( - "Begin {}", - lsp_types::notification::DidChangeWatchedFiles::METHOD - ); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; @@ -4097,15 +4009,11 @@ impl LanguageClient { params, )?; - info!( - "End {}", - lsp_types::notification::DidChangeWatchedFiles::METHOD - ); Ok(()) } + #[tracing::instrument(level = "info", skip(self))] pub fn java_class_file_contents(&self, params: &Value) -> Result { - info!("Begin {}", REQUEST_CLASS_FILE_CONTENTS); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; @@ -4134,12 +4042,11 @@ impl LanguageClient { self.vim()? .command("setlocal buftype=nofile filetype=java noswapfile")?; - info!("End {}", REQUEST_CLASS_FILE_CONTENTS); Ok(Value::String(content)) } + #[tracing::instrument(level = "info", skip(self))] pub fn debug_info(&self, params: &Value) -> Result { - info!("Begin {}", REQUEST_DEBUG_INFO); let filename = self.vim()?.get_filename(params)?; let language_id = self.vim()?.get_language_id(&filename, params)?; let mut msg = String::new(); @@ -4164,7 +4071,6 @@ impl LanguageClient { msg += &format!("Log file: {:?}\n", state.logger.path); })?; self.vim()?.echo(&msg)?; - info!("End {}", REQUEST_DEBUG_INFO); Ok(json!(msg)) } } diff --git a/src/rpcclient.rs b/src/rpcclient.rs index c3a04340..3f5104c0 100644 --- a/src/rpcclient.rs +++ b/src/rpcclient.rs @@ -43,15 +43,24 @@ impl RpcClient { writer: impl Write + Send + 'static, process_id: Option, sink: Sender, + on_crash: impl Fn(&LanguageId) -> () + Clone + Send + 'static, ) -> Result { let (reader_tx, reader_rx): (Sender<(Id, Sender)>, _) = unbounded(); let language_id_clone = language_id.clone(); let reader_thread_name = format!("reader-{:?}", language_id); + let on_crash_clone = on_crash.clone(); thread::Builder::new() .name(reader_thread_name.clone()) .spawn(move || { if let Err(err) = loop_read(reader, reader_rx, &sink, &language_id_clone) { + match err.downcast_ref::() { + Some(err) if err.kind() == std::io::ErrorKind::UnexpectedEof => { + on_crash_clone(&language_id_clone) + } + _ => {} + } + error!("Thread {} exited with error: {:?}", reader_thread_name, err); } })?; @@ -63,6 +72,13 @@ impl RpcClient { .name(writer_thread_name.clone()) .spawn(move || { if let Err(err) = loop_write(writer, &writer_rx, &language_id_clone) { + match err.downcast_ref::() { + Some(err) if err.kind() == std::io::ErrorKind::BrokenPipe => { + on_crash(&language_id_clone) + } + _ => {} + } + error!("Thread {} exited with error: {:?}", writer_thread_name, err); } })?; @@ -188,7 +204,7 @@ fn loop_read( if message.is_empty() { continue; } - info!("<= {:?} {}", language_id, message); + debug!("<= {:?} {}", language_id, message); // FIXME: Remove extra `meta` property from javascript-typescript-langserver and // `requestMethod` sent by Sorbet. let s = RE_REMOVE_EXTRA_FIELDS.replace(message, ""); @@ -235,7 +251,7 @@ fn loop_write( for msg in rx.iter() { let s = serde_json::to_string(&msg)?; - info!("=> {:?} {}", language_id, s); + debug!("=> {:?} {}", language_id, s); if language_id.is_none() { // Use different convention for two reasons, // 1. If using '\r\ncontent', nvim will receive output as `\r` + `content`, while vim diff --git a/src/types.rs b/src/types.rs index 7b15aea8..7cff2160 100644 --- a/src/types.rs +++ b/src/types.rs @@ -142,6 +142,7 @@ pub struct State { #[serde(skip_serializing)] pub clients: HashMap>, + pub restarts: HashMap, #[serde(skip_serializing)] pub vim: Vim, @@ -215,6 +216,8 @@ pub struct State { pub server_stderr: Option, pub logger: Logger, pub preferred_markup_kind: Option>, + pub restart_on_crash: bool, + pub max_restart_retries: u8, } impl State { @@ -228,6 +231,7 @@ impl State { BufWriter::new(std::io::stdout()), None, tx.clone(), + |_: &LanguageId| {}, )?); Ok(Self { @@ -236,6 +240,7 @@ impl State { clients: hashmap! { None => client.clone(), }, + restarts: HashMap::new(), vim: Vim::new(client), @@ -296,6 +301,8 @@ impl State { preferred_markup_kind: None, enable_extensions: None, code_lens_hl_group: "Comment".into(), + restart_on_crash: true, + max_restart_retries: 5, logger, }) diff --git a/src/vim.rs b/src/vim.rs index 1c054a12..248bdf44 100644 --- a/src/vim.rs +++ b/src/vim.rs @@ -154,8 +154,9 @@ impl Vim { Ok(insert_spaces == 1) } - pub fn get_text(&self, bufname: &str) -> Result> { - self.rpcclient.call("LSP#text", json!([bufname])) + pub fn get_text(&self, bufname: &str, params: &Value) -> Result> { + try_get("text", params)? + .map_or_else(|| self.rpcclient.call("LSP#text", json!([bufname])), Ok) } pub fn get_handle(&self, params: &Value) -> Result {