diff options
author | Norbert Preining <norbert@preining.info> | 2019-12-03 03:01:24 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2019-12-03 03:01:24 +0000 |
commit | b8d4bb76703bcb15578e2b23c5d256532180b894 (patch) | |
tree | bedd1df7a00521a2bd986b3c0289d6556a59e39b /support/texlab/src/syntax/latex | |
parent | 02e4625a78a5029e8b5dc2a4ec70193b232f497e (diff) |
CTAN sync 201912030301
Diffstat (limited to 'support/texlab/src/syntax/latex')
-rw-r--r-- | support/texlab/src/syntax/latex/ast.rs | 345 | ||||
-rw-r--r-- | support/texlab/src/syntax/latex/env.rs | 123 | ||||
-rw-r--r-- | support/texlab/src/syntax/latex/finder.rs | 74 | ||||
-rw-r--r-- | support/texlab/src/syntax/latex/glossary.rs | 58 | ||||
-rw-r--r-- | support/texlab/src/syntax/latex/lexer.rs | 179 | ||||
-rw-r--r-- | support/texlab/src/syntax/latex/math.rs | 199 | ||||
-rw-r--r-- | support/texlab/src/syntax/latex/mod.rs | 358 | ||||
-rw-r--r-- | support/texlab/src/syntax/latex/parser.rs | 135 | ||||
-rw-r--r-- | support/texlab/src/syntax/latex/printer.rs | 77 | ||||
-rw-r--r-- | support/texlab/src/syntax/latex/structure.rs | 250 |
10 files changed, 1798 insertions, 0 deletions
diff --git a/support/texlab/src/syntax/latex/ast.rs b/support/texlab/src/syntax/latex/ast.rs new file mode 100644 index 0000000000..0af980b595 --- /dev/null +++ b/support/texlab/src/syntax/latex/ast.rs @@ -0,0 +1,345 @@ +use crate::range::RangeExt; +use crate::syntax::text::{Span, SyntaxNode}; +use itertools::Itertools; +use lsp_types::Range; +use std::sync::Arc; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum LatexTokenKind { + Word, + Command, + Math, + Comma, + BeginGroup, + EndGroup, + BeginOptions, + EndOptions, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexToken { + pub span: Span, + pub kind: LatexTokenKind, +} + +impl LatexToken { + pub fn new(span: Span, kind: LatexTokenKind) -> Self { + Self { span, kind } + } + + pub fn text(&self) -> &str { + &self.span.text + } +} + +impl SyntaxNode for LatexToken { + fn range(&self) -> Range { + self.span.range() + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Default)] +pub struct LatexRoot { + pub children: Vec<LatexContent>, +} + +impl LatexRoot { + pub fn new(children: Vec<LatexContent>) -> Self { + Self { children } + } +} + +impl SyntaxNode for LatexRoot { + fn range(&self) -> Range { + if self.children.is_empty() { + Range::new_simple(0, 0, 0, 0) + } else { + Range::new( + self.children[0].start(), + self.children[self.children.len() - 1].end(), + ) + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum LatexContent { + Group(Arc<LatexGroup>), + Command(Arc<LatexCommand>), + Text(Arc<LatexText>), + Comma(Arc<LatexComma>), + Math(Arc<LatexMath>), +} + +impl LatexContent { + pub fn accept<T: LatexVisitor>(&self, visitor: &mut T) { + match self { + LatexContent::Group(group) => visitor.visit_group(Arc::clone(&group)), + LatexContent::Command(command) => visitor.visit_command(Arc::clone(&command)), + LatexContent::Text(text) => visitor.visit_text(Arc::clone(&text)), + LatexContent::Comma(comma) => visitor.visit_comma(Arc::clone(&comma)), + LatexContent::Math(math) => visitor.visit_math(Arc::clone(&math)), + } + } +} + +impl SyntaxNode for LatexContent { + fn range(&self) -> Range { + match self { + LatexContent::Group(group) => group.range(), + LatexContent::Command(command) => command.range(), + LatexContent::Text(text) => text.range(), + LatexContent::Comma(comma) => comma.range(), + LatexContent::Math(math) => math.range(), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum LatexGroupKind { + Group, + Options, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexGroup { + pub range: Range, + pub left: LatexToken, + pub children: Vec<LatexContent>, + pub right: Option<LatexToken>, + pub kind: LatexGroupKind, +} + +impl LatexGroup { + pub fn new( + left: LatexToken, + children: Vec<LatexContent>, + right: Option<LatexToken>, + kind: LatexGroupKind, + ) -> Self { + let end = if let Some(ref right) = right { + right.end() + } else if !children.is_empty() { + children[children.len() - 1].end() + } else { + left.end() + }; + + Self { + range: Range::new(left.start(), end), + left, + children, + right, + kind, + } + } +} + +impl SyntaxNode for LatexGroup { + fn range(&self) -> Range { + self.range + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexCommand { + pub range: Range, + pub name: LatexToken, + pub options: Vec<Arc<LatexGroup>>, + pub args: Vec<Arc<LatexGroup>>, + pub groups: Vec<Arc<LatexGroup>>, +} + +impl LatexCommand { + pub fn new( + name: LatexToken, + options: Vec<Arc<LatexGroup>>, + args: Vec<Arc<LatexGroup>>, + ) -> Self { + let groups: Vec<Arc<LatexGroup>> = args + .iter() + .chain(options.iter()) + .sorted_by_key(|group| group.range.start) + .map(Arc::clone) + .collect(); + + let end = if let Some(group) = groups.last() { + group.end() + } else { + name.end() + }; + + Self { + range: Range::new(name.start(), end), + name, + options, + args, + groups, + } + } + + pub fn short_name_range(&self) -> Range { + Range::new_simple( + self.name.start().line, + self.name.start().character + 1, + self.name.end().line, + self.name.end().character, + ) + } + + pub fn extract_text(&self, index: usize) -> Option<&LatexText> { + if self.args.len() > index && self.args[index].children.len() == 1 { + if let LatexContent::Text(ref text) = self.args[index].children[0] { + Some(text) + } else { + None + } + } else { + None + } + } + + pub fn extract_word(&self, index: usize) -> Option<&LatexToken> { + let text = self.extract_text(index)?; + if text.words.len() == 1 { + Some(&text.words[0]) + } else { + None + } + } + + pub fn has_word(&self, index: usize) -> bool { + self.extract_word(index).is_some() + } + + pub fn extract_comma_separated_words(&self, index: usize) -> Vec<&LatexToken> { + let mut words = Vec::new(); + for child in &self.args[index].children { + if let LatexContent::Text(text) = child { + for word in &text.words { + words.push(word); + } + } + } + words + } + + pub fn has_comma_separated_words(&self, index: usize) -> bool { + if self.args.len() <= index { + return false; + } + + for node in &self.args[index].children { + match node { + LatexContent::Text(_) | LatexContent::Comma(_) => (), + LatexContent::Command(_) | LatexContent::Group(_) | LatexContent::Math(_) => { + return false; + } + } + } + true + } +} + +impl SyntaxNode for LatexCommand { + fn range(&self) -> Range { + self.range + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexText { + pub range: Range, + pub words: Vec<LatexToken>, +} + +impl LatexText { + pub fn new(words: Vec<LatexToken>) -> Self { + Self { + range: Range::new(words[0].start(), words[words.len() - 1].end()), + words, + } + } +} + +impl SyntaxNode for LatexText { + fn range(&self) -> Range { + self.range + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexComma { + pub token: LatexToken, +} + +impl LatexComma { + pub fn new(token: LatexToken) -> Self { + Self { token } + } +} + +impl SyntaxNode for LatexComma { + fn range(&self) -> Range { + self.token.range() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexMath { + pub token: LatexToken, +} + +impl LatexMath { + pub fn new(token: LatexToken) -> Self { + Self { token } + } +} + +impl SyntaxNode for LatexMath { + fn range(&self) -> Range { + self.token.range() + } +} + +pub trait LatexVisitor { + fn visit_root(&mut self, root: Arc<LatexRoot>); + + fn visit_group(&mut self, group: Arc<LatexGroup>); + + fn visit_command(&mut self, command: Arc<LatexCommand>); + + fn visit_text(&mut self, text: Arc<LatexText>); + + fn visit_comma(&mut self, comma: Arc<LatexComma>); + + fn visit_math(&mut self, math: Arc<LatexMath>); +} + +pub struct LatexWalker; + +impl LatexWalker { + pub fn walk_root<T: LatexVisitor>(visitor: &mut T, root: Arc<LatexRoot>) { + for child in &root.children { + child.accept(visitor); + } + } + + pub fn walk_group<T: LatexVisitor>(visitor: &mut T, group: Arc<LatexGroup>) { + for child in &group.children { + child.accept(visitor); + } + } + + pub fn walk_command<T: LatexVisitor>(visitor: &mut T, command: Arc<LatexCommand>) { + for arg in &command.groups { + visitor.visit_group(Arc::clone(&arg)); + } + } + + pub fn walk_text<T: LatexVisitor>(_visitor: &mut T, _text: Arc<LatexText>) {} + + pub fn walk_comma<T: LatexVisitor>(_visitor: &mut T, _comma: Arc<LatexComma>) {} + + pub fn walk_math<T: LatexVisitor>(_visitor: &mut T, _math: Arc<LatexMath>) {} +} diff --git a/support/texlab/src/syntax/latex/env.rs b/support/texlab/src/syntax/latex/env.rs new file mode 100644 index 0000000000..163db02ca8 --- /dev/null +++ b/support/texlab/src/syntax/latex/env.rs @@ -0,0 +1,123 @@ +use super::ast::*; +use crate::syntax::language::*; +use crate::syntax::text::SyntaxNode; +use lsp_types::Range; +use std::sync::Arc; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexEnvironmentDelimiter { + pub command: Arc<LatexCommand>, +} + +impl LatexEnvironmentDelimiter { + pub fn name(&self) -> Option<&LatexToken> { + self.command.extract_word(0) + } + + pub fn is_math(&self) -> bool { + if let Some(name) = self.name() { + LANGUAGE_DATA + .math_environments + .iter() + .any(|env| env == name.text()) + } else { + false + } + } + + pub fn is_enum(&self) -> bool { + if let Some(name) = self.name() { + LANGUAGE_DATA + .enum_environments + .iter() + .any(|env| env == name.text()) + } else { + false + } + } +} + +impl SyntaxNode for LatexEnvironmentDelimiter { + fn range(&self) -> Range { + self.command.range() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexEnvironment { + pub left: LatexEnvironmentDelimiter, + pub right: LatexEnvironmentDelimiter, +} + +impl LatexEnvironment { + pub fn is_root(&self) -> bool { + self.left + .name() + .iter() + .chain(self.right.name().iter()) + .any(|name| name.text() == "document") + } + + fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> { + let mut stack = Vec::new(); + let mut environments = Vec::new(); + for command in commands { + if let Some(delimiter) = Self::parse_delimiter(command) { + if delimiter.command.name.text() == "\\begin" { + stack.push(delimiter); + } else if let Some(begin) = stack.pop() { + environments.push(Self { + left: begin, + right: delimiter, + }); + } + } + } + environments + } + + fn parse_delimiter(command: &Arc<LatexCommand>) -> Option<LatexEnvironmentDelimiter> { + if command.name.text() != "\\begin" && command.name.text() != "\\end" { + return None; + } + + if command.args.is_empty() { + return None; + } + + if command.has_word(0) + || command.args[0].children.is_empty() + || command.args[0].right.is_none() + { + let delimiter = LatexEnvironmentDelimiter { + command: Arc::clone(&command), + }; + Some(delimiter) + } else { + None + } + } +} + +impl SyntaxNode for LatexEnvironment { + fn range(&self) -> Range { + Range::new(self.left.start(), self.right.end()) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexEnvironmentInfo { + pub environments: Vec<LatexEnvironment>, + pub is_standalone: bool, +} + +impl LatexEnvironmentInfo { + pub fn parse(commands: &[Arc<LatexCommand>]) -> Self { + let environments = LatexEnvironment::parse(commands); + let is_standalone = environments.iter().any(LatexEnvironment::is_root); + Self { + environments, + is_standalone, + } + } +} diff --git a/support/texlab/src/syntax/latex/finder.rs b/support/texlab/src/syntax/latex/finder.rs new file mode 100644 index 0000000000..b59388dc07 --- /dev/null +++ b/support/texlab/src/syntax/latex/finder.rs @@ -0,0 +1,74 @@ +use super::ast::*; +use crate::range::RangeExt; +use crate::syntax::text::SyntaxNode; +use lsp_types::Position; +use std::sync::Arc; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum LatexNode { + Root(Arc<LatexRoot>), + Group(Arc<LatexGroup>), + Command(Arc<LatexCommand>), + Text(Arc<LatexText>), + Comma(Arc<LatexComma>), + Math(Arc<LatexMath>), +} + +#[derive(Debug)] +pub struct LatexFinder { + pub position: Position, + pub results: Vec<LatexNode>, +} + +impl LatexFinder { + pub fn new(position: Position) -> Self { + Self { + position, + results: Vec::new(), + } + } +} + +impl LatexVisitor for LatexFinder { + fn visit_root(&mut self, root: Arc<LatexRoot>) { + if root.range().contains(self.position) { + self.results.push(LatexNode::Root(Arc::clone(&root))); + LatexWalker::walk_root(self, root); + } + } + + fn visit_group(&mut self, group: Arc<LatexGroup>) { + if group.range.contains(self.position) { + self.results.push(LatexNode::Group(Arc::clone(&group))); + LatexWalker::walk_group(self, group); + } + } + + fn visit_command(&mut self, command: Arc<LatexCommand>) { + if command.range.contains(self.position) { + self.results.push(LatexNode::Command(Arc::clone(&command))); + LatexWalker::walk_command(self, command); + } + } + + fn visit_text(&mut self, text: Arc<LatexText>) { + if text.range.contains(self.position) { + self.results.push(LatexNode::Text(Arc::clone(&text))); + LatexWalker::walk_text(self, text); + } + } + + fn visit_comma(&mut self, comma: Arc<LatexComma>) { + if comma.range().contains(self.position) { + self.results.push(LatexNode::Comma(Arc::clone(&comma))); + LatexWalker::walk_comma(self, comma); + } + } + + fn visit_math(&mut self, math: Arc<LatexMath>) { + if math.range().contains(self.position) { + self.results.push(LatexNode::Math(Arc::clone(&math))); + LatexWalker::walk_math(self, math); + } + } +} diff --git a/support/texlab/src/syntax/latex/glossary.rs b/support/texlab/src/syntax/latex/glossary.rs new file mode 100644 index 0000000000..14f87ef130 --- /dev/null +++ b/support/texlab/src/syntax/latex/glossary.rs @@ -0,0 +1,58 @@ +use super::ast::*; +use crate::syntax::language::*; +use crate::syntax::text::SyntaxNode; +use lsp_types::Range; +use std::sync::Arc; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexGlossaryEntry { + pub command: Arc<LatexCommand>, + pub label_index: usize, + pub kind: LatexGlossaryEntryKind, +} + +impl SyntaxNode for LatexGlossaryEntry { + fn range(&self) -> Range { + self.command.range() + } +} + +impl LatexGlossaryEntry { + pub fn label(&self) -> &LatexToken { + self.command.extract_word(self.label_index).unwrap() + } + + fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> { + let mut entries = Vec::new(); + for command in commands { + for LatexGlossaryEntryDefinitionCommand { + name, + label_index, + kind, + } in &LANGUAGE_DATA.glossary_entry_definition_commands + { + if command.name.text() == name && command.has_word(*label_index) { + entries.push(Self { + command: Arc::clone(&command), + label_index: *label_index, + kind: *kind, + }); + } + } + } + entries + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexGlossaryInfo { + pub entries: Vec<LatexGlossaryEntry>, +} + +impl LatexGlossaryInfo { + pub fn parse(commands: &[Arc<LatexCommand>]) -> Self { + Self { + entries: LatexGlossaryEntry::parse(commands), + } + } +} diff --git a/support/texlab/src/syntax/latex/lexer.rs b/support/texlab/src/syntax/latex/lexer.rs new file mode 100644 index 0000000000..f753d7afce --- /dev/null +++ b/support/texlab/src/syntax/latex/lexer.rs @@ -0,0 +1,179 @@ +use super::ast::{LatexToken, LatexTokenKind}; +use crate::syntax::text::CharStream; + +pub struct LatexLexer<'a> { + stream: CharStream<'a>, +} + +impl<'a> LatexLexer<'a> { + pub fn new(text: &'a str) -> Self { + LatexLexer { + stream: CharStream::new(text), + } + } + + fn single_char(&mut self, kind: LatexTokenKind) -> LatexToken { + self.stream.start_span(); + self.stream.next(); + let span = self.stream.end_span(); + LatexToken::new(span, kind) + } + + fn math(&mut self) -> LatexToken { + self.stream.start_span(); + self.stream.next(); + if self.stream.satifies(|c| *c == '$') { + self.stream.next(); + } + let span = self.stream.end_span(); + LatexToken::new(span, LatexTokenKind::Math) + } + + fn command(&mut self) -> LatexToken { + let span = self.stream.command(); + LatexToken::new(span, LatexTokenKind::Command) + } + + fn word(&mut self) -> LatexToken { + self.stream.start_span(); + self.stream.next(); + while self.stream.satifies(|c| is_word_char(*c)) { + self.stream.next(); + } + + let span = self.stream.end_span(); + LatexToken::new(span, LatexTokenKind::Word) + } +} + +impl<'a> Iterator for LatexLexer<'a> { + type Item = LatexToken; + + fn next(&mut self) -> Option<LatexToken> { + loop { + match self.stream.peek() { + Some('%') => { + self.stream.skip_rest_of_line(); + } + Some('{') => { + return Some(self.single_char(LatexTokenKind::BeginGroup)); + } + Some('}') => { + return Some(self.single_char(LatexTokenKind::EndGroup)); + } + Some('[') => { + return Some(self.single_char(LatexTokenKind::BeginOptions)); + } + Some(']') => { + return Some(self.single_char(LatexTokenKind::EndOptions)); + } + Some('$') => { + return Some(self.math()); + } + Some(',') => { + return Some(self.single_char(LatexTokenKind::Comma)); + } + Some('\\') => { + return Some(self.command()); + } + Some(c) => { + if c.is_whitespace() { + self.stream.next(); + } else { + return Some(self.word()); + } + } + None => { + return None; + } + } + } + } +} + +fn is_word_char(c: char) -> bool { + !c.is_whitespace() + && c != '%' + && c != '{' + && c != '}' + && c != '[' + && c != ']' + && c != '\\' + && c != '$' + && c != ',' +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::syntax::text::Span; + use lsp_types::{Position, Range}; + + fn verify<'a>( + lexer: &mut LatexLexer<'a>, + line: u64, + character: u64, + text: &str, + kind: LatexTokenKind, + ) { + let start = Position::new(line, character); + let end = Position::new(line, character + text.chars().count() as u64); + let range = Range::new(start, end); + let span = Span::new(range, text.to_owned()); + let token = LatexToken::new(span, kind); + assert_eq!(Some(token), lexer.next()); + } + + #[test] + fn test_word() { + let mut lexer = LatexLexer::new("foo bar baz"); + verify(&mut lexer, 0, 0, "foo", LatexTokenKind::Word); + verify(&mut lexer, 0, 4, "bar", LatexTokenKind::Word); + verify(&mut lexer, 0, 8, "baz", LatexTokenKind::Word); + assert_eq!(None, lexer.next()); + } + + #[test] + fn test_command() { + let mut lexer = LatexLexer::new("\\foo\\bar@baz\n\\foo*"); + verify(&mut lexer, 0, 0, "\\foo", LatexTokenKind::Command); + verify(&mut lexer, 0, 4, "\\bar@baz", LatexTokenKind::Command); + verify(&mut lexer, 1, 0, "\\foo*", LatexTokenKind::Command); + assert_eq!(None, lexer.next()); + } + + #[test] + fn test_escape_sequence() { + let mut lexer = LatexLexer::new("\\%\\**"); + verify(&mut lexer, 0, 0, "\\%", LatexTokenKind::Command); + verify(&mut lexer, 0, 2, "\\*", LatexTokenKind::Command); + verify(&mut lexer, 0, 4, "*", LatexTokenKind::Word); + assert_eq!(None, lexer.next()); + } + + #[test] + fn test_group_delimiter() { + let mut lexer = LatexLexer::new("{}[]"); + verify(&mut lexer, 0, 0, "{", LatexTokenKind::BeginGroup); + verify(&mut lexer, 0, 1, "}", LatexTokenKind::EndGroup); + verify(&mut lexer, 0, 2, "[", LatexTokenKind::BeginOptions); + verify(&mut lexer, 0, 3, "]", LatexTokenKind::EndOptions); + assert_eq!(None, lexer.next()); + } + + #[test] + fn test_math() { + let mut lexer = LatexLexer::new("$$ $ $"); + verify(&mut lexer, 0, 0, "$$", LatexTokenKind::Math); + verify(&mut lexer, 0, 3, "$", LatexTokenKind::Math); + verify(&mut lexer, 0, 5, "$", LatexTokenKind::Math); + assert_eq!(None, lexer.next()); + } + + #[test] + fn test_line_comment() { + let mut lexer = LatexLexer::new(" %foo \nfoo"); + verify(&mut lexer, 1, 0, "foo", LatexTokenKind::Word); + assert_eq!(None, lexer.next()); + } +} diff --git a/support/texlab/src/syntax/latex/math.rs b/support/texlab/src/syntax/latex/math.rs new file mode 100644 index 0000000000..c234232c14 --- /dev/null +++ b/support/texlab/src/syntax/latex/math.rs @@ -0,0 +1,199 @@ +use super::ast::*; +use crate::syntax::language::*; +use crate::syntax::text::SyntaxNode; +use lsp_types::Range; +use std::sync::Arc; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexEquation { + pub left: Arc<LatexCommand>, + pub right: Arc<LatexCommand>, +} + +impl LatexEquation { + fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> { + let mut equations = Vec::new(); + let mut left = None; + for command in commands { + let name = command.name.text(); + if name == "\\[" || name == "\\(" { + left = Some(command); + } else if name == "\\]" || name == "\\)" { + if let Some(begin) = left { + equations.push(Self { + left: Arc::clone(&begin), + right: Arc::clone(&command), + }); + left = None; + } + } + } + equations + } +} + +impl SyntaxNode for LatexEquation { + fn range(&self) -> Range { + Range::new(self.left.start(), self.right.end()) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexInline { + pub left: Arc<LatexMath>, + pub right: Arc<LatexMath>, +} + +impl LatexInline { + fn parse(root: Arc<LatexRoot>) -> Vec<Self> { + let mut analyzer = LatexInlineAnalyzer::default(); + analyzer.visit_root(root); + analyzer.inlines + } +} + +impl SyntaxNode for LatexInline { + fn range(&self) -> Range { + Range::new(self.left.start(), self.right.end()) + } +} + +#[derive(Debug, Default)] +struct LatexInlineAnalyzer { + inlines: Vec<LatexInline>, + left: Option<Arc<LatexMath>>, +} + +impl LatexVisitor for LatexInlineAnalyzer { + fn visit_root(&mut self, root: Arc<LatexRoot>) { + LatexWalker::walk_root(self, root); + } + + fn visit_group(&mut self, group: Arc<LatexGroup>) { + LatexWalker::walk_group(self, group); + } + + fn visit_command(&mut self, command: Arc<LatexCommand>) { + LatexWalker::walk_command(self, command); + } + + fn visit_text(&mut self, text: Arc<LatexText>) { + LatexWalker::walk_text(self, text); + } + + fn visit_comma(&mut self, comma: Arc<LatexComma>) { + LatexWalker::walk_comma(self, comma); + } + + fn visit_math(&mut self, math: Arc<LatexMath>) { + if let Some(left) = &self.left { + let inline = LatexInline { + left: Arc::clone(&left), + right: Arc::clone(&math), + }; + self.inlines.push(inline); + self.left = None; + } else { + self.left = Some(Arc::clone(&math)); + } + LatexWalker::walk_math(self, math); + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexMathOperator { + pub command: Arc<LatexCommand>, + pub definition: Arc<LatexCommand>, + pub definition_index: usize, + pub implementation_index: usize, +} + +impl LatexMathOperator { + fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> { + let mut operators = Vec::new(); + for command in commands { + for LatexMathOperatorCommand { + name, + definition_index, + implementation_index, + } in &LANGUAGE_DATA.math_operator_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 { + operators.push(Self { + command: Arc::clone(command), + definition: Arc::clone(definition), + definition_index: *definition_index, + implementation_index: *implementation_index, + }) + } + } + } + } + operators + } +} + +impl SyntaxNode for LatexMathOperator { + fn range(&self) -> Range { + self.command.range() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexTheoremDefinition { + pub command: Arc<LatexCommand>, + pub index: usize, +} + +impl LatexTheoremDefinition { + pub fn name(&self) -> &LatexToken { + self.command.extract_word(self.index).unwrap() + } + + fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> { + let mut definitions = Vec::new(); + for command in commands { + for LatexTheoremDefinitionCommand { name, index } in + &LANGUAGE_DATA.theorem_definition_commands + { + if command.name.text() == name && command.has_word(*index) { + definitions.push(Self { + command: Arc::clone(&command), + index: *index, + }); + } + } + } + definitions + } +} + +impl SyntaxNode for LatexTheoremDefinition { + fn range(&self) -> Range { + self.command.range() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexMathInfo { + pub equations: Vec<LatexEquation>, + pub inlines: Vec<LatexInline>, + pub operators: Vec<LatexMathOperator>, + pub theorem_definitions: Vec<LatexTheoremDefinition>, +} + +impl LatexMathInfo { + pub fn parse(root: Arc<LatexRoot>, commands: &[Arc<LatexCommand>]) -> Self { + Self { + equations: LatexEquation::parse(commands), + inlines: LatexInline::parse(root), + operators: LatexMathOperator::parse(commands), + theorem_definitions: LatexTheoremDefinition::parse(commands), + } + } +} diff --git a/support/texlab/src/syntax/latex/mod.rs b/support/texlab/src/syntax/latex/mod.rs new file mode 100644 index 0000000000..dc24682752 --- /dev/null +++ b/support/texlab/src/syntax/latex/mod.rs @@ -0,0 +1,358 @@ +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>>, +} + +impl LatexCommandAnalyzer { + fn parse(root: Arc<LatexRoot>) -> Vec<Arc<LatexCommand>> { + let mut analyzer = Self::default(); + analyzer.visit_root(root); + analyzer.commands + } +} + +impl LatexVisitor for LatexCommandAnalyzer { + fn visit_root(&mut self, root: Arc<LatexRoot>) { + LatexWalker::walk_root(self, root); + } + + fn visit_group(&mut self, group: Arc<LatexGroup>) { + LatexWalker::walk_group(self, group); + } + + fn visit_command(&mut self, command: Arc<LatexCommand>) { + self.commands.push(Arc::clone(&command)); + LatexWalker::walk_command(self, command); + } + + fn visit_text(&mut self, text: Arc<LatexText>) { + LatexWalker::walk_text(self, text); + } + + fn visit_comma(&mut self, comma: Arc<LatexComma>) { + LatexWalker::walk_comma(self, comma); + } + + fn visit_math(&mut self, math: Arc<LatexMath>) { + LatexWalker::walk_math(self, math); + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexCitation { + pub command: Arc<LatexCommand>, + pub index: usize, +} + +impl LatexCitation { + pub fn keys(&self) -> Vec<&LatexToken> { + self.command.extract_comma_separated_words(0) + } + + 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, + }); + } + } + } + citations + } +} + +impl SyntaxNode for LatexCitation { + fn range(&self) -> Range { + self.command.range() + } +} + +#[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, +} + +impl LatexInclude { + pub fn paths(&self) -> Vec<&LatexToken> { + self.command.extract_comma_separated_words(self.index) + } + + 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 => (), + } + } + 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); + } + } + } + includes + } + + fn parse_single( + uri: &Uri, + command: &Arc<LatexCommand>, + description: &LatexIncludeCommand, + ) -> Option<Self> { + 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<LatexCommand>, + pub definition: Arc<LatexCommand>, + pub definition_index: usize, + pub implementation: Arc<LatexGroup>, + pub implementation_index: usize, + pub argument_count_index: usize, +} + +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 + } +} + +impl SyntaxNode for LatexCommandDefinition { + fn range(&self) -> Range { + self.command.range() + } +} + +#[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, +} + +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<LatexNode> { + 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<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 + } + + 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 +} diff --git a/support/texlab/src/syntax/latex/parser.rs b/support/texlab/src/syntax/latex/parser.rs new file mode 100644 index 0000000000..40693071c2 --- /dev/null +++ b/support/texlab/src/syntax/latex/parser.rs @@ -0,0 +1,135 @@ +use super::ast::*; +use std::iter::Peekable; +use std::sync::Arc; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum LatexScope { + Root, + Group, + Options, +} + +pub struct LatexParser<I: Iterator<Item = LatexToken>> { + tokens: Peekable<I>, +} + +impl<I: Iterator<Item = LatexToken>> LatexParser<I> { + pub fn new(tokens: I) -> Self { + LatexParser { + tokens: tokens.peekable(), + } + } + + pub fn root(&mut self) -> LatexRoot { + let children = self.content(LatexScope::Root); + LatexRoot::new(children) + } + + fn content(&mut self, scope: LatexScope) -> Vec<LatexContent> { + let mut children = Vec::new(); + while let Some(ref token) = self.tokens.peek() { + match token.kind { + LatexTokenKind::Word | LatexTokenKind::BeginOptions => { + children.push(LatexContent::Text(self.text(scope))); + } + LatexTokenKind::Command => { + children.push(LatexContent::Command(self.command())); + } + LatexTokenKind::Comma => { + let node = LatexComma::new(self.tokens.next().unwrap()); + children.push(LatexContent::Comma(Arc::new(node))); + } + LatexTokenKind::Math => { + children.push(LatexContent::Math(self.math())); + } + LatexTokenKind::BeginGroup => { + children.push(LatexContent::Group(self.group(LatexGroupKind::Group))); + } + LatexTokenKind::EndGroup => { + if scope == LatexScope::Root { + self.tokens.next(); + } else { + return children; + } + } + LatexTokenKind::EndOptions => { + if scope == LatexScope::Options { + return children; + } else { + children.push(LatexContent::Text(self.text(scope))); + } + } + } + } + children + } + + fn command(&mut self) -> Arc<LatexCommand> { + let name = self.tokens.next().unwrap(); + + let mut options = Vec::new(); + let mut args = Vec::new(); + while let Some(token) = self.tokens.peek() { + match token.kind { + LatexTokenKind::BeginGroup => { + args.push(self.group(LatexGroupKind::Group)); + } + LatexTokenKind::BeginOptions => { + options.push(self.group(LatexGroupKind::Options)); + } + _ => { + break; + } + } + } + Arc::new(LatexCommand::new(name, options, args)) + } + + fn group(&mut self, kind: LatexGroupKind) -> Arc<LatexGroup> { + let left = self.tokens.next().unwrap(); + let scope = match kind { + LatexGroupKind::Group => LatexScope::Group, + LatexGroupKind::Options => LatexScope::Options, + }; + let children = self.content(scope); + let right_kind = match kind { + LatexGroupKind::Group => LatexTokenKind::EndGroup, + LatexGroupKind::Options => LatexTokenKind::EndOptions, + }; + + let right = if self.next_of_kind(right_kind) { + self.tokens.next() + } else { + None + }; + + Arc::new(LatexGroup::new(left, children, right, kind)) + } + + fn text(&mut self, scope: LatexScope) -> Arc<LatexText> { + let mut words = Vec::new(); + while let Some(ref token) = self.tokens.peek() { + let kind = token.kind; + let opts = kind == LatexTokenKind::EndOptions && scope != LatexScope::Options; + if kind == LatexTokenKind::Word || kind == LatexTokenKind::BeginOptions || opts { + words.push(self.tokens.next().unwrap()); + } else { + break; + } + } + Arc::new(LatexText::new(words)) + } + + fn math(&mut self) -> Arc<LatexMath> { + let token = self.tokens.next().unwrap(); + Arc::new(LatexMath::new(token)) + } + + fn next_of_kind(&mut self, kind: LatexTokenKind) -> bool { + if let Some(ref token) = self.tokens.peek() { + token.kind == kind + } else { + false + } + } +} diff --git a/support/texlab/src/syntax/latex/printer.rs b/support/texlab/src/syntax/latex/printer.rs new file mode 100644 index 0000000000..ce03778033 --- /dev/null +++ b/support/texlab/src/syntax/latex/printer.rs @@ -0,0 +1,77 @@ +use super::ast::*; +use crate::syntax::text::*; +use lsp_types::Position; +use std::sync::Arc; + +#[derive(Debug)] +pub struct LatexPrinter { + pub output: String, + position: Position, +} + +impl LatexPrinter { + pub fn new(start_position: Position) -> Self { + Self { + output: String::new(), + position: start_position, + } + } + + fn synchronize(&mut self, position: Position) { + while self.position.line < position.line { + self.output.push('\n'); + self.position.line += 1; + } + + while self.position.character < position.character { + self.output.push(' '); + self.position.character += 1; + } + } + + fn print_token(&mut self, token: &LatexToken) { + self.synchronize(token.start()); + self.output.push_str(token.text()); + self.position.character += token.end().character - token.start().character; + self.synchronize(token.end()); + } +} + +impl LatexVisitor for LatexPrinter { + fn visit_root(&mut self, root: Arc<LatexRoot>) { + for child in &root.children { + child.accept(self); + } + } + + fn visit_group(&mut self, group: Arc<LatexGroup>) { + self.print_token(&group.left); + for child in &group.children { + child.accept(self); + } + if let Some(right) = &group.right { + self.print_token(right); + } + } + + fn visit_command(&mut self, command: Arc<LatexCommand>) { + self.print_token(&command.name); + for group in &command.groups { + self.visit_group(Arc::clone(&group)); + } + } + + fn visit_text(&mut self, text: Arc<LatexText>) { + for word in &text.words { + self.print_token(word); + } + } + + fn visit_comma(&mut self, comma: Arc<LatexComma>) { + self.print_token(&comma.token); + } + + fn visit_math(&mut self, math: Arc<LatexMath>) { + self.print_token(&math.token) + } +} diff --git a/support/texlab/src/syntax/latex/structure.rs b/support/texlab/src/syntax/latex/structure.rs new file mode 100644 index 0000000000..79c9aac216 --- /dev/null +++ b/support/texlab/src/syntax/latex/structure.rs @@ -0,0 +1,250 @@ +use super::ast::*; +use crate::range::RangeExt; +use crate::syntax::language::*; +use crate::syntax::text::{CharStream, SyntaxNode}; +use itertools::Itertools; +use lsp_types::Range; +use std::sync::Arc; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexSection { + pub command: Arc<LatexCommand>, + pub index: usize, + pub level: i32, + pub prefix: &'static str, +} + +impl LatexSection { + fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> { + let mut sections = Vec::new(); + for command in commands { + for LatexSectionCommand { + name, + index, + level, + prefix, + } in &LANGUAGE_DATA.section_commands + { + if command.name.text() == name && command.args.len() > *index { + sections.push(Self { + command: Arc::clone(command), + index: *index, + level: *level, + prefix: prefix.as_ref(), + }) + } + } + } + sections + } + + pub fn extract_text(&self, text: &str) -> Option<String> { + let content = &self.command.args[self.index]; + let right = content.right.as_ref()?; + let range = Range::new_simple( + content.left.start().line, + content.left.start().character + 1, + right.end().line, + right.end().character - 1, + ); + Some(CharStream::extract(&text, range)) + } +} + +impl SyntaxNode for LatexSection { + fn range(&self) -> Range { + self.command.range() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexLabel { + pub command: Arc<LatexCommand>, + index: usize, + pub kind: LatexLabelKind, +} + +impl LatexLabel { + pub fn names(&self) -> Vec<&LatexToken> { + self.command.extract_comma_separated_words(self.index) + } + + fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> { + let mut labels = Vec::new(); + for command in commands { + for LatexLabelCommand { name, index, kind } in &LANGUAGE_DATA.label_commands { + if command.name.text() == name && command.has_comma_separated_words(*index) { + labels.push(Self { + command: Arc::clone(command), + index: *index, + kind: *kind, + }); + } + } + } + labels + } +} + +impl SyntaxNode for LatexLabel { + fn range(&self) -> Range { + self.command.range + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexLabelNumbering { + pub command: Arc<LatexCommand>, + pub number: String, +} + +impl LatexLabelNumbering { + pub fn name(&self) -> &LatexToken { + self.command.extract_word(0).unwrap() + } + + fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> { + commands + .iter() + .map(Arc::clone) + .filter_map(Self::parse_single) + .collect() + } + + fn parse_single(command: Arc<LatexCommand>) -> Option<Self> { + #[derive(Debug, Default)] + struct FirstText { + text: Option<Arc<LatexText>>, + } + + impl LatexVisitor for FirstText { + fn visit_root(&mut self, root: Arc<LatexRoot>) { + LatexWalker::walk_root(self, root); + } + + fn visit_group(&mut self, group: Arc<LatexGroup>) { + LatexWalker::walk_group(self, group); + } + + fn visit_command(&mut self, command: Arc<LatexCommand>) { + LatexWalker::walk_command(self, command); + } + + fn visit_text(&mut self, text: Arc<LatexText>) { + if self.text.is_none() { + self.text = Some(text); + } + } + + fn visit_comma(&mut self, comma: Arc<LatexComma>) { + LatexWalker::walk_comma(self, comma); + } + + fn visit_math(&mut self, math: Arc<LatexMath>) { + LatexWalker::walk_math(self, math); + } + } + + if command.name.text() != "\\newlabel" || !command.has_word(0) { + return None; + } + + let mut analyzer = FirstText::default(); + analyzer.visit_group(Arc::clone(command.args.get(1)?)); + let number = analyzer + .text? + .words + .iter() + .map(|word| word.text()) + .join(" "); + + Some(Self { + command: Arc::clone(&command), + number, + }) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexCaption { + pub command: Arc<LatexCommand>, + pub index: usize, +} + +impl LatexCaption { + fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> { + let mut captions = Vec::new(); + for command in commands { + if command.name.text() == "\\caption" && !command.args.is_empty() { + captions.push(Self { + command: Arc::clone(&command), + index: 0, + }); + } + } + captions + } +} + +impl SyntaxNode for LatexCaption { + fn range(&self) -> Range { + self.command.range() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexItem { + pub command: Arc<LatexCommand>, +} + +impl LatexItem { + fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> { + let mut items = Vec::new(); + for command in commands { + if command.name.text() == "\\item" { + items.push(Self { + command: Arc::clone(&command), + }); + } + } + items + } + + pub fn name(&self) -> Option<String> { + if let Some(options) = self.command.options.get(0) { + if options.children.len() == 1 { + if let LatexContent::Text(text) = &options.children[0] { + return Some(text.words.iter().map(|word| word.text()).join(" ")); + } + } + } + None + } +} + +impl SyntaxNode for LatexItem { + fn range(&self) -> Range { + self.command.range() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexStructureInfo { + pub sections: Vec<LatexSection>, + pub labels: Vec<LatexLabel>, + pub label_numberings: Vec<LatexLabelNumbering>, + pub captions: Vec<LatexCaption>, + pub items: Vec<LatexItem>, +} + +impl LatexStructureInfo { + pub fn parse(commands: &[Arc<LatexCommand>]) -> Self { + Self { + sections: LatexSection::parse(commands), + labels: LatexLabel::parse(commands), + label_numberings: LatexLabelNumbering::parse(commands), + captions: LatexCaption::parse(commands), + items: LatexItem::parse(commands), + } + } +} |