diff options
author | Norbert Preining <norbert@preining.info> | 2024-01-28 03:00:53 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2024-01-28 03:00:53 +0000 |
commit | 7084e3008c8fc947579f46c6b8a08dfd180e72ef (patch) | |
tree | 9c99041cba4afefac3859d6a2fe4cea289458468 /support | |
parent | b93d257f657e619e22b8b7a27446118ce041727e (diff) |
CTAN sync 202401280300
Diffstat (limited to 'support')
57 files changed, 1572 insertions, 1590 deletions
diff --git a/support/texlab/CHANGELOG.md b/support/texlab/CHANGELOG.md index 3a2771ebfd..928eb0c10b 100644 --- a/support/texlab/CHANGELOG.md +++ b/support/texlab/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [5.12.3] - 2024-01-27 + +### Fixed + +- Fix completing includes in conjunction with `\graphicspath` ([#997](https://github.com/latex-lsp/texlab/issues/997)) + ## [5.12.2] - 2024-01-20 ### Fixed diff --git a/support/texlab/Cargo.lock b/support/texlab/Cargo.lock index 8a130372d7..64669f9f36 100644 --- a/support/texlab/Cargo.lock +++ b/support/texlab/Cargo.lock @@ -443,9 +443,19 @@ dependencies = [ name = "diagnostics" version = "0.0.0" dependencies = [ + "anyhow", "base-db", + "dirs", + "encoding_rs", + "encoding_rs_io", + "expect-test", "itertools 0.12.0", "line-index", + "log", + "multimap", + "once_cell", + "parking_lot", + "regex", "rowan", "rustc-hash", "syntax", @@ -977,6 +987,15 @@ dependencies = [ ] [[package]] +name = "multimap" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1a5d38b9b352dbd913288736af36af41c48d61b1a8cd34bcecd727561b7d511" +dependencies = [ + "serde", +] + +[[package]] name = "notify" version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1496,7 +1515,7 @@ dependencies = [ [[package]] name = "texlab" -version = "5.12.2" +version = "5.12.3" dependencies = [ "anyhow", "base-db", diff --git a/support/texlab/crates/base-db/src/semantics/tex.rs b/support/texlab/crates/base-db/src/semantics/tex.rs index c1e9f45bb9..659ec8c166 100644 --- a/support/texlab/crates/base-db/src/semantics/tex.rs +++ b/support/texlab/crates/base-db/src/semantics/tex.rs @@ -59,6 +59,8 @@ impl Semantics { self.process_environment(environment); } else if let Some(theorem_def) = latex::TheoremDefinition::cast(node.clone()) { self.process_theorem_definition(theorem_def); + } else if let Some(graphics_path) = latex::GraphicsPath::cast(node.clone()) { + self.process_graphics_path(graphics_path); } } @@ -268,6 +270,12 @@ impl Semantics { heading, }); } + + fn process_graphics_path(&mut self, graphics_path: latex::GraphicsPath) { + for path in graphics_path.path_list().filter_map(|path| path.key()) { + self.graphics_paths.insert(path.to_string()); + } + } } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] diff --git a/support/texlab/crates/completion/src/providers/include.rs b/support/texlab/crates/completion/src/providers/include.rs index 2dd204cfdc..f8bb55dfeb 100644 --- a/support/texlab/crates/completion/src/providers/include.rs +++ b/support/texlab/crates/completion/src/providers/include.rs @@ -87,7 +87,7 @@ pub fn complete_includes<'a>( if let Some(score) = builder.matcher.score(&name, segment_text) { builder.items.push(CompletionItem::new_simple( score, - cursor.range, + segment_range, CompletionItemData::File(name), )); } @@ -96,7 +96,7 @@ pub fn complete_includes<'a>( if let Some(score) = builder.matcher.score(&name, segment_text) { builder.items.push(CompletionItem::new_simple( score, - cursor.range, + segment_range, CompletionItemData::Directory(name), )); } @@ -121,11 +121,12 @@ fn current_dir( let path = workspace.current_dir(&parent.dir).to_file_path().ok()?; let mut path = PathBuf::from(path.to_str()?.replace('\\', "/")); - if !path_text.is_empty() { - if let Some(graphics_path) = graphics_path { - path.push(graphics_path); - } + if let Some(graphics_path) = graphics_path { + path.push(graphics_path); + } + + if !path_text.is_empty() { path.push(path_text); if !path_text.ends_with('/') { path.pop(); diff --git a/support/texlab/crates/definition/src/lib.rs b/support/texlab/crates/definition/src/lib.rs index 55319b7fe4..e35a0fdcc1 100644 --- a/support/texlab/crates/definition/src/lib.rs +++ b/support/texlab/crates/definition/src/lib.rs @@ -23,12 +23,12 @@ pub struct DefinitionResult<'a> { } #[derive(Debug)] -struct DefinitionContext<'a> { - params: DefinitionParams<'a>, +struct DefinitionContext<'a, 'b> { + params: &'b DefinitionParams<'a>, results: FxHashSet<DefinitionResult<'a>>, } -pub fn goto_definition(params: DefinitionParams) -> FxHashSet<DefinitionResult> { +pub fn goto_definition<'a>(params: &DefinitionParams<'a>) -> FxHashSet<DefinitionResult<'a>> { let mut context = DefinitionContext { params, results: FxHashSet::default(), diff --git a/support/texlab/crates/definition/src/tests.rs b/support/texlab/crates/definition/src/tests.rs index 8d81927d32..54b0e38f30 100644 --- a/support/texlab/crates/definition/src/tests.rs +++ b/support/texlab/crates/definition/src/tests.rs @@ -30,7 +30,7 @@ fn check(input: &str) { } } - let actual = crate::goto_definition(DefinitionParams { feature, offset }); + let actual = crate::goto_definition(&DefinitionParams { feature, offset }); assert_eq!(actual, expected); } diff --git a/support/texlab/crates/diagnostics/Cargo.toml b/support/texlab/crates/diagnostics/Cargo.toml index 7de9c6042d..1fb4b2171d 100644 --- a/support/texlab/crates/diagnostics/Cargo.toml +++ b/support/texlab/crates/diagnostics/Cargo.toml @@ -7,15 +7,25 @@ edition.workspace = true rust-version.workspace = true [dependencies] +anyhow = "1.0.75" base-db = { path = "../base-db" } +dirs = "5.0.1" +encoding_rs = "0.8.33" +encoding_rs_io = "0.1.7" itertools = "0.12.0" line-index = { path = "../line-index" } +log = "0.4.20" +multimap = "0.9.1" +once_cell = "1.19.0" +parking_lot = "0.12.1" +regex = "1.10.2" rowan = "0.15.15" rustc-hash = "1.1.0" syntax = { path = "../syntax" } url = "2.5.0" [dev-dependencies] +expect-test = "1.4.1" test-utils = { path = "../test-utils" } [lib] diff --git a/support/texlab/crates/diagnostics/src/build_log.rs b/support/texlab/crates/diagnostics/src/build_log.rs index 4c53a35ff8..352e18d0d1 100644 --- a/support/texlab/crates/diagnostics/src/build_log.rs +++ b/support/texlab/crates/diagnostics/src/build_log.rs @@ -1,94 +1,54 @@ -use std::borrow::Cow; - use base_db::{Document, Workspace}; use line_index::LineCol; +use multimap::MultiMap; use rowan::{TextLen, TextRange, TextSize}; use rustc_hash::FxHashMap; use syntax::BuildError; use url::Url; -use crate::{ - types::{Diagnostic, DiagnosticData}, - DiagnosticBuilder, DiagnosticSource, -}; - -#[derive(Debug, Default)] -struct BuildLog { - errors: FxHashMap<Url, Vec<Diagnostic>>, -} +use crate::types::Diagnostic; -#[derive(Debug, Default)] -pub struct BuildErrors { - logs: FxHashMap<Url, BuildLog>, -} +pub fn update( + workspace: &Workspace, + log_document: &Document, + results: &mut FxHashMap<Url, MultiMap<Url, Diagnostic>>, +) -> Option<()> { + let mut errors = MultiMap::default(); -impl DiagnosticSource for BuildErrors { - fn update(&mut self, workspace: &Workspace, log_document: &Document) { - let mut errors: FxHashMap<Url, Vec<Diagnostic>> = FxHashMap::default(); + let data = log_document.data.as_log()?; - let Some(data) = log_document.data.as_log() else { - return; - }; + let parents = workspace.parents(log_document); + let root_document = parents.iter().next()?; - let parents = workspace.parents(log_document); - let Some(root_document) = parents.iter().next() else { - return; - }; + let base_path = root_document + .path + .as_deref() + .and_then(|path| path.parent())?; - let Some(base_path) = root_document.path.as_deref().and_then(|path| path.parent()) else { - return; + for error in &data.errors { + let full_path = base_path.join(&error.relative_path); + let Ok(full_path_uri) = Url::from_file_path(&full_path) else { + continue; }; - for error in &data.errors { - let full_path = base_path.join(&error.relative_path); - let Ok(full_path_uri) = Url::from_file_path(&full_path) else { - continue; - }; + let tex_document = workspace.lookup(&full_path_uri).unwrap_or(root_document); - let tex_document = workspace.lookup(&full_path_uri).unwrap_or(root_document); + let range = find_range_of_hint(tex_document, error).unwrap_or_else(|| { + let line = error.line.unwrap_or(0); + let offset = tex_document + .line_index + .offset(LineCol { line, col: 0 }) + .unwrap_or(TextSize::from(0)); - let range = find_range_of_hint(tex_document, error).unwrap_or_else(|| { - let line = error.line.unwrap_or(0); - let offset = tex_document - .line_index - .offset(LineCol { line, col: 0 }) - .unwrap_or(TextSize::from(0)); + TextRange::empty(offset) + }); - TextRange::empty(offset) - }); - - let diagnostic = Diagnostic { - range, - data: DiagnosticData::Build(error.clone()), - }; - - errors - .entry(tex_document.uri.clone()) - .or_default() - .push(diagnostic); - } - - self.logs - .insert(log_document.uri.clone(), BuildLog { errors }); + let diagnostic = Diagnostic::Build(range, error.clone()); + errors.insert(tex_document.uri.clone(), diagnostic); } - fn publish<'db>( - &'db mut self, - workspace: &'db Workspace, - builder: &mut DiagnosticBuilder<'db>, - ) { - self.logs.retain(|uri, _| workspace.lookup(uri).is_some()); - - for document in workspace.iter() { - let Some(log) = self.logs.get(&document.uri) else { - continue; - }; - - for (uri, errors) in &log.errors { - builder.push_many(uri, errors.iter().map(Cow::Borrowed)); - } - } - } + results.insert(log_document.uri.clone(), errors); + Some(()) } fn find_range_of_hint(document: &Document, error: &BuildError) -> Option<TextRange> { diff --git a/support/texlab/crates/texlab/src/util/chktex.rs b/support/texlab/crates/diagnostics/src/chktex.rs index 6166108707..a342f78349 100644 --- a/support/texlab/crates/texlab/src/util/chktex.rs +++ b/support/texlab/crates/diagnostics/src/chktex.rs @@ -5,13 +5,13 @@ use std::{ }; use base_db::{Document, Workspace}; -use distro::Language; use encoding_rs_io::DecodeReaderBytesBuilder; -use lsp_types::{Diagnostic, NumberOrString}; -use lsp_types::{DiagnosticSeverity, Position, Range}; +use line_index::LineCol; use once_cell::sync::Lazy; use regex::Regex; +use crate::{types::Diagnostic, ChktexError, ChktexSeverity}; + #[derive(Debug)] pub struct Command { text: String, @@ -21,9 +21,7 @@ pub struct Command { impl Command { pub fn new(workspace: &Workspace, document: &Document) -> Option<Self> { - if document.language != Language::Tex { - return None; - } + document.data.as_tex()?; let parent = workspace .parents(document) @@ -31,7 +29,7 @@ impl Command { .next() .unwrap_or(document); - if parent.uri.scheme() != "file" { + if parent.path.is_none() { log::warn!("Calling ChkTeX on non-local files is not supported yet."); return None; } @@ -78,30 +76,31 @@ impl Command { let character = captures[2].parse::<u32>().unwrap() - 1; let digit = captures[3].parse::<u32>().unwrap(); let kind = &captures[4]; - let code = &captures[5]; + let code = String::from(&captures[5]); let message = captures[6].into(); - let range = Range::new( - Position::new(line, character), - Position::new(line, character + digit), - ); + let start = LineCol { + line, + col: character, + }; + + let end = LineCol { + line, + col: character + digit, + }; let severity = match kind { - "Message" => DiagnosticSeverity::INFORMATION, - "Warning" => DiagnosticSeverity::WARNING, - _ => DiagnosticSeverity::ERROR, + "Message" => ChktexSeverity::Message, + "Warning" => ChktexSeverity::Warning, + _ => ChktexSeverity::Error, }; - diagnostics.push(Diagnostic { - range, - severity: Some(severity), - code: Some(NumberOrString::String(code.into())), + diagnostics.push(Diagnostic::Chktex(ChktexError { + start, + end, message, - code_description: None, - source: Some(String::from("ChkTeX")), - related_information: None, - tags: None, - data: None, - }); + severity, + code, + })); } diagnostics diff --git a/support/texlab/crates/diagnostics/src/citations.rs b/support/texlab/crates/diagnostics/src/citations.rs index 0b750aaadb..403d2f5bfc 100644 --- a/support/texlab/crates/diagnostics/src/citations.rs +++ b/support/texlab/crates/diagnostics/src/citations.rs @@ -1,46 +1,22 @@ -use std::borrow::Cow; - use base_db::{ semantics::{bib::Entry, tex::Citation}, util::queries::{self, Object}, Document, Project, Workspace, }; +use multimap::MultiMap; use rustc_hash::FxHashSet; +use url::Url; -use crate::{ - types::{BibError, Diagnostic, DiagnosticData, TexError}, - DiagnosticBuilder, DiagnosticSource, -}; +use crate::types::{BibError, Diagnostic, TexError}; const MAX_UNUSED_ENTRIES: usize = 1000; -#[derive(Default)] -pub struct CitationErrors; - -impl DiagnosticSource for CitationErrors { - fn publish<'db>( - &'db mut self, - workspace: &'db Workspace, - builder: &mut DiagnosticBuilder<'db>, - ) { - for document in workspace.iter() { - let project = workspace.project(document); - detect_undefined_citations(&project, document, builder); - detect_unused_entries(&project, document, builder); - } - - detect_duplicate_entries(workspace, builder); - } -} - -fn detect_undefined_citations<'db>( - project: &Project<'db>, - document: &'db Document, - builder: &mut DiagnosticBuilder<'db>, -) { - let Some(data) = document.data.as_tex() else { - return; - }; +pub fn detect_undefined_citations<'a>( + project: &Project<'a>, + document: &'a Document, + results: &mut MultiMap<Url, Diagnostic>, +) -> Option<()> { + let data = document.data.as_tex()?; let entries: FxHashSet<&str> = Entry::find_all(project) .map(|(_, entry)| entry.name_text()) @@ -49,28 +25,24 @@ fn detect_undefined_citations<'db>( for citation in &data.semantics.citations { let name = citation.name_text(); if name != "*" && !entries.contains(name) { - let diagnostic = Diagnostic { - range: citation.name.range, - data: DiagnosticData::Tex(TexError::UndefinedCitation), - }; - - builder.push(&document.uri, Cow::Owned(diagnostic)); + let diagnostic = Diagnostic::Tex(citation.name.range, TexError::UndefinedCitation); + results.insert(document.uri.clone(), diagnostic); } } + + Some(()) } -fn detect_unused_entries<'db>( - project: &Project<'db>, - document: &'db Document, - builder: &mut DiagnosticBuilder<'db>, -) { - let Some(data) = document.data.as_bib() else { - return; - }; +pub fn detect_unused_entries<'a>( + project: &Project<'a>, + document: &'a Document, + results: &mut MultiMap<Url, Diagnostic>, +) -> Option<()> { + let data = document.data.as_bib()?; // If this is a huge bibliography, then don't bother checking for unused entries. if data.semantics.entries.len() > MAX_UNUSED_ENTRIES { - return; + return None; } let citations: FxHashSet<&str> = Citation::find_all(project) @@ -79,17 +51,18 @@ fn detect_unused_entries<'db>( for entry in &data.semantics.entries { if !citations.contains(entry.name.text.as_str()) { - let diagnostic = Diagnostic { - range: entry.name.range, - data: DiagnosticData::Bib(BibError::UnusedEntry), - }; - - builder.push(&document.uri, Cow::Owned(diagnostic)); + let diagnostic = Diagnostic::Bib(entry.name.range, BibError::UnusedEntry); + results.insert(document.uri.clone(), diagnostic); } } + + Some(()) } -fn detect_duplicate_entries<'db>(workspace: &'db Workspace, builder: &mut DiagnosticBuilder<'db>) { +pub fn detect_duplicate_entries<'a>( + workspace: &'a Workspace, + results: &mut MultiMap<Url, Diagnostic>, +) { for conflict in queries::Conflict::find_all::<Entry>(workspace) { let others = conflict .rest @@ -97,11 +70,7 @@ fn detect_duplicate_entries<'db>(workspace: &'db Workspace, builder: &mut Diagno .map(|location| (location.document.uri.clone(), location.range)) .collect(); - let diagnostic = Diagnostic { - range: conflict.main.range, - data: DiagnosticData::Bib(BibError::DuplicateEntry(others)), - }; - - builder.push(&conflict.main.document.uri, Cow::Owned(diagnostic)); + let diagnostic = Diagnostic::Bib(conflict.main.range, BibError::DuplicateEntry(others)); + results.insert(conflict.main.document.uri.clone(), diagnostic); } } diff --git a/support/texlab/crates/diagnostics/src/grammar.rs b/support/texlab/crates/diagnostics/src/grammar.rs index 1a9e3cecda..df05ee6d14 100644 --- a/support/texlab/crates/diagnostics/src/grammar.rs +++ b/support/texlab/crates/diagnostics/src/grammar.rs @@ -1,4 +1,2 @@ -mod bib; -mod tex; - -pub use self::{bib::BibSyntaxErrors, tex::TexSyntaxErrors}; +pub mod bib; +pub mod tex; diff --git a/support/texlab/crates/diagnostics/src/grammar/bib.rs b/support/texlab/crates/diagnostics/src/grammar/bib.rs index e101e68f2a..494cdbe4b1 100644 --- a/support/texlab/crates/diagnostics/src/grammar/bib.rs +++ b/support/texlab/crates/diagnostics/src/grammar/bib.rs @@ -1,48 +1,35 @@ -use base_db::{Document, DocumentData, Workspace}; +use base_db::{BibDocumentData, Document}; +use multimap::MultiMap; use rowan::{ast::AstNode, TextRange}; use syntax::bibtex::{self, HasDelims, HasEq, HasName, HasType, HasValue}; +use url::Url; -use crate::{ - types::{BibError, DiagnosticData}, - util::SimpleDiagnosticSource, - Diagnostic, DiagnosticBuilder, DiagnosticSource, -}; +use crate::types::{BibError, Diagnostic}; -#[derive(Default)] -pub struct BibSyntaxErrors(SimpleDiagnosticSource); +pub fn update(document: &Document, results: &mut MultiMap<Url, Diagnostic>) -> Option<()> { + let data = document.data.as_bib()?; + let mut analyzer = Analyzer { + data, + diagnostics: Vec::new(), + }; -impl DiagnosticSource for BibSyntaxErrors { - fn update(&mut self, _workspace: &Workspace, document: &Document) { - let mut analyzer = Analyzer { - document, - diagnostics: Vec::new(), - }; + analyzer.analyze_root(); - analyzer.analyze_root(); - self.0 - .errors - .insert(document.uri.clone(), analyzer.diagnostics); - } + *results + .entry(document.uri.clone()) + .or_insert_vec(Vec::new()) = analyzer.diagnostics; - fn publish<'db>( - &'db mut self, - workspace: &'db Workspace, - builder: &mut DiagnosticBuilder<'db>, - ) { - self.0.publish(workspace, builder); - } + Some(()) } struct Analyzer<'a> { - document: &'a Document, + data: &'a BibDocumentData, diagnostics: Vec<Diagnostic>, } impl<'a> Analyzer<'a> { fn analyze_root(&mut self) { - let DocumentData::Bib(data) = &self.document.data else { return }; - - for node in bibtex::SyntaxNode::new_root(data.green.clone()).descendants() { + for node in self.data.root_node().descendants() { if let Some(entry) = bibtex::Entry::cast(node.clone()) { self.analyze_entry(entry); } else if let Some(field) = bibtex::Field::cast(node.clone()) { @@ -54,50 +41,50 @@ impl<'a> Analyzer<'a> { fn analyze_entry(&mut self, entry: bibtex::Entry) { if entry.left_delim_token().is_none() { let offset = entry.type_token().unwrap().text_range().end(); - self.diagnostics.push(Diagnostic { - range: TextRange::empty(offset), - data: DiagnosticData::Bib(BibError::ExpectingLCurly), - }); + self.diagnostics.push(Diagnostic::Bib( + TextRange::empty(offset), + BibError::ExpectingLCurly, + )); return; } if entry.name_token().is_none() { let offset = entry.left_delim_token().unwrap().text_range().end(); - self.diagnostics.push(Diagnostic { - range: TextRange::empty(offset), - data: DiagnosticData::Bib(BibError::ExpectingKey), - }); + self.diagnostics.push(Diagnostic::Bib( + TextRange::empty(offset), + BibError::ExpectingKey, + )); return; } if entry.right_delim_token().is_none() { let offset = entry.syntax().text_range().end(); - self.diagnostics.push(Diagnostic { - range: TextRange::empty(offset), - data: DiagnosticData::Bib(BibError::ExpectingRCurly), - }); + self.diagnostics.push(Diagnostic::Bib( + TextRange::empty(offset), + BibError::ExpectingRCurly, + )); } } fn analyze_field(&mut self, field: bibtex::Field) { if field.eq_token().is_none() { let offset = field.name_token().unwrap().text_range().end(); - self.diagnostics.push(Diagnostic { - range: TextRange::empty(offset), - data: DiagnosticData::Bib(BibError::ExpectingEq), - }); + self.diagnostics.push(Diagnostic::Bib( + TextRange::empty(offset), + BibError::ExpectingEq, + )); return; } if field.value().is_none() { let offset = field.eq_token().unwrap().text_range().end(); - self.diagnostics.push(Diagnostic { - range: TextRange::empty(offset), - data: DiagnosticData::Bib(BibError::ExpectingFieldValue), - }); + self.diagnostics.push(Diagnostic::Bib( + TextRange::empty(offset), + BibError::ExpectingFieldValue, + )); } } } diff --git a/support/texlab/crates/diagnostics/src/grammar/tex.rs b/support/texlab/crates/diagnostics/src/grammar/tex.rs index b61ae4b020..fb2fa5b5fd 100644 --- a/support/texlab/crates/diagnostics/src/grammar/tex.rs +++ b/support/texlab/crates/diagnostics/src/grammar/tex.rs @@ -1,56 +1,47 @@ -use base_db::{Config, Document, DocumentData, Workspace}; +use base_db::{Config, Document, TexDocumentData}; +use multimap::MultiMap; use rowan::{ast::AstNode, NodeOrToken, TextRange}; use syntax::latex; - -use crate::{ - types::{DiagnosticData, TexError}, - util::SimpleDiagnosticSource, - Diagnostic, DiagnosticBuilder, DiagnosticSource, -}; - -#[derive(Default)] -pub struct TexSyntaxErrors(SimpleDiagnosticSource); - -impl DiagnosticSource for TexSyntaxErrors { - fn update(&mut self, workspace: &Workspace, document: &Document) { - let mut analyzer = Analyzer { - document, - config: workspace.config(), - diagnostics: Vec::new(), - }; - - analyzer.analyze_root(); - self.0 - .errors - .insert(document.uri.clone(), analyzer.diagnostics); +use url::Url; + +use crate::types::{Diagnostic, TexError}; + +pub fn update( + document: &Document, + config: &Config, + results: &mut MultiMap<Url, Diagnostic>, +) -> Option<()> { + let data = document.data.as_tex()?; + if !document.uri.as_str().ends_with(".tex") { + return None; } - fn publish<'db>( - &'db mut self, - workspace: &'db Workspace, - builder: &mut DiagnosticBuilder<'db>, - ) { - self.0.publish(workspace, builder); - } + let mut analyzer = Analyzer { + data, + config, + diagnostics: Vec::new(), + }; + + analyzer.analyze_root(); + + *results + .entry(document.uri.clone()) + .or_insert_vec(Vec::new()) = analyzer.diagnostics; + + Some(()) } struct Analyzer<'a> { - document: &'a Document, + data: &'a TexDocumentData, config: &'a Config, diagnostics: Vec<Diagnostic>, } impl<'a> Analyzer<'a> { fn analyze_root(&mut self) { - if !self.document.uri.as_str().ends_with(".tex") { - return; - } - - let DocumentData::Tex(data) = &self.document.data else { return }; - let verbatim_envs = &self.config.syntax.verbatim_environments; - let mut traversal = latex::SyntaxNode::new_root(data.green.clone()).preorder(); + let mut traversal = self.data.root_node().preorder(); while let Some(event) = traversal.next() { match event { rowan::WalkEvent::Enter(node) => { @@ -82,10 +73,10 @@ impl<'a> Analyzer<'a> { let begin = environment.begin()?.name()?.key()?; let end = environment.end()?.name()?.key()?; if begin != end { - self.diagnostics.push(Diagnostic { - range: latex::small_range(&begin), - data: DiagnosticData::Tex(TexError::MismatchedEnvironment), - }); + self.diagnostics.push(Diagnostic::Tex( + latex::small_range(&begin), + TexError::MismatchedEnvironment, + )); } Some(()) @@ -108,10 +99,10 @@ impl<'a> Analyzer<'a> { .filter_map(NodeOrToken::into_token) .any(|token| token.kind() == latex::R_CURLY) { - self.diagnostics.push(Diagnostic { - range: TextRange::empty(node.text_range().end()), - data: DiagnosticData::Tex(TexError::ExpectingRCurly), - }); + self.diagnostics.push(Diagnostic::Tex( + TextRange::empty(node.text_range().end()), + TexError::ExpectingRCurly, + )); } Some(()) @@ -119,10 +110,10 @@ impl<'a> Analyzer<'a> { fn analyze_curly_braces(&mut self, node: latex::SyntaxNode) -> Option<()> { if node.kind() == latex::ERROR && node.first_token()?.text() == "}" { - self.diagnostics.push(Diagnostic { - range: node.text_range(), - data: DiagnosticData::Tex(TexError::UnexpectedRCurly), - }); + self.diagnostics.push(Diagnostic::Tex( + node.text_range(), + TexError::UnexpectedRCurly, + )); Some(()) } else { diff --git a/support/texlab/crates/diagnostics/src/labels.rs b/support/texlab/crates/diagnostics/src/labels.rs index 03df664a15..92f9072918 100644 --- a/support/texlab/crates/diagnostics/src/labels.rs +++ b/support/texlab/crates/diagnostics/src/labels.rs @@ -1,35 +1,18 @@ -use std::borrow::Cow; - use base_db::{ semantics::tex::{Label, LabelKind}, util::queries, DocumentData, Workspace, }; use itertools::Itertools; +use multimap::MultiMap; use rustc_hash::FxHashSet; +use url::Url; -use crate::{ - types::{DiagnosticData, TexError}, - Diagnostic, DiagnosticBuilder, DiagnosticSource, -}; - -#[derive(Default)] -pub struct LabelErrors; - -impl DiagnosticSource for LabelErrors { - fn publish<'db>( - &'db mut self, - workspace: &'db Workspace, - builder: &mut DiagnosticBuilder<'db>, - ) { - detect_undefined_and_unused_labels(workspace, builder); - detect_duplicate_labels(workspace, builder); - } -} +use crate::types::{Diagnostic, TexError}; -fn detect_undefined_and_unused_labels<'db>( - workspace: &'db Workspace, - builder: &mut DiagnosticBuilder<'db>, +pub fn detect_undefined_and_unused_labels( + workspace: &Workspace, + results: &mut MultiMap<Url, Diagnostic>, ) { let graphs: Vec<_> = workspace .iter() @@ -61,25 +44,19 @@ fn detect_undefined_and_unused_labels<'db>( for label in &data.semantics.labels { if label.kind != LabelKind::Definition && !label_defs.contains(&label.name.text) { - let diagnostic = Diagnostic { - range: label.name.range, - data: DiagnosticData::Tex(TexError::UndefinedLabel), - }; - builder.push(&document.uri, Cow::Owned(diagnostic)); + let diagnostic = Diagnostic::Tex(label.name.range, TexError::UndefinedLabel); + results.insert(document.uri.clone(), diagnostic); } if label.kind == LabelKind::Definition && !label_refs.contains(&label.name.text) { - let diagnostic = Diagnostic { - range: label.name.range, - data: DiagnosticData::Tex(TexError::UnusedLabel), - }; - builder.push(&document.uri, Cow::Owned(diagnostic)); + let diagnostic = Diagnostic::Tex(label.name.range, TexError::UnusedLabel); + results.insert(document.uri.clone(), diagnostic); } } } } -fn detect_duplicate_labels<'db>(workspace: &'db Workspace, builder: &mut DiagnosticBuilder<'db>) { +pub fn detect_duplicate_labels(workspace: &Workspace, results: &mut MultiMap<Url, Diagnostic>) { for conflict in queries::Conflict::find_all::<Label>(workspace) { let others = conflict .rest @@ -87,11 +64,7 @@ fn detect_duplicate_labels<'db>(workspace: &'db Workspace, builder: &mut Diagnos .map(|location| (location.document.uri.clone(), location.range)) .collect(); - let diagnostic = Diagnostic { - range: conflict.main.range, - data: DiagnosticData::Tex(TexError::DuplicateLabel(others)), - }; - - builder.push(&conflict.main.document.uri, Cow::Owned(diagnostic)); + let diagnostic = Diagnostic::Tex(conflict.main.range, TexError::DuplicateLabel(others)); + results.insert(conflict.main.document.uri.clone(), diagnostic); } } diff --git a/support/texlab/crates/diagnostics/src/lib.rs b/support/texlab/crates/diagnostics/src/lib.rs index d0be31a0ba..e5baf9ebdb 100644 --- a/support/texlab/crates/diagnostics/src/lib.rs +++ b/support/texlab/crates/diagnostics/src/lib.rs @@ -1,88 +1,13 @@ mod build_log; +pub mod chktex; mod citations; mod grammar; mod labels; -pub mod types; -pub(crate) mod util; +mod manager; +mod types; -use std::borrow::Cow; - -use base_db::{Document, Workspace}; -use build_log::BuildErrors; -use citations::CitationErrors; -use grammar::{BibSyntaxErrors, TexSyntaxErrors}; -use labels::LabelErrors; -use rustc_hash::FxHashMap; -use types::Diagnostic; -use url::Url; - -#[derive(Debug, PartialEq, Eq, Clone, Default)] -pub struct DiagnosticBuilder<'db> { - inner: FxHashMap<&'db Url, Vec<Cow<'db, Diagnostic>>>, -} - -impl<'db> DiagnosticBuilder<'db> { - pub fn push(&mut self, uri: &'db Url, diagnostic: Cow<'db, Diagnostic>) { - self.inner.entry(uri).or_default().push(diagnostic); - } - - pub fn push_many( - &mut self, - uri: &'db Url, - diagnostics: impl Iterator<Item = Cow<'db, Diagnostic>>, - ) { - self.inner.entry(uri).or_default().extend(diagnostics); - } - - pub fn iter(&self) -> impl Iterator<Item = (&'db Url, impl Iterator<Item = &Diagnostic>)> { - self.inner - .iter() - .map(|(uri, diagnostics)| (*uri, diagnostics.iter().map(|diag| diag.as_ref()))) - } -} - -pub trait DiagnosticSource { - #[allow(unused_variables)] - fn update(&mut self, workspace: &Workspace, document: &Document) {} - - fn publish<'db>(&'db mut self, workspace: &'db Workspace, builder: &mut DiagnosticBuilder<'db>); -} - -pub struct DiagnosticManager { - sources: Vec<Box<dyn DiagnosticSource>>, -} - -impl Default for DiagnosticManager { - fn default() -> Self { - let sources: Vec<Box<dyn DiagnosticSource>> = vec![ - Box::<TexSyntaxErrors>::default(), - Box::<BibSyntaxErrors>::default(), - Box::<BuildErrors>::default(), - Box::<LabelErrors>::default(), - Box::<CitationErrors>::default(), - ]; - - Self { sources } - } -} - -impl DiagnosticSource for DiagnosticManager { - fn update(&mut self, workspace: &Workspace, document: &Document) { - for source in &mut self.sources { - source.update(workspace, document); - } - } - - fn publish<'db>( - &'db mut self, - workspace: &'db Workspace, - builder: &mut DiagnosticBuilder<'db>, - ) { - for source in &mut self.sources { - source.publish(workspace, builder); - } - } -} +pub use manager::Manager; +pub use types::*; #[cfg(test)] mod tests; diff --git a/support/texlab/crates/diagnostics/src/manager.rs b/support/texlab/crates/diagnostics/src/manager.rs new file mode 100644 index 0000000000..1ce5e194b8 --- /dev/null +++ b/support/texlab/crates/diagnostics/src/manager.rs @@ -0,0 +1,75 @@ +use base_db::{util::filter_regex_patterns, Document, Owner, Workspace}; +use multimap::MultiMap; +use rustc_hash::FxHashMap; +use url::Url; + +use crate::types::Diagnostic; + +/// Manages all diagnostics for a workspace. +#[derive(Debug, Default)] +pub struct Manager { + grammar: MultiMap<Url, Diagnostic>, + chktex: FxHashMap<Url, Vec<Diagnostic>>, + build_log: FxHashMap<Url, MultiMap<Url, Diagnostic>>, +} + +impl Manager { + /// Updates the syntax-based diagnostics for the given document. + pub fn update_syntax(&mut self, workspace: &Workspace, document: &Document) { + self.grammar.remove(&document.uri); + super::grammar::tex::update(document, workspace.config(), &mut self.grammar); + super::grammar::bib::update(document, &mut self.grammar); + + self.build_log.remove(&document.uri); + super::build_log::update(workspace, document, &mut self.build_log); + } + + /// Updates the ChkTeX diagnostics for the given document. + pub fn update_chktex(&mut self, uri: Url, diagnostics: Vec<Diagnostic>) { + self.chktex.insert(uri, diagnostics); + } + + /// Returns all filtered diagnostics for the given workspace. + pub fn get(&self, workspace: &Workspace) -> MultiMap<Url, Diagnostic> { + let mut results = MultiMap::default(); + for (uri, diagnostics) in &self.grammar { + results.insert_many_from_slice(uri.clone(), diagnostics); + } + + for (uri, diagnostics) in self.build_log.values().flatten() { + results.insert_many_from_slice(uri.clone(), diagnostics); + } + + for (uri, diagnostics) in &self.chktex { + if workspace + .lookup(uri) + .map_or(false, |document| document.owner == Owner::Client) + { + results.insert_many_from_slice(uri.clone(), diagnostics); + } + } + + for document in workspace.iter() { + let project = workspace.project(document); + super::citations::detect_undefined_citations(&project, document, &mut results); + super::citations::detect_unused_entries(&project, document, &mut results); + } + + super::citations::detect_duplicate_entries(workspace, &mut results); + super::labels::detect_duplicate_labels(workspace, &mut results); + super::labels::detect_undefined_and_unused_labels(workspace, &mut results); + + let config = &workspace.config().diagnostics; + for (_, diagnostics) in &mut results { + diagnostics.retain(|diagnostic| { + filter_regex_patterns( + &diagnostic.message(), + &config.allowed_patterns, + &config.ignored_patterns, + ) + }); + } + + results + } +} diff --git a/support/texlab/crates/diagnostics/src/tests.rs b/support/texlab/crates/diagnostics/src/tests.rs index 539e3ba765..b7c6004d19 100644 --- a/support/texlab/crates/diagnostics/src/tests.rs +++ b/support/texlab/crates/diagnostics/src/tests.rs @@ -1,37 +1,23 @@ -use std::borrow::Cow; +use expect_test::{expect, Expect}; +use itertools::Itertools; -use crate::{ - types::{BibError, Diagnostic, DiagnosticData, TexError}, - DiagnosticBuilder, DiagnosticManager, DiagnosticSource, -}; - -fn check(input: &str, expected_data: &[DiagnosticData]) { +fn check(input: &str, expect: Expect) { let fixture = test_utils::fixture::Fixture::parse(input); - let mut manager = DiagnosticManager::default(); - - let mut expected = DiagnosticBuilder::default(); - let mut expected_data = expected_data.iter(); - for document in &fixture.documents { - let diagnostics = document.ranges.iter().copied().map(|range| { - let data = expected_data.next().unwrap().clone(); - Cow::Owned(Diagnostic { range, data }) - }); - - expected.push_many(&document.uri, diagnostics); - } + let mut manager = crate::Manager::default(); for document in fixture.workspace.iter() { - manager.update(&fixture.workspace, &document); + manager.update_syntax(&fixture.workspace, &document); } - let mut actual = DiagnosticBuilder::default(); - manager.publish(&fixture.workspace, &mut actual); - - for diagnostics in actual.inner.values_mut() { - diagnostics.sort_by_key(|diag| (diag.range.start(), diag.range.len())); - } + let results = manager.get(&fixture.workspace); + let results = results + .iter_all() + .filter(|(_, diags)| !diags.is_empty()) + .sorted_by(|(uri1, _), (uri2, _)| uri1.cmp(&uri2)) + .map(|(uri, diags)| (uri.as_str(), diags)) + .collect_vec(); - assert_eq!(actual, expected); + expect.assert_debug_eq(&results); } #[test] @@ -42,7 +28,19 @@ fn test_bib_entry_missing_l_delim() { @article ! "#, - &[DiagnosticData::Bib(BibError::ExpectingLCurly)], + expect![[r#" + [ + ( + "file:///texlab/main.bib", + [ + Bib( + 8..8, + ExpectingLCurly, + ), + ], + ), + ] + "#]], ) } @@ -58,7 +56,19 @@ fn test_bib_entry_missing_r_delim() { \bibliography{main} \cite{foo} "#, - &[DiagnosticData::Bib(BibError::ExpectingRCurly)], + expect![[r#" + [ + ( + "file:///texlab/main.bib", + [ + Bib( + 14..14, + ExpectingRCurly, + ), + ], + ), + ] + "#]], ) } @@ -69,7 +79,19 @@ fn test_bib_entry_missing_name() { %! main.bib @article{ !"#, - &[DiagnosticData::Bib(BibError::ExpectingKey)], + expect![[r#" + [ + ( + "file:///texlab/main.bib", + [ + Bib( + 9..9, + ExpectingKey, + ), + ], + ), + ] + "#]], ) } @@ -87,7 +109,19 @@ fn test_bib_field_missing_eq() { \bibliography{main} \cite{foo} "#, - &[DiagnosticData::Bib(BibError::ExpectingEq)], + expect![[r#" + [ + ( + "file:///texlab/main.bib", + [ + Bib( + 23..23, + ExpectingEq, + ), + ], + ), + ] + "#]], ) } @@ -105,7 +139,19 @@ fn test_bib_field_missing_value() { \bibliography{main} \cite{foo} "#, - &[DiagnosticData::Bib(BibError::ExpectingFieldValue)], + expect![[r#" + [ + ( + "file:///texlab/main.bib", + [ + Bib( + 25..25, + ExpectingFieldValue, + ), + ], + ), + ] + "#]], ) } @@ -119,10 +165,23 @@ fn test_tex_unmatched_braces() { { ! "#, - &[ - DiagnosticData::Tex(TexError::UnexpectedRCurly), - DiagnosticData::Tex(TexError::ExpectingRCurly), - ], + expect![[r#" + [ + ( + "file:///texlab/main.tex", + [ + Tex( + 0..1, + UnexpectedRCurly, + ), + Tex( + 4..4, + ExpectingRCurly, + ), + ], + ), + ] + "#]], ) } @@ -135,7 +194,19 @@ fn test_tex_environment_mismatched() { ^^^ \end{bar} "#, - &[DiagnosticData::Tex(TexError::MismatchedEnvironment)], + expect![[r#" + [ + ( + "file:///texlab/main.tex", + [ + Tex( + 7..10, + MismatchedEnvironment, + ), + ], + ), + ] + "#]], ) } @@ -148,7 +219,19 @@ fn test_label_unused() { ^^^ \label{bar}\ref{bar} "#, - &[DiagnosticData::Tex(TexError::UnusedLabel)], + expect![[r#" + [ + ( + "file:///texlab/main.tex", + [ + Tex( + 7..10, + UnusedLabel, + ), + ], + ), + ] + "#]], ) } @@ -160,7 +243,19 @@ fn test_label_undefined() { \ref{foo} ^^^ "#, - &[DiagnosticData::Tex(TexError::UndefinedLabel)], + expect![[r#" + [ + ( + "file:///texlab/main.tex", + [ + Tex( + 5..8, + UndefinedLabel, + ), + ], + ), + ] + "#]], ) } @@ -172,7 +267,19 @@ fn test_citation_undefined() { \cite{foo} ^^^ "#, - &[DiagnosticData::Tex(TexError::UndefinedCitation)], + expect![[r#" + [ + ( + "file:///texlab/main.tex", + [ + Tex( + 6..9, + UndefinedCitation, + ), + ], + ), + ] + "#]], ) } @@ -184,6 +291,18 @@ fn test_citation_unused() { @article{foo,} ^^^ "#, - &[DiagnosticData::Bib(BibError::UnusedEntry)], + expect![[r#" + [ + ( + "file:///texlab/main.bib", + [ + Bib( + 9..12, + UnusedEntry, + ), + ], + ), + ] + "#]], ) } diff --git a/support/texlab/crates/diagnostics/src/types.rs b/support/texlab/crates/diagnostics/src/types.rs index a443245b6f..9cbbf8936f 100644 --- a/support/texlab/crates/diagnostics/src/types.rs +++ b/support/texlab/crates/diagnostics/src/types.rs @@ -1,21 +1,9 @@ +use line_index::LineCol; use rowan::TextRange; use syntax::BuildError; use url::Url; #[derive(Debug, PartialEq, Eq, Clone)] -pub struct Diagnostic { - pub range: TextRange, - pub data: DiagnosticData, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum DiagnosticData { - Tex(TexError), - Bib(BibError), - Build(BuildError), -} - -#[derive(Debug, PartialEq, Eq, Clone)] pub enum TexError { UnexpectedRCurly, ExpectingRCurly, @@ -36,3 +24,54 @@ pub enum BibError { UnusedEntry, DuplicateEntry(Vec<(Url, TextRange)>), } + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct ChktexError { + pub start: LineCol, + pub end: LineCol, + pub message: String, + pub severity: ChktexSeverity, + pub code: String, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum ChktexSeverity { + Error, + Warning, + Message, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Diagnostic { + Tex(TextRange, TexError), + Bib(TextRange, BibError), + Build(TextRange, BuildError), + Chktex(ChktexError), +} + +impl Diagnostic { + pub fn message(&self) -> &str { + match self { + Diagnostic::Tex(_, error) => match error { + TexError::UnexpectedRCurly => "Unexpected \"}\"", + TexError::ExpectingRCurly => "Expecting a curly bracket: \"}\"", + TexError::MismatchedEnvironment => "Mismatched environment", + TexError::UnusedLabel => "Unused label", + TexError::UndefinedLabel => "Undefined reference", + TexError::UndefinedCitation => "Undefined reference", + TexError::DuplicateLabel(_) => "Duplicate label", + }, + Diagnostic::Bib(_, error) => match error { + BibError::ExpectingLCurly => "Expecting a curly bracket: \"{\"", + BibError::ExpectingKey => "Expecting a key", + BibError::ExpectingRCurly => "Expecting a curly bracket: \"}\"", + BibError::ExpectingEq => "Expecting an equality sign: \"=\"", + BibError::ExpectingFieldValue => "Expecting a field value", + BibError::UnusedEntry => "Unused entry", + BibError::DuplicateEntry(_) => "Duplicate entry key", + }, + Diagnostic::Build(_, error) => &error.message, + Diagnostic::Chktex(error) => &error.message, + } + } +} diff --git a/support/texlab/crates/diagnostics/src/util.rs b/support/texlab/crates/diagnostics/src/util.rs deleted file mode 100644 index fd34125dbe..0000000000 --- a/support/texlab/crates/diagnostics/src/util.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::borrow::Cow; - -use base_db::Workspace; -use rustc_hash::FxHashMap; -use url::Url; - -use crate::{Diagnostic, DiagnosticBuilder, DiagnosticSource}; - -#[derive(Default)] -pub struct SimpleDiagnosticSource { - pub errors: FxHashMap<Url, Vec<Diagnostic>>, -} - -impl DiagnosticSource for SimpleDiagnosticSource { - fn publish<'db>( - &'db mut self, - workspace: &'db Workspace, - builder: &mut DiagnosticBuilder<'db>, - ) { - self.errors.retain(|uri, _| workspace.lookup(uri).is_some()); - - for document in workspace.iter() { - if let Some(diagnostics) = self.errors.get(&document.uri) { - builder.push_many(&document.uri, diagnostics.iter().map(Cow::Borrowed)); - } - } - } -} diff --git a/support/texlab/crates/highlights/src/lib.rs b/support/texlab/crates/highlights/src/lib.rs index 1845da3907..009dd20807 100644 --- a/support/texlab/crates/highlights/src/lib.rs +++ b/support/texlab/crates/highlights/src/lib.rs @@ -21,7 +21,7 @@ pub struct HighlightParams<'a> { pub offset: TextSize, } -pub fn find_all(params: HighlightParams) -> Vec<Highlight> { +pub fn find_all(params: &HighlightParams) -> Vec<Highlight> { let mut results = Vec::new(); label::find_highlights(¶ms, &mut results); results diff --git a/support/texlab/crates/highlights/src/tests.rs b/support/texlab/crates/highlights/src/tests.rs index 74c3cc0c18..ef63a3137b 100644 --- a/support/texlab/crates/highlights/src/tests.rs +++ b/support/texlab/crates/highlights/src/tests.rs @@ -10,7 +10,7 @@ fn check(input: &str, expected_kinds: &[HighlightKind]) { .map(|location| location.range) .collect::<Vec<_>>(); - let results = crate::find_all(HighlightParams { feature, offset }); + let results = crate::find_all(&HighlightParams { feature, offset }); let actual_ranges = results .iter() diff --git a/support/texlab/crates/hover/src/lib.rs b/support/texlab/crates/hover/src/lib.rs index bd16be3282..2d22d50b0b 100644 --- a/support/texlab/crates/hover/src/lib.rs +++ b/support/texlab/crates/hover/src/lib.rs @@ -34,7 +34,7 @@ pub enum HoverData<'db> { StringRef(String), } -pub fn find(params: HoverParams) -> Option<Hover> { +pub fn find<'a>(params: &HoverParams<'a>) -> Option<Hover<'a>> { citation::find_hover(¶ms) .or_else(|| package::find_hover(¶ms)) .or_else(|| entry_type::find_hover(¶ms)) diff --git a/support/texlab/crates/hover/src/tests.rs b/support/texlab/crates/hover/src/tests.rs index bbe33fa2db..e6377bb034 100644 --- a/support/texlab/crates/hover/src/tests.rs +++ b/support/texlab/crates/hover/src/tests.rs @@ -6,7 +6,7 @@ fn check(input: &str, expect: Expect) { let fixture = test_utils::fixture::Fixture::parse(input); let (feature, offset) = fixture.make_params().unwrap(); let params = HoverParams { feature, offset }; - let data = crate::find(params).map(|hover| { + let data = crate::find(¶ms).map(|hover| { assert_eq!(fixture.documents[0].ranges[0], hover.range); hover.data }); diff --git a/support/texlab/crates/inlay-hints/src/lib.rs b/support/texlab/crates/inlay-hints/src/lib.rs index e3c80751f0..946f77c1c4 100644 --- a/support/texlab/crates/inlay-hints/src/lib.rs +++ b/support/texlab/crates/inlay-hints/src/lib.rs @@ -20,7 +20,7 @@ pub enum InlayHintData<'a> { LabelReference(RenderedLabel<'a>), } -pub fn find_all<'a>(params: InlayHintParams<'a>) -> Option<Vec<InlayHint>> { +pub fn find_all<'a>(params: &InlayHintParams<'a>) -> Option<Vec<InlayHint<'a>>> { let mut builder = InlayHintBuilder { params, hints: Vec::new(), @@ -30,8 +30,8 @@ pub fn find_all<'a>(params: InlayHintParams<'a>) -> Option<Vec<InlayHint>> { Some(builder.hints) } -struct InlayHintBuilder<'a> { - params: InlayHintParams<'a>, +struct InlayHintBuilder<'a, 'b> { + params: &'b InlayHintParams<'a>, hints: Vec<InlayHint<'a>>, } diff --git a/support/texlab/crates/inlay-hints/src/tests.rs b/support/texlab/crates/inlay-hints/src/tests.rs index 5b8a43d614..f8c4d619b1 100644 --- a/support/texlab/crates/inlay-hints/src/tests.rs +++ b/support/texlab/crates/inlay-hints/src/tests.rs @@ -7,7 +7,7 @@ fn check(input: &str, expect: Expect) { let (feature, _) = fixture.make_params().unwrap(); let range = TextRange::new(0.into(), feature.document.text.text_len()); let params = crate::InlayHintParams { range, feature }; - let actual = crate::find_all(params).unwrap_or_default(); + let actual = crate::find_all(¶ms).unwrap_or_default(); let expected_offsets = fixture.locations().map(|location| location.range.start()); for (hint, offset) in actual.iter().zip(expected_offsets) { diff --git a/support/texlab/crates/links/src/include.rs b/support/texlab/crates/links/src/include.rs index 374e0089df..1392b16eaa 100644 --- a/support/texlab/crates/links/src/include.rs +++ b/support/texlab/crates/links/src/include.rs @@ -1,7 +1,7 @@ use base_db::{DocumentLocation, FeatureParams}; pub(super) fn find_links<'a>( - params: FeatureParams<'a>, + params: &FeatureParams<'a>, results: &mut Vec<DocumentLocation<'a>>, ) -> Option<()> { let document = params.document; diff --git a/support/texlab/crates/links/src/lib.rs b/support/texlab/crates/links/src/lib.rs index 67e48cf930..48f5e09fc0 100644 --- a/support/texlab/crates/links/src/lib.rs +++ b/support/texlab/crates/links/src/lib.rs @@ -2,7 +2,7 @@ use base_db::{DocumentLocation, FeatureParams}; mod include; -pub fn find_links(params: FeatureParams) -> Vec<DocumentLocation> { +pub fn find_links<'a>(params: &FeatureParams<'a>) -> Vec<DocumentLocation<'a>> { let mut results = Vec::new(); include::find_links(params, &mut results); results diff --git a/support/texlab/crates/links/src/tests.rs b/support/texlab/crates/links/src/tests.rs index d083b33be6..6837bc96b7 100644 --- a/support/texlab/crates/links/src/tests.rs +++ b/support/texlab/crates/links/src/tests.rs @@ -3,7 +3,7 @@ use expect_test::{expect, Expect}; fn check(input: &str, expect: Expect) { let fixture = test_utils::fixture::Fixture::parse(input); let (params, _) = fixture.make_params().unwrap(); - let links = crate::find_links(params); + let links = crate::find_links(¶ms); let actual_ranges = links.iter().map(|link| link.range).collect::<Vec<_>>(); diff --git a/support/texlab/crates/references/src/lib.rs b/support/texlab/crates/references/src/lib.rs index c223ad8eba..23dbd7e0ae 100644 --- a/support/texlab/crates/references/src/lib.rs +++ b/support/texlab/crates/references/src/lib.rs @@ -21,15 +21,16 @@ pub enum ReferenceKind { pub struct ReferenceParams<'a> { pub feature: FeatureParams<'a>, pub offset: TextSize, + pub include_declaration: bool, } #[derive(Debug)] -struct ReferenceContext<'a> { - params: ReferenceParams<'a>, +struct ReferenceContext<'a, 'b> { + params: &'b ReferenceParams<'a>, results: Vec<Reference<'a>>, } -pub fn find_all(params: ReferenceParams) -> Vec<Reference<'_>> { +pub fn find_all<'a>(params: &ReferenceParams<'a>) -> Vec<DocumentLocation<'a>> { let mut context = ReferenceContext { params, results: Vec::new(), @@ -38,7 +39,13 @@ pub fn find_all(params: ReferenceParams) -> Vec<Reference<'_>> { entry::find_all(&mut context); label::find_all(&mut context); string_def::find_all(&mut context); - context.results + + context + .results + .into_iter() + .filter(|r| r.kind == ReferenceKind::Reference || params.include_declaration) + .map(|reference| reference.location) + .collect() } #[cfg(test)] diff --git a/support/texlab/crates/references/src/tests.rs b/support/texlab/crates/references/src/tests.rs index 3a3e7008a0..a21a3ce551 100644 --- a/support/texlab/crates/references/src/tests.rs +++ b/support/texlab/crates/references/src/tests.rs @@ -1,17 +1,19 @@ use std::collections::HashSet; -use crate::{ReferenceKind, ReferenceParams}; +use crate::ReferenceParams; -fn check(fixture: &str, include_def: bool) { +fn check(fixture: &str, include_declaration: bool) { let fixture = test_utils::fixture::Fixture::parse(fixture); let (feature, offset) = fixture.make_params().unwrap(); let expected = fixture.locations().collect::<HashSet<_>>(); - let actual = crate::find_all(ReferenceParams { feature, offset }) - .into_iter() - .filter(|reference| reference.kind == ReferenceKind::Reference || include_def) - .map(|reference| reference.location) - .collect::<HashSet<_>>(); + let actual = crate::find_all(&ReferenceParams { + feature, + offset, + include_declaration, + }) + .into_iter() + .collect::<HashSet<_>>(); assert_eq!(actual, expected); } diff --git a/support/texlab/crates/texlab/Cargo.toml b/support/texlab/crates/texlab/Cargo.toml index 0162fe499a..53b63ded3e 100644 --- a/support/texlab/crates/texlab/Cargo.toml +++ b/support/texlab/crates/texlab/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "texlab" description = "LaTeX Language Server" -version = "5.12.2" +version = "5.12.3" license.workspace = true readme = "README.md" authors.workspace = true diff --git a/support/texlab/crates/texlab/benches/bench_main.rs b/support/texlab/crates/texlab/benches/bench_main.rs index 0f54a2c2d1..daeb5409ed 100644 --- a/support/texlab/crates/texlab/benches/bench_main.rs +++ b/support/texlab/crates/texlab/benches/bench_main.rs @@ -1,8 +1,4 @@ -use base_db::{Owner, Workspace}; use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use distro::Language; -use line_index::LineCol; -use lsp_types::{ClientCapabilities, CompletionParams, Position, TextDocumentPositionParams, Url}; use parser::{parse_latex, SyntaxConfig}; const CODE: &str = include_str!("../../../texlab.tex"); @@ -12,34 +8,6 @@ fn criterion_benchmark(c: &mut Criterion) { c.bench_function("LaTeX/Parser", |b| { b.iter(|| parse_latex(black_box(CODE), &config)); }); - - c.bench_function("LaTeX/Completion/Command", |b| { - let uri = Url::parse("http://example.com/texlab.tex").unwrap(); - let text = CODE.to_string(); - let mut workspace = Workspace::default(); - workspace.open( - uri.clone(), - text, - Language::Tex, - Owner::Client, - LineCol { line: 0, col: 0 }, - ); - - let client_capabilities = ClientCapabilities::default(); - let params = CompletionParams { - context: None, - text_document_position: TextDocumentPositionParams::new( - lsp_types::TextDocumentIdentifier { uri: uri.clone() }, - Position::new(0, 1), - ), - work_done_progress_params: Default::default(), - partial_result_params: Default::default(), - }; - - b.iter(|| { - texlab::features::completion::complete(&workspace, ¶ms, &client_capabilities, None) - }); - }); } criterion_group!(benches, criterion_benchmark); diff --git a/support/texlab/crates/texlab/src/features/completion.rs b/support/texlab/crates/texlab/src/features/completion.rs index 8e5fa2adbb..5d32588970 100644 --- a/support/texlab/crates/texlab/src/features/completion.rs +++ b/support/texlab/crates/texlab/src/features/completion.rs @@ -1,73 +1,77 @@ -use base_db::{util::RenderedObject, Document, FeatureParams, Workspace}; -use completion::{ - ArgumentData, CompletionItem, CompletionItemData, CompletionParams, EntryTypeData, - FieldTypeData, -}; +use base_db::{util::RenderedObject, Workspace}; +use completion::{ArgumentData, CompletionItem, CompletionItemData, EntryTypeData, FieldTypeData}; use line_index::LineIndex; -use lsp_types::{ClientCapabilities, ClientInfo, CompletionList}; +use rowan::ast::AstNode; use serde::{Deserialize, Serialize}; +use syntax::bibtex; -use crate::util::{ - capabilities::ClientCapabilitiesExt, line_index_ext::LineIndexExt, lsp_enums::Structure, -}; +use crate::util::{from_proto, line_index_ext::LineIndexExt, lsp_enums::Structure, ClientFlags}; pub fn complete( workspace: &Workspace, - params: &lsp_types::CompletionParams, - client_capabilities: &ClientCapabilities, - client_info: Option<&ClientInfo>, -) -> Option<CompletionList> { - let document = workspace.lookup(¶ms.text_document_position.text_document.uri)?; - let feature = FeatureParams::new(workspace, document); - let offset = document - .line_index - .offset_lsp(params.text_document_position.position)?; - - let params = CompletionParams { feature, offset }; + params: lsp_types::CompletionParams, + client_flags: &ClientFlags, +) -> Option<lsp_types::CompletionList> { + let params = from_proto::completion_params(workspace, params)?; let result = completion::complete(¶ms); - let mut list = CompletionList::default(); - let item_builder = ItemBuilder::new(document, client_capabilities); - let always_incomplete = client_info.map_or(false, |info| info.name == "Visual Studio Code"); - list.is_incomplete = always_incomplete || result.items.len() >= completion::LIMIT; - list.items = result + let item_builder = ItemBuilder { + line_index: ¶ms.feature.document.line_index, + client_flags, + }; + + let is_incomplete = + client_flags.completion_always_incomplete || result.items.len() >= completion::LIMIT; + + let items = result .items .into_iter() .enumerate() .filter_map(|(i, item)| item_builder.convert(item, i)) .collect(); - Some(list) + Some(lsp_types::CompletionList { + is_incomplete, + items, + }) +} + +pub fn resolve(workspace: &Workspace, item: &mut lsp_types::CompletionItem) -> Option<()> { + let data = from_proto::completion_resolve_info(item)?; + match data { + ResolveInfo::Package | ResolveInfo::DocumentClass => { + let metadata = completion_data::DATABASE.meta(&item.label)?; + let value = metadata.description.as_deref()?.into(); + item.documentation = Some(lsp_types::Documentation::MarkupContent( + lsp_types::MarkupContent { + kind: lsp_types::MarkupKind::PlainText, + value, + }, + )); + } + ResolveInfo::Citation { uri, key } => { + let data = workspace.lookup(&uri)?.data.as_bib()?; + let root = bibtex::Root::cast(data.root_node())?; + let entry = root.find_entry(&key)?; + let value = citeproc::render(&entry)?; + item.documentation = Some(lsp_types::Documentation::MarkupContent( + lsp_types::MarkupContent { + kind: lsp_types::MarkupKind::Markdown, + value, + }, + )); + } + } + + Some(()) } struct ItemBuilder<'a> { line_index: &'a LineIndex, - item_kinds: &'a [lsp_types::CompletionItemKind], - supports_snippets: bool, - supports_images: bool, + client_flags: &'a ClientFlags, } impl<'a> ItemBuilder<'a> { - pub fn new(document: &'a Document, client_capabilities: &'a ClientCapabilities) -> Self { - let line_index = &document.line_index; - let item_kinds = client_capabilities - .text_document - .as_ref() - .and_then(|cap| cap.completion.as_ref()) - .and_then(|cap| cap.completion_item_kind.as_ref()) - .and_then(|cap| cap.value_set.as_deref()) - .unwrap_or_default(); - - let supports_snippets = client_capabilities.has_snippet_support(); - let supports_images = client_capabilities.has_completion_markdown_support(); - Self { - line_index, - item_kinds, - supports_snippets, - supports_images, - } - } - pub fn convert(&self, item: CompletionItem, index: usize) -> Option<lsp_types::CompletionItem> { let mut result = lsp_types::CompletionItem::default(); let range = self.line_index.line_col_lsp_range(item.range)?; @@ -125,7 +129,7 @@ impl<'a> ItemBuilder<'a> { if result .kind - .is_some_and(|kind| !self.item_kinds.contains(&kind)) + .is_some_and(|kind| !self.client_flags.completion_kinds.contains(&kind)) { result.kind = Some(lsp_types::CompletionItemKind::TEXT); } @@ -166,7 +170,7 @@ impl<'a> ItemBuilder<'a> { result: &mut lsp_types::CompletionItem, range: lsp_types::Range, ) { - if self.supports_snippets { + if self.client_flags.completion_snippets { result.kind = Some(Structure::Snippet.completion_kind()); result.text_edit = Some(lsp_types::TextEdit::new(range, "begin{$1}\n\t$0\n\\end{$1}".into()).into()); @@ -386,7 +390,7 @@ impl<'a> ItemBuilder<'a> { } fn inline_image(&self, name: &str, base64: &str) -> Option<lsp_types::Documentation> { - if self.supports_images { + if self.client_flags.completion_markdown { let kind = lsp_types::MarkupKind::Markdown; let value = format!("![{name}](data:image/png;base64,{base64}|width=48,height=48)"); let content = lsp_types::MarkupContent { kind, value }; diff --git a/support/texlab/crates/texlab/src/features/definition.rs b/support/texlab/crates/texlab/src/features/definition.rs index ec7f08c296..098505079c 100644 --- a/support/texlab/crates/texlab/src/features/definition.rs +++ b/support/texlab/crates/texlab/src/features/definition.rs @@ -1,49 +1,16 @@ -use base_db::{FeatureParams, Workspace}; -use definition::DefinitionParams; -use lsp_types::{GotoDefinitionResponse, LocationLink, Position, Url}; +use base_db::Workspace; -use crate::util::line_index_ext::LineIndexExt; +use crate::util::{from_proto, to_proto}; pub fn goto_definition( workspace: &Workspace, - uri: &Url, - position: Position, -) -> Option<GotoDefinitionResponse> { - let document = workspace.lookup(uri)?; - let offset = document.line_index.offset_lsp(position)?; - let feature = FeatureParams::new(workspace, document); - let params = DefinitionParams { feature, offset }; + params: lsp_types::GotoDefinitionParams, +) -> Option<lsp_types::GotoDefinitionResponse> { + let params = from_proto::definition_params(workspace, params)?; + let links = definition::goto_definition(¶ms) + .into_iter() + .filter_map(|result| to_proto::location_link(result, ¶ms.feature.document.line_index)) + .collect(); - let mut links = Vec::new(); - for result in definition::goto_definition(params) { - if let Some(link) = convert_link(document, result) { - links.push(link); - } - } - - Some(GotoDefinitionResponse::Link(links)) -} - -fn convert_link( - document: &base_db::Document, - result: definition::DefinitionResult<'_>, -) -> Option<LocationLink> { - let origin_selection_range = Some( - document - .line_index - .line_col_lsp_range(result.origin_selection_range)?, - ); - - let target_line_index = &result.target.line_index; - let target_uri = result.target.uri.clone(); - let target_range = target_line_index.line_col_lsp_range(result.target_range)?; - let target_selection_range = - target_line_index.line_col_lsp_range(result.target_selection_range)?; - let value = LocationLink { - origin_selection_range, - target_uri, - target_range, - target_selection_range, - }; - Some(value) + Some(lsp_types::GotoDefinitionResponse::Link(links)) } diff --git a/support/texlab/crates/texlab/src/features/folding.rs b/support/texlab/crates/texlab/src/features/folding.rs index 6bdfc3a309..b19fa8a4da 100644 --- a/support/texlab/crates/texlab/src/features/folding.rs +++ b/support/texlab/crates/texlab/src/features/folding.rs @@ -1,46 +1,20 @@ use base_db::Workspace; -use folding::FoldingRangeKind; -use lsp_types::{ClientCapabilities, Url}; -use crate::util::line_index_ext::LineIndexExt; +use crate::util::{from_proto, to_proto, ClientFlags}; pub fn find_all( workspace: &Workspace, - uri: &Url, - capabilities: &ClientCapabilities, + params: lsp_types::FoldingRangeParams, + client_flags: &ClientFlags, ) -> Option<Vec<serde_json::Value>> { - let custom_kinds = capabilities - .text_document - .as_ref() - .and_then(|cap| cap.folding_range.as_ref()) - .and_then(|cap| cap.folding_range_kind.as_ref()) - .and_then(|cap| cap.value_set.as_ref()) - .is_some(); + let params = from_proto::feature_params(workspace, params.text_document)?; - let document = workspace.lookup(uri)?; - let foldings = folding::find_all(document) + let foldings = folding::find_all(¶ms.document) .into_iter() .filter_map(|folding| { - let range = document.line_index.line_col_lsp_range(folding.range)?; + to_proto::folding_range(folding, ¶ms.document.line_index, client_flags) + }) + .collect(); - let kind = if custom_kinds { - Some(match folding.kind { - FoldingRangeKind::Section => "section", - FoldingRangeKind::Environment => "environment", - FoldingRangeKind::Entry => "entry", - }) - } else { - None - }; - - Some(serde_json::json!({ - "startLine": range.start.line, - "startCharacter": range.start.character, - "endLine": range.end.line, - "endCharacter": range.end.character, - "kind": kind, - })) - }); - - Some(foldings.collect()) + Some(foldings) } diff --git a/support/texlab/crates/texlab/src/features/formatting.rs b/support/texlab/crates/texlab/src/features/formatting.rs index 9073b04aa3..3fcd66e39b 100644 --- a/support/texlab/crates/texlab/src/features/formatting.rs +++ b/support/texlab/crates/texlab/src/features/formatting.rs @@ -3,15 +3,14 @@ mod latexindent; use base_db::{Formatter, Workspace}; use distro::Language; -use lsp_types::{FormattingOptions, TextEdit, Url}; use self::{bibtex_internal::format_bibtex_internal, latexindent::format_with_latexindent}; pub fn format_source_code( workspace: &Workspace, - uri: &Url, - options: &FormattingOptions, -) -> Option<Vec<TextEdit>> { + uri: &lsp_types::Url, + options: &lsp_types::FormattingOptions, +) -> Option<Vec<lsp_types::TextEdit>> { let document = workspace.lookup(uri)?; match document.language { Language::Tex => match workspace.config().formatting.tex_formatter { diff --git a/support/texlab/crates/texlab/src/features/formatting/bibtex_internal.rs b/support/texlab/crates/texlab/src/features/formatting/bibtex_internal.rs index 8d5894c256..3a441a5292 100644 --- a/support/texlab/crates/texlab/src/features/formatting/bibtex_internal.rs +++ b/support/texlab/crates/texlab/src/features/formatting/bibtex_internal.rs @@ -1,5 +1,4 @@ use base_db::{Document, Workspace}; -use lsp_types::{FormattingOptions, TextEdit}; use rowan::TextLen; use crate::util::line_index_ext::LineIndexExt; @@ -7,8 +6,8 @@ use crate::util::line_index_ext::LineIndexExt; pub fn format_bibtex_internal( workspace: &Workspace, document: &Document, - options: &FormattingOptions, -) -> Option<Vec<TextEdit>> { + options: &lsp_types::FormattingOptions, +) -> Option<Vec<lsp_types::TextEdit>> { let data = document.data.as_bib()?; let options = bibfmt::Options { insert_spaces: options.insert_spaces, diff --git a/support/texlab/crates/texlab/src/features/formatting/latexindent.rs b/support/texlab/crates/texlab/src/features/formatting/latexindent.rs index 510bd80207..061c360cf5 100644 --- a/support/texlab/crates/texlab/src/features/formatting/latexindent.rs +++ b/support/texlab/crates/texlab/src/features/formatting/latexindent.rs @@ -5,7 +5,6 @@ use std::{ use base_db::{Document, LatexIndentConfig, Workspace}; use distro::Language; -use lsp_types::{Position, TextEdit}; use rowan::TextLen; use tempfile::tempdir; @@ -14,7 +13,7 @@ use crate::util::line_index_ext::LineIndexExt; pub fn format_with_latexindent( workspace: &Workspace, document: &Document, -) -> Option<Vec<TextEdit>> { +) -> Option<Vec<lsp_types::TextEdit>> { let config = workspace.config(); let target_dir = tempdir().ok()?; let source_dir = workspace.current_dir(&document.dir).to_file_path().ok()?; @@ -51,9 +50,9 @@ pub fn format_with_latexindent( None } else { let line_index = &document.line_index; - let start = Position::new(0, 0); + let start = lsp_types::Position::new(0, 0); let end = line_index.line_col_lsp(old_text.text_len())?; - Some(vec![TextEdit { + Some(vec![lsp_types::TextEdit { range: lsp_types::Range::new(start, end), new_text, }]) diff --git a/support/texlab/crates/texlab/src/features/highlight.rs b/support/texlab/crates/texlab/src/features/highlight.rs index 10c5d95fb1..4706e3964e 100644 --- a/support/texlab/crates/texlab/src/features/highlight.rs +++ b/support/texlab/crates/texlab/src/features/highlight.rs @@ -1,27 +1,15 @@ -use base_db::{FeatureParams, Workspace}; -use highlights::{HighlightKind, HighlightParams}; +use base_db::Workspace; -use crate::util::line_index_ext::LineIndexExt; +use crate::util::{from_proto, to_proto}; pub fn find_all( workspace: &Workspace, - params: &lsp_types::DocumentHighlightParams, + params: lsp_types::DocumentHighlightParams, ) -> Option<Vec<lsp_types::DocumentHighlight>> { - let uri = ¶ms.text_document_position_params.text_document.uri; - let document = workspace.lookup(uri)?; - let position = params.text_document_position_params.position; - let offset = document.line_index.offset_lsp(position)?; - let feature = FeatureParams::new(workspace, document); - let params = HighlightParams { feature, offset }; - let results = highlights::find_all(params); + let params = from_proto::highlight_params(workspace, params)?; + let results = highlights::find_all(¶ms); let results = results.into_iter().filter_map(|result| { - let range = document.line_index.line_col_lsp_range(result.range)?; - let kind = Some(match result.kind { - HighlightKind::Write => lsp_types::DocumentHighlightKind::WRITE, - HighlightKind::Read => lsp_types::DocumentHighlightKind::READ, - }); - - Some(lsp_types::DocumentHighlight { range, kind }) + to_proto::document_highlight(result, ¶ms.feature.document.line_index) }); Some(results.collect()) diff --git a/support/texlab/crates/texlab/src/features/hover.rs b/support/texlab/crates/texlab/src/features/hover.rs index 1aac645488..725f9ae936 100644 --- a/support/texlab/crates/texlab/src/features/hover.rs +++ b/support/texlab/crates/texlab/src/features/hover.rs @@ -1,44 +1,9 @@ -use base_db::{FeatureParams, Workspace}; -use hover::{HoverData, HoverParams}; +use base_db::Workspace; -use crate::util::line_index_ext::LineIndexExt; +use crate::util::{from_proto, to_proto}; pub fn find(workspace: &Workspace, params: lsp_types::HoverParams) -> Option<lsp_types::Hover> { - let uri_and_pos = ¶ms.text_document_position_params; - let document = workspace.lookup(&uri_and_pos.text_document.uri)?; - let feature = FeatureParams::new(workspace, document); - let offset = document.line_index.offset_lsp(uri_and_pos.position)?; - let hover = ::hover::find(HoverParams { feature, offset })?; - - let contents = match hover.data { - HoverData::Citation(text) => lsp_types::MarkupContent { - kind: lsp_types::MarkupKind::Markdown, - value: text, - }, - HoverData::Package(description) => lsp_types::MarkupContent { - kind: lsp_types::MarkupKind::PlainText, - value: description.into(), - }, - HoverData::EntryType(type_) => lsp_types::MarkupContent { - kind: lsp_types::MarkupKind::Markdown, - value: type_.documentation?.into(), - }, - HoverData::FieldType(type_) => lsp_types::MarkupContent { - kind: lsp_types::MarkupKind::Markdown, - value: type_.documentation.into(), - }, - HoverData::Label(label) => lsp_types::MarkupContent { - kind: lsp_types::MarkupKind::PlainText, - value: label.reference(), - }, - HoverData::StringRef(text) => lsp_types::MarkupContent { - kind: lsp_types::MarkupKind::PlainText, - value: text, - }, - }; - - Some(lsp_types::Hover { - contents: lsp_types::HoverContents::Markup(contents), - range: document.line_index.line_col_lsp_range(hover.range), - }) + let params = from_proto::hover_params(workspace, params)?; + let hover = ::hover::find(¶ms)?; + to_proto::hover(hover, ¶ms.feature.document.line_index) } diff --git a/support/texlab/crates/texlab/src/features/inlay_hint.rs b/support/texlab/crates/texlab/src/features/inlay_hint.rs index 44ed0757a5..ddd8ff26d8 100644 --- a/support/texlab/crates/texlab/src/features/inlay_hint.rs +++ b/support/texlab/crates/texlab/src/features/inlay_hint.rs @@ -1,67 +1,16 @@ -use base_db::{util::RenderedObject, FeatureParams, Workspace}; -use inlay_hints::{InlayHintData, InlayHintParams}; +use base_db::Workspace; -use crate::util::line_index_ext::LineIndexExt; +use crate::util::{from_proto, to_proto}; pub fn find_all( workspace: &Workspace, - uri: &lsp_types::Url, - range: lsp_types::Range, + params: lsp_types::InlayHintParams, ) -> Option<Vec<lsp_types::InlayHint>> { - let document = workspace.lookup(uri)?; - let line_index = &document.line_index; - let range = line_index.offset_lsp_range(range)?; + let params = from_proto::inlay_hint_params(workspace, params)?; + let hints = inlay_hints::find_all(¶ms)? + .into_iter() + .filter_map(|hint| to_proto::inlay_hint(hint, ¶ms.feature.document.line_index)) + .collect(); - let feature = FeatureParams::new(workspace, document); - let params = InlayHintParams { range, feature }; - let hints = inlay_hints::find_all(params)?; - let hints = hints.into_iter().filter_map(|hint| { - let position = line_index.line_col_lsp(hint.offset)?; - Some(match hint.data { - InlayHintData::LabelDefinition(label) => { - let number = label.number?; - - let text = match &label.object { - RenderedObject::Section { prefix, .. } => { - format!("{} {}", prefix, number) - } - RenderedObject::Float { kind, .. } => { - format!("{} {}", kind.as_str(), number) - } - RenderedObject::Theorem { kind, .. } => { - format!("{} {}", kind, number) - } - RenderedObject::Equation => format!("Equation ({})", number), - RenderedObject::EnumItem => format!("Item {}", number), - }; - - lsp_types::InlayHint { - position, - label: lsp_types::InlayHintLabel::String(format!(" {text} ")), - kind: None, - text_edits: None, - tooltip: None, - padding_left: Some(true), - padding_right: None, - data: None, - } - } - InlayHintData::LabelReference(label) => { - let text = label.reference(); - - lsp_types::InlayHint { - position, - label: lsp_types::InlayHintLabel::String(format!(" {text} ")), - kind: None, - text_edits: None, - tooltip: None, - padding_left: Some(true), - padding_right: None, - data: None, - } - } - }) - }); - - Some(hints.collect()) + Some(hints) } diff --git a/support/texlab/crates/texlab/src/features/link.rs b/support/texlab/crates/texlab/src/features/link.rs index b821dcf6e1..5b4de5f705 100644 --- a/support/texlab/crates/texlab/src/features/link.rs +++ b/support/texlab/crates/texlab/src/features/link.rs @@ -1,20 +1,16 @@ -use base_db::{FeatureParams, Workspace}; -use lsp_types::{DocumentLink, Url}; +use base_db::Workspace; -use crate::util::line_index_ext::LineIndexExt; +use crate::util::{from_proto, to_proto}; -pub fn find_all(workspace: &Workspace, uri: &Url) -> Option<Vec<DocumentLink>> { - let document = workspace.lookup(uri)?; +pub fn find_all( + workspace: &Workspace, + params: lsp_types::DocumentLinkParams, +) -> Option<Vec<lsp_types::DocumentLink>> { + let params = from_proto::feature_params(workspace, params.text_document)?; + let links = links::find_links(¶ms) + .into_iter() + .filter_map(|link| to_proto::document_link(link, ¶ms.document.line_index)) + .collect(); - let links = links::find_links(FeatureParams::new(workspace, document)).into_iter(); - let links = links.filter_map(|link| { - Some(lsp_types::DocumentLink { - data: None, - tooltip: None, - target: Some(link.document.uri.clone()), - range: document.line_index.line_col_lsp_range(link.range)?, - }) - }); - - Some(links.collect()) + Some(links) } diff --git a/support/texlab/crates/texlab/src/features/reference.rs b/support/texlab/crates/texlab/src/features/reference.rs index 380471e6cf..823e794d08 100644 --- a/support/texlab/crates/texlab/src/features/reference.rs +++ b/support/texlab/crates/texlab/src/features/reference.rs @@ -1,34 +1,17 @@ -use base_db::{FeatureParams, Workspace}; -use references::{ReferenceKind, ReferenceParams}; +use base_db::Workspace; -use crate::util::line_index_ext::LineIndexExt; +use crate::util::{from_proto, to_proto}; pub fn find_all( workspace: &Workspace, params: lsp_types::ReferenceParams, ) -> Option<Vec<lsp_types::Location>> { - let uri_and_pos = params.text_document_position; - let include_declaration = params.context.include_declaration; + let params = from_proto::reference_params(workspace, params)?; - let document = workspace.lookup(&uri_and_pos.text_document.uri)?; - let offset = document.line_index.offset_lsp(uri_and_pos.position)?; - - let feature = FeatureParams::new(workspace, document); - let mut results = Vec::new(); - for result in references::find_all(ReferenceParams { feature, offset }) + let results = references::find_all(¶ms) .into_iter() - .filter(|result| result.kind == ReferenceKind::Reference || include_declaration) - { - let document = result.location.document; - let uri = document.uri.clone(); - if let Some(range) = document - .line_index - .line_col_lsp_range(result.location.range) - { - let location = lsp_types::Location::new(uri, range); - results.push(location); - } - } + .filter_map(to_proto::location) + .collect(); Some(results) } diff --git a/support/texlab/crates/texlab/src/features/rename.rs b/support/texlab/crates/texlab/src/features/rename.rs index 297547e7e5..b905a7105b 100644 --- a/support/texlab/crates/texlab/src/features/rename.rs +++ b/support/texlab/crates/texlab/src/features/rename.rs @@ -1,50 +1,22 @@ -use std::collections::HashMap; +use base_db::Workspace; -use base_db::{FeatureParams, Workspace}; -use rename::RenameParams; - -use crate::util::line_index_ext::LineIndexExt; +use crate::util::{from_proto, line_index_ext::LineIndexExt, to_proto}; pub fn prepare_rename_all( workspace: &Workspace, - params: &lsp_types::TextDocumentPositionParams, + params: lsp_types::TextDocumentPositionParams, ) -> Option<lsp_types::Range> { - let params = create_params(workspace, params)?; + let params = from_proto::rename_params(workspace, params)?; let range = rename::prepare_rename(¶ms)?; params.feature.document.line_index.line_col_lsp_range(range) } pub fn rename_all( workspace: &Workspace, - params: &lsp_types::RenameParams, + params: lsp_types::RenameParams, ) -> Option<lsp_types::WorkspaceEdit> { let new_name = ¶ms.new_name; - let params = create_params(workspace, ¶ms.text_document_position)?; + let params = from_proto::rename_params(workspace, params.text_document_position)?; let result = rename::rename(params); - - let mut changes = HashMap::default(); - for (document, ranges) in result.changes { - let mut edits = Vec::new(); - ranges - .into_iter() - .filter_map(|range| document.line_index.line_col_lsp_range(range)) - .for_each(|range| edits.push(lsp_types::TextEdit::new(range, new_name.clone()))); - - changes.insert(document.uri.clone(), edits); - } - - Some(lsp_types::WorkspaceEdit::new(changes)) -} - -fn create_params<'db>( - workspace: &'db Workspace, - params: &lsp_types::TextDocumentPositionParams, -) -> Option<RenameParams<'db>> { - let document = workspace.lookup(¶ms.text_document.uri)?; - let inner = FeatureParams::new(workspace, document); - let offset = document.line_index.offset_lsp(params.position)?; - Some(RenameParams { - feature: inner, - offset, - }) + Some(to_proto::workspace_edit(result, &new_name)) } diff --git a/support/texlab/crates/texlab/src/features/symbols.rs b/support/texlab/crates/texlab/src/features/symbols.rs index 65e92a5b17..8e29eb9a9c 100644 --- a/support/texlab/crates/texlab/src/features/symbols.rs +++ b/support/texlab/crates/texlab/src/features/symbols.rs @@ -1,112 +1,27 @@ -use base_db::{data::BibtexEntryTypeCategory, Document, Workspace}; -use lsp_types::{ - ClientCapabilities, DocumentSymbol, DocumentSymbolResponse, Location, WorkspaceSymbolResponse, -}; +use base_db::Workspace; -use crate::util::{capabilities::ClientCapabilitiesExt, line_index_ext::LineIndexExt}; +use crate::util::{from_proto, to_proto, ClientFlags}; pub fn document_symbols( workspace: &Workspace, - document: &Document, - capabilities: &ClientCapabilities, -) -> DocumentSymbolResponse { - let symbols = symbols::document_symbols(workspace, document); - if capabilities.has_hierarchical_document_symbol_support() { - let results = symbols - .into_iter() - .filter_map(|symbol| convert_to_nested_symbol(symbol, document)) - .collect(); - - DocumentSymbolResponse::Nested(results) - } else { - let mut results = Vec::new(); - for symbol in symbols { - convert_to_flat_symbols(symbol, document, &mut results); - } - - DocumentSymbolResponse::Flat(results) - } + params: lsp_types::DocumentSymbolParams, + client_flags: &ClientFlags, +) -> Option<lsp_types::DocumentSymbolResponse> { + let params = from_proto::feature_params(workspace, params.text_document)?; + let symbols = symbols::document_symbols(workspace, params.document); + Some(to_proto::document_symbol_response( + params.document, + symbols, + client_flags, + )) } -pub fn workspace_symbols(workspace: &Workspace, query: &str) -> WorkspaceSymbolResponse { +pub fn workspace_symbols(workspace: &Workspace, query: &str) -> lsp_types::WorkspaceSymbolResponse { let symbols = symbols::workspace_symbols(workspace, query); let mut results = Vec::new(); for symbols::SymbolLocation { symbol, document } in symbols { - convert_to_flat_symbols(symbol, document, &mut results); - } - - WorkspaceSymbolResponse::Flat(results) -} - -fn convert_to_nested_symbol( - symbol: symbols::Symbol, - document: &Document, -) -> Option<DocumentSymbol> { - let children = symbol - .children - .into_iter() - .filter_map(|child| convert_to_nested_symbol(child, document)) - .collect(); - - #[allow(deprecated)] - Some(DocumentSymbol { - name: symbol.name, - detail: symbol.label.map(|label| label.text), - kind: convert_symbol_kind(symbol.kind), - deprecated: Some(false), - range: document.line_index.line_col_lsp_range(symbol.full_range)?, - selection_range: document - .line_index - .line_col_lsp_range(symbol.selection_range)?, - children: Some(children), - tags: None, - }) -} - -fn convert_to_flat_symbols( - symbol: symbols::Symbol, - document: &Document, - results: &mut Vec<lsp_types::SymbolInformation>, -) { - let Some(range) = document.line_index.line_col_lsp_range(symbol.full_range) else { - return; - }; - - #[allow(deprecated)] - results.push(lsp_types::SymbolInformation { - name: symbol.name, - kind: convert_symbol_kind(symbol.kind), - deprecated: Some(false), - location: Location::new(document.uri.clone(), range), - tags: None, - container_name: None, - }); - - for child in symbol.children { - convert_to_flat_symbols(child, document, results); + to_proto::symbol_information(symbol, document, &mut results); } -} -fn convert_symbol_kind(value: symbols::SymbolKind) -> lsp_types::SymbolKind { - match value { - symbols::SymbolKind::Section => lsp_types::SymbolKind::MODULE, - symbols::SymbolKind::Figure => lsp_types::SymbolKind::METHOD, - symbols::SymbolKind::Algorithm => lsp_types::SymbolKind::METHOD, - symbols::SymbolKind::Table => lsp_types::SymbolKind::METHOD, - symbols::SymbolKind::Listing => lsp_types::SymbolKind::METHOD, - symbols::SymbolKind::Enumeration => lsp_types::SymbolKind::ENUM, - symbols::SymbolKind::EnumerationItem => lsp_types::SymbolKind::ENUM_MEMBER, - symbols::SymbolKind::Theorem => lsp_types::SymbolKind::VARIABLE, - symbols::SymbolKind::Equation => lsp_types::SymbolKind::CONSTANT, - symbols::SymbolKind::Entry(category) => match category { - BibtexEntryTypeCategory::Misc => lsp_types::SymbolKind::INTERFACE, - BibtexEntryTypeCategory::String => lsp_types::SymbolKind::STRING, - BibtexEntryTypeCategory::Article => lsp_types::SymbolKind::EVENT, - BibtexEntryTypeCategory::Thesis => lsp_types::SymbolKind::OBJECT, - BibtexEntryTypeCategory::Book => lsp_types::SymbolKind::STRUCT, - BibtexEntryTypeCategory::Part => lsp_types::SymbolKind::OPERATOR, - BibtexEntryTypeCategory::Collection => lsp_types::SymbolKind::TYPE_PARAMETER, - }, - symbols::SymbolKind::Field => lsp_types::SymbolKind::FIELD, - } + lsp_types::WorkspaceSymbolResponse::Flat(results) } diff --git a/support/texlab/crates/texlab/src/lib.rs b/support/texlab/crates/texlab/src/lib.rs index 19ef5ba8a9..50bddc3185 100644 --- a/support/texlab/crates/texlab/src/lib.rs +++ b/support/texlab/crates/texlab/src/lib.rs @@ -1,6 +1,6 @@ mod client; -pub mod features; +pub(crate) mod features; mod server; -pub mod util; +pub(crate) mod util; pub use self::{client::LspClient, server::Server}; diff --git a/support/texlab/crates/texlab/src/main.rs b/support/texlab/crates/texlab/src/main.rs index d588a568bc..08977ae96a 100644 --- a/support/texlab/crates/texlab/src/main.rs +++ b/support/texlab/crates/texlab/src/main.rs @@ -28,7 +28,7 @@ fn main() -> Result<()> { setup_logger(opts); let (connection, threads) = Connection::stdio(); - Server::new(connection).run()?; + Server::exec(connection)?; threads.join()?; Ok(()) diff --git a/support/texlab/crates/texlab/src/server.rs b/support/texlab/crates/texlab/src/server.rs index f06984d596..0fb186f688 100644 --- a/support/texlab/crates/texlab/src/server.rs +++ b/support/texlab/crates/texlab/src/server.rs @@ -14,7 +14,6 @@ use anyhow::Result; use base_db::{Config, Owner, Workspace}; use commands::{BuildCommand, CleanCommand, CleanTarget, ForwardSearch}; use crossbeam_channel::{Receiver, Sender}; -use diagnostics::{DiagnosticManager, DiagnosticSource}; use distro::{Distro, Language}; use line_index::LineCol; use lsp_server::{Connection, ErrorCode, Message, RequestId}; @@ -22,22 +21,17 @@ use lsp_types::{notification::*, request::*, *}; use notify::event::ModifyKind; use notify_debouncer_full::{DebouncedEvent, Debouncer, FileIdMap}; use parking_lot::{Mutex, RwLock}; -use rowan::ast::AstNode; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashSet; use serde::{de::DeserializeOwned, Serialize}; -use syntax::bibtex; use threadpool::ThreadPool; use crate::{ client::LspClient, features::{ - completion::{self, ResolveInfo}, - definition, folding, formatting, highlight, hover, inlay_hint, link, reference, rename, - symbols, - }, - util::{ - self, capabilities::ClientCapabilitiesExt, line_index_ext::LineIndexExt, normalize_uri, + completion, definition, folding, formatting, highlight, hover, inlay_hint, link, reference, + rename, symbols, }, + util::{from_proto, line_index_ext::LineIndexExt, normalize_uri, to_proto, ClientFlags}, }; use self::{ @@ -55,7 +49,7 @@ enum InternalMessage { SetOptions(Options), FileEvent(Vec<DebouncedEvent>), Diagnostics, - ChktexResult(Url, Vec<lsp_types::Diagnostic>), + ChktexFinished(Url, Vec<diagnostics::Diagnostic>), ForwardSearch(Url, Option<Position>), } @@ -65,70 +59,68 @@ pub struct Server { internal_rx: Receiver<InternalMessage>, workspace: Arc<RwLock<Workspace>>, client: LspClient, - client_capabilities: Arc<ClientCapabilities>, - client_info: Option<Arc<ClientInfo>>, - diagnostic_manager: DiagnosticManager, - chktex_diagnostics: FxHashMap<Url, Vec<Diagnostic>>, + client_flags: Arc<ClientFlags>, + diagnostic_manager: diagnostics::Manager, watcher: FileWatcher, pool: ThreadPool, pending_builds: Arc<Mutex<FxHashSet<u32>>>, } impl Server { - pub fn new(connection: Connection) -> Self { + pub fn exec(connection: Connection) -> Result<()> { let client = LspClient::new(connection.sender.clone()); let (internal_tx, internal_rx) = crossbeam_channel::unbounded(); let watcher = FileWatcher::new(internal_tx.clone()).expect("init file watcher"); - Self { + let mut workspace = Workspace::default(); + + let (id, params) = connection.initialize_start()?; + let params: InitializeParams = serde_json::from_value(params)?; + + let workspace_folders = params + .workspace_folders + .unwrap_or_default() + .into_iter() + .filter(|folder| folder.uri.scheme() == "file") + .flat_map(|folder| folder.uri.to_file_path()) + .collect(); + + workspace.set_folders(workspace_folders); + + let result = InitializeResult { + capabilities: Self::capabilities(), + server_info: Some(ServerInfo { + name: "TexLab".to_owned(), + version: Some(env!("CARGO_PKG_VERSION").to_owned()), + }), + }; + + connection.initialize_finish(id, serde_json::to_value(result)?)?; + + let server = Self { connection: Arc::new(connection), internal_tx, internal_rx, - workspace: Default::default(), + workspace: Arc::new(RwLock::new(workspace)), client, - client_capabilities: Default::default(), - client_info: Default::default(), - chktex_diagnostics: Default::default(), - diagnostic_manager: DiagnosticManager::default(), + client_flags: Arc::new(from_proto::client_flags( + params.capabilities, + params.client_info, + )), + diagnostic_manager: diagnostics::Manager::default(), watcher, pool: threadpool::Builder::new().build(), pending_builds: Default::default(), - } - } + }; - fn run_query<R, Q>(&self, id: RequestId, query: Q) - where - R: Serialize, - Q: FnOnce(&Workspace) -> R + Send + 'static, - { - let client = self.client.clone(); - let workspace = Arc::clone(&self.workspace); - self.pool.execute(move || { - let response = lsp_server::Response::new_ok(id, query(&workspace.read())); - client.send_response(response).unwrap(); - }); - } + let options = serde_json::from_value(params.initialization_options.unwrap_or_default()) + .unwrap_or_default(); - fn run_fallible<R, Q>(&self, id: RequestId, query: Q) - where - R: Serialize, - Q: FnOnce() -> Result<R> + Send + 'static, - { - let client = self.client.clone(); - self.pool.execute(move || match query() { - Ok(result) => { - let response = lsp_server::Response::new_ok(id, result); - client.send_response(response).unwrap(); - } - Err(why) => { - client - .send_error(id, ErrorCode::InternalError, why.to_string()) - .unwrap(); - } - }); + server.run(options)?; + Ok(()) } - fn capabilities(&self) -> ServerCapabilities { + fn capabilities() -> ServerCapabilities { ServerCapabilities { text_document_sync: Some(TextDocumentSyncCapability::Options( TextDocumentSyncOptions { @@ -185,57 +177,40 @@ impl Server { } } - fn initialize(&mut self) -> Result<()> { - let (id, params) = self.connection.initialize_start()?; - let params: InitializeParams = serde_json::from_value(params)?; - - self.client_capabilities = Arc::new(params.capabilities); - self.client_info = params.client_info.map(Arc::new); - - let workspace_folders = params - .workspace_folders - .unwrap_or_default() - .into_iter() - .filter(|folder| folder.uri.scheme() == "file") - .flat_map(|folder| folder.uri.to_file_path()) - .collect(); - - self.workspace.write().set_folders(workspace_folders); - - let result = InitializeResult { - capabilities: self.capabilities(), - server_info: Some(ServerInfo { - name: "TexLab".to_owned(), - version: Some(env!("CARGO_PKG_VERSION").to_owned()), - }), - }; - self.connection - .initialize_finish(id, serde_json::to_value(result)?)?; - - let StartupOptions { skip_distro } = - serde_json::from_value(params.initialization_options.unwrap_or_default()) - .unwrap_or_default(); - - if !skip_distro { - let sender = self.internal_tx.clone(); - self.pool.execute(move || { - let distro = Distro::detect().unwrap_or_else(|why| { - log::warn!("Unable to load distro files: {}", why); - Distro::default() - }); - - log::info!("Detected distribution: {:?}", distro.kind); - sender.send(InternalMessage::SetDistro(distro)).unwrap(); - }); - } + fn run_query<R, Q>(&self, id: RequestId, query: Q) + where + R: Serialize, + Q: FnOnce(&Workspace) -> R + Send + 'static, + { + let client = self.client.clone(); + let workspace = Arc::clone(&self.workspace); + self.pool.execute(move || { + let response = lsp_server::Response::new_ok(id, query(&workspace.read())); + client.send_response(response).unwrap(); + }); + } - self.register_configuration(); - self.pull_options(); - Ok(()) + fn run_fallible<R, Q>(&self, id: RequestId, query: Q) + where + R: Serialize, + Q: FnOnce() -> Result<R> + Send + 'static, + { + let client = self.client.clone(); + self.pool.execute(move || match query() { + Ok(result) => { + let response = lsp_server::Response::new_ok(id, result); + client.send_response(response).unwrap(); + } + Err(why) => { + client + .send_error(id, ErrorCode::InternalError, why.to_string()) + .unwrap(); + } + }); } fn register_configuration(&mut self) { - if self.client_capabilities.has_push_configuration_support() { + if self.client_flags.configuration_push { let registration = Registration { id: "pull-config".to_string(), method: DidChangeConfiguration::METHOD.to_string(), @@ -269,7 +244,7 @@ impl Server { .iter() .filter_map(|path| workspace.lookup_path(path)) { - self.diagnostic_manager.update(&workspace, document); + self.diagnostic_manager.update_syntax(&workspace, document); } drop(workspace); @@ -279,23 +254,16 @@ impl Server { fn publish_diagnostics(&mut self) -> Result<()> { let workspace = self.workspace.read(); - let mut all_diagnostics = - util::diagnostics::collect(&workspace, &mut self.diagnostic_manager); - - for (uri, diagnostics) in &self.chktex_diagnostics { - let Some(document) = workspace.lookup(uri) else { - continue; - }; - let Some(existing) = all_diagnostics.get_mut(document) else { + for (uri, diagnostics) in self.diagnostic_manager.get(&workspace) { + let Some(document) = workspace.lookup(&uri) else { continue; }; - existing.extend(diagnostics.iter().cloned()); - } - util::diagnostics::filter(&mut all_diagnostics, &workspace); + let diagnostics = diagnostics + .into_iter() + .filter_map(|diagnostic| to_proto::diagnostic(&workspace, document, &diagnostic)) + .collect(); - for (document, diagnostics) in all_diagnostics { - let uri = document.uri.clone(); let version = None; let params = PublishDiagnosticsParams { uri, @@ -320,7 +288,7 @@ impl Server { } fn pull_options(&mut self) { - if !self.client_capabilities.has_pull_configuration_support() { + if !self.client_flags.configuration_pull { return; } @@ -364,7 +332,7 @@ impl Server { } fn did_change_configuration(&mut self, params: DidChangeConfigurationParams) -> Result<()> { - if self.client_capabilities.has_pull_configuration_support() { + if self.client_flags.configuration_pull { self.pull_options(); } else { let options = self.client.parse_options(params.settings)?; @@ -392,7 +360,7 @@ impl Server { let workspace = self.workspace.read(); self.diagnostic_manager - .update(&workspace, workspace.lookup(&uri).unwrap()); + .update_syntax(&workspace, workspace.lookup(&uri).unwrap()); if workspace.config().diagnostics.chktex.on_open { drop(workspace); @@ -435,7 +403,7 @@ impl Server { } self.diagnostic_manager - .update(&workspace, workspace.lookup(&uri).unwrap()); + .update_syntax(&workspace, workspace.lookup(&uri).unwrap()); drop(workspace); self.update_workspace(); @@ -478,45 +446,38 @@ impl Server { Ok(()) } - fn run_chktex(&mut self, uri: &Url) { + fn run_chktex(&mut self, uri: &Url) -> Option<()> { let workspace = self.workspace.read(); - let Some(document) = workspace.lookup(uri) else { - return; - }; - let Some(command) = util::chktex::Command::new(&workspace, document) else { - return; - }; + + let document = workspace.lookup(uri)?; + let command = diagnostics::chktex::Command::new(&workspace, document)?; let sender = self.internal_tx.clone(); let uri = document.uri.clone(); self.pool.execute(move || { let diagnostics = command.run().unwrap_or_default(); sender - .send(InternalMessage::ChktexResult(uri, diagnostics)) + .send(InternalMessage::ChktexFinished(uri, diagnostics)) .unwrap(); }); + + Some(()) } - fn document_link(&self, id: RequestId, params: DocumentLinkParams) -> Result<()> { - let mut uri = params.text_document.uri; - normalize_uri(&mut uri); + fn document_link(&self, id: RequestId, mut params: DocumentLinkParams) -> Result<()> { + normalize_uri(&mut params.text_document.uri); self.run_query(id, move |workspace| { - link::find_all(workspace, &uri).unwrap_or_default() + link::find_all(workspace, params).unwrap_or_default() }); Ok(()) } - fn document_symbols(&self, id: RequestId, params: DocumentSymbolParams) -> Result<()> { - let mut uri = params.text_document.uri; - normalize_uri(&mut uri); + fn document_symbols(&self, id: RequestId, mut params: DocumentSymbolParams) -> Result<()> { + normalize_uri(&mut params.text_document.uri); - let capabilities = Arc::clone(&self.client_capabilities); + let client_flags = Arc::clone(&self.client_flags); self.run_query(id, move |workspace| { - let Some(document) = workspace.lookup(&uri) else { - return DocumentSymbolResponse::Flat(vec![]); - }; - - symbols::document_symbols(workspace, document, &capabilities) + symbols::document_symbols(workspace, params, &client_flags) }); Ok(()) @@ -533,11 +494,10 @@ impl Server { fn completion(&self, id: RequestId, mut params: CompletionParams) -> Result<()> { normalize_uri(&mut params.text_document_position.text_document.uri); let position = params.text_document_position.position; - let client_capabilities = Arc::clone(&self.client_capabilities); - let client_info = self.client_info.clone(); + let client_flags = Arc::clone(&self.client_flags); self.update_cursor(¶ms.text_document_position.text_document.uri, position); self.run_query(id, move |db| { - completion::complete(db, ¶ms, &client_capabilities, client_info.as_deref()) + completion::complete(db, params, &client_flags) }); Ok(()) @@ -555,54 +515,20 @@ impl Server { fn completion_resolve(&self, id: RequestId, mut item: CompletionItem) -> Result<()> { self.run_query(id, move |workspace| { - match item - .data - .clone() - .map(|data| serde_json::from_value(data).unwrap()) - { - Some(ResolveInfo::Package | ResolveInfo::DocumentClass) => { - item.documentation = completion_data::DATABASE - .meta(&item.label) - .and_then(|meta| meta.description.as_deref()) - .map(|value| { - Documentation::MarkupContent(MarkupContent { - kind: MarkupKind::PlainText, - value: value.into(), - }) - }); - } - Some(ResolveInfo::Citation { uri, key }) => { - if let Some(data) = workspace - .lookup(&uri) - .and_then(|document| document.data.as_bib()) - { - item.documentation = bibtex::Root::cast(data.root_node()) - .and_then(|root| root.find_entry(&key)) - .and_then(|entry| citeproc::render(&entry)) - .map(|value| { - Documentation::MarkupContent(MarkupContent { - kind: MarkupKind::Markdown, - value, - }) - }); - } - } - None => {} - }; - + completion::resolve(workspace, &mut item); item }); Ok(()) } - fn folding_range(&self, id: RequestId, params: FoldingRangeParams) -> Result<()> { - let mut uri = params.text_document.uri; - normalize_uri(&mut uri); - let client_capabilities = Arc::clone(&self.client_capabilities); + fn folding_range(&self, id: RequestId, mut params: FoldingRangeParams) -> Result<()> { + normalize_uri(&mut params.text_document.uri); + let client_flags = Arc::clone(&self.client_flags); self.run_query(id, move |db| { - folding::find_all(db, &uri, &client_capabilities).unwrap_or_default() + folding::find_all(db, params, &client_flags).unwrap_or_default() }); + Ok(()) } @@ -623,33 +549,28 @@ impl Server { Ok(()) } - fn goto_definition(&self, id: RequestId, params: GotoDefinitionParams) -> Result<()> { - let mut uri = params.text_document_position_params.text_document.uri; - normalize_uri(&mut uri); - let position = params.text_document_position_params.position; - self.run_query(id, move |db| { - definition::goto_definition(db, &uri, position) - }); - + fn goto_definition(&self, id: RequestId, mut params: GotoDefinitionParams) -> Result<()> { + normalize_uri(&mut params.text_document_position_params.text_document.uri); + self.run_query(id, move |db| definition::goto_definition(db, params)); Ok(()) } fn prepare_rename(&self, id: RequestId, mut params: TextDocumentPositionParams) -> Result<()> { normalize_uri(&mut params.text_document.uri); - self.run_query(id, move |db| rename::prepare_rename_all(db, ¶ms)); + self.run_query(id, move |db| rename::prepare_rename_all(db, params)); Ok(()) } fn rename(&self, id: RequestId, mut params: RenameParams) -> Result<()> { normalize_uri(&mut params.text_document_position.text_document.uri); - self.run_query(id, move |db| rename::rename_all(db, ¶ms)); + self.run_query(id, move |db| rename::rename_all(db, params)); Ok(()) } fn document_highlight(&self, id: RequestId, mut params: DocumentHighlightParams) -> Result<()> { normalize_uri(&mut params.text_document_position_params.text_document.uri); self.run_query(id, move |db| { - highlight::find_all(db, ¶ms).unwrap_or_default() + highlight::find_all(db, params).unwrap_or_default() }); Ok(()) @@ -716,11 +637,10 @@ impl Server { Ok(()) } - fn inlay_hints(&self, id: RequestId, params: InlayHintParams) -> Result<()> { - let mut uri = params.text_document.uri; - normalize_uri(&mut uri); + fn inlay_hints(&self, id: RequestId, mut params: InlayHintParams) -> Result<()> { + normalize_uri(&mut params.text_document.uri); self.run_query(id, move |db| { - inlay_hint::find_all(db, &uri, params.range).unwrap_or_default() + inlay_hint::find_all(db, params).unwrap_or_default() }); Ok(()) } @@ -757,7 +677,7 @@ impl Server { let command = BuildCommand::new(&workspace, &uri); let internal = self.internal_tx.clone(); - let progress = self.client_capabilities.has_work_done_progress_support(); + let progress = self.client_flags.progress; let pending_builds = Arc::clone(&self.pending_builds); self.pool.execute(move || { @@ -896,7 +816,7 @@ impl Server { changed |= workspace.load(&path, language, Owner::Server).is_ok(); if let Some(document) = workspace.lookup_path(&path) { - self.diagnostic_manager.update(&workspace, document); + self.diagnostic_manager.update_syntax(&workspace, document); } } } @@ -1111,8 +1031,8 @@ impl Server { InternalMessage::Diagnostics => { self.publish_diagnostics()?; } - InternalMessage::ChktexResult(uri, diagnostics) => { - self.chktex_diagnostics.insert(uri, diagnostics); + InternalMessage::ChktexFinished(uri, diagnostics) => { + self.diagnostic_manager.update_chktex(uri, diagnostics); self.publish_diagnostics()?; } InternalMessage::ForwardSearch(uri, position) => { @@ -1124,8 +1044,22 @@ impl Server { } } - pub fn run(mut self) -> Result<()> { - self.initialize()?; + pub fn run(mut self, options: StartupOptions) -> Result<()> { + if !options.skip_distro { + let sender = self.internal_tx.clone(); + self.pool.execute(move || { + let distro = Distro::detect().unwrap_or_else(|why| { + log::warn!("Unable to load distro files: {}", why); + Distro::default() + }); + + log::info!("Detected distribution: {:?}", distro.kind); + sender.send(InternalMessage::SetDistro(distro)).unwrap(); + }); + } + + self.register_configuration(); + self.pull_options(); self.process_messages()?; self.pool.join(); Ok(()) diff --git a/support/texlab/crates/texlab/src/util.rs b/support/texlab/crates/texlab/src/util.rs index ecc7b850d7..c40ba8b51a 100644 --- a/support/texlab/crates/texlab/src/util.rs +++ b/support/texlab/crates/texlab/src/util.rs @@ -1,8 +1,10 @@ -pub mod capabilities; -pub mod chktex; -pub mod diagnostics; +mod client_flags; +pub mod from_proto; pub mod line_index_ext; pub mod lsp_enums; +pub mod to_proto; + +pub use self::client_flags::ClientFlags; pub fn normalize_uri(uri: &mut lsp_types::Url) { if let Some(mut segments) = uri.path_segments() { diff --git a/support/texlab/crates/texlab/src/util/capabilities.rs b/support/texlab/crates/texlab/src/util/capabilities.rs deleted file mode 100644 index 987c0b48dd..0000000000 --- a/support/texlab/crates/texlab/src/util/capabilities.rs +++ /dev/null @@ -1,179 +0,0 @@ -use lsp_types::{ClientCapabilities, MarkupKind}; - -pub trait ClientCapabilitiesExt { - fn has_definition_link_support(&self) -> bool; - - fn has_hierarchical_document_symbol_support(&self) -> bool; - - fn has_work_done_progress_support(&self) -> bool; - - fn has_completion_markdown_support(&self) -> bool; - - fn has_hover_markdown_support(&self) -> bool; - - fn has_pull_configuration_support(&self) -> bool; - - fn has_push_configuration_support(&self) -> bool; - - fn has_file_watching_support(&self) -> bool; - - fn has_snippet_support(&self) -> bool; -} - -impl ClientCapabilitiesExt for ClientCapabilities { - fn has_definition_link_support(&self) -> bool { - self.text_document - .as_ref() - .and_then(|cap| cap.definition.as_ref()) - .and_then(|cap| cap.link_support) - == Some(true) - } - - fn has_hierarchical_document_symbol_support(&self) -> bool { - self.text_document - .as_ref() - .and_then(|cap| cap.document_symbol.as_ref()) - .and_then(|cap| cap.hierarchical_document_symbol_support) - == Some(true) - } - - fn has_work_done_progress_support(&self) -> bool { - self.window.as_ref().and_then(|cap| cap.work_done_progress) == Some(true) - } - - fn has_completion_markdown_support(&self) -> bool { - self.text_document - .as_ref() - .and_then(|cap| cap.completion.as_ref()) - .and_then(|cap| cap.completion_item.as_ref()) - .and_then(|cap| cap.documentation_format.as_ref()) - .map_or(false, |formats| formats.contains(&MarkupKind::Markdown)) - } - - fn has_hover_markdown_support(&self) -> bool { - self.text_document - .as_ref() - .and_then(|cap| cap.hover.as_ref()) - .and_then(|cap| cap.content_format.as_ref()) - .map_or(false, |formats| formats.contains(&MarkupKind::Markdown)) - } - - fn has_pull_configuration_support(&self) -> bool { - self.workspace.as_ref().and_then(|cap| cap.configuration) == Some(true) - } - - fn has_push_configuration_support(&self) -> bool { - self.workspace - .as_ref() - .and_then(|cap| cap.did_change_configuration) - .and_then(|cap| cap.dynamic_registration) - == Some(true) - } - - fn has_file_watching_support(&self) -> bool { - self.workspace - .as_ref() - .and_then(|cap| cap.did_change_watched_files) - .and_then(|cap| cap.dynamic_registration) - == Some(true) - } - - fn has_snippet_support(&self) -> bool { - self.text_document - .as_ref() - .and_then(|cap| cap.completion.as_ref()) - .and_then(|cap| cap.completion_item.as_ref()) - .and_then(|cap| cap.snippet_support) - == Some(true) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use lsp_types::{ - DocumentSymbolClientCapabilities, GotoCapability, HoverClientCapabilities, - TextDocumentClientCapabilities, WindowClientCapabilities, - }; - - #[test] - fn test_has_definition_link_support_true() { - let capabilities = ClientCapabilities { - text_document: Some(TextDocumentClientCapabilities { - definition: Some(GotoCapability { - link_support: Some(true), - ..GotoCapability::default() - }), - ..TextDocumentClientCapabilities::default() - }), - ..ClientCapabilities::default() - }; - assert!(capabilities.has_definition_link_support()); - } - - #[test] - fn test_has_definition_link_support_false() { - let capabilities = ClientCapabilities::default(); - assert!(!capabilities.has_definition_link_support()); - } - - #[test] - fn test_has_hierarchical_document_symbol_support_true() { - let capabilities = ClientCapabilities { - text_document: Some(TextDocumentClientCapabilities { - document_symbol: Some(DocumentSymbolClientCapabilities { - hierarchical_document_symbol_support: Some(true), - ..DocumentSymbolClientCapabilities::default() - }), - ..TextDocumentClientCapabilities::default() - }), - ..ClientCapabilities::default() - }; - assert!(capabilities.has_hierarchical_document_symbol_support()); - } - - #[test] - fn test_has_hierarchical_document_symbol_support_false() { - let capabilities = ClientCapabilities::default(); - assert!(!capabilities.has_hierarchical_document_symbol_support()); - } - - #[test] - fn test_has_work_done_progress_support_true() { - let capabilities = ClientCapabilities { - window: Some(WindowClientCapabilities { - work_done_progress: Some(true), - ..WindowClientCapabilities::default() - }), - ..ClientCapabilities::default() - }; - assert!(capabilities.has_work_done_progress_support()); - } - - #[test] - fn test_has_work_done_progress_support_false() { - let capabilities = ClientCapabilities::default(); - assert!(!capabilities.has_work_done_progress_support()); - } - - #[test] - fn test_has_hover_markdown_support_true() { - let capabilities = ClientCapabilities { - text_document: Some(TextDocumentClientCapabilities { - hover: Some(HoverClientCapabilities { - content_format: Some(vec![MarkupKind::PlainText, MarkupKind::Markdown]), - ..HoverClientCapabilities::default() - }), - ..TextDocumentClientCapabilities::default() - }), - ..ClientCapabilities::default() - }; - assert!(capabilities.has_hover_markdown_support()); - } - - #[test] - fn test_has_hover_markdown_support_false() { - let capabilities = ClientCapabilities::default(); - assert!(!capabilities.has_hover_markdown_support()); - } -} diff --git a/support/texlab/crates/texlab/src/util/client_flags.rs b/support/texlab/crates/texlab/src/util/client_flags.rs new file mode 100644 index 0000000000..e5813e4f02 --- /dev/null +++ b/support/texlab/crates/texlab/src/util/client_flags.rs @@ -0,0 +1,40 @@ +/// Contains information about the client's capabilities. +/// This is used to determine which features the server can use. +#[derive(Debug, Clone)] +pub struct ClientFlags { + /// If `true`, the server can return `DocumentSymbol` instead of `SymbolInformation`. + pub hierarchical_document_symbols: bool, + + /// If `true`, the server can include markdown in completion items. + /// This is used to include images via base64 encoding. + pub completion_markdown: bool, + + /// If `true`, the server can include snippets like `\begin{...}` in completion items. + pub completion_snippets: bool, + + /// The completion kinds supported by the client. Unsupported kinds will be replaced by `TEXT`. + pub completion_kinds: Vec<lsp_types::CompletionItemKind>, + + /// If `true`, the server will always mark the completion list as incomplete. + /// This is used as a workaround for VSCode where the client-side filtering messes with `filterText`. + /// If not set, then fuzzy citation completion will not work. + pub completion_always_incomplete: bool, + + /// If `true`, the server can include markdown in hover responses. + pub hover_markdown: bool, + + /// If `true`, the server can pull the configuration from the client. + pub configuration_pull: bool, + + /// If `true`, the client notifies the server when the configuration changes. + pub configuration_push: bool, + + /// If `true`, the client can return `LocationLink` instead of `Location`. + pub definition_link: bool, + + /// If `true`, the server can return custom kinds like `section`. + pub folding_custom_kinds: bool, + + /// If `true`, the server can report progress using `WorkDoneProgress`. + pub progress: bool, +} diff --git a/support/texlab/crates/texlab/src/util/diagnostics.rs b/support/texlab/crates/texlab/src/util/diagnostics.rs deleted file mode 100644 index 0ef24d065b..0000000000 --- a/support/texlab/crates/texlab/src/util/diagnostics.rs +++ /dev/null @@ -1,201 +0,0 @@ -use base_db::{util::filter_regex_patterns, Document, Workspace}; -use diagnostics::{ - types::{BibError, Diagnostic, DiagnosticData, TexError}, - DiagnosticBuilder, DiagnosticSource, -}; -use rowan::TextRange; -use rustc_hash::FxHashMap; -use syntax::BuildErrorLevel; - -use super::line_index_ext::LineIndexExt; - -pub fn collect<'db>( - workspace: &'db Workspace, - source: &mut dyn DiagnosticSource, -) -> FxHashMap<&'db Document, Vec<lsp_types::Diagnostic>> { - let mut builder = DiagnosticBuilder::default(); - source.publish(workspace, &mut builder); - builder - .iter() - .filter_map(|(uri, diags)| workspace.lookup(uri).map(|document| (document, diags))) - .map(|(document, diags)| { - let diags = diags - .into_iter() - .filter_map(|diag| create_diagnostic(workspace, document, diag)) - .collect::<Vec<_>>(); - - (document, diags) - }) - .collect() -} - -fn create_diagnostic( - workspace: &Workspace, - document: &Document, - diagnostic: &Diagnostic, -) -> Option<lsp_types::Diagnostic> { - let range = document.line_index.line_col_lsp_range(diagnostic.range)?; - - let severity = match &diagnostic.data { - DiagnosticData::Tex(error) => match error { - TexError::UnexpectedRCurly => lsp_types::DiagnosticSeverity::ERROR, - TexError::ExpectingRCurly => lsp_types::DiagnosticSeverity::ERROR, - TexError::MismatchedEnvironment => lsp_types::DiagnosticSeverity::ERROR, - TexError::UnusedLabel => lsp_types::DiagnosticSeverity::HINT, - TexError::UndefinedLabel => lsp_types::DiagnosticSeverity::ERROR, - TexError::UndefinedCitation => lsp_types::DiagnosticSeverity::ERROR, - TexError::DuplicateLabel(_) => lsp_types::DiagnosticSeverity::ERROR, - }, - DiagnosticData::Bib(error) => match error { - BibError::ExpectingLCurly => lsp_types::DiagnosticSeverity::ERROR, - BibError::ExpectingKey => lsp_types::DiagnosticSeverity::ERROR, - BibError::ExpectingRCurly => lsp_types::DiagnosticSeverity::ERROR, - BibError::ExpectingEq => lsp_types::DiagnosticSeverity::ERROR, - BibError::ExpectingFieldValue => lsp_types::DiagnosticSeverity::ERROR, - BibError::UnusedEntry => lsp_types::DiagnosticSeverity::HINT, - BibError::DuplicateEntry(_) => lsp_types::DiagnosticSeverity::ERROR, - }, - DiagnosticData::Build(error) => match error.level { - BuildErrorLevel::Error => lsp_types::DiagnosticSeverity::ERROR, - BuildErrorLevel::Warning => lsp_types::DiagnosticSeverity::WARNING, - }, - }; - - let code = match &diagnostic.data { - DiagnosticData::Tex(error) => match error { - TexError::UnexpectedRCurly => Some(1), - TexError::ExpectingRCurly => Some(2), - TexError::MismatchedEnvironment => Some(3), - TexError::UnusedLabel => Some(9), - TexError::UndefinedLabel => Some(10), - TexError::UndefinedCitation => Some(11), - TexError::DuplicateLabel(_) => Some(14), - }, - DiagnosticData::Bib(error) => match error { - BibError::ExpectingLCurly => Some(4), - BibError::ExpectingKey => Some(5), - BibError::ExpectingRCurly => Some(6), - BibError::ExpectingEq => Some(7), - BibError::ExpectingFieldValue => Some(8), - BibError::UnusedEntry => Some(12), - BibError::DuplicateEntry(_) => Some(13), - }, - DiagnosticData::Build(_) => None, - }; - - let source = match &diagnostic.data { - DiagnosticData::Tex(_) | DiagnosticData::Bib(_) => "texlab", - DiagnosticData::Build(_) => "latex", - }; - - let message = String::from(match &diagnostic.data { - DiagnosticData::Tex(error) => match error { - TexError::UnexpectedRCurly => "Unexpected \"}\"", - TexError::ExpectingRCurly => "Expecting a curly bracket: \"}\"", - TexError::MismatchedEnvironment => "Mismatched environment", - TexError::UnusedLabel => "Unused label", - TexError::UndefinedLabel => "Undefined reference", - TexError::UndefinedCitation => "Undefined reference", - TexError::DuplicateLabel(_) => "Duplicate label", - }, - DiagnosticData::Bib(error) => match error { - BibError::ExpectingLCurly => "Expecting a curly bracket: \"{\"", - BibError::ExpectingKey => "Expecting a key", - BibError::ExpectingRCurly => "Expecting a curly bracket: \"}\"", - BibError::ExpectingEq => "Expecting an equality sign: \"=\"", - BibError::ExpectingFieldValue => "Expecting a field value", - BibError::UnusedEntry => "Unused entry", - BibError::DuplicateEntry(_) => "Duplicate entry key", - }, - DiagnosticData::Build(error) => &error.message, - }); - - let tags = match &diagnostic.data { - DiagnosticData::Tex(error) => match error { - TexError::UnexpectedRCurly => None, - TexError::ExpectingRCurly => None, - TexError::MismatchedEnvironment => None, - TexError::UnusedLabel => Some(vec![lsp_types::DiagnosticTag::UNNECESSARY]), - TexError::UndefinedLabel => None, - TexError::UndefinedCitation => None, - TexError::DuplicateLabel(_) => None, - }, - DiagnosticData::Bib(error) => match error { - BibError::ExpectingLCurly => None, - BibError::ExpectingKey => None, - BibError::ExpectingRCurly => None, - BibError::ExpectingEq => None, - BibError::ExpectingFieldValue => None, - BibError::UnusedEntry => Some(vec![lsp_types::DiagnosticTag::UNNECESSARY]), - BibError::DuplicateEntry(_) => None, - }, - DiagnosticData::Build(_) => None, - }; - - let related_information = match &diagnostic.data { - DiagnosticData::Tex(error) => match error { - TexError::UnexpectedRCurly => None, - TexError::ExpectingRCurly => None, - TexError::MismatchedEnvironment => None, - TexError::UnusedLabel => None, - TexError::UndefinedLabel => None, - TexError::UndefinedCitation => None, - TexError::DuplicateLabel(others) => make_conflict_info(workspace, others, "label"), - }, - DiagnosticData::Bib(error) => match error { - BibError::ExpectingLCurly => None, - BibError::ExpectingKey => None, - BibError::ExpectingRCurly => None, - BibError::ExpectingEq => None, - BibError::ExpectingFieldValue => None, - BibError::UnusedEntry => None, - BibError::DuplicateEntry(others) => make_conflict_info(workspace, others, "entry"), - }, - DiagnosticData::Build(_) => None, - }; - - Some(lsp_types::Diagnostic { - severity: Some(severity), - code: code.map(lsp_types::NumberOrString::Number), - source: Some(String::from(source)), - tags, - related_information, - ..lsp_types::Diagnostic::new_simple(range, message) - }) -} - -fn make_conflict_info( - workspace: &Workspace, - locations: &Vec<(lsp_types::Url, TextRange)>, - object: &str, -) -> Option<Vec<lsp_types::DiagnosticRelatedInformation>> { - let mut items = Vec::new(); - for (uri, range) in locations { - let range = workspace - .lookup(uri)? - .line_index - .line_col_lsp_range(*range)?; - - let message = format!("conflicting {object} defined here"); - let location = lsp_types::Location::new(uri.clone(), range); - items.push(lsp_types::DiagnosticRelatedInformation { location, message }); - } - - Some(items) -} - -pub fn filter( - all_diagnostics: &mut FxHashMap<&Document, Vec<lsp_types::Diagnostic>>, - workspace: &Workspace, -) { - let config = &workspace.config().diagnostics; - for diagnostics in all_diagnostics.values_mut() { - diagnostics.retain(|diagnostic| { - filter_regex_patterns( - &diagnostic.message, - &config.allowed_patterns, - &config.ignored_patterns, - ) - }); - } -} diff --git a/support/texlab/crates/texlab/src/util/from_proto.rs b/support/texlab/crates/texlab/src/util/from_proto.rs new file mode 100644 index 0000000000..e05d317dfe --- /dev/null +++ b/support/texlab/crates/texlab/src/util/from_proto.rs @@ -0,0 +1,223 @@ +use base_db::{FeatureParams, Workspace}; +use completion::CompletionParams; +use definition::DefinitionParams; +use highlights::HighlightParams; +use hover::HoverParams; +use inlay_hints::InlayHintParams; +use references::ReferenceParams; +use rename::RenameParams; +use rowan::TextSize; + +use crate::features::completion::ResolveInfo; + +use super::{line_index_ext::LineIndexExt, ClientFlags}; + +pub fn client_flags( + capabilities: lsp_types::ClientCapabilities, + info: Option<lsp_types::ClientInfo>, +) -> ClientFlags { + let hierarchical_document_symbols = capabilities + .text_document + .as_ref() + .and_then(|cap| cap.document_symbol.as_ref()) + .and_then(|cap| cap.hierarchical_document_symbol_support) + .unwrap_or(false); + + let completion_markdown = capabilities + .text_document + .as_ref() + .and_then(|cap| cap.completion.as_ref()) + .and_then(|cap| cap.completion_item.as_ref()) + .and_then(|cap| cap.documentation_format.as_ref()) + .map_or(false, |formats| { + formats.contains(&lsp_types::MarkupKind::Markdown) + }); + + let completion_snippets = capabilities + .text_document + .as_ref() + .and_then(|cap| cap.completion.as_ref()) + .and_then(|cap| cap.completion_item.as_ref()) + .and_then(|cap| cap.snippet_support) + .unwrap_or(false); + + let completion_kinds = capabilities + .text_document + .as_ref() + .and_then(|cap| cap.completion.as_ref()) + .and_then(|cap| cap.completion_item_kind.as_ref()) + .and_then(|cap| cap.value_set.clone()) + .unwrap_or_default(); + + let completion_always_incomplete = info.map_or(false, |info| info.name == "Visual Studio Code"); + + let hover_markdown = capabilities + .text_document + .as_ref() + .and_then(|cap| cap.hover.as_ref()) + .and_then(|cap| cap.content_format.as_ref()) + .map_or(false, |formats| { + formats.contains(&lsp_types::MarkupKind::Markdown) + }); + + let configuration_pull = capabilities + .workspace + .as_ref() + .and_then(|cap| cap.configuration) + .unwrap_or(false); + + let configuration_push = capabilities + .workspace + .as_ref() + .and_then(|cap| cap.did_change_configuration) + .and_then(|cap| cap.dynamic_registration) + .unwrap_or(false); + + let definition_link = capabilities + .text_document + .as_ref() + .and_then(|cap| cap.definition) + .and_then(|cap| cap.link_support) + .unwrap_or(false); + + let folding_custom_kinds = capabilities + .text_document + .as_ref() + .and_then(|cap| cap.folding_range.as_ref()) + .and_then(|cap| cap.folding_range_kind.as_ref()) + .and_then(|cap| cap.value_set.as_ref()) + .is_some(); + + let progress = capabilities + .window + .as_ref() + .and_then(|cap| cap.work_done_progress) + .unwrap_or(false); + + ClientFlags { + hierarchical_document_symbols, + completion_markdown, + completion_snippets, + completion_kinds, + completion_always_incomplete, + hover_markdown, + configuration_pull, + configuration_push, + definition_link, + folding_custom_kinds, + progress, + } +} + +pub fn rename_params<'a>( + workspace: &'a Workspace, + params: lsp_types::TextDocumentPositionParams, +) -> Option<RenameParams<'a>> { + let (feature, offset) = + feature_params_offset(workspace, params.text_document, params.position)?; + + Some(RenameParams { feature, offset }) +} + +pub fn hover_params<'a>( + workspace: &'a Workspace, + params: lsp_types::HoverParams, +) -> Option<HoverParams<'a>> { + let (feature, offset) = feature_params_offset( + workspace, + params.text_document_position_params.text_document, + params.text_document_position_params.position, + )?; + + Some(HoverParams { feature, offset }) +} + +pub fn inlay_hint_params<'a>( + workspace: &'a Workspace, + params: lsp_types::InlayHintParams, +) -> Option<InlayHintParams> { + let feature = feature_params(workspace, params.text_document)?; + let range = feature.document.line_index.offset_lsp_range(params.range)?; + Some(InlayHintParams { feature, range }) +} + +pub fn highlight_params<'a>( + workspace: &'a Workspace, + params: lsp_types::DocumentHighlightParams, +) -> Option<HighlightParams<'a>> { + let (feature, offset) = feature_params_offset( + workspace, + params.text_document_position_params.text_document, + params.text_document_position_params.position, + )?; + + Some(HighlightParams { feature, offset }) +} + +pub fn definition_params<'a>( + workspace: &'a Workspace, + params: lsp_types::GotoDefinitionParams, +) -> Option<DefinitionParams<'a>> { + let (feature, offset) = feature_params_offset( + workspace, + params.text_document_position_params.text_document, + params.text_document_position_params.position, + )?; + + Some(DefinitionParams { feature, offset }) +} + +pub fn completion_params<'a>( + workspace: &'a Workspace, + params: lsp_types::CompletionParams, +) -> Option<CompletionParams<'a>> { + let (feature, offset) = feature_params_offset( + workspace, + params.text_document_position.text_document, + params.text_document_position.position, + )?; + + Some(CompletionParams { feature, offset }) +} + +pub fn reference_params<'a>( + workspace: &'a Workspace, + params: lsp_types::ReferenceParams, +) -> Option<ReferenceParams<'a>> { + let (feature, offset) = feature_params_offset( + workspace, + params.text_document_position.text_document, + params.text_document_position.position, + )?; + + let include_declaration = params.context.include_declaration; + Some(ReferenceParams { + feature, + offset, + include_declaration, + }) +} + +pub fn feature_params<'a>( + workspace: &'a Workspace, + text_document: lsp_types::TextDocumentIdentifier, +) -> Option<FeatureParams<'a>> { + let document = workspace.lookup(&text_document.uri)?; + Some(FeatureParams::new(workspace, document)) +} + +pub fn feature_params_offset<'a>( + workspace: &'a Workspace, + text_document: lsp_types::TextDocumentIdentifier, + position: lsp_types::Position, +) -> Option<(FeatureParams<'a>, TextSize)> { + let feature = feature_params(workspace, text_document)?; + let offset = feature.document.line_index.offset_lsp(position)?; + Some((feature, offset)) +} + +pub fn completion_resolve_info(item: &mut lsp_types::CompletionItem) -> Option<ResolveInfo> { + item.data + .take() + .and_then(|data| serde_json::from_value(data).ok()) +} diff --git a/support/texlab/crates/texlab/src/util/lsp_enums.rs b/support/texlab/crates/texlab/src/util/lsp_enums.rs index e79297af8a..352b392868 100644 --- a/support/texlab/crates/texlab/src/util/lsp_enums.rs +++ b/support/texlab/crates/texlab/src/util/lsp_enums.rs @@ -1,5 +1,5 @@ use base_db::data::BibtexEntryTypeCategory; -use lsp_types::{CompletionItemKind, SymbolKind}; +use lsp_types::CompletionItemKind; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Structure { @@ -14,7 +14,6 @@ pub enum Structure { Label, Folder, File, - PgfLibrary, TikzLibrary, Color, ColorModel, @@ -40,7 +39,6 @@ impl Structure { Self::Label => CompletionItemKind::CONSTRUCTOR, Self::Folder => CompletionItemKind::FOLDER, Self::File => CompletionItemKind::FILE, - Self::PgfLibrary => CompletionItemKind::PROPERTY, Self::TikzLibrary => CompletionItemKind::PROPERTY, Self::Color => CompletionItemKind::COLOR, Self::ColorModel => CompletionItemKind::COLOR, @@ -58,36 +56,4 @@ impl Structure { Self::GlossaryEntry => CompletionItemKind::KEYWORD, } } - - pub fn symbol_kind(self) -> SymbolKind { - match self { - Self::Command => SymbolKind::FUNCTION, - Self::Snippet => unimplemented!(), - Self::Environment => SymbolKind::ENUM, - Self::Section => SymbolKind::MODULE, - Self::Float => SymbolKind::METHOD, - Self::Theorem => SymbolKind::VARIABLE, - Self::Equation => SymbolKind::CONSTANT, - Self::Item => SymbolKind::ENUM_MEMBER, - Self::Label => SymbolKind::CONSTRUCTOR, - Self::Folder => SymbolKind::NAMESPACE, - Self::File => SymbolKind::FILE, - Self::PgfLibrary => SymbolKind::PROPERTY, - Self::TikzLibrary => SymbolKind::PROPERTY, - Self::Color => unimplemented!(), - Self::ColorModel => unimplemented!(), - Self::Package => SymbolKind::CLASS, - Self::Class => SymbolKind::CLASS, - Self::Entry(BibtexEntryTypeCategory::Misc) => SymbolKind::INTERFACE, - Self::Entry(BibtexEntryTypeCategory::String) => SymbolKind::STRING, - Self::Entry(BibtexEntryTypeCategory::Article) => SymbolKind::EVENT, - Self::Entry(BibtexEntryTypeCategory::Book) => SymbolKind::STRUCT, - Self::Entry(BibtexEntryTypeCategory::Collection) => SymbolKind::TYPE_PARAMETER, - Self::Entry(BibtexEntryTypeCategory::Part) => SymbolKind::OPERATOR, - Self::Entry(BibtexEntryTypeCategory::Thesis) => SymbolKind::OBJECT, - Self::Field => SymbolKind::FIELD, - Self::Argument => SymbolKind::NUMBER, - Self::GlossaryEntry => unimplemented!(), - } - } } diff --git a/support/texlab/crates/texlab/src/util/to_proto.rs b/support/texlab/crates/texlab/src/util/to_proto.rs new file mode 100644 index 0000000000..f5cf2edf8b --- /dev/null +++ b/support/texlab/crates/texlab/src/util/to_proto.rs @@ -0,0 +1,459 @@ +use std::collections::HashMap; + +use base_db::{ + data::BibtexEntryTypeCategory, util::RenderedObject, Document, DocumentLocation, Workspace, +}; +use definition::DefinitionResult; +use diagnostics::{BibError, ChktexSeverity, Diagnostic, TexError}; +use folding::{FoldingRange, FoldingRangeKind}; +use highlights::{Highlight, HighlightKind}; +use hover::{Hover, HoverData}; +use inlay_hints::{InlayHint, InlayHintData}; +use line_index::LineIndex; +use lsp_types::NumberOrString; +use rename::RenameResult; +use rowan::TextRange; +use syntax::BuildErrorLevel; + +use super::{line_index_ext::LineIndexExt, ClientFlags}; + +pub fn diagnostic( + workspace: &Workspace, + document: &Document, + diagnostic: &Diagnostic, +) -> Option<lsp_types::Diagnostic> { + let range = match diagnostic { + Diagnostic::Tex(range, _) | Diagnostic::Bib(range, _) | Diagnostic::Build(range, _) => { + document.line_index.line_col_lsp_range(*range)? + } + Diagnostic::Chktex(range) => { + let start = lsp_types::Position::new(range.start.line, range.start.col); + let end = lsp_types::Position::new(range.end.line, range.end.col); + lsp_types::Range::new(start, end) + } + }; + + let severity = match diagnostic { + Diagnostic::Tex(_, error) => match error { + TexError::UnexpectedRCurly => lsp_types::DiagnosticSeverity::ERROR, + TexError::ExpectingRCurly => lsp_types::DiagnosticSeverity::ERROR, + TexError::MismatchedEnvironment => lsp_types::DiagnosticSeverity::ERROR, + TexError::UnusedLabel => lsp_types::DiagnosticSeverity::HINT, + TexError::UndefinedLabel => lsp_types::DiagnosticSeverity::ERROR, + TexError::UndefinedCitation => lsp_types::DiagnosticSeverity::ERROR, + TexError::DuplicateLabel(_) => lsp_types::DiagnosticSeverity::ERROR, + }, + Diagnostic::Bib(_, error) => match error { + BibError::ExpectingLCurly => lsp_types::DiagnosticSeverity::ERROR, + BibError::ExpectingKey => lsp_types::DiagnosticSeverity::ERROR, + BibError::ExpectingRCurly => lsp_types::DiagnosticSeverity::ERROR, + BibError::ExpectingEq => lsp_types::DiagnosticSeverity::ERROR, + BibError::ExpectingFieldValue => lsp_types::DiagnosticSeverity::ERROR, + BibError::UnusedEntry => lsp_types::DiagnosticSeverity::HINT, + BibError::DuplicateEntry(_) => lsp_types::DiagnosticSeverity::ERROR, + }, + Diagnostic::Build(_, error) => match error.level { + BuildErrorLevel::Error => lsp_types::DiagnosticSeverity::ERROR, + BuildErrorLevel::Warning => lsp_types::DiagnosticSeverity::WARNING, + }, + Diagnostic::Chktex(error) => match error.severity { + ChktexSeverity::Message => lsp_types::DiagnosticSeverity::HINT, + ChktexSeverity::Warning => lsp_types::DiagnosticSeverity::WARNING, + ChktexSeverity::Error => lsp_types::DiagnosticSeverity::ERROR, + }, + }; + + let code: Option<NumberOrString> = match &diagnostic { + Diagnostic::Tex(_, error) => match error { + TexError::UnexpectedRCurly => Some(NumberOrString::Number(1)), + TexError::ExpectingRCurly => Some(NumberOrString::Number(2)), + TexError::MismatchedEnvironment => Some(NumberOrString::Number(3)), + TexError::UnusedLabel => Some(NumberOrString::Number(9)), + TexError::UndefinedLabel => Some(NumberOrString::Number(10)), + TexError::UndefinedCitation => Some(NumberOrString::Number(11)), + TexError::DuplicateLabel(_) => Some(NumberOrString::Number(14)), + }, + Diagnostic::Bib(_, error) => match error { + BibError::ExpectingLCurly => Some(NumberOrString::Number(4)), + BibError::ExpectingKey => Some(NumberOrString::Number(5)), + BibError::ExpectingRCurly => Some(NumberOrString::Number(6)), + BibError::ExpectingEq => Some(NumberOrString::Number(7)), + BibError::ExpectingFieldValue => Some(NumberOrString::Number(8)), + BibError::UnusedEntry => Some(NumberOrString::Number(12)), + BibError::DuplicateEntry(_) => Some(NumberOrString::Number(13)), + }, + Diagnostic::Build(_, _) => None, + Diagnostic::Chktex(error) => Some(NumberOrString::String(error.code.clone())), + }; + + let source = match &diagnostic { + Diagnostic::Tex(_, _) | Diagnostic::Bib(_, _) => "texlab", + Diagnostic::Build(_, _) => "latex", + Diagnostic::Chktex(_) => "ChkTeX", + }; + + let message = String::from(match &diagnostic { + Diagnostic::Tex(_, error) => match error { + TexError::UnexpectedRCurly => "Unexpected \"}\"", + TexError::ExpectingRCurly => "Expecting a curly bracket: \"}\"", + TexError::MismatchedEnvironment => "Mismatched environment", + TexError::UnusedLabel => "Unused label", + TexError::UndefinedLabel => "Undefined reference", + TexError::UndefinedCitation => "Undefined reference", + TexError::DuplicateLabel(_) => "Duplicate label", + }, + Diagnostic::Bib(_, error) => match error { + BibError::ExpectingLCurly => "Expecting a curly bracket: \"{\"", + BibError::ExpectingKey => "Expecting a key", + BibError::ExpectingRCurly => "Expecting a curly bracket: \"}\"", + BibError::ExpectingEq => "Expecting an equality sign: \"=\"", + BibError::ExpectingFieldValue => "Expecting a field value", + BibError::UnusedEntry => "Unused entry", + BibError::DuplicateEntry(_) => "Duplicate entry key", + }, + Diagnostic::Build(_, error) => &error.message, + Diagnostic::Chktex(error) => &error.message, + }); + + let tags = match &diagnostic { + Diagnostic::Tex(_, error) => match error { + TexError::UnexpectedRCurly => None, + TexError::ExpectingRCurly => None, + TexError::MismatchedEnvironment => None, + TexError::UnusedLabel => Some(vec![lsp_types::DiagnosticTag::UNNECESSARY]), + TexError::UndefinedLabel => None, + TexError::UndefinedCitation => None, + TexError::DuplicateLabel(_) => None, + }, + Diagnostic::Bib(_, error) => match error { + BibError::ExpectingLCurly => None, + BibError::ExpectingKey => None, + BibError::ExpectingRCurly => None, + BibError::ExpectingEq => None, + BibError::ExpectingFieldValue => None, + BibError::UnusedEntry => Some(vec![lsp_types::DiagnosticTag::UNNECESSARY]), + BibError::DuplicateEntry(_) => None, + }, + Diagnostic::Build(_, _) => None, + Diagnostic::Chktex(_) => None, + }; + + fn make_conflict_info( + workspace: &Workspace, + locations: &Vec<(lsp_types::Url, TextRange)>, + object: &str, + ) -> Option<Vec<lsp_types::DiagnosticRelatedInformation>> { + let mut items = Vec::new(); + for (uri, range) in locations { + let range = workspace + .lookup(uri)? + .line_index + .line_col_lsp_range(*range)?; + + let message = format!("conflicting {object} defined here"); + let location = lsp_types::Location::new(uri.clone(), range); + items.push(lsp_types::DiagnosticRelatedInformation { location, message }); + } + + Some(items) + } + + let related_information = match &diagnostic { + Diagnostic::Tex(_, error) => match error { + TexError::UnexpectedRCurly => None, + TexError::ExpectingRCurly => None, + TexError::MismatchedEnvironment => None, + TexError::UnusedLabel => None, + TexError::UndefinedLabel => None, + TexError::UndefinedCitation => None, + TexError::DuplicateLabel(others) => make_conflict_info(workspace, others, "label"), + }, + Diagnostic::Bib(_, error) => match error { + BibError::ExpectingLCurly => None, + BibError::ExpectingKey => None, + BibError::ExpectingRCurly => None, + BibError::ExpectingEq => None, + BibError::ExpectingFieldValue => None, + BibError::UnusedEntry => None, + BibError::DuplicateEntry(others) => make_conflict_info(workspace, others, "entry"), + }, + Diagnostic::Build(_, _) => None, + Diagnostic::Chktex(_) => None, + }; + + Some(lsp_types::Diagnostic { + severity: Some(severity), + code, + source: Some(String::from(source)), + tags, + related_information, + ..lsp_types::Diagnostic::new_simple(range, message) + }) +} + +pub fn inlay_hint(hint: InlayHint, line_index: &LineIndex) -> Option<lsp_types::InlayHint> { + let position = line_index.line_col_lsp(hint.offset)?; + Some(match hint.data { + InlayHintData::LabelDefinition(label) => { + let number = label.number?; + + let text = match &label.object { + RenderedObject::Section { prefix, .. } => { + format!("{} {}", prefix, number) + } + RenderedObject::Float { kind, .. } => { + format!("{} {}", kind.as_str(), number) + } + RenderedObject::Theorem { kind, .. } => { + format!("{} {}", kind, number) + } + RenderedObject::Equation => format!("Equation ({})", number), + RenderedObject::EnumItem => format!("Item {}", number), + }; + + lsp_types::InlayHint { + position, + label: lsp_types::InlayHintLabel::String(format!(" {text} ")), + kind: None, + text_edits: None, + tooltip: None, + padding_left: Some(true), + padding_right: None, + data: None, + } + } + InlayHintData::LabelReference(label) => { + let text = label.reference(); + + lsp_types::InlayHint { + position, + label: lsp_types::InlayHintLabel::String(format!(" {text} ")), + kind: None, + text_edits: None, + tooltip: None, + padding_left: Some(true), + padding_right: None, + data: None, + } + } + }) +} + +pub fn document_link( + link: DocumentLocation, + line_index: &LineIndex, +) -> Option<lsp_types::DocumentLink> { + Some(lsp_types::DocumentLink { + data: None, + tooltip: None, + target: Some(link.document.uri.clone()), + range: line_index.line_col_lsp_range(link.range)?, + }) +} + +pub fn folding_range( + folding: FoldingRange, + line_index: &LineIndex, + client_flags: &ClientFlags, +) -> Option<serde_json::Value> { + let range = line_index.line_col_lsp_range(folding.range)?; + + let kind = if client_flags.folding_custom_kinds { + Some(match folding.kind { + FoldingRangeKind::Section => "section", + FoldingRangeKind::Environment => "environment", + FoldingRangeKind::Entry => "entry", + }) + } else { + None + }; + + Some(serde_json::json!({ + "startLine": range.start.line, + "startCharacter": range.start.character, + "endLine": range.end.line, + "endCharacter": range.end.character, + "kind": kind, + })) +} + +pub fn location_link( + result: DefinitionResult, + line_index: &LineIndex, +) -> Option<lsp_types::LocationLink> { + let origin_selection_range = line_index.line_col_lsp_range(result.origin_selection_range); + + let target_line_index = &result.target.line_index; + let target_uri = result.target.uri.clone(); + let target_range = target_line_index.line_col_lsp_range(result.target_range)?; + let target_selection_range = + target_line_index.line_col_lsp_range(result.target_selection_range)?; + + Some(lsp_types::LocationLink { + origin_selection_range, + target_uri, + target_range, + target_selection_range, + }) +} + +pub fn document_symbol( + symbol: symbols::Symbol, + line_index: &LineIndex, +) -> Option<lsp_types::DocumentSymbol> { + let children = symbol + .children + .into_iter() + .filter_map(|child| document_symbol(child, line_index)) + .collect(); + + #[allow(deprecated)] + Some(lsp_types::DocumentSymbol { + name: symbol.name, + detail: symbol.label.map(|label| label.text), + kind: symbol_kind(symbol.kind), + deprecated: Some(false), + range: line_index.line_col_lsp_range(symbol.full_range)?, + selection_range: line_index.line_col_lsp_range(symbol.selection_range)?, + children: Some(children), + tags: None, + }) +} + +pub fn symbol_information( + symbol: symbols::Symbol, + document: &Document, + results: &mut Vec<lsp_types::SymbolInformation>, +) -> Option<()> { + let range = document.line_index.line_col_lsp_range(symbol.full_range)?; + + #[allow(deprecated)] + results.push(lsp_types::SymbolInformation { + name: symbol.name, + kind: symbol_kind(symbol.kind), + deprecated: Some(false), + location: lsp_types::Location::new(document.uri.clone(), range), + tags: None, + container_name: None, + }); + + for child in symbol.children { + symbol_information(child, document, results); + } + + Some(()) +} + +pub fn symbol_kind(value: symbols::SymbolKind) -> lsp_types::SymbolKind { + match value { + symbols::SymbolKind::Section => lsp_types::SymbolKind::MODULE, + symbols::SymbolKind::Figure => lsp_types::SymbolKind::METHOD, + symbols::SymbolKind::Algorithm => lsp_types::SymbolKind::METHOD, + symbols::SymbolKind::Table => lsp_types::SymbolKind::METHOD, + symbols::SymbolKind::Listing => lsp_types::SymbolKind::METHOD, + symbols::SymbolKind::Enumeration => lsp_types::SymbolKind::ENUM, + symbols::SymbolKind::EnumerationItem => lsp_types::SymbolKind::ENUM_MEMBER, + symbols::SymbolKind::Theorem => lsp_types::SymbolKind::VARIABLE, + symbols::SymbolKind::Equation => lsp_types::SymbolKind::CONSTANT, + symbols::SymbolKind::Entry(category) => match category { + BibtexEntryTypeCategory::Misc => lsp_types::SymbolKind::INTERFACE, + BibtexEntryTypeCategory::String => lsp_types::SymbolKind::STRING, + BibtexEntryTypeCategory::Article => lsp_types::SymbolKind::EVENT, + BibtexEntryTypeCategory::Thesis => lsp_types::SymbolKind::OBJECT, + BibtexEntryTypeCategory::Book => lsp_types::SymbolKind::STRUCT, + BibtexEntryTypeCategory::Part => lsp_types::SymbolKind::OPERATOR, + BibtexEntryTypeCategory::Collection => lsp_types::SymbolKind::TYPE_PARAMETER, + }, + symbols::SymbolKind::Field => lsp_types::SymbolKind::FIELD, + } +} + +pub fn document_symbol_response( + document: &Document, + symbols: Vec<symbols::Symbol>, + client_flags: &ClientFlags, +) -> lsp_types::DocumentSymbolResponse { + if client_flags.hierarchical_document_symbols { + let results = symbols + .into_iter() + .filter_map(|symbol| document_symbol(symbol, &document.line_index)) + .collect(); + + lsp_types::DocumentSymbolResponse::Nested(results) + } else { + let mut results = Vec::new(); + for symbol in symbols { + symbol_information(symbol, document, &mut results); + } + + lsp_types::DocumentSymbolResponse::Flat(results) + } +} + +pub fn workspace_edit(result: RenameResult, new_name: &str) -> lsp_types::WorkspaceEdit { + let mut changes = HashMap::default(); + for (document, ranges) in result.changes { + let mut edits = Vec::new(); + ranges + .into_iter() + .filter_map(|range| document.line_index.line_col_lsp_range(range)) + .for_each(|range| edits.push(lsp_types::TextEdit::new(range, new_name.into()))); + + changes.insert(document.uri.clone(), edits); + } + + lsp_types::WorkspaceEdit::new(changes) +} + +pub fn location(location: DocumentLocation) -> Option<lsp_types::Location> { + let document = location.document; + let range = document.line_index.line_col_lsp_range(location.range)?; + Some(lsp_types::Location::new(document.uri.clone(), range)) +} + +pub fn document_highlight( + highlight: Highlight, + line_index: &LineIndex, +) -> Option<lsp_types::DocumentHighlight> { + let range = line_index.line_col_lsp_range(highlight.range)?; + let kind = Some(match highlight.kind { + HighlightKind::Write => lsp_types::DocumentHighlightKind::WRITE, + HighlightKind::Read => lsp_types::DocumentHighlightKind::READ, + }); + + Some(lsp_types::DocumentHighlight { range, kind }) +} + +pub fn hover(hover: Hover, line_index: &LineIndex) -> Option<lsp_types::Hover> { + let contents = match hover.data { + HoverData::Citation(text) => lsp_types::MarkupContent { + kind: lsp_types::MarkupKind::Markdown, + value: text, + }, + HoverData::Package(description) => lsp_types::MarkupContent { + kind: lsp_types::MarkupKind::PlainText, + value: description.into(), + }, + HoverData::EntryType(type_) => lsp_types::MarkupContent { + kind: lsp_types::MarkupKind::Markdown, + value: type_.documentation?.into(), + }, + HoverData::FieldType(type_) => lsp_types::MarkupContent { + kind: lsp_types::MarkupKind::Markdown, + value: type_.documentation.into(), + }, + HoverData::Label(label) => lsp_types::MarkupContent { + kind: lsp_types::MarkupKind::PlainText, + value: label.reference(), + }, + HoverData::StringRef(text) => lsp_types::MarkupContent { + kind: lsp_types::MarkupKind::PlainText, + value: text, + }, + }; + + Some(lsp_types::Hover { + contents: lsp_types::HoverContents::Markup(contents), + range: line_index.line_col_lsp_range(hover.range), + }) +} diff --git a/support/texlab/texlab.1 b/support/texlab/texlab.1 index 97e9fa5a96..ea5ac6ec05 100644 --- a/support/texlab/texlab.1 +++ b/support/texlab/texlab.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13. -.TH TEXLAB "1" "January 2024" "texlab 5.12.2" "User Commands" +.TH TEXLAB "1" "January 2024" "texlab 5.12.3" "User Commands" .SH NAME -texlab \- manual page for texlab 5.12.2 +texlab \- manual page for texlab 5.12.3 .SH SYNOPSIS .B texlab [\fI\,OPTIONS\/\fR] diff --git a/support/texlab/texlab.pdf b/support/texlab/texlab.pdf Binary files differindex 0ba7e1348e..6e2ddb1b35 100644 --- a/support/texlab/texlab.pdf +++ b/support/texlab/texlab.pdf |