diff options
author | Norbert Preining <norbert@preining.info> | 2022-07-04 03:03:43 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2022-07-04 03:03:43 +0000 |
commit | 1c10375ec46d7d83b2f1efc2a71b7ea114c889f0 (patch) | |
tree | 47b3469111105b7767111dcb89858fbc1e73377f /support/texlab/src | |
parent | 34d318af65decbdb242ae03b64bf3f53266067b6 (diff) |
CTAN sync 202207040303
Diffstat (limited to 'support/texlab/src')
-rw-r--r-- | support/texlab/src/debouncer.rs | 55 | ||||
-rw-r--r-- | support/texlab/src/diagnostics.rs | 177 | ||||
-rw-r--r-- | support/texlab/src/diagnostics/bibtex.rs | 147 | ||||
-rw-r--r-- | support/texlab/src/diagnostics/build.rs (renamed from support/texlab/src/diagnostics/build_log.rs) | 44 | ||||
-rw-r--r-- | support/texlab/src/diagnostics/chktex.rs | 43 | ||||
-rw-r--r-- | support/texlab/src/diagnostics/debouncer.rs | 75 | ||||
-rw-r--r-- | support/texlab/src/diagnostics/latex.rs | 90 | ||||
-rw-r--r-- | support/texlab/src/features/build.rs | 8 | ||||
-rw-r--r-- | support/texlab/src/features/forward_search.rs | 74 | ||||
-rw-r--r-- | support/texlab/src/lib.rs | 3 | ||||
-rw-r--r-- | support/texlab/src/main.rs | 6 | ||||
-rw-r--r-- | support/texlab/src/options.rs | 80 | ||||
-rw-r--r-- | support/texlab/src/server.rs | 192 |
13 files changed, 575 insertions, 419 deletions
diff --git a/support/texlab/src/debouncer.rs b/support/texlab/src/debouncer.rs new file mode 100644 index 0000000000..b123dfc433 --- /dev/null +++ b/support/texlab/src/debouncer.rs @@ -0,0 +1,55 @@ +use std::time::{Duration, Instant}; + +use anyhow::Result; + +pub struct Sender<T> { + tx: crossbeam_channel::Sender<(T, crossbeam_channel::Receiver<Instant>)>, +} + +impl<T> Clone for Sender<T> { + fn clone(&self) -> Self { + Self { + tx: self.tx.clone(), + } + } +} + +impl<T> Sender<T> +where + T: Send + Sync + 'static, +{ + pub fn send(&self, msg: T, delay: Duration) -> Result<()> { + self.tx.send((msg, crossbeam_channel::after(delay)))?; + Ok(()) + } +} + +pub struct Receiver<T> { + rx: crossbeam_channel::Receiver<(T, crossbeam_channel::Receiver<Instant>)>, +} + +impl<T> Clone for Receiver<T> { + fn clone(&self) -> Self { + Self { + rx: self.rx.clone(), + } + } +} + +impl<T> Receiver<T> { + pub fn recv(&self) -> Result<T> { + let (mut last_msg, delay) = self.rx.recv()?; + delay.recv()?; + while let Ok((msg, delay)) = self.rx.try_recv() { + delay.recv()?; + last_msg = msg; + } + + Ok(last_msg) + } +} + +pub fn unbounded<T>() -> (Sender<T>, Receiver<T>) { + let (tx, rx) = crossbeam_channel::unbounded(); + (Sender { tx }, Receiver { rx }) +} diff --git a/support/texlab/src/diagnostics.rs b/support/texlab/src/diagnostics.rs index 5ce177b015..bdd8faf106 100644 --- a/support/texlab/src/diagnostics.rs +++ b/support/texlab/src/diagnostics.rs @@ -1,56 +1,167 @@ mod bibtex; -mod build_log; +mod build; mod chktex; -mod debouncer; mod latex; use std::sync::Arc; -use lsp_types::{Diagnostic, Url}; -use multimap::MultiMap; -use rustc_hash::FxHashMap; +use dashmap::DashMap; +use lsp_types::{DiagnosticSeverity, NumberOrString, Range, Url}; +use regex::Regex; -use crate::{Options, Workspace}; - -pub use self::debouncer::{DiagnosticsDebouncer, DiagnosticsMessage}; +use crate::Workspace; use self::{ - bibtex::analyze_bibtex_static, build_log::analyze_build_log_static, - chktex::analyze_latex_chktex, latex::analyze_latex_static, + bibtex::collect_bibtex_diagnostics, build::collect_build_diagnostics, + chktex::collect_chktex_diagnostics, latex::collect_latex_diagnostics, }; -#[derive(Default)] -pub struct DiagnosticsManager { - static_diagnostics: FxHashMap<Arc<Url>, MultiMap<Arc<Url>, Diagnostic>>, - chktex_diagnostics: MultiMap<Arc<Url>, Diagnostic>, +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Diagnostic { + pub severity: DiagnosticSeverity, + pub range: Range, + pub code: DiagnosticCode, + pub message: String, +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)] +pub enum DiagnosticCode { + Latex(LatexCode), + Bibtex(BibtexCode), + Chktex(String), + Build(Arc<Url>), +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] +pub enum LatexCode { + UnexpectedRCurly, + RCurlyInserted, + MismatchedEnvironment, +} + +impl From<LatexCode> for String { + fn from(code: LatexCode) -> Self { + match code { + LatexCode::UnexpectedRCurly => "Unexpected \"}\"".to_string(), + LatexCode::RCurlyInserted => "Missing \"}\" inserted".to_string(), + LatexCode::MismatchedEnvironment => "Mismatched environment".to_string(), + } + } } -impl DiagnosticsManager { - pub fn update_static(&mut self, workspace: &Workspace, uri: Arc<Url>) { - let mut diagnostics_by_uri = MultiMap::new(); - analyze_build_log_static(workspace, &mut diagnostics_by_uri, &uri); - analyze_bibtex_static(workspace, &mut diagnostics_by_uri, &uri); - analyze_latex_static(workspace, &mut diagnostics_by_uri, &uri); - self.static_diagnostics.insert(uri, diagnostics_by_uri); +impl From<LatexCode> for NumberOrString { + fn from(code: LatexCode) -> Self { + match code { + LatexCode::UnexpectedRCurly => NumberOrString::Number(1), + LatexCode::RCurlyInserted => NumberOrString::Number(2), + LatexCode::MismatchedEnvironment => NumberOrString::Number(3), + } } +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] +#[allow(clippy::enum_variant_names)] +pub enum BibtexCode { + ExpectingLCurly, + ExpectingKey, + ExpectingRCurly, + ExpectingEq, + ExpectingFieldValue, +} - pub fn update_chktex(&mut self, workspace: &Workspace, uri: &Url, options: &Options) { - analyze_latex_chktex(workspace, &mut self.chktex_diagnostics, uri, options); +impl From<BibtexCode> for String { + fn from(code: BibtexCode) -> Self { + match code { + BibtexCode::ExpectingLCurly => "Expecting a curly bracket: \"{\"".to_string(), + BibtexCode::ExpectingKey => "Expecting a key".to_string(), + BibtexCode::ExpectingRCurly => "Expecting a curly bracket: \"}\"".to_string(), + BibtexCode::ExpectingEq => "Expecting an equality sign: \"=\"".to_string(), + BibtexCode::ExpectingFieldValue => "Expecting a field value".to_string(), + } } +} - #[must_use] - pub fn publish(&self, uri: &Url) -> Vec<Diagnostic> { - let mut all_diagnostics = Vec::new(); - for diagnostics_by_uri in self.static_diagnostics.values() { - if let Some(diagnostics) = diagnostics_by_uri.get_vec(uri) { - all_diagnostics.append(&mut diagnostics.clone()); - } +impl From<BibtexCode> for NumberOrString { + fn from(code: BibtexCode) -> Self { + match code { + BibtexCode::ExpectingLCurly => NumberOrString::Number(4), + BibtexCode::ExpectingKey => NumberOrString::Number(5), + BibtexCode::ExpectingRCurly => NumberOrString::Number(6), + BibtexCode::ExpectingEq => NumberOrString::Number(7), + BibtexCode::ExpectingFieldValue => NumberOrString::Number(8), } + } +} + +#[derive(Default, Clone)] +pub struct DiagnosticManager { + all_diagnostics: Arc<DashMap<Arc<Url>, Vec<Diagnostic>>>, +} + +impl DiagnosticManager { + pub fn push_syntax(&self, workspace: &Workspace, uri: &Url) { + collect_bibtex_diagnostics(&self.all_diagnostics, workspace, uri) + .or_else(|| collect_latex_diagnostics(&self.all_diagnostics, workspace, uri)) + .or_else(|| collect_build_diagnostics(&self.all_diagnostics, workspace, uri)); + } + + pub fn push_chktex(&self, workspace: &Workspace, uri: &Url) { + collect_chktex_diagnostics(&self.all_diagnostics, workspace, uri); + } - if let Some(diagnostics) = self.chktex_diagnostics.get_vec(uri) { - all_diagnostics.append(&mut diagnostics.clone()); + pub fn publish(&self, workspace: &Workspace, uri: &Url) -> Vec<lsp_types::Diagnostic> { + let options = &workspace.environment.options.diagnostics; + + let mut results = Vec::new(); + if let Some(diagnostics) = self.all_diagnostics.get(uri) { + for diagnostic in diagnostics.iter() { + if !options.allowed_patterns.is_empty() + && !options + .allowed_patterns + .iter() + .any(|pattern| pattern.0.is_match(&diagnostic.message)) + { + continue; + } + + if options + .ignored_patterns + .iter() + .any(|pattern| pattern.0.is_match(&diagnostic.message)) + { + continue; + } + + let source = match diagnostic.code { + DiagnosticCode::Latex(_) | DiagnosticCode::Bibtex(_) => "texlab", + DiagnosticCode::Chktex(_) => "chktex", + DiagnosticCode::Build(_) => "latex-build", + }; + + let code = match diagnostic.code.clone() { + DiagnosticCode::Latex(code) => Some(code.into()), + DiagnosticCode::Bibtex(code) => Some(code.into()), + DiagnosticCode::Chktex(code) => Some(NumberOrString::String(code)), + DiagnosticCode::Build(_) => None, + }; + + results.push(lsp_types::Diagnostic { + range: diagnostic.range, + code, + severity: Some(diagnostic.severity), + message: diagnostic.message.clone(), + source: Some(source.to_string()), + ..Default::default() + }); + } } - all_diagnostics + results } } + +#[derive(Debug, Default)] +pub struct DiagnosticFilter { + pub allowed_patterns: Vec<Regex>, + pub ignored_patterns: Vec<Regex>, +} diff --git a/support/texlab/src/diagnostics/bibtex.rs b/support/texlab/src/diagnostics/bibtex.rs index 1beec52c5c..42e215a794 100644 --- a/support/texlab/src/diagnostics/bibtex.rs +++ b/support/texlab/src/diagnostics/bibtex.rs @@ -1,7 +1,7 @@ use std::sync::Arc; -use lsp_types::{Diagnostic, DiagnosticSeverity, NumberOrString, Url}; -use multimap::MultiMap; +use dashmap::DashMap; +use lsp_types::{DiagnosticSeverity, Url}; use rowan::{ast::AstNode, TextRange}; use crate::{ @@ -9,85 +9,84 @@ use crate::{ Document, LineIndexExt, Workspace, }; -pub fn analyze_bibtex_static( +use super::{BibtexCode, Diagnostic, DiagnosticCode}; + +pub fn collect_bibtex_diagnostics( + all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>, workspace: &Workspace, - diagnostics_by_uri: &mut MultiMap<Arc<Url>, Diagnostic>, uri: &Url, ) -> Option<()> { let document = workspace.documents_by_uri.get(uri)?; let data = document.data.as_bibtex()?; - for node in bibtex::SyntaxNode::new_root(data.green.clone()).descendants() { - analyze_entry(document, diagnostics_by_uri, node.clone()) - .or_else(|| analyze_field(document, diagnostics_by_uri, node)); + all_diagnostics.alter(uri, |_, mut diagnostics| { + diagnostics.retain(|diag| !matches!(diag.code, DiagnosticCode::Bibtex(_))); + diagnostics + }); + + let root = bibtex::SyntaxNode::new_root(data.green.clone()); + for node in root.descendants() { + analyze_entry(all_diagnostics, document, node.clone()) + .or_else(|| analyze_field(all_diagnostics, document, node)); } Some(()) } fn analyze_entry( + all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>, document: &Document, - diagnostics_by_uri: &mut MultiMap<Arc<Url>, Diagnostic>, node: bibtex::SyntaxNode, ) -> Option<()> { let entry = bibtex::Entry::cast(node)?; if entry.left_delim_token().is_none() { - diagnostics_by_uri.insert( - Arc::clone(&document.uri), - Diagnostic { + let code = BibtexCode::ExpectingLCurly; + all_diagnostics + .entry(Arc::clone(&document.uri)) + .or_default() + .push(Diagnostic { + severity: DiagnosticSeverity::ERROR, range: document .line_index .line_col_lsp_range(entry.type_token()?.text_range()), - severity: Some(DiagnosticSeverity::ERROR), - code: Some(NumberOrString::Number(4)), - code_description: None, - source: Some("texlab".to_string()), - message: "Expecting a curly bracket: \"{\"".to_string(), - related_information: None, - tags: None, - data: None, - }, - ); + code: DiagnosticCode::Bibtex(code), + message: String::from(code), + }); + return Some(()); } if entry.name_token().is_none() { - diagnostics_by_uri.insert( - Arc::clone(&document.uri), - Diagnostic { + let code = BibtexCode::ExpectingKey; + all_diagnostics + .entry(Arc::clone(&document.uri)) + .or_default() + .push(Diagnostic { + severity: DiagnosticSeverity::ERROR, range: document .line_index .line_col_lsp_range(entry.left_delim_token()?.text_range()), - severity: Some(DiagnosticSeverity::ERROR), - code: Some(NumberOrString::Number(5)), - code_description: None, - source: Some("texlab".to_string()), - message: "Expecting a key".to_string(), - related_information: None, - tags: None, - data: None, - }, - ); + code: DiagnosticCode::Bibtex(code), + message: String::from(code), + }); + return Some(()); } if entry.right_delim_token().is_none() { - diagnostics_by_uri.insert( - Arc::clone(&document.uri), - Diagnostic { + let code = BibtexCode::ExpectingRCurly; + all_diagnostics + .entry(Arc::clone(&document.uri)) + .or_default() + .push(Diagnostic { + severity: DiagnosticSeverity::ERROR, range: document .line_index .line_col_lsp_range(TextRange::empty(entry.syntax().text_range().end())), - severity: Some(DiagnosticSeverity::ERROR), - code: Some(NumberOrString::Number(6)), - code_description: None, - source: Some("texlab".to_string()), - message: "Expecting a curly bracket: \"}\"".to_string(), - related_information: None, - tags: None, - data: None, - }, - ); + code: DiagnosticCode::Bibtex(code), + message: String::from(code), + }); + return Some(()); } @@ -95,48 +94,44 @@ fn analyze_entry( } fn analyze_field( + all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>, document: &Document, - diagnostics_by_uri: &mut MultiMap<Arc<Url>, Diagnostic>, node: bibtex::SyntaxNode, ) -> Option<()> { let field = bibtex::Field::cast(node)?; if field.eq_token().is_none() { - diagnostics_by_uri.insert( - Arc::clone(&document.uri), - Diagnostic { + let code = BibtexCode::ExpectingEq; + all_diagnostics + .entry(Arc::clone(&document.uri)) + .or_default() + .push(Diagnostic { + severity: DiagnosticSeverity::ERROR, range: document .line_index - .line_col_lsp_range(TextRange::empty(field.name_token()?.text_range().end())), - severity: Some(DiagnosticSeverity::ERROR), - code: Some(NumberOrString::Number(7)), - code_description: None, - source: Some("texlab".to_string()), - message: "Expecting an equality sign: \"=\"".to_string(), - related_information: None, - tags: None, - data: None, - }, - ); + .line_col_lsp_range(field.name_token()?.text_range()), + + code: DiagnosticCode::Bibtex(code), + message: String::from(code), + }); + return Some(()); } if field.value().is_none() { - diagnostics_by_uri.insert( - Arc::clone(&document.uri), - Diagnostic { + let code = BibtexCode::ExpectingFieldValue; + all_diagnostics + .entry(Arc::clone(&document.uri)) + .or_default() + .push(Diagnostic { + severity: DiagnosticSeverity::ERROR, range: document .line_index - .line_col_lsp_range(TextRange::empty(field.eq_token()?.text_range().end())), - severity: Some(DiagnosticSeverity::ERROR), - code: Some(NumberOrString::Number(8)), - code_description: None, - source: Some("texlab".to_string()), - message: "Expecting a field value".to_string(), - related_information: None, - tags: None, - data: None, - }, - ); + .line_col_lsp_range(field.name_token()?.text_range()), + + code: DiagnosticCode::Bibtex(code), + message: String::from(code), + }); + return Some(()); } diff --git a/support/texlab/src/diagnostics/build_log.rs b/support/texlab/src/diagnostics/build.rs index e280d862bc..4b4ffcc8b4 100644 --- a/support/texlab/src/diagnostics/build_log.rs +++ b/support/texlab/src/diagnostics/build.rs @@ -1,20 +1,29 @@ use std::{path::PathBuf, sync::Arc}; -use lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range, Url}; -use multimap::MultiMap; +use dashmap::DashMap; +use lsp_types::{DiagnosticSeverity, Position, Range, Url}; use crate::{syntax::build_log::BuildErrorLevel, Workspace}; -pub fn analyze_build_log_static( +use super::{Diagnostic, DiagnosticCode}; + +pub fn collect_build_diagnostics( + all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>, workspace: &Workspace, - diagnostics_by_uri: &mut MultiMap<Arc<Url>, Diagnostic>, build_log_uri: &Url, ) -> Option<()> { let build_log_document = workspace.documents_by_uri.get(build_log_uri)?; - let parse = build_log_document.data.as_build_log()?; + let build_log = build_log_document.data.as_build_log()?; + + all_diagnostics.alter_all(|_, mut diagnostics| { + diagnostics.retain( + |diag| !matches!(&diag.code, DiagnosticCode::Build(uri) if uri.as_ref() == build_log_uri), + ); + diagnostics + }); let root_document = workspace.documents_by_uri.values().find(|document| { - if let Some(data) = document.data.as_latex() { + document.data.as_latex().map_or(false, |data| { !document.uri.as_str().ends_with(".aux") && data .extras @@ -22,30 +31,22 @@ pub fn analyze_build_log_static( .log .iter() .any(|u| u.as_ref() == build_log_uri) - } else { - false - } + }) })?; let base_path = PathBuf::from(root_document.uri.path()); - - for error in &parse.errors { - let pos = Position::new(error.line.unwrap_or(0), 0); + for error in &build_log.errors { + let position = Position::new(error.line.unwrap_or(0), 0); let severity = match error.level { BuildErrorLevel::Error => DiagnosticSeverity::ERROR, BuildErrorLevel::Warning => DiagnosticSeverity::WARNING, }; - let range = Range::new(pos, pos); + let range = Range::new(position, position); let diagnostic = Diagnostic { + severity, range, - severity: Some(severity), - code: None, - code_description: None, - source: Some("latex".into()), + code: DiagnosticCode::Build(Arc::clone(&build_log_document.uri)), message: error.message.clone(), - related_information: None, - tags: None, - data: None, }; let full_path = base_path.join(&error.relative_path); @@ -60,7 +61,8 @@ pub fn analyze_build_log_static( Arc::clone(&root_document.uri) }; - diagnostics_by_uri.insert(uri, diagnostic); + all_diagnostics.entry(uri).or_default().push(diagnostic); } + Some(()) } diff --git a/support/texlab/src/diagnostics/chktex.rs b/support/texlab/src/diagnostics/chktex.rs index 6b6405d975..ad676bd70d 100644 --- a/support/texlab/src/diagnostics/chktex.rs +++ b/support/texlab/src/diagnostics/chktex.rs @@ -5,24 +5,32 @@ use std::{ sync::Arc, }; -use lsp_types::{Diagnostic, DiagnosticSeverity, NumberOrString, Range, Url}; -use multimap::MultiMap; +use dashmap::DashMap; +use lsp_types::{DiagnosticSeverity, Range, Url}; use once_cell::sync::Lazy; use regex::Regex; use tempfile::tempdir; -use crate::{Options, RangeExt, Workspace}; +use crate::{RangeExt, Workspace}; -pub fn analyze_latex_chktex( +use super::{Diagnostic, DiagnosticCode}; + +pub fn collect_chktex_diagnostics( + all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>, workspace: &Workspace, - diagnostics_by_uri: &mut MultiMap<Arc<Url>, Diagnostic>, uri: &Url, - options: &Options, ) -> Option<()> { let document = workspace.documents_by_uri.get(uri)?; document.data.as_latex()?; - let current_dir = options + all_diagnostics.alter(uri, |_, mut diagnostics| { + diagnostics.retain(|diag| !matches!(diag.code, DiagnosticCode::Chktex(_))); + diagnostics + }); + + let current_dir = workspace + .environment + .options .root_directory .as_ref() .cloned() @@ -40,15 +48,15 @@ pub fn analyze_latex_chktex( }) .unwrap_or_else(|| ".".into()); - diagnostics_by_uri.remove(uri); - diagnostics_by_uri.insert_many( - Arc::clone(&document.uri), - lint(&document.text, ¤t_dir).unwrap_or_default(), - ); + all_diagnostics + .entry(Arc::clone(&document.uri)) + .or_default() + .extend(lint(&document.text, ¤t_dir).unwrap_or_default()); + Some(()) } -pub static LINE_REGEX: Lazy<Regex> = +static LINE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("(\\d+):(\\d+):(\\d+):(\\w+):(\\w+):(.*)").unwrap()); fn lint(text: &str, current_dir: &Path) -> io::Result<Vec<Diagnostic>> { @@ -85,14 +93,9 @@ fn lint(text: &str, current_dir: &Path) -> io::Result<Vec<Diagnostic>> { diagnostics.push(Diagnostic { range, - severity: Some(severity), - code: Some(NumberOrString::String(code.into())), - code_description: None, - source: Some("chktex".into()), + severity, + code: DiagnosticCode::Chktex(code.into()), message, - related_information: None, - tags: None, - data: None, }); } diff --git a/support/texlab/src/diagnostics/debouncer.rs b/support/texlab/src/diagnostics/debouncer.rs deleted file mode 100644 index 4c83636856..0000000000 --- a/support/texlab/src/diagnostics/debouncer.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::{ - sync::Arc, - thread::{self, JoinHandle}, - time::{Duration, Instant}, -}; - -use crossbeam_channel::Sender; -use dashmap::DashMap; -use lsp_types::Url; - -use crate::{Document, Workspace}; - -pub enum DiagnosticsMessage { - Analyze { - workspace: Workspace, - document: Document, - }, - Shutdown, -} - -pub struct DiagnosticsDebouncer { - pub sender: Sender<DiagnosticsMessage>, - handle: Option<JoinHandle<()>>, -} - -impl DiagnosticsDebouncer { - pub fn launch<A>(action: A) -> Self - where - A: Fn(Workspace, Document) + Send + Clone + 'static, - { - let (sender, receiver) = crossbeam_channel::unbounded(); - - let handle = thread::spawn(move || { - let pool = threadpool::Builder::new().build(); - let last_task_time_by_uri: Arc<DashMap<Arc<Url>, Instant>> = Arc::default(); - while let Ok(DiagnosticsMessage::Analyze { - workspace, - document, - }) = receiver.recv() - { - let delay = workspace - .environment - .options - .diagnostics_delay - .unwrap_or(300); - - if let Some(time) = last_task_time_by_uri.get(&document.uri) { - if time.elapsed().as_millis() < u128::from(delay) { - continue; - } - } - - let last_task_time_by_uri = Arc::clone(&last_task_time_by_uri); - let action = action.clone(); - pool.execute(move || { - thread::sleep(Duration::from_millis(delay)); - last_task_time_by_uri.insert(Arc::clone(&document.uri), Instant::now()); - action(workspace, document); - }); - } - }); - - Self { - sender, - handle: Some(handle), - } - } -} - -impl Drop for DiagnosticsDebouncer { - fn drop(&mut self) { - self.sender.send(DiagnosticsMessage::Shutdown).unwrap(); - self.handle.take().unwrap().join().unwrap(); - } -} diff --git a/support/texlab/src/diagnostics/latex.rs b/support/texlab/src/diagnostics/latex.rs index 2c271ad54a..a17aab3f77 100644 --- a/support/texlab/src/diagnostics/latex.rs +++ b/support/texlab/src/diagnostics/latex.rs @@ -1,14 +1,16 @@ use std::sync::Arc; -use lsp_types::{Diagnostic, DiagnosticSeverity, NumberOrString, Url}; -use multimap::MultiMap; +use dashmap::DashMap; +use lsp_types::{DiagnosticSeverity, Url}; use rowan::{ast::AstNode, NodeOrToken, TextRange}; use crate::{syntax::latex, Document, LineIndexExt, Workspace}; -pub fn analyze_latex_static( +use super::{Diagnostic, DiagnosticCode, LatexCode}; + +pub fn collect_latex_diagnostics( + all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>, workspace: &Workspace, - diagnostics_by_uri: &mut MultiMap<Arc<Url>, Diagnostic>, uri: &Url, ) -> Option<()> { let document = workspace.documents_by_uri.get(uri)?; @@ -18,25 +20,27 @@ pub fn analyze_latex_static( let data = document.data.as_latex()?; + all_diagnostics.alter(uri, |_, mut diagnostics| { + diagnostics.retain(|diag| !matches!(diag.code, DiagnosticCode::Latex(_))); + diagnostics + }); + for node in latex::SyntaxNode::new_root(data.green.clone()).descendants() { - analyze_environment(document, diagnostics_by_uri, node.clone()) - .or_else(|| analyze_curly_group(document, diagnostics_by_uri, &node)) + analyze_environment(all_diagnostics, document, node.clone()) + .or_else(|| analyze_curly_group(all_diagnostics, document, &node)) .or_else(|| { if node.kind() == latex::ERROR && node.first_token()?.text() == "}" { - diagnostics_by_uri.insert( - Arc::clone(&document.uri), - Diagnostic { + let code = LatexCode::UnexpectedRCurly; + all_diagnostics + .entry(Arc::clone(&document.uri)) + .or_default() + .push(Diagnostic { + severity: DiagnosticSeverity::ERROR, range: document.line_index.line_col_lsp_range(node.text_range()), - severity: Some(DiagnosticSeverity::ERROR), - code: Some(NumberOrString::Number(1)), - code_description: None, - source: Some("texlab".to_string()), - message: "Unexpected \"}\"".to_string(), - related_information: None, - tags: None, - data: None, - }, - ); + code: DiagnosticCode::Latex(code), + message: String::from(code), + }); + Some(()) } else { None @@ -48,37 +52,33 @@ pub fn analyze_latex_static( } fn analyze_environment( + all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>, document: &Document, - diagnostics_by_uri: &mut MultiMap<Arc<Url>, Diagnostic>, node: latex::SyntaxNode, ) -> Option<()> { let environment = latex::Environment::cast(node)?; let name1 = environment.begin()?.name()?.key()?; let name2 = environment.end()?.name()?.key()?; if name1 != name2 { - diagnostics_by_uri.insert( - Arc::clone(&document.uri), - Diagnostic { + let code = LatexCode::MismatchedEnvironment; + all_diagnostics + .entry(Arc::clone(&document.uri)) + .or_default() + .push(Diagnostic { + severity: DiagnosticSeverity::ERROR, range: document .line_index .line_col_lsp_range(latex::small_range(&name1)), - severity: Some(DiagnosticSeverity::ERROR), - code: Some(NumberOrString::Number(3)), - code_description: None, - source: Some("texlab".to_string()), - message: "Mismatched environment".to_string(), - related_information: None, - tags: None, - data: None, - }, - ); + code: DiagnosticCode::Latex(code), + message: String::from(code), + }); } Some(()) } fn analyze_curly_group( + all_diagnostics: &DashMap<Arc<Url>, Vec<Diagnostic>>, document: &Document, - diagnostics_by_uri: &mut MultiMap<Arc<Url>, Diagnostic>, node: &latex::SyntaxNode, ) -> Option<()> { if !matches!( @@ -108,22 +108,18 @@ fn analyze_curly_group( .filter_map(NodeOrToken::into_token) .any(|token| token.kind() == latex::R_CURLY) { - diagnostics_by_uri.insert( - Arc::clone(&document.uri), - Diagnostic { + let code = LatexCode::RCurlyInserted; + all_diagnostics + .entry(Arc::clone(&document.uri)) + .or_default() + .push(Diagnostic { + severity: DiagnosticSeverity::ERROR, range: document .line_index .line_col_lsp_range(TextRange::empty(node.text_range().end())), - severity: Some(DiagnosticSeverity::ERROR), - code: Some(NumberOrString::Number(2)), - code_description: None, - source: Some("texlab".to_string()), - message: "Missing \"}\" inserted".to_string(), - related_information: None, - tags: None, - data: None, - }, - ); + code: DiagnosticCode::Latex(code), + message: String::from(code), + }); } Some(()) diff --git a/support/texlab/src/features/build.rs b/support/texlab/src/features/build.rs index ae465590fc..d961052061 100644 --- a/support/texlab/src/features/build.rs +++ b/support/texlab/src/features/build.rs @@ -164,12 +164,12 @@ impl BuildEngine { let args: Vec<_> = options .build - .args() - .into_iter() - .map(|arg| replace_placeholder(arg, &path)) + .args + .iter() + .map(|arg| replace_placeholder(arg.clone(), &path)) .collect(); - let mut process = Command::new(options.build.executable()) + let mut process = Command::new(&options.build.executable) .args(args) .stdin(Stdio::null()) .stdout(Stdio::piped()) diff --git a/support/texlab/src/features/forward_search.rs b/support/texlab/src/features/forward_search.rs index 521cc31a59..54629d0dc0 100644 --- a/support/texlab/src/features/forward_search.rs +++ b/support/texlab/src/features/forward_search.rs @@ -86,6 +86,73 @@ pub fn execute_forward_search( Some(ForwardSearchResult { status }) } +/// Iterate overs chunks of a string. Either returns a slice of the +/// original string, or the placeholder replacement. +pub struct PlaceHolderIterator<'a> { + remainder: &'a str, + tex_file: &'a str, + pdf_file: &'a str, + line_number: &'a str, +} + +impl<'a> PlaceHolderIterator<'a> { + pub fn new(s: &'a str, tex_file: &'a str, pdf_file: &'a str, line_number: &'a str) -> Self { + Self { + remainder: s, + tex_file, + pdf_file, + line_number, + } + } + + pub fn yield_remainder(&mut self) -> Option<&'a str> { + let chunk = self.remainder; + self.remainder = ""; + Some(chunk) + } + + pub fn yield_placeholder(&mut self) -> Option<&'a str> { + if self.remainder.len() >= 2 { + let placeholder = self.remainder; + self.remainder = &self.remainder[2..]; + match &placeholder[1..2] { + "f" => Some(self.tex_file), + "p" => Some(self.pdf_file), + "l" => Some(self.line_number), + "%" => Some("%"), // escape % + _ => Some(&placeholder[0..2]), + } + } else { + self.remainder = &self.remainder[1..]; + Some("%") + } + } + + pub fn yield_str(&mut self, end: usize) -> Option<&'a str> { + let chunk = &self.remainder[..end]; + self.remainder = &self.remainder[end..]; + Some(chunk) + } +} + +impl<'a> Iterator for PlaceHolderIterator<'a> { + type Item = &'a str; + + fn next(&mut self) -> Option<Self::Item> { + return if self.remainder.is_empty() { + None + } else if self.remainder.starts_with('%') { + self.yield_placeholder() + } else { + // yield up to the next % or to the end + match self.remainder.find('%') { + None => self.yield_remainder(), + Some(end) => self.yield_str(end), + } + }; + } +} + fn replace_placeholder( tex_file: &Path, pdf_file: &Path, @@ -95,10 +162,9 @@ fn replace_placeholder( let result = if argument.starts_with('"') || argument.ends_with('"') { argument.to_string() } else { - argument - .replace("%f", tex_file.to_str()?) - .replace("%p", pdf_file.to_str()?) - .replace("%l", &(line_number + 1).to_string()) + let line = &(line_number + 1).to_string(); + let it = PlaceHolderIterator::new(argument, tex_file.to_str()?, pdf_file.to_str()?, line); + it.collect::<Vec<&str>>().join("") }; Some(result) } diff --git a/support/texlab/src/lib.rs b/support/texlab/src/lib.rs index ce52bee1da..1f3d22d7aa 100644 --- a/support/texlab/src/lib.rs +++ b/support/texlab/src/lib.rs @@ -2,7 +2,8 @@ mod capabilities; pub mod citation; mod client; pub mod component_db; -pub mod diagnostics; +mod debouncer; +mod diagnostics; mod dispatch; pub mod distro; mod document; diff --git a/support/texlab/src/main.rs b/support/texlab/src/main.rs index 71338f1bcd..06cbba3f5d 100644 --- a/support/texlab/src/main.rs +++ b/support/texlab/src/main.rs @@ -1,7 +1,7 @@ use std::{env, fs::OpenOptions, io, path::PathBuf}; use anyhow::Result; -use clap::Parser; +use clap::{ArgAction, Parser}; use log::LevelFilter; use lsp_server::Connection; use texlab::Server; @@ -11,7 +11,7 @@ use texlab::Server; #[clap(version)] struct Opts { /// Increase message verbosity (-vvvv for max verbosity) - #[clap(short, long, parse(from_occurrences))] + #[clap(short, long, action = ArgAction::Count)] verbosity: u8, /// No output printed to stderr @@ -19,7 +19,7 @@ struct Opts { quiet: bool, /// Write the logging output to FILE - #[clap(long, name = "FILE", parse(from_os_str))] + #[clap(long, name = "FILE", value_parser)] log_file: Option<PathBuf>, /// Print version information and exit diff --git a/support/texlab/src/options.rs b/support/texlab/src/options.rs index fe0d81c639..241126d908 100644 --- a/support/texlab/src/options.rs +++ b/support/texlab/src/options.rs @@ -1,12 +1,15 @@ use std::path::PathBuf; +use regex::Regex; use serde::{Deserialize, Serialize}; -#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Options { + #[serde(default)] pub root_directory: Option<PathBuf>, + #[serde(default)] pub aux_directory: Option<PathBuf>, #[serde(default)] @@ -15,9 +18,14 @@ pub struct Options { #[serde(default)] pub latex_formatter: LatexFormatter, + #[serde(default)] pub formatter_line_length: Option<i32>, - pub diagnostics_delay: Option<u64>, + #[serde(default)] + pub diagnostics: DiagnosticsOptions, + + #[serde(default = "default_diagnostics_delay")] + pub diagnostics_delay: u64, #[serde(default)] pub build: BuildOptions, @@ -32,6 +40,10 @@ pub struct Options { pub forward_search: ForwardSearchOptions, } +fn default_diagnostics_delay() -> u64 { + 300 +} + #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum BibtexFormatter { @@ -61,6 +73,7 @@ impl Default for LatexFormatter { #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct LatexindentOptions { + #[serde(default)] pub local: Option<String>, #[serde(default)] @@ -70,9 +83,11 @@ pub struct LatexindentOptions { #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct BuildOptions { - pub executable: Option<String>, + #[serde(default = "default_build_executable")] + pub executable: String, - pub args: Option<Vec<String>>, + #[serde(default = "default_build_args")] + pub args: Vec<String>, #[serde(default)] pub is_continuous: bool, @@ -84,39 +99,17 @@ pub struct BuildOptions { pub forward_search_after: bool, } -impl BuildOptions { - #[must_use] - pub fn executable(&self) -> String { - self.executable - .as_ref() - .map_or_else(|| "latexmk".to_string(), Clone::clone) - } - - #[must_use] - pub fn args(&self) -> Vec<String> { - self.args.as_ref().map_or_else( - || { - vec![ - "-pdf".to_string(), - "-interaction=nonstopmode".to_string(), - "-synctex=1".to_string(), - "%f".to_string(), - ] - }, - Clone::clone, - ) - } +fn default_build_executable() -> String { + "latexmk".to_string() } -#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ViewerOptions { - #[serde(default)] - pub enabled: bool, - - pub executable: Option<String>, - - pub args: Option<String>, +fn default_build_args() -> Vec<String> { + vec![ + "-pdf".to_string(), + "-interaction=nonstopmode".to_string(), + "-synctex=1".to_string(), + "%f".to_string(), + ] } #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] @@ -130,7 +123,24 @@ pub struct ChktexOptions { } #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct ForwardSearchOptions { + #[serde(default)] pub executable: Option<String>, + + #[serde(default)] pub args: Option<Vec<String>>, } + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DiagnosticsOptions { + #[serde(default)] + pub allowed_patterns: Vec<DiagnosticsPattern>, + + #[serde(default)] + pub ignored_patterns: Vec<DiagnosticsPattern>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DiagnosticsPattern(#[serde(with = "serde_regex")] pub Regex); diff --git a/support/texlab/src/server.rs b/support/texlab/src/server.rs index f3d2bf44f4..0d5d1f8fe1 100644 --- a/support/texlab/src/server.rs +++ b/support/texlab/src/server.rs @@ -1,6 +1,7 @@ use std::{ path::PathBuf, sync::{Arc, Mutex}, + time::Duration, }; use anyhow::Result; @@ -13,7 +14,8 @@ use threadpool::ThreadPool; use crate::{ client::{send_notification, send_request}, - diagnostics::{DiagnosticsDebouncer, DiagnosticsManager, DiagnosticsMessage}, + debouncer, + diagnostics::DiagnosticManager, dispatch::{NotificationDispatcher, RequestDispatcher}, distro::Distribution, features::{ @@ -24,14 +26,14 @@ use crate::{ ForwardSearchStatus, }, req_queue::{IncomingData, ReqQueue}, - ClientCapabilitiesExt, DocumentLanguage, Environment, LineIndex, LineIndexExt, Options, - Workspace, WorkspaceEvent, + ClientCapabilitiesExt, Document, DocumentData, DocumentLanguage, Environment, LineIndex, + LineIndexExt, Options, Workspace, WorkspaceEvent, }; #[derive(Debug)] enum InternalMessage { SetDistro(Distribution), - SetOptions(Options), + SetOptions(Arc<Options>), } #[derive(Clone)] @@ -41,8 +43,8 @@ pub struct Server { internal_rx: Receiver<InternalMessage>, req_queue: Arc<Mutex<ReqQueue>>, workspace: Workspace, - static_debouncer: Arc<DiagnosticsDebouncer>, - chktex_debouncer: Arc<DiagnosticsDebouncer>, + diagnostic_tx: debouncer::Sender<Workspace>, + diagnostic_manager: DiagnosticManager, pool: Arc<Mutex<ThreadPool>>, load_resolver: bool, build_engine: Arc<BuildEngine>, @@ -56,25 +58,17 @@ impl Server { ) -> Self { let req_queue = Arc::default(); let workspace = Workspace::new(Environment::new(Arc::new(current_dir))); - let diag_manager = Arc::new(Mutex::new(DiagnosticsManager::default())); - - let static_debouncer = Arc::new(create_static_debouncer( - Arc::clone(&diag_manager), - &connection, - )); - - let chktex_debouncer = Arc::new(create_chktex_debouncer(diag_manager, &connection)); - let (internal_tx, internal_rx) = crossbeam_channel::unbounded(); - + let diagnostic_manager = DiagnosticManager::default(); + let diagnostic_tx = create_debouncer(connection.sender.clone(), diagnostic_manager.clone()); Self { connection: Arc::new(connection), internal_tx, internal_rx, req_queue, workspace, - static_debouncer, - chktex_debouncer, + diagnostic_tx, + diagnostic_manager, pool: Arc::new(Mutex::new(threadpool::Builder::new().build())), load_resolver, build_engine: Arc::default(), @@ -173,7 +167,7 @@ impl Server { self.spawn(move |server| { server.register_config_capability(); server.register_file_watching(); - server.pull_config(); + let _ = server.pull_config(); }); Ok(()) @@ -246,19 +240,17 @@ impl Server { fn register_diagnostics_handler(&mut self) { let (event_sender, event_receiver) = crossbeam_channel::unbounded(); - let diag_sender = self.static_debouncer.sender.clone(); + let diagnostic_tx = self.diagnostic_tx.clone(); + let diagnostic_manager = self.diagnostic_manager.clone(); std::thread::spawn(move || { for event in event_receiver { match event { WorkspaceEvent::Changed(workspace, document) => { - let message = DiagnosticsMessage::Analyze { - workspace, - document, - }; - - if diag_sender.send(message).is_err() { - break; - } + diagnostic_manager.push_syntax(&workspace, &document.uri); + let delay = workspace.environment.options.diagnostics_delay; + diagnostic_tx + .send(workspace, Duration::from_millis(delay)) + .unwrap(); } }; } @@ -272,14 +264,14 @@ impl Server { req_queue.incoming.register(id, IncomingData); } - fn pull_config(&self) { + fn pull_config(&self) -> Result<()> { if !self .workspace .environment .client_capabilities .has_pull_configuration_support() { - return; + return Ok(()); } let params = ConfigurationParams { @@ -296,22 +288,38 @@ impl Server { ) { Ok(mut json) => { let value = json.pop().expect("invalid configuration request"); - let options = match serde_json::from_value(value) { - Ok(new_options) => new_options, - Err(why) => { - warn!("Invalid configuration section \"texlab\": {}", why); - Options::default() - } - }; - + let options = self.parse_options(value)?; self.internal_tx - .send(InternalMessage::SetOptions(options)) + .send(InternalMessage::SetOptions(Arc::new(options))) .unwrap(); } Err(why) => { error!("Retrieving configuration failed: {}", why); } }; + + Ok(()) + } + + fn parse_options(&self, value: serde_json::Value) -> Result<Options> { + let options = match serde_json::from_value(value) { + Ok(new_options) => new_options, + Err(why) => { + send_notification::<ShowMessage>( + &self.connection.sender, + ShowMessageParams { + message: format!( + "The texlab configuration is invalid; using the default settings instead.\nDetails: {why}" + ), + typ: MessageType::WARNING, + }, + )?; + + Options::default() + } + }; + + Ok(options) } fn cancel(&self, params: CancelParams) -> Result<()> { @@ -352,18 +360,11 @@ impl Server { .has_pull_configuration_support() { self.spawn(move |server| { - server.pull_config(); + let _ = server.pull_config(); }); } else { - match serde_json::from_value(params.settings) { - Ok(options) => { - self.workspace.environment.options = Arc::new(options); - } - Err(why) => { - error!("Invalid configuration: {}", why); - } - }; - + let options = self.parse_options(params.settings)?; + self.workspace.environment.options = Arc::new(options); self.reparse_all()?; } @@ -382,12 +383,7 @@ impl Server { self.workspace.viewport.insert(Arc::clone(&document.uri)); if self.workspace.environment.options.chktex.on_open_and_save { - self.chktex_debouncer - .sender - .send(DiagnosticsMessage::Analyze { - workspace: self.workspace.clone(), - document, - })?; + self.run_chktex(document); } Ok(()) @@ -421,12 +417,7 @@ impl Server { ); if self.workspace.environment.options.chktex.on_edit { - self.chktex_debouncer - .sender - .send(DiagnosticsMessage::Analyze { - workspace: self.workspace.clone(), - document: new_document, - })?; + self.run_chktex(new_document); }; } None => match uri.to_file_path() { @@ -440,7 +431,7 @@ impl Server { Ok(()) } - fn did_save(&self, params: DidSaveTextDocumentParams) -> Result<()> { + fn did_save(&mut self, params: DidSaveTextDocumentParams) -> Result<()> { let uri = params.text_document.uri; if let Some(request) = self @@ -477,13 +468,9 @@ impl Server { .filter(|_| self.workspace.environment.options.chktex.on_open_and_save) .cloned() { - self.chktex_debouncer - .sender - .send(DiagnosticsMessage::Analyze { - workspace: self.workspace.clone(), - document, - })?; - }; + self.run_chktex(document); + } + Ok(()) } @@ -492,6 +479,20 @@ impl Server { Ok(()) } + fn run_chktex(&mut self, document: Document) { + self.spawn(move |server| { + server + .diagnostic_manager + .push_chktex(&server.workspace, &document.uri); + + let delay = server.workspace.environment.options.diagnostics_delay; + server + .diagnostic_tx + .send(server.workspace.clone(), Duration::from_millis(delay)) + .unwrap(); + }); + } + fn feature_request<P>(&self, uri: Arc<Url>, params: P) -> FeatureRequest<P> { FeatureRequest { params, @@ -841,7 +842,7 @@ impl Server { self.reparse_all()?; } InternalMessage::SetOptions(options) => { - self.workspace.environment.options = Arc::new(options); + self.workspace.environment.options = options; self.reparse_all()?; } }; @@ -853,50 +854,40 @@ impl Server { pub fn run(mut self) -> Result<()> { self.initialize()?; self.process_messages()?; - drop(self.static_debouncer); - drop(self.chktex_debouncer); self.pool.lock().unwrap().join(); Ok(()) } } -fn create_static_debouncer( - manager: Arc<Mutex<DiagnosticsManager>>, - conn: &Connection, -) -> DiagnosticsDebouncer { - let sender = conn.sender.clone(); - DiagnosticsDebouncer::launch(move |workspace, document| { - let mut manager = manager.lock().unwrap(); - manager.update_static(&workspace, Arc::clone(&document.uri)); - if let Err(why) = publish_diagnostics(&sender, &workspace, &manager) { - warn!("Failed to publish diagnostics: {}", why); +fn create_debouncer( + lsp_sender: Sender<Message>, + diagnostic_manager: DiagnosticManager, +) -> debouncer::Sender<Workspace> { + let (tx, rx) = debouncer::unbounded(); + std::thread::spawn(move || { + while let Ok(workspace) = rx.recv() { + if let Err(why) = publish_diagnostics(&lsp_sender, &diagnostic_manager, &workspace) { + warn!("Failed to publish diagnostics: {}", why); + } } - }) -} + }); -fn create_chktex_debouncer( - manager: Arc<Mutex<DiagnosticsManager>>, - conn: &Connection, -) -> DiagnosticsDebouncer { - let sender = conn.sender.clone(); - DiagnosticsDebouncer::launch(move |workspace, document| { - let mut manager = manager.lock().unwrap(); - manager.update_chktex(&workspace, &document.uri, &workspace.environment.options); - if let Err(why) = publish_diagnostics(&sender, &workspace, &manager) { - warn!("Failed to publish diagnostics: {}", why); - } - }) + tx } fn publish_diagnostics( - sender: &Sender<lsp_server::Message>, + lsp_sender: &Sender<lsp_server::Message>, + diagnostic_manager: &DiagnosticManager, workspace: &Workspace, - diag_manager: &DiagnosticsManager, ) -> Result<()> { for document in workspace.documents_by_uri.values() { - let diagnostics = diag_manager.publish(&document.uri); + if matches!(document.data, DocumentData::BuildLog(_)) { + continue; + } + + let diagnostics = diagnostic_manager.publish(workspace, &document.uri); send_notification::<PublishDiagnostics>( - sender, + lsp_sender, PublishDiagnosticsParams { uri: document.uri.as_ref().clone(), version: None, @@ -904,6 +895,7 @@ fn publish_diagnostics( }, )?; } + Ok(()) } |