mod ast; mod env; mod finder; mod glossary; mod lexer; mod math; mod parser; mod printer; mod structure; pub use self::ast::*; pub use self::env::*; pub use self::finder::LatexNode; pub use self::glossary::*; pub use self::math::*; pub use self::printer::LatexPrinter; pub use self::structure::*; use self::finder::LatexFinder; use self::lexer::LatexLexer; use self::parser::LatexParser; use super::language::*; use super::text::SyntaxNode; use crate::range::RangeExt; use crate::workspace::Uri; use lsp_types::{Position, Range}; use path_clean::PathClean; use std::path::PathBuf; use std::sync::Arc; #[derive(Debug, Default)] struct LatexCommandAnalyzer { commands: Vec>, } impl LatexCommandAnalyzer { fn parse(root: Arc) -> Vec> { let mut analyzer = Self::default(); analyzer.visit_root(root); analyzer.commands } } impl LatexVisitor for LatexCommandAnalyzer { fn visit_root(&mut self, root: Arc) { LatexWalker::walk_root(self, root); } fn visit_group(&mut self, group: Arc) { LatexWalker::walk_group(self, group); } fn visit_command(&mut self, command: Arc) { self.commands.push(Arc::clone(&command)); LatexWalker::walk_command(self, command); } fn visit_text(&mut self, text: Arc) { LatexWalker::walk_text(self, text); } fn visit_comma(&mut self, comma: Arc) { LatexWalker::walk_comma(self, comma); } fn visit_math(&mut self, math: Arc) { LatexWalker::walk_math(self, math); } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct LatexCitation { pub command: Arc, pub index: usize, } impl LatexCitation { pub fn keys(&self) -> Vec<&LatexToken> { self.command.extract_comma_separated_words(0) } fn parse(commands: &[Arc]) -> Vec { let mut citations = Vec::new(); for command in commands { for LatexCitationCommand { name, index } in &LANGUAGE_DATA.citation_commands { if command.name.text() == name && command.has_comma_separated_words(*index) { citations.push(Self { command: Arc::clone(command), index: *index, }); } } } citations } } impl SyntaxNode for LatexCitation { fn range(&self) -> Range { self.command.range() } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct LatexInclude { pub command: Arc, pub index: usize, pub kind: LatexIncludeKind, pub all_targets: Vec>, pub include_extension: bool, } impl LatexInclude { pub fn paths(&self) -> Vec<&LatexToken> { self.command.extract_comma_separated_words(self.index) } pub fn components(&self) -> Vec { let mut components = Vec::new(); for path in self.paths() { match self.kind { LatexIncludeKind::Package => components.push(format!("{}.sty", path.text())), LatexIncludeKind::Class => components.push(format!("{}.cls", path.text())), LatexIncludeKind::Latex | LatexIncludeKind::Bibliography | LatexIncludeKind::Image | LatexIncludeKind::Svg | LatexIncludeKind::Pdf | LatexIncludeKind::Everything => (), } } components } fn parse(uri: &Uri, commands: &[Arc]) -> Vec { let mut includes = Vec::new(); for command in commands { for description in &LANGUAGE_DATA.include_commands { if let Some(include) = Self::parse_single(uri, &command, &description) { includes.push(include); } } } includes } fn parse_single( uri: &Uri, command: &Arc, description: &LatexIncludeCommand, ) -> Option { if command.name.text() != description.name { return None; } if command.args.len() <= description.index { return None; } let mut all_targets = Vec::new(); for relative_path in command.extract_comma_separated_words(description.index) { let mut path = uri.to_file_path().ok()?; path.pop(); path.push(relative_path.text()); path = PathBuf::from(path.to_string_lossy().into_owned().replace('\\', "/")); path = path.clean(); let path = path.to_str()?.to_owned(); let mut targets = Vec::new(); targets.push(Uri::from_file_path(&path).ok()?); if let Some(extensions) = description.kind.extensions() { for extension in extensions { let path = format!("{}.{}", &path, extension); targets.push(Uri::from_file_path(&path).ok()?); } } all_targets.push(targets); } let include = Self { command: Arc::clone(command), index: description.index, kind: description.kind, all_targets, include_extension: description.include_extension, }; Some(include) } } impl SyntaxNode for LatexInclude { fn range(&self) -> Range { self.command.range() } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct LatexCommandDefinition { pub command: Arc, pub definition: Arc, pub definition_index: usize, pub implementation: Arc, pub implementation_index: usize, pub argument_count_index: usize, } impl LatexCommandDefinition { fn parse(commands: &[Arc]) -> Vec { let mut definitions = Vec::new(); for command in commands { for LatexCommandDefinitionCommand { name, definition_index, argument_count_index, implementation_index, } in &LANGUAGE_DATA.command_definition_commands { if command.name.text() == name && command.args.len() > *definition_index && command.args.len() > *implementation_index { let definition = command.args[0].children.iter().next(); if let Some(LatexContent::Command(definition)) = definition { definitions.push(Self { command: Arc::clone(command), definition: Arc::clone(definition), definition_index: *definition_index, implementation: Arc::clone(&command.args[*implementation_index]), implementation_index: *implementation_index, argument_count_index: *argument_count_index, }) } } } } definitions } } impl SyntaxNode for LatexCommandDefinition { fn range(&self) -> Range { self.command.range() } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct LatexSyntaxTree { pub root: Arc, pub commands: Vec>, pub includes: Vec, pub components: Vec, pub env: LatexEnvironmentInfo, pub structure: LatexStructureInfo, pub citations: Vec, pub math: LatexMathInfo, pub command_definitions: Vec, pub glossary: LatexGlossaryInfo, } impl LatexSyntaxTree { pub fn parse(uri: &Uri, text: &str) -> Self { let lexer = LatexLexer::new(text); let mut parser = LatexParser::new(lexer); let root = Arc::new(parser.root()); let commands = LatexCommandAnalyzer::parse(Arc::clone(&root)); let includes = LatexInclude::parse(uri, &commands); let components = includes.iter().flat_map(LatexInclude::components).collect(); let env = LatexEnvironmentInfo::parse(&commands); let structure = LatexStructureInfo::parse(&commands); let citations = LatexCitation::parse(&commands); let math = LatexMathInfo::parse(Arc::clone(&root), &commands); let command_definitions = LatexCommandDefinition::parse(&commands); let glossary = LatexGlossaryInfo::parse(&commands); Self { root, commands, includes, components, env, structure, citations, math, command_definitions, glossary, } } pub fn find(&self, position: Position) -> Vec { let mut finder = LatexFinder::new(position); finder.visit_root(Arc::clone(&self.root)); finder.results } pub fn find_command_by_name(&self, position: Position) -> Option> { for result in self.find(position) { if let LatexNode::Command(command) = result { if command.name.range().contains(position) && command.name.start().character != position.character { return Some(command); } } } None } pub fn find_label_by_range(&self, range: Range) -> Option<&LatexLabel> { self.structure .labels .iter() .filter(|label| label.kind == LatexLabelKind::Definition) .filter(|label| label.names().len() == 1) .find(|label| range.contains(label.start())) } pub fn find_label_by_environment(&self, environment: &LatexEnvironment) -> Option<&LatexLabel> { self.structure .labels .iter() .filter(|label| label.kind == LatexLabelKind::Definition) .filter(|label| label.names().len() == 1) .find(|label| self.is_direct_child(environment, label.start())) } pub fn is_enumeration_item(&self, enumeration: &LatexEnvironment, item: &LatexItem) -> bool { enumeration.range().contains(item.start()) && !self .env .environments .iter() .filter(|env| *env != enumeration) .filter(|env| env.left.is_enum() && enumeration.range().contains(env.start())) .any(|env| env.range().contains(item.start())) } pub fn is_direct_child(&self, environment: &LatexEnvironment, position: Position) -> bool { environment.range().contains(position) && !self .env .environments .iter() .filter(|env| *env != environment) .filter(|env| environment.range().contains(env.start())) .any(|env| env.range().contains(position)) } } pub fn extract_group(content: &LatexGroup) -> String { if content.children.is_empty() || content.right.is_none() { return String::new(); } let mut printer = LatexPrinter::new(content.children[0].start()); for child in &content.children { child.accept(&mut printer); } printer.output }