diff options
Diffstat (limited to 'support/texlab')
54 files changed, 599 insertions, 444 deletions
diff --git a/support/texlab/CHANGELOG.md b/support/texlab/CHANGELOG.md index 0301227cd1..1f0666306e 100644 --- a/support/texlab/CHANGELOG.md +++ b/support/texlab/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [5.9.1] - 2023-08-11 + +### Fixed + +- Improve performance when completing BibTeX entries ([#493](https://github.com/latex-lsp/texlab/issues/493)) +- Don't report unused entries for very large bibliographies +- Avoid redundant reparses after saving documents + ## [5.9.0] - 2023-08-06 ### Added diff --git a/support/texlab/Cargo.lock b/support/texlab/Cargo.lock index f25cc7c01c..d0dfd6e75d 100644 --- a/support/texlab/Cargo.lock +++ b/support/texlab/Cargo.lock @@ -121,6 +121,7 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" name = "base-db" version = "0.0.0" dependencies = [ + "bibtex-utils", "dirs", "distro", "itertools 0.11.0", @@ -144,6 +145,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" [[package]] +name = "bibtex-utils" +version = "0.0.0" +dependencies = [ + "chrono", + "expect-test", + "human_name", + "itertools 0.11.0", + "parser", + "rowan", + "rustc-hash", + "syntax", + "unicode-normalization", +] + +[[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -234,9 +250,8 @@ dependencies = [ name = "citeproc" version = "0.0.0" dependencies = [ - "chrono", + "bibtex-utils", "expect-test", - "human_name", "isocountry", "itertools 0.11.0", "parser", @@ -602,6 +617,15 @@ dependencies = [ ] [[package]] +name = "file-id" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13be71e6ca82e91bc0cb862bebaac0b2d1924a5a1d970c822b2f98b63fda8c3" +dependencies = [ + "winapi-util", +] + +[[package]] name = "filetime" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -728,6 +752,7 @@ name = "hover" version = "0.0.0" dependencies = [ "base-db", + "bibtex-utils", "citeproc", "completion-data", "expect-test", @@ -1056,6 +1081,19 @@ dependencies = [ ] [[package]] +name = "notify-debouncer-full" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416969970ec751a5d702a88c6cd19ac1332abe997fce43f96db0418550426241" +dependencies = [ + "crossbeam-channel", + "file-id", + "notify", + "parking_lot", + "walkdir", +] + +[[package]] name = "num-traits" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1584,7 +1622,7 @@ dependencies = [ [[package]] name = "texlab" -version = "5.9.0" +version = "5.9.1" dependencies = [ "anyhow", "assert_unordered", @@ -1610,9 +1648,11 @@ dependencies = [ "lsp-server", "lsp-types", "notify", + "notify-debouncer-full", "once_cell", "parking_lot", "parser", + "rayon", "references", "regex", "rowan", 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/citeproc/src/field.rs b/support/texlab/crates/bibtex-utils/src/field.rs index d9f3adacab..d9f3adacab 100644 --- a/support/texlab/crates/citeproc/src/field.rs +++ b/support/texlab/crates/bibtex-utils/src/field.rs diff --git a/support/texlab/crates/citeproc/src/field/author.rs b/support/texlab/crates/bibtex-utils/src/field/author.rs index d8576cbaa4..d8576cbaa4 100644 --- a/support/texlab/crates/citeproc/src/field/author.rs +++ b/support/texlab/crates/bibtex-utils/src/field/author.rs diff --git a/support/texlab/crates/citeproc/src/field/date.rs b/support/texlab/crates/bibtex-utils/src/field/date.rs index 8e2c0b7d72..8e2c0b7d72 100644 --- a/support/texlab/crates/citeproc/src/field/date.rs +++ b/support/texlab/crates/bibtex-utils/src/field/date.rs diff --git a/support/texlab/crates/citeproc/src/field/number.rs b/support/texlab/crates/bibtex-utils/src/field/number.rs index 227f3d5b75..227f3d5b75 100644 --- a/support/texlab/crates/citeproc/src/field/number.rs +++ b/support/texlab/crates/bibtex-utils/src/field/number.rs diff --git a/support/texlab/crates/citeproc/src/field/text.rs b/support/texlab/crates/bibtex-utils/src/field/text.rs index 9689264779..9689264779 100644 --- a/support/texlab/crates/citeproc/src/field/text.rs +++ b/support/texlab/crates/bibtex-utils/src/field/text.rs 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/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<Database<'static>> = 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<DefinitionResult<'db>>, } -pub fn goto_definition<'db>(params: DefinitionParams<'db>) -> Vec<DefinitionResult<'db>> { +pub fn goto_definition(params: DefinitionParams) -> Vec<DefinitionResult> { 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<Url, Vec<Diagnostic>> = 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<Item = Cow<'db, Diagnostic>>, ) { - self.inner.entry(&uri).or_default().extend(diagnostics); + self.inner.entry(uri).or_default().extend(diagnostics); } pub fn iter(&self) -> impl Iterator<Item = (&'db Url, impl Iterator<Item = &Diagnostic>)> { @@ -54,12 +54,14 @@ pub struct DiagnosticManager { impl Default for DiagnosticManager { fn default() -> Self { - let mut sources: Vec<Box<dyn DiagnosticSource>> = 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<Box<dyn DiagnosticSource>> = vec![ + Box::<TexSyntaxErrors>::default(), + Box::<BibSyntaxErrors>::default(), + Box::<BuildErrors>::default(), + Box::<LabelErrors>::default(), + Box::<CitationErrors>::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<Hover<'db>> { 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<Hover<'db .filter(|(_, label)| label.kind == tex::LabelKind::Definition) .find(|(_, label)| label.name_text() == cursor.object.name_text())?; - let label = render_label(¶ms.workspace, ¶ms.project, definition)?; + let label = render_label(params.workspace, ¶ms.project, definition)?; Some(Hover { range: cursor.range, data: HoverData::Label(label), diff --git a/support/texlab/crates/hover/src/lib.rs b/support/texlab/crates/hover/src/lib.rs index 4b06111603..88be3b919a 100644 --- a/support/texlab/crates/hover/src/lib.rs +++ b/support/texlab/crates/hover/src/lib.rs @@ -49,12 +49,12 @@ pub enum HoverData<'db> { } pub fn find<'db>(params: &'db HoverParams<'db>) -> Option<Hover<'db>> { - 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<SyntaxKind> 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::<u16, SyntaxKind>(raw.0) } } @@ -60,245 +22,8 @@ impl rowan::Language for Lang { } } -pub type SyntaxNode = rowan::SyntaxNode<Lang>; - -pub type SyntaxToken = rowan::SyntaxToken<Lang>; - -pub type SyntaxElement = rowan::SyntaxElement<Lang>; - -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<Self> - 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<Self> - 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<Language = Lang> { - fn type_token(&self) -> Option<SyntaxToken> { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == TYPE) - } -} - -pub trait HasDelims: AstNode<Language = Lang> { - fn left_delim_token(&self) -> Option<SyntaxToken> { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == L_DELIM) - } - - fn right_delim_token(&self) -> Option<SyntaxToken> { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == R_DELIM) - } -} - -pub trait HasName: AstNode<Language = Lang> { - fn name_token(&self) -> Option<SyntaxToken> { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == NAME) - } -} - -pub trait HasEq: AstNode<Language = Lang> { - fn eq_token(&self) -> Option<SyntaxToken> { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == EQ) - } -} - -pub trait HasComma: AstNode<Language = Lang> { - fn comma_token(&self) -> Option<SyntaxToken> { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == COMMA) - } -} - -pub trait HasPound: AstNode<Language = Lang> { - fn pound_token(&self) -> Option<SyntaxToken> { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == POUND) - } -} - -pub trait HasInteger: AstNode<Language = Lang> { - fn integer_token(&self) -> Option<SyntaxToken> { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == INTEGER) - } -} - -pub trait HasCommandName: AstNode<Language = Lang> { - fn command_name_token(&self) -> Option<SyntaxToken> { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == COMMAND_NAME) - } -} - -pub trait HasAccentName: AstNode<Language = Lang> { - fn accent_name_token(&self) -> Option<SyntaxToken> { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == ACCENT_NAME) - } -} - -pub trait HasWord: AstNode<Language = Lang> { - fn word_token(&self) -> Option<SyntaxToken> { - self.syntax() - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|token| token.kind() == WORD) - } -} - -pub trait HasValue: AstNode<Language = Lang> { - fn value(&self) -> Option<Value> { - self.syntax().children().find_map(Value::cast) - } -} - -ast_node!(name: Root, kinds: [ROOT], traits: []); - -impl Root { - pub fn strings(&self) -> impl Iterator<Item = StringDef> { - self.syntax().children().filter_map(StringDef::cast) - } - - pub fn entries(&self) -> impl Iterator<Item = Entry> { - self.syntax().children().filter_map(Entry::cast) - } - - pub fn find_entry(&self, name: &str) -> Option<Entry> { - 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<Item = Field> { - 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<Value> { - self.syntax().children().find_map(Value::cast) - } - - pub fn right_value(&self) -> Option<Value> { - self.syntax().children().filter_map(Value::cast).nth(1) - } -} +pub type SyntaxNode = rowan::SyntaxNode<BibtexLanguage>; -ast_node!(name: Accent, kinds: [ACCENT], traits: [HasAccentName, HasWord]); +pub type SyntaxToken = rowan::SyntaxToken<BibtexLanguage>; -ast_node!(name: Command, kinds: [COMMAND], traits: [HasCommandName]); +pub type SyntaxElement = rowan::SyntaxElement<BibtexLanguage>; 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<Self> + 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<Self> + 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<Language = BibtexLanguage> { + fn type_token(&self) -> Option<SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == TYPE) + } +} + +pub trait HasDelims: AstNode<Language = BibtexLanguage> { + fn left_delim_token(&self) -> Option<SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == L_DELIM) + } + + fn right_delim_token(&self) -> Option<SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == R_DELIM) + } +} + +pub trait HasName: AstNode<Language = BibtexLanguage> { + fn name_token(&self) -> Option<SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == NAME) + } +} + +pub trait HasEq: AstNode<Language = BibtexLanguage> { + fn eq_token(&self) -> Option<SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == EQ) + } +} + +pub trait HasComma: AstNode<Language = BibtexLanguage> { + fn comma_token(&self) -> Option<SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == COMMA) + } +} + +pub trait HasPound: AstNode<Language = BibtexLanguage> { + fn pound_token(&self) -> Option<SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == POUND) + } +} + +pub trait HasInteger: AstNode<Language = BibtexLanguage> { + fn integer_token(&self) -> Option<SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == INTEGER) + } +} + +pub trait HasCommandName: AstNode<Language = BibtexLanguage> { + fn command_name_token(&self) -> Option<SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == COMMAND_NAME) + } +} + +pub trait HasAccentName: AstNode<Language = BibtexLanguage> { + fn accent_name_token(&self) -> Option<SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == ACCENT_NAME) + } +} + +pub trait HasWord: AstNode<Language = BibtexLanguage> { + fn word_token(&self) -> Option<SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|token| token.kind() == WORD) + } +} + +pub trait HasValue: AstNode<Language = BibtexLanguage> { + fn value(&self) -> Option<Value> { + self.syntax().children().find_map(Value::cast) + } +} + +cst_node!(name: Root, kinds: [ROOT], traits: []); + +impl Root { + pub fn strings(&self) -> impl Iterator<Item = StringDef> { + self.syntax().children().filter_map(StringDef::cast) + } + + pub fn entries(&self) -> impl Iterator<Item = Entry> { + self.syntax().children().filter_map(Entry::cast) + } + + pub fn find_entry(&self, name: &str) -> Option<Entry> { + 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<Item = Field> { + 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<Value> { + self.syntax().children().find_map(Value::cast) + } + + pub fn right_value(&self) -> Option<Value> { + 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<SyntaxKind> 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<BuildError>, } - -#[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<Regex> = 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<i32>; +pub trait Matcher: Send + Sync { + fn score(&self, choice: &str, pattern: &str) -> Option<i32>; } impl<T: fuzzy_matcher::FuzzyMatcher> Matcher for T { - fn score(&mut self, choice: &str, pattern: &str) -> Option<i32> { + fn score(&self, choice: &str, pattern: &str) -> Option<i32> { fuzzy_matcher::FuzzyMatcher::fuzzy_match(self, choice, pattern) } } @@ -12,7 +12,7 @@ impl<T: fuzzy_matcher::FuzzyMatcher> Matcher for T { pub struct Prefix; impl Matcher for Prefix { - fn score(&mut self, choice: &str, pattern: &str) -> Option<i32> { + fn score(&self, choice: &str, pattern: &str) -> Option<i32> { 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<i32> { + fn score(&self, choice: &str, pattern: &str) -> Option<i32> { 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<DebouncedEvent>), Diagnostics, ChktexResult(Url, Vec<lsp_types::Diagnostic>), ForwardSearch(Url, Option<Position>), @@ -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<notify::RecommendedWatcher, FileIdMap>, watched_dirs: FxHashSet<PathBuf>, } @@ -1151,17 +1156,17 @@ impl FileWatcher { pub fn new(sender: Sender<InternalMessage>) -> Result<Self> { 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<Options> 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::<Vec<_>>(); (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" } ] diff --git a/support/texlab/texlab.1 b/support/texlab/texlab.1 index 7d5e82cd43..9910cd0bb8 100644 --- a/support/texlab/texlab.1 +++ b/support/texlab/texlab.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13. -.TH TEXLAB "1" "August 2023" "texlab 5.9.0" "User Commands" +.TH TEXLAB "1" "August 2023" "texlab 5.9.1" "User Commands" .SH NAME -texlab \- manual page for texlab 5.9.0 +texlab \- manual page for texlab 5.9.1 .SH SYNOPSIS .B texlab [\fI\,OPTIONS\/\fR] diff --git a/support/texlab/texlab.pdf b/support/texlab/texlab.pdf Binary files differindex 71e172b73b..74092ac147 100644 --- a/support/texlab/texlab.pdf +++ b/support/texlab/texlab.pdf |