diff options
Diffstat (limited to 'support/texlab/src/syntax')
154 files changed, 7770 insertions, 4616 deletions
diff --git a/support/texlab/src/syntax/bibtex.rs b/support/texlab/src/syntax/bibtex.rs new file mode 100644 index 0000000000..c6e8bb3760 --- /dev/null +++ b/support/texlab/src/syntax/bibtex.rs @@ -0,0 +1,34 @@ +mod cst; +mod kind; +mod lexer; +mod parser; + +pub use self::{ + cst::*, + kind::SyntaxKind::{self, *}, + parser::{parse, Parse}, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Language {} + +impl cstree::Language for Language { + type Kind = SyntaxKind; + + fn kind_from_raw(raw: cstree::SyntaxKind) -> Self::Kind { + assert!(raw.0 <= ROOT as u16); + unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) } + } + + fn kind_to_raw(kind: Self::Kind) -> cstree::SyntaxKind { + kind.into() + } +} + +pub type SyntaxNode = cstree::ResolvedNode<Language>; + +pub type SyntaxToken = cstree::ResolvedToken<Language>; + +pub type SyntaxElement = cstree::ResolvedElement<Language>; + +pub type SyntaxElementRef<'a> = cstree::ResolvedElementRef<'a, Language>; diff --git a/support/texlab/src/syntax/bibtex/ast.rs b/support/texlab/src/syntax/bibtex/ast.rs deleted file mode 100644 index 77ee98e26d..0000000000 --- a/support/texlab/src/syntax/bibtex/ast.rs +++ /dev/null @@ -1,431 +0,0 @@ -use crate::{ - protocol::{Position, Range, RangeExt}, - syntax::{Span, SyntaxNode}, -}; -use itertools::Itertools; -use petgraph::graph::{Graph, NodeIndex}; -use serde::{Deserialize, Serialize}; -use std::fmt; - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -pub enum TokenKind { - PreambleKind, - StringKind, - EntryKind, - Word, - Command, - Assign, - Comma, - Concat, - Quote, - BeginBrace, - EndBrace, - BeginParen, - EndParen, -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Token { - pub span: Span, - pub kind: TokenKind, -} - -impl SyntaxNode for Token { - fn range(&self) -> Range { - self.span.range() - } -} - -impl Token { - pub fn new(span: Span, kind: TokenKind) -> Self { - Self { span, kind } - } - - pub fn text(&self) -> &str { - &self.span.text - } -} - -#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -pub struct Root { - pub range: Range, -} - -impl SyntaxNode for Root { - fn range(&self) -> Range { - self.range - } -} - -impl fmt::Debug for Root { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Root") - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Comment { - pub token: Token, -} - -impl SyntaxNode for Comment { - fn range(&self) -> Range { - self.token.range() - } -} - -impl fmt::Debug for Comment { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Comment({})", self.token.text()) - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Preamble { - pub range: Range, - pub ty: Token, - pub left: Option<Token>, - pub right: Option<Token>, -} - -impl SyntaxNode for Preamble { - fn range(&self) -> Range { - self.range - } -} - -impl fmt::Debug for Preamble { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Preamble") - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct String { - pub range: Range, - pub ty: Token, - pub left: Option<Token>, - pub name: Option<Token>, - pub assign: Option<Token>, - pub right: Option<Token>, -} - -impl SyntaxNode for String { - fn range(&self) -> Range { - self.range - } -} - -impl fmt::Debug for String { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "String({:?})", self.name.as_ref().map(Token::text)) - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Entry { - pub range: Range, - pub ty: Token, - pub left: Option<Token>, - pub key: Option<Token>, - pub comma: Option<Token>, - pub right: Option<Token>, -} - -impl SyntaxNode for Entry { - fn range(&self) -> Range { - self.range - } -} - -impl fmt::Debug for Entry { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Entry({:?})", self.key.as_ref().map(Token::text)) - } -} - -impl Entry { - pub fn is_comment(&self) -> bool { - self.ty.text().to_lowercase() == "@comment" - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Field { - pub range: Range, - pub name: Token, - pub assign: Option<Token>, - pub comma: Option<Token>, -} - -impl SyntaxNode for Field { - fn range(&self) -> Range { - self.range - } -} - -impl fmt::Debug for Field { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Field({})", self.name.text()) - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Word { - pub token: Token, -} - -impl SyntaxNode for Word { - fn range(&self) -> Range { - self.token.range() - } -} - -impl fmt::Debug for Word { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Word({})", self.token.text()) - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Command { - pub token: Token, -} - -impl SyntaxNode for Command { - fn range(&self) -> Range { - self.token.range() - } -} - -impl fmt::Debug for Command { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Command({})", self.token.text()) - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct QuotedContent { - pub range: Range, - pub left: Token, - pub right: Option<Token>, -} - -impl SyntaxNode for QuotedContent { - fn range(&self) -> Range { - self.range - } -} - -impl fmt::Debug for QuotedContent { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "QuotedContent") - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct BracedContent { - pub range: Range, - pub left: Token, - pub right: Option<Token>, -} - -impl SyntaxNode for BracedContent { - fn range(&self) -> Range { - self.range - } -} - -impl fmt::Debug for BracedContent { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "BracedContent") - } -} - -#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Concat { - pub range: Range, - pub operator: Token, -} - -impl SyntaxNode for Concat { - fn range(&self) -> Range { - self.range - } -} - -impl fmt::Debug for Concat { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Concat") - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub enum Node { - Root(Root), - Comment(Comment), - Preamble(Box<Preamble>), - String(Box<String>), - Entry(Box<Entry>), - Field(Box<Field>), - Word(Word), - Command(Command), - QuotedContent(QuotedContent), - BracedContent(BracedContent), - Concat(Concat), -} - -impl SyntaxNode for Node { - fn range(&self) -> Range { - match self { - Self::Root(root) => root.range(), - Self::Comment(comment) => comment.range(), - Self::Preamble(preamble) => preamble.range(), - Self::String(string) => string.range(), - Self::Entry(entry) => entry.range(), - Self::Field(field) => field.range(), - Self::Word(word) => word.range(), - Self::Command(cmd) => cmd.range(), - Self::QuotedContent(content) => content.range(), - Self::BracedContent(content) => content.range(), - Self::Concat(concat) => concat.range(), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Tree { - pub graph: Graph<Node, ()>, - pub root: NodeIndex, -} - -impl Tree { - pub fn children(&self, parent: NodeIndex) -> impl Iterator<Item = NodeIndex> { - self.graph - .neighbors(parent) - .sorted_by_key(|child| self.graph[*child].start()) - } - - pub fn has_children(&self, parent: NodeIndex) -> bool { - self.children(parent).next().is_some() - } - - pub fn walk<'a, V: Visitor<'a>>(&'a self, visitor: &mut V, parent: NodeIndex) { - for child in self.children(parent) { - visitor.visit(self, child); - } - } - - pub fn find(&self, pos: Position) -> Vec<NodeIndex> { - let mut finder = Finder::new(pos); - finder.visit(self, self.root); - finder.results - } - - pub fn as_preamble(&self, node: NodeIndex) -> Option<&Preamble> { - if let Node::Preamble(preamble) = &self.graph[node] { - Some(preamble) - } else { - None - } - } - - pub fn as_string(&self, node: NodeIndex) -> Option<&String> { - if let Node::String(string) = &self.graph[node] { - Some(string) - } else { - None - } - } - - pub fn as_entry(&self, node: NodeIndex) -> Option<&Entry> { - if let Node::Entry(entry) = &self.graph[node] { - Some(entry) - } else { - None - } - } - - pub fn as_field(&self, node: NodeIndex) -> Option<&Field> { - if let Node::Field(field) = &self.graph[node] { - Some(field) - } else { - None - } - } - - pub fn as_command(&self, node: NodeIndex) -> Option<&Command> { - if let Node::Command(cmd) = &self.graph[node] { - Some(cmd) - } else { - None - } - } - - pub fn as_word(&self, node: NodeIndex) -> Option<&Word> { - if let Node::Word(word) = &self.graph[node] { - Some(word) - } else { - None - } - } - - pub fn entry_by_key(&self, key: &str) -> Option<NodeIndex> { - for node in self.children(self.root) { - if let Some(entry) = self.as_entry(node) { - if entry.key.as_ref().map(Token::text) == Some(key) { - return Some(node); - } - } - } - None - } - - pub fn field_by_name(&self, parent: NodeIndex, name: &str) -> Option<NodeIndex> { - let name = name.to_lowercase(); - self.as_entry(parent)?; - for node in self.children(parent) { - if let Some(field) = self.as_field(node) { - if field.name.text() == name { - return Some(node); - } - } - } - None - } - - pub fn crossref(&self, entry: NodeIndex) -> Option<NodeIndex> { - let field = self.field_by_name(entry, "crossref")?; - let content = self.children(field).next()?; - let key = self.as_word(self.children(content).next()?)?; - self.entry_by_key(key.token.text()) - } -} - -pub trait Visitor<'a> { - fn visit(&mut self, tree: &'a Tree, node: NodeIndex); -} - -#[derive(Debug)] -struct Finder { - position: Position, - results: Vec<NodeIndex>, -} - -impl Finder { - fn new(position: Position) -> Self { - Self { - position, - results: Vec::new(), - } - } -} - -impl<'a> Visitor<'a> for Finder { - fn visit(&mut self, tree: &'a Tree, node: NodeIndex) { - if tree.graph[node].range().contains(self.position) { - self.results.push(node); - tree.walk(self, node); - } - } -} diff --git a/support/texlab/src/syntax/bibtex/cst.rs b/support/texlab/src/syntax/bibtex/cst.rs new file mode 100644 index 0000000000..7f824681dc --- /dev/null +++ b/support/texlab/src/syntax/bibtex/cst.rs @@ -0,0 +1,212 @@ +use cstree::TextRange; + +use crate::syntax::CstNode; + +use super::{Language, SyntaxKind::*, SyntaxNode, SyntaxToken}; + +macro_rules! cst_node { + ($name:ident, $($kind:pat),+) => { + #[derive(Clone)] + #[repr(transparent)] + pub struct $name<'a>(&'a SyntaxNode); + + impl<'a> CstNode<'a> for $name<'a> { + type Lang = Language; + + fn cast(node: &'a cstree::ResolvedNode<Self::Lang>) -> Option<Self> + where + Self: Sized, + { + match node.kind() { + $($kind => Some(Self(node)),)+ + _ => None, + } + } + + fn syntax(&self) -> &'a cstree::ResolvedNode<Self::Lang> { + &self.0 + } + + fn small_range(&self) -> TextRange { + let full_range = self.syntax().text_range(); + let start = full_range.start(); + let mut token = self.syntax().last_token(); + while let Some(current) = token { + if !matches!(current.kind(), WHITESPACE | JUNK) { + return TextRange::new(start, current.text_range().end()); + } + token = current.prev_token(); + } + TextRange::new(start, start) + } + } + }; +} + +pub trait HasCurly<'a>: CstNode<'a, Lang = Language> { + fn left_curly(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == L_CURLY.into()) + } + + fn right_curly(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == R_CURLY.into()) + } +} + +pub trait HasQuotes<'a>: CstNode<'a, Lang = Language> { + fn left_quote(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == QUOTE.into()) + } + + fn right_quote(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .filter(|node| node.kind() == QUOTE.into()) + .nth(1) + } +} + +pub trait HasDelimiters<'a>: CstNode<'a, Lang = Language> { + fn left_delimiter(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| matches!(node.kind(), L_CURLY | L_PAREN)) + } + + fn right_delimiter(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| matches!(node.kind(), R_CURLY | R_PAREN)) + } +} + +pub trait HasType<'a>: CstNode<'a, Lang = Language> { + fn ty(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| { + matches!( + node.kind(), + PREAMBLE_TYPE | STRING_TYPE | COMMENT_TYPE | ENTRY_TYPE + ) + }) + } +} + +cst_node!(Root, ROOT); + +cst_node!(Junk, JUNK); + +cst_node!(Comment, COMMENT); + +impl<'a> HasType<'a> for Comment<'a> {} + +cst_node!(Preamble, PREAMBLE); + +impl<'a> HasType<'a> for Preamble<'a> {} + +impl<'a> HasDelimiters<'a> for Preamble<'a> {} + +impl<'a> Preamble<'a> { + pub fn value(&self) -> Option<Value<'a>> { + self.syntax().children().find_map(Value::cast) + } +} + +cst_node!(String, STRING); + +impl<'a> HasType<'a> for String<'a> {} + +impl<'a> HasDelimiters<'a> for String<'a> {} + +impl<'a> String<'a> { + pub fn name(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == WORD) + } + + pub fn equality_sign(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == EQUALITY_SIGN) + } + + pub fn value(&self) -> Option<Value<'a>> { + self.syntax().children().find_map(Value::cast) + } +} + +cst_node!(Entry, ENTRY); + +impl<'a> HasType<'a> for Entry<'a> {} + +impl<'a> HasDelimiters<'a> for Entry<'a> {} + +impl<'a> Entry<'a> { + pub fn key(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == WORD) + } + + pub fn fields(&self) -> impl Iterator<Item = Field<'a>> { + self.syntax().children().filter_map(Field::cast) + } +} + +cst_node!(Field, FIELD); + +impl<'a> Field<'a> { + pub fn name(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == WORD) + } + + pub fn equality_sign(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == EQUALITY_SIGN) + } + + pub fn value(&self) -> Option<Value<'a>> { + self.syntax().children().find_map(Value::cast) + } +} + +cst_node!(Value, VALUE); + +impl<'a> Value<'a> { + pub fn tokens(&self) -> impl Iterator<Item = Token<'a>> { + self.syntax().children().filter_map(Token::cast) + } +} + +cst_node!(Token, TOKEN); + +cst_node!(BraceGroup, BRACE_GROUP); + +impl<'a> HasCurly<'a> for BraceGroup<'a> {} + +cst_node!(QuoteGroup, QUOTE_GROUP); + +impl<'a> HasQuotes<'a> for QuoteGroup<'a> {} diff --git a/support/texlab/src/syntax/bibtex/formatter.rs b/support/texlab/src/syntax/bibtex/formatter.rs deleted file mode 100644 index d4965e7cd4..0000000000 --- a/support/texlab/src/syntax/bibtex/formatter.rs +++ /dev/null @@ -1,329 +0,0 @@ -use super::ast::*; -use crate::{protocol::BibtexFormattingOptions, syntax::text::SyntaxNode}; -use petgraph::graph::NodeIndex; -use std::{i32, string::String as StdString}; - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct FormattingParams<'a> { - pub tab_size: usize, - pub insert_spaces: bool, - pub options: &'a BibtexFormattingOptions, -} - -impl<'a> FormattingParams<'a> { - fn line_length(self) -> i32 { - let line_length = self.options.line_length.unwrap_or(120); - if line_length <= 0 { - i32::MAX - } else { - line_length - } - } - - fn indent(self) -> StdString { - if self.insert_spaces { - let mut buffer = StdString::new(); - for _ in 0..self.tab_size { - buffer.push(' '); - } - buffer - } else { - "\t".into() - } - } -} - -#[derive(Debug, Clone)] -struct Formatter<'a> { - params: FormattingParams<'a>, - indent: StdString, - output: StdString, - align: Vec<usize>, -} - -impl<'a> Formatter<'a> { - fn new(params: FormattingParams<'a>) -> Self { - Self { - params, - indent: params.indent(), - output: StdString::new(), - align: Vec::new(), - } - } - - fn visit_token_lowercase(&mut self, token: &Token) { - self.output.push_str(token.text().to_lowercase().as_ref()); - } - - fn should_insert_space(previous: &Token, current: &Token) -> bool { - previous.start().line != current.start().line - || previous.end().character < current.start().character - } -} - -impl<'a, 'b> Visitor<'b> for Formatter<'a> { - fn visit(&mut self, tree: &'b Tree, node: NodeIndex) { - match &tree.graph[node] { - Node::Root(_) => tree.walk(self, node), - Node::Comment(comment) => self.output.push_str(comment.token.text()), - Node::Preamble(preamble) => { - self.visit_token_lowercase(&preamble.ty); - self.output.push('{'); - if tree.has_children(node) { - self.align.push(self.output.chars().count()); - tree.walk(self, node); - self.output.push('}'); - } - } - Node::String(string) => { - self.visit_token_lowercase(&string.ty); - self.output.push('{'); - if let Some(name) = &string.name { - self.output.push_str(name.text()); - self.output.push_str(" = "); - if tree.has_children(node) { - self.align.push(self.output.chars().count()); - tree.walk(self, node); - self.output.push('}'); - } - } - } - Node::Entry(entry) => { - self.visit_token_lowercase(&entry.ty); - self.output.push('{'); - if let Some(key) = &entry.key { - self.output.push_str(key.text()); - self.output.push(','); - self.output.push('\n'); - tree.walk(self, node); - self.output.push('}'); - } - } - Node::Field(field) => { - self.output.push_str(&self.indent); - self.visit_token_lowercase(&field.name); - self.output.push_str(" = "); - if tree.has_children(node) { - let count = field.name.text().chars().count(); - self.align.push(self.params.tab_size as usize + count + 3); - tree.walk(self, node); - self.output.push(','); - self.output.push('\n'); - } - } - Node::Word(_) - | Node::Command(_) - | Node::BracedContent(_) - | Node::QuotedContent(_) - | Node::Concat(_) => { - let mut analyzer = ContentAnalyzer::default(); - analyzer.visit(tree, node); - let tokens = analyzer.tokens; - self.output.push_str(tokens[0].text()); - - let align = self.align.pop().unwrap_or_default(); - let mut length = align + tokens[0].text().chars().count(); - for i in 1..tokens.len() { - let previous = tokens[i - 1]; - let current = tokens[i]; - let current_length = current.text().chars().count(); - - let insert_space = Self::should_insert_space(previous, current); - let space_length = if insert_space { 1 } else { 0 }; - - if length + current_length + space_length > self.params.line_length() as usize { - self.output.push('\n'); - self.output.push_str(self.indent.as_ref()); - for _ in 0..=align - self.params.tab_size { - self.output.push(' '); - } - length = align; - } else if insert_space { - self.output.push(' '); - length += 1; - } - self.output.push_str(current.text()); - length += current_length; - } - } - } - } -} - -#[derive(Debug, Default)] -struct ContentAnalyzer<'a> { - tokens: Vec<&'a Token>, -} - -impl<'a> Visitor<'a> for ContentAnalyzer<'a> { - fn visit(&mut self, tree: &'a Tree, node: NodeIndex) { - match &tree.graph[node] { - Node::Root(_) - | Node::Comment(_) - | Node::Preamble(_) - | Node::String(_) - | Node::Entry(_) - | Node::Field(_) => tree.walk(self, node), - Node::Word(word) => self.tokens.push(&word.token), - Node::Command(cmd) => self.tokens.push(&cmd.token), - Node::QuotedContent(content) => { - self.tokens.push(&content.left); - tree.walk(self, node); - if let Some(right) = &content.right { - self.tokens.push(right); - } - } - Node::BracedContent(content) => { - self.tokens.push(&content.left); - tree.walk(self, node); - if let Some(right) = &content.right { - self.tokens.push(right); - } - } - Node::Concat(concat) => { - let mut children = tree.children(node); - let left = children.next().unwrap(); - self.visit(tree, left); - self.tokens.push(&concat.operator); - children.for_each(|right| self.visit(tree, right)); - } - } - } -} - -pub fn format(tree: &Tree, node: NodeIndex, params: FormattingParams) -> StdString { - let mut formatter = Formatter::new(params); - formatter.visit(tree, node); - formatter.output -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::syntax::bibtex; - use indoc::indoc; - - fn verify(source: &str, expected: &str, line_length: i32) { - let tree = bibtex::open(source); - let options = BibtexFormattingOptions { - line_length: Some(line_length), - formatter: None, - }; - - let mut children = tree.children(tree.root); - let declaration = children.next().unwrap(); - assert_eq!(children.next(), None); - - let actual = format( - &tree, - declaration, - FormattingParams { - tab_size: 4, - insert_spaces: true, - options: &options, - }, - ); - assert_eq!(actual, expected); - } - - #[test] - fn wrap_long_lines() { - let source = - "@article{foo, bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit.},}"; - let expected = indoc!( - " - @article{foo, - bar = {Lorem ipsum dolor - sit amet, - consectetur - adipiscing elit.}, - }" - ); - verify(source, expected, 30); - } - - #[test] - fn line_length_zero() { - let source = - "@article{foo, bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit.},}"; - let expected = indoc!( - " - @article{foo, - bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit.}, - }" - ); - verify(source, expected, 0); - } - - #[test] - fn trailing_commas() { - let source = "@article{foo, bar = baz}"; - let expected = indoc!( - " - @article{foo, - bar = baz, - }" - ); - verify(source, expected, 30); - } - - #[test] - fn insert_braces() { - let source = "@article{foo, bar = baz,"; - let expected = indoc!( - " - @article{foo, - bar = baz, - }" - ); - verify(source, expected, 30); - } - - #[test] - fn commands() { - let source = "@article{foo, bar = \"\\baz\",}"; - let expected = indoc!( - "@article{foo, - bar = \"\\baz\", - }" - ); - verify(source, expected, 30); - } - - #[test] - fn concatenation() { - let source = "@article{foo, bar = \"baz\" # \"qux\"}"; - let expected = indoc!( - " - @article{foo, - bar = \"baz\" # \"qux\", - }" - ); - verify(source, expected, 30); - } - - #[test] - fn parentheses() { - let source = "@article(foo,)"; - let expected = indoc!( - " - @article{foo, - }" - ); - verify(source, expected, 30); - } - - #[test] - fn string() { - let source = "@string{foo=\"bar\"}"; - let expected = "@string{foo = \"bar\"}"; - verify(source, expected, 30); - } - - #[test] - fn preamble() { - let source = "@preamble{\n\"foo bar baz\"}"; - let expected = "@preamble{\"foo bar baz\"}"; - verify(source, expected, 30); - } -} diff --git a/support/texlab/src/syntax/bibtex/kind.rs b/support/texlab/src/syntax/bibtex/kind.rs new file mode 100644 index 0000000000..fcb26b69bd --- /dev/null +++ b/support/texlab/src/syntax/bibtex/kind.rs @@ -0,0 +1,51 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)] +#[allow(non_camel_case_types)] +#[repr(u16)] +pub enum SyntaxKind { + ERROR = 0, + MISSING, + + WHITESPACE, + PREAMBLE_TYPE, + STRING_TYPE, + COMMENT_TYPE, + ENTRY_TYPE, + WORD, + L_CURLY, + R_CURLY, + L_PAREN, + R_PAREN, + COMMA, + HASH, + QUOTE, + EQUALITY_SIGN, + COMMAND_NAME, + + JUNK, + PREAMBLE, + STRING, + COMMENT, + ENTRY, + FIELD, + VALUE, + TOKEN, + BRACE_GROUP, + QUOTE_GROUP, + ROOT, +} + +impl SyntaxKind { + pub fn is_type(&self) -> bool { + use SyntaxKind::*; + matches!( + self, + PREAMBLE_TYPE | STRING_TYPE | COMMENT_TYPE | ENTRY_TYPE + ) + } +} + +impl From<SyntaxKind> for cstree::SyntaxKind { + fn from(kind: SyntaxKind) -> Self { + Self(kind as u16) + } +} diff --git a/support/texlab/src/syntax/bibtex/lexer.rs b/support/texlab/src/syntax/bibtex/lexer.rs index e089b15864..dff111d645 100644 --- a/support/texlab/src/syntax/bibtex/lexer.rs +++ b/support/texlab/src/syntax/bibtex/lexer.rs @@ -1,173 +1,82 @@ -use super::ast::{Token, TokenKind}; -use crate::syntax::text::CharStream; +use logos::Logos; -#[derive(Debug)] -pub struct Lexer<'a> { - stream: CharStream<'a>, -} +use super::kind::SyntaxKind; -impl<'a> Lexer<'a> { - pub fn new(text: &'a str) -> Self { - Self { - stream: CharStream::new(text), - } - } +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord, Logos)] +#[allow(non_camel_case_types)] +#[repr(u16)] +enum LogosToken { + #[regex(r"\s+")] + WHITESPACE = 2, - fn kind(&mut self) -> Token { - fn is_type_char(c: char) -> bool { - c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' - } + #[regex(r"@[Pp][Rr][Ee][Aa][Mm][Bb][Ll][Ee]")] + PREAMBLE_TYPE, - self.stream.start_span(); - self.stream.next().unwrap(); - while self.stream.satifies(|c| is_type_char(*c)) { - self.stream.next(); - } - let span = self.stream.end_span(); - let kind = match span.text.to_lowercase().as_ref() { - "@preamble" => TokenKind::PreambleKind, - "@string" => TokenKind::StringKind, - _ => TokenKind::EntryKind, - }; - Token::new(span, kind) - } + #[regex(r"@[Ss][Tt][Rr][Ii][Nn][Gg]")] + STRING_TYPE, - fn single_character(&mut self, kind: TokenKind) -> Token { - self.stream.start_span(); - self.stream.next(); - let span = self.stream.end_span(); - Token::new(span, kind) - } + #[regex(r"@[Cc][Oo][Mm][Mm][Ee][Nn][Tt]")] + COMMENT_TYPE, - fn command(&mut self) -> Token { - let span = self.stream.command(); - Token::new(span, TokenKind::Command) - } + #[regex(r"@[!\$\&\*\+\-\./:;<>\?@\[\]\\\^_`\|\~a-zA-Z][!\$\&\*\+\-\./:;<>\?@\[\]\\\^_`\|\~a-zA-Z0-9]*|@")] + ENTRY_TYPE, - fn word(&mut self) -> Token { - fn is_word_char(c: char) -> bool { - !c.is_whitespace() - && c != '@' - && c != '=' - && c != ',' - && c != '#' - && c != '"' - && c != '{' - && c != '}' - && c != '(' - && c != ')' - } + #[regex(r#"[^\s\{\}\(\),#"=\\]+"#)] + #[error] + WORD, - self.stream.start_span(); - while self.stream.satifies(|c| is_word_char(*c)) { - self.stream.next(); - } - let span = self.stream.end_span(); - Token::new(span, TokenKind::Word) - } -} + #[token("{")] + L_CURLY, -impl<'a> Iterator for Lexer<'a> { - type Item = Token; - - fn next(&mut self) -> Option<Token> { - loop { - match self.stream.peek() { - Some('@') => return Some(self.kind()), - Some('=') => return Some(self.single_character(TokenKind::Assign)), - Some(',') => return Some(self.single_character(TokenKind::Comma)), - Some('#') => return Some(self.single_character(TokenKind::Concat)), - Some('"') => return Some(self.single_character(TokenKind::Quote)), - Some('{') => return Some(self.single_character(TokenKind::BeginBrace)), - Some('}') => return Some(self.single_character(TokenKind::EndBrace)), - Some('(') => return Some(self.single_character(TokenKind::BeginParen)), - Some(')') => return Some(self.single_character(TokenKind::EndParen)), - Some('\\') => return Some(self.command()), - Some(c) => { - if c.is_whitespace() { - self.stream.next(); - } else { - return Some(self.word()); - } - } - None => { - return None; - } - } - } - } -} + #[token("}")] + R_CURLY, -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - protocol::{Position, Range}, - syntax::text::Span, - }; - - fn verify<'a>(lexer: &mut Lexer<'a>, line: u64, character: u64, text: &str, kind: TokenKind) { - 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 = Token::new(span, kind); - assert_eq!(Some(token), lexer.next()); - } + #[token("(")] + L_PAREN, - #[test] - fn word() { - let mut lexer = Lexer::new("foo bar baz"); - verify(&mut lexer, 0, 0, "foo", TokenKind::Word); - verify(&mut lexer, 0, 4, "bar", TokenKind::Word); - verify(&mut lexer, 0, 8, "baz", TokenKind::Word); - assert_eq!(None, lexer.next()); - } + #[token(")")] + R_PAREN, - #[test] - fn command() { - let mut lexer = Lexer::new("\\foo\\bar@baz"); - verify(&mut lexer, 0, 0, "\\foo", TokenKind::Command); - verify(&mut lexer, 0, 4, "\\bar@baz", TokenKind::Command); - assert_eq!(None, lexer.next()); - } + #[token(",")] + COMMA, - #[test] - fn escape_sequence() { - let mut lexer = Lexer::new("\\foo*\n\\%\\**"); - verify(&mut lexer, 0, 0, "\\foo*", TokenKind::Command); - verify(&mut lexer, 1, 0, "\\%", TokenKind::Command); - verify(&mut lexer, 1, 2, "\\*", TokenKind::Command); - verify(&mut lexer, 1, 4, "*", TokenKind::Word); - assert_eq!(None, lexer.next()); - } + #[token("#")] + HASH, + + #[token("\"")] + QUOTE, + + #[token("=")] + EQUALITY_SIGN, - #[test] - fn delimiter() { - let mut lexer = Lexer::new("{}()\""); - verify(&mut lexer, 0, 0, "{", TokenKind::BeginBrace); - verify(&mut lexer, 0, 1, "}", TokenKind::EndBrace); - verify(&mut lexer, 0, 2, "(", TokenKind::BeginParen); - verify(&mut lexer, 0, 3, ")", TokenKind::EndParen); - verify(&mut lexer, 0, 4, "\"", TokenKind::Quote); - assert_eq!(None, lexer.next()); + #[regex(r"\\([^\r\n]|[@a-zA-Z:_]+\*?)?")] + COMMAND_NAME, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Lexer<'a> { + tokens: Vec<(SyntaxKind, &'a str)>, +} + +impl<'a> Lexer<'a> { + pub fn new(text: &'a str) -> Self { + let mut tokens = Vec::new(); + let mut lexer = LogosToken::lexer(text); + while let Some(kind) = lexer.next() { + tokens.push(( + unsafe { std::mem::transmute::<LogosToken, SyntaxKind>(kind) }, + lexer.slice(), + )); + } + tokens.reverse(); + Self { tokens } } - #[test] - fn kind() { - let mut lexer = Lexer::new("@pReAmBlE\n@article\n@string"); - verify(&mut lexer, 0, 0, "@pReAmBlE", TokenKind::PreambleKind); - verify(&mut lexer, 1, 0, "@article", TokenKind::EntryKind); - verify(&mut lexer, 2, 0, "@string", TokenKind::StringKind); - assert_eq!(None, lexer.next()); + pub fn peek(&self) -> Option<SyntaxKind> { + self.tokens.last().map(|(kind, _)| *kind) } - #[test] - fn operator() { - let mut lexer = Lexer::new("=,#"); - verify(&mut lexer, 0, 0, "=", TokenKind::Assign); - verify(&mut lexer, 0, 1, ",", TokenKind::Comma); - verify(&mut lexer, 0, 2, "#", TokenKind::Concat); - assert_eq!(None, lexer.next()); + pub fn consume(&mut self) -> Option<(SyntaxKind, &'a str)> { + self.tokens.pop() } } diff --git a/support/texlab/src/syntax/bibtex/mod.rs b/support/texlab/src/syntax/bibtex/mod.rs deleted file mode 100644 index aa7242c1f7..0000000000 --- a/support/texlab/src/syntax/bibtex/mod.rs +++ /dev/null @@ -1,396 +0,0 @@ -mod ast; -mod formatter; -mod lexer; -mod parser; - -pub use self::{ast::*, formatter::*}; - -use self::{lexer::Lexer, parser::Parser}; - -pub fn open(text: &str) -> Tree { - let lexer = Lexer::new(text); - let parser = Parser::new(lexer); - parser.parse() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - protocol::{Range, RangeExt}, - syntax::text::SyntaxNode, - }; - use petgraph::graph::NodeIndex; - - #[derive(Debug, Default)] - struct TreeTraversal { - nodes: Vec<NodeIndex>, - } - - impl<'a> Visitor<'a> for TreeTraversal { - fn visit(&mut self, tree: &Tree, node: NodeIndex) { - self.nodes.push(node); - tree.walk(self, node); - } - } - - mod range { - use super::*; - use indoc::indoc; - - fn verify(expected_ranges: Vec<Range>, text: &str) { - let tree = open(text.trim()); - - let mut traversal = TreeTraversal::default(); - traversal.visit(&tree, tree.root); - let actual_ranges: Vec<_> = traversal - .nodes - .into_iter() - .map(|node| tree.graph[node].range()) - .collect(); - - assert_eq!(actual_ranges, expected_ranges); - } - - #[test] - fn empty_document() { - verify(vec![Range::new_simple(0, 0, 0, 0)], ""); - } - - #[test] - fn comment() { - verify( - vec![Range::new_simple(0, 0, 0, 3), Range::new_simple(0, 0, 0, 3)], - "foo", - ); - } - - #[test] - fn preamble_no_left() { - verify( - vec![Range::new_simple(0, 0, 0, 9), Range::new_simple(0, 0, 0, 9)], - "@preamble", - ); - } - - #[test] - fn preamble_no_content() { - verify( - vec![ - Range::new_simple(0, 0, 0, 10), - Range::new_simple(0, 0, 0, 10), - ], - "@preamble{", - ); - } - - #[test] - fn preamble_no_right() { - verify( - vec![ - Range::new_simple(0, 0, 0, 15), - Range::new_simple(0, 0, 0, 15), - Range::new_simple(0, 10, 0, 15), - Range::new_simple(0, 11, 0, 14), - ], - r#"@preamble{"foo""#, - ); - } - - #[test] - fn preamble_complete() { - verify( - vec![ - Range::new_simple(0, 0, 0, 16), - Range::new_simple(0, 0, 0, 16), - Range::new_simple(0, 10, 0, 15), - Range::new_simple(0, 11, 0, 14), - ], - r#"@preamble{"foo"}"#, - ); - } - - #[test] - fn string_no_left() { - verify( - vec![Range::new_simple(0, 0, 0, 7), Range::new_simple(0, 0, 0, 7)], - r#"@string"#, - ); - } - - #[test] - fn string_no_name() { - verify( - vec![Range::new_simple(0, 0, 0, 8), Range::new_simple(0, 0, 0, 8)], - r#"@string{"#, - ); - } - - #[test] - fn string_no_assign() { - verify( - vec![ - Range::new_simple(0, 0, 0, 11), - Range::new_simple(0, 0, 0, 11), - ], - r#"@string{foo"#, - ); - } - - #[test] - fn string_no_value() { - verify( - vec![ - Range::new_simple(0, 0, 0, 13), - Range::new_simple(0, 0, 0, 13), - ], - r#"@string{foo ="#, - ); - } - - #[test] - fn string_no_right() { - verify( - vec![ - Range::new_simple(0, 0, 0, 19), - Range::new_simple(0, 0, 0, 19), - Range::new_simple(0, 14, 0, 19), - Range::new_simple(0, 15, 0, 18), - ], - r#"@string{foo = "bar""#, - ); - } - - #[test] - fn string_complete() { - verify( - vec![ - Range::new_simple(0, 0, 0, 20), - Range::new_simple(0, 0, 0, 20), - Range::new_simple(0, 14, 0, 19), - Range::new_simple(0, 15, 0, 18), - ], - r#"@string{foo = "bar"}"#, - ); - } - - #[test] - fn entry_no_left() { - verify( - vec![Range::new_simple(0, 0, 0, 8), Range::new_simple(0, 0, 0, 8)], - r#"@article"#, - ); - } - - #[test] - fn entry_no_key() { - verify( - vec![Range::new_simple(0, 0, 0, 9), Range::new_simple(0, 0, 0, 9)], - r#"@article{"#, - ); - } - - #[test] - fn entry_no_comma() { - verify( - vec![ - Range::new_simple(0, 0, 0, 12), - Range::new_simple(0, 0, 0, 12), - ], - r#"@article{foo"#, - ); - } - - #[test] - fn entry_no_right() { - verify( - vec![ - Range::new_simple(0, 0, 0, 13), - Range::new_simple(0, 0, 0, 13), - ], - r#"@article{foo,"#, - ); - } - - #[test] - fn entry_parentheses() { - verify( - vec![ - Range::new_simple(0, 0, 0, 14), - Range::new_simple(0, 0, 0, 14), - ], - r#"@article(foo,)"#, - ); - } - - #[test] - fn field_no_assign() { - verify( - vec![ - Range::new_simple(0, 0, 1, 10), - Range::new_simple(0, 0, 1, 10), - Range::new_simple(1, 4, 1, 10), - ], - indoc!( - r#" - @article{foo, - author - "# - ), - ); - } - - #[test] - fn field_no_value() { - verify( - vec![ - Range::new_simple(0, 0, 1, 12), - Range::new_simple(0, 0, 1, 12), - Range::new_simple(1, 4, 1, 12), - ], - indoc!( - r#" - @article{foo, - author = - "# - ), - ); - } - - #[test] - fn field_no_comma() { - verify( - vec![ - Range::new_simple(0, 0, 1, 16), - Range::new_simple(0, 0, 1, 16), - Range::new_simple(1, 4, 1, 16), - Range::new_simple(1, 13, 1, 16), - ], - indoc!( - r#" - @article{foo, - author = bar - "# - ), - ); - } - - #[test] - fn field_complete() { - verify( - vec![ - Range::new_simple(0, 0, 2, 1), - Range::new_simple(0, 0, 2, 1), - Range::new_simple(1, 4, 1, 17), - Range::new_simple(1, 13, 1, 16), - ], - indoc!( - r#" - @article{foo, - author = bar, - } - "# - ), - ); - } - - #[test] - fn entry_two_fields() { - verify( - vec![ - Range::new_simple(0, 0, 3, 1), - Range::new_simple(0, 0, 3, 1), - Range::new_simple(1, 4, 1, 17), - Range::new_simple(1, 13, 1, 16), - Range::new_simple(2, 4, 2, 16), - Range::new_simple(2, 12, 2, 15), - ], - indoc!( - r#" - @article{foo, - author = bar, - title = baz, - } - "# - ), - ); - } - - #[test] - fn quoted_content_no_children() { - verify( - vec![ - Range::new_simple(0, 0, 0, 12), - Range::new_simple(0, 0, 0, 12), - Range::new_simple(0, 10, 0, 11), - ], - r#"@preamble{"}"#, - ); - } - - #[test] - fn quoted_content_no_right() { - verify( - vec![ - Range::new_simple(0, 0, 0, 16), - Range::new_simple(0, 0, 0, 16), - Range::new_simple(0, 10, 0, 15), - Range::new_simple(0, 11, 0, 15), - ], - r#"@preamble{"word}"#, - ); - } - - #[test] - fn braced_content_no_children() { - verify( - vec![ - Range::new_simple(0, 0, 0, 11), - Range::new_simple(0, 0, 0, 11), - Range::new_simple(0, 10, 0, 11), - ], - r#"@preamble{{"#, - ); - } - - #[test] - fn braced_content_no_right() { - verify( - vec![ - Range::new_simple(0, 0, 0, 15), - Range::new_simple(0, 0, 0, 15), - Range::new_simple(0, 10, 0, 15), - Range::new_simple(0, 11, 0, 15), - ], - r#"@preamble{{word"#, - ); - } - - #[test] - fn concat_no_right() { - verify( - vec![ - Range::new_simple(0, 0, 0, 16), - Range::new_simple(0, 0, 0, 16), - Range::new_simple(0, 10, 0, 15), - Range::new_simple(0, 10, 0, 13), - ], - r#"@preamble{foo #}"#, - ); - } - - #[test] - fn concat_complete() { - verify( - vec![ - Range::new_simple(0, 0, 0, 20), - Range::new_simple(0, 0, 0, 20), - Range::new_simple(0, 10, 0, 19), - Range::new_simple(0, 10, 0, 13), - Range::new_simple(0, 16, 0, 19), - ], - r#"@preamble{foo # bar}"#, - ); - } - } -} diff --git a/support/texlab/src/syntax/bibtex/parser.rs b/support/texlab/src/syntax/bibtex/parser.rs index b23903aeb3..ffdb1c94a0 100644 --- a/support/texlab/src/syntax/bibtex/parser.rs +++ b/support/texlab/src/syntax/bibtex/parser.rs @@ -1,401 +1,413 @@ -use super::ast::*; -use crate::{ - protocol::{Range, RangeExt}, - syntax::text::SyntaxNode, +use cstree::GreenNodeBuilder; + +use super::{ + lexer::Lexer, + SyntaxKind::{self, *}, + SyntaxNode, }; -use petgraph::graph::{Graph, NodeIndex}; -use std::iter::Peekable; -pub struct Parser<I: Iterator<Item = Token>> { - graph: Graph<Node, ()>, - tokens: Peekable<I>, +#[derive(Debug, Clone)] +pub struct Parse { + pub root: SyntaxNode, +} + +struct Parser<'a> { + lexer: Lexer<'a>, + builder: GreenNodeBuilder<'static, 'static>, } -impl<I: Iterator<Item = Token>> Parser<I> { - pub fn new(tokens: I) -> Self { +impl<'a> Parser<'a> { + pub fn new(lexer: Lexer<'a>) -> Self { Self { - tokens: tokens.peekable(), - graph: Graph::new(), + lexer, + builder: GreenNodeBuilder::new(), } } - pub fn parse(mut self) -> Tree { - let mut children = Vec::new(); + fn peek(&self) -> Option<SyntaxKind> { + self.lexer.peek() + } - while let Some(ref token) = self.tokens.peek() { - match token.kind { - TokenKind::PreambleKind => children.push(self.preamble()), - TokenKind::StringKind => children.push(self.string()), - TokenKind::EntryKind => children.push(self.entry()), - _ => children.push(self.comment()), - } - } + fn eat(&mut self) { + let (kind, text) = self.lexer.consume().unwrap(); + self.builder.token(kind.into(), text); + } - let range = if children.is_empty() { - Range::new_simple(0, 0, 0, 0) + fn expect(&mut self, kind: SyntaxKind) { + if self.peek() == Some(kind) { + self.eat(); + self.trivia(); } else { - let start = self.graph[children[0]].start(); - let end = self.graph[children[children.len() - 1]].end(); - Range::new(start, end) - }; + self.builder.token(MISSING.into(), ""); + } + } - let root = self.graph.add_node(Node::Root(Root { range })); - self.connect(root, &children); - Tree { - graph: self.graph, - root, + pub fn parse(mut self) -> Parse { + self.builder.start_node(ROOT.into()); + while let Some(kind) = self.peek() { + match kind { + PREAMBLE_TYPE => self.preamble(), + STRING_TYPE => self.string(), + COMMENT_TYPE => self.comment(), + ENTRY_TYPE => self.entry(), + _ => self.junk(), + } + } + self.builder.finish_node(); + let (green, resolver) = self.builder.finish(); + Parse { + root: SyntaxNode::new_root_with_resolver(green, resolver.unwrap()), } } - fn preamble(&mut self) -> NodeIndex { - let ty = self.tokens.next().unwrap(); + fn trivia(&mut self) { + while self.peek() == Some(WHITESPACE) { + self.eat(); + } + } - let left = self.expect2(TokenKind::BeginBrace, TokenKind::BeginParen); - if left.is_none() { - return self.graph.add_node(Node::Preamble(Box::new(Preamble { - range: ty.range(), - ty, - left: None, - right: None, - }))); + fn junk(&mut self) { + self.builder.start_node(JUNK.into()); + while self + .lexer + .peek() + .filter(|&kind| { + !matches!( + kind, + PREAMBLE_TYPE | STRING_TYPE | COMMENT_TYPE | ENTRY_TYPE, + ) + }) + .is_some() + { + self.eat(); } + self.builder.finish_node(); + } - if !self.can_match_content() { - return self.graph.add_node(Node::Preamble(Box::new(Preamble { - range: Range::new(ty.start(), left.as_ref().unwrap().end()), - ty, - left, - right: None, - }))); + fn left_delimiter_or_missing(&mut self) { + if self + .lexer + .peek() + .filter(|&kind| matches!(kind, L_CURLY | L_PAREN)) + .is_some() + { + self.eat(); + self.trivia(); + } else { + self.builder.token(MISSING.into(), ""); } + } - let content = self.content(); - - let right = self.expect2(TokenKind::EndBrace, TokenKind::EndParen); - let end = right - .as_ref() - .map(Token::end) - .unwrap_or_else(|| self.graph[content].end()); - - let parent = self.graph.add_node(Node::Preamble(Box::new(Preamble { - range: Range::new(ty.start(), end), - ty, - left, - right, - }))); - self.graph.add_edge(parent, content, ()); - parent - } - - fn string(&mut self) -> NodeIndex { - let ty = self.tokens.next().unwrap(); - - let left = self.expect2(TokenKind::BeginBrace, TokenKind::BeginParen); - if left.is_none() { - return self.graph.add_node(Node::String(Box::new(String { - range: ty.range(), - ty, - left: None, - name: None, - assign: None, - right: None, - }))); + fn right_delimiter_or_missing(&mut self) { + if self + .lexer + .peek() + .filter(|&kind| matches!(kind, R_CURLY | R_PAREN)) + .is_some() + { + self.eat(); + self.trivia(); + } else { + self.builder.token(MISSING.into(), ""); } + } - let name = self.expect1(TokenKind::Word); - if name.is_none() { - return self.graph.add_node(Node::String(Box::new(String { - range: Range::new(ty.start(), left.as_ref().unwrap().end()), - ty, - left, - name: None, - assign: None, - right: None, - }))); + fn value_or_missing(&mut self) { + if self + .lexer + .peek() + .filter(|&kind| matches!(kind, L_CURLY | QUOTE | WORD)) + .is_some() + { + self.value(); + } else { + self.builder.token(MISSING.into(), ""); } + } + + fn preamble(&mut self) { + self.builder.start_node(PREAMBLE.into()); + self.eat(); + self.trivia(); + self.left_delimiter_or_missing(); + self.value_or_missing(); + self.right_delimiter_or_missing(); + self.builder.finish_node(); + } - let assign = self.expect1(TokenKind::Assign); - if assign.is_none() { - return self.graph.add_node(Node::String(Box::new(String { - range: Range::new(ty.start(), name.as_ref().unwrap().end()), - ty, - left, - name, - assign: None, - right: None, - }))); + fn string(&mut self) { + self.builder.start_node(STRING.into()); + self.eat(); + self.trivia(); + self.left_delimiter_or_missing(); + + if self.peek() != Some(WORD) { + self.builder.token(MISSING.into(), ""); + self.builder.finish_node(); + return; } + self.eat(); + self.trivia(); + self.expect(EQUALITY_SIGN); + + self.value_or_missing(); + + self.right_delimiter_or_missing(); + self.builder.finish_node(); + } + + fn comment(&mut self) { + self.builder.start_node(COMMENT.into()); + self.eat(); + self.builder.finish_node(); + } - if !self.can_match_content() { - return self.graph.add_node(Node::String(Box::new(String { - range: Range::new(ty.start(), assign.as_ref().unwrap().end()), - ty, - left, - name, - assign, - right: None, - }))); + fn entry(&mut self) { + self.builder.start_node(ENTRY.into()); + self.eat(); + self.trivia(); + + self.left_delimiter_or_missing(); + + if self.peek() != Some(WORD) { + self.builder.token(MISSING.into(), ""); + self.builder.finish_node(); + return; } - let value = self.content(); - - let right = self.expect2(TokenKind::EndBrace, TokenKind::EndParen); - let end = right - .as_ref() - .map(Token::end) - .unwrap_or_else(|| self.graph[value].end()); - - let parent = self.graph.add_node(Node::String(Box::new(String { - range: Range::new(ty.start(), end), - ty, - left, - name, - assign, - right, - }))); - self.graph.add_edge(parent, value, ()); - parent - } - - fn entry(&mut self) -> NodeIndex { - let ty = self.tokens.next().unwrap(); - - let left = self.expect2(TokenKind::BeginBrace, TokenKind::BeginParen); - if left.is_none() { - return self.graph.add_node(Node::Entry(Box::new(Entry { - range: ty.range(), - ty, - left: None, - key: None, - comma: None, - right: None, - }))); + self.eat(); + + while let Some(kind) = self.peek() { + match kind { + WHITESPACE => self.eat(), + WORD => self.field(), + COMMA => self.eat(), + _ => break, + }; } - let key = self.expect1(TokenKind::Word); - if key.is_none() { - return self.graph.add_node(Node::Entry(Box::new(Entry { - range: Range::new(ty.start(), left.as_ref().unwrap().end()), - ty, - left, - key: None, - comma: None, - right: None, - }))); - } + self.right_delimiter_or_missing(); - let comma = self.expect1(TokenKind::Comma); - if comma.is_none() { - return self.graph.add_node(Node::Entry(Box::new(Entry { - range: Range::new(ty.start(), key.as_ref().unwrap().end()), - ty, - left, - key, - comma: None, - right: None, - }))); - } + self.builder.finish_node(); + } - let mut fields = Vec::new(); - while self.next_of_kind(TokenKind::Word) { - fields.push(self.field()); - } + fn field(&mut self) { + self.builder.start_node(FIELD.into()); + self.eat(); + self.trivia(); - let right = self.expect2(TokenKind::EndBrace, TokenKind::EndParen); - - let end = right - .as_ref() - .map(Token::end) - .or_else(|| fields.last().map(|field| self.graph[*field].end())) - .unwrap_or_else(|| comma.as_ref().unwrap().end()); - let parent = self.graph.add_node(Node::Entry(Box::new(Entry { - range: Range::new(ty.start(), end), - ty, - left, - key, - comma, - right, - }))); - self.connect(parent, &fields); - parent - } - - fn comment(&mut self) -> NodeIndex { - let token = self.tokens.next().unwrap(); - self.graph.add_node(Node::Comment(Comment { token })) - } - - fn field(&mut self) -> NodeIndex { - let name = self.tokens.next().unwrap(); - - let assign = self.expect1(TokenKind::Assign); - if assign.is_none() { - return self.graph.add_node(Node::Field(Box::new(Field { - range: name.range(), - name, - assign: None, - comma: None, - }))); + if self.peek() == Some(EQUALITY_SIGN) { + self.eat(); + self.trivia(); + } else { + self.builder.token(MISSING.into(), ""); } - if !self.can_match_content() { - return self.graph.add_node(Node::Field(Box::new(Field { - range: Range::new(name.start(), assign.as_ref().unwrap().end()), - name, - assign, - comma: None, - }))); + if self + .lexer + .peek() + .filter(|&kind| matches!(kind, L_CURLY | QUOTE | WORD)) + .is_some() + { + self.value(); + } else { + self.builder.token(MISSING.into(), ""); } - let content = self.content(); - - let comma = self.expect1(TokenKind::Comma); - - let end = comma - .as_ref() - .map(Token::end) - .unwrap_or_else(|| self.graph[content].end()); - let parent = self.graph.add_node(Node::Field(Box::new(Field { - range: Range::new(name.start(), end), - name, - assign, - comma, - }))); - self.graph.add_edge(parent, content, ()); - parent - } - - fn content(&mut self) -> NodeIndex { - let token = self.tokens.next().unwrap(); - let left = match token.kind { - TokenKind::PreambleKind - | TokenKind::StringKind - | TokenKind::EntryKind - | TokenKind::Word - | TokenKind::Assign - | TokenKind::Comma - | TokenKind::BeginParen - | TokenKind::EndParen => self.graph.add_node(Node::Word(Word { token })), - TokenKind::Command => self.graph.add_node(Node::Command(Command { token })), - TokenKind::Quote => { - let mut children = Vec::new(); - while self.can_match_content() { - if self.next_of_kind(TokenKind::Quote) { - break; - } - children.push(self.content()); - } - let right = self.expect1(TokenKind::Quote); - - let end = right - .as_ref() - .map(Token::end) - .or_else(|| children.last().map(|child| self.graph[*child].end())) - .unwrap_or_else(|| token.end()); - let parent = self.graph.add_node(Node::QuotedContent(QuotedContent { - range: Range::new(token.start(), end), - left: token, - right, - })); - self.connect(parent, &children); - parent - } - TokenKind::BeginBrace => { - let mut children = Vec::new(); - while self.can_match_content() { - children.push(self.content()); - } - let right = self.expect1(TokenKind::EndBrace); - - let end = right - .as_ref() - .map(Token::end) - .or_else(|| children.last().map(|child| self.graph[*child].end())) - .unwrap_or_else(|| token.end()); - let parent = self.graph.add_node(Node::BracedContent(BracedContent { - range: Range::new(token.start(), end), - left: token, - right, - })); - self.connect(parent, &children); - parent + self.builder.finish_node(); + } + + fn value(&mut self) { + self.builder.start_node(VALUE.into()); + self.token(); + while let Some(kind) = self.peek() { + match kind { + WHITESPACE => self.eat(), + L_CURLY | QUOTE | WORD => self.token(), + HASH => self.eat(), + _ => break, } + } + self.builder.finish_node(); + } + + fn token(&mut self) { + self.builder.start_node(TOKEN.into()); + match self.peek().unwrap() { + L_CURLY => self.brace_group(), + QUOTE => self.quote_group(), + WORD => self.eat(), _ => unreachable!(), }; + self.builder.finish_node(); + } - match self.expect1(TokenKind::Concat) { - Some(operator) => { - if self.can_match_content() { - let right = self.content(); - let parent = self.graph.add_node(Node::Concat(Concat { - range: Range::new(self.graph[left].start(), self.graph[right].end()), - operator, - })); - self.graph.add_edge(parent, left, ()); - self.graph.add_edge(parent, right, ()); - parent - } else { - let parent = self.graph.add_node(Node::Concat(Concat { - range: Range::new(self.graph[left].start(), operator.end()), - operator, - })); - self.graph.add_edge(parent, left, ()); - parent - } - } - None => left, + fn brace_group(&mut self) { + self.builder.start_node(BRACE_GROUP.into()); + self.eat(); + + while let Some(kind) = self.peek() { + match kind { + WHITESPACE => self.eat(), + PREAMBLE_TYPE => break, + STRING_TYPE => break, + COMMENT_TYPE => break, + ENTRY_TYPE => break, + WORD => self.eat(), + L_CURLY => self.brace_group(), + R_CURLY => break, + L_PAREN => self.eat(), + R_PAREN => self.eat(), + COMMA => self.eat(), + HASH => self.eat(), + QUOTE => self.eat(), + EQUALITY_SIGN => self.eat(), + COMMAND_NAME => self.eat(), + _ => unreachable!(), + }; } + + self.expect(R_CURLY); + + self.builder.finish_node(); } - fn connect(&mut self, parent: NodeIndex, children: &[NodeIndex]) { - for child in children { - self.graph.add_edge(parent, *child, ()); + fn quote_group(&mut self) { + self.builder.start_node(QUOTE_GROUP.into()); + self.eat(); + + while let Some(kind) = self.peek() { + match kind { + WHITESPACE => self.eat(), + PREAMBLE_TYPE => break, + STRING_TYPE => break, + COMMENT_TYPE => break, + ENTRY_TYPE => break, + WORD => self.eat(), + L_CURLY => self.brace_group(), + R_CURLY => break, + L_PAREN => self.eat(), + R_PAREN => self.eat(), + COMMA => self.eat(), + HASH => self.eat(), + QUOTE => break, + EQUALITY_SIGN => self.eat(), + COMMAND_NAME => self.eat(), + _ => unreachable!(), + }; } + + self.expect(QUOTE); + self.builder.finish_node(); } +} - fn can_match_content(&mut self) -> bool { - if let Some(ref token) = self.tokens.peek() { - match token.kind { - TokenKind::PreambleKind - | TokenKind::StringKind - | TokenKind::EntryKind - | TokenKind::Word - | TokenKind::Command - | TokenKind::Assign - | TokenKind::Comma - | TokenKind::Quote - | TokenKind::BeginBrace - | TokenKind::BeginParen - | TokenKind::EndParen => true, - TokenKind::Concat | TokenKind::EndBrace => false, - } - } else { - false - } +pub fn parse(text: &str) -> Parse { + Parser::new(Lexer::new(text)).parse() +} + +#[cfg(test)] +mod tests { + use insta::assert_debug_snapshot; + + use super::*; + + fn setup(text: &str) -> SyntaxNode { + parse(&text.trim().replace("\r", "")).root } - fn expect1(&mut self, kind: TokenKind) -> Option<Token> { - if let Some(ref token) = self.tokens.peek() { - if token.kind == kind { - return self.tokens.next(); - } - } - None + #[test] + fn test_empty() { + assert_debug_snapshot!(setup(r#""#)); } - fn expect2(&mut self, kind1: TokenKind, kind2: TokenKind) -> Option<Token> { - if let Some(ref token) = self.tokens.peek() { - if token.kind == kind1 || token.kind == kind2 { - return self.tokens.next(); - } - } - None + #[test] + fn test_junk() { + assert_debug_snapshot!(setup(r#"Hello World!"#)); } - fn next_of_kind(&mut self, kind: TokenKind) -> bool { - if let Some(token) = self.tokens.peek() { - token.kind == kind - } else { - false - } + #[test] + fn test_preamble_complete() { + assert_debug_snapshot!(setup(r#"@preamble{ "Hello World" }"#)); + } + + #[test] + fn test_preamble_missing_end() { + assert_debug_snapshot!(setup(r#"@preamble{ "Hello World" "#)); + } + + #[test] + fn test_preamble_casing() { + assert_debug_snapshot!(setup(r#"@preAmbLe{ "Hello World" }"#)); + } + + #[test] + fn test_string_complete() { + assert_debug_snapshot!(setup(r#"@string{foo = {Hello World}}"#)); + } + + #[test] + fn test_string_incomplete() { + assert_debug_snapshot!(setup(r#"@string{foo = {Hello World}"#)); + } + + #[test] + fn test_string_concatenation() { + assert_debug_snapshot!(setup( + r#"@string{foo = {Hello World}} @string{bar = foo # "!"}"# + )); + } + + #[test] + fn test_string_casing() { + assert_debug_snapshot!(setup(r#"@STRING{foo = "Hello World"}"#)); + } + + #[test] + fn test_string_missing_quote() { + assert_debug_snapshot!(setup(r#"@STRING{foo = "Hello World}"#)); + } + + #[test] + fn test_entry_no_fields() { + assert_debug_snapshot!(setup(r#"@article{foo,}"#)); + } + + #[test] + fn test_entry_no_fields_missing_comma() { + assert_debug_snapshot!(setup(r#"@article{foo}"#)); + } + + #[test] + fn test_entry_one_field() { + assert_debug_snapshot!(setup(r#"@article{foo, author = {Foo Bar}}"#)); + } + + #[test] + fn test_entry_one_field_number_key() { + assert_debug_snapshot!(setup(r#"@article{foo2021, author = {Foo Bar}}"#)); + } + + #[test] + fn test_entry_one_field_trailing_comma() { + assert_debug_snapshot!(setup(r#"@article{foo, author = {Foo Bar},}"#)); + } + + #[test] + fn test_entry_two_fields() { + assert_debug_snapshot!(setup( + r#"@article{foo, author = {Foo Bar}, title = {Hello World}}"# + )); + } + + #[test] + fn test_entry_two_fields_incomplete() { + assert_debug_snapshot!(setup(r#"@article{foo, author = {Foo Bar}, t}"#)); + } + + #[test] + fn test_entry_complete_parens() { + assert_debug_snapshot!(setup( + r#"@article(foo, author = {Foo Bar}, title = {Hello})"# + )); } } diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__empty.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__empty.snap new file mode 100644 index 0000000000..55e1954d2f --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__empty.snap @@ -0,0 +1,7 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"\"#)" + +--- +ROOT@0..0 + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_complete_parens.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_complete_parens.snap new file mode 100644 index 0000000000..cfd469f161 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_complete_parens.snap @@ -0,0 +1,40 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@article(foo, author = {Foo Bar}, title = {Hello})\"#)" + +--- +ROOT@0..50 + ENTRY@0..50 + ENTRY_TYPE@0..8 "@article" + L_PAREN@8..9 "(" + WORD@9..12 "foo" + COMMA@12..13 "," + WHITESPACE@13..14 " " + FIELD@14..32 + WORD@14..20 "author" + WHITESPACE@20..21 " " + EQUALITY_SIGN@21..22 "=" + WHITESPACE@22..23 " " + VALUE@23..32 + TOKEN@23..32 + BRACE_GROUP@23..32 + L_CURLY@23..24 "{" + WORD@24..27 "Foo" + WHITESPACE@27..28 " " + WORD@28..31 "Bar" + R_CURLY@31..32 "}" + COMMA@32..33 "," + WHITESPACE@33..34 " " + FIELD@34..49 + WORD@34..39 "title" + WHITESPACE@39..40 " " + EQUALITY_SIGN@40..41 "=" + WHITESPACE@41..42 " " + VALUE@42..49 + TOKEN@42..49 + BRACE_GROUP@42..49 + L_CURLY@42..43 "{" + WORD@43..48 "Hello" + R_CURLY@48..49 "}" + R_PAREN@49..50 ")" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_no_fields.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_no_fields.snap new file mode 100644 index 0000000000..a98df7e382 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_no_fields.snap @@ -0,0 +1,13 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@article{foo,}\"#)" + +--- +ROOT@0..14 + ENTRY@0..14 + ENTRY_TYPE@0..8 "@article" + L_CURLY@8..9 "{" + WORD@9..12 "foo" + COMMA@12..13 "," + R_CURLY@13..14 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_no_fields_missing_comma.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_no_fields_missing_comma.snap new file mode 100644 index 0000000000..b83037aae0 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_no_fields_missing_comma.snap @@ -0,0 +1,12 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@article{foo}\"#)" + +--- +ROOT@0..13 + ENTRY@0..13 + ENTRY_TYPE@0..8 "@article" + L_CURLY@8..9 "{" + WORD@9..12 "foo" + R_CURLY@12..13 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field.snap new file mode 100644 index 0000000000..704cbbbd47 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field.snap @@ -0,0 +1,27 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@article{foo, author = {Foo Bar}}\"#)" + +--- +ROOT@0..33 + ENTRY@0..33 + ENTRY_TYPE@0..8 "@article" + L_CURLY@8..9 "{" + WORD@9..12 "foo" + COMMA@12..13 "," + WHITESPACE@13..14 " " + FIELD@14..32 + WORD@14..20 "author" + WHITESPACE@20..21 " " + EQUALITY_SIGN@21..22 "=" + WHITESPACE@22..23 " " + VALUE@23..32 + TOKEN@23..32 + BRACE_GROUP@23..32 + L_CURLY@23..24 "{" + WORD@24..27 "Foo" + WHITESPACE@27..28 " " + WORD@28..31 "Bar" + R_CURLY@31..32 "}" + R_CURLY@32..33 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field_number_key.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field_number_key.snap new file mode 100644 index 0000000000..8f00c5ee1c --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field_number_key.snap @@ -0,0 +1,27 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@article{foo2021, author = {Foo Bar}}\"#)" + +--- +ROOT@0..37 + ENTRY@0..37 + ENTRY_TYPE@0..8 "@article" + L_CURLY@8..9 "{" + WORD@9..16 "foo2021" + COMMA@16..17 "," + WHITESPACE@17..18 " " + FIELD@18..36 + WORD@18..24 "author" + WHITESPACE@24..25 " " + EQUALITY_SIGN@25..26 "=" + WHITESPACE@26..27 " " + VALUE@27..36 + TOKEN@27..36 + BRACE_GROUP@27..36 + L_CURLY@27..28 "{" + WORD@28..31 "Foo" + WHITESPACE@31..32 " " + WORD@32..35 "Bar" + R_CURLY@35..36 "}" + R_CURLY@36..37 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field_trailing_comma.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field_trailing_comma.snap new file mode 100644 index 0000000000..af8b1593df --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_one_field_trailing_comma.snap @@ -0,0 +1,28 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@article{foo, author = {Foo Bar},}\"#)" + +--- +ROOT@0..34 + ENTRY@0..34 + ENTRY_TYPE@0..8 "@article" + L_CURLY@8..9 "{" + WORD@9..12 "foo" + COMMA@12..13 "," + WHITESPACE@13..14 " " + FIELD@14..32 + WORD@14..20 "author" + WHITESPACE@20..21 " " + EQUALITY_SIGN@21..22 "=" + WHITESPACE@22..23 " " + VALUE@23..32 + TOKEN@23..32 + BRACE_GROUP@23..32 + L_CURLY@23..24 "{" + WORD@24..27 "Foo" + WHITESPACE@27..28 " " + WORD@28..31 "Bar" + R_CURLY@31..32 "}" + COMMA@32..33 "," + R_CURLY@33..34 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_two_fields.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_two_fields.snap new file mode 100644 index 0000000000..f47b3b6917 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_two_fields.snap @@ -0,0 +1,42 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@article{foo, author = {Foo Bar}, title = {Hello World}}\"#)" + +--- +ROOT@0..56 + ENTRY@0..56 + ENTRY_TYPE@0..8 "@article" + L_CURLY@8..9 "{" + WORD@9..12 "foo" + COMMA@12..13 "," + WHITESPACE@13..14 " " + FIELD@14..32 + WORD@14..20 "author" + WHITESPACE@20..21 " " + EQUALITY_SIGN@21..22 "=" + WHITESPACE@22..23 " " + VALUE@23..32 + TOKEN@23..32 + BRACE_GROUP@23..32 + L_CURLY@23..24 "{" + WORD@24..27 "Foo" + WHITESPACE@27..28 " " + WORD@28..31 "Bar" + R_CURLY@31..32 "}" + COMMA@32..33 "," + WHITESPACE@33..34 " " + FIELD@34..55 + WORD@34..39 "title" + WHITESPACE@39..40 " " + EQUALITY_SIGN@40..41 "=" + WHITESPACE@41..42 " " + VALUE@42..55 + TOKEN@42..55 + BRACE_GROUP@42..55 + L_CURLY@42..43 "{" + WORD@43..48 "Hello" + WHITESPACE@48..49 " " + WORD@49..54 "World" + R_CURLY@54..55 "}" + R_CURLY@55..56 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_two_fields_incomplete.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_two_fields_incomplete.snap new file mode 100644 index 0000000000..34a9019582 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__entry_two_fields_incomplete.snap @@ -0,0 +1,33 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@article{foo, author = {Foo Bar}, t}\"#)" + +--- +ROOT@0..36 + ENTRY@0..36 + ENTRY_TYPE@0..8 "@article" + L_CURLY@8..9 "{" + WORD@9..12 "foo" + COMMA@12..13 "," + WHITESPACE@13..14 " " + FIELD@14..32 + WORD@14..20 "author" + WHITESPACE@20..21 " " + EQUALITY_SIGN@21..22 "=" + WHITESPACE@22..23 " " + VALUE@23..32 + TOKEN@23..32 + BRACE_GROUP@23..32 + L_CURLY@23..24 "{" + WORD@24..27 "Foo" + WHITESPACE@27..28 " " + WORD@28..31 "Bar" + R_CURLY@31..32 "}" + COMMA@32..33 "," + WHITESPACE@33..34 " " + FIELD@34..35 + WORD@34..35 "t" + MISSING@35..35 "" + MISSING@35..35 "" + R_CURLY@35..36 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__junk.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__junk.snap new file mode 100644 index 0000000000..e77313cabf --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__junk.snap @@ -0,0 +1,11 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"Hello World!\"#)" + +--- +ROOT@0..12 + JUNK@0..12 + WORD@0..5 "Hello" + WHITESPACE@5..6 " " + WORD@6..12 "World!" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_casing.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_casing.snap new file mode 100644 index 0000000000..f4669b8a01 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_casing.snap @@ -0,0 +1,21 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@preAmbLe{ \"Hello World\" }\"#)" + +--- +ROOT@0..26 + PREAMBLE@0..26 + PREAMBLE_TYPE@0..9 "@preAmbLe" + L_CURLY@9..10 "{" + WHITESPACE@10..11 " " + VALUE@11..25 + TOKEN@11..25 + QUOTE_GROUP@11..25 + QUOTE@11..12 "\"" + WORD@12..17 "Hello" + WHITESPACE@17..18 " " + WORD@18..23 "World" + QUOTE@23..24 "\"" + WHITESPACE@24..25 " " + R_CURLY@25..26 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_complete.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_complete.snap new file mode 100644 index 0000000000..d1977f2e76 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_complete.snap @@ -0,0 +1,21 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@preamble{ \"Hello World\" }\"#)" + +--- +ROOT@0..26 + PREAMBLE@0..26 + PREAMBLE_TYPE@0..9 "@preamble" + L_CURLY@9..10 "{" + WHITESPACE@10..11 " " + VALUE@11..25 + TOKEN@11..25 + QUOTE_GROUP@11..25 + QUOTE@11..12 "\"" + WORD@12..17 "Hello" + WHITESPACE@17..18 " " + WORD@18..23 "World" + QUOTE@23..24 "\"" + WHITESPACE@24..25 " " + R_CURLY@25..26 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_missing_end.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_missing_end.snap new file mode 100644 index 0000000000..4ba447a2ae --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__preamble_missing_end.snap @@ -0,0 +1,20 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@preamble{ \"Hello World\" \"#)" + +--- +ROOT@0..24 + PREAMBLE@0..24 + PREAMBLE_TYPE@0..9 "@preamble" + L_CURLY@9..10 "{" + WHITESPACE@10..11 " " + VALUE@11..24 + TOKEN@11..24 + QUOTE_GROUP@11..24 + QUOTE@11..12 "\"" + WORD@12..17 "Hello" + WHITESPACE@17..18 " " + WORD@18..23 "World" + QUOTE@23..24 "\"" + MISSING@24..24 "" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_casing.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_casing.snap new file mode 100644 index 0000000000..1c8b1dbea4 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_casing.snap @@ -0,0 +1,23 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@STRING{foo = \"Hello World\"}\"#)" + +--- +ROOT@0..28 + STRING@0..28 + STRING_TYPE@0..7 "@STRING" + L_CURLY@7..8 "{" + WORD@8..11 "foo" + WHITESPACE@11..12 " " + EQUALITY_SIGN@12..13 "=" + WHITESPACE@13..14 " " + VALUE@14..27 + TOKEN@14..27 + QUOTE_GROUP@14..27 + QUOTE@14..15 "\"" + WORD@15..20 "Hello" + WHITESPACE@20..21 " " + WORD@21..26 "World" + QUOTE@26..27 "\"" + R_CURLY@27..28 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_complete.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_complete.snap new file mode 100644 index 0000000000..506452894d --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_complete.snap @@ -0,0 +1,23 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@string{foo = {Hello World}}\"#)" + +--- +ROOT@0..28 + STRING@0..28 + STRING_TYPE@0..7 "@string" + L_CURLY@7..8 "{" + WORD@8..11 "foo" + WHITESPACE@11..12 " " + EQUALITY_SIGN@12..13 "=" + WHITESPACE@13..14 " " + VALUE@14..27 + TOKEN@14..27 + BRACE_GROUP@14..27 + L_CURLY@14..15 "{" + WORD@15..20 "Hello" + WHITESPACE@20..21 " " + WORD@21..26 "World" + R_CURLY@26..27 "}" + R_CURLY@27..28 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_concatenation.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_concatenation.snap new file mode 100644 index 0000000000..7f457d6964 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_concatenation.snap @@ -0,0 +1,43 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@string{foo = {Hello World}} @string{bar = foo # \"!\"}\"#)" + +--- +ROOT@0..53 + STRING@0..29 + STRING_TYPE@0..7 "@string" + L_CURLY@7..8 "{" + WORD@8..11 "foo" + WHITESPACE@11..12 " " + EQUALITY_SIGN@12..13 "=" + WHITESPACE@13..14 " " + VALUE@14..27 + TOKEN@14..27 + BRACE_GROUP@14..27 + L_CURLY@14..15 "{" + WORD@15..20 "Hello" + WHITESPACE@20..21 " " + WORD@21..26 "World" + R_CURLY@26..27 "}" + R_CURLY@27..28 "}" + WHITESPACE@28..29 " " + STRING@29..53 + STRING_TYPE@29..36 "@string" + L_CURLY@36..37 "{" + WORD@37..40 "bar" + WHITESPACE@40..41 " " + EQUALITY_SIGN@41..42 "=" + WHITESPACE@42..43 " " + VALUE@43..52 + TOKEN@43..46 + WORD@43..46 "foo" + WHITESPACE@46..47 " " + HASH@47..48 "#" + WHITESPACE@48..49 " " + TOKEN@49..52 + QUOTE_GROUP@49..52 + QUOTE@49..50 "\"" + WORD@50..51 "!" + QUOTE@51..52 "\"" + R_CURLY@52..53 "}" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_incomplete.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_incomplete.snap new file mode 100644 index 0000000000..685d02a179 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_incomplete.snap @@ -0,0 +1,23 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@string{foo = {Hello World}\"#)" + +--- +ROOT@0..27 + STRING@0..27 + STRING_TYPE@0..7 "@string" + L_CURLY@7..8 "{" + WORD@8..11 "foo" + WHITESPACE@11..12 " " + EQUALITY_SIGN@12..13 "=" + WHITESPACE@13..14 " " + VALUE@14..27 + TOKEN@14..27 + BRACE_GROUP@14..27 + L_CURLY@14..15 "{" + WORD@15..20 "Hello" + WHITESPACE@20..21 " " + WORD@21..26 "World" + R_CURLY@26..27 "}" + MISSING@27..27 "" + diff --git a/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_missing_quote.snap b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_missing_quote.snap new file mode 100644 index 0000000000..8eeefa9bf9 --- /dev/null +++ b/support/texlab/src/syntax/bibtex/snapshots/texlab__syntax__bibtex__parser__tests__string_missing_quote.snap @@ -0,0 +1,23 @@ +--- +source: src/syntax/bibtex/parser.rs +expression: "setup(r#\"@STRING{foo = \"Hello World}\"#)" + +--- +ROOT@0..27 + STRING@0..27 + STRING_TYPE@0..7 "@STRING" + L_CURLY@7..8 "{" + WORD@8..11 "foo" + WHITESPACE@11..12 " " + EQUALITY_SIGN@12..13 "=" + WHITESPACE@13..14 " " + VALUE@14..26 + TOKEN@14..26 + QUOTE_GROUP@14..26 + QUOTE@14..15 "\"" + WORD@15..20 "Hello" + WHITESPACE@20..21 " " + WORD@21..26 "World" + MISSING@26..26 "" + R_CURLY@26..27 "}" + diff --git a/support/texlab/src/syntax/build_log.rs b/support/texlab/src/syntax/build_log.rs new file mode 100644 index 0000000000..65a5938262 --- /dev/null +++ b/support/texlab/src/syntax/build_log.rs @@ -0,0 +1,1161 @@ +use std::{cmp::Ordering, path::PathBuf}; + +use once_cell::sync::Lazy; +use regex::{Match, Regex}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)] +pub enum BuildErrorLevel { + Error, + Warning, +} + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub struct BuildError { + pub relative_path: PathBuf, + pub level: BuildErrorLevel, + pub message: String, + pub line: Option<u32>, +} + +const MAX_LINE_LENGTH: usize = 79; + +pub static PACKAGE_MESSAGE_REGEX: Lazy<Regex> = + Lazy::new(|| Regex::new("^\\([a-zA-Z_\\-]+\\)\\s*(?P<msg>.*)$").unwrap()); + +pub static FILE_REGEX: Lazy<Regex> = + Lazy::new(|| Regex::new("\\((?P<file>[^\r\n()]+\\.(tex|sty|cls))").unwrap()); + +pub static TEX_ERROR_REGEX: Lazy<Regex> = Lazy::new(|| { + Regex::new("(?m)^! ((?P<msg1>(.|\r|\n)*?)\r?\nl\\.(?P<line>\\d+)|(?P<msg2>[^\r\n]*))").unwrap() +}); + +pub static WARNING_REGEX: Lazy<Regex> = + Lazy::new(|| Regex::new("(LaTeX|Package [a-zA-Z_\\-]+) Warning: (?P<msg>[^\r\n]*)").unwrap()); + +pub static BAD_BOX_REGEX: Lazy<Regex> = Lazy::new(|| { + Regex::new("(?P<msg>(Ov|Und)erfull \\\\[hv]box[^\r\n]*lines? (?P<line>\\d+)[^\r\n]*)").unwrap() +}); + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub struct Parse { + pub errors: Vec<BuildError>, +} + +pub fn parse(log: &str) -> Parse { + let log = prepare_log(log); + let mut ranges: Vec<FileRange> = FILE_REGEX + .find_iter(&log) + .map(|result| FileRange::create(&log, result)) + .collect(); + ranges.sort(); + + let tex_errors = extract_matches(&log, &ranges, &TEX_ERROR_REGEX, BuildErrorLevel::Error); + let warnings = extract_matches(&log, &ranges, &WARNING_REGEX, BuildErrorLevel::Warning); + let bad_boxes = extract_matches(&log, &ranges, &BAD_BOX_REGEX, BuildErrorLevel::Warning); + + Parse { + errors: vec![tex_errors, warnings, bad_boxes].concat(), + } +} + +fn extract_matches( + log: &str, + ranges: &[FileRange], + regex: &Regex, + level: BuildErrorLevel, +) -> Vec<BuildError> { + let mut errors = Vec::new(); + for result in regex.find_iter(&log) { + let captures = regex.captures(&log[result.start()..result.end()]).unwrap(); + let message = captures + .name("msg") + .or_else(|| captures.name("msg1")) + .or_else(|| captures.name("msg2")) + .unwrap() + .as_str() + .lines() + .next() + .unwrap_or_default() + .to_owned(); + + if let Some(range) = ranges.iter().find(|range| range.contains(result.start())) { + let line = captures + .name("line") + .map(|result| u32::from_str_radix(result.as_str(), 10).unwrap() - 1); + + errors.push(BuildError { + relative_path: range.path.clone(), + level, + message, + line, + }); + } + } + errors +} + +fn prepare_log(log: &str) -> String { + let mut old_lines = log.lines(); + let mut new_lines: Vec<String> = Vec::new(); + while let Some(line) = old_lines.next() { + if PACKAGE_MESSAGE_REGEX.is_match(&line) { + let captures = PACKAGE_MESSAGE_REGEX.captures(&line).unwrap(); + if let Some(last_line) = new_lines.last_mut() { + last_line.push(' '); + last_line.push_str(captures.name("msg").unwrap().as_str()); + } + } else if line.ends_with("...") { + let mut new_line = line[line.len() - 3..].to_owned(); + if let Some(old_line) = old_lines.next() { + new_line.push_str(&old_line); + } + new_lines.push(new_line); + } else if line.chars().count() == MAX_LINE_LENGTH { + let mut new_line = String::new(); + new_line.push_str(line); + if let Some(old_line) = old_lines.next() { + new_line.push_str(old_line); + } + new_lines.push(new_line); + } else { + new_lines.push(line.to_owned()); + } + } + new_lines.join("\n") +} + +#[derive(Debug, PartialEq, Eq, Clone)] +struct FileRange { + pub path: PathBuf, + pub start: usize, + pub end: usize, +} + +impl FileRange { + fn create(log: &str, result: Match) -> Self { + let mut balance = 1; + let mut end = result.start() + 1; + let chars = (&log[result.start() + 1..]).chars(); + for c in chars { + if balance <= 0 { + break; + } + + if c == '(' { + balance += 1; + } else if c == ')' { + balance -= 1; + } + end += c.len_utf8(); + } + + let captures = FILE_REGEX.captures(result.as_str()).unwrap(); + let path = PathBuf::from(captures.name("file").unwrap().as_str()); + Self { + path, + start: result.start(), + end, + } + } + + fn len(&self) -> usize { + self.end - self.start + 1 + } + + fn contains(&self, index: usize) -> bool { + index >= self.start && index <= self.end + } +} + +impl Ord for FileRange { + fn cmp(&self, other: &Self) -> Ordering { + self.len().cmp(&other.len()) + } +} + +impl PartialOrd for FileRange { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + Some(self.cmp(other)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use indoc::indoc; + + #[test] + fn test_parse_001() { + let log = indoc! { + r#" + This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:50 + entering extended mode + restricted \write18 enabled. + %&-line parsing enabled. + **./parent.tex + (./parent.tex + LaTeX2e <2017-04-15> + Babel <3.10> and hyphenation patterns for 84 language(s) loaded. + (/TexLive/texmf-dist/tex/latex/base/article.cls + Document Class: article 2014/09/29 v1.4h Standard LaTeX document class + (/TexLive/texmf-dist/tex/latex/base/size10.clo + File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option) + ) + \c@part=\count79 + \c@section=\count80 + \c@subsection=\count81 + \c@subsubsection=\count82 + \c@paragraph=\count83 + \c@subparagraph=\count84 + \c@figure=\count85 + \c@table=\count86 + \abovecaptionskip=\skip41 + \belowcaptionskip=\skip42 + \bibindent=\dimen102 + ) + (/TexLive/texmf-dist/tex/latex/multirow/bigstrut.sty + Package: bigstrut 2016/11/25 v2.2 Provide larger struts in tabulars + \bigstrutjot=\dimen103 + ) + (/TexLive/texmf-dist/tex/latex/multirow/multirow.sty + Package: multirow 2016/11/25 v2.2 Span multiple rows of a table + \multirow@colwidth=\skip43 + \multirow@cntb=\count87 + \multirow@dima=\skip44 + ) (./parent.aux) + \openout1 = `parent.aux'. + + LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 6. + LaTeX Font Info: ... okay on input line 6. + LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 6. + LaTeX Font Info: ... okay on input line 6. + LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 6. + LaTeX Font Info: ... okay on input line 6. + LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 6. + LaTeX Font Info: ... okay on input line 6. + LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 6. + LaTeX Font Info: ... okay on input line 6. + LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 6. + LaTeX Font Info: ... okay on input line 6. + + Overfull \hbox (200.00162pt too wide) in paragraph at lines 8--9 + []\OT1/cmr/m/n/10 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + [] + + LaTeX Font Info: External font `cmex10' loaded for size + (Font) <7> on input line 11. + LaTeX Font Info: External font `cmex10' loaded for size + (Font) <5> on input line 11. + + Overfull \vbox (3.19998pt too high) detected at line 23 + [] + + [1 + + {/TexLive/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (./parent.aux) ) + Here is how much of TeX's memory you used: + 265 strings out of 492995 + 3121 string characters out of 6138727 + 55074 words of memory out of 5000000 + 3896 multiletter control sequences out of 15000+600000 + 3640 words of font info for 14 fonts, out of 8000000 for 9000 + 1141 hyphenation exceptions out of 8191 + 23i,20n,20p,124b,282s stack positions out of 5000i,500n,10000p,200000b,80000s + </ + TexLive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb></TexLive/texmf-d + ist/fonts/type1/public/amsfonts/cm/cmr7.pfb> + Output written on parent.pdf (1 page, 17505 bytes). + PDF statistics: + 16 PDF objects out of 1000 (max. 8388607) + 10 compressed objects within 1 object stream + 0 named destinations out of 1000 (max. 500000) + 1 words of extra memory for PDF output out of 10000 (max. 10000000)"#, + }; + + assert_eq!( + parse(&log).errors, + vec![ + BuildError { + relative_path: "./parent.tex".into(), + level: BuildErrorLevel::Warning, + message: "Overfull \\hbox (200.00162pt too wide) in paragraph at lines 8--9" + .into(), + line: Some(7), + }, + BuildError { + relative_path: "./parent.tex".into(), + level: BuildErrorLevel::Warning, + message: "Overfull \\vbox (3.19998pt too high) detected at line 23".into(), + line: Some(22), + } + ] + ); + } + + #[test] + fn test_parse_002() { + let log = indoc! { + r#" + This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:52 + entering extended mode + restricted \write18 enabled. + %&-line parsing enabled. + **./parent.tex + (./parent.tex + LaTeX2e <2017-04-15> + Babel <3.10> and hyphenation patterns for 84 language(s) loaded. + (/TexLive/texmf-dist/tex/latex/base/article.cls + Document Class: article 2014/09/29 v1.4h Standard LaTeX document class + (/TexLive/texmf-dist/tex/latex/base/size10.clo + File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option) + ) + \c@part=\count79 + \c@section=\count80 + \c@subsection=\count81 + \c@subsubsection=\count82 + \c@paragraph=\count83 + \c@subparagraph=\count84 + \c@figure=\count85 + \c@table=\count86 + \abovecaptionskip=\skip41 + \belowcaptionskip=\skip42 + \bibindent=\dimen102 + ) (./parent.aux (./child.aux)) + \openout1 = `parent.aux'. + + LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 4. + LaTeX Font Info: ... okay on input line 4. + LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 4. + LaTeX Font Info: ... okay on input line 4. + LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 4. + LaTeX Font Info: ... okay on input line 4. + LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 4. + LaTeX Font Info: ... okay on input line 4. + LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 4. + LaTeX Font Info: ... okay on input line 4. + LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 4. + LaTeX Font Info: ... okay on input line 4. + (./child.tex + ! Undefined control sequence. + l.1 \foo + + The control sequence at the end of the top line + of your error message was never \def'ed. If you have + misspelled it (e.g., `\hobx'), type `I' and the correct + spelling (e.g., `I\hbox'). Otherwise just continue, + and I'll forget about whatever was undefined. + + ) (./parent.aux) ) + Here is how much of TeX's memory you used: + 205 strings out of 492995 + 2149 string characters out of 6138727 + 54074 words of memory out of 5000000 + 3841 multiletter control sequences out of 15000+600000 + 3640 words of font info for 14 fonts, out of 8000000 for 9000 + 1141 hyphenation exceptions out of 8191 + 23i,1n,17p,116b,36s stack positions out of 5000i,500n,10000p,200000b,80000s + + No pages of output. + PDF statistics: + 0 PDF objects out of 1000 (max. 8388607) + 0 named destinations out of 1000 (max. 500000) + 1 words of extra memory for PDF output out of 10000 (max. 10000000)"#, + }; + + assert_eq!( + parse(&log).errors, + vec![BuildError { + relative_path: "./child.tex".into(), + level: BuildErrorLevel::Error, + message: "Undefined control sequence.".into(), + line: Some(0) + }] + ); + } + + #[test] + fn test_parse_003() { + let log = indoc! { + r#" + This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:51 + entering extended mode + restricted \write18 enabled. + %&-line parsing enabled. + **./parent.tex + (./parent.tex + LaTeX2e <2017-04-15> + Babel <3.10> and hyphenation patterns for 84 language(s) loaded. + (/TexLive/texmf-dist/tex/latex/base/article.cls + Document Class: article 2014/09/29 v1.4h Standard LaTeX document class + (/TexLive/texmf-dist/tex/latex/base/size10.clo + File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option) + ) + \c@part=\count79 + \c@section=\count80 + \c@subsection=\count81 + \c@subsubsection=\count82 + \c@paragraph=\count83 + \c@subparagraph=\count84 + \c@figure=\count85 + \c@table=\count86 + \abovecaptionskip=\skip41 + \belowcaptionskip=\skip42 + \bibindent=\dimen102 + ) (./parent.aux) + \openout1 = `parent.aux'. + + LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 4. + LaTeX Font Info: ... okay on input line 4. + LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 4. + LaTeX Font Info: ... okay on input line 4. + LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 4. + LaTeX Font Info: ... okay on input line 4. + LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 4. + LaTeX Font Info: ... okay on input line 4. + LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 4. + LaTeX Font Info: ... okay on input line 4. + LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 4. + LaTeX Font Info: ... okay on input line 4. + + LaTeX Warning: Citation `foo' on page 1 undefined on input line 6. + + [1 + + {/TexLive/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (./parent.aux) + + LaTeX Warning: There were undefined references. + + ) + Here is how much of TeX's memory you used: + 204 strings out of 492995 + 2142 string characters out of 6138727 + 54074 words of memory out of 5000000 + 3842 multiletter control sequences out of 15000+600000 + 3948 words of font info for 15 fonts, out of 8000000 for 9000 + 1141 hyphenation exceptions out of 8191 + 23i,4n,21p,116b,107s stack positions out of 5000i,500n,10000p,200000b,80000s + </TexLive/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb></TexLive/ + texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb> + Output written on parent.pdf (1 page, 17339 bytes). + PDF statistics: + 16 PDF objects out of 1000 (max. 8388607) + 10 compressed objects within 1 object stream + 0 named destinations out of 1000 (max. 500000) + 1 words of extra memory for PDF output out of 10000 (max. 10000000)"#, + }; + + assert_eq!( + parse(&log).errors, + vec![ + BuildError { + relative_path: "./parent.tex".into(), + level: BuildErrorLevel::Warning, + message: "Citation `foo' on page 1 undefined on input line 6.".into(), + line: None + }, + BuildError { + relative_path: "./parent.tex".into(), + level: BuildErrorLevel::Warning, + message: "There were undefined references.".into(), + line: None, + } + ] + ); + } + + #[test] + fn test_parse_004() { + let log = indoc! { + r#" + This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:40 + entering extended mode + restricted \write18 enabled. + %&-line parsing enabled. + **./parent.tex + (./parent.tex + LaTeX2e <2017-04-15> + Babel <3.10> and hyphenation patterns for 84 language(s) loaded. + (/TexLive/texmf-dist/tex/latex/base/article.cls + Document Class: article 2014/09/29 v1.4h Standard LaTeX document class + (/TexLive/texmf-dist/tex/latex/base/size10.clo + File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option) + ) + \c@part=\count79 + \c@section=\count80 + \c@subsection=\count81 + \c@subsubsection=\count82 + \c@paragraph=\count83 + \c@subparagraph=\count84 + \c@figure=\count85 + \c@table=\count86 + \abovecaptionskip=\skip41 + \belowcaptionskip=\skip42 + \bibindent=\dimen102 + ) + (/TexLive/texmf-dist/tex/generic/babel/babel.sty + Package: babel 2017/05/19 3.10 The Babel package + + ! Package babel Error: Unknown option `foo'. Either you misspelled it + (babel) or the language definition file foo.ldf was not found. + + See the babel package documentation for explanation. + Type H <return> for immediate help. + ... + + l.393 \ProcessOptions* + + Valid options are: shorthands=, KeepShorthandsActive, + activeacute, activegrave, noconfigs, safe=, main=, math= + headfoot=, strings=, config=, hyphenmap=, or a language name. + + ! Package babel Error: You haven't specified a language option. + + See the babel package documentation for explanation. + Type H <return> for immediate help. + ... + + l.426 ...ry to proceed from here, type x to quit.} + + You need to specify a language, either as a global option + or as an optional argument to the \usepackage command; + You shouldn't try to proceed from here, type x to quit. + + ) (./parent.aux) + \openout1 = `parent.aux'. + + LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 5. + LaTeX Font Info: ... okay on input line 5. + LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 5. + LaTeX Font Info: ... okay on input line 5. + LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 5. + LaTeX Font Info: ... okay on input line 5. + LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 5. + LaTeX Font Info: ... okay on input line 5. + LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 5. + LaTeX Font Info: ... okay on input line 5. + LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 5. + LaTeX Font Info: ... okay on input line 5. + (./parent.aux) ) + Here is how much of TeX's memory you used: + 304 strings out of 492995 + 3421 string characters out of 6138727 + 56035 words of memory out of 5000000 + 3938 multiletter control sequences out of 15000+600000 + 3640 words of font info for 14 fonts, out of 8000000 for 9000 + 1141 hyphenation exceptions out of 8191 + 23i,1n,17p,116b,36s stack positions out of 5000i,500n,10000p,200000b,80000s + + No pages of output. + PDF statistics: + 0 PDF objects out of 1000 (max. 8388607) + 0 named destinations out of 1000 (max. 500000) + 1 words of extra memory for PDF output out of 10000 (max. 10000000)"#, + }; + + assert_eq!( + parse(&log).errors, + vec![ + BuildError { + relative_path: "/TexLive/texmf-dist/tex/generic/babel/babel.sty".into(), + level: BuildErrorLevel::Error, + message: "Package babel Error: Unknown option `foo'. Either you misspelled it or the language definition file foo.ldf was not found.".into(), + line: Some(392) + }, + BuildError { + relative_path: "/TexLive/texmf-dist/tex/generic/babel/babel.sty".into(), + level: BuildErrorLevel::Error, + message: "Package babel Error: You haven't specified a language option.".into(), + line: Some(425) + } + ] + ); + } + + #[test] + fn test_parse_005() { + let log = indoc! { + r#" + This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:45 + entering extended mode + restricted \write18 enabled. + %&-line parsing enabled. + **./parent.tex + (./parent.tex + LaTeX2e <2017-04-15> + Babel <3.10> and hyphenation patterns for 84 language(s) loaded. + (/TexLive/texmf-dist/tex/latex/base/article.cls + Document Class: article 2014/09/29 v1.4h Standard LaTeX document class + (/TexLive/texmf-dist/tex/latex/base/size10.clo + File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option) + ) + \c@part=\count79 + \c@section=\count80 + \c@subsection=\count81 + \c@subsubsection=\count82 + \c@paragraph=\count83 + \c@subparagraph=\count84 + \c@figure=\count85 + \c@table=\count86 + \abovecaptionskip=\skip41 + \belowcaptionskip=\skip42 + \bibindent=\dimen102 + ) + (/TexLive/texmf-dist/tex/generic/babel/babel.sty + Package: babel 2017/05/19 3.10 The Babel package + + (/TexLive/texmf-dist/tex/generic/babel-spanish/spanish.ldf + Language: spanish.ldf 2016/03/03 v5.0p Spanish support from the babel system + + (/TexLive/texmf-dist/tex/generic/babel/babel.def + File: babel.def 2017/05/19 3.10 Babel common definitions + \babel@savecnt=\count87 + \U@D=\dimen103 + ) + \es@quottoks=\toks14 + \es@quotdepth=\count88 + Package babel Info: Making " an active character on input line 561. + Package babel Info: Making . an active character on input line 662. + Package babel Info: Making < an active character on input line 707. + Package babel Info: Making > an active character on input line 707. + )) + (/TexLive/texmf-dist/tex/latex/biblatex/biblatex.sty + Package: biblatex 2016/12/05 v3.7 programmable bibliographies (PK/JW/AB) + + (/TexLive/texmf-dist/tex/generic/oberdiek/pdftexcmds.sty + Package: pdftexcmds 2017/03/19 v0.25 Utility functions of pdfTeX for LuaTeX (HO + ) + + (/TexLive/texmf-dist/tex/generic/oberdiek/infwarerr.sty + Package: infwarerr 2016/05/16 v1.4 Providing info/warning/error messages (HO) + ) + (/TexLive/texmf-dist/tex/generic/oberdiek/ifluatex.sty + Package: ifluatex 2016/05/16 v1.4 Provides the ifluatex switch (HO) + Package ifluatex Info: LuaTeX not detected. + ) + (/TexLive/texmf-dist/tex/generic/oberdiek/ltxcmds.sty + Package: ltxcmds 2016/05/16 v1.23 LaTeX kernel commands for general use (HO) + ) + (/TexLive/texmf-dist/tex/generic/oberdiek/ifpdf.sty + Package: ifpdf 2017/03/15 v3.2 Provides the ifpdf switch + ) + Package pdftexcmds Info: LuaTeX not detected. + Package pdftexcmds Info: \pdf@primitive is available. + Package pdftexcmds Info: \pdf@ifprimitive is available. + Package pdftexcmds Info: \pdfdraftmode found. + ) + (/TexLive/texmf-dist/tex/latex/etoolbox/etoolbox.sty + Package: etoolbox 2017/01/02 v2.4 e-TeX tools for LaTeX (JAW) + \etb@tempcnta=\count89 + ) + (/TexLive/texmf-dist/tex/latex/graphics/keyval.sty + Package: keyval 2014/10/28 v1.15 key=value parser (DPC) + \KV@toks@=\toks15 + ) + (/TexLive/texmf-dist/tex/latex/oberdiek/kvoptions.sty + Package: kvoptions 2016/05/16 v3.12 Key value format for package options (HO) + + (/TexLive/texmf-dist/tex/generic/oberdiek/kvsetkeys.sty + Package: kvsetkeys 2016/05/16 v1.17 Key value parser (HO) + + (/TexLive/texmf-dist/tex/generic/oberdiek/etexcmds.sty + Package: etexcmds 2016/05/16 v1.6 Avoid name clashes with e-TeX commands (HO) + Package etexcmds Info: Could not find \expanded. + (etexcmds) That can mean that you are not using pdfTeX 1.50 or + (etexcmds) that some package has redefined \expanded. + (etexcmds) In the latter case, load this package earlier. + ))) + (/TexLive/texmf-dist/tex/latex/logreq/logreq.sty + Package: logreq 2010/08/04 v1.0 xml request logger + \lrq@indent=\count90 + + (/TexLive/texmf-dist/tex/latex/logreq/logreq.def + File: logreq.def 2010/08/04 v1.0 logreq spec v1.0 + )) + (/TexLive/texmf-dist/tex/latex/base/ifthen.sty + Package: ifthen 2014/09/29 v1.1c Standard LaTeX ifthen package (DPC) + ) + (/TexLive/texmf-dist/tex/latex/url/url.sty + \Urlmuskip=\muskip10 + Package: url 2013/09/16 ver 3.4 Verb mode for urls, etc. + ) + (/TexLive/texmf-dist/tex/generic/xstring/xstring.sty + (/TexLive/texmf-dist/tex/generic/xstring/xstring.tex + \@xs@message=\write3 + \integerpart=\count91 + \decimalpart=\count92 + ) + Package: xstring 2013/10/13 v1.7c String manipulations (C Tellechea) + ) + \c@tabx@nest=\count93 + \c@listtotal=\count94 + \c@listcount=\count95 + \c@liststart=\count96 + \c@liststop=\count97 + \c@citecount=\count98 + \c@citetotal=\count99 + \c@multicitecount=\count100 + \c@multicitetotal=\count101 + \c@instcount=\count102 + \c@maxnames=\count103 + \c@minnames=\count104 + \c@maxitems=\count105 + \c@minitems=\count106 + \c@citecounter=\count107 + \c@savedcitecounter=\count108 + \c@uniquelist=\count109 + \c@uniquename=\count110 + \c@refsection=\count111 + \c@refsegment=\count112 + \c@maxextratitle=\count113 + \c@maxextratitleyear=\count114 + \c@maxextrayear=\count115 + \c@maxextraalpha=\count116 + \c@abbrvpenalty=\count117 + \c@highnamepenalty=\count118 + \c@lownamepenalty=\count119 + \c@maxparens=\count120 + \c@parenlevel=\count121 + \blx@tempcnta=\count122 + \blx@tempcntb=\count123 + \blx@tempcntc=\count124 + \blx@maxsection=\count125 + \blx@maxsegment@0=\count126 + \blx@notetype=\count127 + \blx@parenlevel@text=\count128 + \blx@parenlevel@foot=\count129 + \blx@sectionciteorder@0=\count130 + \labelnumberwidth=\skip43 + \labelalphawidth=\skip44 + \biblabelsep=\skip45 + \bibitemsep=\skip46 + \bibnamesep=\skip47 + \bibinitsep=\skip48 + \bibparsep=\skip49 + \bibhang=\skip50 + \blx@bcfin=\read1 + \blx@bcfout=\write4 + \c@mincomprange=\count131 + \c@maxcomprange=\count132 + \c@mincompwidth=\count133 + Package biblatex Info: Trying to load biblatex default data model... + Package biblatex Info: ... file 'blx-dm.def' found. + + (/TexLive/texmf-dist/tex/latex/biblatex/blx-dm.def) + Package biblatex Info: Trying to load biblatex style data model... + Package biblatex Info: ... file 'ieee.dbx' not found. + Package biblatex Info: Trying to load biblatex custom data model... + Package biblatex Info: ... file 'biblatex-dm.cfg' not found. + \c@afterword=\count134 + \c@savedafterword=\count135 + \c@annotator=\count136 + \c@savedannotator=\count137 + \c@author=\count138 + \c@savedauthor=\count139 + \c@bookauthor=\count140 + \c@savedbookauthor=\count141 + \c@commentator=\count142 + \c@savedcommentator=\count143 + \c@editor=\count144 + \c@savededitor=\count145 + \c@editora=\count146 + \c@savededitora=\count147 + \c@editorb=\count148 + \c@savededitorb=\count149 + \c@editorc=\count150 + \c@savededitorc=\count151 + \c@foreword=\count152 + \c@savedforeword=\count153 + \c@holder=\count154 + \c@savedholder=\count155 + \c@introduction=\count156 + \c@savedintroduction=\count157 + \c@namea=\count158 + \c@savednamea=\count159 + \c@nameb=\count160 + \c@savednameb=\count161 + \c@namec=\count162 + \c@savednamec=\count163 + \c@translator=\count164 + \c@savedtranslator=\count165 + \c@shortauthor=\count166 + \c@savedshortauthor=\count167 + \c@shorteditor=\count168 + \c@savedshorteditor=\count169 + \c@labelname=\count170 + \c@savedlabelname=\count171 + \c@institution=\count172 + \c@savedinstitution=\count173 + \c@lista=\count174 + \c@savedlista=\count175 + \c@listb=\count176 + \c@savedlistb=\count177 + \c@listc=\count178 + \c@savedlistc=\count179 + \c@listd=\count180 + \c@savedlistd=\count181 + \c@liste=\count182 + \c@savedliste=\count183 + \c@listf=\count184 + \c@savedlistf=\count185 + \c@location=\count186 + \c@savedlocation=\count187 + \c@organization=\count188 + \c@savedorganization=\count189 + \c@origlocation=\count190 + \c@savedoriglocation=\count191 + \c@origpublisher=\count192 + \c@savedorigpublisher=\count193 + \c@publisher=\count194 + \c@savedpublisher=\count195 + \c@language=\count196 + \c@savedlanguage=\count197 + \c@pageref=\count198 + \c@savedpageref=\count199 + \shorthandwidth=\skip51 + \shortjournalwidth=\skip52 + \shortserieswidth=\skip53 + \shorttitlewidth=\skip54 + \shortauthorwidth=\skip55 + \shorteditorwidth=\skip56 + Package biblatex Info: Trying to load compatibility code... + Package biblatex Info: ... file 'blx-compat.def' found. + + (/TexLive/texmf-dist/tex/latex/biblatex/blx-compat.def + File: blx-compat.def 2016/12/05 v3.7 biblatex compatibility (PK/JW/AB) + ) + Package biblatex Info: Trying to load generic definitions... + Package biblatex Info: ... file 'biblatex.def' found. + + (/TexLive/texmf-dist/tex/latex/biblatex/biblatex.def + File: biblatex.def 2016/12/05 v3.7 biblatex compatibility (PK/JW/AB) + \c@textcitecount=\count266 + \c@textcitetotal=\count267 + \c@textcitemaxnames=\count268 + \c@biburlnumpenalty=\count269 + \c@biburlucpenalty=\count270 + \c@biburllcpenalty=\count271 + \c@smartand=\count272 + ) + Package biblatex Info: Trying to load bibliography style 'ieee'... + Package biblatex Info: ... file 'ieee.bbx' found. + + (/TexLive/texmf-dist/tex/latex/biblatex-ieee/ieee.bbx + File: ieee.bbx 2017/03/27 v1.2d biblatex bibliography style + Package biblatex Info: Trying to load bibliography style 'numeric-comp'... + Package biblatex Info: ... file 'numeric-comp.bbx' found. + + (/TexLive/texmf-dist/tex/latex/biblatex/bbx/numeric-comp.bbx + File: numeric-comp.bbx 2016/12/05 v3.7 biblatex bibliography style (PK/JW/AB) + Package biblatex Info: Trying to load bibliography style 'numeric'... + Package biblatex Info: ... file 'numeric.bbx' found. + + (/TexLive/texmf-dist/tex/latex/biblatex/bbx/numeric.bbx + File: numeric.bbx 2016/12/05 v3.7 biblatex bibliography style (PK/JW/AB) + Package biblatex Info: Trying to load bibliography style 'standard'... + Package biblatex Info: ... file 'standard.bbx' found. + + (/TexLive/texmf-dist/tex/latex/biblatex/bbx/standard.bbx + File: standard.bbx 2016/12/05 v3.7 biblatex bibliography style (PK/JW/AB) + \c@bbx:relatedcount=\count273 + \c@bbx:relatedtotal=\count274 + )))) + Package biblatex Info: Trying to load citation style 'ieee'... + Package biblatex Info: ... file 'ieee.cbx' found. + + (/TexLive/texmf-dist/tex/latex/biblatex-ieee/ieee.cbx + File: ieee.cbx 2017/03/27 v1.2d biblatex citation style + Package biblatex Info: Trying to load citation style 'numeric-comp'... + Package biblatex Info: ... file 'numeric-comp.cbx' found. + + (/TexLive/texmf-dist/tex/latex/biblatex/cbx/numeric-comp.cbx + File: numeric-comp.cbx 2016/12/05 v3.7 biblatex citation style (PK/JW/AB) + \c@cbx@tempcnta=\count275 + \c@cbx@tempcntb=\count276 + Package biblatex Info: Redefining '\cite'. + Package biblatex Info: Redefining '\parencite'. + Package biblatex Info: Redefining '\footcite'. + Package biblatex Info: Redefining '\footcitetext'. + Package biblatex Info: Redefining '\smartcite'. + Package biblatex Info: Redefining '\supercite'. + Package biblatex Info: Redefining '\textcite'. + Package biblatex Info: Redefining '\textcites'. + Package biblatex Info: Redefining '\cites'. + Package biblatex Info: Redefining '\parencites'. + Package biblatex Info: Redefining '\smartcites'. + ) + Package biblatex Info: Redefining '\cite'. + Package biblatex Info: Redefining '\cites'. + ) + Package biblatex Info: Trying to load configuration file... + Package biblatex Info: ... file 'biblatex.cfg' found. + + (/TexLive/texmf-dist/tex/latex/biblatex/biblatex.cfg + File: biblatex.cfg + )) + Package biblatex Info: Trying to load language 'spanish'... + Package biblatex Info: ... file 'spanish.lbx' found. + + (/TexLive/texmf-dist/tex/latex/biblatex/lbx/spanish.lbx + File: spanish.lbx 2016/12/05 v3.7 biblatex localization (PK/JW/AB) + ) + + Package biblatex Warning: 'babel/polyglossia' detected but 'csquotes' missing. + (biblatex) Loading 'csquotes' recommended. + + \@quotelevel=\count277 + \@quotereset=\count278 + (./parent.aux) + \openout1 = `parent.aux'. + + LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 6. + LaTeX Font Info: ... okay on input line 6. + LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 6. + LaTeX Font Info: ... okay on input line 6. + LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 6. + LaTeX Font Info: ... okay on input line 6. + LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 6. + LaTeX Font Info: ... okay on input line 6. + LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 6. + LaTeX Font Info: ... okay on input line 6. + LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 6. + LaTeX Font Info: ... okay on input line 6. + LaTeX Info: Redefining \sptext on input line 6. + LaTeX Info: Redefining \. on input line 6. + LaTeX Info: Redefining \% on input line 6. + Package biblatex Info: No input encoding detected. + (biblatex) Assuming 'ascii'. + Package biblatex Info: Automatic encoding selection. + (biblatex) Assuming data encoding 'ascii'. + \openout4 = `parent.bcf'. + + Package biblatex Info: Trying to load bibliographic data... + Package biblatex Info: ... file 'parent.bbl' not found. + + No file parent.bbl. + Package biblatex Info: Reference section=0 on input line 6. + Package biblatex Info: Reference segment=0 on input line 6. + (./parent.aux) + + LaTeX Warning: There were undefined references. + + Package biblatex Warning: Please (re)run Biber on the file: + (biblatex) parent + (biblatex) and rerun LaTeX afterwards. + + Package logreq Info: Writing requests to 'parent.run.xml'. + \openout1 = `parent.run.xml'. + + ) + Here is how much of TeX's memory you used: + 7717 strings out of 492995 + 133301 string characters out of 6138727 + 557258 words of memory out of 5000000 + 11248 multiletter control sequences out of 15000+600000 + 3640 words of font info for 14 fonts, out of 8000000 for 9000 + 1141 hyphenation exceptions out of 8191 + 35i,1n,30p,856b,700s stack positions out of 5000i,500n,10000p,200000b,80000s + + No pages of output. + PDF statistics: + 0 PDF objects out of 1000 (max. 8388607) + 0 named destinations out of 1000 (max. 500000) + 1 words of extra memory for PDF output out of 10000 (max. 10000000)"#, + }; + + assert_eq!( + parse(&log).errors, + vec![ + BuildError { + relative_path: "./parent.tex".into(), + level: BuildErrorLevel::Warning, + message: "'babel/polyglossia' detected but 'csquotes' missing. Loading 'csquotes' recommended.".into(), + line: None + }, + BuildError { + relative_path: "./parent.tex".into(), + level: BuildErrorLevel::Warning, + message: "There were undefined references.".into(), + line: None, + }, + BuildError { + relative_path: "./parent.tex".into(), + level: BuildErrorLevel::Warning, + message: "Please (re)run Biber on the file: parent and rerun LaTeX afterwards.".into(), + line: None + } + ] + ); + } + + #[test] + fn test_parse_006() { + let log = indoc! { + r#" + This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/W32TeX) (preloaded format=pdflatex 2018.3.30) 26 DEC 2018 16:33 + entering extended mode + restricted \write18 enabled. + %&-line parsing enabled. + **./parent.tex + (./parent.tex + LaTeX2e <2017-04-15> + Babel <3.10> and hyphenation patterns for 84 language(s) loaded. + (/TexLive/texmf-dist/tex/latex/base/article.cls + Document Class: article 2014/09/29 v1.4h Standard LaTeX document class + (/TexLive/texmf-dist/tex/latex/base/size10.clo + File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option) + ) + \c@part=\count79 + \c@section=\count80 + \c@subsection=\count81 + \c@subsubsection=\count82 + \c@paragraph=\count83 + \c@subparagraph=\count84 + \c@figure=\count85 + \c@table=\count86 + \abovecaptionskip=\skip41 + \belowcaptionskip=\skip42 + \bibindent=\dimen102 + ) (./parent.aux + (./child.tex.aux)) + \openout1 = `parent.aux'. + + LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 3. + LaTeX Font Info: ... okay on input line 3. + LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 3. + LaTeX Font Info: ... okay on input line 3. + LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 3. + LaTeX Font Info: ... okay on input line 3. + LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 3. + LaTeX Font Info: ... okay on input line 3. + LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 3. + LaTeX Font Info: ... okay on input line 3. + LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 3. + LaTeX Font Info: ... okay on input line 3. + \openout2 = `child.aux'. + + (./child.tex) + ! Undefined control sequence. + l.7 \foo + + The control sequence at the end of the top line + of your error message was never \def'ed. If you have + misspelled it (e.g., `\hobx'), type `I' and the correct + spelling (e.g., `I\hbox'). Otherwise just continue, + and I'll forget about whatever was undefined. + + ! Missing $ inserted. + <inserted text> + $ + l.8 \bar + + I've inserted a begin-math/end-math symbol since I think + you left one out. Proceed, with fingers crossed. + + LaTeX Font Info: External font `cmex10' loaded for size + (Font) <7> on input line 8. + LaTeX Font Info: External font `cmex10' loaded for size + (Font) <5> on input line 8. + ! Undefined control sequence. + l.9 \baz + + The control sequence at the end of the top line + of your error message was never \def'ed. If you have + misspelled it (e.g., `\hobx'), type `I' and the correct + spelling (e.g., `I\hbox'). Otherwise just continue, + and I'll forget about whatever was undefined. + + ! Missing { inserted. + <to be read again> + \par + l.10 + + A left brace was mandatory here, so I've put one in. + You might want to delete and/or insert some corrections + so that I will find a matching right brace soon. + (If you're confused by all this, try typing `I}' now.) + + ! Missing $ inserted. + <inserted text> + $ + l.10 + + I've inserted a begin-math/end-math symbol since I think + you left one out. Proceed, with fingers crossed. + + ! Missing } inserted. + <inserted text> + } + l.10 + + I've inserted something that you may have forgotten. + (See the <inserted text> above.) + With luck, this will get me unwedged. But if you + really didn't forget anything, try typing `2' now; then + my insertion and my current dilemma will both disappear. + + [1 + + {/TexLive/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (./parent.aux + (./child.aux)) ) + Here is how much of TeX's memory you used: + 212 strings out of 492995 + 2238 string characters out of 6138727 + 54074 words of memory out of 5000000 + 3843 multiletter control sequences out of 15000+600000 + 3640 words of font info for 14 fonts, out of 8000000 for 9000 + 1141 hyphenation exceptions out of 8191 + 23i,4n,17p,116b,107s stack positions out of 5000i,500n,10000p,200000b,80000s + </TexLive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb + > + Output written on parent.pdf (1 page, 8329 bytes). + PDF statistics: + 12 PDF objects out of 1000 (max. 8388607) + 7 compressed objects within 1 object stream + 0 named destinations out of 1000 (max. 500000) + 1 words of extra memory for PDF output out of 10000 (max. 10000000)"#, + }; + + assert_eq!( + parse(&log).errors, + vec![ + BuildError { + relative_path: "./parent.tex".into(), + level: BuildErrorLevel::Error, + message: "Undefined control sequence.".into(), + line: Some(6) + }, + BuildError { + relative_path: "./parent.tex".into(), + level: BuildErrorLevel::Error, + message: "Missing $ inserted.".into(), + line: Some(7) + }, + BuildError { + relative_path: "./parent.tex".into(), + level: BuildErrorLevel::Error, + message: "Undefined control sequence.".into(), + line: Some(8) + }, + BuildError { + relative_path: "./parent.tex".into(), + level: BuildErrorLevel::Error, + message: "Missing { inserted.".into(), + line: Some(9) + }, + BuildError { + relative_path: "./parent.tex".into(), + level: BuildErrorLevel::Error, + message: "Missing $ inserted.".into(), + line: Some(9) + }, + BuildError { + relative_path: "./parent.tex".into(), + level: BuildErrorLevel::Error, + message: "Missing } inserted.".into(), + line: Some(9) + }, + ] + ); + } +} diff --git a/support/texlab/src/syntax/generic_ast.rs b/support/texlab/src/syntax/generic_ast.rs deleted file mode 100644 index 3311460c0a..0000000000 --- a/support/texlab/src/syntax/generic_ast.rs +++ /dev/null @@ -1,51 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::ops::Index; - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -pub struct AstNodeIndex(usize); - -#[derive(Debug, PartialEq, Eq, Clone, Default)] -pub struct Ast<T> { - nodes: Vec<T>, - edges: Vec<Vec<AstNodeIndex>>, -} - -impl<T> Ast<T> { - pub fn new() -> Self { - Self { - nodes: Vec::new(), - edges: Vec::new(), - } - } - - pub fn nodes(&self) -> Vec<AstNodeIndex> { - let mut nodes = Vec::new(); - for i in 0..self.nodes.len() { - nodes.push(AstNodeIndex(i)); - } - nodes - } - - pub fn add_node(&mut self, value: T) -> AstNodeIndex { - let node = AstNodeIndex(self.nodes.len()); - self.nodes.push(value); - self.edges.push(Vec::new()); - node - } - - pub fn add_edge(&mut self, parent: AstNodeIndex, child: AstNodeIndex) { - self.edges[parent.0].push(child); - } - - pub fn children<'a>(&'a self, parent: AstNodeIndex) -> impl Iterator<Item = AstNodeIndex> + 'a { - self.edges[parent.0].iter().map(|child| *child) - } -} - -impl<T> Index<AstNodeIndex> for Ast<T> { - type Output = T; - - fn index(&self, index: AstNodeIndex) -> &Self::Output { - &self.nodes[index.0] - } -} diff --git a/support/texlab/src/syntax/lang_data.rs b/support/texlab/src/syntax/lang_data.rs deleted file mode 100644 index 4ea7cc44da..0000000000 --- a/support/texlab/src/syntax/lang_data.rs +++ /dev/null @@ -1,231 +0,0 @@ -use once_cell::sync::Lazy; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LatexEnvironmentCommand { - pub name: String, - pub index: usize, -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LatexCitationCommand { - pub name: String, - pub index: usize, -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum LatexLabelReferenceSource { - Everything, - Math, -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum LatexLabelKind { - Definition, - Reference(LatexLabelReferenceSource), -} - -impl LatexLabelKind { - pub fn is_reference(self) -> bool { - match self { - LatexLabelKind::Definition => false, - LatexLabelKind::Reference(_) => true, - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LatexLabelCommand { - pub name: String, - pub index: usize, - pub kind: LatexLabelKind, -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LatexSectionCommand { - pub name: String, - pub index: usize, - pub level: i32, - pub prefix: String, -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum LatexIncludeKind { - Package, - Class, - Latex, - Bibliography, - Image, - Svg, - Pdf, - Everything, -} - -impl LatexIncludeKind { - pub fn extensions(self) -> Option<&'static [&'static str]> { - match self { - LatexIncludeKind::Package => Some(&["sty"]), - LatexIncludeKind::Class => Some(&["cls"]), - LatexIncludeKind::Latex => Some(&["tex"]), - LatexIncludeKind::Bibliography => Some(&["bib"]), - LatexIncludeKind::Image => Some(&["pdf", "png", "jpg", "jpeg", "bmp"]), - LatexIncludeKind::Svg => Some(&["svg"]), - LatexIncludeKind::Pdf => Some(&["pdf"]), - LatexIncludeKind::Everything => None, - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LatexIncludeCommand { - pub name: String, - pub index: usize, - pub kind: LatexIncludeKind, - pub include_extension: bool, -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LatexCommandDefinitionCommand { - pub name: String, - pub definition_index: usize, - pub arg_count_index: usize, - pub implementation_index: usize, -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LatexMathOperatorCommand { - pub name: String, - pub definition_index: usize, - pub implementation_index: usize, -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LatexTheoremDefinitionCommand { - pub name: String, - pub index: usize, -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LatexColorCommand { - pub name: String, - pub index: usize, -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LatexColorModelCommand { - pub name: String, - pub index: usize, -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum LatexGlossaryEntryKind { - General, - Acronym, -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LatexGlossaryEntryDefinitionCommand { - pub name: String, - pub label_index: usize, - pub kind: LatexGlossaryEntryKind, -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LatexGlossaryEntryReferenceCommand { - pub name: String, - pub index: usize, - pub kind: LatexGlossaryEntryKind, -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum BibtexEntryTypeCategory { - Misc, - String, - Article, - Book, - Collection, - Part, - Thesis, -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct BibtexEntryTypeDoc { - pub name: String, - pub category: BibtexEntryTypeCategory, - pub documentation: Option<String>, -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct BibtexFieldDoc { - pub name: String, - pub documentation: String, -} - -#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LanguageData { - pub environment_commands: Vec<LatexEnvironmentCommand>, - pub citation_commands: Vec<LatexCitationCommand>, - pub label_commands: Vec<LatexLabelCommand>, - pub section_commands: Vec<LatexSectionCommand>, - pub include_commands: Vec<LatexIncludeCommand>, - pub command_definition_commands: Vec<LatexCommandDefinitionCommand>, - pub math_operator_commands: Vec<LatexMathOperatorCommand>, - pub theorem_definition_commands: Vec<LatexTheoremDefinitionCommand>, - pub colors: Vec<String>, - pub color_commands: Vec<LatexColorCommand>, - pub color_model_commands: Vec<LatexColorModelCommand>, - pub glossary_entry_definition_commands: Vec<LatexGlossaryEntryDefinitionCommand>, - pub glossary_entry_reference_commands: Vec<LatexGlossaryEntryReferenceCommand>, - pub entry_types: Vec<BibtexEntryTypeDoc>, - pub fields: Vec<BibtexFieldDoc>, - pub pgf_libraries: Vec<String>, - pub tikz_libraries: Vec<String>, - pub math_environments: Vec<String>, - pub enum_environments: Vec<String>, -} - -impl LanguageData { - pub fn find_entry_type(&self, name: &str) -> Option<&BibtexEntryTypeDoc> { - let name = name.to_lowercase(); - self.entry_types - .iter() - .find(|ty| ty.name.to_lowercase() == name) - } - - pub fn entry_type_documentation(&self, name: &str) -> Option<&str> { - self.find_entry_type(name) - .and_then(|ty| ty.documentation.as_ref().map(AsRef::as_ref)) - } - - pub fn field_documentation(&self, name: &str) -> Option<&str> { - self.fields - .iter() - .find(|field| field.name.to_lowercase() == name.to_lowercase()) - .map(|field| field.documentation.as_ref()) - } -} - -pub static LANGUAGE_DATA: Lazy<LanguageData> = Lazy::new(|| { - const JSON: &str = include_str!("../../data/lang_data.json"); - serde_json::from_str(JSON).expect("Failed to deserialize language.json") -}); diff --git a/support/texlab/src/syntax/latex.rs b/support/texlab/src/syntax/latex.rs new file mode 100644 index 0000000000..7839c1795b --- /dev/null +++ b/support/texlab/src/syntax/latex.rs @@ -0,0 +1,36 @@ +mod analysis; +mod cst; +mod kind; +mod lexer; +mod parser; + +pub use self::{ + analysis::*, + cst::*, + kind::SyntaxKind::{self, *}, + parser::{parse, Parse}, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Language {} + +impl cstree::Language for Language { + type Kind = SyntaxKind; + + fn kind_from_raw(raw: cstree::SyntaxKind) -> Self::Kind { + assert!(raw.0 <= ROOT as u16); + unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) } + } + + fn kind_to_raw(kind: Self::Kind) -> cstree::SyntaxKind { + kind.into() + } +} + +pub type SyntaxNode = cstree::ResolvedNode<Language>; + +pub type SyntaxToken = cstree::ResolvedToken<Language>; + +pub type SyntaxElement = cstree::ResolvedElement<Language>; + +pub type SyntaxElementRef<'a> = cstree::ResolvedElementRef<'a, Language>; diff --git a/support/texlab/src/syntax/latex/analysis.rs b/support/texlab/src/syntax/latex/analysis.rs index af8b756f31..063d8e95f3 100644 --- a/support/texlab/src/syntax/latex/analysis.rs +++ b/support/texlab/src/syntax/latex/analysis.rs @@ -1,913 +1,36 @@ -use crate::{ - protocol::{Options, Position, Range, RangeExt, Uri}, - syntax::{generic_ast::AstNodeIndex, lang_data::*, latex::ast::*, text::SyntaxNode}, - tex::Resolver, +mod command; +mod distro_file; +mod environment; +mod explicit_link; +mod implicit_link; +mod label_name; +mod label_number; +mod theorem; +mod types; + +use crate::syntax::latex; + +pub use self::types::*; +use self::{ + command::analyze_command, + environment::analyze_begin, + explicit_link::{analyze_import, analyze_include}, + implicit_link::analyze_implicit_links, + label_name::analyze_label_name, + label_number::analyze_label_number, + theorem::analyze_theorem_definition, }; -use itertools::{iproduct, Itertools}; -use serde::{Deserialize, Serialize}; -use std::{borrow::Cow, ops::Deref, path::Path}; -#[derive(Debug, Clone)] -pub struct SymbolTableParams<'a> { - pub tree: Tree, - pub uri: &'a Uri, - pub resolver: &'a Resolver, - pub options: &'a Options, - pub current_dir: &'a Path, -} - -#[derive(Debug, Clone)] -pub struct SymbolTable { - pub(crate) tree: Tree, - pub commands: Vec<AstNodeIndex>, - pub environments: Vec<Environment>, - pub is_standalone: bool, - pub includes: Vec<Include>, - pub imports: Vec<Import>, - pub components: Vec<String>, - pub citations: Vec<Citation>, - pub command_definitions: Vec<CommandDefinition>, - pub glossary_entries: Vec<GlossaryEntry>, - pub equations: Vec<Equation>, - pub inlines: Vec<Inline>, - pub math_operators: Vec<MathOperator>, - pub theorem_definitions: Vec<TheoremDefinition>, - pub sections: Vec<Section>, - pub labels: Vec<Label>, - pub label_numberings: Vec<LabelNumbering>, - pub captions: Vec<Caption>, - pub items: Vec<Item>, -} - -impl SymbolTable { - pub fn analyze(params: SymbolTableParams) -> Self { - let SymbolTableParams { - tree, - uri, - resolver, - options, - current_dir, - } = params; - - let commands: Vec<_> = tree.commands().collect(); - let ctx = SymbolContext { - tree: &tree, - commands: &commands, - uri, - resolver, - options, - current_dir, - }; - - let mut environments = None; - let mut includes = None; - let mut imports = None; - let mut citations = None; - let mut command_definitions = None; - let mut glossary_entries = None; - let mut equations = None; - let mut inlines = None; - let mut math_operators = None; - let mut theorem_definitions = None; - let mut sections = None; - let mut labels = None; - let mut label_numberings = None; - let mut captions = None; - let mut items = None; - - rayon::scope(|s| { - s.spawn(|_| environments = Some(Environment::parse(ctx))); - s.spawn(|_| includes = Some(Include::parse(ctx))); - s.spawn(|_| imports = Some(Import::parse(ctx))); - s.spawn(|_| citations = Some(Citation::parse(ctx))); - s.spawn(|_| command_definitions = Some(CommandDefinition::parse(ctx))); - s.spawn(|_| glossary_entries = Some(GlossaryEntry::parse(ctx))); - s.spawn(|_| equations = Some(Equation::parse(ctx))); - s.spawn(|_| inlines = Some(Inline::parse(ctx))); - s.spawn(|_| math_operators = Some(MathOperator::parse(ctx))); - s.spawn(|_| theorem_definitions = Some(TheoremDefinition::parse(ctx))); - s.spawn(|_| sections = Some(Section::parse(ctx))); - s.spawn(|_| labels = Some(Label::parse(ctx))); - s.spawn(|_| label_numberings = Some(LabelNumbering::parse(ctx))); - s.spawn(|_| captions = Some(Caption::parse(ctx))); - s.spawn(|_| items = Some(Item::parse(ctx))); - }); - - let is_standalone = environments - .as_ref() - .unwrap() - .iter() - .any(|env| env.is_root(&tree)); - - let components = includes - .as_ref() - .unwrap() - .iter() - .flat_map(|include| include.components(&tree)) - .collect(); - - Self { - tree, - commands, - environments: environments.unwrap(), - is_standalone, - includes: includes.unwrap(), - imports: imports.unwrap(), - components, - citations: citations.unwrap(), - command_definitions: command_definitions.unwrap(), - glossary_entries: glossary_entries.unwrap(), - equations: equations.unwrap(), - inlines: inlines.unwrap(), - math_operators: math_operators.unwrap(), - theorem_definitions: theorem_definitions.unwrap(), - sections: sections.unwrap(), - labels: labels.unwrap(), - label_numberings: label_numberings.unwrap(), - captions: captions.unwrap(), - items: items.unwrap(), - } - } - - pub fn is_direct_child(&self, env: Environment, pos: Position) -> bool { - env.range(&self.tree).contains(pos) - && !self - .environments - .iter() - .filter(|e| e.left.parent != env.left.parent) - .filter(|e| env.range(&self.tree).contains(e.range(&self.tree).start)) - .any(|e| e.range(&self.tree).contains(pos)) - } - - pub fn is_enum_item(&self, enumeration: Environment, item: Item) -> bool { - let item_range = self.tree[item.parent].range(); - enumeration.range(&self.tree).contains(item_range.start) - && !self - .environments - .iter() - .filter(|env| env.left.parent != enumeration.left.parent) - .filter(|env| env.left.is_enum(&self.tree)) - .filter(|env| { - enumeration - .range(&self.tree) - .contains(env.range(&self.tree).start) - }) - .any(|env| env.range(&self.tree).contains(item_range.start)) - } - - pub fn find_label_by_range(&self, range: Range) -> Option<&Label> { - self.labels - .iter() - .filter(|label| label.kind == LatexLabelKind::Definition) - .filter(|label| label.names(&self).len() == 1) - .find(|label| range.contains(self[label.parent].range().start)) - } - - pub fn find_label_by_environment(&self, env: Environment) -> Option<&Label> { - self.labels - .iter() - .filter(|label| label.kind == LatexLabelKind::Definition) - .filter(|label| label.names(&self.tree).len() == 1) - .find(|label| self.is_direct_child(env, self.tree[label.parent].start())) - } -} - -impl Deref for SymbolTable { - type Target = Tree; - - fn deref(&self) -> &Self::Target { - &self.tree - } -} - -#[derive(Debug, Clone, Copy)] -pub struct SymbolContext<'a> { - tree: &'a Tree, - commands: &'a [AstNodeIndex], - uri: &'a Uri, - resolver: &'a Resolver, - options: &'a Options, - current_dir: &'a Path, -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct EnvironmentDelimiter { - pub parent: AstNodeIndex, -} - -impl EnvironmentDelimiter { - pub fn name(self, tree: &Tree) -> Option<&Token> { - tree.extract_word(self.parent, GroupKind::Group, 0) - } - - pub fn is_math(self, tree: &Tree) -> bool { - self.is_special(tree, LANGUAGE_DATA.math_environments.iter()) - } - - pub fn is_enum(self, tree: &Tree) -> bool { - self.is_special(tree, LANGUAGE_DATA.enum_environments.iter()) - } - - fn is_special<'a, I: Iterator<Item = &'a String>>(self, tree: &Tree, mut values: I) -> bool { - match self.name(tree) { - Some(name) => values.any(|env| env == name.text()), - None => false, - } - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct Environment { - pub left: EnvironmentDelimiter, - pub right: EnvironmentDelimiter, -} - -impl Environment { - pub fn is_root(self, tree: &Tree) -> bool { - self.left - .name(tree) - .iter() - .chain(self.right.name(tree).iter()) - .any(|name| name.text() == "document") - } - - pub fn range(self, tree: &Tree) -> Range { - let start = tree[self.left.parent].start(); - let end = tree[self.right.parent].end(); - Range::new(start, end) - } - - fn parse(ctx: SymbolContext) -> Vec<Self> { - let mut stack = Vec::new(); - let mut envs = Vec::new(); - for parent in ctx.commands { - if let Some((delim, delim_cmd)) = Self::parse_delimiter(ctx.tree, *parent) { - if delim_cmd.name.text() == "\\begin" { - stack.push(delim); - } else if let Some(left) = stack.pop() { - envs.push(Self { left, right: delim }); - } - } - } - envs - } - - fn parse_delimiter( - tree: &Tree, - parent: AstNodeIndex, - ) -> Option<(EnvironmentDelimiter, &Command)> { - let cmd = tree.as_command(parent)?; - if cmd.name.text() != "\\begin" && cmd.name.text() != "\\end" { - return None; - } - - let group = tree.extract_group(parent, GroupKind::Group, 0)?; - if tree.extract_word(parent, GroupKind::Group, 0).is_some() - || tree.children(group).next().is_none() - || tree.as_group(group)?.right.is_none() - { - Some((EnvironmentDelimiter { parent }, cmd)) - } else { - None - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Include { - pub parent: AstNodeIndex, - pub arg_index: usize, - pub kind: LatexIncludeKind, - pub all_targets: Vec<Vec<Uri>>, - pub include_extension: bool, -} - -impl Include { - pub fn paths<'a>(&self, tree: &'a Tree) -> Vec<&'a Token> { - tree.extract_comma_separated_words(self.parent, GroupKind::Group, self.arg_index) - .unwrap() - } - - pub fn components<'a>(&self, tree: &'a Tree) -> impl Iterator<Item = String> + 'a { - let kind = self.kind; - self.paths(tree) - .into_iter() - .filter_map(move |path| match kind { - LatexIncludeKind::Package => Some(format!("{}.sty", path.text())), - LatexIncludeKind::Class => Some(format!("{}.cls", path.text())), - LatexIncludeKind::Latex - | LatexIncludeKind::Bibliography - | LatexIncludeKind::Image - | LatexIncludeKind::Svg - | LatexIncludeKind::Pdf - | LatexIncludeKind::Everything => None, - }) - } - - fn parse(ctx: SymbolContext) -> Vec<Self> { - iproduct!(ctx.commands, LANGUAGE_DATA.include_commands.iter()) - .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc)) - .collect() - } - - fn parse_single( - ctx: SymbolContext, - parent: AstNodeIndex, - desc: &LatexIncludeCommand, - ) -> Option<Self> { - let cmd = ctx.tree.as_command(parent)?; - if cmd.name.text() != desc.name { - return None; - } - - let mut all_targets = Vec::new(); - let paths = ctx - .tree - .extract_comma_separated_words(parent, GroupKind::Group, desc.index)?; - for path in paths { - let mut targets = Vec::new(); - let base_url = base_url(ctx)?; - targets.push(base_url.join(path.text()).ok()?.into()); - - if let Some(extensions) = desc.kind.extensions() { - for extension in extensions { - let path = format!("{}.{}", path.text(), extension); - targets.push(base_url.join(&path).ok()?.into()); - } - } - - if let Some(target) = Self::resolve_distro_file(ctx, desc, path.text()) { - targets.push(target); - } - all_targets.push(targets); - } - - let include = Self { - parent, - arg_index: desc.index, - kind: desc.kind, - all_targets, - include_extension: desc.include_extension, - }; - Some(include) - } - - fn resolve_distro_file( - ctx: SymbolContext, - desc: &LatexIncludeCommand, - name: &str, - ) -> Option<Uri> { - let mut path = ctx.resolver.files_by_name.get(name); - if let Some(extensions) = desc.kind.extensions() { - for extension in extensions { - path = path.or_else(|| { - let full_name = format!("{}.{}", name, extension); - ctx.resolver.files_by_name.get(&full_name) - }); - } - } - path.and_then(|p| Uri::from_file_path(p).ok()) - } -} - -fn base_url(ctx: SymbolContext) -> Option<Uri> { - if let Some(root_directory) = ctx - .options - .latex - .as_ref() - .and_then(|opts| opts.root_directory.as_ref()) - { - let file_name = ctx.uri.path_segments()?.last()?; - let path = ctx.current_dir.join(root_directory).join(file_name); - Uri::from_file_path(path).ok() - } else { - Some(ctx.uri.clone()) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Import { - pub parent: AstNodeIndex, - pub targets: Vec<Uri>, -} - -impl Import { - pub fn dir<'a>(&self, tree: &'a Tree) -> &'a Token { - tree.extract_word(self.parent, GroupKind::Group, 0).unwrap() - } - - pub fn file<'a>(&self, tree: &'a Tree) -> &'a Token { - tree.extract_word(self.parent, GroupKind::Group, 1).unwrap() - } - - fn parse(ctx: SymbolContext) -> Vec<Self> { - ctx.commands - .iter() - .filter_map(|parent| Self::parse_single(ctx, *parent)) - .collect() - } - - fn parse_single(ctx: SymbolContext, parent: AstNodeIndex) -> Option<Self> { - let cmd = ctx.tree.as_command(parent)?; - if cmd.name.text() != "\\import" && cmd.name.text() != "\\subimport" { - return None; - } - - let dir = ctx.tree.extract_word(parent, GroupKind::Group, 0)?; - let file = ctx.tree.extract_word(parent, GroupKind::Group, 1)?; - - let mut targets = Vec::new(); - let base_url = base_url(ctx)?.join(dir.text()).ok()?; - targets.push(base_url.join(file.text()).ok()?.into()); - targets.push(base_url.join(&format!("{}.tex", file.text())).ok()?.into()); - Some(Self { parent, targets }) - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct Citation { - parent: AstNodeIndex, - arg_index: usize, -} - -impl Citation { - pub fn keys(self, tree: &Tree) -> Vec<&Token> { - tree.extract_comma_separated_words(self.parent, GroupKind::Group, self.arg_index) - .unwrap() - } - - fn parse(ctx: SymbolContext) -> Vec<Self> { - iproduct!(ctx.commands, LANGUAGE_DATA.citation_commands.iter()) - .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc)) - .collect() - } - - fn parse_single( - ctx: SymbolContext, - parent: AstNodeIndex, - desc: &LatexCitationCommand, - ) -> Option<Self> { - let cmd = ctx.tree.as_command(parent)?; - if cmd.name.text() != desc.name { - return None; - } - - ctx.tree - .extract_comma_separated_words(parent, GroupKind::Group, desc.index)?; - - Some(Self { - parent, - arg_index: desc.index, - }) - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct CommandDefinition { - pub parent: AstNodeIndex, - pub definition: AstNodeIndex, - pub definition_index: usize, - pub implementation: AstNodeIndex, - pub implementation_index: usize, - pub arg_count_index: usize, -} - -impl CommandDefinition { - pub fn definition_name(self, tree: &Tree) -> &str { - tree.as_command(self.definition).unwrap().name.text() - } - - fn parse(ctx: SymbolContext) -> Vec<Self> { - let def = LANGUAGE_DATA.command_definition_commands.iter(); - iproduct!(ctx.commands, def) - .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc)) - .collect() - } - - fn parse_single( - ctx: SymbolContext, - parent: AstNodeIndex, - desc: &LatexCommandDefinitionCommand, - ) -> Option<Self> { - let cmd = ctx.tree.as_command(parent)?; - if cmd.name.text() != desc.name { - return None; - } - - let group_kind = GroupKind::Group; - let implementation = - ctx.tree - .extract_group(parent, group_kind, desc.implementation_index)?; - - let def_group = ctx - .tree - .extract_group(parent, group_kind, desc.definition_index)?; - - let mut def_children = ctx.tree.children(def_group); - let definition = def_children.next()?; - ctx.tree.as_command(definition)?; - Some(Self { - parent, - definition, - definition_index: desc.definition_index, - implementation, - implementation_index: desc.implementation_index, - arg_count_index: desc.arg_count_index, - }) - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct GlossaryEntry { - pub parent: AstNodeIndex, - pub label_index: usize, - pub kind: LatexGlossaryEntryKind, -} - -impl GlossaryEntry { - pub fn label(self, tree: &Tree) -> &Token { - tree.extract_word(self.parent, GroupKind::Group, self.label_index) - .unwrap() - } - - fn parse(ctx: SymbolContext) -> Vec<Self> { - let entry = LANGUAGE_DATA.glossary_entry_definition_commands.iter(); - iproduct!(ctx.commands, entry) - .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc)) - .collect() - } - - fn parse_single( - ctx: SymbolContext, - parent: AstNodeIndex, - desc: &LatexGlossaryEntryDefinitionCommand, - ) -> Option<Self> { - let cmd = ctx.tree.as_command(parent)?; - if cmd.name.text() != desc.name { - return None; - } - - ctx.tree - .extract_word(parent, GroupKind::Group, desc.label_index)?; - - Some(Self { - parent, - label_index: desc.label_index, - kind: desc.kind, - }) - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct Equation { - pub left: AstNodeIndex, - pub right: AstNodeIndex, -} - -impl Equation { - pub fn range(self, tree: &Tree) -> Range { - let start = tree[self.left].start(); - let end = tree[self.right].end(); - Range::new(start, end) - } - - fn parse(ctx: SymbolContext) -> Vec<Self> { - let mut equations = Vec::new(); - let mut left = None; - for node in ctx.commands { - let cmd = ctx.tree.as_command(*node).unwrap(); - let name = cmd.name.text(); - if name == "\\[" || name == "\\(" { - left = Some(node); - } else if name == "\\]" || name == "\\)" { - if let Some(begin) = left { - equations.push(Self { - left: *begin, - right: *node, - }); - left = None; - } - } - } - equations - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct Inline { - pub left: AstNodeIndex, - pub right: AstNodeIndex, -} - -impl Inline { - pub fn range(self, tree: &Tree) -> Range { - let start = tree[self.left].start(); - let end = tree[self.right].end(); - Range::new(start, end) - } - - fn parse(ctx: SymbolContext) -> Vec<Self> { - let mut inlines = Vec::new(); - let mut left = None; - for node in ctx - .tree - .nodes() - .into_iter() - .filter(|node| ctx.tree.as_math(*node).is_some()) - .sorted_by_key(|node| ctx.tree[*node].start()) - { - if let Some(l) = left { - inlines.push(Inline { - left: l, - right: node, - }); - left = None; - } else { - left = Some(node); - } - } - inlines - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct MathOperator { - pub parent: AstNodeIndex, - pub definition: AstNodeIndex, - pub definition_index: usize, - pub implementation: AstNodeIndex, - pub implementation_index: usize, -} - -impl MathOperator { - pub fn definition_name(self, tree: &Tree) -> &str { - tree.as_command(self.definition).unwrap().name.text() - } - - fn parse(ctx: SymbolContext) -> Vec<Self> { - iproduct!(ctx.commands, LANGUAGE_DATA.math_operator_commands.iter()) - .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc)) - .collect() - } - - fn parse_single( - ctx: SymbolContext, - parent: AstNodeIndex, - desc: &LatexMathOperatorCommand, - ) -> Option<Self> { - let cmd = ctx.tree.as_command(parent)?; - if cmd.name.text() != desc.name { - return None; - } - - let group_kind = GroupKind::Group; - let def_group = ctx - .tree - .extract_group(parent, group_kind, desc.definition_index)?; - let implementation = - ctx.tree - .extract_group(parent, group_kind, desc.implementation_index)?; - - let mut def_children = ctx.tree.children(def_group); - let definition = def_children.next()?; - Some(Self { - parent, - definition, - definition_index: desc.definition_index, - implementation, - implementation_index: desc.implementation_index, - }) - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct TheoremDefinition { - pub parent: AstNodeIndex, - pub arg_index: usize, -} - -impl TheoremDefinition { - pub fn name(self, tree: &Tree) -> &Token { - tree.extract_word(self.parent, GroupKind::Group, self.arg_index) - .unwrap() - } - - fn parse(ctx: SymbolContext) -> Vec<Self> { - let thm = LANGUAGE_DATA.theorem_definition_commands.iter(); - iproduct!(ctx.commands, thm) - .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc)) - .collect() - } - - fn parse_single( - ctx: SymbolContext, - parent: AstNodeIndex, - desc: &LatexTheoremDefinitionCommand, - ) -> Option<Self> { - let cmd = ctx.tree.as_command(parent)?; - if cmd.name.text() != desc.name { - return None; - } - - let group_kind = GroupKind::Group; - ctx.tree.extract_word(parent, group_kind, desc.index)?; - - Some(Self { - parent, - arg_index: desc.index, - }) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Section { - pub parent: AstNodeIndex, - pub arg_index: usize, - pub level: i32, - pub prefix: Cow<'static, str>, -} - -impl Section { - pub fn print(&self, tree: &Tree) -> Option<String> { - tree.print_group_content(self.parent, GroupKind::Group, self.arg_index) - } - - fn parse(ctx: SymbolContext) -> Vec<Self> { - iproduct!(ctx.commands, LANGUAGE_DATA.section_commands.iter()) - .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc)) - .collect() - } - - fn parse_single( - ctx: SymbolContext, - parent: AstNodeIndex, - desc: &'static LatexSectionCommand, - ) -> Option<Self> { - let cmd = ctx.tree.as_command(parent)?; - if cmd.name.text() != desc.name { - return None; - } - - let group_kind = GroupKind::Group; - ctx.tree.extract_group(parent, group_kind, desc.index)?; - - Some(Self { - parent, - arg_index: desc.index, - level: desc.level, - prefix: Cow::from(&desc.prefix), - }) - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct Label { - pub parent: AstNodeIndex, - pub arg_index: usize, - pub kind: LatexLabelKind, -} - -impl Label { - pub fn names(self, tree: &Tree) -> Vec<&Token> { - tree.extract_comma_separated_words(self.parent, GroupKind::Group, self.arg_index) - .unwrap() - } - - fn parse(ctx: SymbolContext) -> Vec<Self> { - iproduct!(ctx.commands, LANGUAGE_DATA.label_commands.iter()) - .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc)) - .collect() - } - - fn parse_single( - ctx: SymbolContext, - parent: AstNodeIndex, - desc: &LatexLabelCommand, - ) -> Option<Self> { - let cmd = ctx.tree.as_command(parent)?; - if cmd.name.text() != desc.name { - return None; - } - - ctx.tree - .extract_comma_separated_words(parent, GroupKind::Group, desc.index)?; - - Some(Self { - parent, - arg_index: desc.index, - kind: desc.kind, - }) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LabelNumbering { - pub parent: AstNodeIndex, - pub number: String, -} - -impl LabelNumbering { - pub fn name<'a>(&self, tree: &'a Tree) -> &'a Token { - tree.extract_word(self.parent, GroupKind::Group, 0).unwrap() - } - - fn parse(ctx: SymbolContext) -> Vec<Self> { - ctx.commands - .iter() - .filter_map(|parent| Self::parse_single(ctx, *parent)) - .collect() - } - - fn parse_single(ctx: SymbolContext, parent: AstNodeIndex) -> Option<Self> { - let cmd = ctx.tree.as_command(parent)?; - if cmd.name.text() != "\\newlabel" { - return None; - } - - ctx.tree.extract_word(parent, GroupKind::Group, 0)?; - - let arg = ctx.tree.extract_group(parent, GroupKind::Group, 1)?; - let mut analyzer = FirstText::default(); - analyzer.visit(ctx.tree, arg); - Some(Self { - parent, - number: analyzer.text?, - }) - } -} - -#[derive(Debug, Default)] -struct FirstText { - text: Option<String>, -} - -impl Visitor for FirstText { - fn visit(&mut self, tree: &Tree, node: AstNodeIndex) { - if let Some(text) = tree.as_text(node) { - self.text = Some(text.words.iter().map(Token::text).join(" ")); - } - - if self.text.is_none() { - tree.walk(self, node); - } - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct Caption { - pub parent: AstNodeIndex, - pub arg_index: usize, -} - -impl Caption { - pub fn print(self, tree: &Tree) -> Option<String> { - tree.print_group_content(self.parent, GroupKind::Group, self.arg_index) - } - - fn parse(ctx: SymbolContext) -> Vec<Self> { - ctx.commands - .iter() - .flat_map(|parent| Self::parse_single(ctx, *parent)) - .collect() - } - - fn parse_single(ctx: SymbolContext, parent: AstNodeIndex) -> Option<Self> { - let cmd = ctx.tree.as_command(parent)?; - if cmd.name.text() != "\\caption" { - return None; - } - - ctx.tree.extract_group(parent, GroupKind::Group, 0)?; - Some(Self { - parent, - arg_index: 0, - }) - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct Item { - pub parent: AstNodeIndex, -} - -impl Item { - pub fn name(self, tree: &Tree) -> Option<String> { - tree.print_group_content(self.parent, GroupKind::Options, 0) - } - - fn parse(ctx: SymbolContext) -> Vec<Self> { - ctx.commands - .iter() - .filter_map(|parent| Self::parse_single(ctx, *parent)) - .collect() - } - - fn parse_single(ctx: SymbolContext, parent: AstNodeIndex) -> Option<Self> { - let cmd = ctx.tree.as_command(parent)?; - if cmd.name.text() != "\\item" { - return None; - } - - Some(Self { parent }) - } +pub fn analyze(context: &mut LatexAnalyzerContext, root: &latex::SyntaxNode) { + analyze_implicit_links(context); + for node in root.descendants() { + analyze_command(context, node) + .or_else(|| analyze_begin(context, node)) + .or_else(|| analyze_include(context, node)) + .or_else(|| analyze_import(context, node)) + .or_else(|| analyze_label_name(context, node)) + .or_else(|| analyze_label_number(context, node)) + .or_else(|| analyze_theorem_definition(context, node)); + } + context.extras.has_document_environment = context.extras.environment_names.contains("document"); } 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, +} diff --git a/support/texlab/src/syntax/latex/ast.rs b/support/texlab/src/syntax/latex/ast.rs deleted file mode 100644 index ab7c6863e1..0000000000 --- a/support/texlab/src/syntax/latex/ast.rs +++ /dev/null @@ -1,414 +0,0 @@ -use crate::{ - protocol::{Position, Range, RangeExt}, - syntax::{ - generic_ast::{Ast, AstNodeIndex}, - text::{Span, SyntaxNode}, - }, -}; -use serde::{Deserialize, Serialize}; -use std::ops::Deref; - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -pub enum TokenKind { - Word, - Command, - Math, - Comma, - BeginGroup, - EndGroup, - BeginOptions, - EndOptions, -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Token { - pub span: Span, - pub kind: TokenKind, -} - -impl Token { - pub fn new(span: Span, kind: TokenKind) -> Self { - Self { span, kind } - } - - pub fn text(&self) -> &str { - &self.span.text - } -} - -impl SyntaxNode for Token { - fn range(&self) -> Range { - self.span.range() - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -pub struct Root { - pub range: Range, -} - -impl SyntaxNode for Root { - fn range(&self) -> Range { - self.range - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -pub enum GroupKind { - Group, - Options, -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Group { - pub range: Range, - pub left: Token, - pub right: Option<Token>, - pub kind: GroupKind, -} - -impl SyntaxNode for Group { - fn range(&self) -> Range { - self.range - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Command { - pub range: Range, - pub name: Token, -} - -impl Command { - 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, - ) - } -} - -impl SyntaxNode for Command { - fn range(&self) -> Range { - self.range - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Text { - pub range: Range, - pub words: Vec<Token>, -} - -impl SyntaxNode for Text { - fn range(&self) -> Range { - self.range - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Comma { - pub range: Range, - pub token: Token, -} - -impl SyntaxNode for Comma { - fn range(&self) -> Range { - self.range - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Math { - pub range: Range, - pub token: Token, -} - -impl SyntaxNode for Math { - fn range(&self) -> Range { - self.range - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub enum Node { - Root(Root), - Group(Group), - Command(Command), - Text(Text), - Comma(Comma), - Math(Math), -} - -impl SyntaxNode for Node { - fn range(&self) -> Range { - match self { - Self::Root(root) => root.range(), - Self::Group(group) => group.range(), - Self::Command(cmd) => cmd.range(), - Self::Text(text) => text.range(), - Self::Comma(comma) => comma.range(), - Self::Math(math) => math.range(), - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct Tree { - pub inner: Ast<Node>, - pub root: AstNodeIndex, -} - -impl Deref for Tree { - type Target = Ast<Node>; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl Tree { - pub fn walk<V: Visitor>(&self, visitor: &mut V, parent: AstNodeIndex) { - for child in self.children(parent) { - visitor.visit(self, child); - } - } - - pub fn find(&self, pos: Position) -> Vec<AstNodeIndex> { - let mut finder = Finder::new(pos); - finder.visit(self, self.root); - finder.results - } - - pub fn find_command_by_short_name_range(&self, pos: Position) -> Option<AstNodeIndex> { - self.find(pos).into_iter().find(|node| { - self.as_command(*node) - .filter(|cmd| { - cmd.name.range().contains(pos) && cmd.name.start().character != pos.character - }) - .is_some() - }) - } - - pub fn print(&self, node: AstNodeIndex) -> String { - let start_position = self[node].start(); - let mut printer = Printer::new(start_position); - printer.visit(self, node); - printer.output - } - - pub fn commands<'a>(&'a self) -> impl Iterator<Item = AstNodeIndex> + 'a { - self.inner - .nodes() - .into_iter() - .filter(move |node| self.as_command(*node).is_some()) - } - - pub fn as_group(&self, node: AstNodeIndex) -> Option<&Group> { - if let Node::Group(group) = &self[node] { - Some(group) - } else { - None - } - } - - pub fn as_command(&self, node: AstNodeIndex) -> Option<&Command> { - if let Node::Command(cmd) = &self[node] { - Some(cmd) - } else { - None - } - } - - pub fn as_text(&self, node: AstNodeIndex) -> Option<&Text> { - if let Node::Text(text) = &self[node] { - Some(text) - } else { - None - } - } - - pub fn as_math(&self, node: AstNodeIndex) -> Option<&Math> { - if let Node::Math(math) = &self[node] { - Some(math) - } else { - None - } - } - - pub fn extract_group( - &self, - parent: AstNodeIndex, - group_kind: GroupKind, - index: usize, - ) -> Option<AstNodeIndex> { - self.children(parent) - .filter(|child| { - self.as_group(*child) - .filter(|group| group.kind == group_kind) - .is_some() - }) - .nth(index) - } - - pub fn extract_text( - &self, - parent: AstNodeIndex, - group_kind: GroupKind, - index: usize, - ) -> Option<&Text> { - let group = self.extract_group(parent, group_kind, index)?; - let mut contents = self.children(group); - let text = self.as_text(contents.next()?); - if contents.next().is_none() { - text - } else { - None - } - } - - pub fn extract_word( - &self, - parent: AstNodeIndex, - group_kind: GroupKind, - index: usize, - ) -> Option<&Token> { - let text = self.extract_text(parent, group_kind, index)?; - if text.words.len() == 1 { - Some(&text.words[0]) - } else { - None - } - } - - pub fn extract_comma_separated_words( - &self, - parent: AstNodeIndex, - group_kind: GroupKind, - index: usize, - ) -> Option<Vec<&Token>> { - let group = self.extract_group(parent, group_kind, index)?; - let mut words = Vec::new(); - for child in self.children(group) { - match &self[child] { - Node::Root(_) | Node::Group(_) | Node::Command(_) | Node::Math(_) => return None, - Node::Text(text) => { - for word in &text.words { - words.push(word); - } - } - Node::Comma(_) => (), - } - } - Some(words) - } - - pub fn print_group_content( - &self, - parent: AstNodeIndex, - group_kind: GroupKind, - index: usize, - ) -> Option<String> { - let arg = self.extract_group(parent, group_kind, index)?; - let text = self.print(arg); - self.as_group(arg)?.right.as_ref()?; - Some(text[1..text.len() - 1].trim().into()) - } -} - -pub trait Visitor { - fn visit(&mut self, tree: &Tree, node: AstNodeIndex); -} - -#[derive(Debug)] -struct Finder { - position: Position, - results: Vec<AstNodeIndex>, -} - -impl Finder { - fn new(position: Position) -> Self { - Self { - position, - results: Vec::new(), - } - } -} - -impl Visitor for Finder { - fn visit(&mut self, tree: &Tree, node: AstNodeIndex) { - if tree[node].range().contains(self.position) { - self.results.push(node); - tree.walk(self, node); - } - } -} - -#[derive(Debug)] -struct Printer { - output: String, - position: Position, -} - -impl Printer { - 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; - self.position.character = 0; - } - - while self.position.character < position.character { - self.output.push(' '); - self.position.character += 1; - } - - assert_eq!(self.position, position); - } - - fn print_token(&mut self, token: &Token) { - self.synchronize(token.start()); - self.output.push_str(token.text()); - self.position.character += token.end().character - token.start().character; - self.synchronize(token.end()); - } -} - -impl Visitor for Printer { - fn visit(&mut self, tree: &Tree, node: AstNodeIndex) { - match &tree[node] { - Node::Root(_) => tree.walk(self, node), - Node::Group(group) => { - self.print_token(&group.left); - tree.walk(self, node); - if let Some(right) = &group.right { - self.print_token(right); - } - } - Node::Command(cmd) => { - self.print_token(&cmd.name); - tree.walk(self, node); - } - Node::Text(text) => { - for word in &text.words { - self.print_token(word); - } - } - Node::Comma(comma) => { - self.print_token(&comma.token); - } - Node::Math(math) => { - self.print_token(&math.token); - } - } - } -} diff --git a/support/texlab/src/syntax/latex/cst.rs b/support/texlab/src/syntax/latex/cst.rs new file mode 100644 index 0000000000..0b839b706d --- /dev/null +++ b/support/texlab/src/syntax/latex/cst.rs @@ -0,0 +1,639 @@ +use cstree::TextRange; + +use crate::syntax::CstNode; + +use super::{Language, SyntaxKind::*, SyntaxNode, SyntaxToken}; + +macro_rules! cst_node { + ($name:ident, $($kind:pat),+) => { + #[derive(Clone)] + #[repr(transparent)] + pub struct $name<'a>(&'a SyntaxNode); + + impl<'a> CstNode<'a> for $name<'a> { + type Lang = Language; + + fn cast(node: &'a cstree::ResolvedNode<Self::Lang>) -> Option<Self> + where + Self: Sized, + { + match node.kind() { + $($kind => Some(Self(node)),)+ + _ => None, + } + } + + fn syntax(&self) -> &'a cstree::ResolvedNode<Self::Lang> { + &self.0 + } + + fn small_range(&self) -> TextRange { + let full_range = self.syntax().text_range(); + let start = full_range.start(); + let mut token = self.syntax().last_token(); + while let Some(current) = token { + if !matches!(current.kind(), WHITESPACE | COMMENT) { + return TextRange::new(start, current.text_range().end()); + } + token = current.prev_token(); + } + TextRange::new(start, start) + } + } + }; +} + +cst_node!(Text, TEXT); + +pub trait HasCurly<'a>: CstNode<'a, Lang = Language> { + fn left_curly(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == L_CURLY) + } + + fn right_curly(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == R_CURLY) + } + + fn content_text(&self) -> Option<String> { + self.left_curly()?; + self.right_curly()?; + let mut text = String::new(); + for child in self + .syntax() + .descendants_with_tokens() + .filter_map(|child| child.into_token()) + .filter(|token| !matches!(token.kind(), COMMENT)) + { + text.push_str(child.text()); + } + let text = text.trim_end(); + let text = text[1..text.len() - 1].trim().to_string(); + + Some(text) + } +} + +cst_node!(CurlyGroup, CURLY_GROUP); + +impl<'a> HasCurly<'a> for CurlyGroup<'a> {} + +impl<'a> CurlyGroup<'a> {} + +pub trait HasBrack<'a>: CstNode<'a, Lang = Language> { + fn left_brack(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == L_BRACK) + } + + fn right_brack(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == R_BRACK) + } + + fn content_text(&self) -> Option<String> { + self.left_brack()?; + self.right_brack()?; + let mut text = String::new(); + for child in self + .syntax() + .descendants_with_tokens() + .filter_map(|child| child.into_token()) + .filter(|token| !matches!(token.kind(), COMMENT)) + { + text.push_str(child.text()); + } + let text = text.trim_end(); + let text = text[1..text.len() - 1].trim().to_string(); + + Some(text) + } +} + +cst_node!(BrackGroup, BRACK_GROUP); + +impl<'a> BrackGroup<'a> {} + +impl<'a> HasBrack<'a> for BrackGroup<'a> {} + +pub trait HasParen<'a>: CstNode<'a, Lang = Language> { + fn left_paren(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == L_PAREN) + } + + fn right_paren(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == R_PAREN) + } +} + +cst_node!(ParenGroup, PAREN_GROUP); + +impl<'a> HasParen<'a> for ParenGroup<'a> {} + +cst_node!(MixedGroup, MIXED_GROUP); + +impl<'a> MixedGroup<'a> { + pub fn left_delim(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| matches!(node.kind().into(), L_BRACK | L_PAREN)) + } + + pub fn right_delim(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| matches!(node.kind().into(), R_BRACK | R_PAREN)) + } +} + +cst_node!(CurlyGroupWord, CURLY_GROUP_WORD); + +impl<'a> HasCurly<'a> for CurlyGroupWord<'a> {} + +impl<'a> CurlyGroupWord<'a> { + pub fn word(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == WORD) + } +} + +cst_node!(BrackGroupWord, BRACK_GROUP_WORD); + +impl<'a> HasBrack<'a> for BrackGroupWord<'a> {} + +impl<'a> BrackGroupWord<'a> { + pub fn word(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == WORD) + } +} + +cst_node!(CurlyGroupWordList, CURLY_GROUP_WORD_LIST); + +impl<'a> HasCurly<'a> for CurlyGroupWordList<'a> {} + +impl<'a> CurlyGroupWordList<'a> { + pub fn words(&self) -> impl Iterator<Item = &'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .filter(|node| node.kind() == WORD) + } +} + +cst_node!(CurlyGroupCommand, CURLY_GROUP_COMMAND); + +impl<'a> HasCurly<'a> for CurlyGroupCommand<'a> {} + +impl<'a> CurlyGroupCommand<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == GENERIC_COMMAND_NAME) + } +} + +cst_node!(Key, KEY); + +impl<'a> Key<'a> { + pub fn words(&self) -> impl Iterator<Item = &'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .filter(|node| node.kind() == WORD) + } +} + +cst_node!(Value, VALUE); + +cst_node!(KeyValuePair, KEY_VALUE_PAIR); + +impl<'a> KeyValuePair<'a> { + pub fn key(&self) -> Option<Key<'a>> { + self.syntax().children().find_map(Key::cast) + } + + pub fn value(&self) -> Option<Value<'a>> { + self.syntax().children().find_map(Value::cast) + } +} + +cst_node!(KeyValueBody, KEY_VALUE_BODY); + +impl<'a> KeyValueBody<'a> { + pub fn pairs(&self) -> impl Iterator<Item = KeyValuePair<'a>> { + self.syntax().children().filter_map(KeyValuePair::cast) + } +} + +pub trait HasKeyValueBody<'a>: CstNode<'a, Lang = Language> { + fn body(&self) -> Option<KeyValueBody<'a>> { + self.syntax().children().find_map(KeyValueBody::cast) + } +} + +cst_node!(CurlyGroupKeyValue, CURLY_GROUP_KEY_VALUE); + +impl<'a> HasCurly<'a> for CurlyGroupKeyValue<'a> {} + +impl<'a> HasKeyValueBody<'a> for CurlyGroupKeyValue<'a> {} + +cst_node!(BrackGroupKeyValue, BRACK_GROUP_KEY_VALUE); + +impl<'a> HasBrack<'a> for BrackGroupKeyValue<'a> {} + +impl<'a> HasKeyValueBody<'a> for BrackGroupKeyValue<'a> {} + +cst_node!(Formula, FORMULA); + +cst_node!(GenericCommand, GENERIC_COMMAND); + +impl<'a> GenericCommand<'a> { + pub fn name(&self) -> Option<&'a SyntaxToken> { + self.syntax() + .children_with_tokens() + .filter_map(|node| node.into_token()) + .find(|node| node.kind() == GENERIC_COMMAND_NAME) + } +} + +cst_node!(Equation, EQUATION); + +cst_node!(Begin, BEGIN); + +impl<'a> Begin<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn name(&self) -> Option<CurlyGroupWord<'a>> { + self.syntax().children().find_map(CurlyGroupWord::cast) + } + + pub fn options(&self) -> Option<BrackGroup<'a>> { + self.syntax().children().find_map(BrackGroup::cast) + } +} + +cst_node!(End, END); + +impl<'a> End<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn name(&self) -> Option<CurlyGroupWord<'a>> { + self.syntax().children().find_map(CurlyGroupWord::cast) + } +} + +cst_node!(Environment, ENVIRONMENT); + +impl<'a> Environment<'a> { + pub fn begin(&self) -> Option<Begin<'a>> { + self.syntax().children().find_map(Begin::cast) + } + + pub fn end(&self) -> Option<End<'a>> { + self.syntax().children().find_map(End::cast) + } +} + +cst_node!( + Section, + PART, + CHAPTER, + SECTION, + SUBSECTION, + SUBSUBSECTION, + PARAGRAPH, + SUBPARAGRAPH +); + +impl<'a> Section<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn name(&self) -> Option<CurlyGroup<'a>> { + self.syntax().children().find_map(CurlyGroup::cast) + } +} + +cst_node!(EnumItem, ENUM_ITEM); + +impl<'a> EnumItem<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn label(&self) -> Option<BrackGroup<'a>> { + self.syntax().children().find_map(BrackGroup::cast) + } +} + +cst_node!(Caption, CAPTION); + +impl<'a> Caption<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn short(&self) -> Option<BrackGroup<'a>> { + self.syntax().children().find_map(BrackGroup::cast) + } + + pub fn long(&self) -> Option<CurlyGroup<'a>> { + self.syntax().children().find_map(CurlyGroup::cast) + } +} + +cst_node!(Citation, CITATION); + +impl<'a> Citation<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn prenote(&self) -> Option<BrackGroup<'a>> { + self.syntax().children().find_map(BrackGroup::cast) + } + + pub fn postnote(&self) -> Option<BrackGroup<'a>> { + self.syntax().children().filter_map(BrackGroup::cast).nth(1) + } + + pub fn key_list(&self) -> Option<CurlyGroupWordList<'a>> { + self.syntax().children().find_map(CurlyGroupWordList::cast) + } +} + +cst_node!( + Include, + PACKAGE_INCLUDE, + CLASS_INCLUDE, + LATEX_INCLUDE, + BIBLATEX_INCLUDE, + BIBTEX_INCLUDE, + GRAPHICS_INCLUDE, + SVG_INCLUDE, + INKSCAPE_INCLUDE, + VERBATIM_INCLUDE +); + +impl<'a> Include<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn path_list(&self) -> Option<CurlyGroupWordList<'a>> { + self.syntax().children().find_map(CurlyGroupWordList::cast) + } +} + +cst_node!(Import, IMPORT); + +impl<'a> Import<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn directory(&self) -> Option<CurlyGroupWord<'a>> { + self.syntax().children().find_map(CurlyGroupWord::cast) + } + + pub fn file(&self) -> Option<CurlyGroupWord<'a>> { + self.syntax() + .children() + .filter_map(CurlyGroupWord::cast) + .nth(1) + } +} + +cst_node!(LabelDefinition, LABEL_DEFINITION); + +impl<'a> LabelDefinition<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn name(&self) -> Option<CurlyGroupWord<'a>> { + self.syntax().children().find_map(CurlyGroupWord::cast) + } +} + +cst_node!(LabelReference, LABEL_REFERENCE); + +impl<'a> LabelReference<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn name_list(&self) -> Option<CurlyGroupWordList<'a>> { + self.syntax().children().find_map(CurlyGroupWordList::cast) + } +} + +cst_node!(LabelReferenceRange, LABEL_REFERENCE_RANGE); + +impl<'a> LabelReferenceRange<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn from(&self) -> Option<CurlyGroupWord<'a>> { + self.syntax().children().find_map(CurlyGroupWord::cast) + } + + pub fn to(&self) -> Option<CurlyGroupWord<'a>> { + self.syntax() + .children() + .filter_map(CurlyGroupWord::cast) + .nth(1) + } +} + +cst_node!(LabelNumber, LABEL_NUMBER); + +impl<'a> LabelNumber<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn name(&self) -> Option<CurlyGroupWord<'a>> { + self.syntax().children().find_map(CurlyGroupWord::cast) + } + + pub fn text(&self) -> Option<CurlyGroup<'a>> { + self.syntax().children().find_map(CurlyGroup::cast) + } +} + +cst_node!(TheoremDefinition, THEOREM_DEFINITION); + +impl<'a> TheoremDefinition<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn name(&self) -> Option<CurlyGroupWord<'a>> { + self.syntax().children().find_map(CurlyGroupWord::cast) + } + + pub fn description(&self) -> Option<CurlyGroup<'a>> { + self.syntax().children().find_map(CurlyGroup::cast) + } +} + +cst_node!(CommandDefinition, COMMAND_DEFINITION, MATH_OPERATOR); + +impl<'a> CommandDefinition<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn name(&self) -> Option<CurlyGroupCommand<'a>> { + self.syntax().children().find_map(CurlyGroupCommand::cast) + } + + pub fn implementation(&self) -> Option<CurlyGroup<'a>> { + self.syntax().children().find_map(CurlyGroup::cast) + } +} + +cst_node!(AcronymReference, ACRONYM_REFERENCE); + +impl<'a> AcronymReference<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } +} + +cst_node!(AcronymDefinition, ACRONYM_DEFINITION); + +impl<'a> AcronymDefinition<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn name(&self) -> Option<CurlyGroupWord<'a>> { + self.syntax().children().find_map(CurlyGroupWord::cast) + } +} + +cst_node!(AcronymDeclaration, ACRONYM_DECLARATION); + +impl<'a> AcronymDeclaration<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn name(&self) -> Option<CurlyGroupWord<'a>> { + self.syntax().children().find_map(CurlyGroupWord::cast) + } +} + +cst_node!(ColorDefinition, COLOR_DEFINITION); + +impl<'a> ColorDefinition<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn name(&self) -> Option<CurlyGroupWord<'a>> { + self.syntax().children().find_map(CurlyGroupWord::cast) + } + + pub fn model(&self) -> Option<CurlyGroupWord<'a>> { + self.syntax() + .children() + .filter_map(CurlyGroupWord::cast) + .nth(1) + } + + pub fn spec(&self) -> Option<CurlyGroup<'a>> { + self.syntax().children().find_map(CurlyGroup::cast) + } +} + +cst_node!(ColorSetDefinition, COLOR_SET_DEFINITION); + +impl<'a> ColorSetDefinition<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn model_list(&self) -> Option<CurlyGroupWordList<'a>> { + self.syntax().children().find_map(CurlyGroupWordList::cast) + } +} + +cst_node!(ColorReference, COLOR_REFERENCE); + +impl<'a> ColorReference<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn name(&self) -> Option<CurlyGroupWord<'a>> { + self.syntax().children().find_map(CurlyGroupWord::cast) + } +} + +cst_node!(GlossaryEntryReference, GLOSSARY_ENTRY_REFERENCE); + +impl<'a> GlossaryEntryReference<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn name(&self) -> Option<CurlyGroupWord<'a>> { + self.syntax().children().find_map(CurlyGroupWord::cast) + } +} + +cst_node!(GlossaryEntryDefinition, GLOSSARY_ENTRY_DEFINITION); + +impl<'a> GlossaryEntryDefinition<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn name(&self) -> Option<CurlyGroupWord<'a>> { + self.syntax().children().find_map(CurlyGroupWord::cast) + } +} + +cst_node!(TikzLibraryImport, TIKZ_LIBRARY_IMPORT); + +impl<'a> TikzLibraryImport<'a> { + pub fn command(&self) -> Option<&'a SyntaxToken> { + self.syntax().first_token() + } + + pub fn name_list(&self) -> Option<CurlyGroupWordList<'a>> { + self.syntax().children().find_map(CurlyGroupWordList::cast) + } +} diff --git a/support/texlab/src/syntax/latex/kind.rs b/support/texlab/src/syntax/latex/kind.rs new file mode 100644 index 0000000000..fcab393380 --- /dev/null +++ b/support/texlab/src/syntax/latex/kind.rs @@ -0,0 +1,180 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)] +#[allow(non_camel_case_types)] +#[repr(u16)] +pub enum SyntaxKind { + ERROR = 0, + MISSING, + + WHITESPACE, + COMMENT, + L_CURLY, + R_CURLY, + L_BRACK, + R_BRACK, + L_PAREN, + R_PAREN, + PARAMETER, + COMMA, + EQUALITY_SIGN, + WORD, + DOLLAR, + GENERIC_COMMAND_NAME, + BEGIN_ENVIRONMENT_NAME, + END_ENVIRONMENT_NAME, + BEGIN_EQUATION_NAME, + END_EQUATION_NAME, + PART_NAME, + CHAPTER_NAME, + SECTION_NAME, + SUBSECTION_NAME, + SUBSUBSECTION_NAME, + PARAGRAPH_NAME, + SUBPARAGRAPH_NAME, + ENUM_ITEM_NAME, + CAPTION_NAME, + CITATION_NAME, + PACKAGE_INCLUDE_NAME, + CLASS_INCLUDE_NAME, + LATEX_INCLUDE_NAME, + BIBLATEX_INCLUDE_NAME, + BIBTEX_INCLUDE_NAME, + GRAPHICS_INCLUDE_NAME, + SVG_INCLUDE_NAME, + INKSCAPE_INCLUDE_NAME, + VERBATIM_INCLUDE_NAME, + IMPORT_NAME, + LABEL_DEFINITION_NAME, + LABEL_REFERENCE_NAME, + LABEL_REFERENCE_RANGE_NAME, + LABEL_NUMBER_NAME, + COMMAND_DEFINITION_NAME, + MATH_OPERATOR_NAME, + GLOSSARY_ENTRY_DEFINITION_NAME, + GLOSSARY_ENTRY_REFERENCE_NAME, + ACRONYM_DEFINITION_NAME, + ACRONYM_DECLARATION_NAME, + ACRONYM_REFERENCE_NAME, + THEOREM_DEFINITION_NAME, + COLOR_REFERENCE_NAME, + COLOR_DEFINITION_NAME, + COLOR_SET_DEFINITION_NAME, + TIKZ_LIBRARY_IMPORT_NAME, + ENVIRONMENT_DEFINIITION_NAME, + + PREAMBLE, + TEXT, + KEY, + VALUE, + KEY_VALUE_PAIR, + KEY_VALUE_BODY, + CURLY_GROUP, + CURLY_GROUP_WORD, + CURLY_GROUP_WORD_LIST, + CURLY_GROUP_COMMAND, + CURLY_GROUP_KEY_VALUE, + BRACK_GROUP, + BRACK_GROUP_WORD, + BRACK_GROUP_KEY_VALUE, + PAREN_GROUP, + MIXED_GROUP, + GENERIC_COMMAND, + ENVIRONMENT, + BEGIN, + END, + EQUATION, + PART, + CHAPTER, + SECTION, + SUBSECTION, + SUBSUBSECTION, + PARAGRAPH, + SUBPARAGRAPH, + ENUM_ITEM, + FORMULA, + CAPTION, + CITATION, + PACKAGE_INCLUDE, + CLASS_INCLUDE, + LATEX_INCLUDE, + BIBLATEX_INCLUDE, + BIBTEX_INCLUDE, + GRAPHICS_INCLUDE, + SVG_INCLUDE, + INKSCAPE_INCLUDE, + VERBATIM_INCLUDE, + IMPORT, + LABEL_DEFINITION, + LABEL_REFERENCE, + LABEL_REFERENCE_RANGE, + LABEL_NUMBER, + COMMAND_DEFINITION, + MATH_OPERATOR, + GLOSSARY_ENTRY_DEFINITION, + GLOSSARY_ENTRY_REFERENCE, + ACRONYM_DEFINITION, + ACRONYM_DECLARATION, + ACRONYM_REFERENCE, + THEOREM_DEFINITION, + COLOR_REFERENCE, + COLOR_DEFINITION, + COLOR_SET_DEFINITION, + TIKZ_LIBRARY_IMPORT, + ENVIRONMENT_DEFINITION, + ROOT, +} + +impl SyntaxKind { + pub fn is_command_name(&self) -> bool { + use SyntaxKind::*; + matches!( + self, + GENERIC_COMMAND_NAME + | BEGIN_ENVIRONMENT_NAME + | END_ENVIRONMENT_NAME + | BEGIN_EQUATION_NAME + | END_EQUATION_NAME + | PART_NAME + | CHAPTER_NAME + | SECTION_NAME + | SUBSECTION_NAME + | SUBSUBSECTION_NAME + | PARAGRAPH_NAME + | SUBPARAGRAPH_NAME + | ENUM_ITEM_NAME + | CAPTION_NAME + | CITATION_NAME + | PACKAGE_INCLUDE_NAME + | CLASS_INCLUDE_NAME + | LATEX_INCLUDE_NAME + | BIBLATEX_INCLUDE_NAME + | BIBTEX_INCLUDE_NAME + | GRAPHICS_INCLUDE_NAME + | SVG_INCLUDE_NAME + | INKSCAPE_INCLUDE_NAME + | VERBATIM_INCLUDE_NAME + | IMPORT_NAME + | LABEL_DEFINITION_NAME + | LABEL_REFERENCE_NAME + | LABEL_REFERENCE_RANGE_NAME + | LABEL_NUMBER_NAME + | COMMAND_DEFINITION_NAME + | MATH_OPERATOR_NAME + | GLOSSARY_ENTRY_DEFINITION_NAME + | GLOSSARY_ENTRY_REFERENCE_NAME + | ACRONYM_DEFINITION_NAME + | ACRONYM_DECLARATION_NAME + | ACRONYM_REFERENCE_NAME + | THEOREM_DEFINITION_NAME + | COLOR_REFERENCE_NAME + | COLOR_DEFINITION_NAME + | COLOR_SET_DEFINITION_NAME + | TIKZ_LIBRARY_IMPORT_NAME + ) + } +} + +impl From<SyntaxKind> for cstree::SyntaxKind { + fn from(kind: SyntaxKind) -> Self { + Self(kind as u16) + } +} diff --git a/support/texlab/src/syntax/latex/lexer.rs b/support/texlab/src/syntax/latex/lexer.rs index e14f1f6d85..7cd94ec897 100644 --- a/support/texlab/src/syntax/latex/lexer.rs +++ b/support/texlab/src/syntax/latex/lexer.rs @@ -1,176 +1,255 @@ -use super::ast::{Token, TokenKind}; -use crate::syntax::text::CharStream; +use logos::Logos; -#[derive(Debug)] +use super::kind::SyntaxKind; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord, Logos)] +#[allow(non_camel_case_types)] +#[repr(u16)] +enum Token { + #[regex(r"\s+")] + WHITESPACE = 2, + + #[regex(r"%[^\r\n]*")] + COMMENT, + + #[token("{")] + L_CURLY, + + #[token("}")] + R_CURLY, + + #[token("[")] + L_BRACK, + + #[token("]")] + R_BRACK, + + #[token("(")] + L_PAREN, + + #[token(")")] + R_PAREN, + + #[regex(r"#\d?")] + PARAMETER, + + #[token(",")] + COMMA, + + #[token("=")] + EQUALITY_SIGN, + + #[regex(r"[^\s\\%\{\},\$\[\]\(\)=\#]+")] + #[error] + WORD, + + #[regex(r"\$\$?")] + DOLLAR, + + #[regex(r"\\([^\r\n]|[@a-zA-Z:_]+\*?)?")] + GENERIC_COMMAND_NAME, + + #[regex(r"\\begin")] + BEGIN_ENVIRONMENT_NAME, + + #[regex(r"\\end")] + END_ENVIRONMENT_NAME, + + #[regex(r"\\\[")] + BEGIN_EQUATION_NAME, + + #[regex(r"\\\]")] + END_EQUATION_NAME, + + #[regex(r"\\part\*?")] + PART_NAME, + + #[regex(r"\\chapter\*?")] + CHAPTER_NAME, + + #[regex(r"\\section\*?")] + SECTION_NAME, + + #[regex(r"\\subsection\*?")] + SUBSECTION_NAME, + + #[regex(r"\\subsubsection\*?")] + SUBSUBSECTION_NAME, + + #[regex(r"\\paragraph\*?")] + PARAGRAPH_NAME, + + #[regex(r"\\subparagraph\*?")] + SUBPARAGRAPH_NAME, + + #[regex(r"\\item")] + ENUM_ITEM_NAME, + + #[regex(r"\\caption")] + CAPTION_NAME, + + #[regex(r"\\cite|\\cite\*|\\Cite|\\nocite|\\citet|\\citep|\\citet\*|\\citep\*|\\citeauthor|\\citeauthor\*|\\Citeauthor|\\Citeauthor\*|\\citetitle|\\citetitle\*|\\citeyear|\\citeyear\*|\\citedate|\\citedate\*|\\citeurl|\\fullcite|\\citeyearpar|\\citealt|\\citealp|\\citetext|\\parencite|\\parencite\*|\\Parencite|\\footcite|\\footfullcite|\\footcitetext|\\textcite|\\Textcite|\\smartcite|\\Smartcite|\\supercite|\\autocite|\\Autocite|\\autocite\*|\\Autocite\*|\\volcite|\\Volcite|\\pvolcite|\\Pvolcite|\\fvolcite|\\ftvolcite|\\svolcite|\\Svolcite|\\tvolcite|\\Tvolcite|\\avolcite|\\Avolcite|\\notecite|\\notecite|\\pnotecite|\\Pnotecite|\\fnotecite|\\citeA|\\citeA\*")] + CITATION_NAME, + + #[regex(r"\\usepackage|\\RequirePackage")] + PACKAGE_INCLUDE_NAME, + + #[regex(r"\\documentclass")] + CLASS_INCLUDE_NAME, + + #[regex(r"\\include|\\subfileinclude|\\input|\\subfile")] + LATEX_INCLUDE_NAME, + + #[regex(r"\\addbibresource")] + BIBLATEX_INCLUDE_NAME, + + #[regex(r"\\bibliography")] + BIBTEX_INCLUDE_NAME, + + #[regex(r"\\includegraphics")] + GRAPHICS_INCLUDE_NAME, + + #[regex(r"\\includesvg")] + SVG_INCLUDE_NAME, + + #[regex(r"\\includeinkscape")] + INKSCAPE_INCLUDE_NAME, + + #[regex(r"\\verbatiminput|\\VerbatimInput")] + VERBATIM_INCLUDE_NAME, + + #[regex(r"\\import|\\subimport|\\inputfrom|\\subimportfrom|\\includefrom|\\subincludefrom")] + IMPORT_NAME, + + #[regex(r"\\label")] + LABEL_DEFINITION_NAME, + + #[regex(r"\\ref|\\vref|\\Vref|\\autoref|\\pageref|\\cref|\\Cref|\\cref*|\\Cref*|\\namecref|\\nameCref|\\lcnamecref|\\namecrefs|\\nameCrefs|\\lcnamecrefs|\\labelcref|\\labelcpageref|\\eqref")] + LABEL_REFERENCE_NAME, + + #[regex(r"\\crefrange\*?|\\Crefrange\*?")] + LABEL_REFERENCE_RANGE_NAME, + + #[regex(r"\\newlabel")] + LABEL_NUMBER_NAME, + + #[regex(r"\\newcommand\*?|\\renewcommand|\\DeclareRobustCommand")] + COMMAND_DEFINITION_NAME, + + #[regex(r"\\DeclareMathOperator\*?")] + MATH_OPERATOR_NAME, + + #[regex(r"\\newglossaryentry")] + GLOSSARY_ENTRY_DEFINITION_NAME, + + #[regex(r"\\gls|\\Gls|\\GLS|\\glspl|\\Glspl|\\GLSpl|\\glsdisp|\\glslink|\\glstext|\\Glstext|\\GLStext|\\glsfirst|\\Glsfirst|\\GLSfirst|\\glsplural|\\Glsplural|\\GLSplural|\\glsfirstplural|\\Glsfirstplural|\\GLSfirstplural|\\glsname|\\Glsname|\\GLSname|\\glssymbol|\\Glssymbol|\\glsdesc|\\Glsdesc|\\GLSdesc|\\glsuseri|\\Glsuseri|\\GLSuseri|\\glsuserii|\\Glsuserii|\\GLSuserii|\\glsuseriii|\\Glsuseriii|\\GLSuseriii|\\glsuseriv|\\Glsuseriv|\\GLSuseriv|\\glsuserv|\\Glsuserv|\\GLSuserv|\\glsuservi|\\Glsuservi|\\GLSuservi")] + GLOSSARY_ENTRY_REFERENCE_NAME, + + #[regex(r"\\newacronym")] + ACRONYM_DEFINITION_NAME, + + #[regex(r"\\DeclareAcronym")] + ACRONYM_DECLARATION_NAME, + + #[regex(r"\\acrshort|\\Acrshort|\\ACRshort|\\acrshortpl|\\Acrshortpl|\\ACRshortpl|\\acrlong|\\Acrlong|\\ACRlong|\\acrlongpl|\\Acrlongpl|\\ACRlongpl|\\acrfull|\\Acrfull|\\ACRfull|\\acrfullpl|\\Acrfullpl|\\ACRfullpl|\\acs|\\Acs|\\acsp|\\Acsp|\\acl|\\Acl|\\aclp|\\Aclp|\\acf|\\Acf|\\acfp|\\Acfp|\\ac|\\Ac|\\acp|\\glsentrylong|\\Glsentrylong|\\glsentrylongpl|\\Glsentrylongpl|\\glsentryshort|\\Glsentryshort|\\glsentryshortpl|\\Glsentryshortpl|\\glsentryfullpl|\\Glsentryfullpl")] + ACRONYM_REFERENCE_NAME, + + #[regex(r"\\newtheorem|\\declaretheorem")] + THEOREM_DEFINITION_NAME, + + #[regex(r"\\color|\\colorbox|\\textcolor|\\pagecolor")] + COLOR_REFERENCE_NAME, + + #[regex(r"\\definecolor")] + COLOR_DEFINITION_NAME, + + #[regex(r"\\definecolorset")] + COLOR_SET_DEFINITION_NAME, + + #[regex(r"\\usepgflibrary|\\usetikzlibrary")] + TIKZ_LIBRARY_IMPORT_NAME, + + #[regex(r"\\newenvironment|\\newenvironment*")] + ENVIRONMENT_DEFINITION_NAME, +} + +#[derive(Debug, PartialEq, Eq, Clone)] pub struct Lexer<'a> { - stream: CharStream<'a>, + tokens: Vec<(SyntaxKind, &'a str)>, } impl<'a> Lexer<'a> { pub fn new(text: &'a str) -> Self { - Self { - stream: CharStream::new(text), + let mut tokens = Vec::new(); + let mut lexer = Token::lexer(text); + while let Some(kind) = lexer.next() { + tokens.push(( + unsafe { std::mem::transmute::<Token, SyntaxKind>(kind) }, + lexer.slice(), + )); } + tokens.reverse(); + Self { tokens } } - fn single_char(&mut self, kind: TokenKind) -> Token { - self.stream.start_span(); - self.stream.next(); - let span = self.stream.end_span(); - Token::new(span, kind) + pub fn peek(&self) -> Option<SyntaxKind> { + self.tokens.last().map(|(kind, _)| *kind) } - fn math(&mut self) -> Token { - self.stream.start_span(); - self.stream.next(); - if self.stream.satifies(|c| *c == '$') { - self.stream.next(); - } - let span = self.stream.end_span(); - Token::new(span, TokenKind::Math) + pub fn eat(&mut self) -> Option<(SyntaxKind, &'a str)> { + self.tokens.pop() } +} - fn command(&mut self) -> Token { - let span = self.stream.command(); - Token::new(span, TokenKind::Command) - } +#[cfg(test)] +mod tests { + use insta::assert_debug_snapshot; - fn word(&mut self) -> Token { - self.stream.start_span(); - self.stream.next(); - while self.stream.satifies(|c| is_word_char(*c)) { - self.stream.next(); - } + use super::*; - let span = self.stream.end_span(); - Token::new(span, TokenKind::Word) + fn verify(text: &str) -> Vec<(SyntaxKind, &str)> { + let mut tokens = Lexer::new(text).tokens; + tokens.reverse(); + tokens } -} -impl<'a> Iterator for Lexer<'a> { - type Item = Token; - - fn next(&mut self) -> Option<Token> { - loop { - match self.stream.peek() { - Some('%') => { - self.stream.skip_rest_of_line(); - } - Some('{') => { - return Some(self.single_char(TokenKind::BeginGroup)); - } - Some('}') => { - return Some(self.single_char(TokenKind::EndGroup)); - } - Some('[') => { - return Some(self.single_char(TokenKind::BeginOptions)); - } - Some(']') => { - return Some(self.single_char(TokenKind::EndOptions)); - } - Some('$') => { - return Some(self.math()); - } - Some(',') => { - return Some(self.single_char(TokenKind::Comma)); - } - Some('\\') => { - return Some(self.command()); - } - Some(c) => { - if c.is_whitespace() { - self.stream.next(); - } else { - return Some(self.word()); - } - } - None => { - return None; - } - } - } + #[test] + fn test_empty() { + assert_debug_snapshot!(verify(r#""#)); } -} - -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::{ - protocol::{Position, Range}, - syntax::text::Span, - }; - - fn verify<'a>(lexer: &mut Lexer<'a>, line: u64, character: u64, text: &str, kind: TokenKind) { - 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 = Token::new(span, kind); - assert_eq!(Some(token), lexer.next()); + #[test] + fn test_delimiters() { + assert_debug_snapshot!(verify(r#"{foo} (bar) [baz, qux = foo-bar]"#)); } #[test] - fn word() { - let mut lexer = Lexer::new("foo bar baz"); - verify(&mut lexer, 0, 0, "foo", TokenKind::Word); - verify(&mut lexer, 0, 4, "bar", TokenKind::Word); - verify(&mut lexer, 0, 8, "baz", TokenKind::Word); - assert_eq!(None, lexer.next()); + fn test_command_with_parameter() { + assert_debug_snapshot!(verify(r#"\newcommand{\id}[1]{#1}"#)); } #[test] - fn command() { - let mut lexer = Lexer::new("\\foo\\bar@baz\n\\foo*"); - verify(&mut lexer, 0, 0, "\\foo", TokenKind::Command); - verify(&mut lexer, 0, 4, "\\bar@baz", TokenKind::Command); - verify(&mut lexer, 1, 0, "\\foo*", TokenKind::Command); - assert_eq!(None, lexer.next()); + fn test_command_with_star() { + assert_debug_snapshot!(verify(r#"\section*{Foo}"#)); } #[test] - fn escape_sequence() { - let mut lexer = Lexer::new("\\%\\**"); - verify(&mut lexer, 0, 0, "\\%", TokenKind::Command); - verify(&mut lexer, 0, 2, "\\*", TokenKind::Command); - verify(&mut lexer, 0, 4, "*", TokenKind::Word); - assert_eq!(None, lexer.next()); + fn test_escape_sequence() { + assert_debug_snapshot!(verify(r#"\% hello"#)); } #[test] - fn group_delimiter() { - let mut lexer = Lexer::new("{}[]"); - verify(&mut lexer, 0, 0, "{", TokenKind::BeginGroup); - verify(&mut lexer, 0, 1, "}", TokenKind::EndGroup); - verify(&mut lexer, 0, 2, "[", TokenKind::BeginOptions); - verify(&mut lexer, 0, 3, "]", TokenKind::EndOptions); - assert_eq!(None, lexer.next()); + fn test_formula() { + assert_debug_snapshot!(verify(r#"$ f(x) = y $$"#)); } #[test] - fn math() { - let mut lexer = Lexer::new("$$ $ $"); - verify(&mut lexer, 0, 0, "$$", TokenKind::Math); - verify(&mut lexer, 0, 3, "$", TokenKind::Math); - verify(&mut lexer, 0, 5, "$", TokenKind::Math); - assert_eq!(None, lexer.next()); + fn test_comment() { + assert_debug_snapshot!(verify("hello %world\r\ntest %test")); } #[test] - fn line_comment() { - let mut lexer = Lexer::new(" %foo \nfoo"); - verify(&mut lexer, 1, 0, "foo", TokenKind::Word); - assert_eq!(None, lexer.next()); + fn test_invalid_parameter() { + assert_debug_snapshot!(verify(r#"#"#)) } } diff --git a/support/texlab/src/syntax/latex/mod.rs b/support/texlab/src/syntax/latex/mod.rs deleted file mode 100644 index cee83ab04e..0000000000 --- a/support/texlab/src/syntax/latex/mod.rs +++ /dev/null @@ -1,686 +0,0 @@ -mod analysis; -mod ast; -mod lexer; -mod parser; - -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, -} - -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) -} - -#[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(), - }) - } - - #[derive(Debug, Default)] - struct TreeTraversal { - nodes: Vec<AstNodeIndex>, - } - - impl Visitor for TreeTraversal { - fn visit(&mut self, tree: &Tree, node: AstNodeIndex) { - self.nodes.push(node); - tree.walk(self, node); - } - } - - mod range { - use super::*; - - fn verify(expected_ranges: Vec<Range>, text: &str) { - let table = open_simple(text); - - 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); - } - - #[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} - "# - ), - ); - } - - #[test] - fn text() { - verify( - vec![ - Range::new_simple(0, 0, 0, 11), - Range::new_simple(0, 0, 0, 11), - ], - indoc!( - r#" - foo bar baz - "# - ), - ); - } - - #[test] - fn text_bracket() { - verify( - vec![Range::new_simple(0, 0, 0, 5), Range::new_simple(0, 0, 0, 5)], - indoc!( - r#" - ]foo[ - "# - ), - ); - } - - #[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 } } - "# - ), - ); - } - - #[test] - fn group_incomplete() { - verify( - vec![Range::new_simple(0, 1, 0, 2), Range::new_simple(0, 1, 0, 2)], - indoc!( - r#" - }{ - "# - ), - ); - } - - #[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 - "# - ), - ); - } - } - - 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"#)) - } - } - - 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); - } - } - - 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}"#), - ); - } - - #[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}"#), - ); - } - - #[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}"#), - ); - } - - #[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"] - ); - } - } - - #[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); - } - - #[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(); - - assert_eq!(actual_cmds, expected_cmds); - } - - #[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); - } - - #[test] - fn equation() { - let table = open_simple(indoc!( - r#" - \[ - e^{i \pi} + 1 = 0 - \] - \] \[ - "# - )); - - assert_eq!(table.equations.len(), 1); - } - - #[test] - fn inline() { - let table = open_simple(indoc!( - r#" - $ x $ - $ - "# - )); - - assert_eq!(table.inlines.len(), 1); - } - - #[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" - ); - } - - #[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" - ); - } - - #[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); - } - - #[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() - .map(|label| { - label - .names(&table.tree) - .into_iter() - .map(Token::text) - .collect() - }) - .collect(); - - assert_eq!(actual_names, expected_names); - } - - #[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"); - } - - #[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" - ); - } - - #[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); - } - - #[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"); - } -} diff --git a/support/texlab/src/syntax/latex/parser.rs b/support/texlab/src/syntax/latex/parser.rs index 4ace4fbcd6..4333235ba5 100644 --- a/support/texlab/src/syntax/latex/parser.rs +++ b/support/texlab/src/syntax/latex/parser.rs @@ -1,186 +1,1709 @@ -use super::ast::*; -use crate::{ - protocol::{Range, RangeExt}, - syntax::{ - generic_ast::{Ast, AstNodeIndex}, - text::SyntaxNode, - }, +use cstree::GreenNodeBuilder; + +use super::{ + lexer::Lexer, + SyntaxKind::{self, *}, + SyntaxNode, }; -use std::iter::Peekable; -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -enum Scope { - Root, - Group, - Options, +#[derive(Clone)] +pub struct Parse { + pub root: SyntaxNode, +} + +#[derive(Debug, Clone, Copy)] +struct ParserContext { + allow_environment: bool, + allow_comma: bool, +} + +impl Default for ParserContext { + fn default() -> Self { + Self { + allow_environment: true, + allow_comma: true, + } + } } #[derive(Debug)] -pub struct Parser<I: Iterator<Item = Token>> { - tree: Ast<Node>, - tokens: Peekable<I>, +struct Parser<'a> { + lexer: Lexer<'a>, + builder: GreenNodeBuilder<'static, 'static>, } -impl<I: Iterator<Item = Token>> Parser<I> { - pub fn new(tokens: I) -> Self { +impl<'a> Parser<'a> { + pub fn new(text: &'a str) -> Self { Self { - tree: Ast::new(), - tokens: tokens.peekable(), + lexer: Lexer::new(text), + builder: GreenNodeBuilder::new(), } } - pub fn parse(mut self) -> Tree { - let children = self.content(Scope::Root); + fn eat(&mut self) { + let (kind, text) = self.lexer.eat().unwrap(); + self.builder.token(kind.into(), text); + } - let range = if children.is_empty() { - Range::new_simple(0, 0, 0, 0) + fn peek(&self) -> Option<SyntaxKind> { + self.lexer.peek() + } + + fn expect(&mut self, kind: SyntaxKind) { + if self.peek() == Some(kind) { + self.eat(); + self.trivia(); } else { - let start = self.tree[children[0]].start(); - let end = self.tree[children[children.len() - 1]].end(); - Range::new(start, end) - }; + self.builder.token(MISSING.into(), ""); + } + } - let root = self.tree.add_node(Node::Root(Root { range })); - self.connect(root, &children); - Tree { - inner: self.tree, - root, + fn expect2(&mut self, kind1: SyntaxKind, kind2: SyntaxKind) { + if self + .peek() + .filter(|&kind| kind == kind1 || kind == kind2) + .is_some() + { + self.eat(); + self.trivia(); + } else { + self.builder.token(MISSING.into(), ""); } } - fn content(&mut self, scope: Scope) -> Vec<AstNodeIndex> { - let mut children = Vec::new(); - while let Some(ref token) = self.tokens.peek() { - match token.kind { - TokenKind::Word | TokenKind::BeginOptions => { - children.push(self.text(scope)); - } - TokenKind::Command => { - children.push(self.command()); - } - TokenKind::Comma => { - children.push(self.comma()); - } - TokenKind::Math => { - children.push(self.math()); - } - TokenKind::BeginGroup => { - children.push(self.group(GroupKind::Group)); - } - TokenKind::EndGroup => { - if scope == Scope::Root { - self.tokens.next(); - } else { - return children; - } - } - TokenKind::EndOptions => { - if scope == Scope::Options { - return children; + fn trivia(&mut self) { + while self + .peek() + .filter(|&kind| matches!(kind, WHITESPACE | COMMENT)) + .is_some() + { + self.eat(); + } + } + + pub fn parse(mut self) -> Parse { + self.builder.start_node(ROOT.into()); + self.preamble(); + while self.peek().is_some() { + self.content(ParserContext::default()); + } + self.builder.finish_node(); + let (green_node, interner) = self.builder.finish(); + Parse { + root: SyntaxNode::new_root_with_resolver(green_node, interner.unwrap()), + } + } + + fn content(&mut self, context: ParserContext) { + match self.peek().unwrap() { + WHITESPACE | COMMENT => self.eat(), + L_CURLY if context.allow_environment => self.curly_group(), + L_CURLY => self.curly_group_without_environments(), + L_BRACK | L_PAREN => self.mixed_group(), + R_CURLY | R_BRACK | R_PAREN => { + self.builder.start_node(ERROR.into()); + self.eat(); + self.builder.finish_node(); + } + PARAMETER => self.eat(), + WORD | COMMA => self.text(context), + EQUALITY_SIGN => self.eat(), + DOLLAR => self.formula(), + GENERIC_COMMAND_NAME => self.generic_command(), + BEGIN_ENVIRONMENT_NAME if context.allow_environment => self.environment(), + BEGIN_ENVIRONMENT_NAME => self.generic_command(), + END_ENVIRONMENT_NAME => self.generic_command(), + BEGIN_EQUATION_NAME => self.equation(), + END_EQUATION_NAME => self.generic_command(), + MISSING | ERROR => self.eat(), + PART_NAME => self.part(), + CHAPTER_NAME => self.chapter(), + SECTION_NAME => self.section(), + SUBSECTION_NAME => self.subsection(), + SUBSUBSECTION_NAME => self.subsubsection(), + PARAGRAPH_NAME => self.paragraph(), + SUBPARAGRAPH_NAME => self.subparagraph(), + ENUM_ITEM_NAME => self.enum_item(), + CAPTION_NAME => self.caption(), + CITATION_NAME => self.citation(), + PACKAGE_INCLUDE_NAME => self.package_include(), + CLASS_INCLUDE_NAME => self.class_include(), + LATEX_INCLUDE_NAME => self.latex_include(), + BIBLATEX_INCLUDE_NAME => self.biblatex_include(), + BIBTEX_INCLUDE_NAME => self.bibtex_include(), + GRAPHICS_INCLUDE_NAME => self.graphics_include(), + SVG_INCLUDE_NAME => self.svg_include(), + INKSCAPE_INCLUDE_NAME => self.inkscape_include(), + VERBATIM_INCLUDE_NAME => self.verbatim_include(), + IMPORT_NAME => self.import(), + LABEL_DEFINITION_NAME => self.label_definition(), + LABEL_REFERENCE_NAME => self.label_reference(), + LABEL_REFERENCE_RANGE_NAME => self.label_reference_range(), + LABEL_NUMBER_NAME => self.label_number(), + COMMAND_DEFINITION_NAME => self.command_definition(), + MATH_OPERATOR_NAME => self.math_operator(), + GLOSSARY_ENTRY_DEFINITION_NAME => self.glossary_entry_definition(), + GLOSSARY_ENTRY_REFERENCE_NAME => self.glossary_entry_reference(), + ACRONYM_DEFINITION_NAME => self.acronym_definition(), + ACRONYM_DECLARATION_NAME => self.acronym_declaration(), + ACRONYM_REFERENCE_NAME => self.acronym_reference(), + THEOREM_DEFINITION_NAME => self.theorem_definition(), + COLOR_REFERENCE_NAME => self.color_reference(), + COLOR_DEFINITION_NAME => self.color_definition(), + COLOR_SET_DEFINITION_NAME => self.color_set_definition(), + TIKZ_LIBRARY_IMPORT_NAME => self.tikz_library_import(), + ENVIRONMENT_DEFINIITION_NAME => self.environment_definition(), + _ => unreachable!(), + } + } + + fn text(&mut self, context: ParserContext) { + self.builder.start_node(TEXT.into()); + self.eat(); + while self + .peek() + .filter(|&kind| { + matches!(kind, WHITESPACE | COMMENT | WORD | COMMA) + && (context.allow_comma || kind != COMMA) + }) + .is_some() + { + self.eat(); + } + self.builder.finish_node(); + } + + fn group_with_token( + &mut self, + node_kind: SyntaxKind, + content_kind: SyntaxKind, + right_kind: SyntaxKind, + ) { + self.builder.start_node(node_kind.into()); + self.eat(); + self.trivia(); + self.expect(content_kind); + self.expect(right_kind); + self.builder.finish_node(); + } + + fn curly_group(&mut self) { + self.builder.start_node(CURLY_GROUP.into()); + self.eat(); + while self + .peek() + .filter(|&kind| !matches!(kind, R_CURLY | END_ENVIRONMENT_NAME)) + .is_some() + { + self.content(ParserContext::default()); + } + self.expect(R_CURLY); + self.builder.finish_node(); + } + + fn curly_group_without_environments(&mut self) { + self.builder.start_node(CURLY_GROUP.into()); + self.eat(); + while self + .peek() + .filter(|&kind| !matches!(kind, R_CURLY)) + .is_some() + { + self.content(ParserContext { + allow_environment: false, + allow_comma: true, + }); + } + self.expect(R_CURLY); + self.builder.finish_node(); + } + + fn curly_group_word(&mut self) { + self.group_with_token(CURLY_GROUP_WORD, WORD, R_CURLY); + } + + fn curly_group_word_list(&mut self) { + self.builder.start_node(CURLY_GROUP_WORD_LIST.into()); + self.eat(); + + while self + .peek() + .filter(|&kind| matches!(kind, WHITESPACE | COMMENT | WORD | COMMA)) + .is_some() + { + self.eat(); + } + + self.expect(R_CURLY); + self.builder.finish_node(); + } + + fn curly_group_command(&mut self) { + self.group_with_token(CURLY_GROUP_COMMAND, GENERIC_COMMAND_NAME, R_CURLY); + } + + fn brack_group(&mut self) { + self.builder.start_node(BRACK_GROUP.into()); + self.eat(); + while self + .peek() + .filter(|&kind| { + !matches!( + kind, + R_CURLY + | R_BRACK + | PART_NAME + | CHAPTER_NAME + | SECTION_NAME + | SUBSECTION_NAME + | PARAGRAPH_NAME + | SUBPARAGRAPH_NAME + | ENUM_ITEM_NAME + | END_ENVIRONMENT_NAME + ) + }) + .is_some() + { + self.content(ParserContext::default()); + } + self.expect(R_BRACK); + self.builder.finish_node(); + } + + fn brack_group_word(&mut self) { + self.group_with_token(BRACK_GROUP_WORD, WORD, R_BRACK); + } + + // fn paren_group(&mut self) { + // self.builder.start_node(PAREN_GROUP.into()); + // self.eat(); + // while self + // .peek() + // .filter(|&kind| { + // !matches!( + // kind, + // R_CURLY + // | R_BRACK + // | R_PAREN + // | PART_NAME + // | CHAPTER_NAME + // | SECTION_NAME + // | SUBSECTION_NAME + // | PARAGRAPH_NAME + // | SUBPARAGRAPH_NAME + // | ENUM_ITEM_NAME + // | END_ENVIRONMENT_NAME + // ) + // }) + // .is_some() + // { + // self.content(); + // } + // self.expect(R_PAREN); + // self.builder.finish_node(); + // } + + fn mixed_group(&mut self) { + self.builder.start_node(MIXED_GROUP.into()); + self.eat(); + self.trivia(); + while self + .peek() + .filter(|&kind| { + !matches!( + kind, + R_CURLY + | R_BRACK + | R_PAREN + | PART_NAME + | CHAPTER_NAME + | SECTION_NAME + | SUBSECTION_NAME + | PARAGRAPH_NAME + | SUBPARAGRAPH_NAME + | ENUM_ITEM_NAME + | END_ENVIRONMENT_NAME + ) + }) + .is_some() + { + self.content(ParserContext::default()); + } + self.expect2(R_BRACK, R_PAREN); + self.builder.finish_node(); + } + + fn key(&mut self) { + self.builder.start_node(KEY.into()); + self.eat(); + while self + .peek() + .filter(|&kind| matches!(kind, WHITESPACE | COMMENT | WORD)) + .is_some() + { + self.eat(); + } + self.builder.finish_node(); + } + + fn value(&mut self) { + self.builder.start_node(VALUE.into()); + self.content(ParserContext { + allow_environment: true, + allow_comma: false, + }); + self.builder.finish_node(); + } + + fn key_value_pair(&mut self) { + self.builder.start_node(KEY_VALUE_PAIR.into()); + self.key(); + if self.peek() == Some(EQUALITY_SIGN) { + self.eat(); + self.trivia(); + if self + .peek() + .filter(|&kind| { + !matches!( + kind, + END_ENVIRONMENT_NAME | R_CURLY | R_BRACK | R_PAREN | COMMA + ) + }) + .is_some() + { + self.value(); + } else { + self.builder.token(MISSING.into(), ""); + } + } + + self.builder.finish_node(); + } + + fn key_value_body(&mut self) { + self.builder.start_node(KEY_VALUE_BODY.into()); + while let Some(kind) = self.peek() { + match kind { + WHITESPACE | COMMENT => self.eat(), + WORD => { + self.key_value_pair(); + if self.peek() == Some(COMMA) { + self.eat(); } else { - children.push(self.text(scope)); + break; } } + _ => break, } } - children - } - - fn group(&mut self, kind: GroupKind) -> AstNodeIndex { - let left = self.tokens.next().unwrap(); - let scope = match kind { - GroupKind::Group => Scope::Group, - GroupKind::Options => Scope::Options, - }; - - let children = self.content(scope); - let right_kind = match kind { - GroupKind::Group => TokenKind::EndGroup, - GroupKind::Options => TokenKind::EndOptions, - }; - - let right = if self.next_of_kind(right_kind) { - self.tokens.next() - } else { - None - }; - - let end = right - .as_ref() - .map(SyntaxNode::end) - .or_else(|| children.last().map(|child| self.tree[*child].end())) - .unwrap_or_else(|| left.end()); - let range = Range::new(left.start(), end); - - let node = self.tree.add_node(Node::Group(Group { - range, - left, - kind, - right, - })); - self.connect(node, &children); - node - } - - fn command(&mut self) -> AstNodeIndex { - let name = self.tokens.next().unwrap(); - let mut children = Vec::new(); - while let Some(token) = self.tokens.peek() { - match token.kind { - TokenKind::BeginGroup => children.push(self.group(GroupKind::Group)), - TokenKind::BeginOptions => children.push(self.group(GroupKind::Options)), + self.builder.finish_node(); + } + + fn group_key_value(&mut self, node_kind: SyntaxKind, right_kind: SyntaxKind) { + self.builder.start_node(node_kind.into()); + self.eat(); + self.trivia(); + self.key_value_body(); + self.expect(right_kind); + self.builder.finish_node(); + } + + fn curly_group_key_value(&mut self) { + self.group_key_value(CURLY_GROUP_KEY_VALUE, R_CURLY); + } + + fn brack_group_key_value(&mut self) { + self.group_key_value(BRACK_GROUP_KEY_VALUE, R_BRACK); + } + + fn formula(&mut self) { + self.builder.start_node(FORMULA.into()); + self.eat(); + self.trivia(); + while self + .peek() + .filter(|&kind| !matches!(kind, R_CURLY | END_ENVIRONMENT_NAME | DOLLAR)) + .is_some() + { + self.content(ParserContext::default()); + } + self.expect(DOLLAR); + self.builder.finish_node(); + } + + fn generic_command(&mut self) { + self.builder.start_node(GENERIC_COMMAND.into()); + self.eat(); + while let Some(kind) = self.peek() { + match kind { + WHITESPACE | COMMENT => self.eat(), + L_CURLY => self.curly_group(), + L_BRACK | L_PAREN => self.mixed_group(), _ => break, } } + self.builder.finish_node(); + } + + fn equation(&mut self) { + self.builder.start_node(EQUATION.into()); + self.eat(); + while self + .peek() + .filter(|&kind| !matches!(kind, END_ENVIRONMENT_NAME | R_CURLY | END_EQUATION_NAME)) + .is_some() + { + self.content(ParserContext::default()); + } + self.expect(END_EQUATION_NAME); + self.builder.finish_node(); + } - let end = children - .last() - .map(|child| self.tree[*child].end()) - .unwrap_or_else(|| name.end()); - let range = Range::new(name.start(), end); + fn begin(&mut self) { + self.builder.start_node(BEGIN.into()); + self.eat(); + self.trivia(); - let node = self.tree.add_node(Node::Command(Command { range, name })); - self.connect(node, &children); - node + if self.peek() == Some(L_CURLY) { + self.curly_group_word(); + } else { + self.builder.token(MISSING.into(), ""); + } + + if self.peek() == Some(L_BRACK) { + self.brack_group(); + } + self.builder.finish_node(); } - fn text(&mut self, scope: Scope) -> AstNodeIndex { - let mut words = Vec::new(); - while let Some(ref token) = self.tokens.peek() { - let kind = token.kind; - let opts = kind == TokenKind::EndOptions && scope != Scope::Options; - if kind == TokenKind::Word || kind == TokenKind::BeginOptions || opts { - words.push(self.tokens.next().unwrap()); - } else { - break; - } + fn end(&mut self) { + self.builder.start_node(END.into()); + self.eat(); + self.trivia(); + + if self.peek() == Some(L_CURLY) { + self.curly_group_word(); + } else { + self.builder.token(MISSING.into(), ""); } - let range = Range::new(words[0].start(), words[words.len() - 1].end()); - self.tree.add_node(Node::Text(Text { range, words })) + + self.builder.finish_node(); } - fn comma(&mut self) -> AstNodeIndex { - let token = self.tokens.next().unwrap(); - let range = token.range(); - self.tree.add_node(Node::Comma(Comma { range, token })) + fn environment(&mut self) { + self.builder.start_node(ENVIRONMENT.into()); + self.begin(); + + while self + .peek() + .filter(|&kind| kind != END_ENVIRONMENT_NAME) + .is_some() + { + self.content(ParserContext::default()); + } + + if self.peek() == Some(END_ENVIRONMENT_NAME) { + self.end(); + } else { + self.builder.token(MISSING.into(), ""); + } + self.builder.finish_node(); } - fn math(&mut self) -> AstNodeIndex { - let token = self.tokens.next().unwrap(); - let range = token.range(); - self.tree.add_node(Node::Math(Math { range, token })) + fn preamble(&mut self) { + self.builder.start_node(PREAMBLE.into()); + while self + .peek() + .filter(|&kind| kind != END_ENVIRONMENT_NAME) + .is_some() + { + self.content(ParserContext::default()); + } + self.builder.finish_node(); } - fn connect(&mut self, parent: AstNodeIndex, children: &[AstNodeIndex]) { - for child in children { - self.tree.add_edge(parent, *child); + fn part(&mut self) { + self.builder.start_node(PART.into()); + self.eat(); + self.trivia(); + + if self.peek() == Some(L_CURLY) { + self.curly_group(); + } else { + self.builder.token(MISSING.into(), ""); } + + while self + .peek() + .filter(|&kind| !matches!(kind, END_ENVIRONMENT_NAME | R_CURLY | PART_NAME)) + .is_some() + { + self.content(ParserContext::default()); + } + self.builder.finish_node(); } - fn next_of_kind(&mut self, kind: TokenKind) -> bool { - self.tokens + fn chapter(&mut self) { + self.builder.start_node(CHAPTER.into()); + self.eat(); + self.trivia(); + + if self.peek() == Some(L_CURLY) { + self.curly_group(); + } else { + self.builder.token(MISSING.into(), ""); + } + + while self .peek() - .filter(|token| token.kind == kind) + .filter(|&kind| { + !matches!( + kind, + END_ENVIRONMENT_NAME | R_CURLY | PART_NAME | CHAPTER_NAME + ) + }) .is_some() + { + self.content(ParserContext::default()); + } + self.builder.finish_node(); + } + + fn section(&mut self) { + self.builder.start_node(SECTION.into()); + self.eat(); + self.trivia(); + + if self.peek() == Some(L_CURLY) { + self.curly_group(); + } else { + self.builder.token(MISSING.into(), ""); + } + + while self + .peek() + .filter(|&kind| { + !matches!( + kind, + END_ENVIRONMENT_NAME | R_CURLY | PART_NAME | CHAPTER_NAME | SECTION_NAME + ) + }) + .is_some() + { + self.content(ParserContext::default()); + } + self.builder.finish_node(); + } + + fn subsection(&mut self) { + self.builder.start_node(SUBSECTION.into()); + self.eat(); + self.trivia(); + + if self.peek() == Some(L_CURLY) { + self.curly_group(); + } else { + self.builder.token(MISSING.into(), ""); + } + + while self + .peek() + .filter(|&kind| { + !matches!( + kind, + END_ENVIRONMENT_NAME + | R_CURLY + | PART_NAME + | CHAPTER_NAME + | SECTION_NAME + | SUBSECTION_NAME + ) + }) + .is_some() + { + self.content(ParserContext::default()); + } + self.builder.finish_node(); + } + + fn subsubsection(&mut self) { + self.builder.start_node(SUBSUBSECTION.into()); + self.eat(); + self.trivia(); + + if self.peek() == Some(L_CURLY) { + self.curly_group(); + } else { + self.builder.token(MISSING.into(), ""); + } + + while self + .peek() + .filter(|&kind| { + !matches!( + kind, + END_ENVIRONMENT_NAME + | R_CURLY + | PART_NAME + | CHAPTER_NAME + | SECTION_NAME + | SUBSECTION_NAME + | SUBSUBSECTION_NAME + ) + }) + .is_some() + { + self.content(ParserContext::default()); + } + self.builder.finish_node(); + } + + fn paragraph(&mut self) { + self.builder.start_node(PARAGRAPH.into()); + self.eat(); + self.trivia(); + + if self.peek() == Some(L_CURLY) { + self.curly_group(); + } else { + self.builder.token(MISSING.into(), ""); + } + + while self + .peek() + .filter(|&kind| { + !matches!( + kind, + END_ENVIRONMENT_NAME + | R_CURLY + | PART_NAME + | CHAPTER_NAME + | SECTION_NAME + | SUBSECTION_NAME + | SUBSUBSECTION_NAME + | PARAGRAPH_NAME + ) + }) + .is_some() + { + self.content(ParserContext::default()); + } + self.builder.finish_node(); + } + + fn subparagraph(&mut self) { + self.builder.start_node(SUBPARAGRAPH.into()); + self.eat(); + self.trivia(); + + if self.peek() == Some(L_CURLY) { + self.curly_group(); + } else { + self.builder.token(MISSING.into(), ""); + } + + while self + .peek() + .filter(|&kind| { + !matches!( + kind, + END_ENVIRONMENT_NAME + | R_CURLY + | PART_NAME + | CHAPTER_NAME + | SECTION_NAME + | SUBSECTION_NAME + | SUBSUBSECTION_NAME + | PARAGRAPH_NAME + | SUBPARAGRAPH_NAME + ) + }) + .is_some() + { + self.content(ParserContext::default()); + } + self.builder.finish_node(); + } + + fn enum_item(&mut self) { + self.builder.start_node(ENUM_ITEM.into()); + self.eat(); + self.trivia(); + + if self.peek() == Some(L_BRACK) { + self.brack_group(); + } + + while self + .peek() + .filter(|&kind| { + !matches!( + kind, + END_ENVIRONMENT_NAME + | R_CURLY + | PART_NAME + | CHAPTER_NAME + | SECTION_NAME + | SUBSECTION_NAME + | SUBSUBSECTION_NAME + | PARAGRAPH_NAME + | SUBPARAGRAPH_NAME + | ENUM_ITEM_NAME + ) + }) + .is_some() + { + self.content(ParserContext::default()); + } + self.builder.finish_node(); + } + + fn caption(&mut self) { + self.builder.start_node(CAPTION.into()); + self.eat(); + self.trivia(); + + if self.peek() == Some(L_BRACK) { + self.brack_group(); + } + + if self.peek() == Some(L_CURLY) { + self.curly_group(); + } else { + self.builder.token(MISSING.into(), ""); + } + + self.builder.finish_node(); + } + + fn citation(&mut self) { + self.builder.start_node(CITATION.into()); + self.eat(); + self.trivia(); + for _ in 0..2 { + if self.lexer.peek() == Some(L_BRACK) { + self.brack_group(); + } + } + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word_list(); + } else { + self.builder.token(MISSING.into(), ""); + } + + self.builder.finish_node(); + } + + fn generic_include(&mut self, kind: SyntaxKind, options: bool) { + self.builder.start_node(kind.into()); + self.eat(); + self.trivia(); + if options && self.lexer.peek() == Some(L_BRACK) { + self.brack_group_key_value(); + } + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word_list(); + } else { + self.builder.token(MISSING.into(), ""); + } + + self.builder.finish_node(); + } + + fn package_include(&mut self) { + self.generic_include(PACKAGE_INCLUDE, true); + } + + fn class_include(&mut self) { + self.generic_include(CLASS_INCLUDE, true); + } + + fn latex_include(&mut self) { + self.generic_include(LATEX_INCLUDE, false); + } + + fn biblatex_include(&mut self) { + self.generic_include(BIBLATEX_INCLUDE, true); + } + + fn bibtex_include(&mut self) { + self.generic_include(BIBTEX_INCLUDE, false); + } + + fn graphics_include(&mut self) { + self.generic_include(GRAPHICS_INCLUDE, true); + } + + fn svg_include(&mut self) { + self.generic_include(SVG_INCLUDE, true); + } + + fn inkscape_include(&mut self) { + self.generic_include(INKSCAPE_INCLUDE, true); + } + + fn verbatim_include(&mut self) { + self.generic_include(VERBATIM_INCLUDE, false); + } + + fn import(&mut self) { + self.builder.start_node(IMPORT.into()); + self.eat(); + self.trivia(); + + for _ in 0..2 { + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word(); + } else { + self.builder.token(MISSING.into(), ""); + } + } + + self.builder.finish_node(); + } + + fn label_definition(&mut self) { + self.builder.start_node(LABEL_DEFINITION.into()); + self.eat(); + self.trivia(); + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word(); + } else { + self.builder.token(MISSING.into(), ""); + } + self.builder.finish_node(); + } + + fn label_reference(&mut self) { + self.builder.start_node(LABEL_REFERENCE.into()); + self.eat(); + self.trivia(); + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word_list(); + } else { + self.builder.token(MISSING.into(), ""); + } + self.builder.finish_node(); + } + + fn label_reference_range(&mut self) { + self.builder.start_node(LABEL_REFERENCE_RANGE.into()); + self.eat(); + self.trivia(); + + for _ in 0..2 { + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word(); + } else { + self.builder.token(MISSING.into(), ""); + } + } + + self.builder.finish_node(); + } + + fn label_number(&mut self) { + self.builder.start_node(LABEL_NUMBER.into()); + self.eat(); + self.trivia(); + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word(); + } else { + self.builder.token(MISSING.into(), ""); + } + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group(); + self.builder.token(MISSING.into(), ""); + } + + self.builder.finish_node(); + } + + fn command_definition(&mut self) { + self.builder.start_node(COMMAND_DEFINITION.into()); + self.eat(); + self.trivia(); + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_command(); + } else { + self.builder.token(MISSING.into(), ""); + } + + if self.lexer.peek() == Some(L_BRACK) { + self.brack_group_word(); + } + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group(); + } else { + self.builder.token(MISSING.into(), ""); + } + + self.builder.finish_node(); + } + + fn math_operator(&mut self) { + self.builder.start_node(MATH_OPERATOR.into()); + self.eat(); + self.trivia(); + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_command(); + } else { + self.builder.token(MISSING.into(), ""); + } + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group(); + } else { + self.builder.token(MISSING.into(), ""); + } + + self.builder.finish_node(); + } + + fn glossary_entry_definition(&mut self) { + self.builder.start_node(GLOSSARY_ENTRY_DEFINITION.into()); + self.eat(); + self.trivia(); + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word(); + } else { + self.builder.token(MISSING.into(), ""); + } + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_key_value(); + } else { + self.builder.token(MISSING.into(), ""); + } + + self.builder.finish_node(); + } + + fn glossary_entry_reference(&mut self) { + self.builder.start_node(GLOSSARY_ENTRY_REFERENCE.into()); + self.eat(); + self.trivia(); + + if self.lexer.peek() == Some(L_BRACK) { + self.brack_group_key_value(); + } + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word(); + } else { + self.builder.token(MISSING.into(), ""); + } + + self.builder.finish_node(); + } + + fn acronym_definition(&mut self) { + self.builder.start_node(ACRONYM_DEFINITION.into()); + self.eat(); + self.trivia(); + + if self.lexer.peek() == Some(L_BRACK) { + self.brack_group_key_value(); + } + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word(); + } else { + self.builder.token(MISSING.into(), ""); + } + + for _ in 0..2 { + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group(); + } else { + self.builder.token(MISSING.into(), ""); + } + } + + self.builder.finish_node(); + } + + fn acronym_declaration(&mut self) { + self.builder.start_node(ACRONYM_DECLARATION.into()); + self.eat(); + self.trivia(); + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word(); + } else { + self.builder.token(MISSING.into(), ""); + } + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_key_value(); + } else { + self.builder.token(MISSING.into(), ""); + } + + self.builder.finish_node(); + } + + fn acronym_reference(&mut self) { + self.builder.start_node(ACRONYM_REFERENCE.into()); + self.eat(); + self.trivia(); + + if self.lexer.peek() == Some(L_BRACK) { + self.brack_group_key_value(); + } + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word(); + } else { + self.builder.token(MISSING.into(), ""); + } + + self.builder.finish_node(); + } + + fn theorem_definition(&mut self) { + self.builder.start_node(THEOREM_DEFINITION.into()); + self.eat(); + self.trivia(); + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word(); + } else { + self.builder.token(MISSING.into(), ""); + } + + if self.lexer.peek() == Some(L_BRACK) { + self.brack_group_word(); + } + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group(); + } else { + self.builder.token(MISSING.into(), ""); + } + + if self.lexer.peek() == Some(L_BRACK) { + self.brack_group_word(); + } + + self.builder.finish_node(); + } + + fn color_reference(&mut self) { + self.builder.start_node(COLOR_REFERENCE.into()); + self.eat(); + self.trivia(); + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word(); + } else { + self.builder.token(MISSING.into(), ""); + } + + self.builder.finish_node(); + } + + fn color_definition(&mut self) { + self.builder.start_node(COLOR_DEFINITION.into()); + self.eat(); + self.trivia(); + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word(); + } else { + self.builder.token(MISSING.into(), ""); + } + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word(); + } else { + self.builder.token(MISSING.into(), ""); + } + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group(); + } else { + self.builder.token(MISSING.into(), ""); + } + + self.builder.finish_node(); + } + + fn color_set_definition(&mut self) { + self.builder.start_node(COLOR_SET_DEFINITION.into()); + self.eat(); + self.trivia(); + + if self.lexer.peek() == Some(L_BRACK) { + self.brack_group_word(); + } + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word_list(); + } else { + self.builder.token(MISSING.into(), ""); + } + + for _ in 0..3 { + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word(); + } else { + self.builder.token(MISSING.into(), ""); + } + } + + self.builder.finish_node(); + } + + fn tikz_library_import(&mut self) { + self.builder.start_node(TIKZ_LIBRARY_IMPORT.into()); + self.eat(); + self.trivia(); + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word_list(); + } else { + self.builder.token(MISSING.into(), ""); + } + + self.builder.finish_node(); + } + + fn environment_definition(&mut self) { + self.builder.start_node(ENVIRONMENT_DEFINITION.into()); + self.eat(); + self.trivia(); + + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_word(); + } else { + self.builder.token(MISSING.into(), ""); + } + + if self.lexer.peek() == Some(L_BRACK) { + self.brack_group_word(); + } + + for _ in 0..2 { + if self.lexer.peek() == Some(L_CURLY) { + self.curly_group_without_environments(); + } else { + self.builder.token(MISSING.into(), ""); + } + } + + self.builder.finish_node(); + } +} + +pub fn parse(text: &str) -> Parse { + Parser::new(text).parse() +} + +#[cfg(test)] +mod tests { + use insta::assert_debug_snapshot; + + use super::*; + + fn setup(text: &str) -> SyntaxNode { + parse(&text.trim().replace("\r", "")).root + } + + #[test] + fn test_empty() { + assert_debug_snapshot!(setup(r#""#)); + } + + #[test] + fn test_hello_world() { + assert_debug_snapshot!(setup(r#"Hello World!"#)); + } + + #[test] + fn test_generic_command_empty() { + assert_debug_snapshot!(setup(r#"\foo"#)); + } + + #[test] + fn test_generic_command_escape() { + assert_debug_snapshot!(setup(r#"\#"#)); + } + + #[test] + fn test_generic_command_args() { + assert_debug_snapshot!(setup(r#"\foo{bar}[qux]"#)); + } + + #[test] + fn test_inline() { + assert_debug_snapshot!(setup(r#"$x \in [0, \infty)$"#)); + } + + #[test] + fn test_inline_double_dollar() { + assert_debug_snapshot!(setup(r#"$$x \in [0, \infty)$$"#)); + } + + #[test] + fn test_brace_group_simple() { + assert_debug_snapshot!(setup(r#"{hello world}"#)); + } + + #[test] + fn test_brace_group_missing_end() { + assert_debug_snapshot!(setup(r#"{hello world"#)); + } + + #[test] + fn test_unmatched_braces() { + assert_debug_snapshot!(setup(r#"}{"#)); + } + + #[test] + fn test_unmatched_brackets() { + assert_debug_snapshot!(setup(r#"]["#)); + } + + #[test] + fn test_unmatched_brackets_with_group() { + assert_debug_snapshot!(setup(r#"{][}"#)); + } + + #[test] + fn test_escaped_brackets() { + assert_debug_snapshot!(setup(r#"{[}{]}"#)); + } + + #[test] + fn test_parameter() { + assert_debug_snapshot!(setup(r#"#1"#)); + } + + #[test] + fn test_parameter_error() { + assert_debug_snapshot!(setup(r#"#"#)); + } + + #[test] + fn test_environment_simple() { + assert_debug_snapshot!(setup(r#"\begin{foo} Hello World \end{bar}"#)); + } + + #[test] + fn test_environment_nested() { + assert_debug_snapshot!(setup(r#"\begin{foo} \begin{qux} \end{baz} \end{bar}"#)); + } + + #[test] + fn test_environment_nested_missing_braces() { + assert_debug_snapshot!(setup( + r#"\begin{foo \begin{qux Hello World \end{baz} \end{bar"# + )); + } + + #[test] + fn test_structure_siblings() { + assert_debug_snapshot!(setup(r#"\section{Foo} Foo \section{Bar} Bar"#)); + } + + #[test] + fn test_structure_nested() { + assert_debug_snapshot!(setup( + r#"\part{1}\chapter{2}\section{3}\subsection{4}\subsubsection{5}\paragraph{6}\subparagraph{7}"# + )); + } + + #[test] + fn test_structure_enum_item() { + assert_debug_snapshot!(setup( + r#"\begin{enumerate} \item 1 \item[2] 2 \item 3 \end{enumerate}"# + )); + } + + #[test] + fn test_structure_invalid_nesting() { + assert_debug_snapshot!(setup(r#"\section{Foo} \chapter{Bar}"#)); + } + + #[test] + fn test_equation() { + assert_debug_snapshot!(setup(r#"\[ foo bar \]"#)); + } + + #[test] + fn test_equation_missing_end() { + assert_debug_snapshot!(setup(r#"\begin{a} \[ foo bar \end{b}"#)); + } + + #[test] + fn test_equation_missing_begin() { + assert_debug_snapshot!(setup(r#"\begin{a} foo bar \] \end{b}"#)); + } + + #[test] + fn test_caption_minimal() { + assert_debug_snapshot!(setup(r#"\caption{Foo \Bar Baz}"#)); + } + + #[test] + fn test_caption_minimal_error() { + assert_debug_snapshot!(setup(r#"\caption{Foo \Bar Baz"#)); + } + + #[test] + fn test_caption() { + assert_debug_snapshot!(setup(r#"\caption[qux]{Foo \Bar Baz}"#)); + } + + #[test] + fn test_caption_error() { + assert_debug_snapshot!(setup(r#"\caption[qux]{Foo \Bar Baz"#)); + } + + #[test] + fn test_caption_figure() { + assert_debug_snapshot!(setup(r#"\begin{figure}\caption{Foo}\end{figure}"#)); + } + + #[test] + fn test_citation_empty() { + assert_debug_snapshot!(setup(r#"\cite{}"#)); + } + + #[test] + fn test_citation_simple() { + assert_debug_snapshot!(setup(r#"\cite{foo}"#)); + } + + #[test] + fn test_citation_multiple_keys() { + assert_debug_snapshot!(setup(r#"\cite{foo, bar}"#)); + } + + #[test] + fn test_citation_star() { + assert_debug_snapshot!(setup(r#"\nocite{*}"#)); + } + + #[test] + fn test_citation_prenote() { + assert_debug_snapshot!(setup(r#"\cite[foo]{bar}"#)); + } + + #[test] + fn test_citation_prenote_postnote() { + assert_debug_snapshot!(setup(r#"\cite[foo][bar]{baz}"#)); + } + + #[test] + fn test_citation_missing_brace() { + assert_debug_snapshot!(setup(r#"\cite{foo"#)); + } + + #[test] + fn test_citation_redundant_comma() { + assert_debug_snapshot!(setup(r#"\cite{,foo,}"#)); + } + + #[test] + fn test_package_include_empty() { + assert_debug_snapshot!(setup(r#"\usepackage{}"#)); + } + + #[test] + fn test_package_include_simple() { + assert_debug_snapshot!(setup(r#"\usepackage{amsmath}"#)); + } + + #[test] + fn test_package_include_multiple() { + assert_debug_snapshot!(setup(r#"\usepackage{amsmath, lipsum}"#)); + } + + #[test] + fn test_package_include_options() { + assert_debug_snapshot!(setup(r#"\usepackage[foo = bar, baz, qux]{amsmath}"#)); + } + + #[test] + fn test_class_include_empty() { + assert_debug_snapshot!(setup(r#"\documentclass{}"#)); + } + + #[test] + fn test_class_include_simple() { + assert_debug_snapshot!(setup(r#"\documentclass{article}"#)); + } + + #[test] + fn test_class_include_options() { + assert_debug_snapshot!(setup(r#"\documentclass[foo = bar, baz, qux]{article}"#)); + } + + #[test] + fn test_latex_include_simple() { + assert_debug_snapshot!(setup(r#"\include{foo/bar}"#)); + } + + #[test] + fn test_latex_input_simple() { + assert_debug_snapshot!(setup(r#"\input{foo/bar.tex}"#)); + } + + #[test] + fn test_biblatex_include_simple() { + assert_debug_snapshot!(setup(r#"\addbibresource{foo/bar.bib}"#)); + } + + #[test] + fn test_biblatex_include_options() { + assert_debug_snapshot!(setup(r#"\addbibresource[foo=bar, baz]{foo/bar.bib}"#)); + } + + #[test] + fn test_bibtex_include_simple() { + assert_debug_snapshot!(setup(r#"\bibliography{foo/bar}"#)); + } + + #[test] + fn test_graphics_include_simple() { + assert_debug_snapshot!(setup(r#"\includegraphics{foo/bar.pdf}"#)); + } + + #[test] + fn test_graphics_include_options() { + assert_debug_snapshot!(setup(r#"\includegraphics[scale=.5]{foo/bar.pdf}"#)); + } + + #[test] + fn test_svg_include_simple() { + assert_debug_snapshot!(setup(r#"\includesvg{foo/bar.svg}"#)); + } + + #[test] + fn test_svg_include_options() { + assert_debug_snapshot!(setup(r#"\includesvg[scale=.5]{foo/bar.svg}"#)); + } + + #[test] + fn test_inkscape_include_simple() { + assert_debug_snapshot!(setup(r#"\includesvg{foo/bar}"#)); + } + + #[test] + fn test_inkscape_include_options() { + assert_debug_snapshot!(setup(r#"\includesvg[scale=.5]{foo/bar}"#)); + } + + #[test] + fn test_verbatim_include_simple() { + assert_debug_snapshot!(setup(r#"\verbatiminput{foo/bar.txt}"#)); + } + + #[test] + fn test_import_simple() { + assert_debug_snapshot!(setup(r#"\import{foo}{bar}"#)); + } + + #[test] + fn test_import_incomplete() { + assert_debug_snapshot!(setup(r#"\import{foo"#)); + } + + #[test] + fn test_label_definition_simple() { + assert_debug_snapshot!(setup(r#"\label{foo}"#)); + } + + #[test] + fn test_label_reference_simple() { + assert_debug_snapshot!(setup(r#"\ref{foo}"#)); + } + + #[test] + fn test_label_reference_multiple() { + assert_debug_snapshot!(setup(r#"\ref{foo, bar}"#)); + } + + #[test] + fn test_equation_label_reference_simple() { + assert_debug_snapshot!(setup(r#"\eqref{foo}"#)); + } + + #[test] + fn test_label_reference_range_simple() { + assert_debug_snapshot!(setup(r#"\crefrange{foo}{bar}"#)); + } + + #[test] + fn test_label_reference_range_incomplete() { + assert_debug_snapshot!(setup(r#"\crefrange{foo}"#)); + } + + #[test] + fn test_label_reference_range_error() { + assert_debug_snapshot!(setup(r#"\crefrange{foo{bar}"#)); + } + + #[test] + fn test_label_number() { + assert_debug_snapshot!(setup(r#"\newlabel{foo}{{1.1}}"#)); + } + + #[test] + fn test_command_definition_simple() { + assert_debug_snapshot!(setup(r#"\newcommand[1]{\id}{#1}"#)); + } + + #[test] + fn test_command_definition_no_argc() { + assert_debug_snapshot!(setup(r#"\newcommand{\foo}{foo}"#)); + } + + #[test] + fn test_command_definition_no_impl() { + assert_debug_snapshot!(setup(r#"\newcommand{\foo}"#)); + } + + #[test] + fn test_command_definition_no_impl_error() { + assert_debug_snapshot!(setup(r#"\newcommand{\foo"#)); + } + + #[test] + fn test_math_operator_simple() { + assert_debug_snapshot!(setup(r#"\DeclareMathOperator{\foo}{foo}"#)); + } + + #[test] + fn test_math_operator_no_impl() { + assert_debug_snapshot!(setup(r#"\DeclareMathOperator{\foo}"#)); + } + + #[test] + fn test_glossary_entry_definition_simple() { + assert_debug_snapshot!(setup(r#"\newglossaryentry{foo}{bar = baz, qux,}"#)); + } + + #[test] + fn test_glossary_entry_reference_simple() { + assert_debug_snapshot!(setup(r#"\gls{foo}"#)); + } + + #[test] + fn test_glossary_entry_reference_options() { + assert_debug_snapshot!(setup(r#"\gls[foo = bar, qux]{baz}"#)); + } + + #[test] + fn test_acroynm_definition_simple() { + assert_debug_snapshot!(setup(r#"\newacronym{fpsLabel}{FPS}{Frame per Second}"#)); + } + + #[test] + fn test_acroynm_definition_options() { + assert_debug_snapshot!(setup( + r#"\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}"# + )); + } + + #[test] + fn test_acroynm_reference_simple() { + assert_debug_snapshot!(setup(r#"\acrshort{fpsLabel}"#)); + } + + #[test] + fn test_acroynm_reference_options() { + assert_debug_snapshot!(setup(r#"\acrshort[foo=bar,baz]{fpsLabel}"#)); + } + + #[test] + fn test_theorem_definition_only_name() { + assert_debug_snapshot!(setup(r#"\newtheorem{foo}"#)); + } + + #[test] + fn test_theorem_definition_name_with_description() { + assert_debug_snapshot!(setup(r#"\newtheorem{foo}{Foo}"#)); + } + + #[test] + fn test_theorem_definition_name_with_description_and_counter() { + assert_debug_snapshot!(setup(r#"\newtheorem{foo}[bar]{Foo}"#)); + } + + #[test] + fn test_theorem_definition_name_with_counter() { + assert_debug_snapshot!(setup(r#"\newtheorem{foo}[bar]"#)); + } + + #[test] + fn test_theorem_definition_full() { + assert_debug_snapshot!(setup(r#"\newtheorem{foo}[bar]{Foo}[baz]"#)); + } + + #[test] + fn test_color_reference_simple() { + assert_debug_snapshot!(setup(r#"\color{black}"#)); + } + + #[test] + fn test_color_definition_simple() { + assert_debug_snapshot!(setup(r#"\definecolor{foo}{rgb}{255,168,0}"#)); + } + + #[test] + fn test_color_set_definition_simple() { + assert_debug_snapshot!(setup(r#"\definecolorset[ty]{rgb,HTML}{foo}{bar}{baz}"#)); + } + + #[test] + fn test_color_set_definition_error1() { + assert_debug_snapshot!(setup(r#"\definecolorset[ty]{rgb,HTML}{foo}{bar}"#)); + } + + #[test] + fn test_color_set_definition_error2() { + assert_debug_snapshot!(setup(r#"\definecolorset{rgb,HTML}{foo}"#)); + } + + #[test] + fn test_color_set_definition_error3() { + assert_debug_snapshot!(setup(r#"\definecolorset{rgb,HTML}"#)); + } + + #[test] + fn test_color_set_definition_error4() { + assert_debug_snapshot!(setup(r#"\definecolorset"#)); + } + + #[test] + fn test_pgf_library_import_simple() { + assert_debug_snapshot!(setup(r#"\usepgflibrary{foo}"#)); + } + + #[test] + fn test_tikz_library_import_simple() { + assert_debug_snapshot!(setup(r#"\usetikzlibrary{foo}"#)); + } + + #[test] + fn test_environment_definition() { + assert_debug_snapshot!(setup(r#"\newenvironment{bar}[1]{\begin{foo}}{\end{foo}}"#)); + } + + #[test] + fn test_acronym_declaration() { + assert_debug_snapshot!(setup( + r#"\DeclareAcronym{eg}{short = e.g,long = for example,tag = abbrev}"# + )); } } diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_parameter.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_parameter.snap new file mode 100644 index 0000000000..9cff735e99 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_parameter.snap @@ -0,0 +1,47 @@ +--- +source: src/syntax/latex/lexer.rs +expression: "verify(r#\"\\newcommand{\\id}[1]{#1}\"#)" + +--- +[ + ( + COMMAND_DEFINITION_NAME, + "\\newcommand", + ), + ( + L_CURLY, + "{", + ), + ( + GENERIC_COMMAND_NAME, + "\\id", + ), + ( + R_CURLY, + "}", + ), + ( + L_BRACK, + "[", + ), + ( + WORD, + "1", + ), + ( + R_BRACK, + "]", + ), + ( + L_CURLY, + "{", + ), + ( + PARAMETER, + "#1", + ), + ( + R_CURLY, + "}", + ), +] diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_star.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_star.snap new file mode 100644 index 0000000000..41310edaa1 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__command_with_star.snap @@ -0,0 +1,23 @@ +--- +source: src/syntax/latex/lexer.rs +expression: "verify(r#\"\\section*{Foo}\"#)" + +--- +[ + ( + SECTION_NAME, + "\\section*", + ), + ( + L_CURLY, + "{", + ), + ( + WORD, + "Foo", + ), + ( + R_CURLY, + "}", + ), +] diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__comment.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__comment.snap new file mode 100644 index 0000000000..2186c957c5 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__comment.snap @@ -0,0 +1,35 @@ +--- +source: src/syntax/latex/lexer.rs +expression: "verify(\"hello %world\\r\\ntest %test\")" + +--- +[ + ( + WORD, + "hello", + ), + ( + WHITESPACE, + " ", + ), + ( + COMMENT, + "%world", + ), + ( + WHITESPACE, + "\r\n", + ), + ( + WORD, + "test", + ), + ( + WHITESPACE, + " ", + ), + ( + COMMENT, + "%test", + ), +] diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__delimiters.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__delimiters.snap new file mode 100644 index 0000000000..36bec0a276 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__delimiters.snap @@ -0,0 +1,79 @@ +--- +source: src/syntax/latex/lexer.rs +expression: "verify(r#\"{foo} (bar) [baz, qux = foo-bar]\"#)" + +--- +[ + ( + L_CURLY, + "{", + ), + ( + WORD, + "foo", + ), + ( + R_CURLY, + "}", + ), + ( + WHITESPACE, + " ", + ), + ( + L_PAREN, + "(", + ), + ( + WORD, + "bar", + ), + ( + R_PAREN, + ")", + ), + ( + WHITESPACE, + " ", + ), + ( + L_BRACK, + "[", + ), + ( + WORD, + "baz", + ), + ( + COMMA, + ",", + ), + ( + WHITESPACE, + " ", + ), + ( + WORD, + "qux", + ), + ( + WHITESPACE, + " ", + ), + ( + EQUALITY_SIGN, + "=", + ), + ( + WHITESPACE, + " ", + ), + ( + WORD, + "foo-bar", + ), + ( + R_BRACK, + "]", + ), +] diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__empty.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__empty.snap new file mode 100644 index 0000000000..61735b5087 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__empty.snap @@ -0,0 +1,6 @@ +--- +source: src/syntax/latex/lexer.rs +expression: "verify(r#\"\"#)" + +--- +[] diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__escape_sequence.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__escape_sequence.snap new file mode 100644 index 0000000000..4dbc1bb8f5 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__escape_sequence.snap @@ -0,0 +1,19 @@ +--- +source: src/syntax/latex/lexer.rs +expression: "verify(r#\"\\% hello\"#)" + +--- +[ + ( + GENERIC_COMMAND_NAME, + "\\%", + ), + ( + WHITESPACE, + " ", + ), + ( + WORD, + "hello", + ), +] diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__formula.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__formula.snap new file mode 100644 index 0000000000..3fb7079384 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__formula.snap @@ -0,0 +1,55 @@ +--- +source: src/syntax/latex/lexer.rs +expression: "verify(r#\"$ f(x) = y $$\"#)" + +--- +[ + ( + DOLLAR, + "$", + ), + ( + WHITESPACE, + " ", + ), + ( + WORD, + "f", + ), + ( + L_PAREN, + "(", + ), + ( + WORD, + "x", + ), + ( + R_PAREN, + ")", + ), + ( + WHITESPACE, + " ", + ), + ( + EQUALITY_SIGN, + "=", + ), + ( + WHITESPACE, + " ", + ), + ( + WORD, + "y", + ), + ( + WHITESPACE, + " ", + ), + ( + DOLLAR, + "$$", + ), +] diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__invalid_parameter.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__invalid_parameter.snap new file mode 100644 index 0000000000..adadfcf0ef --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__lexer__tests__invalid_parameter.snap @@ -0,0 +1,11 @@ +--- +source: src/syntax/latex/lexer.rs +expression: "verify(r#\"#\"#)" + +--- +[ + ( + PARAMETER, + "#", + ), +] diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acronym_declaration.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acronym_declaration.snap new file mode 100644 index 0000000000..dfd05750d3 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acronym_declaration.snap @@ -0,0 +1,49 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\DeclareAcronym{eg}{short = e.g,long = for example,tag = abbrev}\"#)" + +--- +ROOT@0..64 + PREAMBLE@0..64 + ACRONYM_DECLARATION@0..64 + ACRONYM_DECLARATION_NAME@0..15 "\\DeclareAcronym" + CURLY_GROUP_WORD@15..19 + L_CURLY@15..16 "{" + WORD@16..18 "eg" + R_CURLY@18..19 "}" + CURLY_GROUP_KEY_VALUE@19..64 + L_CURLY@19..20 "{" + KEY_VALUE_BODY@20..63 + KEY_VALUE_PAIR@20..31 + KEY@20..26 + WORD@20..25 "short" + WHITESPACE@25..26 " " + EQUALITY_SIGN@26..27 "=" + WHITESPACE@27..28 " " + VALUE@28..31 + TEXT@28..31 + WORD@28..31 "e.g" + COMMA@31..32 "," + KEY_VALUE_PAIR@32..50 + KEY@32..37 + WORD@32..36 "long" + WHITESPACE@36..37 " " + EQUALITY_SIGN@37..38 "=" + WHITESPACE@38..39 " " + VALUE@39..50 + TEXT@39..50 + WORD@39..42 "for" + WHITESPACE@42..43 " " + WORD@43..50 "example" + COMMA@50..51 "," + KEY_VALUE_PAIR@51..63 + KEY@51..55 + WORD@51..54 "tag" + WHITESPACE@54..55 " " + EQUALITY_SIGN@55..56 "=" + WHITESPACE@56..57 " " + VALUE@57..63 + TEXT@57..63 + WORD@57..63 "abbrev" + R_CURLY@63..64 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_options.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_options.snap new file mode 100644 index 0000000000..1838e80f9a --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_options.snap @@ -0,0 +1,46 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\newacronym[longplural={Frames per Second}]{fpsLabel}{FPS}{Frame per Second}\"#)" + +--- +ROOT@0..76 + PREAMBLE@0..76 + ACRONYM_DEFINITION@0..76 + ACRONYM_DEFINITION_NAME@0..11 "\\newacronym" + BRACK_GROUP_KEY_VALUE@11..43 + L_BRACK@11..12 "[" + KEY_VALUE_BODY@12..42 + KEY_VALUE_PAIR@12..42 + KEY@12..22 + WORD@12..22 "longplural" + EQUALITY_SIGN@22..23 "=" + VALUE@23..42 + CURLY_GROUP@23..42 + L_CURLY@23..24 "{" + TEXT@24..41 + WORD@24..30 "Frames" + WHITESPACE@30..31 " " + WORD@31..34 "per" + WHITESPACE@34..35 " " + WORD@35..41 "Second" + R_CURLY@41..42 "}" + R_BRACK@42..43 "]" + CURLY_GROUP_WORD@43..53 + L_CURLY@43..44 "{" + WORD@44..52 "fpsLabel" + R_CURLY@52..53 "}" + CURLY_GROUP@53..58 + L_CURLY@53..54 "{" + TEXT@54..57 + WORD@54..57 "FPS" + R_CURLY@57..58 "}" + CURLY_GROUP@58..76 + L_CURLY@58..59 "{" + TEXT@59..75 + WORD@59..64 "Frame" + WHITESPACE@64..65 " " + WORD@65..68 "per" + WHITESPACE@68..69 " " + WORD@69..75 "Second" + R_CURLY@75..76 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_simple.snap new file mode 100644 index 0000000000..1d32bed249 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_definition_simple.snap @@ -0,0 +1,28 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\newacronym{fpsLabel}{FPS}{Frame per Second}\"#)" + +--- +ROOT@0..44 + PREAMBLE@0..44 + ACRONYM_DEFINITION@0..44 + ACRONYM_DEFINITION_NAME@0..11 "\\newacronym" + CURLY_GROUP_WORD@11..21 + L_CURLY@11..12 "{" + WORD@12..20 "fpsLabel" + R_CURLY@20..21 "}" + CURLY_GROUP@21..26 + L_CURLY@21..22 "{" + TEXT@22..25 + WORD@22..25 "FPS" + R_CURLY@25..26 "}" + CURLY_GROUP@26..44 + L_CURLY@26..27 "{" + TEXT@27..43 + WORD@27..32 "Frame" + WHITESPACE@32..33 " " + WORD@33..36 "per" + WHITESPACE@36..37 " " + WORD@37..43 "Second" + R_CURLY@43..44 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_options.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_options.snap new file mode 100644 index 0000000000..ce5b85c1cf --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_options.snap @@ -0,0 +1,29 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\acrshort[foo=bar,baz]{fpsLabel}\"#)" + +--- +ROOT@0..32 + PREAMBLE@0..32 + ACRONYM_REFERENCE@0..32 + ACRONYM_REFERENCE_NAME@0..9 "\\acrshort" + BRACK_GROUP_KEY_VALUE@9..22 + L_BRACK@9..10 "[" + KEY_VALUE_BODY@10..21 + KEY_VALUE_PAIR@10..17 + KEY@10..13 + WORD@10..13 "foo" + EQUALITY_SIGN@13..14 "=" + VALUE@14..17 + TEXT@14..17 + WORD@14..17 "bar" + COMMA@17..18 "," + KEY_VALUE_PAIR@18..21 + KEY@18..21 + WORD@18..21 "baz" + R_BRACK@21..22 "]" + CURLY_GROUP_WORD@22..32 + L_CURLY@22..23 "{" + WORD@23..31 "fpsLabel" + R_CURLY@31..32 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_simple.snap new file mode 100644 index 0000000000..5df8059f1b --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__acroynm_reference_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\acrshort{fpsLabel}\"#)" + +--- +ROOT@0..19 + PREAMBLE@0..19 + ACRONYM_REFERENCE@0..19 + ACRONYM_REFERENCE_NAME@0..9 "\\acrshort" + CURLY_GROUP_WORD@9..19 + L_CURLY@9..10 "{" + WORD@10..18 "fpsLabel" + R_CURLY@18..19 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_options.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_options.snap new file mode 100644 index 0000000000..c910699363 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_options.snap @@ -0,0 +1,30 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\addbibresource[foo=bar, baz]{foo/bar.bib}\"#)" + +--- +ROOT@0..42 + PREAMBLE@0..42 + BIBLATEX_INCLUDE@0..42 + BIBLATEX_INCLUDE_NAME@0..15 "\\addbibresource" + BRACK_GROUP_KEY_VALUE@15..29 + L_BRACK@15..16 "[" + KEY_VALUE_BODY@16..28 + KEY_VALUE_PAIR@16..23 + KEY@16..19 + WORD@16..19 "foo" + EQUALITY_SIGN@19..20 "=" + VALUE@20..23 + TEXT@20..23 + WORD@20..23 "bar" + COMMA@23..24 "," + WHITESPACE@24..25 " " + KEY_VALUE_PAIR@25..28 + KEY@25..28 + WORD@25..28 "baz" + R_BRACK@28..29 "]" + CURLY_GROUP_WORD_LIST@29..42 + L_CURLY@29..30 "{" + WORD@30..41 "foo/bar.bib" + R_CURLY@41..42 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_simple.snap new file mode 100644 index 0000000000..3549b985c4 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__biblatex_include_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\addbibresource{foo/bar.bib}\"#)" + +--- +ROOT@0..28 + PREAMBLE@0..28 + BIBLATEX_INCLUDE@0..28 + BIBLATEX_INCLUDE_NAME@0..15 "\\addbibresource" + CURLY_GROUP_WORD_LIST@15..28 + L_CURLY@15..16 "{" + WORD@16..27 "foo/bar.bib" + R_CURLY@27..28 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__bibtex_include_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__bibtex_include_simple.snap new file mode 100644 index 0000000000..f0d30ebfa3 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__bibtex_include_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\bibliography{foo/bar}\"#)" + +--- +ROOT@0..22 + PREAMBLE@0..22 + BIBTEX_INCLUDE@0..22 + BIBTEX_INCLUDE_NAME@0..13 "\\bibliography" + CURLY_GROUP_WORD_LIST@13..22 + L_CURLY@13..14 "{" + WORD@14..21 "foo/bar" + R_CURLY@21..22 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_missing_end.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_missing_end.snap new file mode 100644 index 0000000000..c5fc46ec4d --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_missing_end.snap @@ -0,0 +1,15 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"{hello world\"#)" + +--- +ROOT@0..12 + PREAMBLE@0..12 + CURLY_GROUP@0..12 + L_CURLY@0..1 "{" + TEXT@1..12 + WORD@1..6 "hello" + WHITESPACE@6..7 " " + WORD@7..12 "world" + MISSING@12..12 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_simple.snap new file mode 100644 index 0000000000..10ebc4418e --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__brace_group_simple.snap @@ -0,0 +1,15 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"{hello world}\"#)" + +--- +ROOT@0..13 + PREAMBLE@0..13 + CURLY_GROUP@0..13 + L_CURLY@0..1 "{" + TEXT@1..12 + WORD@1..6 "hello" + WHITESPACE@6..7 " " + WORD@7..12 "world" + R_CURLY@12..13 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption.snap new file mode 100644 index 0000000000..f598a8f7e9 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption.snap @@ -0,0 +1,26 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\caption[qux]{Foo \\Bar Baz}\"#)" + +--- +ROOT@0..27 + PREAMBLE@0..27 + CAPTION@0..27 + CAPTION_NAME@0..8 "\\caption" + BRACK_GROUP@8..13 + L_BRACK@8..9 "[" + TEXT@9..12 + WORD@9..12 "qux" + R_BRACK@12..13 "]" + CURLY_GROUP@13..27 + L_CURLY@13..14 "{" + TEXT@14..18 + WORD@14..17 "Foo" + WHITESPACE@17..18 " " + GENERIC_COMMAND@18..23 + GENERIC_COMMAND_NAME@18..22 "\\Bar" + WHITESPACE@22..23 " " + TEXT@23..26 + WORD@23..26 "Baz" + R_CURLY@26..27 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_error.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_error.snap new file mode 100644 index 0000000000..13697e882b --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_error.snap @@ -0,0 +1,26 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\caption[qux]{Foo \\Bar Baz\"#)" + +--- +ROOT@0..26 + PREAMBLE@0..26 + CAPTION@0..26 + CAPTION_NAME@0..8 "\\caption" + BRACK_GROUP@8..13 + L_BRACK@8..9 "[" + TEXT@9..12 + WORD@9..12 "qux" + R_BRACK@12..13 "]" + CURLY_GROUP@13..26 + L_CURLY@13..14 "{" + TEXT@14..18 + WORD@14..17 "Foo" + WHITESPACE@17..18 " " + GENERIC_COMMAND@18..23 + GENERIC_COMMAND_NAME@18..22 "\\Bar" + WHITESPACE@22..23 " " + TEXT@23..26 + WORD@23..26 "Baz" + MISSING@26..26 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_figure.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_figure.snap new file mode 100644 index 0000000000..4b963caabd --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_figure.snap @@ -0,0 +1,28 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\begin{figure}\\caption{Foo}\\end{figure}\"#)" + +--- +ROOT@0..39 + PREAMBLE@0..39 + ENVIRONMENT@0..39 + BEGIN@0..14 + BEGIN_ENVIRONMENT_NAME@0..6 "\\begin" + CURLY_GROUP_WORD@6..14 + L_CURLY@6..7 "{" + WORD@7..13 "figure" + R_CURLY@13..14 "}" + CAPTION@14..27 + CAPTION_NAME@14..22 "\\caption" + CURLY_GROUP@22..27 + L_CURLY@22..23 "{" + TEXT@23..26 + WORD@23..26 "Foo" + R_CURLY@26..27 "}" + END@27..39 + END_ENVIRONMENT_NAME@27..31 "\\end" + CURLY_GROUP_WORD@31..39 + L_CURLY@31..32 "{" + WORD@32..38 "figure" + R_CURLY@38..39 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal.snap new file mode 100644 index 0000000000..bf9e795e18 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal.snap @@ -0,0 +1,21 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\caption{Foo \\Bar Baz}\"#)" + +--- +ROOT@0..22 + PREAMBLE@0..22 + CAPTION@0..22 + CAPTION_NAME@0..8 "\\caption" + CURLY_GROUP@8..22 + L_CURLY@8..9 "{" + TEXT@9..13 + WORD@9..12 "Foo" + WHITESPACE@12..13 " " + GENERIC_COMMAND@13..18 + GENERIC_COMMAND_NAME@13..17 "\\Bar" + WHITESPACE@17..18 " " + TEXT@18..21 + WORD@18..21 "Baz" + R_CURLY@21..22 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal_error.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal_error.snap new file mode 100644 index 0000000000..6c932ce883 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__caption_minimal_error.snap @@ -0,0 +1,21 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\caption{Foo \\Bar Baz\"#)" + +--- +ROOT@0..21 + PREAMBLE@0..21 + CAPTION@0..21 + CAPTION_NAME@0..8 "\\caption" + CURLY_GROUP@8..21 + L_CURLY@8..9 "{" + TEXT@9..13 + WORD@9..12 "Foo" + WHITESPACE@12..13 " " + GENERIC_COMMAND@13..18 + GENERIC_COMMAND_NAME@13..17 "\\Bar" + WHITESPACE@17..18 " " + TEXT@18..21 + WORD@18..21 "Baz" + MISSING@21..21 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_empty.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_empty.snap new file mode 100644 index 0000000000..faad4c0039 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_empty.snap @@ -0,0 +1,13 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\cite{}\"#)" + +--- +ROOT@0..7 + PREAMBLE@0..7 + CITATION@0..7 + CITATION_NAME@0..5 "\\cite" + CURLY_GROUP_WORD_LIST@5..7 + L_CURLY@5..6 "{" + R_CURLY@6..7 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_missing_brace.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_missing_brace.snap new file mode 100644 index 0000000000..363f8b0142 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_missing_brace.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\cite{foo\"#)" + +--- +ROOT@0..9 + PREAMBLE@0..9 + CITATION@0..9 + CITATION_NAME@0..5 "\\cite" + CURLY_GROUP_WORD_LIST@5..9 + L_CURLY@5..6 "{" + WORD@6..9 "foo" + MISSING@9..9 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_multiple_keys.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_multiple_keys.snap new file mode 100644 index 0000000000..dafc826a97 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_multiple_keys.snap @@ -0,0 +1,17 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\cite{foo, bar}\"#)" + +--- +ROOT@0..15 + PREAMBLE@0..15 + CITATION@0..15 + CITATION_NAME@0..5 "\\cite" + CURLY_GROUP_WORD_LIST@5..15 + L_CURLY@5..6 "{" + WORD@6..9 "foo" + COMMA@9..10 "," + WHITESPACE@10..11 " " + WORD@11..14 "bar" + R_CURLY@14..15 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote.snap new file mode 100644 index 0000000000..93d83bb448 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote.snap @@ -0,0 +1,19 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\cite[foo]{bar}\"#)" + +--- +ROOT@0..15 + PREAMBLE@0..15 + CITATION@0..15 + CITATION_NAME@0..5 "\\cite" + BRACK_GROUP@5..10 + L_BRACK@5..6 "[" + TEXT@6..9 + WORD@6..9 "foo" + R_BRACK@9..10 "]" + CURLY_GROUP_WORD_LIST@10..15 + L_CURLY@10..11 "{" + WORD@11..14 "bar" + R_CURLY@14..15 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote_postnote.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote_postnote.snap new file mode 100644 index 0000000000..3acaafea3b --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_prenote_postnote.snap @@ -0,0 +1,24 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\cite[foo][bar]{baz}\"#)" + +--- +ROOT@0..20 + PREAMBLE@0..20 + CITATION@0..20 + CITATION_NAME@0..5 "\\cite" + BRACK_GROUP@5..10 + L_BRACK@5..6 "[" + TEXT@6..9 + WORD@6..9 "foo" + R_BRACK@9..10 "]" + BRACK_GROUP@10..15 + L_BRACK@10..11 "[" + TEXT@11..14 + WORD@11..14 "bar" + R_BRACK@14..15 "]" + CURLY_GROUP_WORD_LIST@15..20 + L_CURLY@15..16 "{" + WORD@16..19 "baz" + R_CURLY@19..20 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_redundant_comma.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_redundant_comma.snap new file mode 100644 index 0000000000..12f34f16ae --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_redundant_comma.snap @@ -0,0 +1,16 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\cite{,foo,}\"#)" + +--- +ROOT@0..12 + PREAMBLE@0..12 + CITATION@0..12 + CITATION_NAME@0..5 "\\cite" + CURLY_GROUP_WORD_LIST@5..12 + L_CURLY@5..6 "{" + COMMA@6..7 "," + WORD@7..10 "foo" + COMMA@10..11 "," + R_CURLY@11..12 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_simple.snap new file mode 100644 index 0000000000..a22129f85f --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\cite{foo}\"#)" + +--- +ROOT@0..10 + PREAMBLE@0..10 + CITATION@0..10 + CITATION_NAME@0..5 "\\cite" + CURLY_GROUP_WORD_LIST@5..10 + L_CURLY@5..6 "{" + WORD@6..9 "foo" + R_CURLY@9..10 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_star.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_star.snap new file mode 100644 index 0000000000..4388596dfd --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__citation_star.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\nocite{*}\"#)" + +--- +ROOT@0..10 + PREAMBLE@0..10 + CITATION@0..10 + CITATION_NAME@0..7 "\\nocite" + CURLY_GROUP_WORD_LIST@7..10 + L_CURLY@7..8 "{" + WORD@8..9 "*" + R_CURLY@9..10 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_empty.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_empty.snap new file mode 100644 index 0000000000..7d84653225 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_empty.snap @@ -0,0 +1,13 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\documentclass{}\"#)" + +--- +ROOT@0..16 + PREAMBLE@0..16 + CLASS_INCLUDE@0..16 + CLASS_INCLUDE_NAME@0..14 "\\documentclass" + CURLY_GROUP_WORD_LIST@14..16 + L_CURLY@14..15 "{" + R_CURLY@15..16 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_options.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_options.snap new file mode 100644 index 0000000000..0d0eb1b250 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_options.snap @@ -0,0 +1,37 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\documentclass[foo = bar, baz, qux]{article}\"#)" + +--- +ROOT@0..44 + PREAMBLE@0..44 + CLASS_INCLUDE@0..44 + CLASS_INCLUDE_NAME@0..14 "\\documentclass" + BRACK_GROUP_KEY_VALUE@14..35 + L_BRACK@14..15 "[" + KEY_VALUE_BODY@15..34 + KEY_VALUE_PAIR@15..24 + KEY@15..19 + WORD@15..18 "foo" + WHITESPACE@18..19 " " + EQUALITY_SIGN@19..20 "=" + WHITESPACE@20..21 " " + VALUE@21..24 + TEXT@21..24 + WORD@21..24 "bar" + COMMA@24..25 "," + WHITESPACE@25..26 " " + KEY_VALUE_PAIR@26..29 + KEY@26..29 + WORD@26..29 "baz" + COMMA@29..30 "," + WHITESPACE@30..31 " " + KEY_VALUE_PAIR@31..34 + KEY@31..34 + WORD@31..34 "qux" + R_BRACK@34..35 "]" + CURLY_GROUP_WORD_LIST@35..44 + L_CURLY@35..36 "{" + WORD@36..43 "article" + R_CURLY@43..44 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_simple.snap new file mode 100644 index 0000000000..85e21b47a8 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__class_include_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\documentclass{article}\"#)" + +--- +ROOT@0..23 + PREAMBLE@0..23 + CLASS_INCLUDE@0..23 + CLASS_INCLUDE_NAME@0..14 "\\documentclass" + CURLY_GROUP_WORD_LIST@14..23 + L_CURLY@14..15 "{" + WORD@15..22 "article" + R_CURLY@22..23 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_definition_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_definition_simple.snap new file mode 100644 index 0000000000..e4adfd534c --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_definition_simple.snap @@ -0,0 +1,27 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\definecolor{foo}{rgb}{255,168,0}\"#)" + +--- +ROOT@0..33 + PREAMBLE@0..33 + COLOR_DEFINITION@0..33 + COLOR_DEFINITION_NAME@0..12 "\\definecolor" + CURLY_GROUP_WORD@12..17 + L_CURLY@12..13 "{" + WORD@13..16 "foo" + R_CURLY@16..17 "}" + CURLY_GROUP_WORD@17..22 + L_CURLY@17..18 "{" + WORD@18..21 "rgb" + R_CURLY@21..22 "}" + CURLY_GROUP@22..33 + L_CURLY@22..23 "{" + TEXT@23..32 + WORD@23..26 "255" + COMMA@26..27 "," + WORD@27..30 "168" + COMMA@30..31 "," + WORD@31..32 "0" + R_CURLY@32..33 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_reference_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_reference_simple.snap new file mode 100644 index 0000000000..41a3700122 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_reference_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\color{black}\"#)" + +--- +ROOT@0..13 + PREAMBLE@0..13 + COLOR_REFERENCE@0..13 + COLOR_REFERENCE_NAME@0..6 "\\color" + CURLY_GROUP_WORD@6..13 + L_CURLY@6..7 "{" + WORD@7..12 "black" + R_CURLY@12..13 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error1.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error1.snap new file mode 100644 index 0000000000..42095ff974 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error1.snap @@ -0,0 +1,29 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\definecolorset[ty]{rgb,HTML}{foo}{bar}\"#)" + +--- +ROOT@0..39 + PREAMBLE@0..39 + COLOR_SET_DEFINITION@0..39 + COLOR_SET_DEFINITION_NAME@0..15 "\\definecolorset" + BRACK_GROUP_WORD@15..19 + L_BRACK@15..16 "[" + WORD@16..18 "ty" + R_BRACK@18..19 "]" + CURLY_GROUP_WORD_LIST@19..29 + L_CURLY@19..20 "{" + WORD@20..23 "rgb" + COMMA@23..24 "," + WORD@24..28 "HTML" + R_CURLY@28..29 "}" + CURLY_GROUP_WORD@29..34 + L_CURLY@29..30 "{" + WORD@30..33 "foo" + R_CURLY@33..34 "}" + CURLY_GROUP_WORD@34..39 + L_CURLY@34..35 "{" + WORD@35..38 "bar" + R_CURLY@38..39 "}" + MISSING@39..39 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error2.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error2.snap new file mode 100644 index 0000000000..718d49a3e3 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error2.snap @@ -0,0 +1,22 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\definecolorset{rgb,HTML}{foo}\"#)" + +--- +ROOT@0..30 + PREAMBLE@0..30 + COLOR_SET_DEFINITION@0..30 + COLOR_SET_DEFINITION_NAME@0..15 "\\definecolorset" + CURLY_GROUP_WORD_LIST@15..25 + L_CURLY@15..16 "{" + WORD@16..19 "rgb" + COMMA@19..20 "," + WORD@20..24 "HTML" + R_CURLY@24..25 "}" + CURLY_GROUP_WORD@25..30 + L_CURLY@25..26 "{" + WORD@26..29 "foo" + R_CURLY@29..30 "}" + MISSING@30..30 "" + MISSING@30..30 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error3.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error3.snap new file mode 100644 index 0000000000..f92d813123 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error3.snap @@ -0,0 +1,19 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\definecolorset{rgb,HTML}\"#)" + +--- +ROOT@0..25 + PREAMBLE@0..25 + COLOR_SET_DEFINITION@0..25 + COLOR_SET_DEFINITION_NAME@0..15 "\\definecolorset" + CURLY_GROUP_WORD_LIST@15..25 + L_CURLY@15..16 "{" + WORD@16..19 "rgb" + COMMA@19..20 "," + WORD@20..24 "HTML" + R_CURLY@24..25 "}" + MISSING@25..25 "" + MISSING@25..25 "" + MISSING@25..25 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error4.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error4.snap new file mode 100644 index 0000000000..3cafa81bae --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_error4.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\definecolorset\"#)" + +--- +ROOT@0..15 + PREAMBLE@0..15 + COLOR_SET_DEFINITION@0..15 + COLOR_SET_DEFINITION_NAME@0..15 "\\definecolorset" + MISSING@15..15 "" + MISSING@15..15 "" + MISSING@15..15 "" + MISSING@15..15 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_simple.snap new file mode 100644 index 0000000000..f9a736c65f --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__color_set_definition_simple.snap @@ -0,0 +1,32 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\definecolorset[ty]{rgb,HTML}{foo}{bar}{baz}\"#)" + +--- +ROOT@0..44 + PREAMBLE@0..44 + COLOR_SET_DEFINITION@0..44 + COLOR_SET_DEFINITION_NAME@0..15 "\\definecolorset" + BRACK_GROUP_WORD@15..19 + L_BRACK@15..16 "[" + WORD@16..18 "ty" + R_BRACK@18..19 "]" + CURLY_GROUP_WORD_LIST@19..29 + L_CURLY@19..20 "{" + WORD@20..23 "rgb" + COMMA@23..24 "," + WORD@24..28 "HTML" + R_CURLY@28..29 "}" + CURLY_GROUP_WORD@29..34 + L_CURLY@29..30 "{" + WORD@30..33 "foo" + R_CURLY@33..34 "}" + CURLY_GROUP_WORD@34..39 + L_CURLY@34..35 "{" + WORD@35..38 "bar" + R_CURLY@38..39 "}" + CURLY_GROUP_WORD@39..44 + L_CURLY@39..40 "{" + WORD@40..43 "baz" + R_CURLY@43..44 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_argc.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_argc.snap new file mode 100644 index 0000000000..2ff3896080 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_argc.snap @@ -0,0 +1,19 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\newcommand{\\foo}{foo}\"#)" + +--- +ROOT@0..22 + PREAMBLE@0..22 + COMMAND_DEFINITION@0..22 + COMMAND_DEFINITION_NAME@0..11 "\\newcommand" + CURLY_GROUP_COMMAND@11..17 + L_CURLY@11..12 "{" + GENERIC_COMMAND_NAME@12..16 "\\foo" + R_CURLY@16..17 "}" + CURLY_GROUP@17..22 + L_CURLY@17..18 "{" + TEXT@18..21 + WORD@18..21 "foo" + R_CURLY@21..22 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl.snap new file mode 100644 index 0000000000..4602a7a688 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl.snap @@ -0,0 +1,15 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\newcommand{\\foo}\"#)" + +--- +ROOT@0..17 + PREAMBLE@0..17 + COMMAND_DEFINITION@0..17 + COMMAND_DEFINITION_NAME@0..11 "\\newcommand" + CURLY_GROUP_COMMAND@11..17 + L_CURLY@11..12 "{" + GENERIC_COMMAND_NAME@12..16 "\\foo" + R_CURLY@16..17 "}" + MISSING@17..17 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl_error.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl_error.snap new file mode 100644 index 0000000000..4a20be7d7c --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_no_impl_error.snap @@ -0,0 +1,15 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\newcommand{\\foo\"#)" + +--- +ROOT@0..16 + PREAMBLE@0..16 + COMMAND_DEFINITION@0..16 + COMMAND_DEFINITION_NAME@0..11 "\\newcommand" + CURLY_GROUP_COMMAND@11..16 + L_CURLY@11..12 "{" + GENERIC_COMMAND_NAME@12..16 "\\foo" + MISSING@16..16 "" + MISSING@16..16 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_simple.snap new file mode 100644 index 0000000000..e5f2377fcf --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__command_definition_simple.snap @@ -0,0 +1,24 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\newcommand[1]{\\id}{#1}\"#)" + +--- +ROOT@0..23 + PREAMBLE@0..23 + COMMAND_DEFINITION@0..19 + COMMAND_DEFINITION_NAME@0..11 "\\newcommand" + MISSING@11..11 "" + BRACK_GROUP_WORD@11..14 + L_BRACK@11..12 "[" + WORD@12..13 "1" + R_BRACK@13..14 "]" + CURLY_GROUP@14..19 + L_CURLY@14..15 "{" + GENERIC_COMMAND@15..18 + GENERIC_COMMAND_NAME@15..18 "\\id" + R_CURLY@18..19 "}" + CURLY_GROUP@19..23 + L_CURLY@19..20 "{" + PARAMETER@20..22 "#1" + R_CURLY@22..23 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__empty.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__empty.snap new file mode 100644 index 0000000000..f01f7f9092 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__empty.snap @@ -0,0 +1,8 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\"#)" + +--- +ROOT@0..0 + PREAMBLE@0..0 + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_definition.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_definition.snap new file mode 100644 index 0000000000..df3c0d56fb --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_definition.snap @@ -0,0 +1,38 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\newenvironment{bar}[1]{\\begin{foo}}{\\end{foo}}\"#)" + +--- +ROOT@0..47 + PREAMBLE@0..47 + ENVIRONMENT_DEFINITION@0..47 + ENVIRONMENT_DEFINIITION_NAME@0..15 "\\newenvironment" + CURLY_GROUP_WORD@15..20 + L_CURLY@15..16 "{" + WORD@16..19 "bar" + R_CURLY@19..20 "}" + BRACK_GROUP_WORD@20..23 + L_BRACK@20..21 "[" + WORD@21..22 "1" + R_BRACK@22..23 "]" + CURLY_GROUP@23..36 + L_CURLY@23..24 "{" + GENERIC_COMMAND@24..35 + BEGIN_ENVIRONMENT_NAME@24..30 "\\begin" + CURLY_GROUP@30..35 + L_CURLY@30..31 "{" + TEXT@31..34 + WORD@31..34 "foo" + R_CURLY@34..35 "}" + R_CURLY@35..36 "}" + CURLY_GROUP@36..47 + L_CURLY@36..37 "{" + GENERIC_COMMAND@37..46 + END_ENVIRONMENT_NAME@37..41 "\\end" + CURLY_GROUP@41..46 + L_CURLY@41..42 "{" + TEXT@42..45 + WORD@42..45 "foo" + R_CURLY@45..46 "}" + R_CURLY@46..47 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested.snap new file mode 100644 index 0000000000..a5c566f773 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested.snap @@ -0,0 +1,37 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\begin{foo} \\begin{qux} \\end{baz} \\end{bar}\"#)" + +--- +ROOT@0..43 + PREAMBLE@0..43 + ENVIRONMENT@0..43 + BEGIN@0..12 + BEGIN_ENVIRONMENT_NAME@0..6 "\\begin" + CURLY_GROUP_WORD@6..12 + L_CURLY@6..7 "{" + WORD@7..10 "foo" + R_CURLY@10..11 "}" + WHITESPACE@11..12 " " + ENVIRONMENT@12..34 + BEGIN@12..24 + BEGIN_ENVIRONMENT_NAME@12..18 "\\begin" + CURLY_GROUP_WORD@18..24 + L_CURLY@18..19 "{" + WORD@19..22 "qux" + R_CURLY@22..23 "}" + WHITESPACE@23..24 " " + END@24..34 + END_ENVIRONMENT_NAME@24..28 "\\end" + CURLY_GROUP_WORD@28..34 + L_CURLY@28..29 "{" + WORD@29..32 "baz" + R_CURLY@32..33 "}" + WHITESPACE@33..34 " " + END@34..43 + END_ENVIRONMENT_NAME@34..38 "\\end" + CURLY_GROUP_WORD@38..43 + L_CURLY@38..39 "{" + WORD@39..42 "bar" + R_CURLY@42..43 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested_missing_braces.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested_missing_braces.snap new file mode 100644 index 0000000000..9ff76605d6 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_nested_missing_braces.snap @@ -0,0 +1,42 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\begin{foo \\begin{qux Hello World \\end{baz} \\end{bar\"#)" + +--- +ROOT@0..52 + PREAMBLE@0..52 + ENVIRONMENT@0..52 + BEGIN@0..11 + BEGIN_ENVIRONMENT_NAME@0..6 "\\begin" + CURLY_GROUP_WORD@6..11 + L_CURLY@6..7 "{" + WORD@7..10 "foo" + WHITESPACE@10..11 " " + MISSING@11..11 "" + ENVIRONMENT@11..44 + BEGIN@11..22 + BEGIN_ENVIRONMENT_NAME@11..17 "\\begin" + CURLY_GROUP_WORD@17..22 + L_CURLY@17..18 "{" + WORD@18..21 "qux" + WHITESPACE@21..22 " " + MISSING@22..22 "" + TEXT@22..34 + WORD@22..27 "Hello" + WHITESPACE@27..28 " " + WORD@28..33 "World" + WHITESPACE@33..34 " " + END@34..44 + END_ENVIRONMENT_NAME@34..38 "\\end" + CURLY_GROUP_WORD@38..44 + L_CURLY@38..39 "{" + WORD@39..42 "baz" + R_CURLY@42..43 "}" + WHITESPACE@43..44 " " + END@44..52 + END_ENVIRONMENT_NAME@44..48 "\\end" + CURLY_GROUP_WORD@48..52 + L_CURLY@48..49 "{" + WORD@49..52 "bar" + MISSING@52..52 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_simple.snap new file mode 100644 index 0000000000..d58f5b26c4 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__environment_simple.snap @@ -0,0 +1,27 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\begin{foo} Hello World \\end{bar}\"#)" + +--- +ROOT@0..33 + PREAMBLE@0..33 + ENVIRONMENT@0..33 + BEGIN@0..12 + BEGIN_ENVIRONMENT_NAME@0..6 "\\begin" + CURLY_GROUP_WORD@6..12 + L_CURLY@6..7 "{" + WORD@7..10 "foo" + R_CURLY@10..11 "}" + WHITESPACE@11..12 " " + TEXT@12..24 + WORD@12..17 "Hello" + WHITESPACE@17..18 " " + WORD@18..23 "World" + WHITESPACE@23..24 " " + END@24..33 + END_ENVIRONMENT_NAME@24..28 "\\end" + CURLY_GROUP_WORD@28..33 + L_CURLY@28..29 "{" + WORD@29..32 "bar" + R_CURLY@32..33 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation.snap new file mode 100644 index 0000000000..e146fcaee6 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation.snap @@ -0,0 +1,17 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\[ foo bar \\]\"#)" + +--- +ROOT@0..13 + PREAMBLE@0..13 + EQUATION@0..13 + BEGIN_EQUATION_NAME@0..2 "\\[" + WHITESPACE@2..3 " " + TEXT@3..11 + WORD@3..6 "foo" + WHITESPACE@6..7 " " + WORD@7..10 "bar" + WHITESPACE@10..11 " " + END_EQUATION_NAME@11..13 "\\]" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_label_reference_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_label_reference_simple.snap new file mode 100644 index 0000000000..7a851233ef --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_label_reference_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\eqref{foo}\"#)" + +--- +ROOT@0..11 + PREAMBLE@0..11 + LABEL_REFERENCE@0..11 + LABEL_REFERENCE_NAME@0..6 "\\eqref" + CURLY_GROUP_WORD_LIST@6..11 + L_CURLY@6..7 "{" + WORD@7..10 "foo" + R_CURLY@10..11 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_begin.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_begin.snap new file mode 100644 index 0000000000..fe065667c3 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_begin.snap @@ -0,0 +1,30 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\begin{a} foo bar \\] \\end{b}\"#)" + +--- +ROOT@0..28 + PREAMBLE@0..28 + ENVIRONMENT@0..28 + BEGIN@0..10 + BEGIN_ENVIRONMENT_NAME@0..6 "\\begin" + CURLY_GROUP_WORD@6..10 + L_CURLY@6..7 "{" + WORD@7..8 "a" + R_CURLY@8..9 "}" + WHITESPACE@9..10 " " + TEXT@10..18 + WORD@10..13 "foo" + WHITESPACE@13..14 " " + WORD@14..17 "bar" + WHITESPACE@17..18 " " + GENERIC_COMMAND@18..21 + END_EQUATION_NAME@18..20 "\\]" + WHITESPACE@20..21 " " + END@21..28 + END_ENVIRONMENT_NAME@21..25 "\\end" + CURLY_GROUP_WORD@25..28 + L_CURLY@25..26 "{" + WORD@26..27 "b" + R_CURLY@27..28 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_end.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_end.snap new file mode 100644 index 0000000000..f7b6205ffb --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__equation_missing_end.snap @@ -0,0 +1,31 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\begin{a} \\[ foo bar \\end{b}\"#)" + +--- +ROOT@0..28 + PREAMBLE@0..28 + ENVIRONMENT@0..28 + BEGIN@0..10 + BEGIN_ENVIRONMENT_NAME@0..6 "\\begin" + CURLY_GROUP_WORD@6..10 + L_CURLY@6..7 "{" + WORD@7..8 "a" + R_CURLY@8..9 "}" + WHITESPACE@9..10 " " + EQUATION@10..21 + BEGIN_EQUATION_NAME@10..12 "\\[" + WHITESPACE@12..13 " " + TEXT@13..21 + WORD@13..16 "foo" + WHITESPACE@16..17 " " + WORD@17..20 "bar" + WHITESPACE@20..21 " " + MISSING@21..21 "" + END@21..28 + END_ENVIRONMENT_NAME@21..25 "\\end" + CURLY_GROUP_WORD@25..28 + L_CURLY@25..26 "{" + WORD@26..27 "b" + R_CURLY@27..28 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__escaped_brackets.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__escaped_brackets.snap new file mode 100644 index 0000000000..c595828583 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__escaped_brackets.snap @@ -0,0 +1,19 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"{[}{]}\"#)" + +--- +ROOT@0..6 + PREAMBLE@0..6 + CURLY_GROUP@0..3 + L_CURLY@0..1 "{" + MIXED_GROUP@1..2 + L_BRACK@1..2 "[" + MISSING@2..2 "" + R_CURLY@2..3 "}" + CURLY_GROUP@3..6 + L_CURLY@3..4 "{" + ERROR@4..5 + R_BRACK@4..5 "]" + R_CURLY@5..6 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_args.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_args.snap new file mode 100644 index 0000000000..b62c8c2559 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_args.snap @@ -0,0 +1,20 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\foo{bar}[qux]\"#)" + +--- +ROOT@0..14 + PREAMBLE@0..14 + GENERIC_COMMAND@0..14 + GENERIC_COMMAND_NAME@0..4 "\\foo" + CURLY_GROUP@4..9 + L_CURLY@4..5 "{" + TEXT@5..8 + WORD@5..8 "bar" + R_CURLY@8..9 "}" + MIXED_GROUP@9..14 + L_BRACK@9..10 "[" + TEXT@10..13 + WORD@10..13 "qux" + R_BRACK@13..14 "]" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_empty.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_empty.snap new file mode 100644 index 0000000000..84e62ea81d --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_empty.snap @@ -0,0 +1,10 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\foo\"#)" + +--- +ROOT@0..4 + PREAMBLE@0..4 + GENERIC_COMMAND@0..4 + GENERIC_COMMAND_NAME@0..4 "\\foo" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_escape.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_escape.snap new file mode 100644 index 0000000000..a0d5d2e29b --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__generic_command_escape.snap @@ -0,0 +1,10 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\#\"#)" + +--- +ROOT@0..2 + PREAMBLE@0..2 + GENERIC_COMMAND@0..2 + GENERIC_COMMAND_NAME@0..2 "\\#" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_definition_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_definition_simple.snap new file mode 100644 index 0000000000..d1b31e275a --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_definition_simple.snap @@ -0,0 +1,33 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\newglossaryentry{foo}{bar = baz, qux,}\"#)" + +--- +ROOT@0..39 + PREAMBLE@0..39 + GLOSSARY_ENTRY_DEFINITION@0..39 + GLOSSARY_ENTRY_DEFINITION_NAME@0..17 "\\newglossaryentry" + CURLY_GROUP_WORD@17..22 + L_CURLY@17..18 "{" + WORD@18..21 "foo" + R_CURLY@21..22 "}" + CURLY_GROUP_KEY_VALUE@22..39 + L_CURLY@22..23 "{" + KEY_VALUE_BODY@23..38 + KEY_VALUE_PAIR@23..32 + KEY@23..27 + WORD@23..26 "bar" + WHITESPACE@26..27 " " + EQUALITY_SIGN@27..28 "=" + WHITESPACE@28..29 " " + VALUE@29..32 + TEXT@29..32 + WORD@29..32 "baz" + COMMA@32..33 "," + WHITESPACE@33..34 " " + KEY_VALUE_PAIR@34..37 + KEY@34..37 + WORD@34..37 "qux" + COMMA@37..38 "," + R_CURLY@38..39 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_options.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_options.snap new file mode 100644 index 0000000000..ad5c44885a --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_options.snap @@ -0,0 +1,32 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\gls[foo = bar, qux]{baz}\"#)" + +--- +ROOT@0..25 + PREAMBLE@0..25 + GLOSSARY_ENTRY_REFERENCE@0..25 + GLOSSARY_ENTRY_REFERENCE_NAME@0..4 "\\gls" + BRACK_GROUP_KEY_VALUE@4..20 + L_BRACK@4..5 "[" + KEY_VALUE_BODY@5..19 + KEY_VALUE_PAIR@5..14 + KEY@5..9 + WORD@5..8 "foo" + WHITESPACE@8..9 " " + EQUALITY_SIGN@9..10 "=" + WHITESPACE@10..11 " " + VALUE@11..14 + TEXT@11..14 + WORD@11..14 "bar" + COMMA@14..15 "," + WHITESPACE@15..16 " " + KEY_VALUE_PAIR@16..19 + KEY@16..19 + WORD@16..19 "qux" + R_BRACK@19..20 "]" + CURLY_GROUP_WORD@20..25 + L_CURLY@20..21 "{" + WORD@21..24 "baz" + R_CURLY@24..25 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_simple.snap new file mode 100644 index 0000000000..2a6b845733 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__glossary_entry_reference_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\gls{foo}\"#)" + +--- +ROOT@0..9 + PREAMBLE@0..9 + GLOSSARY_ENTRY_REFERENCE@0..9 + GLOSSARY_ENTRY_REFERENCE_NAME@0..4 "\\gls" + CURLY_GROUP_WORD@4..9 + L_CURLY@4..5 "{" + WORD@5..8 "foo" + R_CURLY@8..9 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_options.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_options.snap new file mode 100644 index 0000000000..910a4848ef --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_options.snap @@ -0,0 +1,25 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\includegraphics[scale=.5]{foo/bar.pdf}\"#)" + +--- +ROOT@0..39 + PREAMBLE@0..39 + GRAPHICS_INCLUDE@0..39 + GRAPHICS_INCLUDE_NAME@0..16 "\\includegraphics" + BRACK_GROUP_KEY_VALUE@16..26 + L_BRACK@16..17 "[" + KEY_VALUE_BODY@17..25 + KEY_VALUE_PAIR@17..25 + KEY@17..22 + WORD@17..22 "scale" + EQUALITY_SIGN@22..23 "=" + VALUE@23..25 + TEXT@23..25 + WORD@23..25 ".5" + R_BRACK@25..26 "]" + CURLY_GROUP_WORD_LIST@26..39 + L_CURLY@26..27 "{" + WORD@27..38 "foo/bar.pdf" + R_CURLY@38..39 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_simple.snap new file mode 100644 index 0000000000..fa6fbf1a14 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__graphics_include_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\includegraphics{foo/bar.pdf}\"#)" + +--- +ROOT@0..29 + PREAMBLE@0..29 + GRAPHICS_INCLUDE@0..29 + GRAPHICS_INCLUDE_NAME@0..16 "\\includegraphics" + CURLY_GROUP_WORD_LIST@16..29 + L_CURLY@16..17 "{" + WORD@17..28 "foo/bar.pdf" + R_CURLY@28..29 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__hello_world.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__hello_world.snap new file mode 100644 index 0000000000..ee25cb5453 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__hello_world.snap @@ -0,0 +1,12 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"Hello World!\"#)" + +--- +ROOT@0..12 + PREAMBLE@0..12 + TEXT@0..12 + WORD@0..5 "Hello" + WHITESPACE@5..6 " " + WORD@6..12 "World!" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_incomplete.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_incomplete.snap new file mode 100644 index 0000000000..3dae1f433a --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_incomplete.snap @@ -0,0 +1,15 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\import{foo\"#)" + +--- +ROOT@0..11 + PREAMBLE@0..11 + IMPORT@0..11 + IMPORT_NAME@0..7 "\\import" + CURLY_GROUP_WORD@7..11 + L_CURLY@7..8 "{" + WORD@8..11 "foo" + MISSING@11..11 "" + MISSING@11..11 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_simple.snap new file mode 100644 index 0000000000..6e21f65cbf --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__import_simple.snap @@ -0,0 +1,18 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\import{foo}{bar}\"#)" + +--- +ROOT@0..17 + PREAMBLE@0..17 + IMPORT@0..17 + IMPORT_NAME@0..7 "\\import" + CURLY_GROUP_WORD@7..12 + L_CURLY@7..8 "{" + WORD@8..11 "foo" + R_CURLY@11..12 "}" + CURLY_GROUP_WORD@12..17 + L_CURLY@12..13 "{" + WORD@13..16 "bar" + R_CURLY@16..17 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_options.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_options.snap new file mode 100644 index 0000000000..d1fd2df652 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_options.snap @@ -0,0 +1,25 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\includesvg[scale=.5]{foo/bar}\"#)" + +--- +ROOT@0..30 + PREAMBLE@0..30 + SVG_INCLUDE@0..30 + SVG_INCLUDE_NAME@0..11 "\\includesvg" + BRACK_GROUP_KEY_VALUE@11..21 + L_BRACK@11..12 "[" + KEY_VALUE_BODY@12..20 + KEY_VALUE_PAIR@12..20 + KEY@12..17 + WORD@12..17 "scale" + EQUALITY_SIGN@17..18 "=" + VALUE@18..20 + TEXT@18..20 + WORD@18..20 ".5" + R_BRACK@20..21 "]" + CURLY_GROUP_WORD_LIST@21..30 + L_CURLY@21..22 "{" + WORD@22..29 "foo/bar" + R_CURLY@29..30 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_simple.snap new file mode 100644 index 0000000000..59f50c36d3 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inkscape_include_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\includesvg{foo/bar}\"#)" + +--- +ROOT@0..20 + PREAMBLE@0..20 + SVG_INCLUDE@0..20 + SVG_INCLUDE_NAME@0..11 "\\includesvg" + CURLY_GROUP_WORD_LIST@11..20 + L_CURLY@11..12 "{" + WORD@12..19 "foo/bar" + R_CURLY@19..20 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline.snap new file mode 100644 index 0000000000..b430de766e --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline.snap @@ -0,0 +1,26 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"$x \\in [0, \\infty)$\"#)" + +--- +ROOT@0..19 + PREAMBLE@0..19 + FORMULA@0..19 + DOLLAR@0..1 "$" + TEXT@1..3 + WORD@1..2 "x" + WHITESPACE@2..3 " " + GENERIC_COMMAND@3..18 + GENERIC_COMMAND_NAME@3..6 "\\in" + WHITESPACE@6..7 " " + MIXED_GROUP@7..18 + L_BRACK@7..8 "[" + TEXT@8..11 + WORD@8..9 "0" + COMMA@9..10 "," + WHITESPACE@10..11 " " + GENERIC_COMMAND@11..17 + GENERIC_COMMAND_NAME@11..17 "\\infty" + R_PAREN@17..18 ")" + DOLLAR@18..19 "$" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline_double_dollar.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline_double_dollar.snap new file mode 100644 index 0000000000..8da22f3ecb --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__inline_double_dollar.snap @@ -0,0 +1,26 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"$$x \\in [0, \\infty)$$\"#)" + +--- +ROOT@0..21 + PREAMBLE@0..21 + FORMULA@0..21 + DOLLAR@0..2 "$$" + TEXT@2..4 + WORD@2..3 "x" + WHITESPACE@3..4 " " + GENERIC_COMMAND@4..19 + GENERIC_COMMAND_NAME@4..7 "\\in" + WHITESPACE@7..8 " " + MIXED_GROUP@8..19 + L_BRACK@8..9 "[" + TEXT@9..12 + WORD@9..10 "0" + COMMA@10..11 "," + WHITESPACE@11..12 " " + GENERIC_COMMAND@12..18 + GENERIC_COMMAND_NAME@12..18 "\\infty" + R_PAREN@18..19 ")" + DOLLAR@19..21 "$$" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_definition_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_definition_simple.snap new file mode 100644 index 0000000000..7eacd65c59 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_definition_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\label{foo}\"#)" + +--- +ROOT@0..11 + PREAMBLE@0..11 + LABEL_DEFINITION@0..11 + LABEL_DEFINITION_NAME@0..6 "\\label" + CURLY_GROUP_WORD@6..11 + L_CURLY@6..7 "{" + WORD@7..10 "foo" + R_CURLY@10..11 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_number.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_number.snap new file mode 100644 index 0000000000..46392d7574 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_number.snap @@ -0,0 +1,23 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\newlabel{foo}{{1.1}}\"#)" + +--- +ROOT@0..21 + PREAMBLE@0..21 + LABEL_NUMBER@0..21 + LABEL_NUMBER_NAME@0..9 "\\newlabel" + CURLY_GROUP_WORD@9..14 + L_CURLY@9..10 "{" + WORD@10..13 "foo" + R_CURLY@13..14 "}" + CURLY_GROUP@14..21 + L_CURLY@14..15 "{" + CURLY_GROUP@15..20 + L_CURLY@15..16 "{" + TEXT@16..19 + WORD@16..19 "1.1" + R_CURLY@19..20 "}" + R_CURLY@20..21 "}" + MISSING@21..21 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_multiple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_multiple.snap new file mode 100644 index 0000000000..e399397e09 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_multiple.snap @@ -0,0 +1,17 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\ref{foo, bar}\"#)" + +--- +ROOT@0..14 + PREAMBLE@0..14 + LABEL_REFERENCE@0..14 + LABEL_REFERENCE_NAME@0..4 "\\ref" + CURLY_GROUP_WORD_LIST@4..14 + L_CURLY@4..5 "{" + WORD@5..8 "foo" + COMMA@8..9 "," + WHITESPACE@9..10 " " + WORD@10..13 "bar" + R_CURLY@13..14 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_error.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_error.snap new file mode 100644 index 0000000000..2894a9c2ec --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_error.snap @@ -0,0 +1,18 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\crefrange{foo{bar}\"#)" + +--- +ROOT@0..19 + PREAMBLE@0..19 + LABEL_REFERENCE_RANGE@0..19 + LABEL_REFERENCE_RANGE_NAME@0..10 "\\crefrange" + CURLY_GROUP_WORD@10..14 + L_CURLY@10..11 "{" + WORD@11..14 "foo" + MISSING@14..14 "" + CURLY_GROUP_WORD@14..19 + L_CURLY@14..15 "{" + WORD@15..18 "bar" + R_CURLY@18..19 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_incomplete.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_incomplete.snap new file mode 100644 index 0000000000..0a1753867e --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_incomplete.snap @@ -0,0 +1,15 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\crefrange{foo}\"#)" + +--- +ROOT@0..15 + PREAMBLE@0..15 + LABEL_REFERENCE_RANGE@0..15 + LABEL_REFERENCE_RANGE_NAME@0..10 "\\crefrange" + CURLY_GROUP_WORD@10..15 + L_CURLY@10..11 "{" + WORD@11..14 "foo" + R_CURLY@14..15 "}" + MISSING@15..15 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_simple.snap new file mode 100644 index 0000000000..c06c31c1b7 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_range_simple.snap @@ -0,0 +1,18 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\crefrange{foo}{bar}\"#)" + +--- +ROOT@0..20 + PREAMBLE@0..20 + LABEL_REFERENCE_RANGE@0..20 + LABEL_REFERENCE_RANGE_NAME@0..10 "\\crefrange" + CURLY_GROUP_WORD@10..15 + L_CURLY@10..11 "{" + WORD@11..14 "foo" + R_CURLY@14..15 "}" + CURLY_GROUP_WORD@15..20 + L_CURLY@15..16 "{" + WORD@16..19 "bar" + R_CURLY@19..20 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_simple.snap new file mode 100644 index 0000000000..ed06f5dfc1 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__label_reference_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\ref{foo}\"#)" + +--- +ROOT@0..9 + PREAMBLE@0..9 + LABEL_REFERENCE@0..9 + LABEL_REFERENCE_NAME@0..4 "\\ref" + CURLY_GROUP_WORD_LIST@4..9 + L_CURLY@4..5 "{" + WORD@5..8 "foo" + R_CURLY@8..9 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_include_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_include_simple.snap new file mode 100644 index 0000000000..34565aa072 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_include_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\include{foo/bar}\"#)" + +--- +ROOT@0..17 + PREAMBLE@0..17 + LATEX_INCLUDE@0..17 + LATEX_INCLUDE_NAME@0..8 "\\include" + CURLY_GROUP_WORD_LIST@8..17 + L_CURLY@8..9 "{" + WORD@9..16 "foo/bar" + R_CURLY@16..17 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_input_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_input_simple.snap new file mode 100644 index 0000000000..208e898407 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__latex_input_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\input{foo/bar.tex}\"#)" + +--- +ROOT@0..19 + PREAMBLE@0..19 + LATEX_INCLUDE@0..19 + LATEX_INCLUDE_NAME@0..6 "\\input" + CURLY_GROUP_WORD_LIST@6..19 + L_CURLY@6..7 "{" + WORD@7..18 "foo/bar.tex" + R_CURLY@18..19 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_no_impl.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_no_impl.snap new file mode 100644 index 0000000000..2e39ee899c --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_no_impl.snap @@ -0,0 +1,15 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\DeclareMathOperator{\\foo}\"#)" + +--- +ROOT@0..26 + PREAMBLE@0..26 + MATH_OPERATOR@0..26 + MATH_OPERATOR_NAME@0..20 "\\DeclareMathOperator" + CURLY_GROUP_COMMAND@20..26 + L_CURLY@20..21 "{" + GENERIC_COMMAND_NAME@21..25 "\\foo" + R_CURLY@25..26 "}" + MISSING@26..26 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_simple.snap new file mode 100644 index 0000000000..bb44005153 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__math_operator_simple.snap @@ -0,0 +1,19 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\DeclareMathOperator{\\foo}{foo}\"#)" + +--- +ROOT@0..31 + PREAMBLE@0..31 + MATH_OPERATOR@0..31 + MATH_OPERATOR_NAME@0..20 "\\DeclareMathOperator" + CURLY_GROUP_COMMAND@20..26 + L_CURLY@20..21 "{" + GENERIC_COMMAND_NAME@21..25 "\\foo" + R_CURLY@25..26 "}" + CURLY_GROUP@26..31 + L_CURLY@26..27 "{" + TEXT@27..30 + WORD@27..30 "foo" + R_CURLY@30..31 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_empty.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_empty.snap new file mode 100644 index 0000000000..b8851b6e1d --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_empty.snap @@ -0,0 +1,13 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\usepackage{}\"#)" + +--- +ROOT@0..13 + PREAMBLE@0..13 + PACKAGE_INCLUDE@0..13 + PACKAGE_INCLUDE_NAME@0..11 "\\usepackage" + CURLY_GROUP_WORD_LIST@11..13 + L_CURLY@11..12 "{" + R_CURLY@12..13 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_multiple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_multiple.snap new file mode 100644 index 0000000000..e68b98d1b5 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_multiple.snap @@ -0,0 +1,17 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\usepackage{amsmath, lipsum}\"#)" + +--- +ROOT@0..28 + PREAMBLE@0..28 + PACKAGE_INCLUDE@0..28 + PACKAGE_INCLUDE_NAME@0..11 "\\usepackage" + CURLY_GROUP_WORD_LIST@11..28 + L_CURLY@11..12 "{" + WORD@12..19 "amsmath" + COMMA@19..20 "," + WHITESPACE@20..21 " " + WORD@21..27 "lipsum" + R_CURLY@27..28 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_options.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_options.snap new file mode 100644 index 0000000000..a33c4e0bd7 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_options.snap @@ -0,0 +1,37 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\usepackage[foo = bar, baz, qux]{amsmath}\"#)" + +--- +ROOT@0..41 + PREAMBLE@0..41 + PACKAGE_INCLUDE@0..41 + PACKAGE_INCLUDE_NAME@0..11 "\\usepackage" + BRACK_GROUP_KEY_VALUE@11..32 + L_BRACK@11..12 "[" + KEY_VALUE_BODY@12..31 + KEY_VALUE_PAIR@12..21 + KEY@12..16 + WORD@12..15 "foo" + WHITESPACE@15..16 " " + EQUALITY_SIGN@16..17 "=" + WHITESPACE@17..18 " " + VALUE@18..21 + TEXT@18..21 + WORD@18..21 "bar" + COMMA@21..22 "," + WHITESPACE@22..23 " " + KEY_VALUE_PAIR@23..26 + KEY@23..26 + WORD@23..26 "baz" + COMMA@26..27 "," + WHITESPACE@27..28 " " + KEY_VALUE_PAIR@28..31 + KEY@28..31 + WORD@28..31 "qux" + R_BRACK@31..32 "]" + CURLY_GROUP_WORD_LIST@32..41 + L_CURLY@32..33 "{" + WORD@33..40 "amsmath" + R_CURLY@40..41 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_simple.snap new file mode 100644 index 0000000000..00c075fdfe --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__package_include_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\usepackage{amsmath}\"#)" + +--- +ROOT@0..20 + PREAMBLE@0..20 + PACKAGE_INCLUDE@0..20 + PACKAGE_INCLUDE_NAME@0..11 "\\usepackage" + CURLY_GROUP_WORD_LIST@11..20 + L_CURLY@11..12 "{" + WORD@12..19 "amsmath" + R_CURLY@19..20 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter.snap new file mode 100644 index 0000000000..8c6bd90cb8 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter.snap @@ -0,0 +1,9 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"#1\"#)" + +--- +ROOT@0..2 + PREAMBLE@0..2 + PARAMETER@0..2 "#1" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter_error.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter_error.snap new file mode 100644 index 0000000000..1a9b68fa2d --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__parameter_error.snap @@ -0,0 +1,9 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"#\"#)" + +--- +ROOT@0..1 + PREAMBLE@0..1 + PARAMETER@0..1 "#" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__pgf_library_import_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__pgf_library_import_simple.snap new file mode 100644 index 0000000000..8f289f97a4 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__pgf_library_import_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\usepgflibrary{foo}\"#)" + +--- +ROOT@0..19 + PREAMBLE@0..19 + TIKZ_LIBRARY_IMPORT@0..19 + TIKZ_LIBRARY_IMPORT_NAME@0..14 "\\usepgflibrary" + CURLY_GROUP_WORD_LIST@14..19 + L_CURLY@14..15 "{" + WORD@15..18 "foo" + R_CURLY@18..19 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_enum_item.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_enum_item.snap new file mode 100644 index 0000000000..43dcae1987 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_enum_item.snap @@ -0,0 +1,45 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\begin{enumerate} \\item 1 \\item[2] 2 \\item 3 \\end{enumerate}\"#)" + +--- +ROOT@0..60 + PREAMBLE@0..60 + ENVIRONMENT@0..60 + BEGIN@0..18 + BEGIN_ENVIRONMENT_NAME@0..6 "\\begin" + CURLY_GROUP_WORD@6..18 + L_CURLY@6..7 "{" + WORD@7..16 "enumerate" + R_CURLY@16..17 "}" + WHITESPACE@17..18 " " + ENUM_ITEM@18..26 + ENUM_ITEM_NAME@18..23 "\\item" + WHITESPACE@23..24 " " + TEXT@24..26 + WORD@24..25 "1" + WHITESPACE@25..26 " " + ENUM_ITEM@26..37 + ENUM_ITEM_NAME@26..31 "\\item" + BRACK_GROUP@31..35 + L_BRACK@31..32 "[" + TEXT@32..33 + WORD@32..33 "2" + R_BRACK@33..34 "]" + WHITESPACE@34..35 " " + TEXT@35..37 + WORD@35..36 "2" + WHITESPACE@36..37 " " + ENUM_ITEM@37..45 + ENUM_ITEM_NAME@37..42 "\\item" + WHITESPACE@42..43 " " + TEXT@43..45 + WORD@43..44 "3" + WHITESPACE@44..45 " " + END@45..60 + END_ENVIRONMENT_NAME@45..49 "\\end" + CURLY_GROUP_WORD@49..60 + L_CURLY@49..50 "{" + WORD@50..59 "enumerate" + R_CURLY@59..60 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_invalid_nesting.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_invalid_nesting.snap new file mode 100644 index 0000000000..f999a50e9a --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_invalid_nesting.snap @@ -0,0 +1,23 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\section{Foo} \\chapter{Bar}\"#)" + +--- +ROOT@0..27 + PREAMBLE@0..27 + SECTION@0..14 + SECTION_NAME@0..8 "\\section" + CURLY_GROUP@8..14 + L_CURLY@8..9 "{" + TEXT@9..12 + WORD@9..12 "Foo" + R_CURLY@12..13 "}" + WHITESPACE@13..14 " " + CHAPTER@14..27 + CHAPTER_NAME@14..22 "\\chapter" + CURLY_GROUP@22..27 + L_CURLY@22..23 "{" + TEXT@23..26 + WORD@23..26 "Bar" + R_CURLY@26..27 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_nested.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_nested.snap new file mode 100644 index 0000000000..db5602e591 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_nested.snap @@ -0,0 +1,57 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\part{1}\\chapter{2}\\section{3}\\subsection{4}\\subsubsection{5}\\paragraph{6}\\subparagraph{7}\"#)" + +--- +ROOT@0..90 + PREAMBLE@0..90 + PART@0..90 + PART_NAME@0..5 "\\part" + CURLY_GROUP@5..8 + L_CURLY@5..6 "{" + TEXT@6..7 + WORD@6..7 "1" + R_CURLY@7..8 "}" + CHAPTER@8..90 + CHAPTER_NAME@8..16 "\\chapter" + CURLY_GROUP@16..19 + L_CURLY@16..17 "{" + TEXT@17..18 + WORD@17..18 "2" + R_CURLY@18..19 "}" + SECTION@19..90 + SECTION_NAME@19..27 "\\section" + CURLY_GROUP@27..30 + L_CURLY@27..28 "{" + TEXT@28..29 + WORD@28..29 "3" + R_CURLY@29..30 "}" + SUBSECTION@30..90 + SUBSECTION_NAME@30..41 "\\subsection" + CURLY_GROUP@41..44 + L_CURLY@41..42 "{" + TEXT@42..43 + WORD@42..43 "4" + R_CURLY@43..44 "}" + SUBSUBSECTION@44..90 + SUBSUBSECTION_NAME@44..58 "\\subsubsection" + CURLY_GROUP@58..61 + L_CURLY@58..59 "{" + TEXT@59..60 + WORD@59..60 "5" + R_CURLY@60..61 "}" + PARAGRAPH@61..90 + PARAGRAPH_NAME@61..71 "\\paragraph" + CURLY_GROUP@71..74 + L_CURLY@71..72 "{" + TEXT@72..73 + WORD@72..73 "6" + R_CURLY@73..74 "}" + SUBPARAGRAPH@74..90 + SUBPARAGRAPH_NAME@74..87 "\\subparagraph" + CURLY_GROUP@87..90 + L_CURLY@87..88 "{" + TEXT@88..89 + WORD@88..89 "7" + R_CURLY@89..90 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_siblings.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_siblings.snap new file mode 100644 index 0000000000..94d2ef40bd --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__structure_siblings.snap @@ -0,0 +1,29 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\section{Foo} Foo \\section{Bar} Bar\"#)" + +--- +ROOT@0..35 + PREAMBLE@0..35 + SECTION@0..18 + SECTION_NAME@0..8 "\\section" + CURLY_GROUP@8..14 + L_CURLY@8..9 "{" + TEXT@9..12 + WORD@9..12 "Foo" + R_CURLY@12..13 "}" + WHITESPACE@13..14 " " + TEXT@14..18 + WORD@14..17 "Foo" + WHITESPACE@17..18 " " + SECTION@18..35 + SECTION_NAME@18..26 "\\section" + CURLY_GROUP@26..32 + L_CURLY@26..27 "{" + TEXT@27..30 + WORD@27..30 "Bar" + R_CURLY@30..31 "}" + WHITESPACE@31..32 " " + TEXT@32..35 + WORD@32..35 "Bar" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_options.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_options.snap new file mode 100644 index 0000000000..863f27cf56 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_options.snap @@ -0,0 +1,25 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\includesvg[scale=.5]{foo/bar.svg}\"#)" + +--- +ROOT@0..34 + PREAMBLE@0..34 + SVG_INCLUDE@0..34 + SVG_INCLUDE_NAME@0..11 "\\includesvg" + BRACK_GROUP_KEY_VALUE@11..21 + L_BRACK@11..12 "[" + KEY_VALUE_BODY@12..20 + KEY_VALUE_PAIR@12..20 + KEY@12..17 + WORD@12..17 "scale" + EQUALITY_SIGN@17..18 "=" + VALUE@18..20 + TEXT@18..20 + WORD@18..20 ".5" + R_BRACK@20..21 "]" + CURLY_GROUP_WORD_LIST@21..34 + L_CURLY@21..22 "{" + WORD@22..33 "foo/bar.svg" + R_CURLY@33..34 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_simple.snap new file mode 100644 index 0000000000..02db942d99 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__svg_include_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\includesvg{foo/bar.svg}\"#)" + +--- +ROOT@0..24 + PREAMBLE@0..24 + SVG_INCLUDE@0..24 + SVG_INCLUDE_NAME@0..11 "\\includesvg" + CURLY_GROUP_WORD_LIST@11..24 + L_CURLY@11..12 "{" + WORD@12..23 "foo/bar.svg" + R_CURLY@23..24 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_full.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_full.snap new file mode 100644 index 0000000000..d8fe3816ad --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_full.snap @@ -0,0 +1,27 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\newtheorem{foo}[bar]{Foo}[baz]\"#)" + +--- +ROOT@0..31 + PREAMBLE@0..31 + THEOREM_DEFINITION@0..31 + THEOREM_DEFINITION_NAME@0..11 "\\newtheorem" + CURLY_GROUP_WORD@11..16 + L_CURLY@11..12 "{" + WORD@12..15 "foo" + R_CURLY@15..16 "}" + BRACK_GROUP_WORD@16..21 + L_BRACK@16..17 "[" + WORD@17..20 "bar" + R_BRACK@20..21 "]" + CURLY_GROUP@21..26 + L_CURLY@21..22 "{" + TEXT@22..25 + WORD@22..25 "Foo" + R_CURLY@25..26 "}" + BRACK_GROUP_WORD@26..31 + L_BRACK@26..27 "[" + WORD@27..30 "baz" + R_BRACK@30..31 "]" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_counter.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_counter.snap new file mode 100644 index 0000000000..9e550b19a4 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_counter.snap @@ -0,0 +1,19 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\newtheorem{foo}[bar]\"#)" + +--- +ROOT@0..21 + PREAMBLE@0..21 + THEOREM_DEFINITION@0..21 + THEOREM_DEFINITION_NAME@0..11 "\\newtheorem" + CURLY_GROUP_WORD@11..16 + L_CURLY@11..12 "{" + WORD@12..15 "foo" + R_CURLY@15..16 "}" + BRACK_GROUP_WORD@16..21 + L_BRACK@16..17 "[" + WORD@17..20 "bar" + R_BRACK@20..21 "]" + MISSING@21..21 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description.snap new file mode 100644 index 0000000000..356dc079f1 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description.snap @@ -0,0 +1,19 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\newtheorem{foo}{Foo}\"#)" + +--- +ROOT@0..21 + PREAMBLE@0..21 + THEOREM_DEFINITION@0..21 + THEOREM_DEFINITION_NAME@0..11 "\\newtheorem" + CURLY_GROUP_WORD@11..16 + L_CURLY@11..12 "{" + WORD@12..15 "foo" + R_CURLY@15..16 "}" + CURLY_GROUP@16..21 + L_CURLY@16..17 "{" + TEXT@17..20 + WORD@17..20 "Foo" + R_CURLY@20..21 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description_and_counter.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description_and_counter.snap new file mode 100644 index 0000000000..cc6e34d378 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_name_with_description_and_counter.snap @@ -0,0 +1,23 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\newtheorem{foo}[bar]{Foo}\"#)" + +--- +ROOT@0..26 + PREAMBLE@0..26 + THEOREM_DEFINITION@0..26 + THEOREM_DEFINITION_NAME@0..11 "\\newtheorem" + CURLY_GROUP_WORD@11..16 + L_CURLY@11..12 "{" + WORD@12..15 "foo" + R_CURLY@15..16 "}" + BRACK_GROUP_WORD@16..21 + L_BRACK@16..17 "[" + WORD@17..20 "bar" + R_BRACK@20..21 "]" + CURLY_GROUP@21..26 + L_CURLY@21..22 "{" + TEXT@22..25 + WORD@22..25 "Foo" + R_CURLY@25..26 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_only_name.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_only_name.snap new file mode 100644 index 0000000000..27a040b7ff --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__theorem_definition_only_name.snap @@ -0,0 +1,15 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\newtheorem{foo}\"#)" + +--- +ROOT@0..16 + PREAMBLE@0..16 + THEOREM_DEFINITION@0..16 + THEOREM_DEFINITION_NAME@0..11 "\\newtheorem" + CURLY_GROUP_WORD@11..16 + L_CURLY@11..12 "{" + WORD@12..15 "foo" + R_CURLY@15..16 "}" + MISSING@16..16 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__tikz_library_import_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__tikz_library_import_simple.snap new file mode 100644 index 0000000000..4915306c55 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__tikz_library_import_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\usetikzlibrary{foo}\"#)" + +--- +ROOT@0..20 + PREAMBLE@0..20 + TIKZ_LIBRARY_IMPORT@0..20 + TIKZ_LIBRARY_IMPORT_NAME@0..15 "\\usetikzlibrary" + CURLY_GROUP_WORD_LIST@15..20 + L_CURLY@15..16 "{" + WORD@16..19 "foo" + R_CURLY@19..20 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_braces.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_braces.snap new file mode 100644 index 0000000000..df463f501a --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_braces.snap @@ -0,0 +1,13 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"}{\"#)" + +--- +ROOT@0..2 + PREAMBLE@0..2 + ERROR@0..1 + R_CURLY@0..1 "}" + CURLY_GROUP@1..2 + L_CURLY@1..2 "{" + MISSING@2..2 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets.snap new file mode 100644 index 0000000000..578cbf6099 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets.snap @@ -0,0 +1,13 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"][\"#)" + +--- +ROOT@0..2 + PREAMBLE@0..2 + ERROR@0..1 + R_BRACK@0..1 "]" + MIXED_GROUP@1..2 + L_BRACK@1..2 "[" + MISSING@2..2 "" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets_with_group.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets_with_group.snap new file mode 100644 index 0000000000..d929c98c46 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__unmatched_brackets_with_group.snap @@ -0,0 +1,16 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"{][}\"#)" + +--- +ROOT@0..4 + PREAMBLE@0..4 + CURLY_GROUP@0..4 + L_CURLY@0..1 "{" + ERROR@1..2 + R_BRACK@1..2 "]" + MIXED_GROUP@2..3 + L_BRACK@2..3 "[" + MISSING@3..3 "" + R_CURLY@3..4 "}" + diff --git a/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__verbatim_include_simple.snap b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__verbatim_include_simple.snap new file mode 100644 index 0000000000..399e16d791 --- /dev/null +++ b/support/texlab/src/syntax/latex/snapshots/texlab__syntax__latex__parser__tests__verbatim_include_simple.snap @@ -0,0 +1,14 @@ +--- +source: src/syntax/latex/parser.rs +expression: "setup(r#\"\\verbatiminput{foo/bar.txt}\"#)" + +--- +ROOT@0..27 + PREAMBLE@0..27 + VERBATIM_INCLUDE@0..27 + VERBATIM_INCLUDE_NAME@0..14 "\\verbatiminput" + CURLY_GROUP_WORD_LIST@14..27 + L_CURLY@14..15 "{" + WORD@15..26 "foo/bar.txt" + R_CURLY@26..27 "}" + diff --git a/support/texlab/src/syntax/latexindent.rs b/support/texlab/src/syntax/latexindent.rs deleted file mode 100644 index c45c95d4e5..0000000000 --- a/support/texlab/src/syntax/latexindent.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::{io, process::Stdio}; -use tempfile::tempdir; -use tokio::{fs, process::Command}; - -pub async fn format(text: &str, extension: &str) -> io::Result<String> { - let dir = tempdir()?; - let input = format!("input.{}", extension); - let output = format!("output.{}", extension); - fs::write(dir.path().join(&input), text).await?; - - Command::new("latexindent") - .args(&["-o", &output, &input]) - .current_dir(dir.path()) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn()? - .await?; - - fs::read_to_string(dir.path().join(&output)).await -} diff --git a/support/texlab/src/syntax/lsp_kind.rs b/support/texlab/src/syntax/lsp_kind.rs deleted file mode 100644 index 7d92fd4ee4..0000000000 --- a/support/texlab/src/syntax/lsp_kind.rs +++ /dev/null @@ -1,93 +0,0 @@ -use super::lang_data::BibtexEntryTypeCategory; -use crate::protocol::{CompletionItemKind, SymbolKind}; - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum Structure { - Command, - Snippet, - Environment, - Section, - Float, - Theorem, - Equation, - Item, - Label, - Folder, - File, - PgfLibrary, - TikzLibrary, - Color, - ColorModel, - Package, - Class, - Entry(BibtexEntryTypeCategory), - Field, - Argument, - GlossaryEntry, -} - -impl Structure { - pub fn completion_kind(self) -> CompletionItemKind { - match self { - Self::Command => CompletionItemKind::Function, - Self::Snippet => CompletionItemKind::Snippet, - Self::Environment => CompletionItemKind::Enum, - Self::Section => CompletionItemKind::Module, - Self::Float => CompletionItemKind::Method, - Self::Theorem => CompletionItemKind::Variable, - Self::Equation => CompletionItemKind::Constant, - Self::Item => CompletionItemKind::EnumMember, - Self::Label => CompletionItemKind::Constructor, - Self::Folder => CompletionItemKind::Folder, - Self::File => CompletionItemKind::File, - Self::PgfLibrary => CompletionItemKind::Property, - Self::TikzLibrary => CompletionItemKind::Property, - Self::Color => CompletionItemKind::Color, - Self::ColorModel => CompletionItemKind::Color, - Self::Package => CompletionItemKind::Class, - Self::Class => CompletionItemKind::Class, - Self::Entry(BibtexEntryTypeCategory::Misc) => CompletionItemKind::Interface, - Self::Entry(BibtexEntryTypeCategory::String) => CompletionItemKind::Text, - Self::Entry(BibtexEntryTypeCategory::Article) => CompletionItemKind::Event, - Self::Entry(BibtexEntryTypeCategory::Book) => CompletionItemKind::Struct, - Self::Entry(BibtexEntryTypeCategory::Collection) => CompletionItemKind::TypeParameter, - Self::Entry(BibtexEntryTypeCategory::Part) => CompletionItemKind::Operator, - Self::Entry(BibtexEntryTypeCategory::Thesis) => CompletionItemKind::Unit, - Self::Field => CompletionItemKind::Field, - Self::Argument => CompletionItemKind::Value, - Self::GlossaryEntry => CompletionItemKind::Keyword, - } - } - - pub fn symbol_kind(self) -> SymbolKind { - match self { - Self::Command => SymbolKind::Function, - Self::Snippet => unimplemented!(), - Self::Environment => SymbolKind::Enum, - Self::Section => SymbolKind::Module, - Self::Float => SymbolKind::Method, - Self::Theorem => SymbolKind::Variable, - Self::Equation => SymbolKind::Constant, - Self::Item => SymbolKind::EnumMember, - Self::Label => SymbolKind::Constructor, - Self::Folder => SymbolKind::Namespace, - Self::File => SymbolKind::File, - Self::PgfLibrary => SymbolKind::Property, - Self::TikzLibrary => SymbolKind::Property, - Self::Color => unimplemented!(), - Self::ColorModel => unimplemented!(), - Self::Package => SymbolKind::Class, - Self::Class => SymbolKind::Class, - Self::Entry(BibtexEntryTypeCategory::Misc) => SymbolKind::Interface, - Self::Entry(BibtexEntryTypeCategory::String) => SymbolKind::String, - Self::Entry(BibtexEntryTypeCategory::Article) => SymbolKind::Event, - Self::Entry(BibtexEntryTypeCategory::Book) => SymbolKind::Struct, - Self::Entry(BibtexEntryTypeCategory::Collection) => SymbolKind::TypeParameter, - Self::Entry(BibtexEntryTypeCategory::Part) => SymbolKind::Operator, - Self::Entry(BibtexEntryTypeCategory::Thesis) => SymbolKind::Object, - Self::Field => SymbolKind::Field, - Self::Argument => SymbolKind::Number, - Self::GlossaryEntry => unimplemented!(), - } - } -} diff --git a/support/texlab/src/syntax/mod.rs b/support/texlab/src/syntax/mod.rs deleted file mode 100644 index 87b33792df..0000000000 --- a/support/texlab/src/syntax/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub mod bibtex; -mod generic_ast; -mod lang_data; -pub mod latex; -pub mod latexindent; -mod lsp_kind; -mod text; - -pub use self::{ - generic_ast::{Ast, AstNodeIndex}, - lang_data::*, - lsp_kind::Structure, - text::{CharStream, Span, SyntaxNode}, -}; diff --git a/support/texlab/src/syntax/text.rs b/support/texlab/src/syntax/text.rs deleted file mode 100644 index 17d6876126..0000000000 --- a/support/texlab/src/syntax/text.rs +++ /dev/null @@ -1,260 +0,0 @@ -use crate::protocol::{Position, Range}; -use serde::{Deserialize, Serialize}; -use std::{iter::Peekable, str::CharIndices}; - -pub trait SyntaxNode { - fn range(&self) -> Range; - - fn start(&self) -> Position { - self.range().start - } - - fn end(&self) -> Position { - self.range().end - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Span { - pub range: Range, - pub text: String, -} - -impl Span { - pub fn new(range: Range, text: String) -> Self { - Self { range, text } - } -} - -impl SyntaxNode for Span { - fn range(&self) -> Range { - self.range - } -} - -#[derive(Debug)] -pub struct CharStream<'a> { - text: &'a str, - chars: Peekable<CharIndices<'a>>, - pub current_position: Position, - pub current_index: usize, - start_position: Position, - start_index: usize, -} - -impl<'a> CharStream<'a> { - pub fn new(text: &'a str) -> Self { - Self { - text, - chars: text.char_indices().peekable(), - current_position: Position::new(0, 0), - current_index: 0, - start_position: Position::new(0, 0), - start_index: 0, - } - } - - pub fn peek(&mut self) -> Option<char> { - self.chars.peek().map(|(_, c)| *c) - } - - pub fn satifies<P: FnOnce(&char) -> bool>(&mut self, predicate: P) -> bool { - self.peek().filter(predicate).is_some() - } - - pub fn skip_rest_of_line(&mut self) { - loop { - match self.peek() { - Some('\n') => { - self.next(); - break; - } - Some(_) => { - self.next(); - } - None => { - break; - } - } - } - } - - pub fn start_span(&mut self) { - self.start_index = self.current_index; - self.start_position = self.current_position; - } - - pub fn end_span(&mut self) -> Span { - let range = Range::new(self.start_position, self.current_position); - let text = &self.text[self.start_index..self.current_index]; - Span::new(range, text.to_owned()) - } - - pub fn seek(&mut self, position: Position) { - while self.current_position < position { - self.next(); - } - } - - pub fn command(&mut self) -> Span { - self.start_span(); - self.next(); - let mut escape = true; - while self.satifies(|c| is_command_char(*c)) { - self.next(); - escape = false; - } - - if let Some(c) = self.peek() { - if c != '\r' && c != '\n' && (escape || c == '*') { - self.next(); - } - } - - self.end_span() - } - - fn update_position(&mut self, c: char) { - if c == '\n' { - self.current_position.line += 1; - self.current_position.character = 0; - } else { - self.current_position.character += 1; - } - } - - pub fn extract(text: &'a str, range: Range) -> String { - let mut stream = Self::new(text); - stream.seek(range.start); - stream.start_span(); - stream.seek(range.end); - stream.end_span().text - } -} - -impl<'a> Iterator for CharStream<'a> { - type Item = char; - - fn next(&mut self) -> Option<char> { - if let Some((i, c)) = self.chars.next() { - self.current_index = i + c.len_utf8(); - self.update_position(c); - Some(c) - } else { - None - } - } -} - -fn is_command_char(c: char) -> bool { - c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '@' -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::protocol::RangeExt; - - #[test] - fn peek() { - let mut stream = CharStream::new("ab\nc"); - assert_eq!(Some('a'), stream.peek()); - assert_eq!(Some('a'), stream.next()); - assert_eq!(Some('b'), stream.peek()); - assert_eq!(Some('b'), stream.next()); - assert_eq!(Some('\n'), stream.peek()); - assert_eq!(Some('\n'), stream.next()); - assert_eq!(Some('c'), stream.peek()); - assert_eq!(Some('c'), stream.next()); - assert_eq!(None, stream.peek()); - assert_eq!(None, stream.next()); - } - - #[test] - fn span() { - let mut stream = CharStream::new("abc\ndef"); - stream.next(); - stream.start_span(); - stream.next(); - stream.next(); - let span = stream.end_span(); - assert_eq!( - Span::new(Range::new_simple(0, 1, 0, 3), "bc".to_owned()), - span - ); - assert_eq!(Position::new(0, 1), span.start()); - assert_eq!(Position::new(0, 3), span.end()); - } - - #[test] - fn span_unicode() { - let mut stream = CharStream::new("😀😃😄😁"); - stream.next(); - stream.start_span(); - stream.next(); - stream.next(); - let span = stream.end_span(); - assert_eq!( - Span::new(Range::new_simple(0, 1, 0, 3), "😃😄".to_owned()), - span - ); - } - - #[test] - fn satifies() { - let mut stream = CharStream::new("aBc"); - assert_eq!(true, stream.satifies(|c| c.is_lowercase())); - stream.next(); - assert_eq!(false, stream.satifies(|c| c.is_lowercase())); - } - - #[test] - fn skip_rest_of_line() { - let mut stream = CharStream::new("abc\ndef"); - stream.skip_rest_of_line(); - assert_eq!(Some('d'), stream.next()); - stream.skip_rest_of_line(); - assert_eq!(None, stream.next()); - stream.skip_rest_of_line(); - assert_eq!(None, stream.next()); - } - - #[test] - fn seek() { - let mut stream = CharStream::new("abc\ndefghi"); - let pos = Position::new(1, 2); - stream.seek(pos); - assert_eq!(Some('f'), stream.peek()); - } - - #[test] - fn command_basic() { - let mut stream = CharStream::new("\\foo@bar"); - let span = stream.command(); - assert_eq!( - Span::new(Range::new_simple(0, 0, 0, 8), "\\foo@bar".to_owned()), - span - ); - } - - #[test] - fn command_star() { - let mut stream = CharStream::new("\\foo*"); - let span = stream.command(); - assert_eq!( - Span::new(Range::new_simple(0, 0, 0, 5), "\\foo*".to_owned()), - span - ); - } - - #[test] - fn command_escape() { - let mut stream = CharStream::new("\\**"); - let span = stream.command(); - assert_eq!( - Span::new(Range::new_simple(0, 0, 0, 2), "\\*".to_owned()), - span - ); - } -} |