summaryrefslogtreecommitdiff
path: root/support/texlab/src/syntax
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/syntax')
-rw-r--r--support/texlab/src/syntax/bibtex/ast.rs577
-rw-r--r--support/texlab/src/syntax/bibtex/finder.rs110
-rw-r--r--support/texlab/src/syntax/bibtex/lexer.rs176
-rw-r--r--support/texlab/src/syntax/bibtex/mod.rs75
-rw-r--r--support/texlab/src/syntax/bibtex/parser.rs219
-rw-r--r--support/texlab/src/syntax/language.json2213
-rw-r--r--support/texlab/src/syntax/language.rs231
-rw-r--r--support/texlab/src/syntax/latex/ast.rs345
-rw-r--r--support/texlab/src/syntax/latex/env.rs123
-rw-r--r--support/texlab/src/syntax/latex/finder.rs74
-rw-r--r--support/texlab/src/syntax/latex/glossary.rs58
-rw-r--r--support/texlab/src/syntax/latex/lexer.rs179
-rw-r--r--support/texlab/src/syntax/latex/math.rs199
-rw-r--r--support/texlab/src/syntax/latex/mod.rs358
-rw-r--r--support/texlab/src/syntax/latex/parser.rs135
-rw-r--r--support/texlab/src/syntax/latex/printer.rs77
-rw-r--r--support/texlab/src/syntax/latex/structure.rs250
-rw-r--r--support/texlab/src/syntax/mod.rs27
-rw-r--r--support/texlab/src/syntax/text.rs266
19 files changed, 5692 insertions, 0 deletions
diff --git a/support/texlab/src/syntax/bibtex/ast.rs b/support/texlab/src/syntax/bibtex/ast.rs
new file mode 100644
index 0000000000..09f2ee5b88
--- /dev/null
+++ b/support/texlab/src/syntax/bibtex/ast.rs
@@ -0,0 +1,577 @@
+use crate::range::RangeExt;
+use crate::syntax::text::{Span, SyntaxNode};
+use lsp_types::Range;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum BibtexTokenKind {
+ PreambleKind,
+ StringKind,
+ EntryKind,
+ Word,
+ Command,
+ Assign,
+ Comma,
+ Concat,
+ Quote,
+ BeginBrace,
+ EndBrace,
+ BeginParen,
+ EndParen,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct BibtexToken {
+ pub span: Span,
+ pub kind: BibtexTokenKind,
+}
+
+impl BibtexToken {
+ pub fn new(span: Span, kind: BibtexTokenKind) -> Self {
+ BibtexToken { span, kind }
+ }
+
+ pub fn text(&self) -> &str {
+ &self.span.text
+ }
+}
+
+impl SyntaxNode for BibtexToken {
+ fn range(&self) -> Range {
+ self.span.range
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct BibtexRoot {
+ pub children: Vec<BibtexDeclaration>,
+}
+
+impl BibtexRoot {
+ pub fn new(children: Vec<BibtexDeclaration>) -> Self {
+ BibtexRoot { children }
+ }
+}
+
+impl SyntaxNode for BibtexRoot {
+ fn range(&self) -> Range {
+ if self.children.is_empty() {
+ Range::new_simple(0, 0, 0, 0)
+ } else {
+ Range::new(
+ self.children[0].start(),
+ self.children[self.children.len() - 1].end(),
+ )
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum BibtexDeclaration {
+ Comment(Box<BibtexComment>),
+ Preamble(Box<BibtexPreamble>),
+ String(Box<BibtexString>),
+ Entry(Box<BibtexEntry>),
+}
+
+impl BibtexDeclaration {
+ pub fn accept<'a, T: BibtexVisitor<'a>>(&'a self, visitor: &mut T) {
+ match self {
+ BibtexDeclaration::Comment(comment) => visitor.visit_comment(comment),
+ BibtexDeclaration::Preamble(preamble) => visitor.visit_preamble(preamble),
+ BibtexDeclaration::String(string) => visitor.visit_string(string),
+ BibtexDeclaration::Entry(entry) => visitor.visit_entry(entry),
+ }
+ }
+}
+
+impl SyntaxNode for BibtexDeclaration {
+ fn range(&self) -> Range {
+ match self {
+ BibtexDeclaration::Comment(comment) => comment.range,
+ BibtexDeclaration::Preamble(preamble) => preamble.range,
+ BibtexDeclaration::String(string) => string.range,
+ BibtexDeclaration::Entry(entry) => entry.range,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct BibtexComment {
+ pub range: Range,
+ pub token: BibtexToken,
+}
+
+impl BibtexComment {
+ pub fn new(token: BibtexToken) -> Self {
+ BibtexComment {
+ range: token.range(),
+ token,
+ }
+ }
+}
+
+impl SyntaxNode for BibtexComment {
+ fn range(&self) -> Range {
+ self.range
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct BibtexPreamble {
+ pub range: Range,
+ pub ty: BibtexToken,
+ pub left: Option<BibtexToken>,
+ pub content: Option<BibtexContent>,
+ pub right: Option<BibtexToken>,
+}
+
+impl BibtexPreamble {
+ pub fn new(
+ ty: BibtexToken,
+ left: Option<BibtexToken>,
+ content: Option<BibtexContent>,
+ right: Option<BibtexToken>,
+ ) -> Self {
+ let end = if let Some(ref right) = right {
+ right.end()
+ } else if let Some(ref content) = content {
+ content.end()
+ } else if let Some(ref left) = left {
+ left.end()
+ } else {
+ ty.end()
+ };
+ BibtexPreamble {
+ range: Range::new(ty.start(), end),
+ ty,
+ left,
+ content,
+ right,
+ }
+ }
+}
+
+impl SyntaxNode for BibtexPreamble {
+ fn range(&self) -> Range {
+ self.range
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct BibtexString {
+ pub range: Range,
+ pub ty: BibtexToken,
+ pub left: Option<BibtexToken>,
+ pub name: Option<BibtexToken>,
+ pub assign: Option<BibtexToken>,
+ pub value: Option<BibtexContent>,
+ pub right: Option<BibtexToken>,
+}
+
+impl BibtexString {
+ pub fn new(
+ ty: BibtexToken,
+ left: Option<BibtexToken>,
+ name: Option<BibtexToken>,
+ assign: Option<BibtexToken>,
+ value: Option<BibtexContent>,
+ right: Option<BibtexToken>,
+ ) -> Self {
+ let end = if let Some(ref right) = right {
+ right.end()
+ } else if let Some(ref value) = value {
+ value.end()
+ } else if let Some(ref assign) = assign {
+ assign.end()
+ } else if let Some(ref name) = name {
+ name.end()
+ } else if let Some(ref left) = left {
+ left.end()
+ } else {
+ ty.end()
+ };
+
+ BibtexString {
+ range: Range::new(ty.start(), end),
+ ty,
+ left,
+ name,
+ assign,
+ value,
+ right,
+ }
+ }
+}
+
+impl SyntaxNode for BibtexString {
+ fn range(&self) -> Range {
+ self.range
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct BibtexEntry {
+ pub range: Range,
+ pub ty: BibtexToken,
+ pub left: Option<BibtexToken>,
+ pub key: Option<BibtexToken>,
+ pub comma: Option<BibtexToken>,
+ pub fields: Vec<BibtexField>,
+ pub right: Option<BibtexToken>,
+}
+
+impl BibtexEntry {
+ pub fn new(
+ ty: BibtexToken,
+ left: Option<BibtexToken>,
+ key: Option<BibtexToken>,
+ comma: Option<BibtexToken>,
+ fields: Vec<BibtexField>,
+ right: Option<BibtexToken>,
+ ) -> Self {
+ let end = if let Some(ref right) = right {
+ right.end()
+ } else if !fields.is_empty() {
+ fields[fields.len() - 1].range.end
+ } else if let Some(ref comma) = comma {
+ comma.end()
+ } else if let Some(ref key) = key {
+ key.end()
+ } else if let Some(ref left) = left {
+ left.end()
+ } else {
+ ty.end()
+ };
+
+ BibtexEntry {
+ range: Range::new(ty.start(), end),
+ ty,
+ left,
+ key,
+ comma,
+ fields,
+ right,
+ }
+ }
+
+ pub fn is_comment(&self) -> bool {
+ self.ty.text().to_lowercase() == "@comment"
+ }
+
+ pub fn find_field(&self, name: &str) -> Option<&BibtexField> {
+ self.fields
+ .iter()
+ .find(|field| field.name.text().to_lowercase() == name)
+ }
+}
+
+impl SyntaxNode for BibtexEntry {
+ fn range(&self) -> Range {
+ self.range
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct BibtexField {
+ pub range: Range,
+ pub name: BibtexToken,
+ pub assign: Option<BibtexToken>,
+ pub content: Option<BibtexContent>,
+ pub comma: Option<BibtexToken>,
+}
+
+impl BibtexField {
+ pub fn new(
+ name: BibtexToken,
+ assign: Option<BibtexToken>,
+ content: Option<BibtexContent>,
+ comma: Option<BibtexToken>,
+ ) -> Self {
+ let end = if let Some(ref comma) = comma {
+ comma.end()
+ } else if let Some(ref content) = content {
+ content.end()
+ } else if let Some(ref assign) = assign {
+ assign.end()
+ } else {
+ name.end()
+ };
+
+ BibtexField {
+ range: Range::new(name.start(), end),
+ name,
+ assign,
+ content,
+ comma,
+ }
+ }
+}
+
+impl SyntaxNode for BibtexField {
+ fn range(&self) -> Range {
+ self.range
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum BibtexContent {
+ Word(BibtexWord),
+ Command(BibtexCommand),
+ QuotedContent(BibtexQuotedContent),
+ BracedContent(BibtexBracedContent),
+ Concat(Box<BibtexConcat>),
+}
+
+impl BibtexContent {
+ pub fn accept<'a, T: BibtexVisitor<'a>>(&'a self, visitor: &mut T) {
+ match self {
+ BibtexContent::Word(word) => visitor.visit_word(word),
+ BibtexContent::Command(command) => visitor.visit_command(command),
+ BibtexContent::QuotedContent(content) => visitor.visit_quoted_content(content),
+ BibtexContent::BracedContent(content) => visitor.visit_braced_content(content),
+ BibtexContent::Concat(concat) => visitor.visit_concat(concat),
+ }
+ }
+}
+
+impl SyntaxNode for BibtexContent {
+ fn range(&self) -> Range {
+ match self {
+ BibtexContent::Word(word) => word.range(),
+ BibtexContent::Command(command) => command.range(),
+ BibtexContent::QuotedContent(content) => content.range(),
+ BibtexContent::BracedContent(content) => content.range(),
+ BibtexContent::Concat(concat) => concat.range(),
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct BibtexWord {
+ pub range: Range,
+ pub token: BibtexToken,
+}
+
+impl BibtexWord {
+ pub fn new(token: BibtexToken) -> Self {
+ BibtexWord {
+ range: token.range(),
+ token,
+ }
+ }
+}
+
+impl SyntaxNode for BibtexWord {
+ fn range(&self) -> Range {
+ self.range
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct BibtexCommand {
+ pub range: Range,
+ pub token: BibtexToken,
+}
+
+impl BibtexCommand {
+ pub fn new(token: BibtexToken) -> Self {
+ BibtexCommand {
+ range: token.range(),
+ token,
+ }
+ }
+}
+
+impl SyntaxNode for BibtexCommand {
+ fn range(&self) -> Range {
+ self.range
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct BibtexQuotedContent {
+ pub range: Range,
+ pub left: BibtexToken,
+ pub children: Vec<BibtexContent>,
+ pub right: Option<BibtexToken>,
+}
+
+impl BibtexQuotedContent {
+ pub fn new(
+ left: BibtexToken,
+ children: Vec<BibtexContent>,
+ right: Option<BibtexToken>,
+ ) -> Self {
+ let end = if let Some(ref right) = right {
+ right.end()
+ } else if !children.is_empty() {
+ children[children.len() - 1].end()
+ } else {
+ left.end()
+ };
+
+ BibtexQuotedContent {
+ range: Range::new(left.start(), end),
+ left,
+ children,
+ right,
+ }
+ }
+}
+
+impl SyntaxNode for BibtexQuotedContent {
+ fn range(&self) -> Range {
+ self.range
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct BibtexBracedContent {
+ pub range: Range,
+ pub left: BibtexToken,
+ pub children: Vec<BibtexContent>,
+ pub right: Option<BibtexToken>,
+}
+
+impl BibtexBracedContent {
+ pub fn new(
+ left: BibtexToken,
+ children: Vec<BibtexContent>,
+ right: Option<BibtexToken>,
+ ) -> Self {
+ let end = if let Some(ref right) = right {
+ right.end()
+ } else if !children.is_empty() {
+ children[children.len() - 1].end()
+ } else {
+ left.end()
+ };
+
+ BibtexBracedContent {
+ range: Range::new(left.start(), end),
+ left,
+ children,
+ right,
+ }
+ }
+}
+
+impl SyntaxNode for BibtexBracedContent {
+ fn range(&self) -> Range {
+ self.range
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct BibtexConcat {
+ pub range: Range,
+ pub left: BibtexContent,
+ pub operator: BibtexToken,
+ pub right: Option<BibtexContent>,
+}
+
+impl BibtexConcat {
+ pub fn new(left: BibtexContent, operator: BibtexToken, right: Option<BibtexContent>) -> Self {
+ let end = if let Some(ref right) = right {
+ right.end()
+ } else {
+ operator.end()
+ };
+
+ BibtexConcat {
+ range: Range::new(left.start(), end),
+ left,
+ operator,
+ right,
+ }
+ }
+}
+
+impl SyntaxNode for BibtexConcat {
+ fn range(&self) -> Range {
+ self.range
+ }
+}
+
+pub trait BibtexVisitor<'a> {
+ fn visit_root(&mut self, root: &'a BibtexRoot);
+
+ fn visit_comment(&mut self, comment: &'a BibtexComment);
+
+ fn visit_preamble(&mut self, preamble: &'a BibtexPreamble);
+
+ fn visit_string(&mut self, string: &'a BibtexString);
+
+ fn visit_entry(&mut self, entry: &'a BibtexEntry);
+
+ fn visit_field(&mut self, field: &'a BibtexField);
+
+ fn visit_word(&mut self, word: &'a BibtexWord);
+
+ fn visit_command(&mut self, command: &'a BibtexCommand);
+
+ fn visit_quoted_content(&mut self, content: &'a BibtexQuotedContent);
+
+ fn visit_braced_content(&mut self, content: &'a BibtexBracedContent);
+
+ fn visit_concat(&mut self, concat: &'a BibtexConcat);
+}
+
+pub struct BibtexWalker;
+
+impl BibtexWalker {
+ pub fn walk_root<'a, T: BibtexVisitor<'a>>(visitor: &mut T, root: &'a BibtexRoot) {
+ for declaration in &root.children {
+ declaration.accept(visitor);
+ }
+ }
+
+ pub fn walk_preamble<'a, T: BibtexVisitor<'a>>(visitor: &mut T, preamble: &'a BibtexPreamble) {
+ if let Some(ref content) = preamble.content {
+ content.accept(visitor);
+ }
+ }
+
+ pub fn walk_string<'a, T: BibtexVisitor<'a>>(visitor: &mut T, string: &'a BibtexString) {
+ if let Some(ref value) = string.value {
+ value.accept(visitor);
+ }
+ }
+
+ pub fn walk_entry<'a, T: BibtexVisitor<'a>>(visitor: &mut T, entry: &'a BibtexEntry) {
+ for field in &entry.fields {
+ visitor.visit_field(field);
+ }
+ }
+
+ pub fn walk_field<'a, T: BibtexVisitor<'a>>(visitor: &mut T, field: &'a BibtexField) {
+ if let Some(ref content) = field.content {
+ content.accept(visitor);
+ }
+ }
+
+ pub fn walk_quoted_content<'a, T: BibtexVisitor<'a>>(
+ visitor: &mut T,
+ content: &'a BibtexQuotedContent,
+ ) {
+ for child in &content.children {
+ child.accept(visitor);
+ }
+ }
+
+ pub fn walk_braced_content<'a, T: BibtexVisitor<'a>>(
+ visitor: &mut T,
+ content: &'a BibtexBracedContent,
+ ) {
+ for child in &content.children {
+ child.accept(visitor);
+ }
+ }
+
+ pub fn walk_concat<'a, T: BibtexVisitor<'a>>(visitor: &mut T, concat: &'a BibtexConcat) {
+ concat.left.accept(visitor);
+ if let Some(ref right) = concat.right {
+ right.accept(visitor);
+ }
+ }
+}
diff --git a/support/texlab/src/syntax/bibtex/finder.rs b/support/texlab/src/syntax/bibtex/finder.rs
new file mode 100644
index 0000000000..acc46c6f45
--- /dev/null
+++ b/support/texlab/src/syntax/bibtex/finder.rs
@@ -0,0 +1,110 @@
+use super::ast::*;
+use crate::range::RangeExt;
+use crate::syntax::text::SyntaxNode;
+use lsp_types::Position;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum BibtexNode<'a> {
+ Root(&'a BibtexRoot),
+ Preamble(&'a BibtexPreamble),
+ String(&'a BibtexString),
+ Entry(&'a BibtexEntry),
+ Comment(&'a BibtexComment),
+ Field(&'a BibtexField),
+ Word(&'a BibtexWord),
+ Command(&'a BibtexCommand),
+ QuotedContent(&'a BibtexQuotedContent),
+ BracedContent(&'a BibtexBracedContent),
+ Concat(&'a BibtexConcat),
+}
+
+#[derive(Debug)]
+pub struct BibtexFinder<'a> {
+ pub position: Position,
+ pub results: Vec<BibtexNode<'a>>,
+}
+
+impl<'a> BibtexFinder<'a> {
+ pub fn new(position: Position) -> Self {
+ BibtexFinder {
+ position,
+ results: Vec::new(),
+ }
+ }
+}
+
+impl<'a> BibtexVisitor<'a> for BibtexFinder<'a> {
+ fn visit_root(&mut self, root: &'a BibtexRoot) {
+ if root.range().contains(self.position) {
+ self.results.push(BibtexNode::Root(root));
+ BibtexWalker::walk_root(self, root);
+ }
+ }
+
+ fn visit_comment(&mut self, comment: &'a BibtexComment) {
+ if comment.range.contains(self.position) {
+ self.results.push(BibtexNode::Comment(comment));
+ }
+ }
+
+ fn visit_preamble(&mut self, preamble: &'a BibtexPreamble) {
+ if preamble.range.contains(self.position) {
+ self.results.push(BibtexNode::Preamble(preamble));
+ BibtexWalker::walk_preamble(self, preamble);
+ }
+ }
+
+ fn visit_string(&mut self, string: &'a BibtexString) {
+ if string.range.contains(self.position) {
+ self.results.push(BibtexNode::String(string));
+ BibtexWalker::walk_string(self, string);
+ }
+ }
+
+ fn visit_entry(&mut self, entry: &'a BibtexEntry) {
+ if entry.range.contains(self.position) {
+ self.results.push(BibtexNode::Entry(entry));
+ BibtexWalker::walk_entry(self, entry);
+ }
+ }
+
+ fn visit_field(&mut self, field: &'a BibtexField) {
+ if field.range.contains(self.position) {
+ self.results.push(BibtexNode::Field(field));
+ BibtexWalker::walk_field(self, field);
+ }
+ }
+
+ fn visit_word(&mut self, word: &'a BibtexWord) {
+ if word.range.contains(self.position) {
+ self.results.push(BibtexNode::Word(word));
+ }
+ }
+
+ fn visit_command(&mut self, command: &'a BibtexCommand) {
+ if command.range.contains(self.position) {
+ self.results.push(BibtexNode::Command(command));
+ }
+ }
+
+ fn visit_quoted_content(&mut self, content: &'a BibtexQuotedContent) {
+ if content.range.contains(self.position) {
+ self.results.push(BibtexNode::QuotedContent(content));
+ BibtexWalker::walk_quoted_content(self, content);
+ }
+ }
+
+ fn visit_braced_content(&mut self, content: &'a BibtexBracedContent) {
+ if content.range.contains(self.position) {
+ self.results.push(BibtexNode::BracedContent(content));
+ BibtexWalker::walk_braced_content(self, content);
+ }
+ }
+
+ fn visit_concat(&mut self, concat: &'a BibtexConcat) {
+ if concat.range.contains(self.position) {
+ self.results.push(BibtexNode::Concat(concat));
+ BibtexWalker::walk_concat(self, concat);
+ }
+ }
+}
diff --git a/support/texlab/src/syntax/bibtex/lexer.rs b/support/texlab/src/syntax/bibtex/lexer.rs
new file mode 100644
index 0000000000..8278ef2631
--- /dev/null
+++ b/support/texlab/src/syntax/bibtex/lexer.rs
@@ -0,0 +1,176 @@
+use super::ast::{BibtexToken, BibtexTokenKind};
+use crate::syntax::text::CharStream;
+
+pub struct BibtexLexer<'a> {
+ stream: CharStream<'a>,
+}
+
+impl<'a> BibtexLexer<'a> {
+ pub fn new(text: &'a str) -> Self {
+ BibtexLexer {
+ stream: CharStream::new(text),
+ }
+ }
+
+ fn kind(&mut self) -> BibtexToken {
+ fn is_type_char(c: char) -> bool {
+ c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'
+ }
+
+ 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" => BibtexTokenKind::PreambleKind,
+ "@string" => BibtexTokenKind::StringKind,
+ _ => BibtexTokenKind::EntryKind,
+ };
+ BibtexToken::new(span, kind)
+ }
+
+ fn single_character(&mut self, kind: BibtexTokenKind) -> BibtexToken {
+ self.stream.start_span();
+ self.stream.next();
+ let span = self.stream.end_span();
+ BibtexToken::new(span, kind)
+ }
+
+ fn command(&mut self) -> BibtexToken {
+ let span = self.stream.command();
+ BibtexToken::new(span, BibtexTokenKind::Command)
+ }
+
+ fn word(&mut self) -> BibtexToken {
+ fn is_word_char(c: char) -> bool {
+ !c.is_whitespace()
+ && c != '@'
+ && c != '='
+ && c != ','
+ && c != '#'
+ && c != '"'
+ && c != '{'
+ && c != '}'
+ && c != '('
+ && c != ')'
+ }
+
+ self.stream.start_span();
+ while self.stream.satifies(|c| is_word_char(*c)) {
+ self.stream.next();
+ }
+ let span = self.stream.end_span();
+ BibtexToken::new(span, BibtexTokenKind::Word)
+ }
+}
+
+impl<'a> Iterator for BibtexLexer<'a> {
+ type Item = BibtexToken;
+
+ fn next(&mut self) -> Option<BibtexToken> {
+ loop {
+ match self.stream.peek() {
+ Some('@') => return Some(self.kind()),
+ Some('=') => return Some(self.single_character(BibtexTokenKind::Assign)),
+ Some(',') => return Some(self.single_character(BibtexTokenKind::Comma)),
+ Some('#') => return Some(self.single_character(BibtexTokenKind::Concat)),
+ Some('"') => return Some(self.single_character(BibtexTokenKind::Quote)),
+ Some('{') => return Some(self.single_character(BibtexTokenKind::BeginBrace)),
+ Some('}') => return Some(self.single_character(BibtexTokenKind::EndBrace)),
+ Some('(') => return Some(self.single_character(BibtexTokenKind::BeginParen)),
+ Some(')') => return Some(self.single_character(BibtexTokenKind::EndParen)),
+ Some('\\') => return Some(self.command()),
+ Some(c) => {
+ if c.is_whitespace() {
+ self.stream.next();
+ } else {
+ return Some(self.word());
+ }
+ }
+ None => {
+ return None;
+ }
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::syntax::text::Span;
+ use lsp_types::{Position, Range};
+
+ fn verify<'a>(
+ lexer: &mut BibtexLexer<'a>,
+ line: u64,
+ character: u64,
+ text: &str,
+ kind: BibtexTokenKind,
+ ) {
+ 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 = BibtexToken::new(span, kind);
+ assert_eq!(Some(token), lexer.next());
+ }
+
+ #[test]
+ fn test_word() {
+ let mut lexer = BibtexLexer::new("foo bar baz");
+ verify(&mut lexer, 0, 0, "foo", BibtexTokenKind::Word);
+ verify(&mut lexer, 0, 4, "bar", BibtexTokenKind::Word);
+ verify(&mut lexer, 0, 8, "baz", BibtexTokenKind::Word);
+ assert_eq!(None, lexer.next());
+ }
+
+ #[test]
+ fn test_command() {
+ let mut lexer = BibtexLexer::new("\\foo\\bar@baz");
+ verify(&mut lexer, 0, 0, "\\foo", BibtexTokenKind::Command);
+ verify(&mut lexer, 0, 4, "\\bar@baz", BibtexTokenKind::Command);
+ assert_eq!(None, lexer.next());
+ }
+
+ #[test]
+ fn test_escape_sequence() {
+ let mut lexer = BibtexLexer::new("\\foo*\n\\%\\**");
+ verify(&mut lexer, 0, 0, "\\foo*", BibtexTokenKind::Command);
+ verify(&mut lexer, 1, 0, "\\%", BibtexTokenKind::Command);
+ verify(&mut lexer, 1, 2, "\\*", BibtexTokenKind::Command);
+ verify(&mut lexer, 1, 4, "*", BibtexTokenKind::Word);
+ assert_eq!(None, lexer.next());
+ }
+
+ #[test]
+ fn test_delimiter() {
+ let mut lexer = BibtexLexer::new("{}()\"");
+ verify(&mut lexer, 0, 0, "{", BibtexTokenKind::BeginBrace);
+ verify(&mut lexer, 0, 1, "}", BibtexTokenKind::EndBrace);
+ verify(&mut lexer, 0, 2, "(", BibtexTokenKind::BeginParen);
+ verify(&mut lexer, 0, 3, ")", BibtexTokenKind::EndParen);
+ verify(&mut lexer, 0, 4, "\"", BibtexTokenKind::Quote);
+ assert_eq!(None, lexer.next());
+ }
+
+ #[test]
+ fn test_kind() {
+ let mut lexer = BibtexLexer::new("@pReAmBlE\n@article\n@string");
+ verify(&mut lexer, 0, 0, "@pReAmBlE", BibtexTokenKind::PreambleKind);
+ verify(&mut lexer, 1, 0, "@article", BibtexTokenKind::EntryKind);
+ verify(&mut lexer, 2, 0, "@string", BibtexTokenKind::StringKind);
+ assert_eq!(None, lexer.next());
+ }
+
+ #[test]
+ fn test_operator() {
+ let mut lexer = BibtexLexer::new("=,#");
+ verify(&mut lexer, 0, 0, "=", BibtexTokenKind::Assign);
+ verify(&mut lexer, 0, 1, ",", BibtexTokenKind::Comma);
+ verify(&mut lexer, 0, 2, "#", BibtexTokenKind::Concat);
+ assert_eq!(None, lexer.next());
+ }
+}
diff --git a/support/texlab/src/syntax/bibtex/mod.rs b/support/texlab/src/syntax/bibtex/mod.rs
new file mode 100644
index 0000000000..9910390fe7
--- /dev/null
+++ b/support/texlab/src/syntax/bibtex/mod.rs
@@ -0,0 +1,75 @@
+mod ast;
+mod finder;
+mod lexer;
+mod parser;
+
+use self::lexer::BibtexLexer;
+use self::parser::BibtexParser;
+
+pub use self::ast::*;
+pub use self::finder::*;
+use lsp_types::Position;
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct BibtexSyntaxTree {
+ pub root: BibtexRoot,
+}
+
+impl BibtexSyntaxTree {
+ pub fn entries(&self) -> Vec<&BibtexEntry> {
+ let mut entries: Vec<&BibtexEntry> = Vec::new();
+ for declaration in &self.root.children {
+ if let BibtexDeclaration::Entry(entry) = declaration {
+ entries.push(&entry);
+ }
+ }
+ entries
+ }
+
+ pub fn strings(&self) -> Vec<&BibtexString> {
+ let mut strings: Vec<&BibtexString> = Vec::new();
+ for declaration in &self.root.children {
+ if let BibtexDeclaration::String(string) = declaration {
+ strings.push(&string);
+ }
+ }
+ strings
+ }
+
+ pub fn find(&self, position: Position) -> Vec<BibtexNode> {
+ let mut finder = BibtexFinder::new(position);
+ finder.visit_root(&self.root);
+ finder.results
+ }
+
+ pub fn find_entry(&self, key: &str) -> Option<&BibtexEntry> {
+ self.entries()
+ .into_iter()
+ .find(|entry| entry.key.as_ref().map(BibtexToken::text) == Some(key))
+ }
+
+ pub fn resolve_crossref(&self, entry: &BibtexEntry) -> Option<&BibtexEntry> {
+ if let Some(field) = entry.find_field("crossref") {
+ if let Some(BibtexContent::BracedContent(content)) = &field.content {
+ if let Some(BibtexContent::Word(name)) = content.children.get(0) {
+ return self.find_entry(name.token.text());
+ }
+ }
+ }
+ None
+ }
+}
+
+impl From<BibtexRoot> for BibtexSyntaxTree {
+ fn from(root: BibtexRoot) -> Self {
+ BibtexSyntaxTree { root }
+ }
+}
+
+impl From<&str> for BibtexSyntaxTree {
+ fn from(text: &str) -> Self {
+ let lexer = BibtexLexer::new(text);
+ let mut parser = BibtexParser::new(lexer);
+ parser.root().into()
+ }
+}
diff --git a/support/texlab/src/syntax/bibtex/parser.rs b/support/texlab/src/syntax/bibtex/parser.rs
new file mode 100644
index 0000000000..ffc33da545
--- /dev/null
+++ b/support/texlab/src/syntax/bibtex/parser.rs
@@ -0,0 +1,219 @@
+use super::ast::*;
+use std::iter::Peekable;
+
+pub struct BibtexParser<I: Iterator<Item = BibtexToken>> {
+ tokens: Peekable<I>,
+}
+
+impl<I: Iterator<Item = BibtexToken>> BibtexParser<I> {
+ pub fn new(tokens: I) -> Self {
+ BibtexParser {
+ tokens: tokens.peekable(),
+ }
+ }
+
+ pub fn root(&mut self) -> BibtexRoot {
+ let mut children = Vec::new();
+ while let Some(ref token) = self.tokens.peek() {
+ match token.kind {
+ BibtexTokenKind::PreambleKind => {
+ let preamble = Box::new(self.preamble());
+ children.push(BibtexDeclaration::Preamble(preamble));
+ }
+ BibtexTokenKind::StringKind => {
+ let string = Box::new(self.string());
+ children.push(BibtexDeclaration::String(string));
+ }
+ BibtexTokenKind::EntryKind => {
+ let entry = Box::new(self.entry());
+ children.push(BibtexDeclaration::Entry(entry));
+ }
+ _ => {
+ let comment = BibtexComment::new(self.tokens.next().unwrap());
+ children.push(BibtexDeclaration::Comment(Box::new(comment)));
+ }
+ }
+ }
+ BibtexRoot::new(children)
+ }
+
+ fn preamble(&mut self) -> BibtexPreamble {
+ let ty = self.tokens.next().unwrap();
+
+ let left = self.expect2(BibtexTokenKind::BeginBrace, BibtexTokenKind::BeginParen);
+ if left.is_none() {
+ return BibtexPreamble::new(ty, None, None, None);
+ }
+
+ if !self.can_match_content() {
+ return BibtexPreamble::new(ty, left, None, None);
+ }
+ let content = self.content();
+
+ let right = self.expect2(BibtexTokenKind::EndBrace, BibtexTokenKind::EndParen);
+ BibtexPreamble::new(ty, left, Some(content), right)
+ }
+
+ fn string(&mut self) -> BibtexString {
+ let ty = self.tokens.next().unwrap();
+
+ let left = self.expect2(BibtexTokenKind::BeginBrace, BibtexTokenKind::BeginParen);
+ if left.is_none() {
+ return BibtexString::new(ty, None, None, None, None, None);
+ }
+
+ let name = self.expect1(BibtexTokenKind::Word);
+ if name.is_none() {
+ return BibtexString::new(ty, left, None, None, None, None);
+ }
+
+ let assign = self.expect1(BibtexTokenKind::Assign);
+ if assign.is_none() {
+ return BibtexString::new(ty, left, name, None, None, None);
+ }
+
+ if !self.can_match_content() {
+ return BibtexString::new(ty, left, name, assign, None, None);
+ }
+ let value = self.content();
+
+ let right = self.expect2(BibtexTokenKind::EndBrace, BibtexTokenKind::EndParen);
+ BibtexString::new(ty, left, name, assign, Some(value), right)
+ }
+
+ fn entry(&mut self) -> BibtexEntry {
+ let ty = self.tokens.next().unwrap();
+
+ let left = self.expect2(BibtexTokenKind::BeginBrace, BibtexTokenKind::BeginParen);
+ if left.is_none() {
+ return BibtexEntry::new(ty, None, None, None, Vec::new(), None);
+ }
+
+ let name = self.expect1(BibtexTokenKind::Word);
+ if name.is_none() {
+ return BibtexEntry::new(ty, left, None, None, Vec::new(), None);
+ }
+
+ let comma = self.expect1(BibtexTokenKind::Comma);
+ if comma.is_none() {
+ return BibtexEntry::new(ty, left, name, None, Vec::new(), None);
+ }
+
+ let mut fields = Vec::new();
+ while self.next_of_kind(BibtexTokenKind::Word) {
+ fields.push(self.field());
+ }
+
+ let right = self.expect2(BibtexTokenKind::EndBrace, BibtexTokenKind::EndParen);
+ BibtexEntry::new(ty, left, name, comma, fields, right)
+ }
+
+ fn field(&mut self) -> BibtexField {
+ let name = self.tokens.next().unwrap();
+
+ let assign = self.expect1(BibtexTokenKind::Assign);
+ if assign.is_none() {
+ return BibtexField::new(name, None, None, None);
+ }
+
+ if !self.can_match_content() {
+ return BibtexField::new(name, assign, None, None);
+ }
+ let content = self.content();
+
+ let comma = self.expect1(BibtexTokenKind::Comma);
+ BibtexField::new(name, assign, Some(content), comma)
+ }
+
+ fn content(&mut self) -> BibtexContent {
+ let token = self.tokens.next().unwrap();
+ let left = match token.kind {
+ BibtexTokenKind::PreambleKind
+ | BibtexTokenKind::StringKind
+ | BibtexTokenKind::EntryKind
+ | BibtexTokenKind::Word
+ | BibtexTokenKind::Assign
+ | BibtexTokenKind::Comma
+ | BibtexTokenKind::BeginParen
+ | BibtexTokenKind::EndParen => BibtexContent::Word(BibtexWord::new(token)),
+ BibtexTokenKind::Command => BibtexContent::Command(BibtexCommand::new(token)),
+ BibtexTokenKind::Quote => {
+ let mut children = Vec::new();
+ while self.can_match_content() {
+ if self.next_of_kind(BibtexTokenKind::Quote) {
+ break;
+ }
+ children.push(self.content());
+ }
+ let right = self.expect1(BibtexTokenKind::Quote);
+ BibtexContent::QuotedContent(BibtexQuotedContent::new(token, children, right))
+ }
+ BibtexTokenKind::BeginBrace => {
+ let mut children = Vec::new();
+ while self.can_match_content() {
+ children.push(self.content());
+ }
+ let right = self.expect1(BibtexTokenKind::EndBrace);
+ BibtexContent::BracedContent(BibtexBracedContent::new(token, children, right))
+ }
+ _ => unreachable!(),
+ };
+ if let Some(operator) = self.expect1(BibtexTokenKind::Concat) {
+ let right = if self.can_match_content() {
+ Some(self.content())
+ } else {
+ None
+ };
+ BibtexContent::Concat(Box::new(BibtexConcat::new(left, operator, right)))
+ } else {
+ left
+ }
+ }
+
+ fn can_match_content(&mut self) -> bool {
+ if let Some(ref token) = self.tokens.peek() {
+ match token.kind {
+ BibtexTokenKind::PreambleKind
+ | BibtexTokenKind::StringKind
+ | BibtexTokenKind::EntryKind
+ | BibtexTokenKind::Word
+ | BibtexTokenKind::Command
+ | BibtexTokenKind::Assign
+ | BibtexTokenKind::Comma
+ | BibtexTokenKind::Quote
+ | BibtexTokenKind::BeginBrace
+ | BibtexTokenKind::BeginParen
+ | BibtexTokenKind::EndParen => true,
+ BibtexTokenKind::Concat | BibtexTokenKind::EndBrace => false,
+ }
+ } else {
+ false
+ }
+ }
+
+ fn expect1(&mut self, kind: BibtexTokenKind) -> Option<BibtexToken> {
+ if let Some(ref token) = self.tokens.peek() {
+ if token.kind == kind {
+ return self.tokens.next();
+ }
+ }
+ None
+ }
+
+ fn expect2(&mut self, kind1: BibtexTokenKind, kind2: BibtexTokenKind) -> Option<BibtexToken> {
+ if let Some(ref token) = self.tokens.peek() {
+ if token.kind == kind1 || token.kind == kind2 {
+ return self.tokens.next();
+ }
+ }
+ None
+ }
+
+ fn next_of_kind(&mut self, kind: BibtexTokenKind) -> bool {
+ if let Some(token) = self.tokens.peek() {
+ token.kind == kind
+ } else {
+ false
+ }
+ }
+}
diff --git a/support/texlab/src/syntax/language.json b/support/texlab/src/syntax/language.json
new file mode 100644
index 0000000000..8ffcdd03e1
--- /dev/null
+++ b/support/texlab/src/syntax/language.json
@@ -0,0 +1,2213 @@
+{
+ "environmentCommands": [
+ {
+ "name": "\\begin",
+ "index": 0
+ },
+ {
+ "name": "\\end",
+ "index": 0
+ }
+ ],
+ "citationCommands": [
+ {
+ "name": "\\cite",
+ "index": 0
+ },
+ {
+ "name": "\\cite*",
+ "index": 0
+ },
+ {
+ "name": "\\Cite",
+ "index": 0
+ },
+ {
+ "name": "\\nocite",
+ "index": 0
+ },
+ {
+ "name": "\\citet",
+ "index": 0
+ },
+ {
+ "name": "\\citep",
+ "index": 0
+ },
+ {
+ "name": "\\citet*",
+ "index": 0
+ },
+ {
+ "name": "\\citep*",
+ "index": 0
+ },
+ {
+ "name": "\\citeauthor",
+ "index": 0
+ },
+ {
+ "name": "\\citeauthor*",
+ "index": 0
+ },
+ {
+ "name": "\\Citeauthor",
+ "index": 0
+ },
+ {
+ "name": "\\Citeauthor*",
+ "index": 0
+ },
+ {
+ "name": "\\citetitle",
+ "index": 0
+ },
+ {
+ "name": "\\citetitle*",
+ "index": 0
+ },
+ {
+ "name": "\\citeyear",
+ "index": 0
+ },
+ {
+ "name": "\\citeyear*",
+ "index": 0
+ },
+ {
+ "name": "\\citedate",
+ "index": 0
+ },
+ {
+ "name": "\\citedate*",
+ "index": 0
+ },
+ {
+ "name": "\\citeurl",
+ "index": 0
+ },
+ {
+ "name": "\\fullcite",
+ "index": 0
+ },
+ {
+ "name": "\\citeyearpar",
+ "index": 0
+ },
+ {
+ "name": "\\citealt",
+ "index": 0
+ },
+ {
+ "name": "\\citealp",
+ "index": 0
+ },
+ {
+ "name": "\\citetext",
+ "index": 0
+ },
+ {
+ "name": "\\parencite",
+ "index": 0
+ },
+ {
+ "name": "\\parencite*",
+ "index": 0
+ },
+ {
+ "name": "\\Parencite",
+ "index": 0
+ },
+ {
+ "name": "\\footcite",
+ "index": 0
+ },
+ {
+ "name": "\\footfullcite",
+ "index": 0
+ },
+ {
+ "name": "\\footcitetext",
+ "index": 0
+ },
+ {
+ "name": "\\textcite",
+ "index": 0
+ },
+ {
+ "name": "\\Textcite",
+ "index": 0
+ },
+ {
+ "name": "\\smartcite",
+ "index": 0
+ },
+ {
+ "name": "\\Smartcite",
+ "index": 0
+ },
+ {
+ "name": "\\supercite",
+ "index": 0
+ },
+ {
+ "name": "\\autocite",
+ "index": 0
+ },
+ {
+ "name": "\\Autocite",
+ "index": 0
+ },
+ {
+ "name": "\\autocite*",
+ "index": 0
+ },
+ {
+ "name": "\\Autocite*",
+ "index": 0
+ },
+ {
+ "name": "\\volcite",
+ "index": 0
+ },
+ {
+ "name": "\\Volcite",
+ "index": 0
+ },
+ {
+ "name": "\\pvolcite",
+ "index": 0
+ },
+ {
+ "name": "\\Pvolcite",
+ "index": 0
+ },
+ {
+ "name": "\\fvolcite",
+ "index": 0
+ },
+ {
+ "name": "\\ftvolcite",
+ "index": 0
+ },
+ {
+ "name": "\\svolcite",
+ "index": 0
+ },
+ {
+ "name": "\\Svolcite",
+ "index": 0
+ },
+ {
+ "name": "\\tvolcite",
+ "index": 0
+ },
+ {
+ "name": "\\Tvolcite",
+ "index": 0
+ },
+ {
+ "name": "\\avolcite",
+ "index": 0
+ },
+ {
+ "name": "\\Avolcite",
+ "index": 0
+ },
+ {
+ "name": "\\notecite",
+ "index": 0
+ },
+ {
+ "name": "\\notecite",
+ "index": 0
+ },
+ {
+ "name": "\\pnotecite",
+ "index": 0
+ },
+ {
+ "name": "\\Pnotecite",
+ "index": 0
+ },
+ {
+ "name": "\\fnotecite",
+ "index": 0
+ }
+ ],
+ "labelCommands": [
+ {
+ "name": "\\label",
+ "index": 0,
+ "kind": "definition"
+ },
+ {
+ "name": "\\ref",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\autoref",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\pageref",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\eqref",
+ "index": 0,
+ "kind": {
+ "reference": "math"
+ }
+ },
+ {
+ "name": "\\cref",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\Cref",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\crefrange",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\crefrange",
+ "index": 1,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\Crefrange",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\Crefrange",
+ "index": 1,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\cref*",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\Cref*",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\crefrange*",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\crefrange*",
+ "index": 1,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\Crefrange*",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\Crefrange*",
+ "index": 1,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\namecref",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\nameCref",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\lcnamecref",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\namecrefs",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\nameCrefs",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\lcnamecrefs",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\labelcref",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ },
+ {
+ "name": "\\labelcpageref",
+ "index": 0,
+ "kind": {
+ "reference": "everything"
+ }
+ }
+ ],
+ "sectionCommands": [
+ {
+ "name": "\\part",
+ "index": 0,
+ "level": 0,
+ "prefix": "Part"
+ },
+ {
+ "name": "\\part*",
+ "index": 0,
+ "level": 0,
+ "prefix": "Part"
+ },
+ {
+ "name": "\\chapter",
+ "index": 0,
+ "level": 1,
+ "prefix": "Chapter"
+ },
+ {
+ "name": "\\chapter*",
+ "index": 0,
+ "level": 1,
+ "prefix": "Chapter"
+ },
+ {
+ "name": "\\section",
+ "index": 0,
+ "level": 2,
+ "prefix": "Section"
+ },
+ {
+ "name": "\\section*",
+ "index": 0,
+ "level": 2,
+ "prefix": "Section"
+ },
+ {
+ "name": "\\subsection",
+ "index": 0,
+ "level": 3,
+ "prefix": "Subsection"
+ },
+ {
+ "name": "\\subsection*",
+ "index": 0,
+ "level": 3,
+ "prefix": "Subsection"
+ },
+ {
+ "name": "\\subsubsection",
+ "index": 0,
+ "level": 4,
+ "prefix": "Subsection"
+ },
+ {
+ "name": "\\subsubsection*",
+ "index": 0,
+ "level": 4,
+ "prefix": "Subsection"
+ },
+ {
+ "name": "\\paragraph",
+ "index": 0,
+ "level": 5,
+ "prefix": "Paragraph"
+ },
+ {
+ "name": "\\paragraph*",
+ "index": 0,
+ "level": 5,
+ "prefix": "Paragraph"
+ },
+ {
+ "name": "\\subparagraph",
+ "index": 0,
+ "level": 6,
+ "prefix": "Subparagraph"
+ },
+ {
+ "name": "\\subparagraph*",
+ "index": 0,
+ "level": 6,
+ "prefix": "Subparagraph"
+ }
+ ],
+ "includeCommands": [
+ {
+ "name": "\\documentclass",
+ "index": 0,
+ "kind": "class",
+ "includeExtension": false
+ },
+ {
+ "name": "\\usepackage",
+ "index": 0,
+ "kind": "package",
+ "includeExtension": false
+ },
+ {
+ "name": "\\RequirePackage",
+ "index": 0,
+ "kind": "package",
+ "includeExtension": false
+ },
+ {
+ "name": "\\include",
+ "index": 0,
+ "kind": "latex",
+ "includeExtension": false
+ },
+ {
+ "name": "\\input",
+ "index": 0,
+ "kind": "latex",
+ "includeExtension": true
+ },
+ {
+ "name": "\\bibliography",
+ "index": 0,
+ "kind": "bibliography",
+ "includeExtension": true
+ },
+ {
+ "name": "\\addbibresource",
+ "index": 0,
+ "kind": "bibliography",
+ "includeExtension": true
+ },
+ {
+ "name": "\\includegraphics",
+ "index": 0,
+ "kind": "image",
+ "includeExtension": true
+ },
+ {
+ "name": "\\includesvg",
+ "index": 0,
+ "kind": "svg",
+ "includeExtension": false
+ },
+ {
+ "name": "\\includepdf",
+ "index": 0,
+ "kind": "pdf",
+ "includeExtension": true
+ },
+ {
+ "name": "\\verbatiminput",
+ "index": 0,
+ "kind": "everything",
+ "includeExtension": true
+ },
+ {
+ "name": "\\VerbatimInput",
+ "index": 0,
+ "kind": "everything",
+ "includeExtension": true
+ },
+ {
+ "name": "\\subfile",
+ "index": 0,
+ "kind": "latex",
+ "includeExtension": false
+ }
+ ],
+ "commandDefinitionCommands": [
+ {
+ "name": "\\newcommand",
+ "definitionIndex": 0,
+ "argumentCountIndex": 0,
+ "implementationIndex": 1
+ },
+ {
+ "name": "\\renewcommand",
+ "definitionIndex": 0,
+ "argumentCountIndex": 0,
+ "implementationIndex": 1
+ },
+ {
+ "name": "\\DeclareRobustCommand",
+ "definitionIndex": 0,
+ "argumentCountIndex": 0,
+ "implementationIndex": 1
+ }
+ ],
+ "mathOperatorCommands": [
+ {
+ "name": "\\DeclareMathOperator",
+ "definitionIndex": 0,
+ "implementationIndex": 1
+ },
+ {
+ "name": "\\DeclareMathOperator*",
+ "definitionIndex": 0,
+ "implementationIndex": 1
+ }
+ ],
+ "theoremDefinitionCommands": [
+ {
+ "name": "\\newtheorem",
+ "index": 0
+ },
+ {
+ "name": "\\declaretheorem",
+ "index": 0
+ }
+ ],
+ "colors": [
+ "black",
+ "blue",
+ "brown",
+ "cyan",
+ "darkgray",
+ "gray",
+ "green",
+ "lightgray",
+ "lime",
+ "magenta",
+ "olive",
+ "orange",
+ "pink",
+ "purple",
+ "red",
+ "teal",
+ "violet",
+ "white",
+ "yellow",
+ "Apricot",
+ "Bittersweet",
+ "Blue",
+ "BlueViolet",
+ "Brown",
+ "CadetBlue",
+ "Cerulean",
+ "Cyan",
+ "DarkOrchid",
+ "ForestGreen",
+ "Goldenrod",
+ "Green",
+ "JungleGreen",
+ "LimeGreen",
+ "Mahogany",
+ "Melon",
+ "Mulberry",
+ "OliveGreen",
+ "OrangeRed",
+ "Peach",
+ "PineGreen",
+ "ProcessBlue",
+ "RawSienna",
+ "RedOrange",
+ "Rhodamine",
+ "RoyalPurple",
+ "Salmon",
+ "Sepia",
+ "SpringGreen",
+ "TealBlue",
+ "Turquoise",
+ "VioletRed",
+ "WildStrawberry",
+ "YellowGreen",
+ "Aquamarine",
+ "Black",
+ "BlueGreen",
+ "BrickRed",
+ "BurntOrange",
+ "CarnationPink",
+ "CornflowerBlue",
+ "Dandelion",
+ "Emerald",
+ "Fuchsia",
+ "Gray",
+ "GreenYellow",
+ "Lavender",
+ "Magenta",
+ "Maroon",
+ "MidnightBlue",
+ "NavyBlue",
+ "Orange",
+ "Orchid",
+ "Periwinkle",
+ "Plum",
+ "Purple",
+ "Red",
+ "RedViolet",
+ "RoyalBlue",
+ "RubineRed",
+ "SeaGreen",
+ "SkyBlue",
+ "Tan",
+ "Thistle",
+ "Violet",
+ "White",
+ "Yellow",
+ "YellowOrange"
+ ],
+ "colorCommands": [
+ {
+ "name": "\\color",
+ "index": 0
+ },
+ {
+ "name": "\\colorbox",
+ "index": 0
+ },
+ {
+ "name": "\\textcolor",
+ "index": 0
+ },
+ {
+ "name": "\\pagecolor",
+ "index": 0
+ }
+ ],
+ "colorModelCommands": [
+ {
+ "name": "\\definecolor",
+ "index": 1
+ },
+ {
+ "name": "\\definecolorset",
+ "index": 0
+ }
+ ],
+ "glossaryEntryDefinitionCommands": [
+ {
+ "name": "\\newglossaryentry",
+ "labelIndex": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\newacronym",
+ "labelIndex": 0,
+ "kind": "acronym"
+ }
+ ],
+ "glossaryEntryReferenceCommands": [
+ {
+ "name": "\\gls",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\Gls",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\GLS",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\glspl",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\Glspl",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\GLSpl",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\glsdisp",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\glslink",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\glstext",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\Glstext",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\GLStext",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\glsfirst",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\Glsfirst",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\GLSfirst",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\glsplural",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\Glsplural",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\GLSplural",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\glsfirstplural",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\Glsfirstplural",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\GLSfirstplural",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\glsname",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\Glsname",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\GLSname",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\glssymbol",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\Glssymbol",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\glsdesc",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\Glsdesc",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\GLSdesc",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\glsuseri",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\Glsuseri",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\GLSuseri",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\glsuserii",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\Glsuserii",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\GLSuserii",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\glsuseriii",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\Glsuseriii",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\GLSuseriii",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\glsuseriv",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\Glsuseriv",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\GLSuseriv",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\glsuserv",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\Glsuserv",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\GLSuserv",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\glsuservi",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\Glsuservi",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\GLSuservi",
+ "index": 0,
+ "kind": "general"
+ },
+ {
+ "name": "\\acrshort",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\Acrshort",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\ACRshort",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\acrshortpl",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\Acrshortpl",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\ACRshortpl",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\acrlong",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\Acrlong",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\ACRlong",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\acrlongpl",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\Acrlongpl",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\ACRlongpl",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\acrfull",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\Acrfull",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\ACRfull",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\acrfullpl",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\Acrfullpl",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\ACRfullpl",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\acs",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\Acs",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\acsp",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\Acsp",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\acl",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\Acl",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\aclp",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\Aclp",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\acf",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\Acf",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\acfp",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\Acfp",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\ac",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\Ac",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\acp",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\glsentrylong",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\Glsentrylong",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\glsentrylongpl",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\Glsentrylongpl",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\glsentryshort",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\Glsentryshort",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\glsentryshortpl",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\Glsentryshortpl",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\glsentryfullpl",
+ "index": 0,
+ "kind": "acronym"
+ },
+ {
+ "name": "\\Glsentryfullpl",
+ "index": 0,
+ "kind": "acronym"
+ }
+ ],
+ "entryTypes": [
+ {
+ "name": "preamble",
+ "category": "misc",
+ "documentation": null
+ },
+ {
+ "name": "string",
+ "category": "string",
+ "documentation": null
+ },
+ {
+ "name": "comment",
+ "category": "misc",
+ "documentation": null
+ },
+ {
+ "name": "article",
+ "category": "article",
+ "documentation": "An article in a journal, magazine, newspaper, or other periodical which forms a \n self-contained unit with its own title. The title of the periodical is given in the \n journaltitle field. If the issue has its own title in addition to the main title of \n the periodical, it goes in the issuetitle field. Note that editor and related \n fields refer to the journal while translator and related fields refer to the article.\n\nRequired fields: `author`, `title`, `journaltitle`, `year/date`"
+ },
+ {
+ "name": "book",
+ "category": "book",
+ "documentation": "A single-volume book with one or more authors where the authors share credit for\n the work as a whole. This entry type also covers the function of the `@inbook` type\n of traditional BibTeX.\n\nRequired fields: `author`, `title`, `year/date`"
+ },
+ {
+ "name": "mvbook",
+ "category": "book",
+ "documentation": "A multi-volume `@book`. For backwards compatibility, multi-volume books are also\n supported by the entry type `@book`. However, it is advisable to make use of the\n dedicated entry type `@mvbook`.\n\nRequired fields: `author`, `title`, `year/date`"
+ },
+ {
+ "name": "inbook",
+ "category": "part",
+ "documentation": "A part of a book which forms a self-contained unit with its own title. Note that the\n profile of this entry type is different from standard BibTeX.\n\nRequired fields: `author`, `title`, `booktitle`, `year/date`"
+ },
+ {
+ "name": "bookinbook",
+ "category": "part",
+ "documentation": "This type is similar to `@inbook` but intended for works originally published as a\n stand-alone book. A typical example are books reprinted in the collected works of\n an author."
+ },
+ {
+ "name": "suppbook",
+ "category": "book",
+ "documentation": "Supplemental material in a `@book`. This type is closely related to the `@inbook`\n entry type. While `@inbook` is primarily intended for a part of a book with its own\n title (e. g., a single essay in a collection of essays by the same author), this type is\n provided for elements such as prefaces, introductions, forewords, afterwords, etc.\n which often have a generic title only. Style guides may require such items to be\n formatted differently from other `@inbook` items. The standard styles will treat this\n entry type as an alias for `@inbook`."
+ },
+ {
+ "name": "booklet",
+ "category": "book",
+ "documentation": "A book-like work without a formal publisher or sponsoring institution. Use the field\n howpublished to supply publishing information in free format, if applicable. The\n field type may be useful as well.\n\nRequired fields: `author/editor`, `title`, `year/date`"
+ },
+ {
+ "name": "collection",
+ "category": "collection",
+ "documentation": "A single-volume collection with multiple, self-contained contributions by distinct\n authors which have their own title. The work as a whole has no overall author but it\n will usually have an editor.\n\nRequired fields: `editor`, `title`, `year/date`"
+ },
+ {
+ "name": "mvcollection",
+ "category": "collection",
+ "documentation": "A multi-volume `@collection`. For backwards compatibility, multi-volume collections\n are also supported by the entry type `@collection`. However, it is advisable\n to make use of the dedicated entry type `@mvcollection`.\n\nRequired fields: `editor`, `title`, `year/date`"
+ },
+ {
+ "name": "incollection",
+ "category": "part",
+ "documentation": "A contribution to a collection which forms a self-contained unit with a distinct author\n and title. The `author` refers to the `title`, the `editor` to the `booktitle`, i. e.,\n the title of the collection.\n\nRequired fields: `author`, `title`, `booktitle`, `year/date`"
+ },
+ {
+ "name": "suppcollection",
+ "category": "collection",
+ "documentation": "Supplemental material in a `@collection`. This type is similar to `@suppbook` but\n related to the `@collection` entry type. The standard styles will treat this entry\n type as an alias for `@incollection`."
+ },
+ {
+ "name": "manual",
+ "category": "misc",
+ "documentation": "Technical or other documentation, not necessarily in printed form. The author or\n editor is omissible.\n\nRequired fields: `author/editor`, `title`, `year/date`"
+ },
+ {
+ "name": "misc",
+ "category": "misc",
+ "documentation": "A fallback type for entries which do not fit into any other category. Use the field\n howpublished to supply publishing information in free format, if applicable. The\n field type may be useful as well. author, editor, and year are omissible.\n\nRequired fields: `author/editor`, `title`, `year/date`"
+ },
+ {
+ "name": "online",
+ "category": "misc",
+ "documentation": "An online resource. `author`, `editor`, and `year` are omissible.\n This entry type is intended for sources such as web sites which are intrinsically\n online resources. Note that all entry types support the url field. For example, when\n adding an article from an online journal, it may be preferable to use the `@article`\n type and its url field.\n\nRequired fields: `author/editor`, `title`, `year/date`, `url`"
+ },
+ {
+ "name": "patent",
+ "category": "misc",
+ "documentation": "A patent or patent request. The number or record token is given in the number\n field. Use the type field to specify the type and the location field to indicate the\n scope of the patent, if different from the scope implied by the type. Note that the\n location field is treated as a key list with this entry type.\n\nRequired fields: `author`, `title`, `number`, `year/date`"
+ },
+ {
+ "name": "periodical",
+ "category": "misc",
+ "documentation": "An complete issue of a periodical, such as a special issue of a journal. The title of\n the periodical is given in the title field. If the issue has its own title in addition to\n the main title of the periodical, it goes in the issuetitle field. The editor is\n omissible.\n\nRequired fields: `editor`, `title`, `year/date`"
+ },
+ {
+ "name": "suppperiodical",
+ "category": "misc",
+ "documentation": "Supplemental material in a `@periodical`. This type is similar to `@suppbook`\n but related to the `@periodical` entry type. The role of this entry type may be\n more obvious if you bear in mind that the `@article` type could also be called\n `@inperiodical`. This type may be useful when referring to items such as regular\n columns, obituaries, letters to the editor, etc. which only have a generic title. Style\n guides may require such items to be formatted differently from articles in the strict\n sense of the word. The standard styles will treat this entry type as an alias for\n `@article`."
+ },
+ {
+ "name": "proceedings",
+ "category": "book",
+ "documentation": "A single-volume conference proceedings. This type is very similar to `@collection`.\n It supports an optional organization field which holds the sponsoring institution.\n The editor is omissible.\n\nRequired fields: `title`, `year/date`"
+ },
+ {
+ "name": "mvproceedings",
+ "category": "book",
+ "documentation": "A multi-volume `@proceedings` entry. For backwards compatibility, multi-volume\n proceedings are also supported by the entry type `@proceedings`. However, it is\n advisable to make use of the dedicated entry type `@mvproceedings`\n\nRequired fields: `title`, `year/date`"
+ },
+ {
+ "name": "inproceedings",
+ "category": "part",
+ "documentation": "An article in a conference proceedings. This type is similar to `@incollection`. It\n supports an optional `organization` field.\n\nRequired fields: `author`, `title`, `booktitle`, `year/date`"
+ },
+ {
+ "name": "reference",
+ "category": "collection",
+ "documentation": "A single-volume work of reference such as an encyclopedia or a dictionary. This is a\n more specific variant of the generic `@collection` entry type. The standard styles\n will treat this entry type as an alias for `@collection`."
+ },
+ {
+ "name": "mvreference",
+ "category": "collection",
+ "documentation": "A multi-volume `@reference` entry. The standard styles will treat this entry type\n as an alias for `@mvcollection`. For backwards compatibility, multi-volume references\n are also supported by the entry type `@reference`. However, it is advisable\n to make use of the dedicated entry type `@mvreference`."
+ },
+ {
+ "name": "inreference",
+ "category": "part",
+ "documentation": "An article in a work of reference. This is a more specific variant of the generic\n `@incollection` entry type. The standard styles will treat this entry type as an\n alias for `@incollection`."
+ },
+ {
+ "name": "report",
+ "category": "misc",
+ "documentation": "A technical report, research report, or white paper published by a university or some\n other institution. Use the `type` field to specify the type of report. The sponsoring\n institution goes in the `institution` field.\n\nRequired fields: `author`, `title`, `type`, `institution`, `year/date`"
+ },
+ {
+ "name": "set",
+ "category": "misc",
+ "documentation": "An entry set. This entry type is special."
+ },
+ {
+ "name": "thesis",
+ "category": "thesis",
+ "documentation": "A thesis written for an educational institution to satisfy the requirements for a degree.\n Use the `type` field to specify the type of thesis.\n\nRequired fields: `author`, `title`, `type`, `institution`, `year/date`"
+ },
+ {
+ "name": "unpublished",
+ "category": "misc",
+ "documentation": "A work with an author and a title which has not been formally published, such as\n a manuscript or the script of a talk. Use the fields `howpublished` and `note` to\n supply additional information in free format, if applicable.\n\nRequired fields: `author`, `title`, `year/date`"
+ },
+ {
+ "name": "xdata",
+ "category": "misc",
+ "documentation": "This entry type is special. `@xdata` entries hold data which may be inherited by other\n entries using the `xdata` field. Entries of this type only serve as data containers;\n they may not be cited or added to the bibliography."
+ },
+ {
+ "name": "conference",
+ "category": "part",
+ "documentation": "A legacy alias for `@inproceedings`."
+ },
+ {
+ "name": "electronic",
+ "category": "misc",
+ "documentation": "An alias for `@online`."
+ },
+ {
+ "name": "mastersthesis",
+ "category": "thesis",
+ "documentation": "Similar to `@thesis` except that the `type` field is optional and defaults to the\n localised term ‘Master’s thesis’. You may still use the `type` field to override that."
+ },
+ {
+ "name": "phdthesis",
+ "category": "thesis",
+ "documentation": "Similar to `@thesis` except that the `type` field is optional and defaults to the\n localised term ‘PhD thesis’. You may still use the `type` field to override that."
+ },
+ {
+ "name": "techreport",
+ "category": "misc",
+ "documentation": "Similar to `@report` except that the `type` field is optional and defaults to the\n localised term ‘technical report’. You may still use the `type` field to override that."
+ },
+ {
+ "name": "www",
+ "category": "misc",
+ "documentation": "An alias for `@online`, provided for `jurabib` compatibility."
+ },
+ {
+ "name": "artwork",
+ "category": "misc",
+ "documentation": "Works of the visual arts such as paintings, sculpture, and installations."
+ },
+ {
+ "name": "audio",
+ "category": "misc",
+ "documentation": "Audio recordings, typically on audio cd, dvd, audio cassette, or similar media. See\n also `@music`."
+ },
+ {
+ "name": "bibnote",
+ "category": "misc",
+ "documentation": "This special entry type is not meant to be used in the `bib` file like other types. It is\n provided for third-party packages like `notes2bib` which merge notes into the bibliography.\n The notes should go into the `note` field. Be advised that the `@bibnote`\n type is not related to the `defbibnote` command in any way. `defbibnote`\n is for adding comments at the beginning or the end of the bibliography, whereas\n the `@bibnote` type is meant for packages which render endnotes as bibliography\n entries."
+ },
+ {
+ "name": "commentary",
+ "category": "misc",
+ "documentation": "Commentaries which have a status different from regular books, such as legal commentaries."
+ },
+ {
+ "name": "image",
+ "category": "misc",
+ "documentation": "Images, pictures, photographs, and similar media."
+ },
+ {
+ "name": "jurisdiction",
+ "category": "misc",
+ "documentation": "Court decisions, court recordings, and similar things."
+ },
+ {
+ "name": "legislation",
+ "category": "misc",
+ "documentation": "Laws, bills, legislative proposals, and similar things."
+ },
+ {
+ "name": "legal",
+ "category": "misc",
+ "documentation": "Legal documents such as treaties."
+ },
+ {
+ "name": "letter",
+ "category": "misc",
+ "documentation": "Personal correspondence such as letters, emails, memoranda, etc."
+ },
+ {
+ "name": "movie",
+ "category": "misc",
+ "documentation": "Motion pictures. See also `@video`."
+ },
+ {
+ "name": "music",
+ "category": "misc",
+ "documentation": "Musical recordings. This is a more specific variant of `@audio`."
+ },
+ {
+ "name": "performance",
+ "category": "misc",
+ "documentation": "Musical and theatrical performances as well as other works of the performing arts.\n This type refers to the event as opposed to a recording, a score, or a printed play."
+ },
+ {
+ "name": "review",
+ "category": "misc",
+ "documentation": "Reviews of some other work. This is a more specific variant of the `@article` type.\n The standard styles will treat this entry type as an alias for `@article`."
+ },
+ {
+ "name": "software",
+ "category": "misc",
+ "documentation": "Computer software."
+ },
+ {
+ "name": "standard",
+ "category": "misc",
+ "documentation": "National and international standards issued by a standards body such as the International\n Organization for Standardization."
+ },
+ {
+ "name": "video",
+ "category": "misc",
+ "documentation": "Audiovisual recordings, typically on dvd, vhs cassette, or similar media. See also\n `@movie`."
+ }
+ ],
+ "fields": [
+ {
+ "name": "abstract",
+ "documentation": "This field is intended for recording abstracts in a bib file, to be printed by a special bibliography style. It is not used by all standard bibliography styles."
+ },
+ {
+ "name": "addendum",
+ "documentation": "Miscellaneous bibliographic data to be printed at the end of the entry. This is similar to the `note` field except that it is printed at the end of the bibliography entry."
+ },
+ {
+ "name": "afterword",
+ "documentation": "The author(s) of an afterword to the work. If the author of the afterword is identical to the `editor` and/or `translator`, the standard styles will automatically concatenate these fields in the bibliography. See also `introduction` and `foreword`."
+ },
+ {
+ "name": "annotation",
+ "documentation": "This field may be useful when implementing a style for annotated bibliographies. It is not used by all standard bibliography styles. Note that this field is completely unrelated to `annotator`. The `annotator` is the author of annotations which are part of the work cited."
+ },
+ {
+ "name": "annotator",
+ "documentation": "The author(s) of annotations to the work. If the annotator is identical to the `editor` and/or `translator`, the standard styles will automatically concatenate these fields in the bibliography. See also `commentator`."
+ },
+ {
+ "name": "author",
+ "documentation": "The author(s) of the `title`."
+ },
+ {
+ "name": "authortype",
+ "documentation": "The type of author. This field will affect the string (if any) used to introduce the author. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "bookauthor",
+ "documentation": "The author(s) of the `booktitle`."
+ },
+ {
+ "name": "bookpagination",
+ "documentation": "If the work is published as part of another one, this is the pagination scheme of the enclosing work, i. e., `bookpagination` relates to `pagination` like `booktitle` to `title`. The value of this field will affect the formatting of the `pages` and `pagetotal` fields. The key should be given in the singular form. Possible keys are `page`, `column`, `line`, `verse`, `section`, and `paragraph`. See also `pagination`."
+ },
+ {
+ "name": "booksubtitle",
+ "documentation": "The subtitle related to the `booktitle`. If the subtitle field refers to a work which is part of a larger publication, a possible subtitle of the main work is given in this field. See also `subtitle`."
+ },
+ {
+ "name": "booktitle",
+ "documentation": "If the `title` field indicates the title of a work which is part of a larger publication, the title of the main work is given in this field. See also `title`."
+ },
+ {
+ "name": "booktitleaddon",
+ "documentation": "An annex to the `booktitle`, to be printed in a different font."
+ },
+ {
+ "name": "chapter",
+ "documentation": "A chapter or section or any other unit of a work."
+ },
+ {
+ "name": "commentator",
+ "documentation": "The author(s) of a commentary to the work. Note that this field is intended for commented editions which have a commentator in addition to the author. If the work is a stand-alone commentary, the commentator should be given in the `author` field. If the commentator is identical to the `editor` and/or `translator`, the standard styles will automatically concatenate these fields in the bibliography. See also `annotator`."
+ },
+ {
+ "name": "date",
+ "documentation": "The publication date. See also `month` and `year`."
+ },
+ {
+ "name": "doi",
+ "documentation": "The Digital Object Identifier of the work."
+ },
+ {
+ "name": "edition",
+ "documentation": "The edition of a printed publication. This must be an integer, not an ordinal. Don’t say `edition={First}` or `edition={1st}` but `edition={1}`. The bibliography style converts this to a language dependent ordinal. It is also possible to give the edition as a literal string, for example \"Third, revised and expanded edition\"."
+ },
+ {
+ "name": "editor",
+ "documentation": "The editor(s) of the `title`, `booktitle`, or `maintitle`, depending on the entry type. Use the `editortype` field to specify the role if it is different from `editor`."
+ },
+ {
+ "name": "editora",
+ "documentation": "A secondary editor performing a different editorial role, such as compiling, redacting, etc. Use the `editoratype` field to specify the role."
+ },
+ {
+ "name": "editorb",
+ "documentation": "Another secondary editor performing a different role. Use the `editorbtype` field to specify the role."
+ },
+ {
+ "name": "editorc",
+ "documentation": "Another secondary editor performing a different role. Use the `editorctype` field to specify the role."
+ },
+ {
+ "name": "editortype",
+ "documentation": "The type of editorial role performed by the `editor`. Roles supported by default are `editor`, `compiler`, `founder`, `continuator`, `redactor`, `reviser`, `collaborator`, `organizer`. The role `editor` is the default. In this case, the field is omissible."
+ },
+ {
+ "name": "editoratype",
+ "documentation": "Similar to `editortype` but referring to the `editora` field."
+ },
+ {
+ "name": "editorbtype",
+ "documentation": "Similar to `editortype` but referring to the `editorb` field."
+ },
+ {
+ "name": "editorctype",
+ "documentation": "Similar to `editortype` but referring to the `editorc` field."
+ },
+ {
+ "name": "eid",
+ "documentation": "The electronic identifier of an `@article`."
+ },
+ {
+ "name": "entrysubtype",
+ "documentation": "This field, which is not used by the standard styles, may be used to specify a subtype of an entry type. This may be useful for bibliography styles which support a finergrained set of entry types."
+ },
+ {
+ "name": "eprint",
+ "documentation": "The electronic identifier of an online publication. This is roughly comparable to a doi but specific to a certain archive, repository, service, or system. See also `eprinttype` and `eprintclass`."
+ },
+ {
+ "name": "eprintclass",
+ "documentation": "Additional information related to the resource indicated by the `eprinttype` field. This could be a section of an archive, a path indicating a service, a classification of some sort, etc. See also`eprint` and `eprinttype`."
+ },
+ {
+ "name": "eprinttype",
+ "documentation": "The type of `eprint` identifier, e. g., the name of the archive, repository, service, or system the `eprint` field refers to. See also `eprint` and `eprintclass`."
+ },
+ {
+ "name": "eventdate",
+ "documentation": "The date of a conference, a symposium, or some other event in `@proceedings` and `@inproceedings` entries. See also `eventtitle` and `venue`."
+ },
+ {
+ "name": "eventtitle",
+ "documentation": "The title of a conference, a symposium, or some other event in `@proceedings` and `@inproceedings` entries. Note that this field holds the plain title of the event. Things like \"Proceedings of the Fifth XYZ Conference\" go into the `titleaddon` or `booktitleaddon` field, respectively. See also `eventdate` and `venue`."
+ },
+ {
+ "name": "eventtitleaddon",
+ "documentation": "An annex to the `eventtitle` field. Can be used for known event acronyms, for example."
+ },
+ {
+ "name": "file",
+ "documentation": "A local link to a PDF or other version of the work. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "foreword",
+ "documentation": "The author(s) of a foreword to the work. If the author of the foreword is identical to the `editor` and/or `translator`, the standard styles will automatically concatenate these fields in the bibliography. See also `introduction` and `afterword`."
+ },
+ {
+ "name": "holder",
+ "documentation": "The holder(s) of a `@patent`, if different from the `author`. Note that corporate holders need to be wrapped in an additional set of braces."
+ },
+ {
+ "name": "howpublished",
+ "documentation": "A publication notice for unusual publications which do not fit into any of the common categories."
+ },
+ {
+ "name": "indextitle",
+ "documentation": "A title to use for indexing instead of the regular `title` field. This field may be useful if you have an entry with a title like \"An Introduction to …\" and want that indexed as \"Introduction to …, An\". Style authors should note that `biblatex` automatically copies the value of the `title` field to `indextitle` if the latter field is undefined."
+ },
+ {
+ "name": "institution",
+ "documentation": "The name of a university or some other institution, depending on the entry type. Traditional BibTeX uses the field name `school` for theses, which is supported as an alias."
+ },
+ {
+ "name": "introduction",
+ "documentation": "The author(s) of an introduction to the work. If the author of the introduction is identical to the `editor` and/or `translator`, the standard styles will automatically concatenate these fields in the bibliography. See also `foreword` and `afterword`."
+ },
+ {
+ "name": "isan",
+ "documentation": "The International Standard Audiovisual Number of an audiovisual work. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "isbn",
+ "documentation": "The International Standard Book Number of a book."
+ },
+ {
+ "name": "ismn",
+ "documentation": "The International Standard Music Number for printed music such as musical scores. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "isrn",
+ "documentation": "The International Standard Technical Report Number of a technical report."
+ },
+ {
+ "name": "issn",
+ "documentation": "The International Standard Serial Number of a periodical."
+ },
+ {
+ "name": "issue",
+ "documentation": "The issue of a journal. This field is intended for journals whose individual issues are identified by a designation such as ‘Spring’ or ‘Summer’ rather than the month or a number. The placement of `issue` is similar to `month` and `number`, integer ranges and short designators are better written to the number field. See also `month` and `number`."
+ },
+ {
+ "name": "issuesubtitle",
+ "documentation": "The subtitle of a specific issue of a journal or other periodical."
+ },
+ {
+ "name": "issuetitle",
+ "documentation": "The title of a specific issue of a journal or other periodical."
+ },
+ {
+ "name": "iswc",
+ "documentation": "The International Standard Work Code of a musical work. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "journalsubtitle",
+ "documentation": "The subtitle of a journal, a newspaper, or some other periodical."
+ },
+ {
+ "name": "journaltitle",
+ "documentation": "The name of a journal, a newspaper, or some other periodical."
+ },
+ {
+ "name": "label",
+ "documentation": "A designation to be used by the citation style as a substitute for the regular label if any data required to generate the regular label is missing. For example, when an author-year citation style is generating a citation for an entry which is missing the author or the year, it may fall back to `label`. Note that, in contrast to `shorthand`, `label` is only used as a fallback. See also `shorthand`."
+ },
+ {
+ "name": "language",
+ "documentation": "The language(s) of the work. Languages may be specified literally or as localisation keys. If localisation keys are used, the prefix lang is omissible. See also `origlanguage`."
+ },
+ {
+ "name": "library",
+ "documentation": "This field may be useful to record information such as a library name and a call number. This may be printed by a special bibliography style if desired. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "location",
+ "documentation": "The place(s) of publication, i. e., the location of the `publisher` or `institution`, depending on the entry type. Traditional BibTeX uses the field name `address`, which is supported as an alias. With `@patent` entries, this list indicates the scope of a patent."
+ },
+ {
+ "name": "mainsubtitle",
+ "documentation": "The subtitle related to the `maintitle`. See also `subtitle`."
+ },
+ {
+ "name": "maintitle",
+ "documentation": "The main title of a multi-volume book, such as *Collected Works*. If the `title` or `booktitle` field indicates the title of a single volume which is part of multi-volume book, the title of the complete work is given in this field."
+ },
+ {
+ "name": "maintitleaddon",
+ "documentation": "An annex to the `maintitle`, to be printed in a different font."
+ },
+ {
+ "name": "month",
+ "documentation": "The publication month. This must be an integer, not an ordinal or a string. Don’t say `month={January}` but `month={1}`. The bibliography style converts this to a language dependent string or ordinal where required. This field is a literal field only when given explicitly in the data (for plain BibTeX compatibility for example). It is however better to use the `date` field as this supports many more features."
+ },
+ {
+ "name": "nameaddon",
+ "documentation": "An addon to be printed immediately after the author name in the bibliography. Not used by the standard bibliography styles. This field may be useful to add an alias or pen name (or give the real name if the pseudonym is commonly used to refer to that author)."
+ },
+ {
+ "name": "note",
+ "documentation": "Miscellaneous bibliographic data which does not fit into any other field. The note field may be used to record bibliographic data in a free format. Publication facts such as \"Reprint of the edition London 1831\" are typical candidates for the note field. See also `addendum`."
+ },
+ {
+ "name": "number",
+ "documentation": "The number of a journal or the volume/number of a book in a `series`. See also `issue`. With `@patent` entries, this is the number or record token of a patent or patent request. Normally this field will be an integer or an integer range, but in certain cases it may also contain \"S1\", \"Suppl. 1\", in these cases the output should be scrutinised carefully."
+ },
+ {
+ "name": "organization",
+ "documentation": "The organization(s) that published a `@manual` or an `@online` resource, or sponsored a conference."
+ },
+ {
+ "name": "origdate",
+ "documentation": "If the work is a translation, a reprint, or something similar, the publication date of the original edition. Not used by the standard bibliography styles. See also `date`."
+ },
+ {
+ "name": "origlanguage",
+ "documentation": "If the work is a translation, the language(s) of the original work. See also `language`."
+ },
+ {
+ "name": "origlocation",
+ "documentation": "If the work is a translation, a reprint, or something similar, the location of the original edition. Not used by the standard bibliography styles. See also `location`."
+ },
+ {
+ "name": "origpublisher",
+ "documentation": "If the work is a translation, a reprint, or something similar, the publisher of the original edition. Not used by the standard bibliography styles. See also `publisher`."
+ },
+ {
+ "name": "origtitle",
+ "documentation": "If the work is a translation, the `title` of the original work. Not used by the standard bibliography styles. See also `title`."
+ },
+ {
+ "name": "pages",
+ "documentation": "One or more page numbers or page ranges. If the work is published as part of another one, such as an article in a journal or a collection, this field holds the relevant page range in that other work. It may also be used to limit the reference to a specific part of a work (a chapter in a book, for example)."
+ },
+ {
+ "name": "pagetotal",
+ "documentation": "The total number of pages of the work."
+ },
+ {
+ "name": "pagination",
+ "documentation": "The pagination of the work. The value of this field will affect the formatting the *postnote* argument to a citation command. The key should be given in the singular form. Possible keys are `page`, `column`, `line`, `verse`, `section`, and `paragraph`. See also `bookpagination`."
+ },
+ {
+ "name": "part",
+ "documentation": "The number of a partial volume. This field applies to books only, not to journals. It may be used when a logical volume consists of two or more physical ones. In this case the number of the logical volume goes in the `volume` field and the number of the part of that volume in the `part` field. See also `volume`."
+ },
+ {
+ "name": "publisher",
+ "documentation": "The name(s) of the publisher(s)."
+ },
+ {
+ "name": "pubstate",
+ "documentation": "The publication state of the work, e. g., 'in press'."
+ },
+ {
+ "name": "reprinttitle",
+ "documentation": "The title of a reprint of the work. Not used by the standard styles."
+ },
+ {
+ "name": "series",
+ "documentation": "The name of a publication series, such as \"Studies in …\", or the number of a journal series. Books in a publication series are usually numbered. The number or volume of a book in a series is given in the `number` field. Note that the `@article` entry type makes use of the `series` field as well, but handles it in a special way."
+ },
+ {
+ "name": "shortauthor",
+ "documentation": "The author(s) of the work, given in an abbreviated form. This field is mainly intended for abbreviated forms of corporate authors."
+ },
+ {
+ "name": "shorteditor",
+ "documentation": "The editor(s) of the work, given in an abbreviated form. This field is mainly intended for abbreviated forms of corporate editors."
+ },
+ {
+ "name": "shorthand",
+ "documentation": "A special designation to be used by the citation style instead of the usual label. If defined, it overrides the default label. See also `label`."
+ },
+ {
+ "name": "shorthandintro",
+ "documentation": "The verbose citation styles which comes with this package use a phrase like \"henceforth cited as [shorthand]\" to introduce shorthands on the first citation. If the `shorthandintro` field is defined, it overrides the standard phrase. Note that the alternative phrase must include the shorthand."
+ },
+ {
+ "name": "shortjournal",
+ "documentation": "A short version or an acronym of the `journaltitle`. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "shortseries",
+ "documentation": "A short version or an acronym of the `series` field. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "shorttitle",
+ "documentation": "The title in an abridged form. This field is usually not included in the bibliography. It is intended for citations in author-title format. If present, the author-title citation styles use this field instead of `title`."
+ },
+ {
+ "name": "subtitle",
+ "documentation": "The subtitle of the work."
+ },
+ {
+ "name": "title",
+ "documentation": "The title of the work."
+ },
+ {
+ "name": "titleaddon",
+ "documentation": "An annex to the `title`, to be printed in a different font."
+ },
+ {
+ "name": "translator",
+ "documentation": "The translator(s) of the `title` or `booktitle`, depending on the entry type. If the translator is identical to the `editor`, the standard styles will automatically concatenate these fields in the bibliography."
+ },
+ {
+ "name": "type",
+ "documentation": "The type of a `manual`, `patent`, `report`, or `thesis`."
+ },
+ {
+ "name": "url",
+ "documentation": "The URL of an online publication. If it is not URL-escaped (no ‘%’ chars) it will be URI-escaped according to RFC 3987, that is, even Unicode chars will be correctly escaped."
+ },
+ {
+ "name": "urldate",
+ "documentation": "The access date of the address specified in the `url` field."
+ },
+ {
+ "name": "venue",
+ "documentation": "The location of a conference, a symposium, or some other event in `@proceedings` and `@inproceedings` entries. Note that the `location` list holds the place of publication. It therefore corresponds to the `publisher` and `institution` lists. The location of the event is given in the `venue` field. See also `eventdate` and `eventtitle`."
+ },
+ {
+ "name": "version",
+ "documentation": "The revision number of a piece of software, a manual, etc."
+ },
+ {
+ "name": "volume",
+ "documentation": "The volume of a multi-volume book or a periodical. It is expected to be an integer, not necessarily in arabic numerals since `biber` will automatically from roman numerals or arabic letter to integers internally for sorting purposes. See also `part`. See the `noroman` option which can be used to suppress roman numeral parsing. This can help in cases where there is an ambiguity between parsing as roman numerals or alphanumeric (e.g. ‘C’)."
+ },
+ {
+ "name": "volumes",
+ "documentation": "The total number of volumes of a multi-volume work. Depending on the entry type, this field refers to `title` or `maintitle`. It is expected to be an integer, not necessarily in arabic numerals since `biber` will automatically from roman numerals or arabic letter to integers internally for sorting purposes. See the `noroman` option which can be used to suppress roman numeral parsing. This can help in cases where there is an ambiguity between parsing as roman numerals or alphanumeric (e.g. ‘C’)."
+ },
+ {
+ "name": "year",
+ "documentation": "The year of publication. This field is a literal field only when given explicitly in the data (for plain BibTeX compatibility for example). It is however better to use the `date` field as this is compatible with plain years too and supports many more features."
+ },
+ {
+ "name": "crossref",
+ "documentation": "This field holds an entry key for the cross-referencing feature. Child entries with a `crossref` field inherit data from the parent entry specified in the `crossref` field. If the number of child entries referencing a specific parent entry hits a certain threshold, the parent entry is automatically added to the bibliography even if it has not been cited explicitly. The threshold is settable with the `mincrossrefs` package option. Style authors should note that whether or not the `crossref` fields of the child entries are defined on the `biblatex` level depends on the availability of the parent entry. If the parent entry is available, the `crossref` fields of the child entries will be defined. If not, the child entries still inherit the data from the parent entry but their `crossref` fields will be undefined. Whether the parent entry is added to the bibliography implicitly because of the threshold or explicitly because it has been cited does not matter. See also the `xref` field."
+ },
+ {
+ "name": "entryset",
+ "documentation": "This field is specific to entry sets. This field is consumed by the backend processing and does not appear in the `.bbl`."
+ },
+ {
+ "name": "execute",
+ "documentation": "A special field which holds arbitrary TeX code to be executed whenever the data of the respective entry is accessed. This may be useful to handle special cases. Conceptually, this field is comparable to the hooks `AtEveryBibitem`, `AtEveryLositem`, and `AtEveryCitekey`, except that it is definable on a per-entry basis in the `bib` file. Any code in this field is executed automatically immediately after these hooks."
+ },
+ {
+ "name": "gender",
+ "documentation": "The gender of the author or the gender of the editor, if there is no author. The following identifiers are supported: `sf` (feminine singular, a single female name), `sm` (masculine singular, a single male name), `sn` (neuter singular, a single neuter name), `pf` (feminine plural, a list of female names), `pm` (masculine plural, a list of male names), `pn` (neuter plural, a list of neuter names),`pp` (plural, a mixed gender list of names). This information is only required by special bibliography and citation styles and only in certain languages. For example, a citation style may replace recurrent author names with a term such as 'idem'. If the Latin word is used, as is custom in English and French, there is no need to specify the gender. In German publications, however, such key terms are usually given in German and in this case they are gender-sensitive."
+ },
+ {
+ "name": "langid",
+ "documentation": "The language id of the bibliography entry. The alias `hyphenation` is provided for backwards compatibility. The identifier must be a language name known to the `babel/polyglossia` packages. This information may be used to switch hyphenation patterns and localise strings in the bibliography. Note that the language names are case sensitive. The languages currently supported by this package are given in table 2. Note that `babel` treats the identifier `english` as an alias for `british` or `american`, depending on the `babel` version. The `biblatex` package always treats it as an alias for `american`. It is preferable to use the language identifiers `american` and `british` (`babel`) or a language specific option to specify a language variant (`polyglossia`, using the `langidopts` field) to avoid any possible confusion."
+ },
+ {
+ "name": "langidopts",
+ "documentation": "For `polyglossia` users, allows per-entry language specific options. The literal value of this field is passed to `polyglossia`’s language switching facility when using the package option `autolang=langname`."
+ },
+ {
+ "name": "ids",
+ "documentation": "Citation key aliases for the main citation key. An entry may be cited by any of its aliases and `biblatex` will treat the citation as if it had used the primary citation key. This is to aid users who change their citation keys but have legacy documents which use older keys for the same entry. This field is consumed by the backend processing and does not appear in the `.bbl`."
+ },
+ {
+ "name": "indexsorttitle",
+ "documentation": "The title used when sorting the index. In contrast to indextitle, this field is used for sorting only. The printed title in the index is the indextitle or the title field. This field may be useful if the title contains special characters or commands which interfere with the sorting of the index. Style authors should note that biblatex automatically copies the value of either the indextitle or the title field to indexsorttitle if the latter field is undefined."
+ },
+ {
+ "name": "keywords",
+ "documentation": "A separated list of keywords. These keywords are intended for the bibliography filters, they are usually not printed. Note that with the default separator (comma), spaces around the separator are ignored."
+ },
+ {
+ "name": "options",
+ "documentation": "A separated list of entry options in *key*=*value* notation. This field is used to set options on a per-entry basis. Note that citation and bibliography styles may define additional entry options."
+ },
+ {
+ "name": "presort",
+ "documentation": "A special field used to modify the sorting order of the bibliography. This field is the first item the sorting routine considers when sorting the bibliography, hence it may be used to arrange the entries in groups. This may be useful when creating subdivided bibliographies with the bibliography filters. This field is consumed by the backend processing and does not appear in the `.bbl`."
+ },
+ {
+ "name": "related",
+ "documentation": "Citation keys of other entries which have a relationship to this entry. The relationship is specified by the `relatedtype` field."
+ },
+ {
+ "name": "relatedoptions",
+ "documentation": "Per-type options to set for a related entry. Note that this does not set the options on the related entry itself, only the `dataonly` clone which is used as a datasource for the parent entry."
+ },
+ {
+ "name": "relatedtype",
+ "documentation": "An identifier which specified the type of relationship for the keys listed in the `related` field. The identifier is a localised bibliography string printed before the data from the related entry list. It is also used to identify type-specific formatting directives and bibliography macros for the related entries."
+ },
+ {
+ "name": "relatedstring",
+ "documentation": "A field used to override the bibliography string specified by `relatedtype`."
+ },
+ {
+ "name": "sortkey",
+ "documentation": "A field used to modify the sorting order of the bibliography. Think of this field as the master sort key. If present, `biblatex` uses this field during sorting and ignores everything else, except for the presort field. This field is consumed by the backend processing and does not appear in the `.bbl`."
+ },
+ {
+ "name": "sortname",
+ "documentation": "A name or a list of names used to modify the sorting order of the bibliography. If present, this list is used instead of `author` or `editor` when sorting the bibliography. This field is consumed by the backend processing and does not appear in the `.bbl`."
+ },
+ {
+ "name": "sortshorthand",
+ "documentation": "Similar to sortkey but used in the list of shorthands. If present, biblatex uses this field instead of shorthand when sorting the list of shorthands. This is useful if the shorthand field holds shorthands with formatting commands such as `emph` or `\textbf`. This field is consumed by the backend processing and does not appear in the `.bbl`."
+ },
+ {
+ "name": "sorttitle",
+ "documentation": "A field used to modify the sorting order of the bibliography. If present, this field is used instead of the title field when sorting the bibliography. The sorttitle field may come in handy if you have an entry with a title like \"An Introduction to…\" and want that alphabetized under ‘I’ rather than ‘A’. In this case, you could put \"Introduction to…\" in the sorttitle field. This field is consumed by the backend processing and does not appear in the `.bbl`."
+ },
+ {
+ "name": "sortyear",
+ "documentation": "A field used to modify the sorting order of the bibliography. In the default sorting templates, if this field is present, it is used instead of the year field when sorting the bibliography. This field is consumed by the backend processing and does not appear in the `.bbl`."
+ },
+ {
+ "name": "xdata",
+ "documentation": "This field inherits data from one or more `@xdata` entries. Conceptually, the `xdata` field is related to crossref and xref: `crossref` establishes a logical parent/child relation and inherits data; `xref` establishes as logical parent/child relation without inheriting data; `xdata` inherits data without establishing a relation. The value of the `xdata` may be a single entry key or a separated list of keys. This field is consumed by the backend processing and does not appear in the `.bbl`."
+ },
+ {
+ "name": "xref",
+ "documentation": "This field is an alternative cross-referencing mechanism. It differs from `crossref` in that the child entry will not inherit any data from the parent entry specified in the `xref` field. If the number of child entries referencing a specific parent entry hits a certain threshold, the parent entry is automatically added to the bibliography even if it has not been cited explicitly. The threshold is settable with the `minxrefs` package option. Style authors should note that whether or not the `xref` fields of the child entries are defined on the `biblatex` level depends on the availability of the parent entry. If the parent entry is available, the `xref` fields of the child entries will be defined. If not, their `xref` fields will be undefined. Whether the parent entry is added to the bibliography implicitly because of the threshold or explicitly because it has been cited does not matter. See also the `crossref` field."
+ },
+ {
+ "name": "namea",
+ "documentation": "Custom lists for special bibliography styles. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "nameb",
+ "documentation": "Custom lists for special bibliography styles. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "namec",
+ "documentation": "Custom lists for special bibliography styles. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "nameatype",
+ "documentation": "Similar to `authortype` and `editortype` but referring to the fields `name[a--c]`. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "namebtype",
+ "documentation": "Similar to `authortype` and `editortype` but referring to the fields `name[a--c]`. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "namectype",
+ "documentation": "Similar to `authortype` and `editortype` but referring to the fields `name[a--c]`. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "lista",
+ "documentation": "Custom lists for special bibliography styles. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "listb",
+ "documentation": "Custom lists for special bibliography styles. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "listc",
+ "documentation": "Custom lists for special bibliography styles. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "listd",
+ "documentation": "Custom lists for special bibliography styles. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "liste",
+ "documentation": "Custom lists for special bibliography styles. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "listf",
+ "documentation": "Custom lists for special bibliography styles. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "usera",
+ "documentation": "Custom fields for special bibliography styles. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "userb",
+ "documentation": "Custom fields for special bibliography styles. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "userc",
+ "documentation": "Custom fields for special bibliography styles. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "userd",
+ "documentation": "Custom fields for special bibliography styles. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "usere",
+ "documentation": "Custom fields for special bibliography styles. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "userf",
+ "documentation": "Custom fields for special bibliography styles. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "verba",
+ "documentation": "Similar to the custom fields except that these are verbatim fields. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "verbb",
+ "documentation": "Similar to the custom fields except that these are verbatim fields. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "verbc",
+ "documentation": "Similar to the custom fields except that these are verbatim fields. Not used by the standard bibliography styles."
+ },
+ {
+ "name": "address",
+ "documentation": "An alias for `location`, provided for BibTeX compatibility. Traditional BibTeX uses the slightly misleading field name `address` for the place of publication, i. e., the location of the publisher, while `biblatex` uses the generic field name `location`."
+ },
+ {
+ "name": "annote",
+ "documentation": "An alias for `annotation`, provided for jurabib compatibility."
+ },
+ {
+ "name": "archiveprefix",
+ "documentation": "An alias for `eprinttype`, provided for arXiv compatibility."
+ },
+ {
+ "name": "journal",
+ "documentation": "An alias for `journaltitle`, provided for BibTeX compatibility."
+ },
+ {
+ "name": "key",
+ "documentation": "An alias for `sortkey`, provided for BibTeX compatibility."
+ },
+ {
+ "name": "pdf",
+ "documentation": "An alias for `file`, provided for JabRef compatibility."
+ },
+ {
+ "name": "primaryclass",
+ "documentation": "An alias for `eprintclass`, provided for arXiv compatibility."
+ },
+ {
+ "name": "school",
+ "documentation": "An alias for `institution`, provided for BibTeX compatibility. The `institution` field is used by traditional BibTeX for technical reports whereas the `school` field holds the institution associated with theses. The `biblatex` package employs the generic field name `institution` in both cases."
+ }
+ ],
+ "pgfLibraries": [
+ "arrows",
+ "arrows.meta",
+ "arrows.spaced",
+ "curvilinear",
+ "datavisualization.barcharts",
+ "datavisualization.formats.functions",
+ "datavisualization.polar",
+ "decorations.footprints",
+ "decorations.fractals",
+ "decorations.markings",
+ "decorations.pathmorphing",
+ "decorations.pathreplacing",
+ "decorations.shapes",
+ "decorations.text",
+ "fadings",
+ "fixedpointarithmetic",
+ "fpu",
+ "intersections",
+ "lindenmayersystems",
+ "luamath",
+ "patterns",
+ "patterns.meta",
+ "plothandlers",
+ "plotmarks",
+ "profiler",
+ "shadings",
+ "shapes.arrows",
+ "shapes.callouts",
+ "shapes",
+ "shapes.gates.ee",
+ "shapes.gates.ee.IEC",
+ "shapes.gates.logic",
+ "shapes.gates.logic.IEC",
+ "shapes.gates.logic.US",
+ "shapes.geometric",
+ "shapes.misc",
+ "shapes.multipart",
+ "shapes.symbols",
+ "snakes",
+ "svg.path"
+ ],
+ "tikzLibraries": [
+ "3d",
+ "angles",
+ "arrows",
+ "automata",
+ "babel",
+ "backgrounds",
+ "bending",
+ "calc",
+ "calendar",
+ "chains",
+ "circuits",
+ "circuits.ee",
+ "circuits.ee.IEC",
+ "circuits.logic.CDH",
+ "circuits.logic",
+ "circuits.logic.IEC",
+ "circuits.logic.US",
+ "datavisualization.3d",
+ "datavisualization.barcharts",
+ "datavisualization",
+ "datavisualization.formats.functions",
+ "datavisualization.polar",
+ "datavisualization.sparklines",
+ "decorations",
+ "decorations.footprints",
+ "decorations.fractals",
+ "decorations.markings",
+ "decorations.pathmorphing",
+ "decorations.pathreplacing",
+ "decorations.shapes",
+ "decorations.text",
+ "er",
+ "fadings",
+ "fit",
+ "fixedpointarithmetic",
+ "folding",
+ "fpu",
+ "graphs",
+ "graphs.standard",
+ "intersections",
+ "lindenmayersystems",
+ "math",
+ "matrix",
+ "mindmap",
+ "patterns",
+ "patterns.meta",
+ "petri",
+ "plothandlers",
+ "plotmarks",
+ "positioning",
+ "quotes",
+ "scopes",
+ "shadings",
+ "shadows",
+ "shapes.arrows",
+ "shapes.callouts",
+ "shapes",
+ "shapes.gates.logic.IEC",
+ "shapes.gates.logic.US",
+ "shapes.geometric",
+ "shapes.misc",
+ "shapes.multipart",
+ "shapes.symbols",
+ "snakes",
+ "spy",
+ "svg.path",
+ "through",
+ "topaths",
+ "trees",
+ "turtle"
+ ],
+ "mathEnvironments": [
+ "align",
+ "align*",
+ "alignat",
+ "alignat*",
+ "aligned",
+ "aligned*",
+ "alignedat",
+ "alignedat*",
+ "array",
+ "array*",
+ "Bmatrix",
+ "Bmatrix*",
+ "bmatrix",
+ "bmatrix*",
+ "cases",
+ "cases*",
+ "CD",
+ "CD*",
+ "eqnarray",
+ "eqnarray*",
+ "equation",
+ "equation*",
+ "gather",
+ "gather*",
+ "gathered",
+ "gathered*",
+ "matrix",
+ "matrix*",
+ "multline",
+ "multline*",
+ "pmatrix",
+ "pmatrix*",
+ "smallmatrix",
+ "smallmatrix*",
+ "split",
+ "split*",
+ "subarray",
+ "subarray*",
+ "Vmatrix",
+ "Vmatrix*",
+ "vmatrix",
+ "vmatrix*"
+ ],
+ "enumEnvironments": [
+ "enumerate",
+ "itemize",
+ "description"
+ ]
+} \ No newline at end of file
diff --git a/support/texlab/src/syntax/language.rs b/support/texlab/src/syntax/language.rs
new file mode 100644
index 0000000000..3618a079fd
--- /dev/null
+++ b/support/texlab/src/syntax/language.rs
@@ -0,0 +1,231 @@
+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 argument_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!("language.json");
+ serde_json::from_str(JSON).expect("Failed to deserialize language.json")
+});
diff --git a/support/texlab/src/syntax/latex/ast.rs b/support/texlab/src/syntax/latex/ast.rs
new file mode 100644
index 0000000000..0af980b595
--- /dev/null
+++ b/support/texlab/src/syntax/latex/ast.rs
@@ -0,0 +1,345 @@
+use crate::range::RangeExt;
+use crate::syntax::text::{Span, SyntaxNode};
+use itertools::Itertools;
+use lsp_types::Range;
+use std::sync::Arc;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum LatexTokenKind {
+ Word,
+ Command,
+ Math,
+ Comma,
+ BeginGroup,
+ EndGroup,
+ BeginOptions,
+ EndOptions,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexToken {
+ pub span: Span,
+ pub kind: LatexTokenKind,
+}
+
+impl LatexToken {
+ pub fn new(span: Span, kind: LatexTokenKind) -> Self {
+ Self { span, kind }
+ }
+
+ pub fn text(&self) -> &str {
+ &self.span.text
+ }
+}
+
+impl SyntaxNode for LatexToken {
+ fn range(&self) -> Range {
+ self.span.range()
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Default)]
+pub struct LatexRoot {
+ pub children: Vec<LatexContent>,
+}
+
+impl LatexRoot {
+ pub fn new(children: Vec<LatexContent>) -> Self {
+ Self { children }
+ }
+}
+
+impl SyntaxNode for LatexRoot {
+ fn range(&self) -> Range {
+ if self.children.is_empty() {
+ Range::new_simple(0, 0, 0, 0)
+ } else {
+ Range::new(
+ self.children[0].start(),
+ self.children[self.children.len() - 1].end(),
+ )
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum LatexContent {
+ Group(Arc<LatexGroup>),
+ Command(Arc<LatexCommand>),
+ Text(Arc<LatexText>),
+ Comma(Arc<LatexComma>),
+ Math(Arc<LatexMath>),
+}
+
+impl LatexContent {
+ pub fn accept<T: LatexVisitor>(&self, visitor: &mut T) {
+ match self {
+ LatexContent::Group(group) => visitor.visit_group(Arc::clone(&group)),
+ LatexContent::Command(command) => visitor.visit_command(Arc::clone(&command)),
+ LatexContent::Text(text) => visitor.visit_text(Arc::clone(&text)),
+ LatexContent::Comma(comma) => visitor.visit_comma(Arc::clone(&comma)),
+ LatexContent::Math(math) => visitor.visit_math(Arc::clone(&math)),
+ }
+ }
+}
+
+impl SyntaxNode for LatexContent {
+ fn range(&self) -> Range {
+ match self {
+ LatexContent::Group(group) => group.range(),
+ LatexContent::Command(command) => command.range(),
+ LatexContent::Text(text) => text.range(),
+ LatexContent::Comma(comma) => comma.range(),
+ LatexContent::Math(math) => math.range(),
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum LatexGroupKind {
+ Group,
+ Options,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexGroup {
+ pub range: Range,
+ pub left: LatexToken,
+ pub children: Vec<LatexContent>,
+ pub right: Option<LatexToken>,
+ pub kind: LatexGroupKind,
+}
+
+impl LatexGroup {
+ pub fn new(
+ left: LatexToken,
+ children: Vec<LatexContent>,
+ right: Option<LatexToken>,
+ kind: LatexGroupKind,
+ ) -> Self {
+ let end = if let Some(ref right) = right {
+ right.end()
+ } else if !children.is_empty() {
+ children[children.len() - 1].end()
+ } else {
+ left.end()
+ };
+
+ Self {
+ range: Range::new(left.start(), end),
+ left,
+ children,
+ right,
+ kind,
+ }
+ }
+}
+
+impl SyntaxNode for LatexGroup {
+ fn range(&self) -> Range {
+ self.range
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexCommand {
+ pub range: Range,
+ pub name: LatexToken,
+ pub options: Vec<Arc<LatexGroup>>,
+ pub args: Vec<Arc<LatexGroup>>,
+ pub groups: Vec<Arc<LatexGroup>>,
+}
+
+impl LatexCommand {
+ pub fn new(
+ name: LatexToken,
+ options: Vec<Arc<LatexGroup>>,
+ args: Vec<Arc<LatexGroup>>,
+ ) -> Self {
+ let groups: Vec<Arc<LatexGroup>> = args
+ .iter()
+ .chain(options.iter())
+ .sorted_by_key(|group| group.range.start)
+ .map(Arc::clone)
+ .collect();
+
+ let end = if let Some(group) = groups.last() {
+ group.end()
+ } else {
+ name.end()
+ };
+
+ Self {
+ range: Range::new(name.start(), end),
+ name,
+ options,
+ args,
+ groups,
+ }
+ }
+
+ pub fn short_name_range(&self) -> Range {
+ Range::new_simple(
+ self.name.start().line,
+ self.name.start().character + 1,
+ self.name.end().line,
+ self.name.end().character,
+ )
+ }
+
+ pub fn extract_text(&self, index: usize) -> Option<&LatexText> {
+ if self.args.len() > index && self.args[index].children.len() == 1 {
+ if let LatexContent::Text(ref text) = self.args[index].children[0] {
+ Some(text)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+
+ pub fn extract_word(&self, index: usize) -> Option<&LatexToken> {
+ let text = self.extract_text(index)?;
+ if text.words.len() == 1 {
+ Some(&text.words[0])
+ } else {
+ None
+ }
+ }
+
+ pub fn has_word(&self, index: usize) -> bool {
+ self.extract_word(index).is_some()
+ }
+
+ pub fn extract_comma_separated_words(&self, index: usize) -> Vec<&LatexToken> {
+ let mut words = Vec::new();
+ for child in &self.args[index].children {
+ if let LatexContent::Text(text) = child {
+ for word in &text.words {
+ words.push(word);
+ }
+ }
+ }
+ words
+ }
+
+ pub fn has_comma_separated_words(&self, index: usize) -> bool {
+ if self.args.len() <= index {
+ return false;
+ }
+
+ for node in &self.args[index].children {
+ match node {
+ LatexContent::Text(_) | LatexContent::Comma(_) => (),
+ LatexContent::Command(_) | LatexContent::Group(_) | LatexContent::Math(_) => {
+ return false;
+ }
+ }
+ }
+ true
+ }
+}
+
+impl SyntaxNode for LatexCommand {
+ fn range(&self) -> Range {
+ self.range
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexText {
+ pub range: Range,
+ pub words: Vec<LatexToken>,
+}
+
+impl LatexText {
+ pub fn new(words: Vec<LatexToken>) -> Self {
+ Self {
+ range: Range::new(words[0].start(), words[words.len() - 1].end()),
+ words,
+ }
+ }
+}
+
+impl SyntaxNode for LatexText {
+ fn range(&self) -> Range {
+ self.range
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexComma {
+ pub token: LatexToken,
+}
+
+impl LatexComma {
+ pub fn new(token: LatexToken) -> Self {
+ Self { token }
+ }
+}
+
+impl SyntaxNode for LatexComma {
+ fn range(&self) -> Range {
+ self.token.range()
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexMath {
+ pub token: LatexToken,
+}
+
+impl LatexMath {
+ pub fn new(token: LatexToken) -> Self {
+ Self { token }
+ }
+}
+
+impl SyntaxNode for LatexMath {
+ fn range(&self) -> Range {
+ self.token.range()
+ }
+}
+
+pub trait LatexVisitor {
+ fn visit_root(&mut self, root: Arc<LatexRoot>);
+
+ fn visit_group(&mut self, group: Arc<LatexGroup>);
+
+ fn visit_command(&mut self, command: Arc<LatexCommand>);
+
+ fn visit_text(&mut self, text: Arc<LatexText>);
+
+ fn visit_comma(&mut self, comma: Arc<LatexComma>);
+
+ fn visit_math(&mut self, math: Arc<LatexMath>);
+}
+
+pub struct LatexWalker;
+
+impl LatexWalker {
+ pub fn walk_root<T: LatexVisitor>(visitor: &mut T, root: Arc<LatexRoot>) {
+ for child in &root.children {
+ child.accept(visitor);
+ }
+ }
+
+ pub fn walk_group<T: LatexVisitor>(visitor: &mut T, group: Arc<LatexGroup>) {
+ for child in &group.children {
+ child.accept(visitor);
+ }
+ }
+
+ pub fn walk_command<T: LatexVisitor>(visitor: &mut T, command: Arc<LatexCommand>) {
+ for arg in &command.groups {
+ visitor.visit_group(Arc::clone(&arg));
+ }
+ }
+
+ pub fn walk_text<T: LatexVisitor>(_visitor: &mut T, _text: Arc<LatexText>) {}
+
+ pub fn walk_comma<T: LatexVisitor>(_visitor: &mut T, _comma: Arc<LatexComma>) {}
+
+ pub fn walk_math<T: LatexVisitor>(_visitor: &mut T, _math: Arc<LatexMath>) {}
+}
diff --git a/support/texlab/src/syntax/latex/env.rs b/support/texlab/src/syntax/latex/env.rs
new file mode 100644
index 0000000000..163db02ca8
--- /dev/null
+++ b/support/texlab/src/syntax/latex/env.rs
@@ -0,0 +1,123 @@
+use super::ast::*;
+use crate::syntax::language::*;
+use crate::syntax::text::SyntaxNode;
+use lsp_types::Range;
+use std::sync::Arc;
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexEnvironmentDelimiter {
+ pub command: Arc<LatexCommand>,
+}
+
+impl LatexEnvironmentDelimiter {
+ pub fn name(&self) -> Option<&LatexToken> {
+ self.command.extract_word(0)
+ }
+
+ pub fn is_math(&self) -> bool {
+ if let Some(name) = self.name() {
+ LANGUAGE_DATA
+ .math_environments
+ .iter()
+ .any(|env| env == name.text())
+ } else {
+ false
+ }
+ }
+
+ pub fn is_enum(&self) -> bool {
+ if let Some(name) = self.name() {
+ LANGUAGE_DATA
+ .enum_environments
+ .iter()
+ .any(|env| env == name.text())
+ } else {
+ false
+ }
+ }
+}
+
+impl SyntaxNode for LatexEnvironmentDelimiter {
+ fn range(&self) -> Range {
+ self.command.range()
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexEnvironment {
+ pub left: LatexEnvironmentDelimiter,
+ pub right: LatexEnvironmentDelimiter,
+}
+
+impl LatexEnvironment {
+ pub fn is_root(&self) -> bool {
+ self.left
+ .name()
+ .iter()
+ .chain(self.right.name().iter())
+ .any(|name| name.text() == "document")
+ }
+
+ fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> {
+ let mut stack = Vec::new();
+ let mut environments = Vec::new();
+ for command in commands {
+ if let Some(delimiter) = Self::parse_delimiter(command) {
+ if delimiter.command.name.text() == "\\begin" {
+ stack.push(delimiter);
+ } else if let Some(begin) = stack.pop() {
+ environments.push(Self {
+ left: begin,
+ right: delimiter,
+ });
+ }
+ }
+ }
+ environments
+ }
+
+ fn parse_delimiter(command: &Arc<LatexCommand>) -> Option<LatexEnvironmentDelimiter> {
+ if command.name.text() != "\\begin" && command.name.text() != "\\end" {
+ return None;
+ }
+
+ if command.args.is_empty() {
+ return None;
+ }
+
+ if command.has_word(0)
+ || command.args[0].children.is_empty()
+ || command.args[0].right.is_none()
+ {
+ let delimiter = LatexEnvironmentDelimiter {
+ command: Arc::clone(&command),
+ };
+ Some(delimiter)
+ } else {
+ None
+ }
+ }
+}
+
+impl SyntaxNode for LatexEnvironment {
+ fn range(&self) -> Range {
+ Range::new(self.left.start(), self.right.end())
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexEnvironmentInfo {
+ pub environments: Vec<LatexEnvironment>,
+ pub is_standalone: bool,
+}
+
+impl LatexEnvironmentInfo {
+ pub fn parse(commands: &[Arc<LatexCommand>]) -> Self {
+ let environments = LatexEnvironment::parse(commands);
+ let is_standalone = environments.iter().any(LatexEnvironment::is_root);
+ Self {
+ environments,
+ is_standalone,
+ }
+ }
+}
diff --git a/support/texlab/src/syntax/latex/finder.rs b/support/texlab/src/syntax/latex/finder.rs
new file mode 100644
index 0000000000..b59388dc07
--- /dev/null
+++ b/support/texlab/src/syntax/latex/finder.rs
@@ -0,0 +1,74 @@
+use super::ast::*;
+use crate::range::RangeExt;
+use crate::syntax::text::SyntaxNode;
+use lsp_types::Position;
+use std::sync::Arc;
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum LatexNode {
+ Root(Arc<LatexRoot>),
+ Group(Arc<LatexGroup>),
+ Command(Arc<LatexCommand>),
+ Text(Arc<LatexText>),
+ Comma(Arc<LatexComma>),
+ Math(Arc<LatexMath>),
+}
+
+#[derive(Debug)]
+pub struct LatexFinder {
+ pub position: Position,
+ pub results: Vec<LatexNode>,
+}
+
+impl LatexFinder {
+ pub fn new(position: Position) -> Self {
+ Self {
+ position,
+ results: Vec::new(),
+ }
+ }
+}
+
+impl LatexVisitor for LatexFinder {
+ fn visit_root(&mut self, root: Arc<LatexRoot>) {
+ if root.range().contains(self.position) {
+ self.results.push(LatexNode::Root(Arc::clone(&root)));
+ LatexWalker::walk_root(self, root);
+ }
+ }
+
+ fn visit_group(&mut self, group: Arc<LatexGroup>) {
+ if group.range.contains(self.position) {
+ self.results.push(LatexNode::Group(Arc::clone(&group)));
+ LatexWalker::walk_group(self, group);
+ }
+ }
+
+ fn visit_command(&mut self, command: Arc<LatexCommand>) {
+ if command.range.contains(self.position) {
+ self.results.push(LatexNode::Command(Arc::clone(&command)));
+ LatexWalker::walk_command(self, command);
+ }
+ }
+
+ fn visit_text(&mut self, text: Arc<LatexText>) {
+ if text.range.contains(self.position) {
+ self.results.push(LatexNode::Text(Arc::clone(&text)));
+ LatexWalker::walk_text(self, text);
+ }
+ }
+
+ fn visit_comma(&mut self, comma: Arc<LatexComma>) {
+ if comma.range().contains(self.position) {
+ self.results.push(LatexNode::Comma(Arc::clone(&comma)));
+ LatexWalker::walk_comma(self, comma);
+ }
+ }
+
+ fn visit_math(&mut self, math: Arc<LatexMath>) {
+ if math.range().contains(self.position) {
+ self.results.push(LatexNode::Math(Arc::clone(&math)));
+ LatexWalker::walk_math(self, math);
+ }
+ }
+}
diff --git a/support/texlab/src/syntax/latex/glossary.rs b/support/texlab/src/syntax/latex/glossary.rs
new file mode 100644
index 0000000000..14f87ef130
--- /dev/null
+++ b/support/texlab/src/syntax/latex/glossary.rs
@@ -0,0 +1,58 @@
+use super::ast::*;
+use crate::syntax::language::*;
+use crate::syntax::text::SyntaxNode;
+use lsp_types::Range;
+use std::sync::Arc;
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexGlossaryEntry {
+ pub command: Arc<LatexCommand>,
+ pub label_index: usize,
+ pub kind: LatexGlossaryEntryKind,
+}
+
+impl SyntaxNode for LatexGlossaryEntry {
+ fn range(&self) -> Range {
+ self.command.range()
+ }
+}
+
+impl LatexGlossaryEntry {
+ pub fn label(&self) -> &LatexToken {
+ self.command.extract_word(self.label_index).unwrap()
+ }
+
+ fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> {
+ let mut entries = Vec::new();
+ for command in commands {
+ for LatexGlossaryEntryDefinitionCommand {
+ name,
+ label_index,
+ kind,
+ } in &LANGUAGE_DATA.glossary_entry_definition_commands
+ {
+ if command.name.text() == name && command.has_word(*label_index) {
+ entries.push(Self {
+ command: Arc::clone(&command),
+ label_index: *label_index,
+ kind: *kind,
+ });
+ }
+ }
+ }
+ entries
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexGlossaryInfo {
+ pub entries: Vec<LatexGlossaryEntry>,
+}
+
+impl LatexGlossaryInfo {
+ pub fn parse(commands: &[Arc<LatexCommand>]) -> Self {
+ Self {
+ entries: LatexGlossaryEntry::parse(commands),
+ }
+ }
+}
diff --git a/support/texlab/src/syntax/latex/lexer.rs b/support/texlab/src/syntax/latex/lexer.rs
new file mode 100644
index 0000000000..f753d7afce
--- /dev/null
+++ b/support/texlab/src/syntax/latex/lexer.rs
@@ -0,0 +1,179 @@
+use super::ast::{LatexToken, LatexTokenKind};
+use crate::syntax::text::CharStream;
+
+pub struct LatexLexer<'a> {
+ stream: CharStream<'a>,
+}
+
+impl<'a> LatexLexer<'a> {
+ pub fn new(text: &'a str) -> Self {
+ LatexLexer {
+ stream: CharStream::new(text),
+ }
+ }
+
+ fn single_char(&mut self, kind: LatexTokenKind) -> LatexToken {
+ self.stream.start_span();
+ self.stream.next();
+ let span = self.stream.end_span();
+ LatexToken::new(span, kind)
+ }
+
+ fn math(&mut self) -> LatexToken {
+ self.stream.start_span();
+ self.stream.next();
+ if self.stream.satifies(|c| *c == '$') {
+ self.stream.next();
+ }
+ let span = self.stream.end_span();
+ LatexToken::new(span, LatexTokenKind::Math)
+ }
+
+ fn command(&mut self) -> LatexToken {
+ let span = self.stream.command();
+ LatexToken::new(span, LatexTokenKind::Command)
+ }
+
+ fn word(&mut self) -> LatexToken {
+ self.stream.start_span();
+ self.stream.next();
+ while self.stream.satifies(|c| is_word_char(*c)) {
+ self.stream.next();
+ }
+
+ let span = self.stream.end_span();
+ LatexToken::new(span, LatexTokenKind::Word)
+ }
+}
+
+impl<'a> Iterator for LatexLexer<'a> {
+ type Item = LatexToken;
+
+ fn next(&mut self) -> Option<LatexToken> {
+ loop {
+ match self.stream.peek() {
+ Some('%') => {
+ self.stream.skip_rest_of_line();
+ }
+ Some('{') => {
+ return Some(self.single_char(LatexTokenKind::BeginGroup));
+ }
+ Some('}') => {
+ return Some(self.single_char(LatexTokenKind::EndGroup));
+ }
+ Some('[') => {
+ return Some(self.single_char(LatexTokenKind::BeginOptions));
+ }
+ Some(']') => {
+ return Some(self.single_char(LatexTokenKind::EndOptions));
+ }
+ Some('$') => {
+ return Some(self.math());
+ }
+ Some(',') => {
+ return Some(self.single_char(LatexTokenKind::Comma));
+ }
+ Some('\\') => {
+ return Some(self.command());
+ }
+ Some(c) => {
+ if c.is_whitespace() {
+ self.stream.next();
+ } else {
+ return Some(self.word());
+ }
+ }
+ None => {
+ return None;
+ }
+ }
+ }
+ }
+}
+
+fn is_word_char(c: char) -> bool {
+ !c.is_whitespace()
+ && c != '%'
+ && c != '{'
+ && c != '}'
+ && c != '['
+ && c != ']'
+ && c != '\\'
+ && c != '$'
+ && c != ','
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::syntax::text::Span;
+ use lsp_types::{Position, Range};
+
+ fn verify<'a>(
+ lexer: &mut LatexLexer<'a>,
+ line: u64,
+ character: u64,
+ text: &str,
+ kind: LatexTokenKind,
+ ) {
+ let start = Position::new(line, character);
+ let end = Position::new(line, character + text.chars().count() as u64);
+ let range = Range::new(start, end);
+ let span = Span::new(range, text.to_owned());
+ let token = LatexToken::new(span, kind);
+ assert_eq!(Some(token), lexer.next());
+ }
+
+ #[test]
+ fn test_word() {
+ let mut lexer = LatexLexer::new("foo bar baz");
+ verify(&mut lexer, 0, 0, "foo", LatexTokenKind::Word);
+ verify(&mut lexer, 0, 4, "bar", LatexTokenKind::Word);
+ verify(&mut lexer, 0, 8, "baz", LatexTokenKind::Word);
+ assert_eq!(None, lexer.next());
+ }
+
+ #[test]
+ fn test_command() {
+ let mut lexer = LatexLexer::new("\\foo\\bar@baz\n\\foo*");
+ verify(&mut lexer, 0, 0, "\\foo", LatexTokenKind::Command);
+ verify(&mut lexer, 0, 4, "\\bar@baz", LatexTokenKind::Command);
+ verify(&mut lexer, 1, 0, "\\foo*", LatexTokenKind::Command);
+ assert_eq!(None, lexer.next());
+ }
+
+ #[test]
+ fn test_escape_sequence() {
+ let mut lexer = LatexLexer::new("\\%\\**");
+ verify(&mut lexer, 0, 0, "\\%", LatexTokenKind::Command);
+ verify(&mut lexer, 0, 2, "\\*", LatexTokenKind::Command);
+ verify(&mut lexer, 0, 4, "*", LatexTokenKind::Word);
+ assert_eq!(None, lexer.next());
+ }
+
+ #[test]
+ fn test_group_delimiter() {
+ let mut lexer = LatexLexer::new("{}[]");
+ verify(&mut lexer, 0, 0, "{", LatexTokenKind::BeginGroup);
+ verify(&mut lexer, 0, 1, "}", LatexTokenKind::EndGroup);
+ verify(&mut lexer, 0, 2, "[", LatexTokenKind::BeginOptions);
+ verify(&mut lexer, 0, 3, "]", LatexTokenKind::EndOptions);
+ assert_eq!(None, lexer.next());
+ }
+
+ #[test]
+ fn test_math() {
+ let mut lexer = LatexLexer::new("$$ $ $");
+ verify(&mut lexer, 0, 0, "$$", LatexTokenKind::Math);
+ verify(&mut lexer, 0, 3, "$", LatexTokenKind::Math);
+ verify(&mut lexer, 0, 5, "$", LatexTokenKind::Math);
+ assert_eq!(None, lexer.next());
+ }
+
+ #[test]
+ fn test_line_comment() {
+ let mut lexer = LatexLexer::new(" %foo \nfoo");
+ verify(&mut lexer, 1, 0, "foo", LatexTokenKind::Word);
+ assert_eq!(None, lexer.next());
+ }
+}
diff --git a/support/texlab/src/syntax/latex/math.rs b/support/texlab/src/syntax/latex/math.rs
new file mode 100644
index 0000000000..c234232c14
--- /dev/null
+++ b/support/texlab/src/syntax/latex/math.rs
@@ -0,0 +1,199 @@
+use super::ast::*;
+use crate::syntax::language::*;
+use crate::syntax::text::SyntaxNode;
+use lsp_types::Range;
+use std::sync::Arc;
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexEquation {
+ pub left: Arc<LatexCommand>,
+ pub right: Arc<LatexCommand>,
+}
+
+impl LatexEquation {
+ fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> {
+ let mut equations = Vec::new();
+ let mut left = None;
+ for command in commands {
+ let name = command.name.text();
+ if name == "\\[" || name == "\\(" {
+ left = Some(command);
+ } else if name == "\\]" || name == "\\)" {
+ if let Some(begin) = left {
+ equations.push(Self {
+ left: Arc::clone(&begin),
+ right: Arc::clone(&command),
+ });
+ left = None;
+ }
+ }
+ }
+ equations
+ }
+}
+
+impl SyntaxNode for LatexEquation {
+ fn range(&self) -> Range {
+ Range::new(self.left.start(), self.right.end())
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexInline {
+ pub left: Arc<LatexMath>,
+ pub right: Arc<LatexMath>,
+}
+
+impl LatexInline {
+ fn parse(root: Arc<LatexRoot>) -> Vec<Self> {
+ let mut analyzer = LatexInlineAnalyzer::default();
+ analyzer.visit_root(root);
+ analyzer.inlines
+ }
+}
+
+impl SyntaxNode for LatexInline {
+ fn range(&self) -> Range {
+ Range::new(self.left.start(), self.right.end())
+ }
+}
+
+#[derive(Debug, Default)]
+struct LatexInlineAnalyzer {
+ inlines: Vec<LatexInline>,
+ left: Option<Arc<LatexMath>>,
+}
+
+impl LatexVisitor for LatexInlineAnalyzer {
+ fn visit_root(&mut self, root: Arc<LatexRoot>) {
+ LatexWalker::walk_root(self, root);
+ }
+
+ fn visit_group(&mut self, group: Arc<LatexGroup>) {
+ LatexWalker::walk_group(self, group);
+ }
+
+ fn visit_command(&mut self, command: Arc<LatexCommand>) {
+ LatexWalker::walk_command(self, command);
+ }
+
+ fn visit_text(&mut self, text: Arc<LatexText>) {
+ LatexWalker::walk_text(self, text);
+ }
+
+ fn visit_comma(&mut self, comma: Arc<LatexComma>) {
+ LatexWalker::walk_comma(self, comma);
+ }
+
+ fn visit_math(&mut self, math: Arc<LatexMath>) {
+ if let Some(left) = &self.left {
+ let inline = LatexInline {
+ left: Arc::clone(&left),
+ right: Arc::clone(&math),
+ };
+ self.inlines.push(inline);
+ self.left = None;
+ } else {
+ self.left = Some(Arc::clone(&math));
+ }
+ LatexWalker::walk_math(self, math);
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexMathOperator {
+ pub command: Arc<LatexCommand>,
+ pub definition: Arc<LatexCommand>,
+ pub definition_index: usize,
+ pub implementation_index: usize,
+}
+
+impl LatexMathOperator {
+ fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> {
+ let mut operators = Vec::new();
+ for command in commands {
+ for LatexMathOperatorCommand {
+ name,
+ definition_index,
+ implementation_index,
+ } in &LANGUAGE_DATA.math_operator_commands
+ {
+ if command.name.text() == name
+ && command.args.len() > *definition_index
+ && command.args.len() > *implementation_index
+ {
+ let definition = command.args[0].children.iter().next();
+ if let Some(LatexContent::Command(definition)) = definition {
+ operators.push(Self {
+ command: Arc::clone(command),
+ definition: Arc::clone(definition),
+ definition_index: *definition_index,
+ implementation_index: *implementation_index,
+ })
+ }
+ }
+ }
+ }
+ operators
+ }
+}
+
+impl SyntaxNode for LatexMathOperator {
+ fn range(&self) -> Range {
+ self.command.range()
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexTheoremDefinition {
+ pub command: Arc<LatexCommand>,
+ pub index: usize,
+}
+
+impl LatexTheoremDefinition {
+ pub fn name(&self) -> &LatexToken {
+ self.command.extract_word(self.index).unwrap()
+ }
+
+ fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> {
+ let mut definitions = Vec::new();
+ for command in commands {
+ for LatexTheoremDefinitionCommand { name, index } in
+ &LANGUAGE_DATA.theorem_definition_commands
+ {
+ if command.name.text() == name && command.has_word(*index) {
+ definitions.push(Self {
+ command: Arc::clone(&command),
+ index: *index,
+ });
+ }
+ }
+ }
+ definitions
+ }
+}
+
+impl SyntaxNode for LatexTheoremDefinition {
+ fn range(&self) -> Range {
+ self.command.range()
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexMathInfo {
+ pub equations: Vec<LatexEquation>,
+ pub inlines: Vec<LatexInline>,
+ pub operators: Vec<LatexMathOperator>,
+ pub theorem_definitions: Vec<LatexTheoremDefinition>,
+}
+
+impl LatexMathInfo {
+ pub fn parse(root: Arc<LatexRoot>, commands: &[Arc<LatexCommand>]) -> Self {
+ Self {
+ equations: LatexEquation::parse(commands),
+ inlines: LatexInline::parse(root),
+ operators: LatexMathOperator::parse(commands),
+ theorem_definitions: LatexTheoremDefinition::parse(commands),
+ }
+ }
+}
diff --git a/support/texlab/src/syntax/latex/mod.rs b/support/texlab/src/syntax/latex/mod.rs
new file mode 100644
index 0000000000..dc24682752
--- /dev/null
+++ b/support/texlab/src/syntax/latex/mod.rs
@@ -0,0 +1,358 @@
+mod ast;
+mod env;
+mod finder;
+mod glossary;
+mod lexer;
+mod math;
+mod parser;
+mod printer;
+mod structure;
+
+pub use self::ast::*;
+pub use self::env::*;
+pub use self::finder::LatexNode;
+pub use self::glossary::*;
+pub use self::math::*;
+pub use self::printer::LatexPrinter;
+pub use self::structure::*;
+
+use self::finder::LatexFinder;
+use self::lexer::LatexLexer;
+use self::parser::LatexParser;
+use super::language::*;
+use super::text::SyntaxNode;
+use crate::range::RangeExt;
+use crate::workspace::Uri;
+use lsp_types::{Position, Range};
+use path_clean::PathClean;
+use std::path::PathBuf;
+use std::sync::Arc;
+
+#[derive(Debug, Default)]
+struct LatexCommandAnalyzer {
+ commands: Vec<Arc<LatexCommand>>,
+}
+
+impl LatexCommandAnalyzer {
+ fn parse(root: Arc<LatexRoot>) -> Vec<Arc<LatexCommand>> {
+ let mut analyzer = Self::default();
+ analyzer.visit_root(root);
+ analyzer.commands
+ }
+}
+
+impl LatexVisitor for LatexCommandAnalyzer {
+ fn visit_root(&mut self, root: Arc<LatexRoot>) {
+ LatexWalker::walk_root(self, root);
+ }
+
+ fn visit_group(&mut self, group: Arc<LatexGroup>) {
+ LatexWalker::walk_group(self, group);
+ }
+
+ fn visit_command(&mut self, command: Arc<LatexCommand>) {
+ self.commands.push(Arc::clone(&command));
+ LatexWalker::walk_command(self, command);
+ }
+
+ fn visit_text(&mut self, text: Arc<LatexText>) {
+ LatexWalker::walk_text(self, text);
+ }
+
+ fn visit_comma(&mut self, comma: Arc<LatexComma>) {
+ LatexWalker::walk_comma(self, comma);
+ }
+
+ fn visit_math(&mut self, math: Arc<LatexMath>) {
+ LatexWalker::walk_math(self, math);
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexCitation {
+ pub command: Arc<LatexCommand>,
+ pub index: usize,
+}
+
+impl LatexCitation {
+ pub fn keys(&self) -> Vec<&LatexToken> {
+ self.command.extract_comma_separated_words(0)
+ }
+
+ fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> {
+ let mut citations = Vec::new();
+ for command in commands {
+ for LatexCitationCommand { name, index } in &LANGUAGE_DATA.citation_commands {
+ if command.name.text() == name && command.has_comma_separated_words(*index) {
+ citations.push(Self {
+ command: Arc::clone(command),
+ index: *index,
+ });
+ }
+ }
+ }
+ citations
+ }
+}
+
+impl SyntaxNode for LatexCitation {
+ fn range(&self) -> Range {
+ self.command.range()
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexInclude {
+ pub command: Arc<LatexCommand>,
+ pub index: usize,
+ pub kind: LatexIncludeKind,
+ pub all_targets: Vec<Vec<Uri>>,
+ pub include_extension: bool,
+}
+
+impl LatexInclude {
+ pub fn paths(&self) -> Vec<&LatexToken> {
+ self.command.extract_comma_separated_words(self.index)
+ }
+
+ pub fn components(&self) -> Vec<String> {
+ let mut components = Vec::new();
+ for path in self.paths() {
+ match self.kind {
+ LatexIncludeKind::Package => components.push(format!("{}.sty", path.text())),
+ LatexIncludeKind::Class => components.push(format!("{}.cls", path.text())),
+ LatexIncludeKind::Latex
+ | LatexIncludeKind::Bibliography
+ | LatexIncludeKind::Image
+ | LatexIncludeKind::Svg
+ | LatexIncludeKind::Pdf
+ | LatexIncludeKind::Everything => (),
+ }
+ }
+ components
+ }
+
+ fn parse(uri: &Uri, commands: &[Arc<LatexCommand>]) -> Vec<Self> {
+ let mut includes = Vec::new();
+ for command in commands {
+ for description in &LANGUAGE_DATA.include_commands {
+ if let Some(include) = Self::parse_single(uri, &command, &description) {
+ includes.push(include);
+ }
+ }
+ }
+ includes
+ }
+
+ fn parse_single(
+ uri: &Uri,
+ command: &Arc<LatexCommand>,
+ description: &LatexIncludeCommand,
+ ) -> Option<Self> {
+ if command.name.text() != description.name {
+ return None;
+ }
+
+ if command.args.len() <= description.index {
+ return None;
+ }
+
+ let mut all_targets = Vec::new();
+ for relative_path in command.extract_comma_separated_words(description.index) {
+ let mut path = uri.to_file_path().ok()?;
+ path.pop();
+ path.push(relative_path.text());
+ path = PathBuf::from(path.to_string_lossy().into_owned().replace('\\', "/"));
+ path = path.clean();
+ let path = path.to_str()?.to_owned();
+
+ let mut targets = Vec::new();
+ targets.push(Uri::from_file_path(&path).ok()?);
+ if let Some(extensions) = description.kind.extensions() {
+ for extension in extensions {
+ let path = format!("{}.{}", &path, extension);
+ targets.push(Uri::from_file_path(&path).ok()?);
+ }
+ }
+ all_targets.push(targets);
+ }
+
+ let include = Self {
+ command: Arc::clone(command),
+ index: description.index,
+ kind: description.kind,
+ all_targets,
+ include_extension: description.include_extension,
+ };
+ Some(include)
+ }
+}
+
+impl SyntaxNode for LatexInclude {
+ fn range(&self) -> Range {
+ self.command.range()
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexCommandDefinition {
+ pub command: Arc<LatexCommand>,
+ pub definition: Arc<LatexCommand>,
+ pub definition_index: usize,
+ pub implementation: Arc<LatexGroup>,
+ pub implementation_index: usize,
+ pub argument_count_index: usize,
+}
+
+impl LatexCommandDefinition {
+ fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> {
+ let mut definitions = Vec::new();
+ for command in commands {
+ for LatexCommandDefinitionCommand {
+ name,
+ definition_index,
+ argument_count_index,
+ implementation_index,
+ } in &LANGUAGE_DATA.command_definition_commands
+ {
+ if command.name.text() == name
+ && command.args.len() > *definition_index
+ && command.args.len() > *implementation_index
+ {
+ let definition = command.args[0].children.iter().next();
+ if let Some(LatexContent::Command(definition)) = definition {
+ definitions.push(Self {
+ command: Arc::clone(command),
+ definition: Arc::clone(definition),
+ definition_index: *definition_index,
+ implementation: Arc::clone(&command.args[*implementation_index]),
+ implementation_index: *implementation_index,
+ argument_count_index: *argument_count_index,
+ })
+ }
+ }
+ }
+ }
+ definitions
+ }
+}
+
+impl SyntaxNode for LatexCommandDefinition {
+ fn range(&self) -> Range {
+ self.command.range()
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexSyntaxTree {
+ pub root: Arc<LatexRoot>,
+ pub commands: Vec<Arc<LatexCommand>>,
+ pub includes: Vec<LatexInclude>,
+ pub components: Vec<String>,
+ pub env: LatexEnvironmentInfo,
+ pub structure: LatexStructureInfo,
+ pub citations: Vec<LatexCitation>,
+ pub math: LatexMathInfo,
+ pub command_definitions: Vec<LatexCommandDefinition>,
+ pub glossary: LatexGlossaryInfo,
+}
+
+impl LatexSyntaxTree {
+ pub fn parse(uri: &Uri, text: &str) -> Self {
+ let lexer = LatexLexer::new(text);
+ let mut parser = LatexParser::new(lexer);
+ let root = Arc::new(parser.root());
+ let commands = LatexCommandAnalyzer::parse(Arc::clone(&root));
+ let includes = LatexInclude::parse(uri, &commands);
+ let components = includes.iter().flat_map(LatexInclude::components).collect();
+ let env = LatexEnvironmentInfo::parse(&commands);
+ let structure = LatexStructureInfo::parse(&commands);
+ let citations = LatexCitation::parse(&commands);
+ let math = LatexMathInfo::parse(Arc::clone(&root), &commands);
+ let command_definitions = LatexCommandDefinition::parse(&commands);
+ let glossary = LatexGlossaryInfo::parse(&commands);
+ Self {
+ root,
+ commands,
+ includes,
+ components,
+ env,
+ structure,
+ citations,
+ math,
+ command_definitions,
+ glossary,
+ }
+ }
+
+ pub fn find(&self, position: Position) -> Vec<LatexNode> {
+ let mut finder = LatexFinder::new(position);
+ finder.visit_root(Arc::clone(&self.root));
+ finder.results
+ }
+
+ pub fn find_command_by_name(&self, position: Position) -> Option<Arc<LatexCommand>> {
+ for result in self.find(position) {
+ if let LatexNode::Command(command) = result {
+ if command.name.range().contains(position)
+ && command.name.start().character != position.character
+ {
+ return Some(command);
+ }
+ }
+ }
+ None
+ }
+
+ pub fn find_label_by_range(&self, range: Range) -> Option<&LatexLabel> {
+ self.structure
+ .labels
+ .iter()
+ .filter(|label| label.kind == LatexLabelKind::Definition)
+ .filter(|label| label.names().len() == 1)
+ .find(|label| range.contains(label.start()))
+ }
+
+ pub fn find_label_by_environment(&self, environment: &LatexEnvironment) -> Option<&LatexLabel> {
+ self.structure
+ .labels
+ .iter()
+ .filter(|label| label.kind == LatexLabelKind::Definition)
+ .filter(|label| label.names().len() == 1)
+ .find(|label| self.is_direct_child(environment, label.start()))
+ }
+
+ pub fn is_enumeration_item(&self, enumeration: &LatexEnvironment, item: &LatexItem) -> bool {
+ enumeration.range().contains(item.start())
+ && !self
+ .env
+ .environments
+ .iter()
+ .filter(|env| *env != enumeration)
+ .filter(|env| env.left.is_enum() && enumeration.range().contains(env.start()))
+ .any(|env| env.range().contains(item.start()))
+ }
+
+ pub fn is_direct_child(&self, environment: &LatexEnvironment, position: Position) -> bool {
+ environment.range().contains(position)
+ && !self
+ .env
+ .environments
+ .iter()
+ .filter(|env| *env != environment)
+ .filter(|env| environment.range().contains(env.start()))
+ .any(|env| env.range().contains(position))
+ }
+}
+
+pub fn extract_group(content: &LatexGroup) -> String {
+ if content.children.is_empty() || content.right.is_none() {
+ return String::new();
+ }
+
+ let mut printer = LatexPrinter::new(content.children[0].start());
+ for child in &content.children {
+ child.accept(&mut printer);
+ }
+ printer.output
+}
diff --git a/support/texlab/src/syntax/latex/parser.rs b/support/texlab/src/syntax/latex/parser.rs
new file mode 100644
index 0000000000..40693071c2
--- /dev/null
+++ b/support/texlab/src/syntax/latex/parser.rs
@@ -0,0 +1,135 @@
+use super::ast::*;
+use std::iter::Peekable;
+use std::sync::Arc;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+enum LatexScope {
+ Root,
+ Group,
+ Options,
+}
+
+pub struct LatexParser<I: Iterator<Item = LatexToken>> {
+ tokens: Peekable<I>,
+}
+
+impl<I: Iterator<Item = LatexToken>> LatexParser<I> {
+ pub fn new(tokens: I) -> Self {
+ LatexParser {
+ tokens: tokens.peekable(),
+ }
+ }
+
+ pub fn root(&mut self) -> LatexRoot {
+ let children = self.content(LatexScope::Root);
+ LatexRoot::new(children)
+ }
+
+ fn content(&mut self, scope: LatexScope) -> Vec<LatexContent> {
+ let mut children = Vec::new();
+ while let Some(ref token) = self.tokens.peek() {
+ match token.kind {
+ LatexTokenKind::Word | LatexTokenKind::BeginOptions => {
+ children.push(LatexContent::Text(self.text(scope)));
+ }
+ LatexTokenKind::Command => {
+ children.push(LatexContent::Command(self.command()));
+ }
+ LatexTokenKind::Comma => {
+ let node = LatexComma::new(self.tokens.next().unwrap());
+ children.push(LatexContent::Comma(Arc::new(node)));
+ }
+ LatexTokenKind::Math => {
+ children.push(LatexContent::Math(self.math()));
+ }
+ LatexTokenKind::BeginGroup => {
+ children.push(LatexContent::Group(self.group(LatexGroupKind::Group)));
+ }
+ LatexTokenKind::EndGroup => {
+ if scope == LatexScope::Root {
+ self.tokens.next();
+ } else {
+ return children;
+ }
+ }
+ LatexTokenKind::EndOptions => {
+ if scope == LatexScope::Options {
+ return children;
+ } else {
+ children.push(LatexContent::Text(self.text(scope)));
+ }
+ }
+ }
+ }
+ children
+ }
+
+ fn command(&mut self) -> Arc<LatexCommand> {
+ let name = self.tokens.next().unwrap();
+
+ let mut options = Vec::new();
+ let mut args = Vec::new();
+ while let Some(token) = self.tokens.peek() {
+ match token.kind {
+ LatexTokenKind::BeginGroup => {
+ args.push(self.group(LatexGroupKind::Group));
+ }
+ LatexTokenKind::BeginOptions => {
+ options.push(self.group(LatexGroupKind::Options));
+ }
+ _ => {
+ break;
+ }
+ }
+ }
+ Arc::new(LatexCommand::new(name, options, args))
+ }
+
+ fn group(&mut self, kind: LatexGroupKind) -> Arc<LatexGroup> {
+ let left = self.tokens.next().unwrap();
+ let scope = match kind {
+ LatexGroupKind::Group => LatexScope::Group,
+ LatexGroupKind::Options => LatexScope::Options,
+ };
+ let children = self.content(scope);
+ let right_kind = match kind {
+ LatexGroupKind::Group => LatexTokenKind::EndGroup,
+ LatexGroupKind::Options => LatexTokenKind::EndOptions,
+ };
+
+ let right = if self.next_of_kind(right_kind) {
+ self.tokens.next()
+ } else {
+ None
+ };
+
+ Arc::new(LatexGroup::new(left, children, right, kind))
+ }
+
+ fn text(&mut self, scope: LatexScope) -> Arc<LatexText> {
+ let mut words = Vec::new();
+ while let Some(ref token) = self.tokens.peek() {
+ let kind = token.kind;
+ let opts = kind == LatexTokenKind::EndOptions && scope != LatexScope::Options;
+ if kind == LatexTokenKind::Word || kind == LatexTokenKind::BeginOptions || opts {
+ words.push(self.tokens.next().unwrap());
+ } else {
+ break;
+ }
+ }
+ Arc::new(LatexText::new(words))
+ }
+
+ fn math(&mut self) -> Arc<LatexMath> {
+ let token = self.tokens.next().unwrap();
+ Arc::new(LatexMath::new(token))
+ }
+
+ fn next_of_kind(&mut self, kind: LatexTokenKind) -> bool {
+ if let Some(ref token) = self.tokens.peek() {
+ token.kind == kind
+ } else {
+ false
+ }
+ }
+}
diff --git a/support/texlab/src/syntax/latex/printer.rs b/support/texlab/src/syntax/latex/printer.rs
new file mode 100644
index 0000000000..ce03778033
--- /dev/null
+++ b/support/texlab/src/syntax/latex/printer.rs
@@ -0,0 +1,77 @@
+use super::ast::*;
+use crate::syntax::text::*;
+use lsp_types::Position;
+use std::sync::Arc;
+
+#[derive(Debug)]
+pub struct LatexPrinter {
+ pub output: String,
+ position: Position,
+}
+
+impl LatexPrinter {
+ pub fn new(start_position: Position) -> Self {
+ Self {
+ output: String::new(),
+ position: start_position,
+ }
+ }
+
+ fn synchronize(&mut self, position: Position) {
+ while self.position.line < position.line {
+ self.output.push('\n');
+ self.position.line += 1;
+ }
+
+ while self.position.character < position.character {
+ self.output.push(' ');
+ self.position.character += 1;
+ }
+ }
+
+ fn print_token(&mut self, token: &LatexToken) {
+ self.synchronize(token.start());
+ self.output.push_str(token.text());
+ self.position.character += token.end().character - token.start().character;
+ self.synchronize(token.end());
+ }
+}
+
+impl LatexVisitor for LatexPrinter {
+ fn visit_root(&mut self, root: Arc<LatexRoot>) {
+ for child in &root.children {
+ child.accept(self);
+ }
+ }
+
+ fn visit_group(&mut self, group: Arc<LatexGroup>) {
+ self.print_token(&group.left);
+ for child in &group.children {
+ child.accept(self);
+ }
+ if let Some(right) = &group.right {
+ self.print_token(right);
+ }
+ }
+
+ fn visit_command(&mut self, command: Arc<LatexCommand>) {
+ self.print_token(&command.name);
+ for group in &command.groups {
+ self.visit_group(Arc::clone(&group));
+ }
+ }
+
+ fn visit_text(&mut self, text: Arc<LatexText>) {
+ for word in &text.words {
+ self.print_token(word);
+ }
+ }
+
+ fn visit_comma(&mut self, comma: Arc<LatexComma>) {
+ self.print_token(&comma.token);
+ }
+
+ fn visit_math(&mut self, math: Arc<LatexMath>) {
+ self.print_token(&math.token)
+ }
+}
diff --git a/support/texlab/src/syntax/latex/structure.rs b/support/texlab/src/syntax/latex/structure.rs
new file mode 100644
index 0000000000..79c9aac216
--- /dev/null
+++ b/support/texlab/src/syntax/latex/structure.rs
@@ -0,0 +1,250 @@
+use super::ast::*;
+use crate::range::RangeExt;
+use crate::syntax::language::*;
+use crate::syntax::text::{CharStream, SyntaxNode};
+use itertools::Itertools;
+use lsp_types::Range;
+use std::sync::Arc;
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexSection {
+ pub command: Arc<LatexCommand>,
+ pub index: usize,
+ pub level: i32,
+ pub prefix: &'static str,
+}
+
+impl LatexSection {
+ fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> {
+ let mut sections = Vec::new();
+ for command in commands {
+ for LatexSectionCommand {
+ name,
+ index,
+ level,
+ prefix,
+ } in &LANGUAGE_DATA.section_commands
+ {
+ if command.name.text() == name && command.args.len() > *index {
+ sections.push(Self {
+ command: Arc::clone(command),
+ index: *index,
+ level: *level,
+ prefix: prefix.as_ref(),
+ })
+ }
+ }
+ }
+ sections
+ }
+
+ pub fn extract_text(&self, text: &str) -> Option<String> {
+ let content = &self.command.args[self.index];
+ let right = content.right.as_ref()?;
+ let range = Range::new_simple(
+ content.left.start().line,
+ content.left.start().character + 1,
+ right.end().line,
+ right.end().character - 1,
+ );
+ Some(CharStream::extract(&text, range))
+ }
+}
+
+impl SyntaxNode for LatexSection {
+ fn range(&self) -> Range {
+ self.command.range()
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexLabel {
+ pub command: Arc<LatexCommand>,
+ index: usize,
+ pub kind: LatexLabelKind,
+}
+
+impl LatexLabel {
+ pub fn names(&self) -> Vec<&LatexToken> {
+ self.command.extract_comma_separated_words(self.index)
+ }
+
+ fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> {
+ let mut labels = Vec::new();
+ for command in commands {
+ for LatexLabelCommand { name, index, kind } in &LANGUAGE_DATA.label_commands {
+ if command.name.text() == name && command.has_comma_separated_words(*index) {
+ labels.push(Self {
+ command: Arc::clone(command),
+ index: *index,
+ kind: *kind,
+ });
+ }
+ }
+ }
+ labels
+ }
+}
+
+impl SyntaxNode for LatexLabel {
+ fn range(&self) -> Range {
+ self.command.range
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexLabelNumbering {
+ pub command: Arc<LatexCommand>,
+ pub number: String,
+}
+
+impl LatexLabelNumbering {
+ pub fn name(&self) -> &LatexToken {
+ self.command.extract_word(0).unwrap()
+ }
+
+ fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> {
+ commands
+ .iter()
+ .map(Arc::clone)
+ .filter_map(Self::parse_single)
+ .collect()
+ }
+
+ fn parse_single(command: Arc<LatexCommand>) -> Option<Self> {
+ #[derive(Debug, Default)]
+ struct FirstText {
+ text: Option<Arc<LatexText>>,
+ }
+
+ impl LatexVisitor for FirstText {
+ fn visit_root(&mut self, root: Arc<LatexRoot>) {
+ LatexWalker::walk_root(self, root);
+ }
+
+ fn visit_group(&mut self, group: Arc<LatexGroup>) {
+ LatexWalker::walk_group(self, group);
+ }
+
+ fn visit_command(&mut self, command: Arc<LatexCommand>) {
+ LatexWalker::walk_command(self, command);
+ }
+
+ fn visit_text(&mut self, text: Arc<LatexText>) {
+ if self.text.is_none() {
+ self.text = Some(text);
+ }
+ }
+
+ fn visit_comma(&mut self, comma: Arc<LatexComma>) {
+ LatexWalker::walk_comma(self, comma);
+ }
+
+ fn visit_math(&mut self, math: Arc<LatexMath>) {
+ LatexWalker::walk_math(self, math);
+ }
+ }
+
+ if command.name.text() != "\\newlabel" || !command.has_word(0) {
+ return None;
+ }
+
+ let mut analyzer = FirstText::default();
+ analyzer.visit_group(Arc::clone(command.args.get(1)?));
+ let number = analyzer
+ .text?
+ .words
+ .iter()
+ .map(|word| word.text())
+ .join(" ");
+
+ Some(Self {
+ command: Arc::clone(&command),
+ number,
+ })
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexCaption {
+ pub command: Arc<LatexCommand>,
+ pub index: usize,
+}
+
+impl LatexCaption {
+ fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> {
+ let mut captions = Vec::new();
+ for command in commands {
+ if command.name.text() == "\\caption" && !command.args.is_empty() {
+ captions.push(Self {
+ command: Arc::clone(&command),
+ index: 0,
+ });
+ }
+ }
+ captions
+ }
+}
+
+impl SyntaxNode for LatexCaption {
+ fn range(&self) -> Range {
+ self.command.range()
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexItem {
+ pub command: Arc<LatexCommand>,
+}
+
+impl LatexItem {
+ fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> {
+ let mut items = Vec::new();
+ for command in commands {
+ if command.name.text() == "\\item" {
+ items.push(Self {
+ command: Arc::clone(&command),
+ });
+ }
+ }
+ items
+ }
+
+ pub fn name(&self) -> Option<String> {
+ if let Some(options) = self.command.options.get(0) {
+ if options.children.len() == 1 {
+ if let LatexContent::Text(text) = &options.children[0] {
+ return Some(text.words.iter().map(|word| word.text()).join(" "));
+ }
+ }
+ }
+ None
+ }
+}
+
+impl SyntaxNode for LatexItem {
+ fn range(&self) -> Range {
+ self.command.range()
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct LatexStructureInfo {
+ pub sections: Vec<LatexSection>,
+ pub labels: Vec<LatexLabel>,
+ pub label_numberings: Vec<LatexLabelNumbering>,
+ pub captions: Vec<LatexCaption>,
+ pub items: Vec<LatexItem>,
+}
+
+impl LatexStructureInfo {
+ pub fn parse(commands: &[Arc<LatexCommand>]) -> Self {
+ Self {
+ sections: LatexSection::parse(commands),
+ labels: LatexLabel::parse(commands),
+ label_numberings: LatexLabelNumbering::parse(commands),
+ captions: LatexCaption::parse(commands),
+ items: LatexItem::parse(commands),
+ }
+ }
+}
diff --git a/support/texlab/src/syntax/mod.rs b/support/texlab/src/syntax/mod.rs
new file mode 100644
index 0000000000..c92aa995f9
--- /dev/null
+++ b/support/texlab/src/syntax/mod.rs
@@ -0,0 +1,27 @@
+mod bibtex;
+mod language;
+mod latex;
+mod text;
+
+pub use self::bibtex::*;
+pub use self::language::*;
+pub use self::latex::*;
+pub use self::text::*;
+
+use crate::workspace::Uri;
+use tex::Language;
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum SyntaxTree {
+ Latex(Box<LatexSyntaxTree>),
+ Bibtex(Box<BibtexSyntaxTree>),
+}
+
+impl SyntaxTree {
+ pub fn parse(uri: &Uri, text: &str, language: Language) -> Self {
+ match language {
+ Language::Latex => SyntaxTree::Latex(Box::new(LatexSyntaxTree::parse(uri, text))),
+ Language::Bibtex => SyntaxTree::Bibtex(Box::new(text.into())),
+ }
+ }
+}
diff --git a/support/texlab/src/syntax/text.rs b/support/texlab/src/syntax/text.rs
new file mode 100644
index 0000000000..df38635940
--- /dev/null
+++ b/support/texlab/src/syntax/text.rs
@@ -0,0 +1,266 @@
+use lsp_types::{Position, Range};
+use std::iter::Peekable;
+use std::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)]
+pub struct Span {
+ pub range: Range,
+ pub text: String,
+}
+
+impl Span {
+ pub fn new(range: Range, text: String) -> Self {
+ Span { range, text }
+ }
+}
+
+impl SyntaxNode for Span {
+ fn range(&self) -> Range {
+ self.range
+ }
+}
+
+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 {
+ CharStream {
+ 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 == '@'
+}
+
+pub fn titlelize(string: &str) -> String {
+ let mut chars = string.chars();
+ match chars.next() {
+ None => String::new(),
+ Some(c) => c.to_uppercase().chain(chars).collect(),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::range::RangeExt;
+
+ #[test]
+ fn test_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 test_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 test_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 test_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 test_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 test_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 test_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 test_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 test_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
+ );
+ }
+}