summaryrefslogtreecommitdiff
path: root/support/texlab/src/syntax/latex/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/syntax/latex/mod.rs')
-rw-r--r--support/texlab/src/syntax/latex/mod.rs912
1 files changed, 620 insertions, 292 deletions
diff --git a/support/texlab/src/syntax/latex/mod.rs b/support/texlab/src/syntax/latex/mod.rs
index dc24682752..cee83ab04e 100644
--- a/support/texlab/src/syntax/latex/mod.rs
+++ b/support/texlab/src/syntax/latex/mod.rs
@@ -1,358 +1,686 @@
+mod analysis;
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<Arc<LatexCommand>>,
+
+pub use self::{analysis::*, ast::*};
+
+use self::{lexer::Lexer, parser::Parser};
+use crate::{
+ protocol::{Options, Uri},
+ tex::Resolver,
+};
+use std::path::Path;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct OpenParams<'a> {
+ pub text: &'a str,
+ pub uri: &'a Uri,
+ pub resolver: &'a Resolver,
+ pub options: &'a Options,
+ pub current_dir: &'a Path,
}
-impl LatexCommandAnalyzer {
- fn parse(root: Arc<LatexRoot>) -> Vec<Arc<LatexCommand>> {
- let mut analyzer = Self::default();
- analyzer.visit_root(root);
- analyzer.commands
- }
+pub fn open(params: OpenParams) -> SymbolTable {
+ let OpenParams {
+ text,
+ uri,
+ resolver,
+ options,
+ current_dir,
+ } = params;
+
+ let lexer = Lexer::new(text);
+ let parser = Parser::new(lexer);
+ let tree = parser.parse();
+
+ let params = SymbolTableParams {
+ tree,
+ uri,
+ resolver,
+ options,
+ current_dir,
+ };
+ SymbolTable::analyze(params)
}
-impl LatexVisitor for LatexCommandAnalyzer {
- fn visit_root(&mut self, root: Arc<LatexRoot>) {
- LatexWalker::walk_root(self, root);
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{
+ protocol::{Options, Range, RangeExt, Uri},
+ syntax::{generic_ast::AstNodeIndex, text::SyntaxNode},
+ tex::Resolver,
+ };
+ use indoc::indoc;
+ use std::env;
+
+ fn open_simple(text: &str) -> SymbolTable {
+ open(OpenParams {
+ text: text.trim(),
+ uri: &Uri::parse("http://www.foo.com/bar.tex").unwrap(),
+ resolver: &Resolver::default(),
+ options: &Options::default(),
+ current_dir: &env::current_dir().unwrap(),
+ })
}
- fn visit_group(&mut self, group: Arc<LatexGroup>) {
- LatexWalker::walk_group(self, group);
+ #[derive(Debug, Default)]
+ struct TreeTraversal {
+ nodes: Vec<AstNodeIndex>,
}
- fn visit_command(&mut self, command: Arc<LatexCommand>) {
- self.commands.push(Arc::clone(&command));
- LatexWalker::walk_command(self, command);
+ impl Visitor for TreeTraversal {
+ fn visit(&mut self, tree: &Tree, node: AstNodeIndex) {
+ self.nodes.push(node);
+ tree.walk(self, node);
+ }
}
- fn visit_text(&mut self, text: Arc<LatexText>) {
- LatexWalker::walk_text(self, text);
- }
+ mod range {
+ use super::*;
- fn visit_comma(&mut self, comma: Arc<LatexComma>) {
- LatexWalker::walk_comma(self, comma);
- }
+ fn verify(expected_ranges: Vec<Range>, text: &str) {
+ let table = open_simple(text);
- fn visit_math(&mut self, math: Arc<LatexMath>) {
- LatexWalker::walk_math(self, math);
- }
-}
+ let mut traversal = TreeTraversal::default();
+ traversal.visit(&table.tree, table.tree.root);
+ let actual_ranges: Vec<_> = traversal
+ .nodes
+ .into_iter()
+ .map(|node| table[node].range())
+ .collect();
+ assert_eq!(actual_ranges, expected_ranges);
+ }
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexCitation {
- pub command: Arc<LatexCommand>,
- pub index: usize,
-}
+ #[test]
+ fn command() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 2, 14),
+ Range::new_simple(0, 0, 0, 23),
+ Range::new_simple(0, 14, 0, 23),
+ Range::new_simple(0, 15, 0, 22),
+ Range::new_simple(1, 0, 1, 20),
+ Range::new_simple(1, 11, 1, 20),
+ Range::new_simple(1, 12, 1, 19),
+ Range::new_simple(2, 0, 2, 14),
+ Range::new_simple(2, 4, 2, 9),
+ Range::new_simple(2, 5, 2, 8),
+ Range::new_simple(2, 9, 2, 14),
+ Range::new_simple(2, 10, 2, 13),
+ ],
+ indoc!(
+ r#"
+ \documentclass{article}
+ \usepackage{amsmath}
+ \foo[bar]{baz}
+ "#
+ ),
+ );
+ }
-impl LatexCitation {
- pub fn keys(&self) -> Vec<&LatexToken> {
- self.command.extract_comma_separated_words(0)
- }
+ #[test]
+ fn text() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 11),
+ Range::new_simple(0, 0, 0, 11),
+ ],
+ indoc!(
+ r#"
+ foo bar baz
+ "#
+ ),
+ );
+ }
- fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> {
- 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,
- });
- }
- }
+ #[test]
+ fn text_bracket() {
+ verify(
+ vec![Range::new_simple(0, 0, 0, 5), Range::new_simple(0, 0, 0, 5)],
+ indoc!(
+ r#"
+ ]foo[
+ "#
+ ),
+ );
}
- citations
- }
-}
-impl SyntaxNode for LatexCitation {
- fn range(&self) -> Range {
- self.command.range()
- }
-}
+ #[test]
+ fn group() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 15),
+ Range::new_simple(0, 0, 0, 15),
+ Range::new_simple(0, 2, 0, 5),
+ Range::new_simple(0, 6, 0, 13),
+ Range::new_simple(0, 8, 0, 11),
+ ],
+ indoc!(
+ r#"
+ { foo { bar } }
+ "#
+ ),
+ );
+ }
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexInclude {
- pub command: Arc<LatexCommand>,
- pub index: usize,
- pub kind: LatexIncludeKind,
- pub all_targets: Vec<Vec<Uri>>,
- pub include_extension: bool,
-}
+ #[test]
+ fn group_incomplete() {
+ verify(
+ vec![Range::new_simple(0, 1, 0, 2), Range::new_simple(0, 1, 0, 2)],
+ indoc!(
+ r#"
+ }{
+ "#
+ ),
+ );
+ }
-impl LatexInclude {
- pub fn paths(&self) -> Vec<&LatexToken> {
- self.command.extract_comma_separated_words(self.index)
+ #[test]
+ fn math() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 9),
+ Range::new_simple(0, 0, 0, 1),
+ Range::new_simple(0, 2, 0, 7),
+ Range::new_simple(0, 8, 0, 9),
+ ],
+ indoc!(
+ r#"
+ $ x = 1 $
+ "#
+ ),
+ );
+ }
+
+ #[test]
+ fn comma() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 8),
+ Range::new_simple(0, 0, 0, 3),
+ Range::new_simple(0, 3, 0, 4),
+ Range::new_simple(0, 5, 0, 8),
+ ],
+ indoc!(
+ r#"
+ foo, bar
+ "#
+ ),
+ );
+ }
}
- pub fn components(&self) -> Vec<String> {
- 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 => (),
- }
+ mod command {
+ use super::*;
+
+ fn verify(expected_names: Vec<&str>, text: &str) {
+ let table = open(OpenParams {
+ text,
+ uri: &Uri::parse("http://www.foo.com/bar.tex").unwrap(),
+ resolver: &Resolver::default(),
+ options: &Options::default(),
+ current_dir: &env::current_dir().unwrap(),
+ });
+
+ let actual_names: Vec<_> = table
+ .commands
+ .iter()
+ .map(|node| table.tree.as_command(*node).unwrap().name.text())
+ .collect();
+
+ assert_eq!(actual_names, expected_names);
+ }
+
+ #[test]
+ fn basic() {
+ verify(
+ vec!["\\documentclass", "\\usepackage", "\\begin", "\\end"],
+ indoc!(
+ r#"
+ \documentclass{article}
+ \usepackage{amsmath}
+ \begin{document}
+ Hello World
+ \end{document}
+ "#
+ ),
+ );
+ }
+
+ #[test]
+ fn star() {
+ verify(
+ vec!["\\section*", "\\subsection*"],
+ indoc!(
+ r#"
+ \section*{Foo}
+ \subsection**{Bar}
+ "#
+ ),
+ );
+ }
+
+ #[test]
+ fn at() {
+ verify(vec!["\\foo@bar"], indoc!(r#"\foo@bar"#));
+ }
+
+ #[test]
+ fn escape() {
+ verify(vec!["\\%"], indoc!(r#"\%foo"#))
}
- components
}
- fn parse(uri: &Uri, commands: &[Arc<LatexCommand>]) -> Vec<Self> {
- 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);
- }
- }
+ mod environment {
+ use super::*;
+
+ fn verify(expected_names: Vec<(&str, &str)>, text: &str) {
+ let table = open_simple(text);
+ let actual_names: Vec<_> = table
+ .environments
+ .iter()
+ .map(|env| {
+ (
+ env.left
+ .name(&table.tree)
+ .map(Token::text)
+ .unwrap_or_default(),
+ env.right
+ .name(&table.tree)
+ .map(Token::text)
+ .unwrap_or_default(),
+ )
+ })
+ .collect();
+
+ assert_eq!(actual_names, expected_names);
+ }
+
+ #[test]
+ fn nested() {
+ verify(
+ vec![("b", "b"), ("a", "a")],
+ indoc!(
+ r#"
+ \begin{a}
+ \begin{b}
+ \end{b}
+ \end{a}
+ "#
+ ),
+ );
+ }
+
+ #[test]
+ fn empty_name() {
+ verify(
+ vec![("a", ""), ("", "b")],
+ indoc!(
+ r#"
+ \begin{a}
+ \end{}
+ \begin{}
+ \end{b}
+ "#
+ ),
+ );
+ }
+
+ #[test]
+ fn incomplete() {
+ verify(
+ Vec::new(),
+ indoc!(
+ r#"
+ \end{a}
+ \begin{a}
+ "#
+ ),
+ );
+ }
+
+ #[test]
+ fn standalone_true() {
+ let table = open_simple(r#"\begin{document}\end{document}"#);
+ assert!(table.is_standalone);
+ }
+
+ #[test]
+ fn standalone_false() {
+ let table = open_simple(r#"\begin{doc}\end{doc}"#);
+ assert!(!table.is_standalone);
}
- includes
}
- fn parse_single(
- uri: &Uri,
- command: &Arc<LatexCommand>,
- description: &LatexIncludeCommand,
- ) -> Option<Self> {
- if command.name.text() != description.name {
- return None;
+ mod include {
+ use super::*;
+
+ fn verify(expected_targets: Vec<Vec<&str>>, resolver: Resolver, text: &str) {
+ let table = open(OpenParams {
+ text,
+ uri: &Uri::parse("http://www.foo.com/dir1/dir2/foo.tex").unwrap(),
+ resolver: &resolver,
+ options: &Options::default(),
+ current_dir: &env::current_dir().unwrap(),
+ });
+
+ assert_eq!(table.includes.len(), 1);
+ let include = &table.includes[0];
+ let actual_targets: Vec<Vec<&str>> = include
+ .all_targets
+ .iter()
+ .map(|targets| targets.iter().map(|target| target.as_str()).collect())
+ .collect();
+
+ assert_eq!(actual_targets, expected_targets);
+ }
+
+ #[test]
+ fn same_directory() {
+ verify(
+ vec![vec![
+ "http://www.foo.com/dir1/dir2/bar",
+ "http://www.foo.com/dir1/dir2/bar.tex",
+ ]],
+ Resolver::default(),
+ indoc!(r#"\include{bar}"#),
+ );
+ }
+
+ #[test]
+ fn two_paths() {
+ verify(
+ vec![
+ vec![
+ "http://www.foo.com/dir1/dir2/bar.tex",
+ "http://www.foo.com/dir1/dir2/bar.tex.tex",
+ ],
+ vec![
+ "http://www.foo.com/dir1/dir2/baz.tex",
+ "http://www.foo.com/dir1/dir2/baz.tex.tex",
+ ],
+ ],
+ Resolver::default(),
+ indoc!(r#"\input{bar.tex, ./baz.tex}"#),
+ );
+ }
+
+ #[test]
+ fn sub_directory() {
+ verify(
+ vec![vec![
+ "http://www.foo.com/dir1/dir2/dir3/bar",
+ "http://www.foo.com/dir1/dir2/dir3/bar.tex",
+ ]],
+ Resolver::default(),
+ indoc!(r#"\include{dir3/bar}"#),
+ );
}
- if command.args.len() <= description.index {
- return None;
+ #[test]
+ fn parent_directory() {
+ verify(
+ vec![vec![
+ "http://www.foo.com/dir1/bar",
+ "http://www.foo.com/dir1/bar.tex",
+ ]],
+ Resolver::default(),
+ indoc!(r#"\include{../bar}"#),
+ );
}
- 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);
+ #[test]
+ fn distro_file() {
+ let mut resolver = Resolver::default();
+ let path = env::current_dir().unwrap().join("biblatex-examples.bib");
+ resolver
+ .files_by_name
+ .insert("biblatex-examples.bib".into(), path.clone());
+ verify(
+ vec![vec![
+ "http://www.foo.com/dir1/dir2/biblatex-examples.bib",
+ "http://www.foo.com/dir1/dir2/biblatex-examples.bib.bib",
+ Uri::from_file_path(&path).unwrap().as_str(),
+ ]],
+ resolver,
+ indoc!(r#"\addbibresource{biblatex-examples.bib}"#),
+ );
}
- let include = Self {
- command: Arc::clone(command),
- index: description.index,
- kind: description.kind,
- all_targets,
- include_extension: description.include_extension,
- };
- Some(include)
+ #[test]
+ fn component() {
+ let table = open(OpenParams {
+ text: indoc!(
+ r#"
+ \documentclass{article}
+ \usepackage{amsmath}
+ \usepackage{geometry, lipsum}
+ "#
+ ),
+ uri: &Uri::parse("http://www.foo.com/bar.tex").unwrap(),
+ resolver: &Resolver::default(),
+ options: &Options::default(),
+ current_dir: &env::current_dir().unwrap(),
+ });
+ assert_eq!(
+ table.components,
+ vec!["article.cls", "amsmath.sty", "geometry.sty", "lipsum.sty"]
+ );
+ }
}
-}
-impl SyntaxNode for LatexInclude {
- fn range(&self) -> Range {
- self.command.range()
+ #[test]
+ fn citation() {
+ let table = open_simple(indoc!(
+ r#"
+ \cite{key1}
+ \cite{key2, key3}
+ \nocite{*}
+ "#
+ ));
+
+ let expected_keys = vec![vec!["key1"], vec!["key2", "key3"], vec!["*"]];
+
+ let actual_keys: Vec<Vec<&str>> = table
+ .citations
+ .iter()
+ .map(|cit| cit.keys(&table.tree).into_iter().map(Token::text).collect())
+ .collect();
+
+ assert_eq!(actual_keys, expected_keys);
}
-}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexCommandDefinition {
- pub command: Arc<LatexCommand>,
- pub definition: Arc<LatexCommand>,
- pub definition_index: usize,
- pub implementation: Arc<LatexGroup>,
- pub implementation_index: usize,
- pub argument_count_index: usize,
-}
+ #[test]
+ fn command_definition() {
+ let table = open_simple(indoc!(
+ r#"
+ \newcommand{\foo}{Foo}
+ \newcommand[2]{\bar}{Bar}
+ \renewcommand{\baz}{Baz}
+ \qux
+ "#
+ ));
+
+ let expected_cmds = vec!["\\foo", "\\bar", "\\baz"];
+
+ let actual_cmds: Vec<&str> = table
+ .command_definitions
+ .iter()
+ .map(|def| def.definition_name(&table.tree))
+ .collect();
-impl LatexCommandDefinition {
- fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> {
- 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
+ assert_eq!(actual_cmds, expected_cmds);
}
-}
-impl SyntaxNode for LatexCommandDefinition {
- fn range(&self) -> Range {
- self.command.range()
+ #[test]
+ fn glossary_entry() {
+ let table = open_simple(indoc!(
+ r#"
+ \newglossaryentry{foo}{...}
+ \newacronym{bar}{...}
+ "#
+ ));
+
+ let expected_entries = vec!["foo", "bar"];
+
+ let actual_entries: Vec<&str> = table
+ .glossary_entries
+ .iter()
+ .map(|entry| entry.label(&table.tree).text())
+ .collect();
+
+ assert_eq!(actual_entries, expected_entries);
}
-}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexSyntaxTree {
- pub root: Arc<LatexRoot>,
- pub commands: Vec<Arc<LatexCommand>>,
- pub includes: Vec<LatexInclude>,
- pub components: Vec<String>,
- pub env: LatexEnvironmentInfo,
- pub structure: LatexStructureInfo,
- pub citations: Vec<LatexCitation>,
- pub math: LatexMathInfo,
- pub command_definitions: Vec<LatexCommandDefinition>,
- pub glossary: LatexGlossaryInfo,
-}
+ #[test]
+ fn equation() {
+ let table = open_simple(indoc!(
+ r#"
+ \[
+ e^{i \pi} + 1 = 0
+ \]
+ \] \[
+ "#
+ ));
+
+ assert_eq!(table.equations.len(), 1);
+ }
-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,
- }
+ #[test]
+ fn inline() {
+ let table = open_simple(indoc!(
+ r#"
+ $ x $
+ $
+ "#
+ ));
+
+ assert_eq!(table.inlines.len(), 1);
}
- pub fn find(&self, position: Position) -> Vec<LatexNode> {
- let mut finder = LatexFinder::new(position);
- finder.visit_root(Arc::clone(&self.root));
- finder.results
+ #[test]
+ fn math_operator() {
+ let table = open_simple(indoc!(
+ r#"
+ \DeclareMathOperator{\foo}{foo}
+ "#
+ ));
+
+ assert_eq!(table.math_operators.len(), 1);
+ assert_eq!(
+ table.math_operators[0].definition_name(&table.tree),
+ "\\foo"
+ );
}
- pub fn find_command_by_name(&self, position: Position) -> Option<Arc<LatexCommand>> {
- 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
+ #[test]
+ fn theorem_definition() {
+ let table = open_simple(indoc!(
+ r#"
+ \newtheorem{lemma}{Lemma}
+ "#
+ ));
+
+ assert_eq!(table.theorem_definitions.len(), 1);
+ assert_eq!(
+ table.theorem_definitions[0].name(&table.tree).text(),
+ "lemma"
+ );
}
- 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()))
+ #[test]
+ fn section() {
+ let table = open_simple(indoc!(
+ r#"
+ \section{Introduction to \LaTeX}
+ \subsection*{Foo
+ "#
+ ));
+ assert_eq!(table.sections.len(), 2);
+ assert_eq!(
+ table.sections[0].print(&table.tree).unwrap(),
+ "Introduction to \\LaTeX"
+ );
+ assert_eq!(table.sections[1].print(&table.tree), None);
}
- pub fn find_label_by_environment(&self, environment: &LatexEnvironment) -> Option<&LatexLabel> {
- self.structure
+ #[test]
+ fn label() {
+ let table = open_simple(indoc!(
+ r#"
+ \label{foo}
+ \ref{bar, baz}
+ "#
+ ));
+
+ let expected_names = vec![vec!["foo"], vec!["bar", "baz"]];
+
+ let actual_names: Vec<Vec<&str>> = table
.labels
.iter()
- .filter(|label| label.kind == LatexLabelKind::Definition)
- .filter(|label| label.names().len() == 1)
- .find(|label| self.is_direct_child(environment, label.start()))
+ .map(|label| {
+ label
+ .names(&table.tree)
+ .into_iter()
+ .map(Token::text)
+ .collect()
+ })
+ .collect();
+
+ assert_eq!(actual_names, expected_names);
}
- 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()))
+ #[test]
+ fn label_numbering() {
+ let table = open_simple(indoc!(
+ r#"
+ \newlabel{foo}{{1}{1}}
+ "#
+ ));
+
+ assert_eq!(table.label_numberings.len(), 1);
+ assert_eq!(table.label_numberings[0].name(&table.tree).text(), "foo");
+ assert_eq!(table.label_numberings[0].number, "1");
}
- 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))
+ #[test]
+ fn caption() {
+ let table = open_simple(indoc!(
+ r#"
+ \caption{Foo \LaTeX Bar}
+ "#
+ ));
+
+ assert_eq!(table.captions.len(), 1);
+ assert_eq!(
+ table.captions[0].print(&table.tree).unwrap(),
+ "Foo \\LaTeX Bar"
+ );
}
-}
-pub fn extract_group(content: &LatexGroup) -> String {
- if content.children.is_empty() || content.right.is_none() {
- return String::new();
+ #[test]
+ fn item_without_name() {
+ let table = open_simple(indoc!(
+ r#"
+ \item
+ "#
+ ));
+
+ assert_eq!(table.items.len(), 1);
+ assert_eq!(table.items[0].name(&table.tree), None);
}
- let mut printer = LatexPrinter::new(content.children[0].start());
- for child in &content.children {
- child.accept(&mut printer);
+ #[test]
+ fn item_with_name() {
+ let table = open_simple(indoc!(
+ r#"
+ \item[foo bar]
+ "#
+ ));
+
+ assert_eq!(table.items.len(), 1);
+ assert_eq!(table.items[0].name(&table.tree).unwrap(), "foo bar");
}
- printer.output
}