diff options
author | Norbert Preining <norbert@preining.info> | 2024-03-17 03:01:42 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2024-03-17 03:01:42 +0000 |
commit | 55140d5421e1ad0024c9acbfa72bd06402c2aa9f (patch) | |
tree | b6c0d76888f025b58f1221d8dadbefb0264e9f6c /support/texlab/crates | |
parent | 966f9ba332e49c29e961cb86e08f97d70eda51f6 (diff) |
CTAN sync 202403170301
Diffstat (limited to 'support/texlab/crates')
35 files changed, 333 insertions, 117 deletions
diff --git a/support/texlab/crates/base-db/Cargo.toml b/support/texlab/crates/base-db/Cargo.toml index 0aceff8e5f..010c83d0e4 100644 --- a/support/texlab/crates/base-db/Cargo.toml +++ b/support/texlab/crates/base-db/Cargo.toml @@ -12,7 +12,7 @@ dirs = "5.0.1" distro = { path = "../distro" } itertools = "0.12.0" line-index = { path = "../line-index" } -log = "0.4.20" +log = "0.4.21" notify = "6.0.1" once_cell = "1.19.0" parser = { path = "../parser" } diff --git a/support/texlab/crates/base-db/src/document.rs b/support/texlab/crates/base-db/src/document.rs index 9f45876d7e..25607a1bf5 100644 --- a/support/texlab/crates/base-db/src/document.rs +++ b/support/texlab/crates/base-db/src/document.rs @@ -12,6 +12,7 @@ use crate::{semantics, Config}; pub enum Owner { Client, Server, + Distro, } #[derive(Debug)] diff --git a/support/texlab/crates/base-db/src/semantics/bib.rs b/support/texlab/crates/base-db/src/semantics/bib.rs index 14fa98fb6e..1a03fbb2c2 100644 --- a/support/texlab/crates/base-db/src/semantics/bib.rs +++ b/support/texlab/crates/base-db/src/semantics/bib.rs @@ -2,6 +2,7 @@ use bibtex_utils::field::text::TextFieldData; use itertools::Itertools; use rowan::{ast::AstNode, TextRange}; use syntax::bibtex::{self, HasName, HasType, HasValue}; +use rustc_hash::FxHashMap; use crate::data::{BibtexEntryType, BibtexEntryTypeCategory}; @@ -11,6 +12,8 @@ use super::Span; pub struct Semantics { pub entries: Vec<Entry>, pub strings: Vec<StringDef>, + /// Map from string definition keys to their expanded values. + pub expanded_defs: FxHashMap<String, String>, } impl Semantics { @@ -32,7 +35,7 @@ impl Semantics { let field_values = entry .fields() - .filter_map(|field| Some(TextFieldData::parse(&field.value()?)?.text)); + .filter_map(|field| Some(TextFieldData::parse(&field.value()?, &self.expanded_defs)?.text)); let keywords = [name.text().into(), type_token.text().into()] .into_iter() @@ -60,6 +63,11 @@ impl Semantics { }, full_range: string.syntax().text_range(), }); + if let Some(value) = string.value() { + if let Some(data) = TextFieldData::parse(&value, &self.expanded_defs) { + self.expanded_defs.insert(name.text().into(), data.text); + } + } } } } diff --git a/support/texlab/crates/base-db/src/workspace.rs b/support/texlab/crates/base-db/src/workspace.rs index 330ed5a493..0fe347f1b3 100644 --- a/support/texlab/crates/base-db/src/workspace.rs +++ b/support/texlab/crates/base-db/src/workspace.rs @@ -67,7 +67,7 @@ impl Workspace { })); } - pub fn load(&mut self, path: &Path, language: Language, owner: Owner) -> std::io::Result<()> { + pub fn load(&mut self, path: &Path, language: Language) -> std::io::Result<()> { log::debug!("Loading document {} from disk...", path.display()); let uri = Url::from_file_path(path).unwrap(); let data = std::fs::read(path)?; @@ -76,6 +76,12 @@ impl Workspace { Cow::Owned(text) => text, }; + let owner = if self.distro.file_name_db.contains(&path) { + Owner::Distro + } else { + Owner::Server + }; + if let Some(document) = self.lookup_path(path) { if document.text == text { return Ok(()); @@ -329,7 +335,7 @@ impl Workspace { } if self.lookup_path(&file).is_none() && file.exists() { - changed |= self.load(&file, lang, Owner::Server).is_ok(); + changed |= self.load(&file, lang).is_ok(); checked_paths.insert(file); } } @@ -350,8 +356,9 @@ impl Workspace { let mut changed = false; for file in files { let language = Language::from_path(&file).unwrap_or(Language::Tex); + if self.lookup_path(&file).is_none() && file.exists() { - changed |= self.load(&file, language, Owner::Server).is_ok(); + changed |= self.load(&file, language).is_ok(); checked_paths.insert(file); } } diff --git a/support/texlab/crates/bibtex-utils/src/field.rs b/support/texlab/crates/bibtex-utils/src/field.rs index d9f3adacab..71bd25f6d0 100644 --- a/support/texlab/crates/bibtex-utils/src/field.rs +++ b/support/texlab/crates/bibtex-utils/src/field.rs @@ -1,4 +1,9 @@ +use rustc_hash::FxHashMap; + pub mod author; pub mod date; pub mod number; pub mod text; + +/// A cache used to accelerate related field parses. +pub type FieldParseCache = FxHashMap<String, String>;
\ No newline at end of file diff --git a/support/texlab/crates/bibtex-utils/src/field/author.rs b/support/texlab/crates/bibtex-utils/src/field/author.rs index d8576cbaa4..4be999efbd 100644 --- a/support/texlab/crates/bibtex-utils/src/field/author.rs +++ b/support/texlab/crates/bibtex-utils/src/field/author.rs @@ -4,7 +4,7 @@ use human_name::Name; use itertools::Itertools; use syntax::bibtex::Value; -use super::text::TextFieldData; +use super::{text::TextFieldData, FieldParseCache}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] pub enum AuthorField { @@ -58,8 +58,8 @@ impl fmt::Display for AuthorFieldData { } impl AuthorFieldData { - pub fn parse(value: &Value) -> Option<Self> { - let TextFieldData { text } = TextFieldData::parse(value)?; + pub fn parse(value: &Value, cache: &FieldParseCache) -> Option<Self> { + let TextFieldData { text } = TextFieldData::parse(value, cache)?; let mut authors = Vec::new(); let mut words = Vec::new(); for word in text.split_whitespace() { diff --git a/support/texlab/crates/bibtex-utils/src/field/date.rs b/support/texlab/crates/bibtex-utils/src/field/date.rs index 8e2c0b7d72..4e3dfd19d9 100644 --- a/support/texlab/crates/bibtex-utils/src/field/date.rs +++ b/support/texlab/crates/bibtex-utils/src/field/date.rs @@ -3,7 +3,7 @@ use std::{fmt, ops::Add, str::FromStr}; use chrono::{Datelike, Month, NaiveDate}; use syntax::bibtex::Value; -use super::text::TextFieldData; +use super::{text::TextFieldData, FieldParseCache}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] pub enum DateField { @@ -73,8 +73,8 @@ impl fmt::Display for DateFieldData { } impl DateFieldData { - pub fn parse(value: &Value) -> Option<Self> { - let TextFieldData { text } = TextFieldData::parse(value)?; + pub fn parse(value: &Value, cache: &FieldParseCache) -> Option<Self> { + let TextFieldData { text } = TextFieldData::parse(value, cache)?; NaiveDate::from_str(&text) .ok() .map(Self::Date) diff --git a/support/texlab/crates/bibtex-utils/src/field/number.rs b/support/texlab/crates/bibtex-utils/src/field/number.rs index 227f3d5b75..34240bcfb3 100644 --- a/support/texlab/crates/bibtex-utils/src/field/number.rs +++ b/support/texlab/crates/bibtex-utils/src/field/number.rs @@ -2,7 +2,7 @@ use std::fmt; use syntax::bibtex::Value; -use super::text::TextFieldData; +use super::{text::TextFieldData, FieldParseCache}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] pub enum NumberField { @@ -48,8 +48,8 @@ impl fmt::Display for NumberFieldData { } impl NumberFieldData { - pub fn parse(value: &Value) -> Option<Self> { - let TextFieldData { text } = TextFieldData::parse(value)?; + pub fn parse(value: &Value, cache: &FieldParseCache) -> Option<Self> { + let TextFieldData { text } = TextFieldData::parse(value, cache)?; text.split_once("--") .or_else(|| text.split_once('-')) .and_then(|(a, b)| Some((a.parse().ok()?, b.parse().ok()?))) diff --git a/support/texlab/crates/bibtex-utils/src/field/text.rs b/support/texlab/crates/bibtex-utils/src/field/text.rs index 9689264779..b3eabaff0e 100644 --- a/support/texlab/crates/bibtex-utils/src/field/text.rs +++ b/support/texlab/crates/bibtex-utils/src/field/text.rs @@ -1,11 +1,12 @@ use rowan::{ast::AstNode, NodeOrToken}; -use rustc_hash::FxHashSet; use syntax::bibtex::{ - Accent, Command, CurlyGroup, HasAccentName, HasCommandName, HasName, HasValue, HasWord, Join, - Literal, QuoteGroup, Root, SyntaxKind::*, SyntaxToken, Value, + Accent, Command, CurlyGroup, HasAccentName, HasCommandName, HasName, HasWord, Join, + Literal, QuoteGroup, SyntaxKind::*, SyntaxToken, Value, }; +use super::FieldParseCache; + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] pub enum TextField { Abstract, @@ -118,20 +119,28 @@ pub struct TextFieldData { } impl TextFieldData { - pub fn parse(value: &Value) -> Option<Self> { - let mut builder = TextFieldDataBuilder::default(); + pub fn parse(value: &Value, cache: &FieldParseCache) -> Option<Self> { + let mut builder = TextFieldDataBuilder::new(cache); builder.visit_value(value)?; Some(builder.data) } } -#[derive(Default)] -struct TextFieldDataBuilder { +struct TextFieldDataBuilder<'a> { data: TextFieldData, - string_stack: FxHashSet<String>, + cache: &'a FieldParseCache, +} + +impl<'a> TextFieldDataBuilder<'a> { + fn new(cache: &'a FieldParseCache) -> Self { + TextFieldDataBuilder { + data: Default::default(), + cache, + } + } } -impl TextFieldDataBuilder { +impl<'a> TextFieldDataBuilder<'a> { fn visit_value(&mut self, value: &Value) -> Option<()> { match value { Value::Literal(lit) => { @@ -170,24 +179,9 @@ impl TextFieldDataBuilder { } fn visit_string_reference(&mut self, name: &SyntaxToken) -> Option<()> { - let root = Root::cast(name.parent_ancestors().last()?)?; let name = name.text(); - - let value = root - .strings() - .filter(|string| { - string - .name_token() - .map_or(false, |token| token.text() == name) - }) - .find_map(|string| string.value())?; - - if !self.string_stack.insert(name.to_string()) { - return None; - } - - let _ = self.visit_value(&value); - self.string_stack.remove(name); + let value = self.cache.get(name)?; + self.data.text.push_str(value); Some(()) } diff --git a/support/texlab/crates/citeproc/Cargo.toml b/support/texlab/crates/citeproc/Cargo.toml index c87b2d17c3..9d356b3778 100644 --- a/support/texlab/crates/citeproc/Cargo.toml +++ b/support/texlab/crates/citeproc/Cargo.toml @@ -8,6 +8,7 @@ rust-version.workspace = true [dependencies] bibtex-utils = { path = "../bibtex-utils" } +base-db = { path = "../base-db" } isocountry = "0.3.2" itertools = "0.12.0" rowan = "0.15.15" diff --git a/support/texlab/crates/citeproc/src/driver.rs b/support/texlab/crates/citeproc/src/driver.rs index 778e2539b5..cee6bb097e 100644 --- a/support/texlab/crates/citeproc/src/driver.rs +++ b/support/texlab/crates/citeproc/src/driver.rs @@ -1,3 +1,4 @@ +use base_db::semantics::bib::Semantics; use bibtex_utils::field::{ author::AuthorField, date::DateField, @@ -21,8 +22,8 @@ pub struct Driver { } impl Driver { - pub fn process(&mut self, entry: &bibtex::Entry) { - let entry = EntryData::from(entry); + pub fn process(&mut self, entry: &bibtex::Entry, semantics: &Semantics) { + let entry = EntryData::from_entry(entry, semantics); match entry.kind { EntryKind::Article | EntryKind::DataSet diff --git a/support/texlab/crates/citeproc/src/entry.rs b/support/texlab/crates/citeproc/src/entry.rs index 243908da95..f544858efc 100644 --- a/support/texlab/crates/citeproc/src/entry.rs +++ b/support/texlab/crates/citeproc/src/entry.rs @@ -3,7 +3,9 @@ use bibtex_utils::field::{ date::{DateField, DateFieldData}, number::{NumberField, NumberFieldData}, text::{TextField, TextFieldData}, + FieldParseCache, }; +use base_db::semantics::bib::Semantics; use rustc_hash::FxHashMap; use syntax::bibtex::{Entry, Field, HasName, HasType, HasValue, Value}; @@ -103,8 +105,8 @@ pub struct EntryData { pub number: FxHashMap<NumberField, NumberFieldData>, } -impl From<&Entry> for EntryData { - fn from(entry: &Entry) -> Self { +impl EntryData { + pub fn from_entry(entry: &Entry, semantics: &Semantics) -> Self { let mut data = EntryData { kind: entry.type_token().map_or(EntryKind::Unknown, |token| { EntryKind::parse(&token.text()[1..]) @@ -113,7 +115,7 @@ impl From<&Entry> for EntryData { }; for field in entry.fields() { - let _ = data.parse_field(&field); + let _ = data.parse_field(&field, &semantics.expanded_defs); } data @@ -121,40 +123,40 @@ impl From<&Entry> for EntryData { } impl EntryData { - fn parse_field(&mut self, field: &Field) -> Option<()> { + fn parse_field(&mut self, field: &Field, expanded_defs: &FieldParseCache) -> Option<()> { let name = field.name_token()?; let name = name.text(); let value = field.value()?; - self.parse_author_field(name, &value) - .or_else(|| self.parse_date_field(name, &value)) - .or_else(|| self.parse_number_field(name, &value)) - .or_else(|| self.parse_text_field(name, &value)) + self.parse_author_field(name, &value, expanded_defs) + .or_else(|| self.parse_date_field(name, &value, expanded_defs)) + .or_else(|| self.parse_number_field(name, &value, expanded_defs)) + .or_else(|| self.parse_text_field(name, &value, expanded_defs)) } - fn parse_author_field(&mut self, name: &str, value: &Value) -> Option<()> { + fn parse_author_field(&mut self, name: &str, value: &Value, expanded_defs: &FieldParseCache) -> Option<()> { let name = AuthorField::parse(name)?; - let data = AuthorFieldData::parse(value)?; + let data = AuthorFieldData::parse(value, expanded_defs)?; self.author.insert(name, data); Some(()) } - fn parse_date_field(&mut self, name: &str, value: &Value) -> Option<()> { + fn parse_date_field(&mut self, name: &str, value: &Value, expanded_defs: &FieldParseCache) -> Option<()> { let name = DateField::parse(name)?; - let data = DateFieldData::parse(value)?; + let data = DateFieldData::parse(value, expanded_defs)?; self.date.insert(name, data); Some(()) } - fn parse_number_field(&mut self, name: &str, value: &Value) -> Option<()> { + fn parse_number_field(&mut self, name: &str, value: &Value, expanded_defs: &FieldParseCache) -> Option<()> { let name = NumberField::parse(name)?; - let data = NumberFieldData::parse(value)?; + let data = NumberFieldData::parse(value, expanded_defs)?; self.number.insert(name, data); Some(()) } - fn parse_text_field(&mut self, name: &str, value: &Value) -> Option<()> { + fn parse_text_field(&mut self, name: &str, value: &Value, expanded_defs: &FieldParseCache) -> Option<()> { let name = TextField::parse(name).unwrap_or(TextField::Unknown); - let data = TextFieldData::parse(value)?; + let data = TextFieldData::parse(value, expanded_defs)?; self.text.insert(name, data); Some(()) } diff --git a/support/texlab/crates/citeproc/src/lib.rs b/support/texlab/crates/citeproc/src/lib.rs index de24306583..915d3d69ba 100644 --- a/support/texlab/crates/citeproc/src/lib.rs +++ b/support/texlab/crates/citeproc/src/lib.rs @@ -2,16 +2,17 @@ mod driver; mod entry; mod output; +use base_db::semantics::bib::Semantics; use syntax::bibtex; use unicode_normalization::UnicodeNormalization; use self::{driver::Driver, output::Inline}; #[must_use] -pub fn render(entry: &bibtex::Entry) -> Option<String> { +pub fn render(entry: &bibtex::Entry, semantics: &Semantics) -> Option<String> { let mut output = String::new(); let mut driver = Driver::default(); - driver.process(entry); + driver.process(entry, semantics); driver.finish().for_each(|(inline, punct)| { let text = match inline { Inline::Regular(text) => text, diff --git a/support/texlab/crates/citeproc/src/tests.rs b/support/texlab/crates/citeproc/src/tests.rs index 1c91580f39..b5838b52b5 100644 --- a/support/texlab/crates/citeproc/src/tests.rs +++ b/support/texlab/crates/citeproc/src/tests.rs @@ -1,3 +1,4 @@ +use base_db::semantics::bib::Semantics; use expect_test::{expect, Expect}; use parser::parse_bibtex; use rowan::ast::AstNode; @@ -5,9 +6,12 @@ use syntax::bibtex; fn check(input: &str, expect: Expect) { let green = parse_bibtex(input); - let root = bibtex::Root::cast(bibtex::SyntaxNode::new_root(green)).unwrap(); + let root = bibtex::SyntaxNode::new_root(green); + let mut semantics = Semantics::default(); + semantics.process_root(&root); + let root = bibtex::Root::cast(root).unwrap(); let entry = root.entries().next().unwrap(); - let output = super::render(&entry).unwrap(); + let output = super::render(&entry, &semantics).unwrap(); expect.assert_eq(&output); } diff --git a/support/texlab/crates/commands/Cargo.toml b/support/texlab/crates/commands/Cargo.toml index c54d77abf4..512a05ee61 100644 --- a/support/texlab/crates/commands/Cargo.toml +++ b/support/texlab/crates/commands/Cargo.toml @@ -9,15 +9,15 @@ rust-version.workspace = true [dependencies] anyhow = "1.0.72" base-db = { path = "../base-db" } -bstr = "1.8.0" +bstr = "1.9.0" crossbeam-channel = "0.5.11" itertools = "0.12.0" -libc = "0.2.150" -log = "0.4.19" +libc = "0.2.153" +log = "0.4.21" rowan = "0.15.15" rustc-hash = "1.1.0" syntax = { path = "../syntax" } -thiserror = "1.0.56" +thiserror = "1.0.57" url = "2.5.0" [dev-dependencies] diff --git a/support/texlab/crates/completion-data/Cargo.toml b/support/texlab/crates/completion-data/Cargo.toml index c32c02d3bc..37ecf1b3cf 100644 --- a/support/texlab/crates/completion-data/Cargo.toml +++ b/support/texlab/crates/completion-data/Cargo.toml @@ -12,7 +12,7 @@ itertools = "0.12.0" once_cell = "1.19.0" rustc-hash = "1.1.0" serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.108" +serde_json = "1.0.114" [lib] doctest = false diff --git a/support/texlab/crates/completion/Cargo.toml b/support/texlab/crates/completion/Cargo.toml index 5bcebea435..fbdd2d7201 100644 --- a/support/texlab/crates/completion/Cargo.toml +++ b/support/texlab/crates/completion/Cargo.toml @@ -11,7 +11,7 @@ base-db = { path = "../base-db" } completion-data = { path = "../completion-data" } fuzzy-matcher = { version = "0.3.7", features = ["compact"] } line-index = { path = "../line-index" } -rayon = "1.7.0" +rayon = "1.8.1" rowan = "0.15.15" rustc-hash = "1.1.0" syntax = { path = "../syntax" } diff --git a/support/texlab/crates/diagnostics/Cargo.toml b/support/texlab/crates/diagnostics/Cargo.toml index 1fb4b2171d..91c2ff9dbd 100644 --- a/support/texlab/crates/diagnostics/Cargo.toml +++ b/support/texlab/crates/diagnostics/Cargo.toml @@ -14,8 +14,8 @@ 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" +log = "0.4.21" +multimap = "0.10.0" once_cell = "1.19.0" parking_lot = "0.12.1" regex = "1.10.2" diff --git a/support/texlab/crates/diagnostics/src/citations.rs b/support/texlab/crates/diagnostics/src/citations.rs index 403d2f5bfc..ea4ef03d58 100644 --- a/support/texlab/crates/diagnostics/src/citations.rs +++ b/support/texlab/crates/diagnostics/src/citations.rs @@ -3,8 +3,7 @@ use base_db::{ util::queries::{self, Object}, Document, Project, Workspace, }; -use multimap::MultiMap; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; use url::Url; use crate::types::{BibError, Diagnostic, TexError}; @@ -14,7 +13,7 @@ const MAX_UNUSED_ENTRIES: usize = 1000; pub fn detect_undefined_citations<'a>( project: &Project<'a>, document: &'a Document, - results: &mut MultiMap<Url, Diagnostic>, + results: &mut FxHashMap<Url, Vec<Diagnostic>>, ) -> Option<()> { let data = document.data.as_tex()?; @@ -26,7 +25,10 @@ pub fn detect_undefined_citations<'a>( let name = citation.name_text(); if name != "*" && !entries.contains(name) { let diagnostic = Diagnostic::Tex(citation.name.range, TexError::UndefinedCitation); - results.insert(document.uri.clone(), diagnostic); + results + .entry(document.uri.clone()) + .or_default() + .push(diagnostic); } } @@ -36,7 +38,7 @@ pub fn detect_undefined_citations<'a>( pub fn detect_unused_entries<'a>( project: &Project<'a>, document: &'a Document, - results: &mut MultiMap<Url, Diagnostic>, + results: &mut FxHashMap<Url, Vec<Diagnostic>>, ) -> Option<()> { let data = document.data.as_bib()?; @@ -52,7 +54,10 @@ pub fn detect_unused_entries<'a>( for entry in &data.semantics.entries { if !citations.contains(entry.name.text.as_str()) { let diagnostic = Diagnostic::Bib(entry.name.range, BibError::UnusedEntry); - results.insert(document.uri.clone(), diagnostic); + results + .entry(document.uri.clone()) + .or_default() + .push(diagnostic); } } @@ -61,7 +66,7 @@ pub fn detect_unused_entries<'a>( pub fn detect_duplicate_entries<'a>( workspace: &'a Workspace, - results: &mut MultiMap<Url, Diagnostic>, + results: &mut FxHashMap<Url, Vec<Diagnostic>>, ) { for conflict in queries::Conflict::find_all::<Entry>(workspace) { let others = conflict @@ -71,6 +76,9 @@ pub fn detect_duplicate_entries<'a>( .collect(); let diagnostic = Diagnostic::Bib(conflict.main.range, BibError::DuplicateEntry(others)); - results.insert(conflict.main.document.uri.clone(), diagnostic); + results + .entry(conflict.main.document.uri.clone()) + .or_default() + .push(diagnostic); } } diff --git a/support/texlab/crates/diagnostics/src/labels.rs b/support/texlab/crates/diagnostics/src/labels.rs index 92f9072918..9a39f3f24b 100644 --- a/support/texlab/crates/diagnostics/src/labels.rs +++ b/support/texlab/crates/diagnostics/src/labels.rs @@ -4,15 +4,14 @@ use base_db::{ DocumentData, Workspace, }; use itertools::Itertools; -use multimap::MultiMap; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; use url::Url; use crate::types::{Diagnostic, TexError}; pub fn detect_undefined_and_unused_labels( workspace: &Workspace, - results: &mut MultiMap<Url, Diagnostic>, + results: &mut FxHashMap<Url, Vec<Diagnostic>>, ) { let graphs: Vec<_> = workspace .iter() @@ -45,18 +44,27 @@ pub fn detect_undefined_and_unused_labels( for label in &data.semantics.labels { if label.kind != LabelKind::Definition && !label_defs.contains(&label.name.text) { let diagnostic = Diagnostic::Tex(label.name.range, TexError::UndefinedLabel); - results.insert(document.uri.clone(), diagnostic); + results + .entry(document.uri.clone()) + .or_default() + .push(diagnostic); } if label.kind == LabelKind::Definition && !label_refs.contains(&label.name.text) { let diagnostic = Diagnostic::Tex(label.name.range, TexError::UnusedLabel); - results.insert(document.uri.clone(), diagnostic); + results + .entry(document.uri.clone()) + .or_default() + .push(diagnostic); } } } } -pub fn detect_duplicate_labels(workspace: &Workspace, results: &mut MultiMap<Url, Diagnostic>) { +pub fn detect_duplicate_labels( + workspace: &Workspace, + results: &mut FxHashMap<Url, Vec<Diagnostic>>, +) { for conflict in queries::Conflict::find_all::<Label>(workspace) { let others = conflict .rest @@ -65,6 +73,9 @@ pub fn detect_duplicate_labels(workspace: &Workspace, results: &mut MultiMap<Url .collect(); let diagnostic = Diagnostic::Tex(conflict.main.range, TexError::DuplicateLabel(others)); - results.insert(conflict.main.document.uri.clone(), diagnostic); + results + .entry(conflict.main.document.uri.clone()) + .or_default() + .push(diagnostic); } } diff --git a/support/texlab/crates/diagnostics/src/manager.rs b/support/texlab/crates/diagnostics/src/manager.rs index 1ce5e194b8..38c0a514f4 100644 --- a/support/texlab/crates/diagnostics/src/manager.rs +++ b/support/texlab/crates/diagnostics/src/manager.rs @@ -16,6 +16,10 @@ pub struct Manager { impl Manager { /// Updates the syntax-based diagnostics for the given document. pub fn update_syntax(&mut self, workspace: &Workspace, document: &Document) { + if !Self::is_relevant(document) { + return; + } + self.grammar.remove(&document.uri); super::grammar::tex::update(document, workspace.config(), &mut self.grammar); super::grammar::bib::update(document, &mut self.grammar); @@ -30,14 +34,20 @@ impl Manager { } /// Returns all filtered diagnostics for the given workspace. - pub fn get(&self, workspace: &Workspace) -> MultiMap<Url, Diagnostic> { - let mut results = MultiMap::default(); + pub fn get(&self, workspace: &Workspace) -> FxHashMap<Url, Vec<Diagnostic>> { + let mut results: FxHashMap<Url, Vec<Diagnostic>> = FxHashMap::default(); for (uri, diagnostics) in &self.grammar { - results.insert_many_from_slice(uri.clone(), diagnostics); + results + .entry(uri.clone()) + .or_default() + .extend(diagnostics.iter().cloned()); } for (uri, diagnostics) in self.build_log.values().flatten() { - results.insert_many_from_slice(uri.clone(), diagnostics); + results + .entry(uri.clone()) + .or_default() + .extend(diagnostics.iter().cloned()); } for (uri, diagnostics) in &self.chktex { @@ -45,11 +55,17 @@ impl Manager { .lookup(uri) .map_or(false, |document| document.owner == Owner::Client) { - results.insert_many_from_slice(uri.clone(), diagnostics); + results + .entry(uri.clone()) + .or_default() + .extend(diagnostics.iter().cloned()); } } - for document in workspace.iter() { + for document in workspace + .iter() + .filter(|document| Self::is_relevant(document)) + { let project = workspace.project(document); super::citations::detect_undefined_citations(&project, document, &mut results); super::citations::detect_unused_entries(&project, document, &mut results); @@ -60,6 +76,13 @@ impl Manager { super::labels::detect_undefined_and_unused_labels(workspace, &mut results); let config = &workspace.config().diagnostics; + + results.retain(|uri, _| { + workspace + .lookup(uri) + .map_or(false, |document| Self::is_relevant(document)) + }); + for (_, diagnostics) in &mut results { diagnostics.retain(|diagnostic| { filter_regex_patterns( @@ -72,4 +95,12 @@ impl Manager { results } + + fn is_relevant(document: &Document) -> bool { + match document.owner { + Owner::Client => true, + Owner::Server => true, + Owner::Distro => false, + } + } } diff --git a/support/texlab/crates/diagnostics/src/tests.rs b/support/texlab/crates/diagnostics/src/tests.rs index b7c6004d19..3688abeaf4 100644 --- a/support/texlab/crates/diagnostics/src/tests.rs +++ b/support/texlab/crates/diagnostics/src/tests.rs @@ -11,7 +11,7 @@ fn check(input: &str, expect: Expect) { let results = manager.get(&fixture.workspace); let results = results - .iter_all() + .iter() .filter(|(_, diags)| !diags.is_empty()) .sorted_by(|(uri1, _), (uri2, _)| uri1.cmp(&uri2)) .map(|(uri, diags)| (uri.as_str(), diags)) diff --git a/support/texlab/crates/distro/src/file_name_db.rs b/support/texlab/crates/distro/src/file_name_db.rs index 06871bde83..fd6750fffa 100644 --- a/support/texlab/crates/distro/src/file_name_db.rs +++ b/support/texlab/crates/distro/src/file_name_db.rs @@ -55,6 +55,11 @@ impl FileNameDB { self.files.get(name).map(|file| file.path()) } + pub fn contains(&self, path: &Path) -> bool { + let name = path.file_name().unwrap().to_str().unwrap(); + self.get(name) == Some(path) + } + pub fn iter(&self) -> impl Iterator<Item = (&str, &Path)> + '_ { self.files.iter().map(|file| (file.name(), file.path())) } diff --git a/support/texlab/crates/hover/src/citation.rs b/support/texlab/crates/hover/src/citation.rs index 372668997e..a39ea2e5f5 100644 --- a/support/texlab/crates/hover/src/citation.rs +++ b/support/texlab/crates/hover/src/citation.rs @@ -31,7 +31,7 @@ pub(super) fn find_hover<'a>(params: &HoverParams<'a>) -> Option<Hover<'a>> { let data = document.data.as_bib()?; let root = bibtex::Root::cast(data.root_node())?; let entry = root.find_entry(name)?; - citeproc::render(&entry) + citeproc::render(&entry, &data.semantics) })?; let data = HoverData::Citation(text); diff --git a/support/texlab/crates/hover/src/string_ref.rs b/support/texlab/crates/hover/src/string_ref.rs index 6726041af2..cf8062a5a0 100644 --- a/support/texlab/crates/hover/src/string_ref.rs +++ b/support/texlab/crates/hover/src/string_ref.rs @@ -24,7 +24,7 @@ pub(super) fn find_hover<'a>(params: &HoverParams<'a>) -> Option<Hover<'a>> { continue; } - let value = TextFieldData::parse(&string.value()?)?.text; + let value = TextFieldData::parse(&string.value()?, &data.semantics.expanded_defs)?.text; return Some(Hover { range: name.text_range(), data: HoverData::StringRef(value), diff --git a/support/texlab/crates/parser/Cargo.toml b/support/texlab/crates/parser/Cargo.toml index c3e31b7022..0fd8889899 100644 --- a/support/texlab/crates/parser/Cargo.toml +++ b/support/texlab/crates/parser/Cargo.toml @@ -13,7 +13,7 @@ regex = "1.10.2" rowan = "0.15.15" rustc-hash = "1.1.0" syntax = { path = "../syntax" } -tempfile = "3.8.1" +tempfile = "3.10.1" [dev-dependencies] expect-test = "1.4.1" diff --git a/support/texlab/crates/parser/src/config.rs b/support/texlab/crates/parser/src/config.rs index 3a2e9e3e93..6537d21b0d 100644 --- a/support/texlab/crates/parser/src/config.rs +++ b/support/texlab/crates/parser/src/config.rs @@ -7,6 +7,7 @@ pub struct SyntaxConfig { pub enum_environments: FxHashSet<String>, pub verbatim_environments: FxHashSet<String>, pub citation_commands: FxHashSet<String>, + pub label_reference_commands: FxHashSet<String>, } impl Default for SyntaxConfig { @@ -31,12 +32,18 @@ impl Default for SyntaxConfig { .map(ToString::to_string) .collect(); + let label_reference_commands = DEFAULT_LABEL_REFERENCE_COMMANDS + .iter() + .map(ToString::to_string) + .collect(); + Self { follow_package_links: false, math_environments, enum_environments, verbatim_environments, citation_commands, + label_reference_commands, } } } @@ -155,3 +162,24 @@ static DEFAULT_CITATION_COMMANDS: &[&str] = &[ "citeA", "citeA*", ]; + +static DEFAULT_LABEL_REFERENCE_COMMANDS: &[&str] = &[ + "ref", + "vref", + "Vref", + "autoref", + "pageref", + "cref", + "cref*", + "Cref", + "Cref*", + "namecref", + "nameCref", + "lcnamecref", + "namecrefs", + "nameCrefs", + "lcnamecrefs", + "labelcref", + "labelcpageref", + "eqref", +]; diff --git a/support/texlab/crates/parser/src/latex.rs b/support/texlab/crates/parser/src/latex.rs index d88eb39556..4a6fabae21 100644 --- a/support/texlab/crates/parser/src/latex.rs +++ b/support/texlab/crates/parser/src/latex.rs @@ -25,6 +25,13 @@ impl Default for ParserContext { } } +#[derive(Debug, Clone, Copy)] +struct KeyOptions { + allow_eq: bool, + allow_parens: bool, + allow_bracks: bool, +} + #[derive(Debug)] struct Parser<'a> { lexer: Lexer<'a>, @@ -309,7 +316,11 @@ impl<'a> Parser<'a> { self.trivia(); match self.peek() { Some(Token::Word | Token::Pipe) => { - self.key(); + self.key_with_opts(KeyOptions { + allow_eq: true, + allow_bracks: false, + allow_parens: true, + }); } Some(_) | None => {} } @@ -340,16 +351,22 @@ impl<'a> Parser<'a> { } fn key(&mut self) { - self.key_with_eq(true); + self.key_with_opts(KeyOptions { + allow_eq: true, + allow_parens: true, + allow_bracks: true, + }); } - fn key_with_eq(&mut self, allow_eq: bool) { + fn key_with_opts(&mut self, options: KeyOptions) { self.builder.start_node(KEY.into()); self.eat(); while let Some(kind) = self.peek() { match kind { Token::Whitespace | Token::LineComment | Token::Word | Token::Pipe => self.eat(), - Token::Eq if allow_eq => self.eat(), + Token::LBrack | Token::RBrack if options.allow_bracks => self.eat(), + Token::LParen | Token::RParen if options.allow_parens => self.eat(), + Token::Eq if options.allow_eq => self.eat(), _ => break, } } @@ -374,7 +391,12 @@ impl<'a> Parser<'a> { fn key_value_pair(&mut self) { self.builder.start_node(KEY_VALUE_PAIR.into()); - self.key_with_eq(false); + self.key_with_opts(KeyOptions { + allow_eq: false, + allow_parens: true, + allow_bracks: false, + }); + if self.peek() == Some(Token::Eq) { self.eat(); self.trivia(); diff --git a/support/texlab/crates/parser/src/latex/lexer/commands.rs b/support/texlab/crates/parser/src/latex/lexer/commands.rs index b6e1d11737..95eef4becd 100644 --- a/support/texlab/crates/parser/src/latex/lexer/commands.rs +++ b/support/texlab/crates/parser/src/latex/lexer/commands.rs @@ -30,9 +30,6 @@ pub fn classify(name: &str, config: &SyntaxConfig) -> CommandName { CommandName::Import } "label" => CommandName::LabelDefinition, - "ref" | "vref" | "Vref" | "autoref" | "pageref" | "cref" | "cref*" | "Cref" | "Cref*" - | "namecref" | "nameCref" | "lcnamecref" | "namecrefs" | "nameCrefs" | "lcnamecrefs" - | "labelcref" | "labelcpageref" | "eqref" => CommandName::LabelReference, "crefrange" | "crefrange*" | "Crefrange" | "Crefrange*" => CommandName::LabelReferenceRange, "newlabel" => CommandName::LabelNumber, "newcommand" @@ -81,6 +78,7 @@ pub fn classify(name: &str, config: &SyntaxConfig) -> CommandName { "fi" => CommandName::EndBlockComment, "verb" => CommandName::VerbatimBlock, _ if config.citation_commands.contains(name) => CommandName::Citation, + _ if config.label_reference_commands.contains(name) => CommandName::LabelReference, _ => CommandName::Generic, } } diff --git a/support/texlab/crates/parser/src/latex/tests.rs b/support/texlab/crates/parser/src/latex/tests.rs index 6e5019776f..5e1a4044cd 100644 --- a/support/texlab/crates/parser/src/latex/tests.rs +++ b/support/texlab/crates/parser/src/latex/tests.rs @@ -3542,3 +3542,72 @@ fn test_command_subscript() { "#]], ); } + +#[test] +fn test_label_parens() { + check( + r#"\label{foo (bar)}"#, + expect![[r#" + ROOT@0..17 + PREAMBLE@0..17 + LABEL_DEFINITION@0..17 + COMMAND_NAME@0..6 "\\label" + CURLY_GROUP_WORD@6..17 + L_CURLY@6..7 "{" + KEY@7..16 + WORD@7..10 "foo" + WHITESPACE@10..11 " " + L_PAREN@11..12 "(" + WORD@12..15 "bar" + R_PAREN@15..16 ")" + R_CURLY@16..17 "}" + + "#]], + ); +} + +#[test] +fn test_label_brackets() { + check( + r#"\label{foo [bar]}"#, + expect![[r#" + ROOT@0..17 + PREAMBLE@0..17 + LABEL_DEFINITION@0..17 + COMMAND_NAME@0..6 "\\label" + CURLY_GROUP_WORD@6..17 + L_CURLY@6..7 "{" + KEY@7..16 + WORD@7..10 "foo" + WHITESPACE@10..11 " " + L_BRACK@11..12 "[" + WORD@12..15 "bar" + R_BRACK@15..16 "]" + R_CURLY@16..17 "}" + + "#]], + ); +} + +#[test] +fn test_label_brackets_unbalanced() { + check( + r#"\label{foo ]bar[}"#, + expect![[r#" + ROOT@0..17 + PREAMBLE@0..17 + LABEL_DEFINITION@0..17 + COMMAND_NAME@0..6 "\\label" + CURLY_GROUP_WORD@6..17 + L_CURLY@6..7 "{" + KEY@7..16 + WORD@7..10 "foo" + WHITESPACE@10..11 " " + R_BRACK@11..12 "]" + WORD@12..15 "bar" + L_BRACK@15..16 "[" + R_CURLY@16..17 "}" + + "#]], + ); +} diff --git a/support/texlab/crates/texlab/Cargo.toml b/support/texlab/crates/texlab/Cargo.toml index 53b63ded3e..ccc02bff01 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.3" +version = "5.13.1" license.workspace = true readme = "README.md" authors.workspace = true @@ -26,7 +26,7 @@ anyhow = "1.0.75" base-db = { path = "../base-db" } bibfmt = { path = "../bibfmt" } citeproc = { path = "../citeproc" } -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.18", features = ["derive"] } commands = { path = "../commands" } completion = { path = "../completion" } completion-data = { path = "../completion-data" } @@ -43,9 +43,9 @@ hover = { path = "../hover" } inlay-hints = { path = "../inlay-hints" } line-index = { path = "../line-index" } links = { path = "../links" } -log = "0.4.19" +log = "0.4.21" lsp-server = "0.7.6" -lsp-types = "0.94.1" +lsp-types = "0.95.0" notify = "6.1.1" notify-debouncer-full = "0.3.1" once_cell = "1.19.0" @@ -57,12 +57,12 @@ rename = { path = "../rename" } rowan = "0.15.15" rustc-hash = "1.1.0" serde = "1.0.195" -serde_json = "1.0.108" +serde_json = "1.0.114" serde_regex = "1.1.0" -serde_repr = "0.1.17" +serde_repr = "0.1.18" symbols = { path = "../symbols" } syntax = { path = "../syntax" } -tempfile = "3.8.0" +tempfile = "3.10.1" threadpool = "1.8.1" [dev-dependencies] diff --git a/support/texlab/crates/texlab/src/client.rs b/support/texlab/crates/texlab/src/client.rs index 9bbace9938..b79f876436 100644 --- a/support/texlab/crates/texlab/src/client.rs +++ b/support/texlab/crates/texlab/src/client.rs @@ -93,8 +93,14 @@ impl LspClient { Ok(()) } - pub fn parse_options(&self, value: serde_json::Value) -> Result<Options> { - let options = match serde_json::from_value(value) { + pub fn parse_options(&self, mut value: serde_json::Value) -> Result<Options> { + // if there are multiple servers, we need to extract the options for texlab first + let options = match value.get_mut("texlab") { + Some(section) => section.take(), + None => value, + }; + + let options = match serde_json::from_value(options) { Ok(new_options) => new_options, Err(why) => { let message = format!( diff --git a/support/texlab/crates/texlab/src/features/completion.rs b/support/texlab/crates/texlab/src/features/completion.rs index 5d32588970..5bca0070ef 100644 --- a/support/texlab/crates/texlab/src/features/completion.rs +++ b/support/texlab/crates/texlab/src/features/completion.rs @@ -1,4 +1,4 @@ -use base_db::{util::RenderedObject, Workspace}; +use base_db::{util::RenderedObject, MatchingAlgo, Workspace}; use completion::{ArgumentData, CompletionItem, CompletionItemData, EntryTypeData, FieldTypeData}; use line_index::LineIndex; use rowan::ast::AstNode; @@ -20,8 +20,16 @@ pub fn complete( client_flags, }; - let is_incomplete = - client_flags.completion_always_incomplete || result.items.len() >= completion::LIMIT; + let is_fuzzy = match workspace.config().completion.matcher { + MatchingAlgo::Skim => true, + MatchingAlgo::SkimIgnoreCase => true, + MatchingAlgo::Prefix => false, + MatchingAlgo::PrefixIgnoreCase => false, + }; + + let is_incomplete = client_flags.completion_always_incomplete + || !is_fuzzy + || result.items.len() >= completion::LIMIT; let items = result .items @@ -53,7 +61,7 @@ pub fn resolve(workspace: &Workspace, item: &mut lsp_types::CompletionItem) -> O 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)?; + let value = citeproc::render(&entry, &data.semantics)?; item.documentation = Some(lsp_types::Documentation::MarkupContent( lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, diff --git a/support/texlab/crates/texlab/src/server.rs b/support/texlab/crates/texlab/src/server.rs index 0fb186f688..fc16775bc9 100644 --- a/support/texlab/crates/texlab/src/server.rs +++ b/support/texlab/crates/texlab/src/server.rs @@ -813,7 +813,7 @@ impl Server { .map_or(true, |document| document.owner == Owner::Server) { if let Some(language) = Language::from_path(&path) { - changed |= workspace.load(&path, language, Owner::Server).is_ok(); + changed |= workspace.load(&path, language).is_ok(); if let Some(document) = workspace.lookup_path(&path) { self.diagnostic_manager.update_syntax(&workspace, document); diff --git a/support/texlab/crates/texlab/src/server/options.rs b/support/texlab/crates/texlab/src/server/options.rs index 72b2b127fb..60c0bf90f7 100644 --- a/support/texlab/crates/texlab/src/server/options.rs +++ b/support/texlab/crates/texlab/src/server/options.rs @@ -128,6 +128,7 @@ pub struct ExperimentalOptions { pub enum_environments: Vec<String>, pub verbatim_environments: Vec<String>, pub citation_commands: Vec<String>, + pub label_reference_commands: Vec<String>, } #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] @@ -283,5 +284,10 @@ impl From<Options> for Config { .extend(value.experimental.citation_commands); config + .syntax + .label_reference_commands + .extend(value.experimental.label_reference_commands); + + config } } |