diff options
Diffstat (limited to 'support/texlab/src/syntax/latex/analysis')
9 files changed, 391 insertions, 0 deletions
diff --git a/support/texlab/src/syntax/latex/analysis/command.rs b/support/texlab/src/syntax/latex/analysis/command.rs new file mode 100644 index 0000000000..9dea4d84ee --- /dev/null +++ b/support/texlab/src/syntax/latex/analysis/command.rs @@ -0,0 +1,12 @@ +use crate::syntax::{latex, CstNode}; + +use super::LatexAnalyzerContext; + +pub fn analyze_command(context: &mut LatexAnalyzerContext, node: &latex::SyntaxNode) -> Option<()> { + let command = latex::GenericCommand::cast(node)?; + context + .extras + .command_names + .insert(command.name()?.text().into()); + Some(()) +} diff --git a/support/texlab/src/syntax/latex/analysis/distro_file.rs b/support/texlab/src/syntax/latex/analysis/distro_file.rs new file mode 100644 index 0000000000..4c747ce760 --- /dev/null +++ b/support/texlab/src/syntax/latex/analysis/distro_file.rs @@ -0,0 +1,71 @@ +use crate::{distro::Resolver, Uri}; + +pub fn resolve_distro_file(resolver: &Resolver, stem: &str, extensions: &[&str]) -> Option<Uri> { + let mut document = resolver.files_by_name.get(stem); + for extension in extensions { + document = document.or_else(|| { + let full_name = format!("{}.{}", stem, extension); + resolver.files_by_name.get(full_name.as_str()) + }); + } + document.and_then(|path| Uri::from_file_path(path).ok()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[cfg(windows)] + fn test() { + let mut resolver = Resolver::default(); + resolver + .files_by_name + .insert("foo.tex".into(), "C:/distro/foo.tex".into()); + resolver + .files_by_name + .insert("foo.sty".into(), "C:/distro/foo.sty".into()); + resolver + .files_by_name + .insert("bar.tex".into(), "C:/distro/bar.tex".into()); + + assert_eq!( + resolve_distro_file(&resolver, "foo", &["tex"]), + Some(Uri::from_file_path("C:/distro/foo.tex").unwrap()) + ); + + assert_eq!( + resolve_distro_file(&resolver, "foo", &["sty"]), + Some(Uri::from_file_path("C:/distro/foo.sty").unwrap()) + ); + + assert_eq!(resolve_distro_file(&resolver, "foo", &["cls"]), None); + } + + #[test] + #[cfg(unix)] + fn test() { + let mut resolver = Resolver::default(); + resolver + .files_by_name + .insert("foo.tex".into(), "/distro/foo.tex".into()); + resolver + .files_by_name + .insert("foo.sty".into(), "/distro/foo.sty".into()); + resolver + .files_by_name + .insert("bar.tex".into(), "/distro/bar.tex".into()); + + assert_eq!( + resolve_distro_file(&resolver, "foo", &["tex"]), + Some(Uri::from_file_path("/distro/foo.tex").unwrap()) + ); + + assert_eq!( + resolve_distro_file(&resolver, "foo", &["sty"]), + Some(Uri::from_file_path("/distro/foo.sty").unwrap()) + ); + + assert_eq!(resolve_distro_file(&resolver, "foo", &["cls"]), None); + } +} diff --git a/support/texlab/src/syntax/latex/analysis/environment.rs b/support/texlab/src/syntax/latex/analysis/environment.rs new file mode 100644 index 0000000000..3b83b40b2f --- /dev/null +++ b/support/texlab/src/syntax/latex/analysis/environment.rs @@ -0,0 +1,11 @@ +use crate::syntax::{latex, CstNode}; + +use super::LatexAnalyzerContext; + +pub fn analyze_begin(context: &mut LatexAnalyzerContext, node: &latex::SyntaxNode) -> Option<()> { + let begin = latex::Begin::cast(node)?; + let name = begin.name()?.word()?.text(); + let extras = &mut context.extras; + extras.environment_names.insert(name.into()); + Some(()) +} diff --git a/support/texlab/src/syntax/latex/analysis/explicit_link.rs b/support/texlab/src/syntax/latex/analysis/explicit_link.rs new file mode 100644 index 0000000000..363a576aaa --- /dev/null +++ b/support/texlab/src/syntax/latex/analysis/explicit_link.rs @@ -0,0 +1,72 @@ +use std::sync::Arc; + +use crate::syntax::{latex, CstNode}; + +use super::{ + distro_file::resolve_distro_file, ExplicitLink, ExplicitLinkKind, LatexAnalyzerContext, +}; + +pub fn analyze_include(context: &mut LatexAnalyzerContext, node: &latex::SyntaxNode) -> Option<()> { + let include = latex::Include::cast(node)?; + let kind = match include.syntax().kind() { + latex::LATEX_INCLUDE => ExplicitLinkKind::Latex, + latex::BIBLATEX_INCLUDE | latex::BIBTEX_INCLUDE => ExplicitLinkKind::Bibtex, + latex::PACKAGE_INCLUDE => ExplicitLinkKind::Package, + latex::CLASS_INCLUDE => ExplicitLinkKind::Class, + _ => return None, + }; + + let extensions = match kind { + ExplicitLinkKind::Latex => &["tex"], + ExplicitLinkKind::Bibtex => &["bib"], + ExplicitLinkKind::Package => &["sty"], + ExplicitLinkKind::Class => &["cls"], + }; + + for path in include.path_list()?.words() { + let stem = path.text(); + let mut targets = vec![Arc::new(context.base_uri.join(stem).ok()?.into())]; + for extension in extensions { + let path = format!("{}.{}", stem, extension); + targets.push(Arc::new(context.base_uri.join(&path).ok()?.into())); + } + + resolve_distro_file(&context.inner.resolver.lock().unwrap(), stem, extensions) + .into_iter() + .for_each(|target| targets.push(Arc::new(target))); + + context.extras.explicit_links.push(ExplicitLink { + kind, + stem: stem.into(), + stem_range: path.text_range(), + targets, + }); + } + + Some(()) +} + +pub fn analyze_import(context: &mut LatexAnalyzerContext, node: &latex::SyntaxNode) -> Option<()> { + let import = latex::Import::cast(node)?; + + let mut targets = Vec::new(); + let directory = context + .base_uri + .join(import.directory()?.word()?.text()) + .ok()?; + + let file = import.file()?.word()?; + let stem = file.text(); + targets.push(Arc::new(directory.join(stem).ok()?.into())); + targets.push(Arc::new( + directory.join(&format!("{}.tex", stem)).ok()?.into(), + )); + + context.extras.explicit_links.push(ExplicitLink { + stem: stem.into(), + stem_range: file.text_range(), + targets, + kind: ExplicitLinkKind::Latex, + }); + Some(()) +} diff --git a/support/texlab/src/syntax/latex/analysis/implicit_link.rs b/support/texlab/src/syntax/latex/analysis/implicit_link.rs new file mode 100644 index 0000000000..74055b3e31 --- /dev/null +++ b/support/texlab/src/syntax/latex/analysis/implicit_link.rs @@ -0,0 +1,41 @@ +use std::sync::Arc; + +use crate::Uri; + +use super::LatexAnalyzerContext; + +pub fn analyze_implicit_links(context: &mut LatexAnalyzerContext) { + context.extras.implicit_links.aux = find_by_extension(context, "aux").unwrap_or_default(); + context.extras.implicit_links.log = find_by_extension(context, "log").unwrap_or_default(); + context.extras.implicit_links.pdf = find_by_extension(context, "pdf").unwrap_or_default(); +} + +fn find_by_extension(context: &LatexAnalyzerContext, extension: &str) -> Option<Vec<Arc<Uri>>> { + let mut targets: Vec<Arc<Uri>> = Vec::new(); + targets.push(Arc::new(context.document_uri.with_extension(extension)?)); + if context.document_uri.scheme() == "file" { + let file_path = context.document_uri.to_file_path().ok()?; + let file_stem = file_path.file_stem()?; + let aux_name = format!("{}.{}", file_stem.to_str()?, extension); + + let options = context.inner.options.read().unwrap(); + if let Some(root_dir) = options.root_directory.as_ref() { + let path = context + .inner + .current_directory + .join(root_dir) + .join(&aux_name); + targets.push(Arc::new(Uri::from_file_path(path).ok()?)); + } + + if let Some(build_dir) = options.aux_directory.as_ref() { + let path = context + .inner + .current_directory + .join(build_dir) + .join(&aux_name); + targets.push(Arc::new(Uri::from_file_path(path).ok()?)); + } + } + Some(targets) +} diff --git a/support/texlab/src/syntax/latex/analysis/label_name.rs b/support/texlab/src/syntax/latex/analysis/label_name.rs new file mode 100644 index 0000000000..d0410b953c --- /dev/null +++ b/support/texlab/src/syntax/latex/analysis/label_name.rs @@ -0,0 +1,66 @@ +use latex::LabelReferenceRange; + +use crate::syntax::{latex, CstNode}; + +use super::{LabelName, LatexAnalyzerContext}; + +pub fn analyze_label_name( + context: &mut LatexAnalyzerContext, + node: &latex::SyntaxNode, +) -> Option<()> { + analyze_label_definition_name(context, node) + .or_else(|| analyze_label_reference_name(context, node)) + .or_else(|| analyze_label_reference_range_name(context, node)) +} + +fn analyze_label_definition_name( + context: &mut LatexAnalyzerContext, + node: &latex::SyntaxNode, +) -> Option<()> { + let label = latex::LabelDefinition::cast(node)?; + let name = label.name()?.word()?; + context.extras.label_names.push(LabelName { + text: name.text().into(), + range: name.text_range(), + is_definition: true, + }); + Some(()) +} + +fn analyze_label_reference_name( + context: &mut LatexAnalyzerContext, + node: &latex::SyntaxNode, +) -> Option<()> { + let label = latex::LabelReference::cast(node)?; + for name in label.name_list()?.words() { + context.extras.label_names.push(LabelName { + text: name.text().into(), + range: name.text_range(), + is_definition: false, + }); + } + Some(()) +} + +fn analyze_label_reference_range_name( + context: &mut LatexAnalyzerContext, + node: &latex::SyntaxNode, +) -> Option<()> { + let label = LabelReferenceRange::cast(node)?; + if let Some(name1) = label.from().and_then(|name| name.word()) { + context.extras.label_names.push(LabelName { + text: name1.text().into(), + range: name1.text_range(), + is_definition: false, + }); + } + + if let Some(name2) = label.to().and_then(|name| name.word()) { + context.extras.label_names.push(LabelName { + text: name2.text().into(), + range: name2.text_range(), + is_definition: false, + }); + } + Some(()) +} diff --git a/support/texlab/src/syntax/latex/analysis/label_number.rs b/support/texlab/src/syntax/latex/analysis/label_number.rs new file mode 100644 index 0000000000..de8d0dc1e1 --- /dev/null +++ b/support/texlab/src/syntax/latex/analysis/label_number.rs @@ -0,0 +1,22 @@ +use crate::syntax::{latex, CstNode}; + +use super::LatexAnalyzerContext; + +pub fn analyze_label_number( + context: &mut LatexAnalyzerContext, + node: &latex::SyntaxNode, +) -> Option<()> { + let number = latex::LabelNumber::cast(node)?; + let name = number.name()?.word()?.text().into(); + let text = number + .text()? + .syntax() + .descendants_with_tokens() + .filter_map(|element| element.into_node()) + .find(|node| node.kind() == latex::TEXT || node.kind() == latex::MIXED_GROUP)? + .text() + .to_string(); + + context.extras.label_numbers_by_name.insert(name, text); + Some(()) +} diff --git a/support/texlab/src/syntax/latex/analysis/theorem.rs b/support/texlab/src/syntax/latex/analysis/theorem.rs new file mode 100644 index 0000000000..0c365f0ef5 --- /dev/null +++ b/support/texlab/src/syntax/latex/analysis/theorem.rs @@ -0,0 +1,23 @@ +use crate::syntax::{ + latex::{self, HasCurly}, + CstNode, +}; + +use super::{LatexAnalyzerContext, TheoremEnvironment}; + +pub fn analyze_theorem_definition( + context: &mut LatexAnalyzerContext, + node: &latex::SyntaxNode, +) -> Option<()> { + let theorem = latex::TheoremDefinition::cast(node)?; + let name = theorem.name()?.word()?.text().into(); + let description = theorem.description()?; + let description = description.content_text()?; + + context + .extras + .theorem_environments + .push(TheoremEnvironment { name, description }); + + Some(()) +} diff --git a/support/texlab/src/syntax/latex/analysis/types.rs b/support/texlab/src/syntax/latex/analysis/types.rs new file mode 100644 index 0000000000..f089721397 --- /dev/null +++ b/support/texlab/src/syntax/latex/analysis/types.rs @@ -0,0 +1,73 @@ +use std::sync::Arc; + +use cstree::TextRange; +use rustc_hash::{FxHashMap, FxHashSet}; +use smol_str::SmolStr; + +use crate::{ServerContext, Uri}; + +#[derive(Debug)] +pub struct LatexAnalyzerContext { + pub inner: Arc<ServerContext>, + pub document_uri: Arc<Uri>, + pub base_uri: Arc<Uri>, + pub extras: Extras, +} + +#[derive(Debug, Clone, Default)] +pub struct Extras { + pub implicit_links: ImplicitLinks, + pub explicit_links: Vec<ExplicitLink>, + pub has_document_environment: bool, + pub command_names: FxHashSet<SmolStr>, + pub environment_names: FxHashSet<SmolStr>, + pub label_names: Vec<LabelName>, + pub label_numbers_by_name: FxHashMap<SmolStr, String>, + pub theorem_environments: Vec<TheoremEnvironment>, +} + +#[derive(Debug, PartialEq, Eq, Clone, Default, Hash)] +pub struct ImplicitLinks { + pub aux: Vec<Arc<Uri>>, + pub log: Vec<Arc<Uri>>, + pub pdf: Vec<Arc<Uri>>, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)] +pub enum ExplicitLinkKind { + Package, + Class, + Latex, + Bibtex, +} + +#[derive(Debug, Clone)] +pub struct ExplicitLink { + pub stem: SmolStr, + pub stem_range: TextRange, + pub targets: Vec<Arc<Uri>>, + pub kind: ExplicitLinkKind, +} + +impl ExplicitLink { + pub fn as_component_name(&self) -> Option<String> { + match self.kind { + ExplicitLinkKind::Package => Some(format!("{}.sty", self.stem)), + ExplicitLinkKind::Class => Some(format!("{}.cls", self.stem)), + ExplicitLinkKind::Latex | ExplicitLinkKind::Bibtex => None, + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Default, Hash)] +pub struct TheoremEnvironment { + pub name: SmolStr, + pub description: String, +} + +#[derive(Debug, PartialEq, Eq, Clone, Default, Hash)] +pub struct LabelName { + pub text: SmolStr, + pub range: TextRange, + pub is_definition: bool, +} |