From 03f1daec22e3e5e2ae8d117d503ee9648c5b3f91 Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Sun, 13 Aug 2023 03:03:49 +0000 Subject: CTAN sync 202308130303 --- support/texlab/crates/base-db/Cargo.toml | 1 + support/texlab/crates/base-db/src/semantics/bib.rs | 23 +- support/texlab/crates/base-db/src/workspace.rs | 6 + support/texlab/crates/bibtex-utils/Cargo.toml | 23 ++ support/texlab/crates/bibtex-utils/src/field.rs | 4 + .../texlab/crates/bibtex-utils/src/field/author.rs | 80 ++++++ .../texlab/crates/bibtex-utils/src/field/date.rs | 85 ++++++ .../texlab/crates/bibtex-utils/src/field/number.rs | 60 ++++ .../texlab/crates/bibtex-utils/src/field/text.rs | 306 +++++++++++++++++++++ support/texlab/crates/bibtex-utils/src/lib.rs | 1 + support/texlab/crates/citeproc/Cargo.toml | 3 +- support/texlab/crates/citeproc/src/driver.rs | 12 +- support/texlab/crates/citeproc/src/entry.rs | 7 +- support/texlab/crates/citeproc/src/field.rs | 4 - support/texlab/crates/citeproc/src/field/author.rs | 80 ------ support/texlab/crates/citeproc/src/field/date.rs | 85 ------ support/texlab/crates/citeproc/src/field/number.rs | 60 ---- support/texlab/crates/citeproc/src/field/text.rs | 306 --------------------- support/texlab/crates/citeproc/src/lib.rs | 1 - support/texlab/crates/completion-data/src/lib.rs | 2 +- support/texlab/crates/definition/src/citation.rs | 2 +- support/texlab/crates/definition/src/command.rs | 2 +- support/texlab/crates/definition/src/include.rs | 2 +- support/texlab/crates/definition/src/label.rs | 2 +- support/texlab/crates/definition/src/lib.rs | 2 +- support/texlab/crates/definition/src/string_ref.rs | 2 +- support/texlab/crates/diagnostics/src/build_log.rs | 26 +- support/texlab/crates/diagnostics/src/citations.rs | 27 +- support/texlab/crates/diagnostics/src/lib.rs | 18 +- support/texlab/crates/hover/Cargo.toml | 1 + support/texlab/crates/hover/src/citation.rs | 2 +- support/texlab/crates/hover/src/label.rs | 2 +- support/texlab/crates/hover/src/lib.rs | 12 +- support/texlab/crates/hover/src/string_ref.rs | 2 +- support/texlab/crates/references/src/entry.rs | 2 +- support/texlab/crates/references/src/label.rs | 2 +- support/texlab/crates/references/src/string_def.rs | 2 +- support/texlab/crates/syntax/src/bibtex.rs | 299 +------------------- support/texlab/crates/syntax/src/bibtex/cst.rs | 244 ++++++++++++++++ support/texlab/crates/syntax/src/bibtex/kind.rs | 43 +++ support/texlab/crates/syntax/src/lib.rs | 13 - support/texlab/crates/texlab/Cargo.toml | 4 +- support/texlab/crates/texlab/benches/bench_main.rs | 35 +++ .../texlab/src/features/completion/argument.rs | 2 +- .../texlab/src/features/completion/builder.rs | 92 ++++--- .../texlab/src/features/completion/citation.rs | 10 +- .../src/features/completion/component_command.rs | 2 +- .../texlab/src/features/completion/matcher.rs | 10 +- support/texlab/crates/texlab/src/server.rs | 21 +- support/texlab/crates/texlab/src/server/options.rs | 2 +- .../texlab/crates/texlab/src/util/diagnostics.rs | 3 +- .../lsp__text_document__completion__citation.snap | 6 +- ...ext_document__completion__citation_acronym.snap | 4 +- ..._document__completion__citation_open_brace.snap | 4 +- ...__completion__citation_open_brace_multiple.snap | 4 +- 55 files changed, 1081 insertions(+), 974 deletions(-) create mode 100644 support/texlab/crates/bibtex-utils/Cargo.toml create mode 100644 support/texlab/crates/bibtex-utils/src/field.rs create mode 100644 support/texlab/crates/bibtex-utils/src/field/author.rs create mode 100644 support/texlab/crates/bibtex-utils/src/field/date.rs create mode 100644 support/texlab/crates/bibtex-utils/src/field/number.rs create mode 100644 support/texlab/crates/bibtex-utils/src/field/text.rs create mode 100644 support/texlab/crates/bibtex-utils/src/lib.rs delete mode 100644 support/texlab/crates/citeproc/src/field.rs delete mode 100644 support/texlab/crates/citeproc/src/field/author.rs delete mode 100644 support/texlab/crates/citeproc/src/field/date.rs delete mode 100644 support/texlab/crates/citeproc/src/field/number.rs delete mode 100644 support/texlab/crates/citeproc/src/field/text.rs create mode 100644 support/texlab/crates/syntax/src/bibtex/cst.rs create mode 100644 support/texlab/crates/syntax/src/bibtex/kind.rs (limited to 'support/texlab/crates') diff --git a/support/texlab/crates/base-db/Cargo.toml b/support/texlab/crates/base-db/Cargo.toml index fa015b24cc..0ae198db7b 100644 --- a/support/texlab/crates/base-db/Cargo.toml +++ b/support/texlab/crates/base-db/Cargo.toml @@ -21,6 +21,7 @@ rustc-hash = "1.1.0" syntax = { path = "../syntax" } text-size = "1.1.1" url = "=2.3.1" +bibtex-utils = { path = "../bibtex-utils" } [lib] doctest = false diff --git a/support/texlab/crates/base-db/src/semantics/bib.rs b/support/texlab/crates/base-db/src/semantics/bib.rs index 22017d3c03..29f9477896 100644 --- a/support/texlab/crates/base-db/src/semantics/bib.rs +++ b/support/texlab/crates/base-db/src/semantics/bib.rs @@ -1,7 +1,11 @@ +use bibtex_utils::field::text::TextFieldData; +use itertools::Itertools; use rowan::ast::AstNode; -use syntax::bibtex::{self, HasName}; +use syntax::bibtex::{self, HasName, HasType, HasValue}; use text_size::TextRange; +use crate::data::{BibtexEntryType, BibtexEntryTypeCategory}; + use super::Span; #[derive(Debug, Clone, Default)] @@ -23,12 +27,27 @@ impl Semantics { fn process_entry(&mut self, entry: bibtex::Entry) { if let Some(name) = entry.name_token() { + let type_token = entry.type_token().unwrap(); + let category = BibtexEntryType::find(&type_token.text()[1..]) + .map_or(BibtexEntryTypeCategory::Misc, |ty| ty.category); + + let field_values = entry + .fields() + .filter_map(|field| Some(TextFieldData::parse(&field.value()?)?.text)); + + let keywords = [name.text().into(), type_token.text().into()] + .into_iter() + .chain(field_values) + .join(" "); + self.entries.push(Entry { name: Span { range: name.text_range(), text: name.text().into(), }, full_range: entry.syntax().text_range(), + category, + keywords, }); } } @@ -50,6 +69,8 @@ impl Semantics { pub struct Entry { pub name: Span, pub full_range: TextRange, + pub keywords: String, + pub category: BibtexEntryTypeCategory, } #[derive(Debug, Clone)] diff --git a/support/texlab/crates/base-db/src/workspace.rs b/support/texlab/crates/base-db/src/workspace.rs index 9308c2401d..35e3904d75 100644 --- a/support/texlab/crates/base-db/src/workspace.rs +++ b/support/texlab/crates/base-db/src/workspace.rs @@ -75,6 +75,12 @@ impl Workspace { Cow::Owned(text) => text, }; + if let Some(document) = self.lookup_path(path) { + if document.text == text { + return Ok(()); + } + } + self.open(uri, text, language, owner, LineCol { line: 0, col: 0 }); Ok(()) } diff --git a/support/texlab/crates/bibtex-utils/Cargo.toml b/support/texlab/crates/bibtex-utils/Cargo.toml new file mode 100644 index 0000000000..7dd75ff738 --- /dev/null +++ b/support/texlab/crates/bibtex-utils/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "bibtex-utils" +version = "0.0.0" +license.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true + +[dependencies] +chrono = { version = "0.4.26", default-features = false, features = ["std"] } +human_name = "2.0.2" +itertools = "0.11.0" +rowan = "0.15.11" +rustc-hash = "1.1.0" +syntax = { path = "../syntax" } +unicode-normalization = "0.1.22" + +[lib] +doctest = false + +[dev-dependencies] +expect-test = "1.4.1" +parser = { path = "../parser" } diff --git a/support/texlab/crates/bibtex-utils/src/field.rs b/support/texlab/crates/bibtex-utils/src/field.rs new file mode 100644 index 0000000000..d9f3adacab --- /dev/null +++ b/support/texlab/crates/bibtex-utils/src/field.rs @@ -0,0 +1,4 @@ +pub mod author; +pub mod date; +pub mod number; +pub mod text; diff --git a/support/texlab/crates/bibtex-utils/src/field/author.rs b/support/texlab/crates/bibtex-utils/src/field/author.rs new file mode 100644 index 0000000000..d8576cbaa4 --- /dev/null +++ b/support/texlab/crates/bibtex-utils/src/field/author.rs @@ -0,0 +1,80 @@ +use std::{borrow::Cow, fmt}; + +use human_name::Name; +use itertools::Itertools; +use syntax::bibtex::Value; + +use super::text::TextFieldData; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] +pub enum AuthorField { + Afterword, + Annotator, + Author, + Commentator, + Editor, + EditorA, + EditorB, + EditorC, + Foreword, + Introduction, + Translator, +} + +impl AuthorField { + pub fn parse(input: &str) -> Option { + Some(match input.to_ascii_lowercase().as_str() { + "afterword" => Self::Afterword, + "annotator" => Self::Annotator, + "author" => Self::Author, + "commentator" => Self::Commentator, + "editor" => Self::Editor, + "editora" => Self::EditorA, + "editorb" => Self::EditorB, + "editorc" => Self::EditorC, + "foreword" => Self::Foreword, + "introduction" => Self::Introduction, + "translator" => Self::Translator, + _ => return None, + }) + } +} + +#[derive(Debug, Clone, Default)] +pub struct AuthorFieldData { + pub authors: Vec, +} + +impl fmt::Display for AuthorFieldData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let names = self.authors.iter().map(Name::display_initial_surname); + + for part in Itertools::intersperse(names, Cow::Borrowed(", ")) { + write!(f, "{}", part)?; + } + + Ok(()) + } +} + +impl AuthorFieldData { + pub fn parse(value: &Value) -> Option { + let TextFieldData { text } = TextFieldData::parse(value)?; + let mut authors = Vec::new(); + let mut words = Vec::new(); + for word in text.split_whitespace() { + if word.eq_ignore_ascii_case("and") { + authors.push(Name::parse(&words.join(" "))?); + words.clear(); + } else { + words.push(word); + } + } + + if !words.is_empty() { + authors.push(Name::parse(&words.join(" "))?); + } + + Some(Self { authors }) + } +} diff --git a/support/texlab/crates/bibtex-utils/src/field/date.rs b/support/texlab/crates/bibtex-utils/src/field/date.rs new file mode 100644 index 0000000000..8e2c0b7d72 --- /dev/null +++ b/support/texlab/crates/bibtex-utils/src/field/date.rs @@ -0,0 +1,85 @@ +use std::{fmt, ops::Add, str::FromStr}; + +use chrono::{Datelike, Month, NaiveDate}; +use syntax::bibtex::Value; + +use super::text::TextFieldData; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] +pub enum DateField { + Date, + EventDate, + Month, + UrlDate, + Year, +} + +impl DateField { + pub fn parse(input: &str) -> Option { + Some(match input.to_ascii_lowercase().as_str() { + "date" => Self::Date, + "eventdate" => Self::EventDate, + "month" => Self::Month, + "urldate" => Self::UrlDate, + "year" => Self::Year, + _ => return None, + }) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub enum DateFieldData { + Date(NaiveDate), + Year(i32), + Month(Month), + Other(String), +} + +impl Add for DateFieldData { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (date @ Self::Year(_), Self::Year(_)) + | (date @ Self::Month(_), Self::Month(_)) + | (date @ Self::Date(_), Self::Date(_)) + | (Self::Other(_), date) + | (date, Self::Other(_)) => date, + (Self::Year(year), Self::Month(month)) | (Self::Month(month), Self::Year(year)) => { + let new_date = NaiveDate::from_ymd_opt(year, month.number_from_month(), 1).unwrap(); + Self::Date(new_date) + } + (Self::Year(year), Self::Date(date)) | (Self::Date(date), Self::Year(year)) => { + let new_date = date.with_year(year).unwrap_or(date); + Self::Date(new_date) + } + (Self::Date(date), Self::Month(month)) | (Self::Month(month), Self::Date(date)) => { + let new_date = date.with_month(month.number_from_month()).unwrap_or(date); + Self::Date(new_date) + } + } + } +} + +impl fmt::Display for DateFieldData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Year(year) => write!(f, "{}", year), + Self::Date(date) => write!(f, "{}", date.format("%b. %Y")), + Self::Month(month) => write!(f, "{}", month.name()), + Self::Other(text) => write!(f, "{}", text), + } + } +} + +impl DateFieldData { + pub fn parse(value: &Value) -> Option { + let TextFieldData { text } = TextFieldData::parse(value)?; + NaiveDate::from_str(&text) + .ok() + .map(Self::Date) + .or_else(|| text.parse().ok().map(Self::Year)) + .or_else(|| text.parse().ok().map(Self::Month)) + .or(Some(Self::Other(text))) + } +} diff --git a/support/texlab/crates/bibtex-utils/src/field/number.rs b/support/texlab/crates/bibtex-utils/src/field/number.rs new file mode 100644 index 0000000000..227f3d5b75 --- /dev/null +++ b/support/texlab/crates/bibtex-utils/src/field/number.rs @@ -0,0 +1,60 @@ +use std::fmt; + +use syntax::bibtex::Value; + +use super::text::TextFieldData; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] +pub enum NumberField { + Edition, + Number, + Pages, + PageTotal, + Part, + Volume, + Volumes, +} + +impl NumberField { + pub fn parse(input: &str) -> Option { + Some(match input.to_ascii_lowercase().as_str() { + "edition" => Self::Edition, + "number" => Self::Number, + "pages" => Self::Pages, + "pagetotal" => Self::PageTotal, + "part" => Self::Part, + "volume" => Self::Volume, + "volumes" => Self::Volumes, + _ => return None, + }) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub enum NumberFieldData { + Scalar(u32), + Range(u32, u32), + Other(String), +} + +impl fmt::Display for NumberFieldData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Scalar(value) => write!(f, "{}", value), + Self::Range(start, end) => write!(f, "{}-{}", start, end), + Self::Other(value) => write!(f, "{}", value.replace("--", "-")), + } + } +} + +impl NumberFieldData { + pub fn parse(value: &Value) -> Option { + let TextFieldData { text } = TextFieldData::parse(value)?; + text.split_once("--") + .or_else(|| text.split_once('-')) + .and_then(|(a, b)| Some((a.parse().ok()?, b.parse().ok()?))) + .map(|(a, b)| Self::Range(a, b)) + .or_else(|| text.parse().ok().map(Self::Scalar)) + .or(Some(Self::Other(text))) + } +} diff --git a/support/texlab/crates/bibtex-utils/src/field/text.rs b/support/texlab/crates/bibtex-utils/src/field/text.rs new file mode 100644 index 0000000000..9689264779 --- /dev/null +++ b/support/texlab/crates/bibtex-utils/src/field/text.rs @@ -0,0 +1,306 @@ +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, +}; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] +pub enum TextField { + Abstract, + Addendum, + BookSubtitle, + BookTitle, + BookTitleAddon, + Chapter, + Doi, + EditorType, + EditorTypeA, + EditorTypeB, + EditorTypeC, + Eid, + Eprint, + EprintClass, + EprintType, + EventTitle, + EventTitleAddon, + Holder, + HowPublished, + Isbn, + Issn, + Issue, + IssueSubtitle, + IssueTitle, + IssueTitleAddon, + Journal, + JournalSubtitle, + JournalTitle, + JournalTitleAddon, + Language, + Location, + MainTitle, + MainSubtitle, + MainTitleAddon, + Note, + OrigLanguage, + Publisher, + Pubstate, + Series, + Subtitle, + Title, + TitleAddon, + Type, + Unknown, + Url, + Venue, + Version, +} + +impl TextField { + pub fn parse(input: &str) -> Option { + Some(match input.to_ascii_lowercase().as_str() { + "abstract" => Self::Abstract, + "addendum" => Self::Addendum, + "booksubtitle" => Self::BookSubtitle, + "booktitle" => Self::BookTitle, + "booktitleaddon" => Self::BookTitleAddon, + "chapter" => Self::Chapter, + "doi" => Self::Doi, + "editortype" => Self::EditorType, + "editortypea" => Self::EditorTypeA, + "editortypeb" => Self::EditorTypeB, + "editortypec" => Self::EditorTypeC, + "eid" => Self::Eid, + "eprint" => Self::Eprint, + "eprintclass" => Self::EprintClass, + "eprinttype" => Self::EprintType, + "eventtitle" => Self::EventTitle, + "eventtitleaddon" => Self::EventTitleAddon, + "holder" => Self::Holder, + "howpublished" => Self::HowPublished, + "isbn" => Self::Isbn, + "issn" => Self::Issn, + "issue" => Self::Issue, + "issuesubtitle" => Self::IssueSubtitle, + "issuetitle" => Self::IssueTitle, + "issuetitleaddon" => Self::IssueTitleAddon, + "journal" => Self::Journal, + "journalsubtitle" => Self::JournalSubtitle, + "journaltitle" => Self::JournalTitle, + "journaltitleaddon" => Self::JournalTitleAddon, + "language" => Self::Language, + "location" => Self::Location, + "maintitle" => Self::MainTitle, + "mainsubtitle" => Self::MainSubtitle, + "maintitleaddon" => Self::MainTitleAddon, + "note" => Self::Note, + "origlanguage" => Self::OrigLanguage, + "publisher" => Self::Publisher, + "pubstate" => Self::Pubstate, + "series" => Self::Series, + "subtitle" => Self::Subtitle, + "title" => Self::Title, + "titleaddon" => Self::TitleAddon, + "type" => Self::Type, + "unknown" => Self::Unknown, + "url" => Self::Url, + "venue" => Self::Venue, + "version" => Self::Version, + _ => return None, + }) + } +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Default)] +pub struct TextFieldData { + pub text: String, +} + +impl TextFieldData { + pub fn parse(value: &Value) -> Option { + let mut builder = TextFieldDataBuilder::default(); + builder.visit_value(value)?; + Some(builder.data) + } +} + +#[derive(Default)] +struct TextFieldDataBuilder { + data: TextFieldData, + string_stack: FxHashSet, +} + +impl TextFieldDataBuilder { + fn visit_value(&mut self, value: &Value) -> Option<()> { + match value { + Value::Literal(lit) => { + self.visit_literal(lit); + } + Value::CurlyGroup(group) => { + self.visit_curly_group(group)?; + } + Value::QuoteGroup(group) => { + self.visit_quote_group(group)?; + } + Value::Join(join) => { + self.visit_join(join)?; + } + Value::Accent(accent) => { + let _ = self.visit_accent(accent); + } + Value::Command(command) => { + let _ = self.visit_command(command); + } + }; + + Some(()) + } + + fn visit_literal(&mut self, lit: &Literal) { + if lit + .name_token() + .and_then(|name| self.visit_string_reference(&name)) + .is_none() + { + lit.syntax() + .text() + .for_each_chunk(|text| self.data.text.push_str(text)); + } + } + + 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); + Some(()) + } + + fn visit_curly_group(&mut self, group: &CurlyGroup) -> Option<()> { + for child in group.syntax().children_with_tokens() { + match child { + NodeOrToken::Node(node) => { + self.visit_value(&Value::cast(node)?)?; + } + NodeOrToken::Token(token) => { + match token.kind() { + L_CURLY | R_CURLY => (), + WHITESPACE | NBSP => self.data.text.push(' '), + _ => self.data.text.push_str(token.text()), + }; + } + }; + } + + Some(()) + } + + fn visit_quote_group(&mut self, group: &QuoteGroup) -> Option<()> { + for child in group.syntax().children_with_tokens() { + match child { + NodeOrToken::Node(node) => { + self.visit_value(&Value::cast(node)?)?; + } + NodeOrToken::Token(token) => { + match token.kind() { + QUOTE => (), + WHITESPACE | NBSP => self.data.text.push(' '), + _ => self.data.text.push_str(token.text()), + }; + } + }; + } + + Some(()) + } + + fn visit_join(&mut self, join: &Join) -> Option<()> { + if let Some(left) = join.left_value() { + self.visit_value(&left)?; + } + + if let Some(right) = join.right_value() { + self.visit_value(&right)?; + } + + Some(()) + } + + fn visit_accent(&mut self, accent: &Accent) -> Option<()> { + let name = accent.accent_name_token()?; + let word = accent.word_token()?; + + let mut chars = word.text().chars(); + let a = chars.next()?; + + if chars.next().is_some() { + self.data.text.push_str(word.text()); + } else { + let b = match name.text() { + r#"\`"# => '\u{0300}', + r#"\'"# => '\u{0301}', + r#"\^"# => '\u{0302}', + r#"\""# => '\u{0308}', + r#"\H"# => '\u{030B}', + r#"\~"# => '\u{0303}', + + r#"\c"# => '\u{0327}', + r#"\k"# => '\u{0328}', + r#"\="# => '\u{0304}', + r#"\b"# => '\u{0331}', + r#"\."# => '\u{0307}', + r#"\d"# => '\u{0323}', + r#"\r"# => '\u{030A}', + r#"\u"# => '\u{0306}', + r#"\v"# => '\u{030C}', + _ => '\u{0000}', + }; + + match unicode_normalization::char::compose(a, b) { + Some(c) => self.data.text.push(c), + None => self.data.text.push_str(word.text()), + }; + } + + Some(()) + } + + fn visit_command(&mut self, command: &Command) -> Option<()> { + let name = command.command_name_token()?; + let replacement = match name.text() { + r#"\l"# => "\u{0142}", + r#"\o"# => "\u{00F8}", + r#"\i"# => "\u{0131}", + r#"\&"# => "&", + r#"\$"# => "$", + r#"\{"# => "{", + r#"\}"# => "}", + r#"\%"# => "%", + r#"\#"# => "#", + r#"\_"# => "_", + r#"\ "# | r#"\,"# | r#"\;"# => " ", + r#"\hyphen"# => "-", + r#"\TeX"# => "TeX", + r#"\LaTeX"# => "LaTeX", + text => text, + }; + + self.data.text.push_str(replacement); + Some(()) + } +} diff --git a/support/texlab/crates/bibtex-utils/src/lib.rs b/support/texlab/crates/bibtex-utils/src/lib.rs new file mode 100644 index 0000000000..5b8f2df9f4 --- /dev/null +++ b/support/texlab/crates/bibtex-utils/src/lib.rs @@ -0,0 +1 @@ +pub mod field; diff --git a/support/texlab/crates/citeproc/Cargo.toml b/support/texlab/crates/citeproc/Cargo.toml index ff1af00c97..c298c6f7d1 100644 --- a/support/texlab/crates/citeproc/Cargo.toml +++ b/support/texlab/crates/citeproc/Cargo.toml @@ -7,8 +7,7 @@ edition.workspace = true rust-version.workspace = true [dependencies] -chrono = { version = "0.4.26", default-features = false, features = ["std"] } -human_name = "2.0.2" +bibtex-utils = { path = "../bibtex-utils" } isocountry = "0.3.2" itertools = "0.11.0" rowan = "0.15.11" diff --git a/support/texlab/crates/citeproc/src/driver.rs b/support/texlab/crates/citeproc/src/driver.rs index af09b9ed32..778e2539b5 100644 --- a/support/texlab/crates/citeproc/src/driver.rs +++ b/support/texlab/crates/citeproc/src/driver.rs @@ -1,3 +1,9 @@ +use bibtex_utils::field::{ + author::AuthorField, + date::DateField, + number::{NumberField, NumberFieldData}, + text::TextField, +}; use isocountry::CountryCode; use itertools::Itertools; use syntax::bibtex; @@ -6,12 +12,6 @@ use url::Url; use super::{ entry::{EntryData, EntryKind}, - field::{ - author::AuthorField, - date::DateField, - number::{NumberField, NumberFieldData}, - text::TextField, - }, output::{Inline, InlineBuilder, Punct}, }; diff --git a/support/texlab/crates/citeproc/src/entry.rs b/support/texlab/crates/citeproc/src/entry.rs index e29864e2c2..243908da95 100644 --- a/support/texlab/crates/citeproc/src/entry.rs +++ b/support/texlab/crates/citeproc/src/entry.rs @@ -1,12 +1,11 @@ -use rustc_hash::FxHashMap; -use syntax::bibtex::{Entry, Field, HasName, HasType, HasValue, Value}; - -use super::field::{ +use bibtex_utils::field::{ author::{AuthorField, AuthorFieldData}, date::{DateField, DateFieldData}, number::{NumberField, NumberFieldData}, text::{TextField, TextFieldData}, }; +use rustc_hash::FxHashMap; +use syntax::bibtex::{Entry, Field, HasName, HasType, HasValue, Value}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] pub enum EntryKind { diff --git a/support/texlab/crates/citeproc/src/field.rs b/support/texlab/crates/citeproc/src/field.rs deleted file mode 100644 index d9f3adacab..0000000000 --- a/support/texlab/crates/citeproc/src/field.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod author; -pub mod date; -pub mod number; -pub mod text; diff --git a/support/texlab/crates/citeproc/src/field/author.rs b/support/texlab/crates/citeproc/src/field/author.rs deleted file mode 100644 index d8576cbaa4..0000000000 --- a/support/texlab/crates/citeproc/src/field/author.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::{borrow::Cow, fmt}; - -use human_name::Name; -use itertools::Itertools; -use syntax::bibtex::Value; - -use super::text::TextFieldData; - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] -pub enum AuthorField { - Afterword, - Annotator, - Author, - Commentator, - Editor, - EditorA, - EditorB, - EditorC, - Foreword, - Introduction, - Translator, -} - -impl AuthorField { - pub fn parse(input: &str) -> Option { - Some(match input.to_ascii_lowercase().as_str() { - "afterword" => Self::Afterword, - "annotator" => Self::Annotator, - "author" => Self::Author, - "commentator" => Self::Commentator, - "editor" => Self::Editor, - "editora" => Self::EditorA, - "editorb" => Self::EditorB, - "editorc" => Self::EditorC, - "foreword" => Self::Foreword, - "introduction" => Self::Introduction, - "translator" => Self::Translator, - _ => return None, - }) - } -} - -#[derive(Debug, Clone, Default)] -pub struct AuthorFieldData { - pub authors: Vec, -} - -impl fmt::Display for AuthorFieldData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let names = self.authors.iter().map(Name::display_initial_surname); - - for part in Itertools::intersperse(names, Cow::Borrowed(", ")) { - write!(f, "{}", part)?; - } - - Ok(()) - } -} - -impl AuthorFieldData { - pub fn parse(value: &Value) -> Option { - let TextFieldData { text } = TextFieldData::parse(value)?; - let mut authors = Vec::new(); - let mut words = Vec::new(); - for word in text.split_whitespace() { - if word.eq_ignore_ascii_case("and") { - authors.push(Name::parse(&words.join(" "))?); - words.clear(); - } else { - words.push(word); - } - } - - if !words.is_empty() { - authors.push(Name::parse(&words.join(" "))?); - } - - Some(Self { authors }) - } -} diff --git a/support/texlab/crates/citeproc/src/field/date.rs b/support/texlab/crates/citeproc/src/field/date.rs deleted file mode 100644 index 8e2c0b7d72..0000000000 --- a/support/texlab/crates/citeproc/src/field/date.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::{fmt, ops::Add, str::FromStr}; - -use chrono::{Datelike, Month, NaiveDate}; -use syntax::bibtex::Value; - -use super::text::TextFieldData; - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] -pub enum DateField { - Date, - EventDate, - Month, - UrlDate, - Year, -} - -impl DateField { - pub fn parse(input: &str) -> Option { - Some(match input.to_ascii_lowercase().as_str() { - "date" => Self::Date, - "eventdate" => Self::EventDate, - "month" => Self::Month, - "urldate" => Self::UrlDate, - "year" => Self::Year, - _ => return None, - }) - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub enum DateFieldData { - Date(NaiveDate), - Year(i32), - Month(Month), - Other(String), -} - -impl Add for DateFieldData { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - match (self, rhs) { - (date @ Self::Year(_), Self::Year(_)) - | (date @ Self::Month(_), Self::Month(_)) - | (date @ Self::Date(_), Self::Date(_)) - | (Self::Other(_), date) - | (date, Self::Other(_)) => date, - (Self::Year(year), Self::Month(month)) | (Self::Month(month), Self::Year(year)) => { - let new_date = NaiveDate::from_ymd_opt(year, month.number_from_month(), 1).unwrap(); - Self::Date(new_date) - } - (Self::Year(year), Self::Date(date)) | (Self::Date(date), Self::Year(year)) => { - let new_date = date.with_year(year).unwrap_or(date); - Self::Date(new_date) - } - (Self::Date(date), Self::Month(month)) | (Self::Month(month), Self::Date(date)) => { - let new_date = date.with_month(month.number_from_month()).unwrap_or(date); - Self::Date(new_date) - } - } - } -} - -impl fmt::Display for DateFieldData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Year(year) => write!(f, "{}", year), - Self::Date(date) => write!(f, "{}", date.format("%b. %Y")), - Self::Month(month) => write!(f, "{}", month.name()), - Self::Other(text) => write!(f, "{}", text), - } - } -} - -impl DateFieldData { - pub fn parse(value: &Value) -> Option { - let TextFieldData { text } = TextFieldData::parse(value)?; - NaiveDate::from_str(&text) - .ok() - .map(Self::Date) - .or_else(|| text.parse().ok().map(Self::Year)) - .or_else(|| text.parse().ok().map(Self::Month)) - .or(Some(Self::Other(text))) - } -} diff --git a/support/texlab/crates/citeproc/src/field/number.rs b/support/texlab/crates/citeproc/src/field/number.rs deleted file mode 100644 index 227f3d5b75..0000000000 --- a/support/texlab/crates/citeproc/src/field/number.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::fmt; - -use syntax::bibtex::Value; - -use super::text::TextFieldData; - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] -pub enum NumberField { - Edition, - Number, - Pages, - PageTotal, - Part, - Volume, - Volumes, -} - -impl NumberField { - pub fn parse(input: &str) -> Option { - Some(match input.to_ascii_lowercase().as_str() { - "edition" => Self::Edition, - "number" => Self::Number, - "pages" => Self::Pages, - "pagetotal" => Self::PageTotal, - "part" => Self::Part, - "volume" => Self::Volume, - "volumes" => Self::Volumes, - _ => return None, - }) - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub enum NumberFieldData { - Scalar(u32), - Range(u32, u32), - Other(String), -} - -impl fmt::Display for NumberFieldData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Scalar(value) => write!(f, "{}", value), - Self::Range(start, end) => write!(f, "{}-{}", start, end), - Self::Other(value) => write!(f, "{}", value.replace("--", "-")), - } - } -} - -impl NumberFieldData { - pub fn parse(value: &Value) -> Option { - let TextFieldData { text } = TextFieldData::parse(value)?; - text.split_once("--") - .or_else(|| text.split_once('-')) - .and_then(|(a, b)| Some((a.parse().ok()?, b.parse().ok()?))) - .map(|(a, b)| Self::Range(a, b)) - .or_else(|| text.parse().ok().map(Self::Scalar)) - .or(Some(Self::Other(text))) - } -} diff --git a/support/texlab/crates/citeproc/src/field/text.rs b/support/texlab/crates/citeproc/src/field/text.rs deleted file mode 100644 index 9689264779..0000000000 --- a/support/texlab/crates/citeproc/src/field/text.rs +++ /dev/null @@ -1,306 +0,0 @@ -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, -}; - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] -pub enum TextField { - Abstract, - Addendum, - BookSubtitle, - BookTitle, - BookTitleAddon, - Chapter, - Doi, - EditorType, - EditorTypeA, - EditorTypeB, - EditorTypeC, - Eid, - Eprint, - EprintClass, - EprintType, - EventTitle, - EventTitleAddon, - Holder, - HowPublished, - Isbn, - Issn, - Issue, - IssueSubtitle, - IssueTitle, - IssueTitleAddon, - Journal, - JournalSubtitle, - JournalTitle, - JournalTitleAddon, - Language, - Location, - MainTitle, - MainSubtitle, - MainTitleAddon, - Note, - OrigLanguage, - Publisher, - Pubstate, - Series, - Subtitle, - Title, - TitleAddon, - Type, - Unknown, - Url, - Venue, - Version, -} - -impl TextField { - pub fn parse(input: &str) -> Option { - Some(match input.to_ascii_lowercase().as_str() { - "abstract" => Self::Abstract, - "addendum" => Self::Addendum, - "booksubtitle" => Self::BookSubtitle, - "booktitle" => Self::BookTitle, - "booktitleaddon" => Self::BookTitleAddon, - "chapter" => Self::Chapter, - "doi" => Self::Doi, - "editortype" => Self::EditorType, - "editortypea" => Self::EditorTypeA, - "editortypeb" => Self::EditorTypeB, - "editortypec" => Self::EditorTypeC, - "eid" => Self::Eid, - "eprint" => Self::Eprint, - "eprintclass" => Self::EprintClass, - "eprinttype" => Self::EprintType, - "eventtitle" => Self::EventTitle, - "eventtitleaddon" => Self::EventTitleAddon, - "holder" => Self::Holder, - "howpublished" => Self::HowPublished, - "isbn" => Self::Isbn, - "issn" => Self::Issn, - "issue" => Self::Issue, - "issuesubtitle" => Self::IssueSubtitle, - "issuetitle" => Self::IssueTitle, - "issuetitleaddon" => Self::IssueTitleAddon, - "journal" => Self::Journal, - "journalsubtitle" => Self::JournalSubtitle, - "journaltitle" => Self::JournalTitle, - "journaltitleaddon" => Self::JournalTitleAddon, - "language" => Self::Language, - "location" => Self::Location, - "maintitle" => Self::MainTitle, - "mainsubtitle" => Self::MainSubtitle, - "maintitleaddon" => Self::MainTitleAddon, - "note" => Self::Note, - "origlanguage" => Self::OrigLanguage, - "publisher" => Self::Publisher, - "pubstate" => Self::Pubstate, - "series" => Self::Series, - "subtitle" => Self::Subtitle, - "title" => Self::Title, - "titleaddon" => Self::TitleAddon, - "type" => Self::Type, - "unknown" => Self::Unknown, - "url" => Self::Url, - "venue" => Self::Venue, - "version" => Self::Version, - _ => return None, - }) - } -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Default)] -pub struct TextFieldData { - pub text: String, -} - -impl TextFieldData { - pub fn parse(value: &Value) -> Option { - let mut builder = TextFieldDataBuilder::default(); - builder.visit_value(value)?; - Some(builder.data) - } -} - -#[derive(Default)] -struct TextFieldDataBuilder { - data: TextFieldData, - string_stack: FxHashSet, -} - -impl TextFieldDataBuilder { - fn visit_value(&mut self, value: &Value) -> Option<()> { - match value { - Value::Literal(lit) => { - self.visit_literal(lit); - } - Value::CurlyGroup(group) => { - self.visit_curly_group(group)?; - } - Value::QuoteGroup(group) => { - self.visit_quote_group(group)?; - } - Value::Join(join) => { - self.visit_join(join)?; - } - Value::Accent(accent) => { - let _ = self.visit_accent(accent); - } - Value::Command(command) => { - let _ = self.visit_command(command); - } - }; - - Some(()) - } - - fn visit_literal(&mut self, lit: &Literal) { - if lit - .name_token() - .and_then(|name| self.visit_string_reference(&name)) - .is_none() - { - lit.syntax() - .text() - .for_each_chunk(|text| self.data.text.push_str(text)); - } - } - - 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); - Some(()) - } - - fn visit_curly_group(&mut self, group: &CurlyGroup) -> Option<()> { - for child in group.syntax().children_with_tokens() { - match child { - NodeOrToken::Node(node) => { - self.visit_value(&Value::cast(node)?)?; - } - NodeOrToken::Token(token) => { - match token.kind() { - L_CURLY | R_CURLY => (), - WHITESPACE | NBSP => self.data.text.push(' '), - _ => self.data.text.push_str(token.text()), - }; - } - }; - } - - Some(()) - } - - fn visit_quote_group(&mut self, group: &QuoteGroup) -> Option<()> { - for child in group.syntax().children_with_tokens() { - match child { - NodeOrToken::Node(node) => { - self.visit_value(&Value::cast(node)?)?; - } - NodeOrToken::Token(token) => { - match token.kind() { - QUOTE => (), - WHITESPACE | NBSP => self.data.text.push(' '), - _ => self.data.text.push_str(token.text()), - }; - } - }; - } - - Some(()) - } - - fn visit_join(&mut self, join: &Join) -> Option<()> { - if let Some(left) = join.left_value() { - self.visit_value(&left)?; - } - - if let Some(right) = join.right_value() { - self.visit_value(&right)?; - } - - Some(()) - } - - fn visit_accent(&mut self, accent: &Accent) -> Option<()> { - let name = accent.accent_name_token()?; - let word = accent.word_token()?; - - let mut chars = word.text().chars(); - let a = chars.next()?; - - if chars.next().is_some() { - self.data.text.push_str(word.text()); - } else { - let b = match name.text() { - r#"\`"# => '\u{0300}', - r#"\'"# => '\u{0301}', - r#"\^"# => '\u{0302}', - r#"\""# => '\u{0308}', - r#"\H"# => '\u{030B}', - r#"\~"# => '\u{0303}', - - r#"\c"# => '\u{0327}', - r#"\k"# => '\u{0328}', - r#"\="# => '\u{0304}', - r#"\b"# => '\u{0331}', - r#"\."# => '\u{0307}', - r#"\d"# => '\u{0323}', - r#"\r"# => '\u{030A}', - r#"\u"# => '\u{0306}', - r#"\v"# => '\u{030C}', - _ => '\u{0000}', - }; - - match unicode_normalization::char::compose(a, b) { - Some(c) => self.data.text.push(c), - None => self.data.text.push_str(word.text()), - }; - } - - Some(()) - } - - fn visit_command(&mut self, command: &Command) -> Option<()> { - let name = command.command_name_token()?; - let replacement = match name.text() { - r#"\l"# => "\u{0142}", - r#"\o"# => "\u{00F8}", - r#"\i"# => "\u{0131}", - r#"\&"# => "&", - r#"\$"# => "$", - r#"\{"# => "{", - r#"\}"# => "}", - r#"\%"# => "%", - r#"\#"# => "#", - r#"\_"# => "_", - r#"\ "# | r#"\,"# | r#"\;"# => " ", - r#"\hyphen"# => "-", - r#"\TeX"# => "TeX", - r#"\LaTeX"# => "LaTeX", - text => text, - }; - - self.data.text.push_str(replacement); - Some(()) - } -} diff --git a/support/texlab/crates/citeproc/src/lib.rs b/support/texlab/crates/citeproc/src/lib.rs index 1e0ac6b070..de24306583 100644 --- a/support/texlab/crates/citeproc/src/lib.rs +++ b/support/texlab/crates/citeproc/src/lib.rs @@ -1,6 +1,5 @@ mod driver; mod entry; -pub mod field; mod output; use syntax::bibtex; diff --git a/support/texlab/crates/completion-data/src/lib.rs b/support/texlab/crates/completion-data/src/lib.rs index c63564a58f..9bfdf71e48 100644 --- a/support/texlab/crates/completion-data/src/lib.rs +++ b/support/texlab/crates/completion-data/src/lib.rs @@ -107,7 +107,7 @@ const JSON_GZ: &[u8] = include_bytes!("../data/completion.json.gz"); pub static DATABASE: Lazy> = Lazy::new(|| { let mut decoder = GzDecoder::new(JSON_GZ); - let json = Box::leak(Box::new(String::new())); + let json = Box::leak(Box::default()); decoder.read_to_string(json).unwrap(); let mut db: Database = serde_json::from_str(json).unwrap(); db.lookup_packages = db diff --git a/support/texlab/crates/definition/src/citation.rs b/support/texlab/crates/definition/src/citation.rs index 1497711cf6..51550a585d 100644 --- a/support/texlab/crates/definition/src/citation.rs +++ b/support/texlab/crates/definition/src/citation.rs @@ -7,7 +7,7 @@ use crate::DefinitionContext; use super::DefinitionResult; -pub(super) fn goto_definition<'db>(context: &mut DefinitionContext<'db>) -> Option<()> { +pub(super) fn goto_definition(context: &mut DefinitionContext) -> Option<()> { let data = context.params.document.data.as_tex()?; let citation = queries::object_at_cursor( diff --git a/support/texlab/crates/definition/src/command.rs b/support/texlab/crates/definition/src/command.rs index 0a367beb14..661f953212 100644 --- a/support/texlab/crates/definition/src/command.rs +++ b/support/texlab/crates/definition/src/command.rs @@ -6,7 +6,7 @@ use crate::DefinitionContext; use super::DefinitionResult; -pub(super) fn goto_definition<'db>(context: &mut DefinitionContext<'db>) -> Option<()> { +pub(super) fn goto_definition(context: &mut DefinitionContext) -> Option<()> { let data = context.params.document.data.as_tex()?; let root = data.root_node(); let name = root diff --git a/support/texlab/crates/definition/src/include.rs b/support/texlab/crates/definition/src/include.rs index 2c24f05321..df2c5c763c 100644 --- a/support/texlab/crates/definition/src/include.rs +++ b/support/texlab/crates/definition/src/include.rs @@ -4,7 +4,7 @@ use crate::DefinitionContext; use super::DefinitionResult; -pub(super) fn goto_definition<'db>(context: &mut DefinitionContext<'db>) -> Option<()> { +pub(super) fn goto_definition(context: &mut DefinitionContext) -> Option<()> { let start = context.params.document; let parents = context.params.workspace.parents(start); let results = parents diff --git a/support/texlab/crates/definition/src/label.rs b/support/texlab/crates/definition/src/label.rs index 127f6ff497..3c8c922059 100644 --- a/support/texlab/crates/definition/src/label.rs +++ b/support/texlab/crates/definition/src/label.rs @@ -10,7 +10,7 @@ use crate::DefinitionContext; use super::DefinitionResult; -pub(super) fn goto_definition<'db>(context: &mut DefinitionContext<'db>) -> Option<()> { +pub(super) fn goto_definition(context: &mut DefinitionContext) -> Option<()> { let data = context.params.document.data.as_tex()?; let reference = queries::object_at_cursor( &data.semantics.labels, diff --git a/support/texlab/crates/definition/src/lib.rs b/support/texlab/crates/definition/src/lib.rs index b96c48ed3f..8678bb0016 100644 --- a/support/texlab/crates/definition/src/lib.rs +++ b/support/texlab/crates/definition/src/lib.rs @@ -29,7 +29,7 @@ struct DefinitionContext<'db> { results: Vec>, } -pub fn goto_definition<'db>(params: DefinitionParams<'db>) -> Vec> { +pub fn goto_definition(params: DefinitionParams) -> Vec { let project = params.workspace.project(params.document); let mut context = DefinitionContext { params, diff --git a/support/texlab/crates/definition/src/string_ref.rs b/support/texlab/crates/definition/src/string_ref.rs index 124cc33c7c..883b126cc0 100644 --- a/support/texlab/crates/definition/src/string_ref.rs +++ b/support/texlab/crates/definition/src/string_ref.rs @@ -5,7 +5,7 @@ use crate::DefinitionContext; use super::DefinitionResult; -pub(super) fn goto_definition<'db>(context: &mut DefinitionContext<'db>) -> Option<()> { +pub(super) fn goto_definition(context: &mut DefinitionContext) -> Option<()> { let data = context.params.document.data.as_bib()?; let root = data.root_node(); let name = root diff --git a/support/texlab/crates/diagnostics/src/build_log.rs b/support/texlab/crates/diagnostics/src/build_log.rs index f5770c90fb..525bd982ff 100644 --- a/support/texlab/crates/diagnostics/src/build_log.rs +++ b/support/texlab/crates/diagnostics/src/build_log.rs @@ -25,19 +25,24 @@ impl DiagnosticSource for BuildErrors { fn update(&mut self, workspace: &Workspace, log_document: &Document) { let mut errors: FxHashMap> = FxHashMap::default(); - let Some(data) = log_document.data.as_log() else { return }; + let Some(data) = log_document.data.as_log() else { + return; + }; let parents = workspace.parents(log_document); - let Some(root_document) = parents.iter().next() else { return }; + let Some(root_document) = parents.iter().next() else { + return; + }; - let Some(base_path) = root_document - .path - .as_deref() - .and_then(|path| path.parent()) else { return }; + 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 }; + 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 range = find_range_of_hint(tex_document, error).unwrap_or_else(|| { @@ -74,9 +79,12 @@ impl DiagnosticSource for BuildErrors { self.logs.retain(|uri, _| workspace.lookup(uri).is_some()); for document in workspace.iter() { - let Some(log) = self.logs.get(&document.uri) else { continue }; + 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)); + builder.push_many(uri, errors.iter().map(Cow::Borrowed)); } } } diff --git a/support/texlab/crates/diagnostics/src/citations.rs b/support/texlab/crates/diagnostics/src/citations.rs index e6b4d1022a..1ab7b6db30 100644 --- a/support/texlab/crates/diagnostics/src/citations.rs +++ b/support/texlab/crates/diagnostics/src/citations.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use base_db::{ semantics::{bib::Entry, tex::Citation}, util::queries::{self, Object}, - BibDocumentData, Document, DocumentData, Project, TexDocumentData, Workspace, + Document, Project, Workspace, }; use rustc_hash::FxHashSet; @@ -12,6 +12,8 @@ use crate::{ DiagnosticBuilder, DiagnosticSource, }; +const MAX_UNUSED_ENTRIES: usize = 1000; + #[derive(Default)] pub struct CitationErrors; @@ -23,12 +25,8 @@ impl DiagnosticSource for CitationErrors { ) { for document in workspace.iter() { let project = workspace.project(document); - - if let DocumentData::Tex(data) = &document.data { - detect_undefined_citations(&project, document, data, builder); - } else if let DocumentData::Bib(data) = &document.data { - detect_unused_entries(&project, document, data, builder); - } + detect_undefined_citations(&project, document, builder); + detect_unused_entries(&project, document, builder); } detect_duplicate_entries(workspace, builder); @@ -38,9 +36,12 @@ impl DiagnosticSource for CitationErrors { fn detect_undefined_citations<'db>( project: &Project<'db>, document: &'db Document, - data: &TexDocumentData, builder: &mut DiagnosticBuilder<'db>, ) { + let Some(data) = document.data.as_tex() else { + return; + }; + let entries: FxHashSet<&str> = Entry::find_all(project) .map(|(_, entry)| entry.name_text()) .collect(); @@ -60,9 +61,17 @@ fn detect_undefined_citations<'db>( fn detect_unused_entries<'db>( project: &Project<'db>, document: &'db Document, - data: &BibDocumentData, builder: &mut DiagnosticBuilder<'db>, ) { + let Some(data) = document.data.as_bib() else { + return; + }; + + // If this is a huge bibliography, then don't bother checking for unused entries. + if data.semantics.entries.len() > MAX_UNUSED_ENTRIES { + return; + } + let citations: FxHashSet<&str> = Citation::find_all(project) .map(|(_, citation)| citation.name_text()) .collect(); diff --git a/support/texlab/crates/diagnostics/src/lib.rs b/support/texlab/crates/diagnostics/src/lib.rs index e55ecb78e6..d0be31a0ba 100644 --- a/support/texlab/crates/diagnostics/src/lib.rs +++ b/support/texlab/crates/diagnostics/src/lib.rs @@ -23,7 +23,7 @@ pub struct DiagnosticBuilder<'db> { impl<'db> DiagnosticBuilder<'db> { pub fn push(&mut self, uri: &'db Url, diagnostic: Cow<'db, Diagnostic>) { - self.inner.entry(&uri).or_default().push(diagnostic); + self.inner.entry(uri).or_default().push(diagnostic); } pub fn push_many( @@ -31,7 +31,7 @@ impl<'db> DiagnosticBuilder<'db> { uri: &'db Url, diagnostics: impl Iterator>, ) { - self.inner.entry(&uri).or_default().extend(diagnostics); + self.inner.entry(uri).or_default().extend(diagnostics); } pub fn iter(&self) -> impl Iterator)> { @@ -54,12 +54,14 @@ pub struct DiagnosticManager { impl Default for DiagnosticManager { fn default() -> Self { - let mut sources: Vec> = Vec::new(); - sources.push(Box::new(TexSyntaxErrors::default())); - sources.push(Box::new(BibSyntaxErrors::default())); - sources.push(Box::new(BuildErrors::default())); - sources.push(Box::new(LabelErrors::default())); - sources.push(Box::new(CitationErrors::default())); + let sources: Vec> = vec![ + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + ]; + Self { sources } } } diff --git a/support/texlab/crates/hover/Cargo.toml b/support/texlab/crates/hover/Cargo.toml index f92939dd24..0db19b8189 100644 --- a/support/texlab/crates/hover/Cargo.toml +++ b/support/texlab/crates/hover/Cargo.toml @@ -8,6 +8,7 @@ rust-version.workspace = true [dependencies] base-db = { path = "../base-db" } +bibtex-utils = { path = "../bibtex-utils" } citeproc = { path = "../citeproc" } completion-data = { path = "../completion-data" } rowan = "0.15.11" diff --git a/support/texlab/crates/hover/src/citation.rs b/support/texlab/crates/hover/src/citation.rs index d36370e709..49e5359bba 100644 --- a/support/texlab/crates/hover/src/citation.rs +++ b/support/texlab/crates/hover/src/citation.rs @@ -30,7 +30,7 @@ pub(super) fn find_hover<'db>(params: &HoverParams<'db>) -> Option> { let text = params.project.documents.iter().find_map(|document| { let data = document.data.as_bib()?; let root = bibtex::Root::cast(data.root_node())?; - let entry = root.find_entry(&name)?; + let entry = root.find_entry(name)?; citeproc::render(&entry) })?; diff --git a/support/texlab/crates/hover/src/label.rs b/support/texlab/crates/hover/src/label.rs index c5f72a287e..fc9694fc6c 100644 --- a/support/texlab/crates/hover/src/label.rs +++ b/support/texlab/crates/hover/src/label.rs @@ -20,7 +20,7 @@ pub(super) fn find_hover<'db>(params: &'db HoverParams<'db>) -> Option { } pub fn find<'db>(params: &'db HoverParams<'db>) -> Option> { - citation::find_hover(¶ms) - .or_else(|| package::find_hover(¶ms)) - .or_else(|| entry_type::find_hover(¶ms)) - .or_else(|| field_type::find_hover(¶ms)) - .or_else(|| label::find_hover(¶ms)) - .or_else(|| string_ref::find_hover(¶ms)) + citation::find_hover(params) + .or_else(|| package::find_hover(params)) + .or_else(|| entry_type::find_hover(params)) + .or_else(|| field_type::find_hover(params)) + .or_else(|| label::find_hover(params)) + .or_else(|| string_ref::find_hover(params)) } #[cfg(test)] diff --git a/support/texlab/crates/hover/src/string_ref.rs b/support/texlab/crates/hover/src/string_ref.rs index 64449909d7..7ea84113e0 100644 --- a/support/texlab/crates/hover/src/string_ref.rs +++ b/support/texlab/crates/hover/src/string_ref.rs @@ -1,4 +1,4 @@ -use citeproc::field::text::TextFieldData; +use bibtex_utils::field::text::TextFieldData; use rowan::ast::AstNode; use syntax::bibtex::{self, HasName, HasValue}; diff --git a/support/texlab/crates/references/src/entry.rs b/support/texlab/crates/references/src/entry.rs index 3e6b7d7a6e..617ff86304 100644 --- a/support/texlab/crates/references/src/entry.rs +++ b/support/texlab/crates/references/src/entry.rs @@ -6,7 +6,7 @@ use base_db::{ use crate::{Reference, ReferenceContext, ReferenceKind}; -pub(super) fn find_all<'db>(context: &mut ReferenceContext<'db>) -> Option<()> { +pub(super) fn find_all(context: &mut ReferenceContext) -> Option<()> { let offset = context.params.offset; let name = match &context.params.document.data { diff --git a/support/texlab/crates/references/src/label.rs b/support/texlab/crates/references/src/label.rs index 8e7c17d6a7..5ed8096c5c 100644 --- a/support/texlab/crates/references/src/label.rs +++ b/support/texlab/crates/references/src/label.rs @@ -5,7 +5,7 @@ use base_db::{ use crate::{Reference, ReferenceContext, ReferenceKind}; -pub(super) fn find_all<'db>(context: &mut ReferenceContext<'db>) -> Option<()> { +pub(super) fn find_all(context: &mut ReferenceContext) -> Option<()> { let data = context.params.document.data.as_tex()?; let mode = queries::SearchMode::Full; let name = queries::object_at_cursor(&data.semantics.labels, context.params.offset, mode)? diff --git a/support/texlab/crates/references/src/string_def.rs b/support/texlab/crates/references/src/string_def.rs index 8f64a71e36..9657686079 100644 --- a/support/texlab/crates/references/src/string_def.rs +++ b/support/texlab/crates/references/src/string_def.rs @@ -3,7 +3,7 @@ use syntax::bibtex; use crate::{Reference, ReferenceContext, ReferenceKind}; -pub(super) fn find_all<'db>(context: &mut ReferenceContext<'db>) -> Option<()> { +pub(super) fn find_all(context: &mut ReferenceContext) -> Option<()> { let document = context.params.document; let data = document.data.as_bib()?; let root = data.root_node(); diff --git a/support/texlab/crates/syntax/src/bibtex.rs b/support/texlab/crates/syntax/src/bibtex.rs index b568fbf8e8..97ff6f6150 100644 --- a/support/texlab/crates/syntax/src/bibtex.rs +++ b/support/texlab/crates/syntax/src/bibtex.rs @@ -1,57 +1,19 @@ -use rowan::{ast::AstNode, NodeOrToken}; +mod cst; +mod kind; -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] -#[allow(non_camel_case_types)] -#[repr(u16)] -pub enum SyntaxKind { - WHITESPACE, - JUNK, - L_DELIM, - R_DELIM, - L_CURLY, - R_CURLY, - COMMA, - POUND, - QUOTE, - EQ, - TYPE, - WORD, - NAME, - INTEGER, - NBSP, - ACCENT_NAME, - COMMAND_NAME, - - PREAMBLE, - STRING, - ENTRY, - FIELD, - VALUE, - LITERAL, - JOIN, - ACCENT, - COMMAND, - CURLY_GROUP, - QUOTE_GROUP, - ROOT, -} - -pub use SyntaxKind::*; - -impl From for rowan::SyntaxKind { - fn from(kind: SyntaxKind) -> Self { - Self(kind as u16) - } -} +pub use self::{ + cst::*, + kind::SyntaxKind::{self, *}, +}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] -pub enum Lang {} +pub enum BibtexLanguage {} -impl rowan::Language for Lang { +impl rowan::Language for BibtexLanguage { type Kind = SyntaxKind; fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind { - assert!(raw.0 <= SyntaxKind::ROOT as u16); + assert!(raw.0 <= ROOT as u16); unsafe { std::mem::transmute::(raw.0) } } @@ -60,245 +22,8 @@ impl rowan::Language for Lang { } } -pub type SyntaxNode = rowan::SyntaxNode; - -pub type SyntaxToken = rowan::SyntaxToken; - -pub type SyntaxElement = rowan::SyntaxElement; - -macro_rules! ast_node { - (name: $name:ident, kinds: [$($kind:pat),+], traits: [$($trait: ident),*]) => { - #[derive(Clone)] - pub struct $name { - node: SyntaxNode, - } - - impl AstNode for $name { - type Language = Lang; - - fn can_cast(kind: SyntaxKind) -> bool { - match kind { - $($kind => true,)+ - _ => false, - } - } - - fn cast(node: SyntaxNode) -> Option - where - Self: Sized, - { - match node.kind() { - $($kind => Some(Self { node}),)+ - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode { - &self.node - } - } - - $( - impl $trait for $name { } - )* - }; -} - -macro_rules! ast_node_enum { - (name: $name:ident, variants: [$($variant:ident),+]) => { - #[derive(Clone)] - pub enum $name { - $($variant($variant),)* - } - - impl AstNode for $name { - type Language = Lang; - - fn can_cast(kind: SyntaxKind) -> bool { - false $(|| $variant::can_cast(kind))+ - } - - fn cast(node: SyntaxNode) -> Option - where - Self: Sized, - { - None $(.or_else(|| $variant::cast(node.clone()).map(Self::$variant)))* - } - - fn syntax(&self) -> &SyntaxNode { - match self { - $(Self::$variant(node) => node.syntax(),)* - } - } - } - - $( - impl From<$variant> for $name { - fn from(node: $variant) -> Self { - Self::$variant(node) - } - } - )* - }; -} - -pub trait HasType: AstNode { - fn type_token(&self) -> Option { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == TYPE) - } -} - -pub trait HasDelims: AstNode { - fn left_delim_token(&self) -> Option { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == L_DELIM) - } - - fn right_delim_token(&self) -> Option { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == R_DELIM) - } -} - -pub trait HasName: AstNode { - fn name_token(&self) -> Option { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == NAME) - } -} - -pub trait HasEq: AstNode { - fn eq_token(&self) -> Option { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == EQ) - } -} - -pub trait HasComma: AstNode { - fn comma_token(&self) -> Option { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == COMMA) - } -} - -pub trait HasPound: AstNode { - fn pound_token(&self) -> Option { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == POUND) - } -} - -pub trait HasInteger: AstNode { - fn integer_token(&self) -> Option { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == INTEGER) - } -} - -pub trait HasCommandName: AstNode { - fn command_name_token(&self) -> Option { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == COMMAND_NAME) - } -} - -pub trait HasAccentName: AstNode { - fn accent_name_token(&self) -> Option { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == ACCENT_NAME) - } -} - -pub trait HasWord: AstNode { - fn word_token(&self) -> Option { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == WORD) - } -} - -pub trait HasValue: AstNode { - fn value(&self) -> Option { - self.syntax().children().find_map(Value::cast) - } -} - -ast_node!(name: Root, kinds: [ROOT], traits: []); - -impl Root { - pub fn strings(&self) -> impl Iterator { - self.syntax().children().filter_map(StringDef::cast) - } - - pub fn entries(&self) -> impl Iterator { - self.syntax().children().filter_map(Entry::cast) - } - - pub fn find_entry(&self, name: &str) -> Option { - self.entries().find(|entry| { - entry - .name_token() - .map_or(false, |token| token.text() == name) - }) - } -} - -ast_node!(name: Preamble, kinds: [PREAMBLE], traits: [HasType, HasDelims, HasValue]); - -ast_node!(name: StringDef, kinds: [STRING], traits: [HasType, HasDelims, HasName, HasEq, HasValue]); - -ast_node!(name: Entry, kinds: [ENTRY], traits: [HasType, HasDelims, HasName, HasComma]); - -impl Entry { - pub fn fields(&self) -> impl Iterator { - self.syntax().children().filter_map(Field::cast) - } -} - -ast_node!(name: Field, kinds: [FIELD], traits: [HasName, HasEq, HasValue, HasComma]); - -ast_node_enum!(name: Value, variants: [Literal, CurlyGroup, QuoteGroup, Join, Accent, Command]); - -ast_node!(name: Literal, kinds: [LITERAL], traits: [HasName, HasInteger]); - -ast_node!(name: CurlyGroup, kinds: [CURLY_GROUP], traits: []); - -ast_node!(name: QuoteGroup, kinds: [QUOTE_GROUP], traits: []); - -ast_node!(name: Join, kinds: [JOIN], traits: [HasPound]); - -impl Join { - pub fn left_value(&self) -> Option { - self.syntax().children().find_map(Value::cast) - } - - pub fn right_value(&self) -> Option { - self.syntax().children().filter_map(Value::cast).nth(1) - } -} +pub type SyntaxNode = rowan::SyntaxNode; -ast_node!(name: Accent, kinds: [ACCENT], traits: [HasAccentName, HasWord]); +pub type SyntaxToken = rowan::SyntaxToken; -ast_node!(name: Command, kinds: [COMMAND], traits: [HasCommandName]); +pub type SyntaxElement = rowan::SyntaxElement; diff --git a/support/texlab/crates/syntax/src/bibtex/cst.rs b/support/texlab/crates/syntax/src/bibtex/cst.rs new file mode 100644 index 0000000000..eb67fdb0d7 --- /dev/null +++ b/support/texlab/crates/syntax/src/bibtex/cst.rs @@ -0,0 +1,244 @@ +use rowan::{ast::AstNode, NodeOrToken}; + +use super::{ + BibtexLanguage, + SyntaxKind::{self, *}, + SyntaxNode, SyntaxToken, +}; + +macro_rules! cst_node { + (name: $name:ident, kinds: [$($kind:pat),+], traits: [$($trait: ident),*]) => { + #[derive(Clone)] + pub struct $name { + node: SyntaxNode, + } + + impl AstNode for $name { + type Language = BibtexLanguage; + + fn can_cast(kind: SyntaxKind) -> bool { + match kind { + $($kind => true,)+ + _ => false, + } + } + + fn cast(node: SyntaxNode) -> Option + where + Self: Sized, + { + match node.kind() { + $($kind => Some(Self { node}),)+ + _ => None, + } + } + + fn syntax(&self) -> &SyntaxNode { + &self.node + } + } + + $( + impl $trait for $name { } + )* + }; +} + +macro_rules! cst_node_enum { + (name: $name:ident, variants: [$($variant:ident),+]) => { + #[derive(Clone)] + pub enum $name { + $($variant($variant),)* + } + + impl AstNode for $name { + type Language = BibtexLanguage; + + fn can_cast(kind: SyntaxKind) -> bool { + false $(|| $variant::can_cast(kind))+ + } + + fn cast(node: SyntaxNode) -> Option + where + Self: Sized, + { + None $(.or_else(|| $variant::cast(node.clone()).map(Self::$variant)))* + } + + fn syntax(&self) -> &SyntaxNode { + match self { + $(Self::$variant(node) => node.syntax(),)* + } + } + } + + $( + impl From<$variant> for $name { + fn from(node: $variant) -> Self { + Self::$variant(node) + } + } + )* + }; +} + +pub trait HasType: AstNode { + fn type_token(&self) -> Option { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == TYPE) + } +} + +pub trait HasDelims: AstNode { + fn left_delim_token(&self) -> Option { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == L_DELIM) + } + + fn right_delim_token(&self) -> Option { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == R_DELIM) + } +} + +pub trait HasName: AstNode { + fn name_token(&self) -> Option { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == NAME) + } +} + +pub trait HasEq: AstNode { + fn eq_token(&self) -> Option { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == EQ) + } +} + +pub trait HasComma: AstNode { + fn comma_token(&self) -> Option { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == COMMA) + } +} + +pub trait HasPound: AstNode { + fn pound_token(&self) -> Option { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == POUND) + } +} + +pub trait HasInteger: AstNode { + fn integer_token(&self) -> Option { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == INTEGER) + } +} + +pub trait HasCommandName: AstNode { + fn command_name_token(&self) -> Option { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == COMMAND_NAME) + } +} + +pub trait HasAccentName: AstNode { + fn accent_name_token(&self) -> Option { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == ACCENT_NAME) + } +} + +pub trait HasWord: AstNode { + fn word_token(&self) -> Option { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == WORD) + } +} + +pub trait HasValue: AstNode { + fn value(&self) -> Option { + self.syntax().children().find_map(Value::cast) + } +} + +cst_node!(name: Root, kinds: [ROOT], traits: []); + +impl Root { + pub fn strings(&self) -> impl Iterator { + self.syntax().children().filter_map(StringDef::cast) + } + + pub fn entries(&self) -> impl Iterator { + self.syntax().children().filter_map(Entry::cast) + } + + pub fn find_entry(&self, name: &str) -> Option { + self.entries().find(|entry| { + entry + .name_token() + .map_or(false, |token| token.text() == name) + }) + } +} + +cst_node!(name: Preamble, kinds: [PREAMBLE], traits: [HasType, HasDelims, HasValue]); + +cst_node!(name: StringDef, kinds: [STRING], traits: [HasType, HasDelims, HasName, HasEq, HasValue]); + +cst_node!(name: Entry, kinds: [ENTRY], traits: [HasType, HasDelims, HasName, HasComma]); + +impl Entry { + pub fn fields(&self) -> impl Iterator { + self.syntax().children().filter_map(Field::cast) + } +} + +cst_node!(name: Field, kinds: [FIELD], traits: [HasName, HasEq, HasValue, HasComma]); + +cst_node_enum!(name: Value, variants: [Literal, CurlyGroup, QuoteGroup, Join, Accent, Command]); + +cst_node!(name: Literal, kinds: [LITERAL], traits: [HasName, HasInteger]); + +cst_node!(name: CurlyGroup, kinds: [CURLY_GROUP], traits: []); + +cst_node!(name: QuoteGroup, kinds: [QUOTE_GROUP], traits: []); + +cst_node!(name: Join, kinds: [JOIN], traits: [HasPound]); + +impl Join { + pub fn left_value(&self) -> Option { + self.syntax().children().find_map(Value::cast) + } + + pub fn right_value(&self) -> Option { + self.syntax().children().filter_map(Value::cast).nth(1) + } +} + +cst_node!(name: Accent, kinds: [ACCENT], traits: [HasAccentName, HasWord]); + +cst_node!(name: Command, kinds: [COMMAND], traits: [HasCommandName]); diff --git a/support/texlab/crates/syntax/src/bibtex/kind.rs b/support/texlab/crates/syntax/src/bibtex/kind.rs new file mode 100644 index 0000000000..7817f47495 --- /dev/null +++ b/support/texlab/crates/syntax/src/bibtex/kind.rs @@ -0,0 +1,43 @@ +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] +#[allow(non_camel_case_types)] +#[repr(u16)] +pub enum SyntaxKind { + WHITESPACE, + JUNK, + L_DELIM, + R_DELIM, + L_CURLY, + R_CURLY, + COMMA, + POUND, + QUOTE, + EQ, + TYPE, + WORD, + NAME, + INTEGER, + NBSP, + ACCENT_NAME, + COMMAND_NAME, + + PREAMBLE, + STRING, + ENTRY, + FIELD, + VALUE, + LITERAL, + JOIN, + ACCENT, + COMMAND, + CURLY_GROUP, + QUOTE_GROUP, + ROOT, +} + +pub use SyntaxKind::*; + +impl From for rowan::SyntaxKind { + fn from(kind: SyntaxKind) -> Self { + Self(kind as u16) + } +} diff --git a/support/texlab/crates/syntax/src/lib.rs b/support/texlab/crates/syntax/src/lib.rs index c2c0552652..dc8761b73d 100644 --- a/support/texlab/crates/syntax/src/lib.rs +++ b/support/texlab/crates/syntax/src/lib.rs @@ -20,16 +20,3 @@ pub struct BuildError { pub struct BuildLog { pub errors: Vec, } - -#[macro_export] -macro_rules! match_ast { - (match $node:ident { $($tt:tt)* }) => { $crate::match_ast!(match ($node) { $($tt)* }) }; - - (match ($node:expr) { - $( $( $path:ident )::+ ($it:pat) => $res:expr, )* - _ => $catch_all:expr $(,)? - }) => {{ - $( if let Some($it) = $($path::)+cast($node.clone()) { $res } else )* - { $catch_all } - }}; -} diff --git a/support/texlab/crates/texlab/Cargo.toml b/support/texlab/crates/texlab/Cargo.toml index 65752a4e60..98771a3900 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.9.0" +version = "5.9.1" license.workspace = true readme = "README.md" authors.workspace = true @@ -43,9 +43,11 @@ log = "0.4.19" lsp-server = "0.7.2" lsp-types = "0.94.0" notify = "6.0.1" +notify-debouncer-full = "0.2.0" once_cell = "1.18.0" parking_lot = "0.12.1" parser = { path = "../parser" } +rayon = "1.7.0" references = { path = "../references" } regex = "1.9.1" rowan = "0.15.11" diff --git a/support/texlab/crates/texlab/benches/bench_main.rs b/support/texlab/crates/texlab/benches/bench_main.rs index 47b302e313..7fc6d8fc95 100644 --- a/support/texlab/crates/texlab/benches/bench_main.rs +++ b/support/texlab/crates/texlab/benches/bench_main.rs @@ -36,6 +36,41 @@ fn criterion_benchmark(c: &mut Criterion) { ) }); }); + + c.bench_function("BibTeX/Cite", |b| { + let uri1 = Url::parse("http://example.com/texlab.tex").unwrap(); + let uri2 = Url::parse("http://example.com/rht.bib").unwrap(); + let text1 = r#"\cite{a}\addbibresource{rht.bib}"#.to_string(); + let text2 = include_str!("/home/paddy/texlab-testing/rht.bib").to_string(); + let mut workspace = Workspace::default(); + workspace.open( + uri1.clone(), + text1, + Language::Tex, + Owner::Client, + LineCol { line: 0, col: 0 }, + ); + + workspace.open( + uri2.clone(), + text2, + Language::Bib, + Owner::Client, + LineCol { line: 0, col: 6 }, + ); + + let client_capabilities = ClientCapabilities::default(); + + b.iter(|| { + texlab::features::completion::complete( + &workspace, + &uri1, + Position::new(0, 7), + &client_capabilities, + None, + ) + }); + }); } criterion_group!(benches, criterion_benchmark); diff --git a/support/texlab/crates/texlab/src/features/completion/argument.rs b/support/texlab/crates/texlab/src/features/completion/argument.rs index 2481685ea5..842ce7cab7 100644 --- a/support/texlab/crates/texlab/src/features/completion/argument.rs +++ b/support/texlab/crates/texlab/src/features/completion/argument.rs @@ -47,7 +47,7 @@ pub fn complete<'a>(context: &'a CursorContext, builder: &mut CompletionBuilder< .filter(|(i, _)| *i == index) { for arg in ¶m.0 { - builder.generic_argument(range, &arg.name, arg.image.as_deref()); + builder.generic_argument(range, arg.name, arg.image); } } } diff --git a/support/texlab/crates/texlab/src/features/completion/builder.rs b/support/texlab/crates/texlab/src/features/completion/builder.rs index fede7aeaf9..8457c276fe 100644 --- a/support/texlab/crates/texlab/src/features/completion/builder.rs +++ b/support/texlab/crates/texlab/src/features/completion/builder.rs @@ -1,5 +1,6 @@ use base_db::{ data::{BibtexEntryType, BibtexEntryTypeCategory, BibtexFieldType}, + semantics::bib, Document, MatchingAlgo, }; use fuzzy_matcher::skim::SkimMatcherV2; @@ -8,14 +9,10 @@ use lsp_types::{ ClientCapabilities, ClientInfo, CompletionItem, CompletionItemKind, CompletionList, CompletionTextEdit, Documentation, InsertTextFormat, MarkupContent, MarkupKind, TextEdit, Url, }; -use once_cell::sync::Lazy; -use regex::Regex; +use rayon::prelude::{IntoParallelRefIterator, ParallelExtend, ParallelIterator}; use rowan::{ast::AstNode, TextRange, TextSize}; use serde::{Deserialize, Serialize}; -use syntax::{ - bibtex::{self, HasName, HasType}, - latex, -}; +use syntax::{bibtex, latex}; use crate::util::{ capabilities::ClientCapabilitiesExt, @@ -176,33 +173,51 @@ impl<'a> CompletionBuilder<'a> { Some(()) } + pub fn citations(&mut self, range: TextRange, document: &'a Document) { + let Some(data) = document.data.as_bib() else { + return; + }; + + let iter = data + .semantics + .entries + .par_iter() + .filter_map(|entry| { + let score = self.matcher.score(&entry.keywords, &self.text_pattern)?; + Some((entry, score)) + }) + .map(|(entry, score)| { + let data = Data::Citation { + document, + key: &entry.name.text, + filter_text: &entry.keywords, + category: entry.category, + }; + + Item { + range, + data, + preselect: false, + score, + } + }); + + self.items.par_extend(iter); + } + pub fn citation( &mut self, range: TextRange, document: &'a Document, - entry: &bibtex::Entry, + entry: &'a bib::Entry, ) -> Option<()> { - let key = entry.name_token()?.to_string(); - - let category = BibtexEntryType::find(&entry.type_token()?.text()[1..]) - .map_or(BibtexEntryTypeCategory::Misc, |ty| ty.category); - - let code = entry.syntax().text().to_string(); - let filter_text = format!( - "{} {}", - key, - WHITESPACE_REGEX - .replace_all(&code.replace(['{', '}', ',', '='], " "), " ") - .trim(), - ); - - let score = self.matcher.score(&filter_text, &self.text_pattern)?; + let score = self.matcher.score(&entry.keywords, &self.text_pattern)?; let data = Data::Citation { document, - key, - filter_text, - category, + key: &entry.name.text, + filter_text: &entry.keywords, + category: entry.category, }; self.items.push(Item { @@ -509,18 +524,17 @@ impl<'a> CompletionBuilder<'a> { filter_text, category, } => CompletionItem { - label: key.clone(), + label: key.into(), kind: Some(Structure::Entry(category).completion_kind()), - filter_text: Some(filter_text.clone()), - sort_text: Some(filter_text), + filter_text: Some(filter_text.into()), data: Some( serde_json::to_value(CompletionItemData::Citation { uri: document.uri.clone(), - key: key.clone(), + key: key.into(), }) .unwrap(), ), - text_edit: Some(TextEdit::new(range, key).into()), + text_edit: Some(TextEdit::new(range, key.into()).into()), ..CompletionItem::default() }, Data::ComponentCommand { @@ -599,7 +613,6 @@ impl<'a> CompletionBuilder<'a> { kind: Some(kind.completion_kind()), detail: header, documentation: footer.map(|footer| Documentation::String(footer.into())), - sort_text: Some(text.clone()), filter_text: Some(text), text_edit: Some(TextEdit::new(range, name.into()).into()), ..CompletionItem::default() @@ -634,16 +647,7 @@ impl<'a> CompletionBuilder<'a> { item.kind = Some(CompletionItemKind::TEXT); } - let sort_prefix = format!("{:0>2}", index); - match &item.sort_text { - Some(sort_text) => { - item.sort_text = Some(format!("{} {}", sort_prefix, sort_text)); - } - None => { - item.sort_text = Some(sort_prefix); - } - }; - + item.sort_text = Some(format!("{:0>2}", index)); item } @@ -693,8 +697,8 @@ enum Data<'a> { BeginSnippet, Citation { document: &'a Document, - key: String, - filter_text: String, + key: &'a str, + filter_text: &'a str, category: BibtexEntryTypeCategory, }, ComponentCommand { @@ -778,5 +782,3 @@ pub(crate) enum CompletionItemData { Class, Citation { uri: Url, key: String }, } - -static WHITESPACE_REGEX: Lazy = Lazy::new(|| Regex::new("\\s+").unwrap()); diff --git a/support/texlab/crates/texlab/src/features/completion/citation.rs b/support/texlab/crates/texlab/src/features/completion/citation.rs index 01afa5c28e..b22f361f36 100644 --- a/support/texlab/crates/texlab/src/features/completion/citation.rs +++ b/support/texlab/crates/texlab/src/features/completion/citation.rs @@ -1,6 +1,5 @@ -use base_db::DocumentData; use rowan::{ast::AstNode, TextRange}; -use syntax::{bibtex, latex}; +use syntax::latex; use crate::util::cursor::CursorContext; @@ -26,12 +25,9 @@ pub fn complete<'db>( }; check_citation(context).or_else(|| check_acronym(context))?; - for document in &context.project.documents { - let DocumentData::Bib(data) = &document.data else { continue }; - for entry in data.root_node().children().filter_map(bibtex::Entry::cast) { - builder.citation(range, document, &entry); - } + for document in &context.project.documents { + builder.citations(range, document); } Some(()) diff --git a/support/texlab/crates/texlab/src/features/completion/component_command.rs b/support/texlab/crates/texlab/src/features/completion/component_command.rs index fd7ad5f9f4..3810224a38 100644 --- a/support/texlab/crates/texlab/src/features/completion/component_command.rs +++ b/support/texlab/crates/texlab/src/features/completion/component_command.rs @@ -13,7 +13,7 @@ pub fn complete<'db>( builder.component_command( range, &command.name, - command.image.as_deref(), + command.image, command.glyph.as_deref(), &package.file_names, ); diff --git a/support/texlab/crates/texlab/src/features/completion/matcher.rs b/support/texlab/crates/texlab/src/features/completion/matcher.rs index 5d5c93b2b9..fb53d1c032 100644 --- a/support/texlab/crates/texlab/src/features/completion/matcher.rs +++ b/support/texlab/crates/texlab/src/features/completion/matcher.rs @@ -1,9 +1,9 @@ -pub trait Matcher { - fn score(&mut self, choice: &str, pattern: &str) -> Option; +pub trait Matcher: Send + Sync { + fn score(&self, choice: &str, pattern: &str) -> Option; } impl Matcher for T { - fn score(&mut self, choice: &str, pattern: &str) -> Option { + fn score(&self, choice: &str, pattern: &str) -> Option { fuzzy_matcher::FuzzyMatcher::fuzzy_match(self, choice, pattern) } } @@ -12,7 +12,7 @@ impl Matcher for T { pub struct Prefix; impl Matcher for Prefix { - fn score(&mut self, choice: &str, pattern: &str) -> Option { + fn score(&self, choice: &str, pattern: &str) -> Option { if choice.starts_with(pattern) { Some(-(choice.len() as i32)) } else { @@ -25,7 +25,7 @@ impl Matcher for Prefix { pub struct PrefixIgnoreCase; impl Matcher for PrefixIgnoreCase { - fn score(&mut self, choice: &str, pattern: &str) -> Option { + fn score(&self, choice: &str, pattern: &str) -> Option { if pattern.len() > choice.len() { return None; } diff --git a/support/texlab/crates/texlab/src/server.rs b/support/texlab/crates/texlab/src/server.rs index ef919bab74..5d3fc4a2b4 100644 --- a/support/texlab/crates/texlab/src/server.rs +++ b/support/texlab/crates/texlab/src/server.rs @@ -7,6 +7,7 @@ use std::{ collections::HashMap, path::PathBuf, sync::{atomic::AtomicI32, Arc}, + time::Duration, }; use anyhow::Result; @@ -17,6 +18,7 @@ use diagnostics::{DiagnosticManager, DiagnosticSource}; use distro::{Distro, Language}; use lsp_server::{Connection, ErrorCode, Message, RequestId}; use lsp_types::{notification::*, request::*, *}; +use notify_debouncer_full::{DebouncedEvent, Debouncer, FileIdMap}; use parking_lot::{Mutex, RwLock}; use rowan::ast::AstNode; use rustc_hash::{FxHashMap, FxHashSet}; @@ -49,7 +51,7 @@ use self::{ enum InternalMessage { SetDistro(Distro), SetOptions(Options), - FileEvent(notify::Event), + FileEvent(Vec), Diagnostics, ChktexResult(Url, Vec), ForwardSearch(Url, Option), @@ -887,7 +889,8 @@ impl Server { Ok(()) } - fn handle_file_event(&mut self, event: notify::Event) { + fn handle_file_event(&mut self, debounced_event: DebouncedEvent) { + let event = debounced_event.event; let mut changed = false; let mut workspace = self.workspace.write(); @@ -1115,8 +1118,10 @@ impl Server { InternalMessage::SetOptions(options) => { self.update_options(options); } - InternalMessage::FileEvent(event) => { - self.handle_file_event(event); + InternalMessage::FileEvent(events) => { + for event in events { + self.handle_file_event(event); + } } InternalMessage::Diagnostics => { self.publish_diagnostics()?; @@ -1143,7 +1148,7 @@ impl Server { } struct FileWatcher { - watcher: notify::RecommendedWatcher, + watcher: Debouncer, watched_dirs: FxHashSet, } @@ -1151,17 +1156,17 @@ impl FileWatcher { pub fn new(sender: Sender) -> Result { let handle = move |event| { if let Ok(event) = event { - sender.send(InternalMessage::FileEvent(event)).unwrap(); + let _ = sender.send(InternalMessage::FileEvent(event)); } }; Ok(Self { - watcher: notify::recommended_watcher(handle)?, + watcher: notify_debouncer_full::new_debouncer(Duration::from_secs(1), None, handle)?, watched_dirs: FxHashSet::default(), }) } pub fn watch(&mut self, workspace: &mut Workspace) { - workspace.watch(&mut self.watcher, &mut self.watched_dirs); + workspace.watch(self.watcher.watcher(), &mut self.watched_dirs); } } diff --git a/support/texlab/crates/texlab/src/server/options.rs b/support/texlab/crates/texlab/src/server/options.rs index a602e5d792..30eff447d7 100644 --- a/support/texlab/crates/texlab/src/server/options.rs +++ b/support/texlab/crates/texlab/src/server/options.rs @@ -168,7 +168,7 @@ impl From for Config { config.build.pdf_dir = value .build .pdf_directory - .or_else(|| value.aux_directory) + .or(value.aux_directory) .unwrap_or_else(|| String::from(".")); config.build.log_dir = value diff --git a/support/texlab/crates/texlab/src/util/diagnostics.rs b/support/texlab/crates/texlab/src/util/diagnostics.rs index 4a26765897..89d33060a1 100644 --- a/support/texlab/crates/texlab/src/util/diagnostics.rs +++ b/support/texlab/crates/texlab/src/util/diagnostics.rs @@ -17,12 +17,11 @@ pub fn collect<'db>( source.publish(workspace, &mut builder); builder .iter() - .into_iter() .filter_map(|(uri, diags)| workspace.lookup(uri).map(|document| (document, diags))) .map(|(document, diags)| { let diags = diags .into_iter() - .map(|diag| create_diagnostic(workspace, document, &diag)) + .map(|diag| create_diagnostic(workspace, document, diag)) .collect::>(); (document, diags) diff --git a/support/texlab/crates/texlab/tests/lsp/text_document/snapshots/lsp__text_document__completion__citation.snap b/support/texlab/crates/texlab/tests/lsp/text_document/snapshots/lsp__text_document__completion__citation.snap index b8755fdef0..db26e6125d 100644 --- a/support/texlab/crates/texlab/tests/lsp/text_document/snapshots/lsp__text_document__completion__citation.snap +++ b/support/texlab/crates/texlab/tests/lsp/text_document/snapshots/lsp__text_document__completion__citation.snap @@ -1,16 +1,16 @@ --- -source: tests/lsp/text_document/completion.rs +source: crates/texlab/tests/lsp/text_document/completion.rs expression: "complete(r#\"\n%! main.tex\n\\documentclass{article}\n\\bibliography{main}\n\\begin{document}\n\\cite{\n |\n\\end{document}\n\n%! main.bib\n@article{foo:2019,\n author = {Foo Bar},\n title = {Baz Qux},\n year = {2019},\n}\n\n@article{bar:2005,}\"#)" --- [ { "label": "bar:2005", "preselect": false, - "filterText": "bar:2005 @article bar:2005" + "filterText": "bar:2005 @article" }, { "label": "foo:2019", "preselect": false, - "filterText": "foo:2019 @article foo:2019 author Foo Bar title Baz Qux year 2019" + "filterText": "foo:2019 @article Foo Bar Baz Qux 2019" } ] diff --git a/support/texlab/crates/texlab/tests/lsp/text_document/snapshots/lsp__text_document__completion__citation_acronym.snap b/support/texlab/crates/texlab/tests/lsp/text_document/snapshots/lsp__text_document__completion__citation_acronym.snap index 36478c45c8..242d5d2a0b 100644 --- a/support/texlab/crates/texlab/tests/lsp/text_document/snapshots/lsp__text_document__completion__citation_acronym.snap +++ b/support/texlab/crates/texlab/tests/lsp/text_document/snapshots/lsp__text_document__completion__citation_acronym.snap @@ -1,11 +1,11 @@ --- -source: tests/lsp/text_document/completion.rs +source: crates/texlab/tests/lsp/text_document/completion.rs expression: "complete(r#\"\n%! main.tex\n\\addbibresource{main.bib}\n\\DeclareAcronym{foo}{cite={}}\n |\n\n%! main.bib\n@article{foo,}\"#)" --- [ { "label": "foo", "preselect": false, - "filterText": "foo @article foo" + "filterText": "foo @article" } ] diff --git a/support/texlab/crates/texlab/tests/lsp/text_document/snapshots/lsp__text_document__completion__citation_open_brace.snap b/support/texlab/crates/texlab/tests/lsp/text_document/snapshots/lsp__text_document__completion__citation_open_brace.snap index 41a1d9ced3..b205903a04 100644 --- a/support/texlab/crates/texlab/tests/lsp/text_document/snapshots/lsp__text_document__completion__citation_open_brace.snap +++ b/support/texlab/crates/texlab/tests/lsp/text_document/snapshots/lsp__text_document__completion__citation_open_brace.snap @@ -1,11 +1,11 @@ --- -source: tests/lsp/text_document/completion.rs +source: crates/texlab/tests/lsp/text_document/completion.rs expression: "complete(r#\"\n%! main.tex\n\\addbibresource{main.bib}\n\\cite{\n |\n\n%! main.bib\n@article{foo,}\"#)" --- [ { "label": "foo", "preselect": false, - "filterText": "foo @article foo" + "filterText": "foo @article" } ] diff --git a/support/texlab/crates/texlab/tests/lsp/text_document/snapshots/lsp__text_document__completion__citation_open_brace_multiple.snap b/support/texlab/crates/texlab/tests/lsp/text_document/snapshots/lsp__text_document__completion__citation_open_brace_multiple.snap index 8ef509b463..519fa60fa6 100644 --- a/support/texlab/crates/texlab/tests/lsp/text_document/snapshots/lsp__text_document__completion__citation_open_brace_multiple.snap +++ b/support/texlab/crates/texlab/tests/lsp/text_document/snapshots/lsp__text_document__completion__citation_open_brace_multiple.snap @@ -1,11 +1,11 @@ --- -source: tests/lsp/text_document/completion.rs +source: crates/texlab/tests/lsp/text_document/completion.rs expression: "complete(r#\"\n%! main.tex\n\\addbibresource{main.bib}\n\\cite{foo,a\n |\n ^\n\n%! main.bib\n@article{foo,}\"#)" --- [ { "label": "foo", "preselect": false, - "filterText": "foo @article foo" + "filterText": "foo @article" } ] -- cgit v1.2.3