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.rs686
-rw-r--r--support/texlab/src/syntax/bibtex/finder.rs110
-rw-r--r--support/texlab/src/syntax/bibtex/formatter.rs329
-rw-r--r--support/texlab/src/syntax/bibtex/lexer.rs135
-rw-r--r--support/texlab/src/syntax/bibtex/mod.rs431
-rw-r--r--support/texlab/src/syntax/bibtex/parser.rs372
-rw-r--r--support/texlab/src/syntax/generic_ast.rs51
-rw-r--r--support/texlab/src/syntax/lang_data.rs (renamed from support/texlab/src/syntax/language.rs)4
-rw-r--r--support/texlab/src/syntax/language.json2213
-rw-r--r--support/texlab/src/syntax/latex/analysis.rs913
-rw-r--r--support/texlab/src/syntax/latex/ast.rs529
-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.rs117
-rw-r--r--support/texlab/src/syntax/latex/math.rs199
-rw-r--r--support/texlab/src/syntax/latex/mod.rs912
-rw-r--r--support/texlab/src/syntax/latex/parser.rs177
-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/latexindent.rs21
-rw-r--r--support/texlab/src/syntax/lsp_kind.rs93
-rw-r--r--support/texlab/src/syntax/mod.rs37
-rw-r--r--support/texlab/src/syntax/text.rs42
24 files changed, 3518 insertions, 4435 deletions
diff --git a/support/texlab/src/syntax/bibtex/ast.rs b/support/texlab/src/syntax/bibtex/ast.rs
index 09f2ee5b88..77ee98e26d 100644
--- a/support/texlab/src/syntax/bibtex/ast.rs
+++ b/support/texlab/src/syntax/bibtex/ast.rs
@@ -1,9 +1,14 @@
-use crate::range::RangeExt;
-use crate::syntax::text::{Span, SyntaxNode};
-use lsp_types::Range;
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum BibtexTokenKind {
+use crate::{
+ protocol::{Position, Range, RangeExt},
+ syntax::{Span, SyntaxNode},
+};
+use itertools::Itertools;
+use petgraph::graph::{Graph, NodeIndex};
+use serde::{Deserialize, Serialize};
+use std::fmt;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
+pub enum TokenKind {
PreambleKind,
StringKind,
EntryKind,
@@ -19,559 +24,408 @@ pub enum BibtexTokenKind {
EndParen,
}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct BibtexToken {
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Token {
pub span: Span,
- pub kind: BibtexTokenKind,
+ pub kind: TokenKind,
}
-impl BibtexToken {
- pub fn new(span: Span, kind: BibtexTokenKind) -> Self {
- BibtexToken { span, kind }
+impl SyntaxNode for Token {
+ fn range(&self) -> Range {
+ self.span.range()
}
+}
- pub fn text(&self) -> &str {
- &self.span.text
+impl Token {
+ pub fn new(span: Span, kind: TokenKind) -> Self {
+ Self { span, kind }
}
-}
-impl SyntaxNode for BibtexToken {
- fn range(&self) -> Range {
- self.span.range
+ pub fn text(&self) -> &str {
+ &self.span.text
}
}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct BibtexRoot {
- pub children: Vec<BibtexDeclaration>,
+#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
+pub struct Root {
+ pub range: Range,
}
-impl BibtexRoot {
- pub fn new(children: Vec<BibtexDeclaration>) -> Self {
- BibtexRoot { children }
+impl SyntaxNode for Root {
+ fn range(&self) -> Range {
+ self.range
}
}
-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(),
- )
- }
+impl fmt::Debug for Root {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "Root")
}
}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub enum BibtexDeclaration {
- Comment(Box<BibtexComment>),
- Preamble(Box<BibtexPreamble>),
- String(Box<BibtexString>),
- Entry(Box<BibtexEntry>),
+#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Comment {
+ pub token: Token,
}
-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 Comment {
+ fn range(&self) -> Range {
+ self.token.range()
}
}
-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,
- }
+impl fmt::Debug for Comment {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "Comment({})", self.token.text())
}
}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct BibtexComment {
+#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Preamble {
pub range: Range,
- pub token: BibtexToken,
+ pub ty: Token,
+ pub left: Option<Token>,
+ pub right: Option<Token>,
}
-impl BibtexComment {
- pub fn new(token: BibtexToken) -> Self {
- BibtexComment {
- range: token.range(),
- token,
- }
+impl SyntaxNode for Preamble {
+ fn range(&self) -> Range {
+ self.range
}
}
-impl SyntaxNode for BibtexComment {
- fn range(&self) -> Range {
- self.range
+impl fmt::Debug for Preamble {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "Preamble")
}
}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct BibtexPreamble {
+#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct String {
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,
- }
- }
+ pub ty: Token,
+ pub left: Option<Token>,
+ pub name: Option<Token>,
+ pub assign: Option<Token>,
+ pub right: Option<Token>,
}
-impl SyntaxNode for BibtexPreamble {
+impl SyntaxNode for String {
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 fmt::Debug for String {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "String({:?})", self.name.as_ref().map(Token::text))
}
}
-impl SyntaxNode for BibtexString {
+#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Entry {
+ pub range: Range,
+ pub ty: Token,
+ pub left: Option<Token>,
+ pub key: Option<Token>,
+ pub comma: Option<Token>,
+ pub right: Option<Token>,
+}
+
+impl SyntaxNode for Entry {
fn range(&self) -> Range {
self.range
}
}
-#[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,
- }
+impl fmt::Debug for Entry {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "Entry({:?})", self.key.as_ref().map(Token::text))
}
+}
+impl Entry {
pub fn is_comment(&self) -> bool {
self.ty.text().to_lowercase() == "@comment"
}
+}
- pub fn find_field(&self, name: &str) -> Option<&BibtexField> {
- self.fields
- .iter()
- .find(|field| field.name.text().to_lowercase() == name)
- }
+#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Field {
+ pub range: Range,
+ pub name: Token,
+ pub assign: Option<Token>,
+ pub comma: Option<Token>,
}
-impl SyntaxNode for BibtexEntry {
+impl SyntaxNode for Field {
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
+impl fmt::Debug for Field {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "Field({})", self.name.text())
}
}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub enum BibtexContent {
- Word(BibtexWord),
- Command(BibtexCommand),
- QuotedContent(BibtexQuotedContent),
- BracedContent(BibtexBracedContent),
- Concat(Box<BibtexConcat>),
+#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Word {
+ pub token: Token,
}
-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 Word {
+ fn range(&self) -> Range {
+ self.token.range()
}
}
-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(),
- }
+impl fmt::Debug for Word {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "Word({})", self.token.text())
}
}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct BibtexWord {
- pub range: Range,
- pub token: BibtexToken,
+#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Command {
+ pub token: Token,
}
-impl BibtexWord {
- pub fn new(token: BibtexToken) -> Self {
- BibtexWord {
- range: token.range(),
- token,
- }
+impl SyntaxNode for Command {
+ fn range(&self) -> Range {
+ self.token.range()
}
}
-impl SyntaxNode for BibtexWord {
- fn range(&self) -> Range {
- self.range
+impl fmt::Debug for Command {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "Command({})", self.token.text())
}
}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct BibtexCommand {
+#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct QuotedContent {
pub range: Range,
- pub token: BibtexToken,
+ pub left: Token,
+ pub right: Option<Token>,
}
-impl BibtexCommand {
- pub fn new(token: BibtexToken) -> Self {
- BibtexCommand {
- range: token.range(),
- token,
- }
+impl SyntaxNode for QuotedContent {
+ fn range(&self) -> Range {
+ self.range
}
}
-impl SyntaxNode for BibtexCommand {
- fn range(&self) -> Range {
- self.range
+impl fmt::Debug for QuotedContent {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "QuotedContent")
}
}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct BibtexQuotedContent {
+#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct BracedContent {
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,
- }
- }
+ pub left: Token,
+ pub right: Option<Token>,
}
-impl SyntaxNode for BibtexQuotedContent {
+impl SyntaxNode for BracedContent {
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 fmt::Debug for BracedContent {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "BracedContent")
}
}
-impl SyntaxNode for BibtexBracedContent {
+#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Concat {
+ pub range: Range,
+ pub operator: Token,
+}
+
+impl SyntaxNode for Concat {
fn range(&self) -> Range {
self.range
}
}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct BibtexConcat {
- pub range: Range,
- pub left: BibtexContent,
- pub operator: BibtexToken,
- pub right: Option<BibtexContent>,
+impl fmt::Debug for Concat {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "Concat")
+ }
}
-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,
- }
- }
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub enum Node {
+ Root(Root),
+ Comment(Comment),
+ Preamble(Box<Preamble>),
+ String(Box<String>),
+ Entry(Box<Entry>),
+ Field(Box<Field>),
+ Word(Word),
+ Command(Command),
+ QuotedContent(QuotedContent),
+ BracedContent(BracedContent),
+ Concat(Concat),
}
-impl SyntaxNode for BibtexConcat {
+impl SyntaxNode for Node {
fn range(&self) -> Range {
- self.range
+ match self {
+ Self::Root(root) => root.range(),
+ Self::Comment(comment) => comment.range(),
+ Self::Preamble(preamble) => preamble.range(),
+ Self::String(string) => string.range(),
+ Self::Entry(entry) => entry.range(),
+ Self::Field(field) => field.range(),
+ Self::Word(word) => word.range(),
+ Self::Command(cmd) => cmd.range(),
+ Self::QuotedContent(content) => content.range(),
+ Self::BracedContent(content) => content.range(),
+ Self::Concat(concat) => concat.range(),
+ }
}
}
-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);
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Tree {
+ pub graph: Graph<Node, ()>,
+ pub root: NodeIndex,
+}
- fn visit_command(&mut self, command: &'a BibtexCommand);
+impl Tree {
+ pub fn children(&self, parent: NodeIndex) -> impl Iterator<Item = NodeIndex> {
+ self.graph
+ .neighbors(parent)
+ .sorted_by_key(|child| self.graph[*child].start())
+ }
- fn visit_quoted_content(&mut self, content: &'a BibtexQuotedContent);
+ pub fn has_children(&self, parent: NodeIndex) -> bool {
+ self.children(parent).next().is_some()
+ }
- fn visit_braced_content(&mut self, content: &'a BibtexBracedContent);
+ pub fn walk<'a, V: Visitor<'a>>(&'a self, visitor: &mut V, parent: NodeIndex) {
+ for child in self.children(parent) {
+ visitor.visit(self, child);
+ }
+ }
- fn visit_concat(&mut self, concat: &'a BibtexConcat);
-}
+ pub fn find(&self, pos: Position) -> Vec<NodeIndex> {
+ let mut finder = Finder::new(pos);
+ finder.visit(self, self.root);
+ finder.results
+ }
-pub struct BibtexWalker;
+ pub fn as_preamble(&self, node: NodeIndex) -> Option<&Preamble> {
+ if let Node::Preamble(preamble) = &self.graph[node] {
+ Some(preamble)
+ } else {
+ None
+ }
+ }
-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 as_string(&self, node: NodeIndex) -> Option<&String> {
+ if let Node::String(string) = &self.graph[node] {
+ Some(string)
+ } else {
+ None
}
}
- 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 as_entry(&self, node: NodeIndex) -> Option<&Entry> {
+ if let Node::Entry(entry) = &self.graph[node] {
+ Some(entry)
+ } else {
+ None
}
}
- 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 as_field(&self, node: NodeIndex) -> Option<&Field> {
+ if let Node::Field(field) = &self.graph[node] {
+ Some(field)
+ } else {
+ None
}
}
- 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 as_command(&self, node: NodeIndex) -> Option<&Command> {
+ if let Node::Command(cmd) = &self.graph[node] {
+ Some(cmd)
+ } else {
+ None
}
}
- 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 as_word(&self, node: NodeIndex) -> Option<&Word> {
+ if let Node::Word(word) = &self.graph[node] {
+ Some(word)
+ } else {
+ None
}
}
- 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 entry_by_key(&self, key: &str) -> Option<NodeIndex> {
+ for node in self.children(self.root) {
+ if let Some(entry) = self.as_entry(node) {
+ if entry.key.as_ref().map(Token::text) == Some(key) {
+ return Some(node);
+ }
+ }
+ }
+ None
+ }
+
+ pub fn field_by_name(&self, parent: NodeIndex, name: &str) -> Option<NodeIndex> {
+ let name = name.to_lowercase();
+ self.as_entry(parent)?;
+ for node in self.children(parent) {
+ if let Some(field) = self.as_field(node) {
+ if field.name.text() == name {
+ return Some(node);
+ }
+ }
}
+ None
+ }
+
+ pub fn crossref(&self, entry: NodeIndex) -> Option<NodeIndex> {
+ let field = self.field_by_name(entry, "crossref")?;
+ let content = self.children(field).next()?;
+ let key = self.as_word(self.children(content).next()?)?;
+ self.entry_by_key(key.token.text())
}
+}
+
+pub trait Visitor<'a> {
+ fn visit(&mut self, tree: &'a Tree, node: NodeIndex);
+}
- pub fn walk_braced_content<'a, T: BibtexVisitor<'a>>(
- visitor: &mut T,
- content: &'a BibtexBracedContent,
- ) {
- for child in &content.children {
- child.accept(visitor);
+#[derive(Debug)]
+struct Finder {
+ position: Position,
+ results: Vec<NodeIndex>,
+}
+
+impl Finder {
+ fn new(position: Position) -> Self {
+ Self {
+ position,
+ results: Vec::new(),
}
}
+}
- 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);
+impl<'a> Visitor<'a> for Finder {
+ fn visit(&mut self, tree: &'a Tree, node: NodeIndex) {
+ if tree.graph[node].range().contains(self.position) {
+ self.results.push(node);
+ tree.walk(self, node);
}
}
}
diff --git a/support/texlab/src/syntax/bibtex/finder.rs b/support/texlab/src/syntax/bibtex/finder.rs
deleted file mode 100644
index acc46c6f45..0000000000
--- a/support/texlab/src/syntax/bibtex/finder.rs
+++ /dev/null
@@ -1,110 +0,0 @@
-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/formatter.rs b/support/texlab/src/syntax/bibtex/formatter.rs
new file mode 100644
index 0000000000..d4965e7cd4
--- /dev/null
+++ b/support/texlab/src/syntax/bibtex/formatter.rs
@@ -0,0 +1,329 @@
+use super::ast::*;
+use crate::{protocol::BibtexFormattingOptions, syntax::text::SyntaxNode};
+use petgraph::graph::NodeIndex;
+use std::{i32, string::String as StdString};
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct FormattingParams<'a> {
+ pub tab_size: usize,
+ pub insert_spaces: bool,
+ pub options: &'a BibtexFormattingOptions,
+}
+
+impl<'a> FormattingParams<'a> {
+ fn line_length(self) -> i32 {
+ let line_length = self.options.line_length.unwrap_or(120);
+ if line_length <= 0 {
+ i32::MAX
+ } else {
+ line_length
+ }
+ }
+
+ fn indent(self) -> StdString {
+ if self.insert_spaces {
+ let mut buffer = StdString::new();
+ for _ in 0..self.tab_size {
+ buffer.push(' ');
+ }
+ buffer
+ } else {
+ "\t".into()
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+struct Formatter<'a> {
+ params: FormattingParams<'a>,
+ indent: StdString,
+ output: StdString,
+ align: Vec<usize>,
+}
+
+impl<'a> Formatter<'a> {
+ fn new(params: FormattingParams<'a>) -> Self {
+ Self {
+ params,
+ indent: params.indent(),
+ output: StdString::new(),
+ align: Vec::new(),
+ }
+ }
+
+ fn visit_token_lowercase(&mut self, token: &Token) {
+ self.output.push_str(token.text().to_lowercase().as_ref());
+ }
+
+ fn should_insert_space(previous: &Token, current: &Token) -> bool {
+ previous.start().line != current.start().line
+ || previous.end().character < current.start().character
+ }
+}
+
+impl<'a, 'b> Visitor<'b> for Formatter<'a> {
+ fn visit(&mut self, tree: &'b Tree, node: NodeIndex) {
+ match &tree.graph[node] {
+ Node::Root(_) => tree.walk(self, node),
+ Node::Comment(comment) => self.output.push_str(comment.token.text()),
+ Node::Preamble(preamble) => {
+ self.visit_token_lowercase(&preamble.ty);
+ self.output.push('{');
+ if tree.has_children(node) {
+ self.align.push(self.output.chars().count());
+ tree.walk(self, node);
+ self.output.push('}');
+ }
+ }
+ Node::String(string) => {
+ self.visit_token_lowercase(&string.ty);
+ self.output.push('{');
+ if let Some(name) = &string.name {
+ self.output.push_str(name.text());
+ self.output.push_str(" = ");
+ if tree.has_children(node) {
+ self.align.push(self.output.chars().count());
+ tree.walk(self, node);
+ self.output.push('}');
+ }
+ }
+ }
+ Node::Entry(entry) => {
+ self.visit_token_lowercase(&entry.ty);
+ self.output.push('{');
+ if let Some(key) = &entry.key {
+ self.output.push_str(key.text());
+ self.output.push(',');
+ self.output.push('\n');
+ tree.walk(self, node);
+ self.output.push('}');
+ }
+ }
+ Node::Field(field) => {
+ self.output.push_str(&self.indent);
+ self.visit_token_lowercase(&field.name);
+ self.output.push_str(" = ");
+ if tree.has_children(node) {
+ let count = field.name.text().chars().count();
+ self.align.push(self.params.tab_size as usize + count + 3);
+ tree.walk(self, node);
+ self.output.push(',');
+ self.output.push('\n');
+ }
+ }
+ Node::Word(_)
+ | Node::Command(_)
+ | Node::BracedContent(_)
+ | Node::QuotedContent(_)
+ | Node::Concat(_) => {
+ let mut analyzer = ContentAnalyzer::default();
+ analyzer.visit(tree, node);
+ let tokens = analyzer.tokens;
+ self.output.push_str(tokens[0].text());
+
+ let align = self.align.pop().unwrap_or_default();
+ let mut length = align + tokens[0].text().chars().count();
+ for i in 1..tokens.len() {
+ let previous = tokens[i - 1];
+ let current = tokens[i];
+ let current_length = current.text().chars().count();
+
+ let insert_space = Self::should_insert_space(previous, current);
+ let space_length = if insert_space { 1 } else { 0 };
+
+ if length + current_length + space_length > self.params.line_length() as usize {
+ self.output.push('\n');
+ self.output.push_str(self.indent.as_ref());
+ for _ in 0..=align - self.params.tab_size {
+ self.output.push(' ');
+ }
+ length = align;
+ } else if insert_space {
+ self.output.push(' ');
+ length += 1;
+ }
+ self.output.push_str(current.text());
+ length += current_length;
+ }
+ }
+ }
+ }
+}
+
+#[derive(Debug, Default)]
+struct ContentAnalyzer<'a> {
+ tokens: Vec<&'a Token>,
+}
+
+impl<'a> Visitor<'a> for ContentAnalyzer<'a> {
+ fn visit(&mut self, tree: &'a Tree, node: NodeIndex) {
+ match &tree.graph[node] {
+ Node::Root(_)
+ | Node::Comment(_)
+ | Node::Preamble(_)
+ | Node::String(_)
+ | Node::Entry(_)
+ | Node::Field(_) => tree.walk(self, node),
+ Node::Word(word) => self.tokens.push(&word.token),
+ Node::Command(cmd) => self.tokens.push(&cmd.token),
+ Node::QuotedContent(content) => {
+ self.tokens.push(&content.left);
+ tree.walk(self, node);
+ if let Some(right) = &content.right {
+ self.tokens.push(right);
+ }
+ }
+ Node::BracedContent(content) => {
+ self.tokens.push(&content.left);
+ tree.walk(self, node);
+ if let Some(right) = &content.right {
+ self.tokens.push(right);
+ }
+ }
+ Node::Concat(concat) => {
+ let mut children = tree.children(node);
+ let left = children.next().unwrap();
+ self.visit(tree, left);
+ self.tokens.push(&concat.operator);
+ children.for_each(|right| self.visit(tree, right));
+ }
+ }
+ }
+}
+
+pub fn format(tree: &Tree, node: NodeIndex, params: FormattingParams) -> StdString {
+ let mut formatter = Formatter::new(params);
+ formatter.visit(tree, node);
+ formatter.output
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::syntax::bibtex;
+ use indoc::indoc;
+
+ fn verify(source: &str, expected: &str, line_length: i32) {
+ let tree = bibtex::open(source);
+ let options = BibtexFormattingOptions {
+ line_length: Some(line_length),
+ formatter: None,
+ };
+
+ let mut children = tree.children(tree.root);
+ let declaration = children.next().unwrap();
+ assert_eq!(children.next(), None);
+
+ let actual = format(
+ &tree,
+ declaration,
+ FormattingParams {
+ tab_size: 4,
+ insert_spaces: true,
+ options: &options,
+ },
+ );
+ assert_eq!(actual, expected);
+ }
+
+ #[test]
+ fn wrap_long_lines() {
+ let source =
+ "@article{foo, bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit.},}";
+ let expected = indoc!(
+ "
+ @article{foo,
+ bar = {Lorem ipsum dolor
+ sit amet,
+ consectetur
+ adipiscing elit.},
+ }"
+ );
+ verify(source, expected, 30);
+ }
+
+ #[test]
+ fn line_length_zero() {
+ let source =
+ "@article{foo, bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit.},}";
+ let expected = indoc!(
+ "
+ @article{foo,
+ bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit.},
+ }"
+ );
+ verify(source, expected, 0);
+ }
+
+ #[test]
+ fn trailing_commas() {
+ let source = "@article{foo, bar = baz}";
+ let expected = indoc!(
+ "
+ @article{foo,
+ bar = baz,
+ }"
+ );
+ verify(source, expected, 30);
+ }
+
+ #[test]
+ fn insert_braces() {
+ let source = "@article{foo, bar = baz,";
+ let expected = indoc!(
+ "
+ @article{foo,
+ bar = baz,
+ }"
+ );
+ verify(source, expected, 30);
+ }
+
+ #[test]
+ fn commands() {
+ let source = "@article{foo, bar = \"\\baz\",}";
+ let expected = indoc!(
+ "@article{foo,
+ bar = \"\\baz\",
+ }"
+ );
+ verify(source, expected, 30);
+ }
+
+ #[test]
+ fn concatenation() {
+ let source = "@article{foo, bar = \"baz\" # \"qux\"}";
+ let expected = indoc!(
+ "
+ @article{foo,
+ bar = \"baz\" # \"qux\",
+ }"
+ );
+ verify(source, expected, 30);
+ }
+
+ #[test]
+ fn parentheses() {
+ let source = "@article(foo,)";
+ let expected = indoc!(
+ "
+ @article{foo,
+ }"
+ );
+ verify(source, expected, 30);
+ }
+
+ #[test]
+ fn string() {
+ let source = "@string{foo=\"bar\"}";
+ let expected = "@string{foo = \"bar\"}";
+ verify(source, expected, 30);
+ }
+
+ #[test]
+ fn preamble() {
+ let source = "@preamble{\n\"foo bar baz\"}";
+ let expected = "@preamble{\"foo bar baz\"}";
+ verify(source, expected, 30);
+ }
+}
diff --git a/support/texlab/src/syntax/bibtex/lexer.rs b/support/texlab/src/syntax/bibtex/lexer.rs
index 8278ef2631..e089b15864 100644
--- a/support/texlab/src/syntax/bibtex/lexer.rs
+++ b/support/texlab/src/syntax/bibtex/lexer.rs
@@ -1,18 +1,19 @@
-use super::ast::{BibtexToken, BibtexTokenKind};
+use super::ast::{Token, TokenKind};
use crate::syntax::text::CharStream;
-pub struct BibtexLexer<'a> {
+#[derive(Debug)]
+pub struct Lexer<'a> {
stream: CharStream<'a>,
}
-impl<'a> BibtexLexer<'a> {
+impl<'a> Lexer<'a> {
pub fn new(text: &'a str) -> Self {
- BibtexLexer {
+ Self {
stream: CharStream::new(text),
}
}
- fn kind(&mut self) -> BibtexToken {
+ fn kind(&mut self) -> Token {
fn is_type_char(c: char) -> bool {
c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'
}
@@ -24,26 +25,26 @@ impl<'a> BibtexLexer<'a> {
}
let span = self.stream.end_span();
let kind = match span.text.to_lowercase().as_ref() {
- "@preamble" => BibtexTokenKind::PreambleKind,
- "@string" => BibtexTokenKind::StringKind,
- _ => BibtexTokenKind::EntryKind,
+ "@preamble" => TokenKind::PreambleKind,
+ "@string" => TokenKind::StringKind,
+ _ => TokenKind::EntryKind,
};
- BibtexToken::new(span, kind)
+ Token::new(span, kind)
}
- fn single_character(&mut self, kind: BibtexTokenKind) -> BibtexToken {
+ fn single_character(&mut self, kind: TokenKind) -> Token {
self.stream.start_span();
self.stream.next();
let span = self.stream.end_span();
- BibtexToken::new(span, kind)
+ Token::new(span, kind)
}
- fn command(&mut self) -> BibtexToken {
+ fn command(&mut self) -> Token {
let span = self.stream.command();
- BibtexToken::new(span, BibtexTokenKind::Command)
+ Token::new(span, TokenKind::Command)
}
- fn word(&mut self) -> BibtexToken {
+ fn word(&mut self) -> Token {
fn is_word_char(c: char) -> bool {
!c.is_whitespace()
&& c != '@'
@@ -62,25 +63,25 @@ impl<'a> BibtexLexer<'a> {
self.stream.next();
}
let span = self.stream.end_span();
- BibtexToken::new(span, BibtexTokenKind::Word)
+ Token::new(span, TokenKind::Word)
}
}
-impl<'a> Iterator for BibtexLexer<'a> {
- type Item = BibtexToken;
+impl<'a> Iterator for Lexer<'a> {
+ type Item = Token;
- fn next(&mut self) -> Option<BibtexToken> {
+ fn next(&mut self) -> Option<Token> {
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.single_character(TokenKind::Assign)),
+ Some(',') => return Some(self.single_character(TokenKind::Comma)),
+ Some('#') => return Some(self.single_character(TokenKind::Concat)),
+ Some('"') => return Some(self.single_character(TokenKind::Quote)),
+ Some('{') => return Some(self.single_character(TokenKind::BeginBrace)),
+ Some('}') => return Some(self.single_character(TokenKind::EndBrace)),
+ Some('(') => return Some(self.single_character(TokenKind::BeginParen)),
+ Some(')') => return Some(self.single_character(TokenKind::EndParen)),
Some('\\') => return Some(self.command()),
Some(c) => {
if c.is_whitespace() {
@@ -100,77 +101,73 @@ impl<'a> Iterator for BibtexLexer<'a> {
#[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,
- ) {
+ use crate::{
+ protocol::{Position, Range},
+ syntax::text::Span,
+ };
+
+ fn verify<'a>(lexer: &mut Lexer<'a>, line: u64, character: u64, text: &str, kind: TokenKind) {
let start = Position::new(line, character);
let end = Position::new(line, character + text.chars().count() as u64);
let range = Range::new(start, end);
let span = Span::new(range, text.to_owned());
- let token = BibtexToken::new(span, kind);
+ let token = Token::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);
+ fn word() {
+ let mut lexer = Lexer::new("foo bar baz");
+ verify(&mut lexer, 0, 0, "foo", TokenKind::Word);
+ verify(&mut lexer, 0, 4, "bar", TokenKind::Word);
+ verify(&mut lexer, 0, 8, "baz", TokenKind::Word);
assert_eq!(None, lexer.next());
}
#[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);
+ fn command() {
+ let mut lexer = Lexer::new("\\foo\\bar@baz");
+ verify(&mut lexer, 0, 0, "\\foo", TokenKind::Command);
+ verify(&mut lexer, 0, 4, "\\bar@baz", TokenKind::Command);
assert_eq!(None, lexer.next());
}
#[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);
+ fn escape_sequence() {
+ let mut lexer = Lexer::new("\\foo*\n\\%\\**");
+ verify(&mut lexer, 0, 0, "\\foo*", TokenKind::Command);
+ verify(&mut lexer, 1, 0, "\\%", TokenKind::Command);
+ verify(&mut lexer, 1, 2, "\\*", TokenKind::Command);
+ verify(&mut lexer, 1, 4, "*", TokenKind::Word);
assert_eq!(None, lexer.next());
}
#[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);
+ fn delimiter() {
+ let mut lexer = Lexer::new("{}()\"");
+ verify(&mut lexer, 0, 0, "{", TokenKind::BeginBrace);
+ verify(&mut lexer, 0, 1, "}", TokenKind::EndBrace);
+ verify(&mut lexer, 0, 2, "(", TokenKind::BeginParen);
+ verify(&mut lexer, 0, 3, ")", TokenKind::EndParen);
+ verify(&mut lexer, 0, 4, "\"", TokenKind::Quote);
assert_eq!(None, lexer.next());
}
#[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);
+ fn kind() {
+ let mut lexer = Lexer::new("@pReAmBlE\n@article\n@string");
+ verify(&mut lexer, 0, 0, "@pReAmBlE", TokenKind::PreambleKind);
+ verify(&mut lexer, 1, 0, "@article", TokenKind::EntryKind);
+ verify(&mut lexer, 2, 0, "@string", TokenKind::StringKind);
assert_eq!(None, lexer.next());
}
#[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);
+ fn operator() {
+ let mut lexer = Lexer::new("=,#");
+ verify(&mut lexer, 0, 0, "=", TokenKind::Assign);
+ verify(&mut lexer, 0, 1, ",", TokenKind::Comma);
+ verify(&mut lexer, 0, 2, "#", TokenKind::Concat);
assert_eq!(None, lexer.next());
}
}
diff --git a/support/texlab/src/syntax/bibtex/mod.rs b/support/texlab/src/syntax/bibtex/mod.rs
index 9910390fe7..aa7242c1f7 100644
--- a/support/texlab/src/syntax/bibtex/mod.rs
+++ b/support/texlab/src/syntax/bibtex/mod.rs
@@ -1,75 +1,396 @@
mod ast;
-mod finder;
+mod formatter;
mod lexer;
mod parser;
-use self::lexer::BibtexLexer;
-use self::parser::BibtexParser;
+pub use self::{ast::*, formatter::*};
-pub use self::ast::*;
-pub use self::finder::*;
-use lsp_types::Position;
+use self::{lexer::Lexer, parser::Parser};
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct BibtexSyntaxTree {
- pub root: BibtexRoot,
+pub fn open(text: &str) -> Tree {
+ let lexer = Lexer::new(text);
+ let parser = Parser::new(lexer);
+ parser.parse()
}
-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
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{
+ protocol::{Range, RangeExt},
+ syntax::text::SyntaxNode,
+ };
+ use petgraph::graph::NodeIndex;
+
+ #[derive(Debug, Default)]
+ struct TreeTraversal {
+ nodes: Vec<NodeIndex>,
}
- 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);
- }
+ impl<'a> Visitor<'a> for TreeTraversal {
+ fn visit(&mut self, tree: &Tree, node: NodeIndex) {
+ self.nodes.push(node);
+ tree.walk(self, node);
}
- strings
}
- pub fn find(&self, position: Position) -> Vec<BibtexNode> {
- let mut finder = BibtexFinder::new(position);
- finder.visit_root(&self.root);
- finder.results
- }
+ mod range {
+ use super::*;
+ use indoc::indoc;
- pub fn find_entry(&self, key: &str) -> Option<&BibtexEntry> {
- self.entries()
- .into_iter()
- .find(|entry| entry.key.as_ref().map(BibtexToken::text) == Some(key))
- }
+ fn verify(expected_ranges: Vec<Range>, text: &str) {
+ let tree = open(text.trim());
- 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());
- }
- }
+ let mut traversal = TreeTraversal::default();
+ traversal.visit(&tree, tree.root);
+ let actual_ranges: Vec<_> = traversal
+ .nodes
+ .into_iter()
+ .map(|node| tree.graph[node].range())
+ .collect();
+
+ assert_eq!(actual_ranges, expected_ranges);
}
- None
- }
-}
-impl From<BibtexRoot> for BibtexSyntaxTree {
- fn from(root: BibtexRoot) -> Self {
- BibtexSyntaxTree { root }
- }
-}
+ #[test]
+ fn empty_document() {
+ verify(vec![Range::new_simple(0, 0, 0, 0)], "");
+ }
+
+ #[test]
+ fn comment() {
+ verify(
+ vec![Range::new_simple(0, 0, 0, 3), Range::new_simple(0, 0, 0, 3)],
+ "foo",
+ );
+ }
+
+ #[test]
+ fn preamble_no_left() {
+ verify(
+ vec![Range::new_simple(0, 0, 0, 9), Range::new_simple(0, 0, 0, 9)],
+ "@preamble",
+ );
+ }
+
+ #[test]
+ fn preamble_no_content() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 10),
+ Range::new_simple(0, 0, 0, 10),
+ ],
+ "@preamble{",
+ );
+ }
+
+ #[test]
+ fn preamble_no_right() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 15),
+ Range::new_simple(0, 0, 0, 15),
+ Range::new_simple(0, 10, 0, 15),
+ Range::new_simple(0, 11, 0, 14),
+ ],
+ r#"@preamble{"foo""#,
+ );
+ }
+
+ #[test]
+ fn preamble_complete() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 16),
+ Range::new_simple(0, 0, 0, 16),
+ Range::new_simple(0, 10, 0, 15),
+ Range::new_simple(0, 11, 0, 14),
+ ],
+ r#"@preamble{"foo"}"#,
+ );
+ }
+
+ #[test]
+ fn string_no_left() {
+ verify(
+ vec![Range::new_simple(0, 0, 0, 7), Range::new_simple(0, 0, 0, 7)],
+ r#"@string"#,
+ );
+ }
+
+ #[test]
+ fn string_no_name() {
+ verify(
+ vec![Range::new_simple(0, 0, 0, 8), Range::new_simple(0, 0, 0, 8)],
+ r#"@string{"#,
+ );
+ }
+
+ #[test]
+ fn string_no_assign() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 11),
+ Range::new_simple(0, 0, 0, 11),
+ ],
+ r#"@string{foo"#,
+ );
+ }
+
+ #[test]
+ fn string_no_value() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 13),
+ Range::new_simple(0, 0, 0, 13),
+ ],
+ r#"@string{foo ="#,
+ );
+ }
+
+ #[test]
+ fn string_no_right() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 19),
+ Range::new_simple(0, 0, 0, 19),
+ Range::new_simple(0, 14, 0, 19),
+ Range::new_simple(0, 15, 0, 18),
+ ],
+ r#"@string{foo = "bar""#,
+ );
+ }
+
+ #[test]
+ fn string_complete() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 20),
+ Range::new_simple(0, 0, 0, 20),
+ Range::new_simple(0, 14, 0, 19),
+ Range::new_simple(0, 15, 0, 18),
+ ],
+ r#"@string{foo = "bar"}"#,
+ );
+ }
-impl From<&str> for BibtexSyntaxTree {
- fn from(text: &str) -> Self {
- let lexer = BibtexLexer::new(text);
- let mut parser = BibtexParser::new(lexer);
- parser.root().into()
+ #[test]
+ fn entry_no_left() {
+ verify(
+ vec![Range::new_simple(0, 0, 0, 8), Range::new_simple(0, 0, 0, 8)],
+ r#"@article"#,
+ );
+ }
+
+ #[test]
+ fn entry_no_key() {
+ verify(
+ vec![Range::new_simple(0, 0, 0, 9), Range::new_simple(0, 0, 0, 9)],
+ r#"@article{"#,
+ );
+ }
+
+ #[test]
+ fn entry_no_comma() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 12),
+ Range::new_simple(0, 0, 0, 12),
+ ],
+ r#"@article{foo"#,
+ );
+ }
+
+ #[test]
+ fn entry_no_right() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 13),
+ Range::new_simple(0, 0, 0, 13),
+ ],
+ r#"@article{foo,"#,
+ );
+ }
+
+ #[test]
+ fn entry_parentheses() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 14),
+ Range::new_simple(0, 0, 0, 14),
+ ],
+ r#"@article(foo,)"#,
+ );
+ }
+
+ #[test]
+ fn field_no_assign() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 1, 10),
+ Range::new_simple(0, 0, 1, 10),
+ Range::new_simple(1, 4, 1, 10),
+ ],
+ indoc!(
+ r#"
+ @article{foo,
+ author
+ "#
+ ),
+ );
+ }
+
+ #[test]
+ fn field_no_value() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 1, 12),
+ Range::new_simple(0, 0, 1, 12),
+ Range::new_simple(1, 4, 1, 12),
+ ],
+ indoc!(
+ r#"
+ @article{foo,
+ author =
+ "#
+ ),
+ );
+ }
+
+ #[test]
+ fn field_no_comma() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 1, 16),
+ Range::new_simple(0, 0, 1, 16),
+ Range::new_simple(1, 4, 1, 16),
+ Range::new_simple(1, 13, 1, 16),
+ ],
+ indoc!(
+ r#"
+ @article{foo,
+ author = bar
+ "#
+ ),
+ );
+ }
+
+ #[test]
+ fn field_complete() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 2, 1),
+ Range::new_simple(0, 0, 2, 1),
+ Range::new_simple(1, 4, 1, 17),
+ Range::new_simple(1, 13, 1, 16),
+ ],
+ indoc!(
+ r#"
+ @article{foo,
+ author = bar,
+ }
+ "#
+ ),
+ );
+ }
+
+ #[test]
+ fn entry_two_fields() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 3, 1),
+ Range::new_simple(0, 0, 3, 1),
+ Range::new_simple(1, 4, 1, 17),
+ Range::new_simple(1, 13, 1, 16),
+ Range::new_simple(2, 4, 2, 16),
+ Range::new_simple(2, 12, 2, 15),
+ ],
+ indoc!(
+ r#"
+ @article{foo,
+ author = bar,
+ title = baz,
+ }
+ "#
+ ),
+ );
+ }
+
+ #[test]
+ fn quoted_content_no_children() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 12),
+ Range::new_simple(0, 0, 0, 12),
+ Range::new_simple(0, 10, 0, 11),
+ ],
+ r#"@preamble{"}"#,
+ );
+ }
+
+ #[test]
+ fn quoted_content_no_right() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 16),
+ Range::new_simple(0, 0, 0, 16),
+ Range::new_simple(0, 10, 0, 15),
+ Range::new_simple(0, 11, 0, 15),
+ ],
+ r#"@preamble{"word}"#,
+ );
+ }
+
+ #[test]
+ fn braced_content_no_children() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 11),
+ Range::new_simple(0, 0, 0, 11),
+ Range::new_simple(0, 10, 0, 11),
+ ],
+ r#"@preamble{{"#,
+ );
+ }
+
+ #[test]
+ fn braced_content_no_right() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 15),
+ Range::new_simple(0, 0, 0, 15),
+ Range::new_simple(0, 10, 0, 15),
+ Range::new_simple(0, 11, 0, 15),
+ ],
+ r#"@preamble{{word"#,
+ );
+ }
+
+ #[test]
+ fn concat_no_right() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 16),
+ Range::new_simple(0, 0, 0, 16),
+ Range::new_simple(0, 10, 0, 15),
+ Range::new_simple(0, 10, 0, 13),
+ ],
+ r#"@preamble{foo #}"#,
+ );
+ }
+
+ #[test]
+ fn concat_complete() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 20),
+ Range::new_simple(0, 0, 0, 20),
+ Range::new_simple(0, 10, 0, 19),
+ Range::new_simple(0, 10, 0, 13),
+ Range::new_simple(0, 16, 0, 19),
+ ],
+ r#"@preamble{foo # bar}"#,
+ );
+ }
}
}
diff --git a/support/texlab/src/syntax/bibtex/parser.rs b/support/texlab/src/syntax/bibtex/parser.rs
index ffc33da545..b23903aeb3 100644
--- a/support/texlab/src/syntax/bibtex/parser.rs
+++ b/support/texlab/src/syntax/bibtex/parser.rs
@@ -1,197 +1,379 @@
use super::ast::*;
+use crate::{
+ protocol::{Range, RangeExt},
+ syntax::text::SyntaxNode,
+};
+use petgraph::graph::{Graph, NodeIndex};
use std::iter::Peekable;
-pub struct BibtexParser<I: Iterator<Item = BibtexToken>> {
+pub struct Parser<I: Iterator<Item = Token>> {
+ graph: Graph<Node, ()>,
tokens: Peekable<I>,
}
-impl<I: Iterator<Item = BibtexToken>> BibtexParser<I> {
+impl<I: Iterator<Item = Token>> Parser<I> {
pub fn new(tokens: I) -> Self {
- BibtexParser {
+ Self {
tokens: tokens.peekable(),
+ graph: Graph::new(),
}
}
- pub fn root(&mut self) -> BibtexRoot {
+ pub fn parse(mut self) -> Tree {
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)));
- }
+ TokenKind::PreambleKind => children.push(self.preamble()),
+ TokenKind::StringKind => children.push(self.string()),
+ TokenKind::EntryKind => children.push(self.entry()),
+ _ => children.push(self.comment()),
}
}
- BibtexRoot::new(children)
+
+ let range = if children.is_empty() {
+ Range::new_simple(0, 0, 0, 0)
+ } else {
+ let start = self.graph[children[0]].start();
+ let end = self.graph[children[children.len() - 1]].end();
+ Range::new(start, end)
+ };
+
+ let root = self.graph.add_node(Node::Root(Root { range }));
+ self.connect(root, &children);
+ Tree {
+ graph: self.graph,
+ root,
+ }
}
- fn preamble(&mut self) -> BibtexPreamble {
+ fn preamble(&mut self) -> NodeIndex {
let ty = self.tokens.next().unwrap();
- let left = self.expect2(BibtexTokenKind::BeginBrace, BibtexTokenKind::BeginParen);
+ let left = self.expect2(TokenKind::BeginBrace, TokenKind::BeginParen);
if left.is_none() {
- return BibtexPreamble::new(ty, None, None, None);
+ return self.graph.add_node(Node::Preamble(Box::new(Preamble {
+ range: ty.range(),
+ ty,
+ left: None,
+ right: None,
+ })));
}
if !self.can_match_content() {
- return BibtexPreamble::new(ty, left, None, None);
+ return self.graph.add_node(Node::Preamble(Box::new(Preamble {
+ range: Range::new(ty.start(), left.as_ref().unwrap().end()),
+ ty,
+ left,
+ right: None,
+ })));
}
+
let content = self.content();
- let right = self.expect2(BibtexTokenKind::EndBrace, BibtexTokenKind::EndParen);
- BibtexPreamble::new(ty, left, Some(content), right)
+ let right = self.expect2(TokenKind::EndBrace, TokenKind::EndParen);
+ let end = right
+ .as_ref()
+ .map(Token::end)
+ .unwrap_or_else(|| self.graph[content].end());
+
+ let parent = self.graph.add_node(Node::Preamble(Box::new(Preamble {
+ range: Range::new(ty.start(), end),
+ ty,
+ left,
+ right,
+ })));
+ self.graph.add_edge(parent, content, ());
+ parent
}
- fn string(&mut self) -> BibtexString {
+ fn string(&mut self) -> NodeIndex {
let ty = self.tokens.next().unwrap();
- let left = self.expect2(BibtexTokenKind::BeginBrace, BibtexTokenKind::BeginParen);
+ let left = self.expect2(TokenKind::BeginBrace, TokenKind::BeginParen);
if left.is_none() {
- return BibtexString::new(ty, None, None, None, None, None);
+ return self.graph.add_node(Node::String(Box::new(String {
+ range: ty.range(),
+ ty,
+ left: None,
+ name: None,
+ assign: None,
+ right: None,
+ })));
}
- let name = self.expect1(BibtexTokenKind::Word);
+ let name = self.expect1(TokenKind::Word);
if name.is_none() {
- return BibtexString::new(ty, left, None, None, None, None);
+ return self.graph.add_node(Node::String(Box::new(String {
+ range: Range::new(ty.start(), left.as_ref().unwrap().end()),
+ ty,
+ left,
+ name: None,
+ assign: None,
+ right: None,
+ })));
}
- let assign = self.expect1(BibtexTokenKind::Assign);
+ let assign = self.expect1(TokenKind::Assign);
if assign.is_none() {
- return BibtexString::new(ty, left, name, None, None, None);
+ return self.graph.add_node(Node::String(Box::new(String {
+ range: Range::new(ty.start(), name.as_ref().unwrap().end()),
+ ty,
+ left,
+ name,
+ assign: None,
+ right: None,
+ })));
}
if !self.can_match_content() {
- return BibtexString::new(ty, left, name, assign, None, None);
+ return self.graph.add_node(Node::String(Box::new(String {
+ range: Range::new(ty.start(), assign.as_ref().unwrap().end()),
+ ty,
+ left,
+ name,
+ assign,
+ right: None,
+ })));
}
let value = self.content();
- let right = self.expect2(BibtexTokenKind::EndBrace, BibtexTokenKind::EndParen);
- BibtexString::new(ty, left, name, assign, Some(value), right)
+ let right = self.expect2(TokenKind::EndBrace, TokenKind::EndParen);
+ let end = right
+ .as_ref()
+ .map(Token::end)
+ .unwrap_or_else(|| self.graph[value].end());
+
+ let parent = self.graph.add_node(Node::String(Box::new(String {
+ range: Range::new(ty.start(), end),
+ ty,
+ left,
+ name,
+ assign,
+ right,
+ })));
+ self.graph.add_edge(parent, value, ());
+ parent
}
- fn entry(&mut self) -> BibtexEntry {
+ fn entry(&mut self) -> NodeIndex {
let ty = self.tokens.next().unwrap();
- let left = self.expect2(BibtexTokenKind::BeginBrace, BibtexTokenKind::BeginParen);
+ let left = self.expect2(TokenKind::BeginBrace, TokenKind::BeginParen);
if left.is_none() {
- return BibtexEntry::new(ty, None, None, None, Vec::new(), None);
+ return self.graph.add_node(Node::Entry(Box::new(Entry {
+ range: ty.range(),
+ ty,
+ left: None,
+ key: None,
+ comma: None,
+ right: None,
+ })));
}
- let name = self.expect1(BibtexTokenKind::Word);
- if name.is_none() {
- return BibtexEntry::new(ty, left, None, None, Vec::new(), None);
+ let key = self.expect1(TokenKind::Word);
+ if key.is_none() {
+ return self.graph.add_node(Node::Entry(Box::new(Entry {
+ range: Range::new(ty.start(), left.as_ref().unwrap().end()),
+ ty,
+ left,
+ key: None,
+ comma: None,
+ right: None,
+ })));
}
- let comma = self.expect1(BibtexTokenKind::Comma);
+ let comma = self.expect1(TokenKind::Comma);
if comma.is_none() {
- return BibtexEntry::new(ty, left, name, None, Vec::new(), None);
+ return self.graph.add_node(Node::Entry(Box::new(Entry {
+ range: Range::new(ty.start(), key.as_ref().unwrap().end()),
+ ty,
+ left,
+ key,
+ comma: None,
+ right: None,
+ })));
}
let mut fields = Vec::new();
- while self.next_of_kind(BibtexTokenKind::Word) {
+ while self.next_of_kind(TokenKind::Word) {
fields.push(self.field());
}
- let right = self.expect2(BibtexTokenKind::EndBrace, BibtexTokenKind::EndParen);
- BibtexEntry::new(ty, left, name, comma, fields, right)
+ let right = self.expect2(TokenKind::EndBrace, TokenKind::EndParen);
+
+ let end = right
+ .as_ref()
+ .map(Token::end)
+ .or_else(|| fields.last().map(|field| self.graph[*field].end()))
+ .unwrap_or_else(|| comma.as_ref().unwrap().end());
+ let parent = self.graph.add_node(Node::Entry(Box::new(Entry {
+ range: Range::new(ty.start(), end),
+ ty,
+ left,
+ key,
+ comma,
+ right,
+ })));
+ self.connect(parent, &fields);
+ parent
}
- fn field(&mut self) -> BibtexField {
+ fn comment(&mut self) -> NodeIndex {
+ let token = self.tokens.next().unwrap();
+ self.graph.add_node(Node::Comment(Comment { token }))
+ }
+
+ fn field(&mut self) -> NodeIndex {
let name = self.tokens.next().unwrap();
- let assign = self.expect1(BibtexTokenKind::Assign);
+ let assign = self.expect1(TokenKind::Assign);
if assign.is_none() {
- return BibtexField::new(name, None, None, None);
+ return self.graph.add_node(Node::Field(Box::new(Field {
+ range: name.range(),
+ name,
+ assign: None,
+ comma: None,
+ })));
}
if !self.can_match_content() {
- return BibtexField::new(name, assign, None, None);
+ return self.graph.add_node(Node::Field(Box::new(Field {
+ range: Range::new(name.start(), assign.as_ref().unwrap().end()),
+ name,
+ assign,
+ comma: None,
+ })));
}
+
let content = self.content();
- let comma = self.expect1(BibtexTokenKind::Comma);
- BibtexField::new(name, assign, Some(content), comma)
+ let comma = self.expect1(TokenKind::Comma);
+
+ let end = comma
+ .as_ref()
+ .map(Token::end)
+ .unwrap_or_else(|| self.graph[content].end());
+ let parent = self.graph.add_node(Node::Field(Box::new(Field {
+ range: Range::new(name.start(), end),
+ name,
+ assign,
+ comma,
+ })));
+ self.graph.add_edge(parent, content, ());
+ parent
}
- fn content(&mut self) -> BibtexContent {
+ fn content(&mut self) -> NodeIndex {
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 => {
+ TokenKind::PreambleKind
+ | TokenKind::StringKind
+ | TokenKind::EntryKind
+ | TokenKind::Word
+ | TokenKind::Assign
+ | TokenKind::Comma
+ | TokenKind::BeginParen
+ | TokenKind::EndParen => self.graph.add_node(Node::Word(Word { token })),
+ TokenKind::Command => self.graph.add_node(Node::Command(Command { token })),
+ TokenKind::Quote => {
let mut children = Vec::new();
while self.can_match_content() {
- if self.next_of_kind(BibtexTokenKind::Quote) {
+ if self.next_of_kind(TokenKind::Quote) {
break;
}
children.push(self.content());
}
- let right = self.expect1(BibtexTokenKind::Quote);
- BibtexContent::QuotedContent(BibtexQuotedContent::new(token, children, right))
+ let right = self.expect1(TokenKind::Quote);
+
+ let end = right
+ .as_ref()
+ .map(Token::end)
+ .or_else(|| children.last().map(|child| self.graph[*child].end()))
+ .unwrap_or_else(|| token.end());
+ let parent = self.graph.add_node(Node::QuotedContent(QuotedContent {
+ range: Range::new(token.start(), end),
+ left: token,
+ right,
+ }));
+ self.connect(parent, &children);
+ parent
}
- BibtexTokenKind::BeginBrace => {
+ TokenKind::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))
+ let right = self.expect1(TokenKind::EndBrace);
+
+ let end = right
+ .as_ref()
+ .map(Token::end)
+ .or_else(|| children.last().map(|child| self.graph[*child].end()))
+ .unwrap_or_else(|| token.end());
+ let parent = self.graph.add_node(Node::BracedContent(BracedContent {
+ range: Range::new(token.start(), end),
+ left: token,
+ right,
+ }));
+ self.connect(parent, &children);
+ parent
}
_ => 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
+
+ match self.expect1(TokenKind::Concat) {
+ Some(operator) => {
+ if self.can_match_content() {
+ let right = self.content();
+ let parent = self.graph.add_node(Node::Concat(Concat {
+ range: Range::new(self.graph[left].start(), self.graph[right].end()),
+ operator,
+ }));
+ self.graph.add_edge(parent, left, ());
+ self.graph.add_edge(parent, right, ());
+ parent
+ } else {
+ let parent = self.graph.add_node(Node::Concat(Concat {
+ range: Range::new(self.graph[left].start(), operator.end()),
+ operator,
+ }));
+ self.graph.add_edge(parent, left, ());
+ parent
+ }
+ }
+ None => left,
+ }
+ }
+
+ fn connect(&mut self, parent: NodeIndex, children: &[NodeIndex]) {
+ for child in children {
+ self.graph.add_edge(parent, *child, ());
}
}
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,
+ TokenKind::PreambleKind
+ | TokenKind::StringKind
+ | TokenKind::EntryKind
+ | TokenKind::Word
+ | TokenKind::Command
+ | TokenKind::Assign
+ | TokenKind::Comma
+ | TokenKind::Quote
+ | TokenKind::BeginBrace
+ | TokenKind::BeginParen
+ | TokenKind::EndParen => true,
+ TokenKind::Concat | TokenKind::EndBrace => false,
}
} else {
false
}
}
- fn expect1(&mut self, kind: BibtexTokenKind) -> Option<BibtexToken> {
+ fn expect1(&mut self, kind: TokenKind) -> Option<Token> {
if let Some(ref token) = self.tokens.peek() {
if token.kind == kind {
return self.tokens.next();
@@ -200,7 +382,7 @@ impl<I: Iterator<Item = BibtexToken>> BibtexParser<I> {
None
}
- fn expect2(&mut self, kind1: BibtexTokenKind, kind2: BibtexTokenKind) -> Option<BibtexToken> {
+ fn expect2(&mut self, kind1: TokenKind, kind2: TokenKind) -> Option<Token> {
if let Some(ref token) = self.tokens.peek() {
if token.kind == kind1 || token.kind == kind2 {
return self.tokens.next();
@@ -209,7 +391,7 @@ impl<I: Iterator<Item = BibtexToken>> BibtexParser<I> {
None
}
- fn next_of_kind(&mut self, kind: BibtexTokenKind) -> bool {
+ fn next_of_kind(&mut self, kind: TokenKind) -> bool {
if let Some(token) = self.tokens.peek() {
token.kind == kind
} else {
diff --git a/support/texlab/src/syntax/generic_ast.rs b/support/texlab/src/syntax/generic_ast.rs
new file mode 100644
index 0000000000..3311460c0a
--- /dev/null
+++ b/support/texlab/src/syntax/generic_ast.rs
@@ -0,0 +1,51 @@
+use serde::{Deserialize, Serialize};
+use std::ops::Index;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
+pub struct AstNodeIndex(usize);
+
+#[derive(Debug, PartialEq, Eq, Clone, Default)]
+pub struct Ast<T> {
+ nodes: Vec<T>,
+ edges: Vec<Vec<AstNodeIndex>>,
+}
+
+impl<T> Ast<T> {
+ pub fn new() -> Self {
+ Self {
+ nodes: Vec::new(),
+ edges: Vec::new(),
+ }
+ }
+
+ pub fn nodes(&self) -> Vec<AstNodeIndex> {
+ let mut nodes = Vec::new();
+ for i in 0..self.nodes.len() {
+ nodes.push(AstNodeIndex(i));
+ }
+ nodes
+ }
+
+ pub fn add_node(&mut self, value: T) -> AstNodeIndex {
+ let node = AstNodeIndex(self.nodes.len());
+ self.nodes.push(value);
+ self.edges.push(Vec::new());
+ node
+ }
+
+ pub fn add_edge(&mut self, parent: AstNodeIndex, child: AstNodeIndex) {
+ self.edges[parent.0].push(child);
+ }
+
+ pub fn children<'a>(&'a self, parent: AstNodeIndex) -> impl Iterator<Item = AstNodeIndex> + 'a {
+ self.edges[parent.0].iter().map(|child| *child)
+ }
+}
+
+impl<T> Index<AstNodeIndex> for Ast<T> {
+ type Output = T;
+
+ fn index(&self, index: AstNodeIndex) -> &Self::Output {
+ &self.nodes[index.0]
+ }
+}
diff --git a/support/texlab/src/syntax/language.rs b/support/texlab/src/syntax/lang_data.rs
index 3618a079fd..4ea7cc44da 100644
--- a/support/texlab/src/syntax/language.rs
+++ b/support/texlab/src/syntax/lang_data.rs
@@ -97,7 +97,7 @@ pub struct LatexIncludeCommand {
pub struct LatexCommandDefinitionCommand {
pub name: String,
pub definition_index: usize,
- pub argument_count_index: usize,
+ pub arg_count_index: usize,
pub implementation_index: usize,
}
@@ -226,6 +226,6 @@ impl LanguageData {
}
pub static LANGUAGE_DATA: Lazy<LanguageData> = Lazy::new(|| {
- const JSON: &str = include_str!("language.json");
+ const JSON: &str = include_str!("../../data/lang_data.json");
serde_json::from_str(JSON).expect("Failed to deserialize language.json")
});
diff --git a/support/texlab/src/syntax/language.json b/support/texlab/src/syntax/language.json
deleted file mode 100644
index 8ffcdd03e1..0000000000
--- a/support/texlab/src/syntax/language.json
+++ /dev/null
@@ -1,2213 +0,0 @@
-{
- "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/latex/analysis.rs b/support/texlab/src/syntax/latex/analysis.rs
new file mode 100644
index 0000000000..af8b756f31
--- /dev/null
+++ b/support/texlab/src/syntax/latex/analysis.rs
@@ -0,0 +1,913 @@
+use crate::{
+ protocol::{Options, Position, Range, RangeExt, Uri},
+ syntax::{generic_ast::AstNodeIndex, lang_data::*, latex::ast::*, text::SyntaxNode},
+ tex::Resolver,
+};
+use itertools::{iproduct, Itertools};
+use serde::{Deserialize, Serialize};
+use std::{borrow::Cow, ops::Deref, path::Path};
+
+#[derive(Debug, Clone)]
+pub struct SymbolTableParams<'a> {
+ pub tree: Tree,
+ pub uri: &'a Uri,
+ pub resolver: &'a Resolver,
+ pub options: &'a Options,
+ pub current_dir: &'a Path,
+}
+
+#[derive(Debug, Clone)]
+pub struct SymbolTable {
+ pub(crate) tree: Tree,
+ pub commands: Vec<AstNodeIndex>,
+ pub environments: Vec<Environment>,
+ pub is_standalone: bool,
+ pub includes: Vec<Include>,
+ pub imports: Vec<Import>,
+ pub components: Vec<String>,
+ pub citations: Vec<Citation>,
+ pub command_definitions: Vec<CommandDefinition>,
+ pub glossary_entries: Vec<GlossaryEntry>,
+ pub equations: Vec<Equation>,
+ pub inlines: Vec<Inline>,
+ pub math_operators: Vec<MathOperator>,
+ pub theorem_definitions: Vec<TheoremDefinition>,
+ pub sections: Vec<Section>,
+ pub labels: Vec<Label>,
+ pub label_numberings: Vec<LabelNumbering>,
+ pub captions: Vec<Caption>,
+ pub items: Vec<Item>,
+}
+
+impl SymbolTable {
+ pub fn analyze(params: SymbolTableParams) -> Self {
+ let SymbolTableParams {
+ tree,
+ uri,
+ resolver,
+ options,
+ current_dir,
+ } = params;
+
+ let commands: Vec<_> = tree.commands().collect();
+ let ctx = SymbolContext {
+ tree: &tree,
+ commands: &commands,
+ uri,
+ resolver,
+ options,
+ current_dir,
+ };
+
+ let mut environments = None;
+ let mut includes = None;
+ let mut imports = None;
+ let mut citations = None;
+ let mut command_definitions = None;
+ let mut glossary_entries = None;
+ let mut equations = None;
+ let mut inlines = None;
+ let mut math_operators = None;
+ let mut theorem_definitions = None;
+ let mut sections = None;
+ let mut labels = None;
+ let mut label_numberings = None;
+ let mut captions = None;
+ let mut items = None;
+
+ rayon::scope(|s| {
+ s.spawn(|_| environments = Some(Environment::parse(ctx)));
+ s.spawn(|_| includes = Some(Include::parse(ctx)));
+ s.spawn(|_| imports = Some(Import::parse(ctx)));
+ s.spawn(|_| citations = Some(Citation::parse(ctx)));
+ s.spawn(|_| command_definitions = Some(CommandDefinition::parse(ctx)));
+ s.spawn(|_| glossary_entries = Some(GlossaryEntry::parse(ctx)));
+ s.spawn(|_| equations = Some(Equation::parse(ctx)));
+ s.spawn(|_| inlines = Some(Inline::parse(ctx)));
+ s.spawn(|_| math_operators = Some(MathOperator::parse(ctx)));
+ s.spawn(|_| theorem_definitions = Some(TheoremDefinition::parse(ctx)));
+ s.spawn(|_| sections = Some(Section::parse(ctx)));
+ s.spawn(|_| labels = Some(Label::parse(ctx)));
+ s.spawn(|_| label_numberings = Some(LabelNumbering::parse(ctx)));
+ s.spawn(|_| captions = Some(Caption::parse(ctx)));
+ s.spawn(|_| items = Some(Item::parse(ctx)));
+ });
+
+ let is_standalone = environments
+ .as_ref()
+ .unwrap()
+ .iter()
+ .any(|env| env.is_root(&tree));
+
+ let components = includes
+ .as_ref()
+ .unwrap()
+ .iter()
+ .flat_map(|include| include.components(&tree))
+ .collect();
+
+ Self {
+ tree,
+ commands,
+ environments: environments.unwrap(),
+ is_standalone,
+ includes: includes.unwrap(),
+ imports: imports.unwrap(),
+ components,
+ citations: citations.unwrap(),
+ command_definitions: command_definitions.unwrap(),
+ glossary_entries: glossary_entries.unwrap(),
+ equations: equations.unwrap(),
+ inlines: inlines.unwrap(),
+ math_operators: math_operators.unwrap(),
+ theorem_definitions: theorem_definitions.unwrap(),
+ sections: sections.unwrap(),
+ labels: labels.unwrap(),
+ label_numberings: label_numberings.unwrap(),
+ captions: captions.unwrap(),
+ items: items.unwrap(),
+ }
+ }
+
+ pub fn is_direct_child(&self, env: Environment, pos: Position) -> bool {
+ env.range(&self.tree).contains(pos)
+ && !self
+ .environments
+ .iter()
+ .filter(|e| e.left.parent != env.left.parent)
+ .filter(|e| env.range(&self.tree).contains(e.range(&self.tree).start))
+ .any(|e| e.range(&self.tree).contains(pos))
+ }
+
+ pub fn is_enum_item(&self, enumeration: Environment, item: Item) -> bool {
+ let item_range = self.tree[item.parent].range();
+ enumeration.range(&self.tree).contains(item_range.start)
+ && !self
+ .environments
+ .iter()
+ .filter(|env| env.left.parent != enumeration.left.parent)
+ .filter(|env| env.left.is_enum(&self.tree))
+ .filter(|env| {
+ enumeration
+ .range(&self.tree)
+ .contains(env.range(&self.tree).start)
+ })
+ .any(|env| env.range(&self.tree).contains(item_range.start))
+ }
+
+ pub fn find_label_by_range(&self, range: Range) -> Option<&Label> {
+ self.labels
+ .iter()
+ .filter(|label| label.kind == LatexLabelKind::Definition)
+ .filter(|label| label.names(&self).len() == 1)
+ .find(|label| range.contains(self[label.parent].range().start))
+ }
+
+ pub fn find_label_by_environment(&self, env: Environment) -> Option<&Label> {
+ self.labels
+ .iter()
+ .filter(|label| label.kind == LatexLabelKind::Definition)
+ .filter(|label| label.names(&self.tree).len() == 1)
+ .find(|label| self.is_direct_child(env, self.tree[label.parent].start()))
+ }
+}
+
+impl Deref for SymbolTable {
+ type Target = Tree;
+
+ fn deref(&self) -> &Self::Target {
+ &self.tree
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct SymbolContext<'a> {
+ tree: &'a Tree,
+ commands: &'a [AstNodeIndex],
+ uri: &'a Uri,
+ resolver: &'a Resolver,
+ options: &'a Options,
+ current_dir: &'a Path,
+}
+
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+pub struct EnvironmentDelimiter {
+ pub parent: AstNodeIndex,
+}
+
+impl EnvironmentDelimiter {
+ pub fn name(self, tree: &Tree) -> Option<&Token> {
+ tree.extract_word(self.parent, GroupKind::Group, 0)
+ }
+
+ pub fn is_math(self, tree: &Tree) -> bool {
+ self.is_special(tree, LANGUAGE_DATA.math_environments.iter())
+ }
+
+ pub fn is_enum(self, tree: &Tree) -> bool {
+ self.is_special(tree, LANGUAGE_DATA.enum_environments.iter())
+ }
+
+ fn is_special<'a, I: Iterator<Item = &'a String>>(self, tree: &Tree, mut values: I) -> bool {
+ match self.name(tree) {
+ Some(name) => values.any(|env| env == name.text()),
+ None => false,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+pub struct Environment {
+ pub left: EnvironmentDelimiter,
+ pub right: EnvironmentDelimiter,
+}
+
+impl Environment {
+ pub fn is_root(self, tree: &Tree) -> bool {
+ self.left
+ .name(tree)
+ .iter()
+ .chain(self.right.name(tree).iter())
+ .any(|name| name.text() == "document")
+ }
+
+ pub fn range(self, tree: &Tree) -> Range {
+ let start = tree[self.left.parent].start();
+ let end = tree[self.right.parent].end();
+ Range::new(start, end)
+ }
+
+ fn parse(ctx: SymbolContext) -> Vec<Self> {
+ let mut stack = Vec::new();
+ let mut envs = Vec::new();
+ for parent in ctx.commands {
+ if let Some((delim, delim_cmd)) = Self::parse_delimiter(ctx.tree, *parent) {
+ if delim_cmd.name.text() == "\\begin" {
+ stack.push(delim);
+ } else if let Some(left) = stack.pop() {
+ envs.push(Self { left, right: delim });
+ }
+ }
+ }
+ envs
+ }
+
+ fn parse_delimiter(
+ tree: &Tree,
+ parent: AstNodeIndex,
+ ) -> Option<(EnvironmentDelimiter, &Command)> {
+ let cmd = tree.as_command(parent)?;
+ if cmd.name.text() != "\\begin" && cmd.name.text() != "\\end" {
+ return None;
+ }
+
+ let group = tree.extract_group(parent, GroupKind::Group, 0)?;
+ if tree.extract_word(parent, GroupKind::Group, 0).is_some()
+ || tree.children(group).next().is_none()
+ || tree.as_group(group)?.right.is_none()
+ {
+ Some((EnvironmentDelimiter { parent }, cmd))
+ } else {
+ None
+ }
+ }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Include {
+ pub parent: AstNodeIndex,
+ pub arg_index: usize,
+ pub kind: LatexIncludeKind,
+ pub all_targets: Vec<Vec<Uri>>,
+ pub include_extension: bool,
+}
+
+impl Include {
+ pub fn paths<'a>(&self, tree: &'a Tree) -> Vec<&'a Token> {
+ tree.extract_comma_separated_words(self.parent, GroupKind::Group, self.arg_index)
+ .unwrap()
+ }
+
+ pub fn components<'a>(&self, tree: &'a Tree) -> impl Iterator<Item = String> + 'a {
+ let kind = self.kind;
+ self.paths(tree)
+ .into_iter()
+ .filter_map(move |path| match kind {
+ LatexIncludeKind::Package => Some(format!("{}.sty", path.text())),
+ LatexIncludeKind::Class => Some(format!("{}.cls", path.text())),
+ LatexIncludeKind::Latex
+ | LatexIncludeKind::Bibliography
+ | LatexIncludeKind::Image
+ | LatexIncludeKind::Svg
+ | LatexIncludeKind::Pdf
+ | LatexIncludeKind::Everything => None,
+ })
+ }
+
+ fn parse(ctx: SymbolContext) -> Vec<Self> {
+ iproduct!(ctx.commands, LANGUAGE_DATA.include_commands.iter())
+ .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc))
+ .collect()
+ }
+
+ fn parse_single(
+ ctx: SymbolContext,
+ parent: AstNodeIndex,
+ desc: &LatexIncludeCommand,
+ ) -> Option<Self> {
+ let cmd = ctx.tree.as_command(parent)?;
+ if cmd.name.text() != desc.name {
+ return None;
+ }
+
+ let mut all_targets = Vec::new();
+ let paths = ctx
+ .tree
+ .extract_comma_separated_words(parent, GroupKind::Group, desc.index)?;
+ for path in paths {
+ let mut targets = Vec::new();
+ let base_url = base_url(ctx)?;
+ targets.push(base_url.join(path.text()).ok()?.into());
+
+ if let Some(extensions) = desc.kind.extensions() {
+ for extension in extensions {
+ let path = format!("{}.{}", path.text(), extension);
+ targets.push(base_url.join(&path).ok()?.into());
+ }
+ }
+
+ if let Some(target) = Self::resolve_distro_file(ctx, desc, path.text()) {
+ targets.push(target);
+ }
+ all_targets.push(targets);
+ }
+
+ let include = Self {
+ parent,
+ arg_index: desc.index,
+ kind: desc.kind,
+ all_targets,
+ include_extension: desc.include_extension,
+ };
+ Some(include)
+ }
+
+ fn resolve_distro_file(
+ ctx: SymbolContext,
+ desc: &LatexIncludeCommand,
+ name: &str,
+ ) -> Option<Uri> {
+ let mut path = ctx.resolver.files_by_name.get(name);
+ if let Some(extensions) = desc.kind.extensions() {
+ for extension in extensions {
+ path = path.or_else(|| {
+ let full_name = format!("{}.{}", name, extension);
+ ctx.resolver.files_by_name.get(&full_name)
+ });
+ }
+ }
+ path.and_then(|p| Uri::from_file_path(p).ok())
+ }
+}
+
+fn base_url(ctx: SymbolContext) -> Option<Uri> {
+ if let Some(root_directory) = ctx
+ .options
+ .latex
+ .as_ref()
+ .and_then(|opts| opts.root_directory.as_ref())
+ {
+ let file_name = ctx.uri.path_segments()?.last()?;
+ let path = ctx.current_dir.join(root_directory).join(file_name);
+ Uri::from_file_path(path).ok()
+ } else {
+ Some(ctx.uri.clone())
+ }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Import {
+ pub parent: AstNodeIndex,
+ pub targets: Vec<Uri>,
+}
+
+impl Import {
+ pub fn dir<'a>(&self, tree: &'a Tree) -> &'a Token {
+ tree.extract_word(self.parent, GroupKind::Group, 0).unwrap()
+ }
+
+ pub fn file<'a>(&self, tree: &'a Tree) -> &'a Token {
+ tree.extract_word(self.parent, GroupKind::Group, 1).unwrap()
+ }
+
+ fn parse(ctx: SymbolContext) -> Vec<Self> {
+ ctx.commands
+ .iter()
+ .filter_map(|parent| Self::parse_single(ctx, *parent))
+ .collect()
+ }
+
+ fn parse_single(ctx: SymbolContext, parent: AstNodeIndex) -> Option<Self> {
+ let cmd = ctx.tree.as_command(parent)?;
+ if cmd.name.text() != "\\import" && cmd.name.text() != "\\subimport" {
+ return None;
+ }
+
+ let dir = ctx.tree.extract_word(parent, GroupKind::Group, 0)?;
+ let file = ctx.tree.extract_word(parent, GroupKind::Group, 1)?;
+
+ let mut targets = Vec::new();
+ let base_url = base_url(ctx)?.join(dir.text()).ok()?;
+ targets.push(base_url.join(file.text()).ok()?.into());
+ targets.push(base_url.join(&format!("{}.tex", file.text())).ok()?.into());
+ Some(Self { parent, targets })
+ }
+}
+
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+pub struct Citation {
+ parent: AstNodeIndex,
+ arg_index: usize,
+}
+
+impl Citation {
+ pub fn keys(self, tree: &Tree) -> Vec<&Token> {
+ tree.extract_comma_separated_words(self.parent, GroupKind::Group, self.arg_index)
+ .unwrap()
+ }
+
+ fn parse(ctx: SymbolContext) -> Vec<Self> {
+ iproduct!(ctx.commands, LANGUAGE_DATA.citation_commands.iter())
+ .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc))
+ .collect()
+ }
+
+ fn parse_single(
+ ctx: SymbolContext,
+ parent: AstNodeIndex,
+ desc: &LatexCitationCommand,
+ ) -> Option<Self> {
+ let cmd = ctx.tree.as_command(parent)?;
+ if cmd.name.text() != desc.name {
+ return None;
+ }
+
+ ctx.tree
+ .extract_comma_separated_words(parent, GroupKind::Group, desc.index)?;
+
+ Some(Self {
+ parent,
+ arg_index: desc.index,
+ })
+ }
+}
+
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+pub struct CommandDefinition {
+ pub parent: AstNodeIndex,
+ pub definition: AstNodeIndex,
+ pub definition_index: usize,
+ pub implementation: AstNodeIndex,
+ pub implementation_index: usize,
+ pub arg_count_index: usize,
+}
+
+impl CommandDefinition {
+ pub fn definition_name(self, tree: &Tree) -> &str {
+ tree.as_command(self.definition).unwrap().name.text()
+ }
+
+ fn parse(ctx: SymbolContext) -> Vec<Self> {
+ let def = LANGUAGE_DATA.command_definition_commands.iter();
+ iproduct!(ctx.commands, def)
+ .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc))
+ .collect()
+ }
+
+ fn parse_single(
+ ctx: SymbolContext,
+ parent: AstNodeIndex,
+ desc: &LatexCommandDefinitionCommand,
+ ) -> Option<Self> {
+ let cmd = ctx.tree.as_command(parent)?;
+ if cmd.name.text() != desc.name {
+ return None;
+ }
+
+ let group_kind = GroupKind::Group;
+ let implementation =
+ ctx.tree
+ .extract_group(parent, group_kind, desc.implementation_index)?;
+
+ let def_group = ctx
+ .tree
+ .extract_group(parent, group_kind, desc.definition_index)?;
+
+ let mut def_children = ctx.tree.children(def_group);
+ let definition = def_children.next()?;
+ ctx.tree.as_command(definition)?;
+ Some(Self {
+ parent,
+ definition,
+ definition_index: desc.definition_index,
+ implementation,
+ implementation_index: desc.implementation_index,
+ arg_count_index: desc.arg_count_index,
+ })
+ }
+}
+
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+pub struct GlossaryEntry {
+ pub parent: AstNodeIndex,
+ pub label_index: usize,
+ pub kind: LatexGlossaryEntryKind,
+}
+
+impl GlossaryEntry {
+ pub fn label(self, tree: &Tree) -> &Token {
+ tree.extract_word(self.parent, GroupKind::Group, self.label_index)
+ .unwrap()
+ }
+
+ fn parse(ctx: SymbolContext) -> Vec<Self> {
+ let entry = LANGUAGE_DATA.glossary_entry_definition_commands.iter();
+ iproduct!(ctx.commands, entry)
+ .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc))
+ .collect()
+ }
+
+ fn parse_single(
+ ctx: SymbolContext,
+ parent: AstNodeIndex,
+ desc: &LatexGlossaryEntryDefinitionCommand,
+ ) -> Option<Self> {
+ let cmd = ctx.tree.as_command(parent)?;
+ if cmd.name.text() != desc.name {
+ return None;
+ }
+
+ ctx.tree
+ .extract_word(parent, GroupKind::Group, desc.label_index)?;
+
+ Some(Self {
+ parent,
+ label_index: desc.label_index,
+ kind: desc.kind,
+ })
+ }
+}
+
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+pub struct Equation {
+ pub left: AstNodeIndex,
+ pub right: AstNodeIndex,
+}
+
+impl Equation {
+ pub fn range(self, tree: &Tree) -> Range {
+ let start = tree[self.left].start();
+ let end = tree[self.right].end();
+ Range::new(start, end)
+ }
+
+ fn parse(ctx: SymbolContext) -> Vec<Self> {
+ let mut equations = Vec::new();
+ let mut left = None;
+ for node in ctx.commands {
+ let cmd = ctx.tree.as_command(*node).unwrap();
+ let name = cmd.name.text();
+ if name == "\\[" || name == "\\(" {
+ left = Some(node);
+ } else if name == "\\]" || name == "\\)" {
+ if let Some(begin) = left {
+ equations.push(Self {
+ left: *begin,
+ right: *node,
+ });
+ left = None;
+ }
+ }
+ }
+ equations
+ }
+}
+
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+pub struct Inline {
+ pub left: AstNodeIndex,
+ pub right: AstNodeIndex,
+}
+
+impl Inline {
+ pub fn range(self, tree: &Tree) -> Range {
+ let start = tree[self.left].start();
+ let end = tree[self.right].end();
+ Range::new(start, end)
+ }
+
+ fn parse(ctx: SymbolContext) -> Vec<Self> {
+ let mut inlines = Vec::new();
+ let mut left = None;
+ for node in ctx
+ .tree
+ .nodes()
+ .into_iter()
+ .filter(|node| ctx.tree.as_math(*node).is_some())
+ .sorted_by_key(|node| ctx.tree[*node].start())
+ {
+ if let Some(l) = left {
+ inlines.push(Inline {
+ left: l,
+ right: node,
+ });
+ left = None;
+ } else {
+ left = Some(node);
+ }
+ }
+ inlines
+ }
+}
+
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+pub struct MathOperator {
+ pub parent: AstNodeIndex,
+ pub definition: AstNodeIndex,
+ pub definition_index: usize,
+ pub implementation: AstNodeIndex,
+ pub implementation_index: usize,
+}
+
+impl MathOperator {
+ pub fn definition_name(self, tree: &Tree) -> &str {
+ tree.as_command(self.definition).unwrap().name.text()
+ }
+
+ fn parse(ctx: SymbolContext) -> Vec<Self> {
+ iproduct!(ctx.commands, LANGUAGE_DATA.math_operator_commands.iter())
+ .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc))
+ .collect()
+ }
+
+ fn parse_single(
+ ctx: SymbolContext,
+ parent: AstNodeIndex,
+ desc: &LatexMathOperatorCommand,
+ ) -> Option<Self> {
+ let cmd = ctx.tree.as_command(parent)?;
+ if cmd.name.text() != desc.name {
+ return None;
+ }
+
+ let group_kind = GroupKind::Group;
+ let def_group = ctx
+ .tree
+ .extract_group(parent, group_kind, desc.definition_index)?;
+ let implementation =
+ ctx.tree
+ .extract_group(parent, group_kind, desc.implementation_index)?;
+
+ let mut def_children = ctx.tree.children(def_group);
+ let definition = def_children.next()?;
+ Some(Self {
+ parent,
+ definition,
+ definition_index: desc.definition_index,
+ implementation,
+ implementation_index: desc.implementation_index,
+ })
+ }
+}
+
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+pub struct TheoremDefinition {
+ pub parent: AstNodeIndex,
+ pub arg_index: usize,
+}
+
+impl TheoremDefinition {
+ pub fn name(self, tree: &Tree) -> &Token {
+ tree.extract_word(self.parent, GroupKind::Group, self.arg_index)
+ .unwrap()
+ }
+
+ fn parse(ctx: SymbolContext) -> Vec<Self> {
+ let thm = LANGUAGE_DATA.theorem_definition_commands.iter();
+ iproduct!(ctx.commands, thm)
+ .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc))
+ .collect()
+ }
+
+ fn parse_single(
+ ctx: SymbolContext,
+ parent: AstNodeIndex,
+ desc: &LatexTheoremDefinitionCommand,
+ ) -> Option<Self> {
+ let cmd = ctx.tree.as_command(parent)?;
+ if cmd.name.text() != desc.name {
+ return None;
+ }
+
+ let group_kind = GroupKind::Group;
+ ctx.tree.extract_word(parent, group_kind, desc.index)?;
+
+ Some(Self {
+ parent,
+ arg_index: desc.index,
+ })
+ }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Section {
+ pub parent: AstNodeIndex,
+ pub arg_index: usize,
+ pub level: i32,
+ pub prefix: Cow<'static, str>,
+}
+
+impl Section {
+ pub fn print(&self, tree: &Tree) -> Option<String> {
+ tree.print_group_content(self.parent, GroupKind::Group, self.arg_index)
+ }
+
+ fn parse(ctx: SymbolContext) -> Vec<Self> {
+ iproduct!(ctx.commands, LANGUAGE_DATA.section_commands.iter())
+ .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc))
+ .collect()
+ }
+
+ fn parse_single(
+ ctx: SymbolContext,
+ parent: AstNodeIndex,
+ desc: &'static LatexSectionCommand,
+ ) -> Option<Self> {
+ let cmd = ctx.tree.as_command(parent)?;
+ if cmd.name.text() != desc.name {
+ return None;
+ }
+
+ let group_kind = GroupKind::Group;
+ ctx.tree.extract_group(parent, group_kind, desc.index)?;
+
+ Some(Self {
+ parent,
+ arg_index: desc.index,
+ level: desc.level,
+ prefix: Cow::from(&desc.prefix),
+ })
+ }
+}
+
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+pub struct Label {
+ pub parent: AstNodeIndex,
+ pub arg_index: usize,
+ pub kind: LatexLabelKind,
+}
+
+impl Label {
+ pub fn names(self, tree: &Tree) -> Vec<&Token> {
+ tree.extract_comma_separated_words(self.parent, GroupKind::Group, self.arg_index)
+ .unwrap()
+ }
+
+ fn parse(ctx: SymbolContext) -> Vec<Self> {
+ iproduct!(ctx.commands, LANGUAGE_DATA.label_commands.iter())
+ .filter_map(|(parent, desc)| Self::parse_single(ctx, *parent, desc))
+ .collect()
+ }
+
+ fn parse_single(
+ ctx: SymbolContext,
+ parent: AstNodeIndex,
+ desc: &LatexLabelCommand,
+ ) -> Option<Self> {
+ let cmd = ctx.tree.as_command(parent)?;
+ if cmd.name.text() != desc.name {
+ return None;
+ }
+
+ ctx.tree
+ .extract_comma_separated_words(parent, GroupKind::Group, desc.index)?;
+
+ Some(Self {
+ parent,
+ arg_index: desc.index,
+ kind: desc.kind,
+ })
+ }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct LabelNumbering {
+ pub parent: AstNodeIndex,
+ pub number: String,
+}
+
+impl LabelNumbering {
+ pub fn name<'a>(&self, tree: &'a Tree) -> &'a Token {
+ tree.extract_word(self.parent, GroupKind::Group, 0).unwrap()
+ }
+
+ fn parse(ctx: SymbolContext) -> Vec<Self> {
+ ctx.commands
+ .iter()
+ .filter_map(|parent| Self::parse_single(ctx, *parent))
+ .collect()
+ }
+
+ fn parse_single(ctx: SymbolContext, parent: AstNodeIndex) -> Option<Self> {
+ let cmd = ctx.tree.as_command(parent)?;
+ if cmd.name.text() != "\\newlabel" {
+ return None;
+ }
+
+ ctx.tree.extract_word(parent, GroupKind::Group, 0)?;
+
+ let arg = ctx.tree.extract_group(parent, GroupKind::Group, 1)?;
+ let mut analyzer = FirstText::default();
+ analyzer.visit(ctx.tree, arg);
+ Some(Self {
+ parent,
+ number: analyzer.text?,
+ })
+ }
+}
+
+#[derive(Debug, Default)]
+struct FirstText {
+ text: Option<String>,
+}
+
+impl Visitor for FirstText {
+ fn visit(&mut self, tree: &Tree, node: AstNodeIndex) {
+ if let Some(text) = tree.as_text(node) {
+ self.text = Some(text.words.iter().map(Token::text).join(" "));
+ }
+
+ if self.text.is_none() {
+ tree.walk(self, node);
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+pub struct Caption {
+ pub parent: AstNodeIndex,
+ pub arg_index: usize,
+}
+
+impl Caption {
+ pub fn print(self, tree: &Tree) -> Option<String> {
+ tree.print_group_content(self.parent, GroupKind::Group, self.arg_index)
+ }
+
+ fn parse(ctx: SymbolContext) -> Vec<Self> {
+ ctx.commands
+ .iter()
+ .flat_map(|parent| Self::parse_single(ctx, *parent))
+ .collect()
+ }
+
+ fn parse_single(ctx: SymbolContext, parent: AstNodeIndex) -> Option<Self> {
+ let cmd = ctx.tree.as_command(parent)?;
+ if cmd.name.text() != "\\caption" {
+ return None;
+ }
+
+ ctx.tree.extract_group(parent, GroupKind::Group, 0)?;
+ Some(Self {
+ parent,
+ arg_index: 0,
+ })
+ }
+}
+
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+pub struct Item {
+ pub parent: AstNodeIndex,
+}
+
+impl Item {
+ pub fn name(self, tree: &Tree) -> Option<String> {
+ tree.print_group_content(self.parent, GroupKind::Options, 0)
+ }
+
+ fn parse(ctx: SymbolContext) -> Vec<Self> {
+ ctx.commands
+ .iter()
+ .filter_map(|parent| Self::parse_single(ctx, *parent))
+ .collect()
+ }
+
+ fn parse_single(ctx: SymbolContext, parent: AstNodeIndex) -> Option<Self> {
+ let cmd = ctx.tree.as_command(parent)?;
+ if cmd.name.text() != "\\item" {
+ return None;
+ }
+
+ Some(Self { parent })
+ }
+}
diff --git a/support/texlab/src/syntax/latex/ast.rs b/support/texlab/src/syntax/latex/ast.rs
index 0af980b595..ab7c6863e1 100644
--- a/support/texlab/src/syntax/latex/ast.rs
+++ b/support/texlab/src/syntax/latex/ast.rs
@@ -1,11 +1,15 @@
-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 {
+use crate::{
+ protocol::{Position, Range, RangeExt},
+ syntax::{
+ generic_ast::{Ast, AstNodeIndex},
+ text::{Span, SyntaxNode},
+ },
+};
+use serde::{Deserialize, Serialize};
+use std::ops::Deref;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
+pub enum TokenKind {
Word,
Command,
Math,
@@ -16,14 +20,14 @@ pub enum LatexTokenKind {
EndOptions,
}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexToken {
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Token {
pub span: Span,
- pub kind: LatexTokenKind,
+ pub kind: TokenKind,
}
-impl LatexToken {
- pub fn new(span: Span, kind: LatexTokenKind) -> Self {
+impl Token {
+ pub fn new(span: Span, kind: TokenKind) -> Self {
Self { span, kind }
}
@@ -32,314 +36,379 @@ impl LatexToken {
}
}
-impl SyntaxNode for LatexToken {
+impl SyntaxNode for Token {
fn range(&self) -> Range {
self.span.range()
}
}
-#[derive(Debug, PartialEq, Eq, Clone, Default)]
-pub struct LatexRoot {
- pub children: Vec<LatexContent>,
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
+pub struct Root {
+ pub range: Range,
}
-impl LatexRoot {
- pub fn new(children: Vec<LatexContent>) -> Self {
- Self { children }
+impl SyntaxNode for Root {
+ fn range(&self) -> Range {
+ self.range
}
}
-impl SyntaxNode for LatexRoot {
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
+pub enum GroupKind {
+ Group,
+ Options,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Group {
+ pub range: Range,
+ pub left: Token,
+ pub right: Option<Token>,
+ pub kind: GroupKind,
+}
+
+impl SyntaxNode for Group {
fn range(&self) -> Range {
- 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(),
- )
- }
+ self.range
}
}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub enum LatexContent {
- Group(Arc<LatexGroup>),
- Command(Arc<LatexCommand>),
- Text(Arc<LatexText>),
- Comma(Arc<LatexComma>),
- Math(Arc<LatexMath>),
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Command {
+ pub range: Range,
+ pub name: Token,
}
-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 Command {
+ pub fn short_name_range(&self) -> Range {
+ Range::new_simple(
+ self.name.start().line,
+ self.name.start().character + 1,
+ self.name.end().line,
+ self.name.end().character,
+ )
}
}
-impl SyntaxNode for LatexContent {
+impl SyntaxNode for Command {
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(),
- }
+ self.range
}
}
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum LatexGroupKind {
- Group,
- Options,
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Text {
+ pub range: Range,
+ pub words: Vec<Token>,
}
-#[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 SyntaxNode for Text {
+ fn range(&self) -> Range {
+ self.range
+ }
}
-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()
- };
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Comma {
+ pub range: Range,
+ pub token: Token,
+}
- Self {
- range: Range::new(left.start(), end),
- left,
- children,
- right,
- kind,
- }
+impl SyntaxNode for Comma {
+ fn range(&self) -> Range {
+ self.range
}
}
-impl SyntaxNode for LatexGroup {
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Math {
+ pub range: Range,
+ pub token: Token,
+}
+
+impl SyntaxNode for Math {
fn range(&self) -> Range {
self.range
}
}
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub enum Node {
+ Root(Root),
+ Group(Group),
+ Command(Command),
+ Text(Text),
+ Comma(Comma),
+ Math(Math),
+}
+
+impl SyntaxNode for Node {
+ fn range(&self) -> Range {
+ match self {
+ Self::Root(root) => root.range(),
+ Self::Group(group) => group.range(),
+ Self::Command(cmd) => cmd.range(),
+ Self::Text(text) => text.range(),
+ Self::Comma(comma) => comma.range(),
+ Self::Math(math) => math.range(),
+ }
+ }
+}
+
#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexCommand {
- pub range: Range,
- pub name: LatexToken,
- pub options: Vec<Arc<LatexGroup>>,
- pub args: Vec<Arc<LatexGroup>>,
- pub groups: Vec<Arc<LatexGroup>>,
+pub struct Tree {
+ pub inner: Ast<Node>,
+ pub root: AstNodeIndex,
}
-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()
- };
+impl Deref for Tree {
+ type Target = Ast<Node>;
- Self {
- range: Range::new(name.start(), end),
- name,
- options,
- args,
- groups,
+ fn deref(&self) -> &Self::Target {
+ &self.inner
+ }
+}
+
+impl Tree {
+ pub fn walk<V: Visitor>(&self, visitor: &mut V, parent: AstNodeIndex) {
+ for child in self.children(parent) {
+ visitor.visit(self, child);
}
}
- pub fn 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 find(&self, pos: Position) -> Vec<AstNodeIndex> {
+ let mut finder = Finder::new(pos);
+ finder.visit(self, self.root);
+ finder.results
}
- 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
- }
+ pub fn find_command_by_short_name_range(&self, pos: Position) -> Option<AstNodeIndex> {
+ self.find(pos).into_iter().find(|node| {
+ self.as_command(*node)
+ .filter(|cmd| {
+ cmd.name.range().contains(pos) && cmd.name.start().character != pos.character
+ })
+ .is_some()
+ })
+ }
+
+ pub fn print(&self, node: AstNodeIndex) -> String {
+ let start_position = self[node].start();
+ let mut printer = Printer::new(start_position);
+ printer.visit(self, node);
+ printer.output
+ }
+
+ pub fn commands<'a>(&'a self) -> impl Iterator<Item = AstNodeIndex> + 'a {
+ self.inner
+ .nodes()
+ .into_iter()
+ .filter(move |node| self.as_command(*node).is_some())
+ }
+
+ pub fn as_group(&self, node: AstNodeIndex) -> Option<&Group> {
+ if let Node::Group(group) = &self[node] {
+ Some(group)
} else {
None
}
}
- pub fn extract_word(&self, index: usize) -> Option<&LatexToken> {
- let text = self.extract_text(index)?;
- if text.words.len() == 1 {
- Some(&text.words[0])
+ pub fn as_command(&self, node: AstNodeIndex) -> Option<&Command> {
+ if let Node::Command(cmd) = &self[node] {
+ Some(cmd)
} 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);
- }
- }
+ pub fn as_text(&self, node: AstNodeIndex) -> Option<&Text> {
+ if let Node::Text(text) = &self[node] {
+ Some(text)
+ } else {
+ None
}
- 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;
- }
- }
+ pub fn as_math(&self, node: AstNodeIndex) -> Option<&Math> {
+ if let Node::Math(math) = &self[node] {
+ Some(math)
+ } else {
+ None
}
- true
}
-}
-impl SyntaxNode for LatexCommand {
- fn range(&self) -> Range {
- self.range
+ pub fn extract_group(
+ &self,
+ parent: AstNodeIndex,
+ group_kind: GroupKind,
+ index: usize,
+ ) -> Option<AstNodeIndex> {
+ self.children(parent)
+ .filter(|child| {
+ self.as_group(*child)
+ .filter(|group| group.kind == group_kind)
+ .is_some()
+ })
+ .nth(index)
}
-}
-#[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,
+ pub fn extract_text(
+ &self,
+ parent: AstNodeIndex,
+ group_kind: GroupKind,
+ index: usize,
+ ) -> Option<&Text> {
+ let group = self.extract_group(parent, group_kind, index)?;
+ let mut contents = self.children(group);
+ let text = self.as_text(contents.next()?);
+ if contents.next().is_none() {
+ text
+ } else {
+ None
}
}
-}
-impl SyntaxNode for LatexText {
- fn range(&self) -> Range {
- self.range
+ pub fn extract_word(
+ &self,
+ parent: AstNodeIndex,
+ group_kind: GroupKind,
+ index: usize,
+ ) -> Option<&Token> {
+ let text = self.extract_text(parent, group_kind, index)?;
+ if text.words.len() == 1 {
+ Some(&text.words[0])
+ } else {
+ None
+ }
}
-}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexComma {
- pub token: LatexToken,
-}
+ pub fn extract_comma_separated_words(
+ &self,
+ parent: AstNodeIndex,
+ group_kind: GroupKind,
+ index: usize,
+ ) -> Option<Vec<&Token>> {
+ let group = self.extract_group(parent, group_kind, index)?;
+ let mut words = Vec::new();
+ for child in self.children(group) {
+ match &self[child] {
+ Node::Root(_) | Node::Group(_) | Node::Command(_) | Node::Math(_) => return None,
+ Node::Text(text) => {
+ for word in &text.words {
+ words.push(word);
+ }
+ }
+ Node::Comma(_) => (),
+ }
+ }
+ Some(words)
+ }
-impl LatexComma {
- pub fn new(token: LatexToken) -> Self {
- Self { token }
+ pub fn print_group_content(
+ &self,
+ parent: AstNodeIndex,
+ group_kind: GroupKind,
+ index: usize,
+ ) -> Option<String> {
+ let arg = self.extract_group(parent, group_kind, index)?;
+ let text = self.print(arg);
+ self.as_group(arg)?.right.as_ref()?;
+ Some(text[1..text.len() - 1].trim().into())
}
}
-impl SyntaxNode for LatexComma {
- fn range(&self) -> Range {
- self.token.range()
- }
+pub trait Visitor {
+ fn visit(&mut self, tree: &Tree, node: AstNodeIndex);
}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexMath {
- pub token: LatexToken,
+#[derive(Debug)]
+struct Finder {
+ position: Position,
+ results: Vec<AstNodeIndex>,
}
-impl LatexMath {
- pub fn new(token: LatexToken) -> Self {
- Self { token }
+impl Finder {
+ fn new(position: Position) -> Self {
+ Self {
+ position,
+ results: Vec::new(),
+ }
}
}
-impl SyntaxNode for LatexMath {
- fn range(&self) -> Range {
- self.token.range()
+impl Visitor for Finder {
+ fn visit(&mut self, tree: &Tree, node: AstNodeIndex) {
+ if tree[node].range().contains(self.position) {
+ self.results.push(node);
+ tree.walk(self, node);
+ }
}
}
-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>);
+#[derive(Debug)]
+struct Printer {
+ output: String,
+ position: Position,
}
-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);
+impl Printer {
+ fn new(start_position: Position) -> Self {
+ Self {
+ output: String::new(),
+ position: start_position,
}
}
- pub fn walk_group<T: LatexVisitor>(visitor: &mut T, group: Arc<LatexGroup>) {
- for child in &group.children {
- child.accept(visitor);
+ fn synchronize(&mut self, position: Position) {
+ while self.position.line < position.line {
+ self.output.push('\n');
+ self.position.line += 1;
+ self.position.character = 0;
}
- }
- pub fn walk_command<T: LatexVisitor>(visitor: &mut T, command: Arc<LatexCommand>) {
- for arg in &command.groups {
- visitor.visit_group(Arc::clone(&arg));
+ while self.position.character < position.character {
+ self.output.push(' ');
+ self.position.character += 1;
}
- }
- pub fn walk_text<T: LatexVisitor>(_visitor: &mut T, _text: Arc<LatexText>) {}
+ assert_eq!(self.position, position);
+ }
- pub fn walk_comma<T: LatexVisitor>(_visitor: &mut T, _comma: Arc<LatexComma>) {}
+ fn print_token(&mut self, token: &Token) {
+ self.synchronize(token.start());
+ self.output.push_str(token.text());
+ self.position.character += token.end().character - token.start().character;
+ self.synchronize(token.end());
+ }
+}
- pub fn walk_math<T: LatexVisitor>(_visitor: &mut T, _math: Arc<LatexMath>) {}
+impl Visitor for Printer {
+ fn visit(&mut self, tree: &Tree, node: AstNodeIndex) {
+ match &tree[node] {
+ Node::Root(_) => tree.walk(self, node),
+ Node::Group(group) => {
+ self.print_token(&group.left);
+ tree.walk(self, node);
+ if let Some(right) = &group.right {
+ self.print_token(right);
+ }
+ }
+ Node::Command(cmd) => {
+ self.print_token(&cmd.name);
+ tree.walk(self, node);
+ }
+ Node::Text(text) => {
+ for word in &text.words {
+ self.print_token(word);
+ }
+ }
+ Node::Comma(comma) => {
+ self.print_token(&comma.token);
+ }
+ Node::Math(math) => {
+ self.print_token(&math.token);
+ }
+ }
+ }
}
diff --git a/support/texlab/src/syntax/latex/env.rs b/support/texlab/src/syntax/latex/env.rs
deleted file mode 100644
index 163db02ca8..0000000000
--- a/support/texlab/src/syntax/latex/env.rs
+++ /dev/null
@@ -1,123 +0,0 @@
-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
deleted file mode 100644
index b59388dc07..0000000000
--- a/support/texlab/src/syntax/latex/finder.rs
+++ /dev/null
@@ -1,74 +0,0 @@
-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
deleted file mode 100644
index 14f87ef130..0000000000
--- a/support/texlab/src/syntax/latex/glossary.rs
+++ /dev/null
@@ -1,58 +0,0 @@
-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
index f753d7afce..e14f1f6d85 100644
--- a/support/texlab/src/syntax/latex/lexer.rs
+++ b/support/texlab/src/syntax/latex/lexer.rs
@@ -1,40 +1,41 @@
-use super::ast::{LatexToken, LatexTokenKind};
+use super::ast::{Token, TokenKind};
use crate::syntax::text::CharStream;
-pub struct LatexLexer<'a> {
+#[derive(Debug)]
+pub struct Lexer<'a> {
stream: CharStream<'a>,
}
-impl<'a> LatexLexer<'a> {
+impl<'a> Lexer<'a> {
pub fn new(text: &'a str) -> Self {
- LatexLexer {
+ Self {
stream: CharStream::new(text),
}
}
- fn single_char(&mut self, kind: LatexTokenKind) -> LatexToken {
+ fn single_char(&mut self, kind: TokenKind) -> Token {
self.stream.start_span();
self.stream.next();
let span = self.stream.end_span();
- LatexToken::new(span, kind)
+ Token::new(span, kind)
}
- fn math(&mut self) -> LatexToken {
+ fn math(&mut self) -> Token {
self.stream.start_span();
self.stream.next();
if self.stream.satifies(|c| *c == '$') {
self.stream.next();
}
let span = self.stream.end_span();
- LatexToken::new(span, LatexTokenKind::Math)
+ Token::new(span, TokenKind::Math)
}
- fn command(&mut self) -> LatexToken {
+ fn command(&mut self) -> Token {
let span = self.stream.command();
- LatexToken::new(span, LatexTokenKind::Command)
+ Token::new(span, TokenKind::Command)
}
- fn word(&mut self) -> LatexToken {
+ fn word(&mut self) -> Token {
self.stream.start_span();
self.stream.next();
while self.stream.satifies(|c| is_word_char(*c)) {
@@ -42,36 +43,36 @@ impl<'a> LatexLexer<'a> {
}
let span = self.stream.end_span();
- LatexToken::new(span, LatexTokenKind::Word)
+ Token::new(span, TokenKind::Word)
}
}
-impl<'a> Iterator for LatexLexer<'a> {
- type Item = LatexToken;
+impl<'a> Iterator for Lexer<'a> {
+ type Item = Token;
- fn next(&mut self) -> Option<LatexToken> {
+ fn next(&mut self) -> Option<Token> {
loop {
match self.stream.peek() {
Some('%') => {
self.stream.skip_rest_of_line();
}
Some('{') => {
- return Some(self.single_char(LatexTokenKind::BeginGroup));
+ return Some(self.single_char(TokenKind::BeginGroup));
}
Some('}') => {
- return Some(self.single_char(LatexTokenKind::EndGroup));
+ return Some(self.single_char(TokenKind::EndGroup));
}
Some('[') => {
- return Some(self.single_char(LatexTokenKind::BeginOptions));
+ return Some(self.single_char(TokenKind::BeginOptions));
}
Some(']') => {
- return Some(self.single_char(LatexTokenKind::EndOptions));
+ return Some(self.single_char(TokenKind::EndOptions));
}
Some('$') => {
return Some(self.math());
}
Some(',') => {
- return Some(self.single_char(LatexTokenKind::Comma));
+ return Some(self.single_char(TokenKind::Comma));
}
Some('\\') => {
return Some(self.command());
@@ -106,74 +107,70 @@ fn is_word_char(c: char) -> bool {
#[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,
- ) {
+ use crate::{
+ protocol::{Position, Range},
+ syntax::text::Span,
+ };
+
+ fn verify<'a>(lexer: &mut Lexer<'a>, line: u64, character: u64, text: &str, kind: TokenKind) {
let start = Position::new(line, character);
let end = Position::new(line, character + text.chars().count() as u64);
let range = Range::new(start, end);
let span = Span::new(range, text.to_owned());
- let token = LatexToken::new(span, kind);
+ let token = Token::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);
+ fn word() {
+ let mut lexer = Lexer::new("foo bar baz");
+ verify(&mut lexer, 0, 0, "foo", TokenKind::Word);
+ verify(&mut lexer, 0, 4, "bar", TokenKind::Word);
+ verify(&mut lexer, 0, 8, "baz", TokenKind::Word);
assert_eq!(None, lexer.next());
}
#[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);
+ fn command() {
+ let mut lexer = Lexer::new("\\foo\\bar@baz\n\\foo*");
+ verify(&mut lexer, 0, 0, "\\foo", TokenKind::Command);
+ verify(&mut lexer, 0, 4, "\\bar@baz", TokenKind::Command);
+ verify(&mut lexer, 1, 0, "\\foo*", TokenKind::Command);
assert_eq!(None, lexer.next());
}
#[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);
+ fn escape_sequence() {
+ let mut lexer = Lexer::new("\\%\\**");
+ verify(&mut lexer, 0, 0, "\\%", TokenKind::Command);
+ verify(&mut lexer, 0, 2, "\\*", TokenKind::Command);
+ verify(&mut lexer, 0, 4, "*", TokenKind::Word);
assert_eq!(None, lexer.next());
}
#[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);
+ fn group_delimiter() {
+ let mut lexer = Lexer::new("{}[]");
+ verify(&mut lexer, 0, 0, "{", TokenKind::BeginGroup);
+ verify(&mut lexer, 0, 1, "}", TokenKind::EndGroup);
+ verify(&mut lexer, 0, 2, "[", TokenKind::BeginOptions);
+ verify(&mut lexer, 0, 3, "]", TokenKind::EndOptions);
assert_eq!(None, lexer.next());
}
#[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);
+ fn math() {
+ let mut lexer = Lexer::new("$$ $ $");
+ verify(&mut lexer, 0, 0, "$$", TokenKind::Math);
+ verify(&mut lexer, 0, 3, "$", TokenKind::Math);
+ verify(&mut lexer, 0, 5, "$", TokenKind::Math);
assert_eq!(None, lexer.next());
}
#[test]
- fn test_line_comment() {
- let mut lexer = LatexLexer::new(" %foo \nfoo");
- verify(&mut lexer, 1, 0, "foo", LatexTokenKind::Word);
+ fn line_comment() {
+ let mut lexer = Lexer::new(" %foo \nfoo");
+ verify(&mut lexer, 1, 0, "foo", TokenKind::Word);
assert_eq!(None, lexer.next());
}
}
diff --git a/support/texlab/src/syntax/latex/math.rs b/support/texlab/src/syntax/latex/math.rs
deleted file mode 100644
index c234232c14..0000000000
--- a/support/texlab/src/syntax/latex/math.rs
+++ /dev/null
@@ -1,199 +0,0 @@
-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
index dc24682752..cee83ab04e 100644
--- a/support/texlab/src/syntax/latex/mod.rs
+++ b/support/texlab/src/syntax/latex/mod.rs
@@ -1,358 +1,686 @@
+mod analysis;
mod ast;
-mod env;
-mod finder;
-mod glossary;
mod lexer;
-mod math;
mod parser;
-mod printer;
-mod structure;
-
-pub use self::ast::*;
-pub use self::env::*;
-pub use self::finder::LatexNode;
-pub use self::glossary::*;
-pub use self::math::*;
-pub use self::printer::LatexPrinter;
-pub use self::structure::*;
-
-use self::finder::LatexFinder;
-use self::lexer::LatexLexer;
-use self::parser::LatexParser;
-use super::language::*;
-use super::text::SyntaxNode;
-use crate::range::RangeExt;
-use crate::workspace::Uri;
-use lsp_types::{Position, Range};
-use path_clean::PathClean;
-use std::path::PathBuf;
-use std::sync::Arc;
-
-#[derive(Debug, Default)]
-struct LatexCommandAnalyzer {
- commands: Vec<Arc<LatexCommand>>,
+
+pub use self::{analysis::*, ast::*};
+
+use self::{lexer::Lexer, parser::Parser};
+use crate::{
+ protocol::{Options, Uri},
+ tex::Resolver,
+};
+use std::path::Path;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct OpenParams<'a> {
+ pub text: &'a str,
+ pub uri: &'a Uri,
+ pub resolver: &'a Resolver,
+ pub options: &'a Options,
+ pub current_dir: &'a Path,
}
-impl LatexCommandAnalyzer {
- fn parse(root: Arc<LatexRoot>) -> Vec<Arc<LatexCommand>> {
- let mut analyzer = Self::default();
- analyzer.visit_root(root);
- analyzer.commands
- }
+pub fn open(params: OpenParams) -> SymbolTable {
+ let OpenParams {
+ text,
+ uri,
+ resolver,
+ options,
+ current_dir,
+ } = params;
+
+ let lexer = Lexer::new(text);
+ let parser = Parser::new(lexer);
+ let tree = parser.parse();
+
+ let params = SymbolTableParams {
+ tree,
+ uri,
+ resolver,
+ options,
+ current_dir,
+ };
+ SymbolTable::analyze(params)
}
-impl LatexVisitor for LatexCommandAnalyzer {
- fn visit_root(&mut self, root: Arc<LatexRoot>) {
- LatexWalker::walk_root(self, root);
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{
+ protocol::{Options, Range, RangeExt, Uri},
+ syntax::{generic_ast::AstNodeIndex, text::SyntaxNode},
+ tex::Resolver,
+ };
+ use indoc::indoc;
+ use std::env;
+
+ fn open_simple(text: &str) -> SymbolTable {
+ open(OpenParams {
+ text: text.trim(),
+ uri: &Uri::parse("http://www.foo.com/bar.tex").unwrap(),
+ resolver: &Resolver::default(),
+ options: &Options::default(),
+ current_dir: &env::current_dir().unwrap(),
+ })
}
- fn visit_group(&mut self, group: Arc<LatexGroup>) {
- LatexWalker::walk_group(self, group);
+ #[derive(Debug, Default)]
+ struct TreeTraversal {
+ nodes: Vec<AstNodeIndex>,
}
- fn visit_command(&mut self, command: Arc<LatexCommand>) {
- self.commands.push(Arc::clone(&command));
- LatexWalker::walk_command(self, command);
+ impl Visitor for TreeTraversal {
+ fn visit(&mut self, tree: &Tree, node: AstNodeIndex) {
+ self.nodes.push(node);
+ tree.walk(self, node);
+ }
}
- fn visit_text(&mut self, text: Arc<LatexText>) {
- LatexWalker::walk_text(self, text);
- }
+ mod range {
+ use super::*;
- fn visit_comma(&mut self, comma: Arc<LatexComma>) {
- LatexWalker::walk_comma(self, comma);
- }
+ fn verify(expected_ranges: Vec<Range>, text: &str) {
+ let table = open_simple(text);
- fn visit_math(&mut self, math: Arc<LatexMath>) {
- LatexWalker::walk_math(self, math);
- }
-}
+ let mut traversal = TreeTraversal::default();
+ traversal.visit(&table.tree, table.tree.root);
+ let actual_ranges: Vec<_> = traversal
+ .nodes
+ .into_iter()
+ .map(|node| table[node].range())
+ .collect();
+ assert_eq!(actual_ranges, expected_ranges);
+ }
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexCitation {
- pub command: Arc<LatexCommand>,
- pub index: usize,
-}
+ #[test]
+ fn command() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 2, 14),
+ Range::new_simple(0, 0, 0, 23),
+ Range::new_simple(0, 14, 0, 23),
+ Range::new_simple(0, 15, 0, 22),
+ Range::new_simple(1, 0, 1, 20),
+ Range::new_simple(1, 11, 1, 20),
+ Range::new_simple(1, 12, 1, 19),
+ Range::new_simple(2, 0, 2, 14),
+ Range::new_simple(2, 4, 2, 9),
+ Range::new_simple(2, 5, 2, 8),
+ Range::new_simple(2, 9, 2, 14),
+ Range::new_simple(2, 10, 2, 13),
+ ],
+ indoc!(
+ r#"
+ \documentclass{article}
+ \usepackage{amsmath}
+ \foo[bar]{baz}
+ "#
+ ),
+ );
+ }
-impl LatexCitation {
- pub fn keys(&self) -> Vec<&LatexToken> {
- self.command.extract_comma_separated_words(0)
- }
+ #[test]
+ fn text() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 11),
+ Range::new_simple(0, 0, 0, 11),
+ ],
+ indoc!(
+ r#"
+ foo bar baz
+ "#
+ ),
+ );
+ }
- fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> {
- let mut citations = Vec::new();
- for command in commands {
- for LatexCitationCommand { name, index } in &LANGUAGE_DATA.citation_commands {
- if command.name.text() == name && command.has_comma_separated_words(*index) {
- citations.push(Self {
- command: Arc::clone(command),
- index: *index,
- });
- }
- }
+ #[test]
+ fn text_bracket() {
+ verify(
+ vec![Range::new_simple(0, 0, 0, 5), Range::new_simple(0, 0, 0, 5)],
+ indoc!(
+ r#"
+ ]foo[
+ "#
+ ),
+ );
}
- citations
- }
-}
-impl SyntaxNode for LatexCitation {
- fn range(&self) -> Range {
- self.command.range()
- }
-}
+ #[test]
+ fn group() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 15),
+ Range::new_simple(0, 0, 0, 15),
+ Range::new_simple(0, 2, 0, 5),
+ Range::new_simple(0, 6, 0, 13),
+ Range::new_simple(0, 8, 0, 11),
+ ],
+ indoc!(
+ r#"
+ { foo { bar } }
+ "#
+ ),
+ );
+ }
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexInclude {
- pub command: Arc<LatexCommand>,
- pub index: usize,
- pub kind: LatexIncludeKind,
- pub all_targets: Vec<Vec<Uri>>,
- pub include_extension: bool,
-}
+ #[test]
+ fn group_incomplete() {
+ verify(
+ vec![Range::new_simple(0, 1, 0, 2), Range::new_simple(0, 1, 0, 2)],
+ indoc!(
+ r#"
+ }{
+ "#
+ ),
+ );
+ }
-impl LatexInclude {
- pub fn paths(&self) -> Vec<&LatexToken> {
- self.command.extract_comma_separated_words(self.index)
+ #[test]
+ fn math() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 9),
+ Range::new_simple(0, 0, 0, 1),
+ Range::new_simple(0, 2, 0, 7),
+ Range::new_simple(0, 8, 0, 9),
+ ],
+ indoc!(
+ r#"
+ $ x = 1 $
+ "#
+ ),
+ );
+ }
+
+ #[test]
+ fn comma() {
+ verify(
+ vec![
+ Range::new_simple(0, 0, 0, 8),
+ Range::new_simple(0, 0, 0, 3),
+ Range::new_simple(0, 3, 0, 4),
+ Range::new_simple(0, 5, 0, 8),
+ ],
+ indoc!(
+ r#"
+ foo, bar
+ "#
+ ),
+ );
+ }
}
- pub fn components(&self) -> Vec<String> {
- let mut components = Vec::new();
- for path in self.paths() {
- match self.kind {
- LatexIncludeKind::Package => components.push(format!("{}.sty", path.text())),
- LatexIncludeKind::Class => components.push(format!("{}.cls", path.text())),
- LatexIncludeKind::Latex
- | LatexIncludeKind::Bibliography
- | LatexIncludeKind::Image
- | LatexIncludeKind::Svg
- | LatexIncludeKind::Pdf
- | LatexIncludeKind::Everything => (),
- }
+ mod command {
+ use super::*;
+
+ fn verify(expected_names: Vec<&str>, text: &str) {
+ let table = open(OpenParams {
+ text,
+ uri: &Uri::parse("http://www.foo.com/bar.tex").unwrap(),
+ resolver: &Resolver::default(),
+ options: &Options::default(),
+ current_dir: &env::current_dir().unwrap(),
+ });
+
+ let actual_names: Vec<_> = table
+ .commands
+ .iter()
+ .map(|node| table.tree.as_command(*node).unwrap().name.text())
+ .collect();
+
+ assert_eq!(actual_names, expected_names);
+ }
+
+ #[test]
+ fn basic() {
+ verify(
+ vec!["\\documentclass", "\\usepackage", "\\begin", "\\end"],
+ indoc!(
+ r#"
+ \documentclass{article}
+ \usepackage{amsmath}
+ \begin{document}
+ Hello World
+ \end{document}
+ "#
+ ),
+ );
+ }
+
+ #[test]
+ fn star() {
+ verify(
+ vec!["\\section*", "\\subsection*"],
+ indoc!(
+ r#"
+ \section*{Foo}
+ \subsection**{Bar}
+ "#
+ ),
+ );
+ }
+
+ #[test]
+ fn at() {
+ verify(vec!["\\foo@bar"], indoc!(r#"\foo@bar"#));
+ }
+
+ #[test]
+ fn escape() {
+ verify(vec!["\\%"], indoc!(r#"\%foo"#))
}
- components
}
- fn parse(uri: &Uri, commands: &[Arc<LatexCommand>]) -> Vec<Self> {
- let mut includes = Vec::new();
- for command in commands {
- for description in &LANGUAGE_DATA.include_commands {
- if let Some(include) = Self::parse_single(uri, &command, &description) {
- includes.push(include);
- }
- }
+ mod environment {
+ use super::*;
+
+ fn verify(expected_names: Vec<(&str, &str)>, text: &str) {
+ let table = open_simple(text);
+ let actual_names: Vec<_> = table
+ .environments
+ .iter()
+ .map(|env| {
+ (
+ env.left
+ .name(&table.tree)
+ .map(Token::text)
+ .unwrap_or_default(),
+ env.right
+ .name(&table.tree)
+ .map(Token::text)
+ .unwrap_or_default(),
+ )
+ })
+ .collect();
+
+ assert_eq!(actual_names, expected_names);
+ }
+
+ #[test]
+ fn nested() {
+ verify(
+ vec![("b", "b"), ("a", "a")],
+ indoc!(
+ r#"
+ \begin{a}
+ \begin{b}
+ \end{b}
+ \end{a}
+ "#
+ ),
+ );
+ }
+
+ #[test]
+ fn empty_name() {
+ verify(
+ vec![("a", ""), ("", "b")],
+ indoc!(
+ r#"
+ \begin{a}
+ \end{}
+ \begin{}
+ \end{b}
+ "#
+ ),
+ );
+ }
+
+ #[test]
+ fn incomplete() {
+ verify(
+ Vec::new(),
+ indoc!(
+ r#"
+ \end{a}
+ \begin{a}
+ "#
+ ),
+ );
+ }
+
+ #[test]
+ fn standalone_true() {
+ let table = open_simple(r#"\begin{document}\end{document}"#);
+ assert!(table.is_standalone);
+ }
+
+ #[test]
+ fn standalone_false() {
+ let table = open_simple(r#"\begin{doc}\end{doc}"#);
+ assert!(!table.is_standalone);
}
- includes
}
- fn parse_single(
- uri: &Uri,
- command: &Arc<LatexCommand>,
- description: &LatexIncludeCommand,
- ) -> Option<Self> {
- if command.name.text() != description.name {
- return None;
+ mod include {
+ use super::*;
+
+ fn verify(expected_targets: Vec<Vec<&str>>, resolver: Resolver, text: &str) {
+ let table = open(OpenParams {
+ text,
+ uri: &Uri::parse("http://www.foo.com/dir1/dir2/foo.tex").unwrap(),
+ resolver: &resolver,
+ options: &Options::default(),
+ current_dir: &env::current_dir().unwrap(),
+ });
+
+ assert_eq!(table.includes.len(), 1);
+ let include = &table.includes[0];
+ let actual_targets: Vec<Vec<&str>> = include
+ .all_targets
+ .iter()
+ .map(|targets| targets.iter().map(|target| target.as_str()).collect())
+ .collect();
+
+ assert_eq!(actual_targets, expected_targets);
+ }
+
+ #[test]
+ fn same_directory() {
+ verify(
+ vec![vec![
+ "http://www.foo.com/dir1/dir2/bar",
+ "http://www.foo.com/dir1/dir2/bar.tex",
+ ]],
+ Resolver::default(),
+ indoc!(r#"\include{bar}"#),
+ );
+ }
+
+ #[test]
+ fn two_paths() {
+ verify(
+ vec![
+ vec![
+ "http://www.foo.com/dir1/dir2/bar.tex",
+ "http://www.foo.com/dir1/dir2/bar.tex.tex",
+ ],
+ vec![
+ "http://www.foo.com/dir1/dir2/baz.tex",
+ "http://www.foo.com/dir1/dir2/baz.tex.tex",
+ ],
+ ],
+ Resolver::default(),
+ indoc!(r#"\input{bar.tex, ./baz.tex}"#),
+ );
+ }
+
+ #[test]
+ fn sub_directory() {
+ verify(
+ vec![vec![
+ "http://www.foo.com/dir1/dir2/dir3/bar",
+ "http://www.foo.com/dir1/dir2/dir3/bar.tex",
+ ]],
+ Resolver::default(),
+ indoc!(r#"\include{dir3/bar}"#),
+ );
}
- if command.args.len() <= description.index {
- return None;
+ #[test]
+ fn parent_directory() {
+ verify(
+ vec![vec![
+ "http://www.foo.com/dir1/bar",
+ "http://www.foo.com/dir1/bar.tex",
+ ]],
+ Resolver::default(),
+ indoc!(r#"\include{../bar}"#),
+ );
}
- let mut all_targets = Vec::new();
- for relative_path in command.extract_comma_separated_words(description.index) {
- let mut path = uri.to_file_path().ok()?;
- path.pop();
- path.push(relative_path.text());
- path = PathBuf::from(path.to_string_lossy().into_owned().replace('\\', "/"));
- path = path.clean();
- let path = path.to_str()?.to_owned();
-
- let mut targets = Vec::new();
- targets.push(Uri::from_file_path(&path).ok()?);
- if let Some(extensions) = description.kind.extensions() {
- for extension in extensions {
- let path = format!("{}.{}", &path, extension);
- targets.push(Uri::from_file_path(&path).ok()?);
- }
- }
- all_targets.push(targets);
+ #[test]
+ fn distro_file() {
+ let mut resolver = Resolver::default();
+ let path = env::current_dir().unwrap().join("biblatex-examples.bib");
+ resolver
+ .files_by_name
+ .insert("biblatex-examples.bib".into(), path.clone());
+ verify(
+ vec![vec![
+ "http://www.foo.com/dir1/dir2/biblatex-examples.bib",
+ "http://www.foo.com/dir1/dir2/biblatex-examples.bib.bib",
+ Uri::from_file_path(&path).unwrap().as_str(),
+ ]],
+ resolver,
+ indoc!(r#"\addbibresource{biblatex-examples.bib}"#),
+ );
}
- let include = Self {
- command: Arc::clone(command),
- index: description.index,
- kind: description.kind,
- all_targets,
- include_extension: description.include_extension,
- };
- Some(include)
+ #[test]
+ fn component() {
+ let table = open(OpenParams {
+ text: indoc!(
+ r#"
+ \documentclass{article}
+ \usepackage{amsmath}
+ \usepackage{geometry, lipsum}
+ "#
+ ),
+ uri: &Uri::parse("http://www.foo.com/bar.tex").unwrap(),
+ resolver: &Resolver::default(),
+ options: &Options::default(),
+ current_dir: &env::current_dir().unwrap(),
+ });
+ assert_eq!(
+ table.components,
+ vec!["article.cls", "amsmath.sty", "geometry.sty", "lipsum.sty"]
+ );
+ }
}
-}
-impl SyntaxNode for LatexInclude {
- fn range(&self) -> Range {
- self.command.range()
+ #[test]
+ fn citation() {
+ let table = open_simple(indoc!(
+ r#"
+ \cite{key1}
+ \cite{key2, key3}
+ \nocite{*}
+ "#
+ ));
+
+ let expected_keys = vec![vec!["key1"], vec!["key2", "key3"], vec!["*"]];
+
+ let actual_keys: Vec<Vec<&str>> = table
+ .citations
+ .iter()
+ .map(|cit| cit.keys(&table.tree).into_iter().map(Token::text).collect())
+ .collect();
+
+ assert_eq!(actual_keys, expected_keys);
}
-}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexCommandDefinition {
- pub command: Arc<LatexCommand>,
- pub definition: Arc<LatexCommand>,
- pub definition_index: usize,
- pub implementation: Arc<LatexGroup>,
- pub implementation_index: usize,
- pub argument_count_index: usize,
-}
+ #[test]
+ fn command_definition() {
+ let table = open_simple(indoc!(
+ r#"
+ \newcommand{\foo}{Foo}
+ \newcommand[2]{\bar}{Bar}
+ \renewcommand{\baz}{Baz}
+ \qux
+ "#
+ ));
+
+ let expected_cmds = vec!["\\foo", "\\bar", "\\baz"];
+
+ let actual_cmds: Vec<&str> = table
+ .command_definitions
+ .iter()
+ .map(|def| def.definition_name(&table.tree))
+ .collect();
-impl LatexCommandDefinition {
- fn parse(commands: &[Arc<LatexCommand>]) -> Vec<Self> {
- let mut definitions = Vec::new();
- for command in commands {
- for LatexCommandDefinitionCommand {
- name,
- definition_index,
- argument_count_index,
- implementation_index,
- } in &LANGUAGE_DATA.command_definition_commands
- {
- if command.name.text() == name
- && command.args.len() > *definition_index
- && command.args.len() > *implementation_index
- {
- let definition = command.args[0].children.iter().next();
- if let Some(LatexContent::Command(definition)) = definition {
- definitions.push(Self {
- command: Arc::clone(command),
- definition: Arc::clone(definition),
- definition_index: *definition_index,
- implementation: Arc::clone(&command.args[*implementation_index]),
- implementation_index: *implementation_index,
- argument_count_index: *argument_count_index,
- })
- }
- }
- }
- }
- definitions
+ assert_eq!(actual_cmds, expected_cmds);
}
-}
-impl SyntaxNode for LatexCommandDefinition {
- fn range(&self) -> Range {
- self.command.range()
+ #[test]
+ fn glossary_entry() {
+ let table = open_simple(indoc!(
+ r#"
+ \newglossaryentry{foo}{...}
+ \newacronym{bar}{...}
+ "#
+ ));
+
+ let expected_entries = vec!["foo", "bar"];
+
+ let actual_entries: Vec<&str> = table
+ .glossary_entries
+ .iter()
+ .map(|entry| entry.label(&table.tree).text())
+ .collect();
+
+ assert_eq!(actual_entries, expected_entries);
}
-}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexSyntaxTree {
- pub root: Arc<LatexRoot>,
- pub commands: Vec<Arc<LatexCommand>>,
- pub includes: Vec<LatexInclude>,
- pub components: Vec<String>,
- pub env: LatexEnvironmentInfo,
- pub structure: LatexStructureInfo,
- pub citations: Vec<LatexCitation>,
- pub math: LatexMathInfo,
- pub command_definitions: Vec<LatexCommandDefinition>,
- pub glossary: LatexGlossaryInfo,
-}
+ #[test]
+ fn equation() {
+ let table = open_simple(indoc!(
+ r#"
+ \[
+ e^{i \pi} + 1 = 0
+ \]
+ \] \[
+ "#
+ ));
+
+ assert_eq!(table.equations.len(), 1);
+ }
-impl LatexSyntaxTree {
- pub fn parse(uri: &Uri, text: &str) -> Self {
- let lexer = LatexLexer::new(text);
- let mut parser = LatexParser::new(lexer);
- let root = Arc::new(parser.root());
- let commands = LatexCommandAnalyzer::parse(Arc::clone(&root));
- let includes = LatexInclude::parse(uri, &commands);
- let components = includes.iter().flat_map(LatexInclude::components).collect();
- let env = LatexEnvironmentInfo::parse(&commands);
- let structure = LatexStructureInfo::parse(&commands);
- let citations = LatexCitation::parse(&commands);
- let math = LatexMathInfo::parse(Arc::clone(&root), &commands);
- let command_definitions = LatexCommandDefinition::parse(&commands);
- let glossary = LatexGlossaryInfo::parse(&commands);
- Self {
- root,
- commands,
- includes,
- components,
- env,
- structure,
- citations,
- math,
- command_definitions,
- glossary,
- }
+ #[test]
+ fn inline() {
+ let table = open_simple(indoc!(
+ r#"
+ $ x $
+ $
+ "#
+ ));
+
+ assert_eq!(table.inlines.len(), 1);
}
- pub fn find(&self, position: Position) -> Vec<LatexNode> {
- let mut finder = LatexFinder::new(position);
- finder.visit_root(Arc::clone(&self.root));
- finder.results
+ #[test]
+ fn math_operator() {
+ let table = open_simple(indoc!(
+ r#"
+ \DeclareMathOperator{\foo}{foo}
+ "#
+ ));
+
+ assert_eq!(table.math_operators.len(), 1);
+ assert_eq!(
+ table.math_operators[0].definition_name(&table.tree),
+ "\\foo"
+ );
}
- pub fn find_command_by_name(&self, position: Position) -> Option<Arc<LatexCommand>> {
- for result in self.find(position) {
- if let LatexNode::Command(command) = result {
- if command.name.range().contains(position)
- && command.name.start().character != position.character
- {
- return Some(command);
- }
- }
- }
- None
+ #[test]
+ fn theorem_definition() {
+ let table = open_simple(indoc!(
+ r#"
+ \newtheorem{lemma}{Lemma}
+ "#
+ ));
+
+ assert_eq!(table.theorem_definitions.len(), 1);
+ assert_eq!(
+ table.theorem_definitions[0].name(&table.tree).text(),
+ "lemma"
+ );
}
- pub fn find_label_by_range(&self, range: Range) -> Option<&LatexLabel> {
- self.structure
- .labels
- .iter()
- .filter(|label| label.kind == LatexLabelKind::Definition)
- .filter(|label| label.names().len() == 1)
- .find(|label| range.contains(label.start()))
+ #[test]
+ fn section() {
+ let table = open_simple(indoc!(
+ r#"
+ \section{Introduction to \LaTeX}
+ \subsection*{Foo
+ "#
+ ));
+ assert_eq!(table.sections.len(), 2);
+ assert_eq!(
+ table.sections[0].print(&table.tree).unwrap(),
+ "Introduction to \\LaTeX"
+ );
+ assert_eq!(table.sections[1].print(&table.tree), None);
}
- pub fn find_label_by_environment(&self, environment: &LatexEnvironment) -> Option<&LatexLabel> {
- self.structure
+ #[test]
+ fn label() {
+ let table = open_simple(indoc!(
+ r#"
+ \label{foo}
+ \ref{bar, baz}
+ "#
+ ));
+
+ let expected_names = vec![vec!["foo"], vec!["bar", "baz"]];
+
+ let actual_names: Vec<Vec<&str>> = table
.labels
.iter()
- .filter(|label| label.kind == LatexLabelKind::Definition)
- .filter(|label| label.names().len() == 1)
- .find(|label| self.is_direct_child(environment, label.start()))
+ .map(|label| {
+ label
+ .names(&table.tree)
+ .into_iter()
+ .map(Token::text)
+ .collect()
+ })
+ .collect();
+
+ assert_eq!(actual_names, expected_names);
}
- pub fn is_enumeration_item(&self, enumeration: &LatexEnvironment, item: &LatexItem) -> bool {
- enumeration.range().contains(item.start())
- && !self
- .env
- .environments
- .iter()
- .filter(|env| *env != enumeration)
- .filter(|env| env.left.is_enum() && enumeration.range().contains(env.start()))
- .any(|env| env.range().contains(item.start()))
+ #[test]
+ fn label_numbering() {
+ let table = open_simple(indoc!(
+ r#"
+ \newlabel{foo}{{1}{1}}
+ "#
+ ));
+
+ assert_eq!(table.label_numberings.len(), 1);
+ assert_eq!(table.label_numberings[0].name(&table.tree).text(), "foo");
+ assert_eq!(table.label_numberings[0].number, "1");
}
- pub fn is_direct_child(&self, environment: &LatexEnvironment, position: Position) -> bool {
- environment.range().contains(position)
- && !self
- .env
- .environments
- .iter()
- .filter(|env| *env != environment)
- .filter(|env| environment.range().contains(env.start()))
- .any(|env| env.range().contains(position))
+ #[test]
+ fn caption() {
+ let table = open_simple(indoc!(
+ r#"
+ \caption{Foo \LaTeX Bar}
+ "#
+ ));
+
+ assert_eq!(table.captions.len(), 1);
+ assert_eq!(
+ table.captions[0].print(&table.tree).unwrap(),
+ "Foo \\LaTeX Bar"
+ );
}
-}
-pub fn extract_group(content: &LatexGroup) -> String {
- if content.children.is_empty() || content.right.is_none() {
- return String::new();
+ #[test]
+ fn item_without_name() {
+ let table = open_simple(indoc!(
+ r#"
+ \item
+ "#
+ ));
+
+ assert_eq!(table.items.len(), 1);
+ assert_eq!(table.items[0].name(&table.tree), None);
}
- let mut printer = LatexPrinter::new(content.children[0].start());
- for child in &content.children {
- child.accept(&mut printer);
+ #[test]
+ fn item_with_name() {
+ let table = open_simple(indoc!(
+ r#"
+ \item[foo bar]
+ "#
+ ));
+
+ assert_eq!(table.items.len(), 1);
+ assert_eq!(table.items[0].name(&table.tree).unwrap(), "foo bar");
}
- printer.output
}
diff --git a/support/texlab/src/syntax/latex/parser.rs b/support/texlab/src/syntax/latex/parser.rs
index 40693071c2..4ace4fbcd6 100644
--- a/support/texlab/src/syntax/latex/parser.rs
+++ b/support/texlab/src/syntax/latex/parser.rs
@@ -1,62 +1,84 @@
use super::ast::*;
+use crate::{
+ protocol::{Range, RangeExt},
+ syntax::{
+ generic_ast::{Ast, AstNodeIndex},
+ text::SyntaxNode,
+ },
+};
use std::iter::Peekable;
-use std::sync::Arc;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-enum LatexScope {
+enum Scope {
Root,
Group,
Options,
}
-pub struct LatexParser<I: Iterator<Item = LatexToken>> {
+#[derive(Debug)]
+pub struct Parser<I: Iterator<Item = Token>> {
+ tree: Ast<Node>,
tokens: Peekable<I>,
}
-impl<I: Iterator<Item = LatexToken>> LatexParser<I> {
+impl<I: Iterator<Item = Token>> Parser<I> {
pub fn new(tokens: I) -> Self {
- LatexParser {
+ Self {
+ tree: Ast::new(),
tokens: tokens.peekable(),
}
}
- pub fn root(&mut self) -> LatexRoot {
- let children = self.content(LatexScope::Root);
- LatexRoot::new(children)
+ pub fn parse(mut self) -> Tree {
+ let children = self.content(Scope::Root);
+
+ let range = if children.is_empty() {
+ Range::new_simple(0, 0, 0, 0)
+ } else {
+ let start = self.tree[children[0]].start();
+ let end = self.tree[children[children.len() - 1]].end();
+ Range::new(start, end)
+ };
+
+ let root = self.tree.add_node(Node::Root(Root { range }));
+ self.connect(root, &children);
+ Tree {
+ inner: self.tree,
+ root,
+ }
}
- fn content(&mut self, scope: LatexScope) -> Vec<LatexContent> {
+ fn content(&mut self, scope: Scope) -> Vec<AstNodeIndex> {
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)));
+ TokenKind::Word | TokenKind::BeginOptions => {
+ children.push(self.text(scope));
}
- LatexTokenKind::Command => {
- children.push(LatexContent::Command(self.command()));
+ TokenKind::Command => {
+ children.push(self.command());
}
- LatexTokenKind::Comma => {
- let node = LatexComma::new(self.tokens.next().unwrap());
- children.push(LatexContent::Comma(Arc::new(node)));
+ TokenKind::Comma => {
+ children.push(self.comma());
}
- LatexTokenKind::Math => {
- children.push(LatexContent::Math(self.math()));
+ TokenKind::Math => {
+ children.push(self.math());
}
- LatexTokenKind::BeginGroup => {
- children.push(LatexContent::Group(self.group(LatexGroupKind::Group)));
+ TokenKind::BeginGroup => {
+ children.push(self.group(GroupKind::Group));
}
- LatexTokenKind::EndGroup => {
- if scope == LatexScope::Root {
+ TokenKind::EndGroup => {
+ if scope == Scope::Root {
self.tokens.next();
} else {
return children;
}
}
- LatexTokenKind::EndOptions => {
- if scope == LatexScope::Options {
+ TokenKind::EndOptions => {
+ if scope == Scope::Options {
return children;
} else {
- children.push(LatexContent::Text(self.text(scope)));
+ children.push(self.text(scope));
}
}
}
@@ -64,37 +86,17 @@ impl<I: Iterator<Item = LatexToken>> LatexParser<I> {
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> {
+ fn group(&mut self, kind: GroupKind) -> AstNodeIndex {
let left = self.tokens.next().unwrap();
let scope = match kind {
- LatexGroupKind::Group => LatexScope::Group,
- LatexGroupKind::Options => LatexScope::Options,
+ GroupKind::Group => Scope::Group,
+ GroupKind::Options => Scope::Options,
};
+
let children = self.content(scope);
let right_kind = match kind {
- LatexGroupKind::Group => LatexTokenKind::EndGroup,
- LatexGroupKind::Options => LatexTokenKind::EndOptions,
+ GroupKind::Group => TokenKind::EndGroup,
+ GroupKind::Options => TokenKind::EndOptions,
};
let right = if self.next_of_kind(right_kind) {
@@ -103,33 +105,82 @@ impl<I: Iterator<Item = LatexToken>> LatexParser<I> {
None
};
- Arc::new(LatexGroup::new(left, children, right, kind))
+ let end = right
+ .as_ref()
+ .map(SyntaxNode::end)
+ .or_else(|| children.last().map(|child| self.tree[*child].end()))
+ .unwrap_or_else(|| left.end());
+ let range = Range::new(left.start(), end);
+
+ let node = self.tree.add_node(Node::Group(Group {
+ range,
+ left,
+ kind,
+ right,
+ }));
+ self.connect(node, &children);
+ node
}
- fn text(&mut self, scope: LatexScope) -> Arc<LatexText> {
+ fn command(&mut self) -> AstNodeIndex {
+ let name = self.tokens.next().unwrap();
+ let mut children = Vec::new();
+ while let Some(token) = self.tokens.peek() {
+ match token.kind {
+ TokenKind::BeginGroup => children.push(self.group(GroupKind::Group)),
+ TokenKind::BeginOptions => children.push(self.group(GroupKind::Options)),
+ _ => break,
+ }
+ }
+
+ let end = children
+ .last()
+ .map(|child| self.tree[*child].end())
+ .unwrap_or_else(|| name.end());
+ let range = Range::new(name.start(), end);
+
+ let node = self.tree.add_node(Node::Command(Command { range, name }));
+ self.connect(node, &children);
+ node
+ }
+
+ fn text(&mut self, scope: Scope) -> AstNodeIndex {
let mut words = Vec::new();
while let Some(ref token) = self.tokens.peek() {
let kind = token.kind;
- let opts = kind == LatexTokenKind::EndOptions && scope != LatexScope::Options;
- if kind == LatexTokenKind::Word || kind == LatexTokenKind::BeginOptions || opts {
+ let opts = kind == TokenKind::EndOptions && scope != Scope::Options;
+ if kind == TokenKind::Word || kind == TokenKind::BeginOptions || opts {
words.push(self.tokens.next().unwrap());
} else {
break;
}
}
- Arc::new(LatexText::new(words))
+ let range = Range::new(words[0].start(), words[words.len() - 1].end());
+ self.tree.add_node(Node::Text(Text { range, words }))
}
- fn math(&mut self) -> Arc<LatexMath> {
+ fn comma(&mut self) -> AstNodeIndex {
let token = self.tokens.next().unwrap();
- Arc::new(LatexMath::new(token))
+ let range = token.range();
+ self.tree.add_node(Node::Comma(Comma { range, token }))
}
- fn next_of_kind(&mut self, kind: LatexTokenKind) -> bool {
- if let Some(ref token) = self.tokens.peek() {
- token.kind == kind
- } else {
- false
+ fn math(&mut self) -> AstNodeIndex {
+ let token = self.tokens.next().unwrap();
+ let range = token.range();
+ self.tree.add_node(Node::Math(Math { range, token }))
+ }
+
+ fn connect(&mut self, parent: AstNodeIndex, children: &[AstNodeIndex]) {
+ for child in children {
+ self.tree.add_edge(parent, *child);
}
}
+
+ fn next_of_kind(&mut self, kind: TokenKind) -> bool {
+ self.tokens
+ .peek()
+ .filter(|token| token.kind == kind)
+ .is_some()
+ }
}
diff --git a/support/texlab/src/syntax/latex/printer.rs b/support/texlab/src/syntax/latex/printer.rs
deleted file mode 100644
index ce03778033..0000000000
--- a/support/texlab/src/syntax/latex/printer.rs
+++ /dev/null
@@ -1,77 +0,0 @@
-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
deleted file mode 100644
index 79c9aac216..0000000000
--- a/support/texlab/src/syntax/latex/structure.rs
+++ /dev/null
@@ -1,250 +0,0 @@
-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/latexindent.rs b/support/texlab/src/syntax/latexindent.rs
new file mode 100644
index 0000000000..c45c95d4e5
--- /dev/null
+++ b/support/texlab/src/syntax/latexindent.rs
@@ -0,0 +1,21 @@
+use std::{io, process::Stdio};
+use tempfile::tempdir;
+use tokio::{fs, process::Command};
+
+pub async fn format(text: &str, extension: &str) -> io::Result<String> {
+ let dir = tempdir()?;
+ let input = format!("input.{}", extension);
+ let output = format!("output.{}", extension);
+ fs::write(dir.path().join(&input), text).await?;
+
+ Command::new("latexindent")
+ .args(&["-o", &output, &input])
+ .current_dir(dir.path())
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .stderr(Stdio::null())
+ .spawn()?
+ .await?;
+
+ fs::read_to_string(dir.path().join(&output)).await
+}
diff --git a/support/texlab/src/syntax/lsp_kind.rs b/support/texlab/src/syntax/lsp_kind.rs
new file mode 100644
index 0000000000..7d92fd4ee4
--- /dev/null
+++ b/support/texlab/src/syntax/lsp_kind.rs
@@ -0,0 +1,93 @@
+use super::lang_data::BibtexEntryTypeCategory;
+use crate::protocol::{CompletionItemKind, SymbolKind};
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum Structure {
+ Command,
+ Snippet,
+ Environment,
+ Section,
+ Float,
+ Theorem,
+ Equation,
+ Item,
+ Label,
+ Folder,
+ File,
+ PgfLibrary,
+ TikzLibrary,
+ Color,
+ ColorModel,
+ Package,
+ Class,
+ Entry(BibtexEntryTypeCategory),
+ Field,
+ Argument,
+ GlossaryEntry,
+}
+
+impl Structure {
+ pub fn completion_kind(self) -> CompletionItemKind {
+ match self {
+ Self::Command => CompletionItemKind::Function,
+ Self::Snippet => CompletionItemKind::Snippet,
+ Self::Environment => CompletionItemKind::Enum,
+ Self::Section => CompletionItemKind::Module,
+ Self::Float => CompletionItemKind::Method,
+ Self::Theorem => CompletionItemKind::Variable,
+ Self::Equation => CompletionItemKind::Constant,
+ Self::Item => CompletionItemKind::EnumMember,
+ Self::Label => CompletionItemKind::Constructor,
+ Self::Folder => CompletionItemKind::Folder,
+ Self::File => CompletionItemKind::File,
+ Self::PgfLibrary => CompletionItemKind::Property,
+ Self::TikzLibrary => CompletionItemKind::Property,
+ Self::Color => CompletionItemKind::Color,
+ Self::ColorModel => CompletionItemKind::Color,
+ Self::Package => CompletionItemKind::Class,
+ Self::Class => CompletionItemKind::Class,
+ Self::Entry(BibtexEntryTypeCategory::Misc) => CompletionItemKind::Interface,
+ Self::Entry(BibtexEntryTypeCategory::String) => CompletionItemKind::Text,
+ Self::Entry(BibtexEntryTypeCategory::Article) => CompletionItemKind::Event,
+ Self::Entry(BibtexEntryTypeCategory::Book) => CompletionItemKind::Struct,
+ Self::Entry(BibtexEntryTypeCategory::Collection) => CompletionItemKind::TypeParameter,
+ Self::Entry(BibtexEntryTypeCategory::Part) => CompletionItemKind::Operator,
+ Self::Entry(BibtexEntryTypeCategory::Thesis) => CompletionItemKind::Unit,
+ Self::Field => CompletionItemKind::Field,
+ Self::Argument => CompletionItemKind::Value,
+ Self::GlossaryEntry => CompletionItemKind::Keyword,
+ }
+ }
+
+ pub fn symbol_kind(self) -> SymbolKind {
+ match self {
+ Self::Command => SymbolKind::Function,
+ Self::Snippet => unimplemented!(),
+ Self::Environment => SymbolKind::Enum,
+ Self::Section => SymbolKind::Module,
+ Self::Float => SymbolKind::Method,
+ Self::Theorem => SymbolKind::Variable,
+ Self::Equation => SymbolKind::Constant,
+ Self::Item => SymbolKind::EnumMember,
+ Self::Label => SymbolKind::Constructor,
+ Self::Folder => SymbolKind::Namespace,
+ Self::File => SymbolKind::File,
+ Self::PgfLibrary => SymbolKind::Property,
+ Self::TikzLibrary => SymbolKind::Property,
+ Self::Color => unimplemented!(),
+ Self::ColorModel => unimplemented!(),
+ Self::Package => SymbolKind::Class,
+ Self::Class => SymbolKind::Class,
+ Self::Entry(BibtexEntryTypeCategory::Misc) => SymbolKind::Interface,
+ Self::Entry(BibtexEntryTypeCategory::String) => SymbolKind::String,
+ Self::Entry(BibtexEntryTypeCategory::Article) => SymbolKind::Event,
+ Self::Entry(BibtexEntryTypeCategory::Book) => SymbolKind::Struct,
+ Self::Entry(BibtexEntryTypeCategory::Collection) => SymbolKind::TypeParameter,
+ Self::Entry(BibtexEntryTypeCategory::Part) => SymbolKind::Operator,
+ Self::Entry(BibtexEntryTypeCategory::Thesis) => SymbolKind::Object,
+ Self::Field => SymbolKind::Field,
+ Self::Argument => SymbolKind::Number,
+ Self::GlossaryEntry => unimplemented!(),
+ }
+ }
+}
diff --git a/support/texlab/src/syntax/mod.rs b/support/texlab/src/syntax/mod.rs
index c92aa995f9..87b33792df 100644
--- a/support/texlab/src/syntax/mod.rs
+++ b/support/texlab/src/syntax/mod.rs
@@ -1,27 +1,14 @@
-mod bibtex;
-mod language;
-mod latex;
+pub mod bibtex;
+mod generic_ast;
+mod lang_data;
+pub mod latex;
+pub mod latexindent;
+mod lsp_kind;
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())),
- }
- }
-}
+pub use self::{
+ generic_ast::{Ast, AstNodeIndex},
+ lang_data::*,
+ lsp_kind::Structure,
+ text::{CharStream, Span, SyntaxNode},
+};
diff --git a/support/texlab/src/syntax/text.rs b/support/texlab/src/syntax/text.rs
index df38635940..17d6876126 100644
--- a/support/texlab/src/syntax/text.rs
+++ b/support/texlab/src/syntax/text.rs
@@ -1,6 +1,6 @@
-use lsp_types::{Position, Range};
-use std::iter::Peekable;
-use std::str::CharIndices;
+use crate::protocol::{Position, Range};
+use serde::{Deserialize, Serialize};
+use std::{iter::Peekable, str::CharIndices};
pub trait SyntaxNode {
fn range(&self) -> Range;
@@ -14,7 +14,8 @@ pub trait SyntaxNode {
}
}
-#[derive(Debug, PartialEq, Eq, Clone)]
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
pub struct Span {
pub range: Range,
pub text: String,
@@ -22,7 +23,7 @@ pub struct Span {
impl Span {
pub fn new(range: Range, text: String) -> Self {
- Span { range, text }
+ Self { range, text }
}
}
@@ -32,6 +33,7 @@ impl SyntaxNode for Span {
}
}
+#[derive(Debug)]
pub struct CharStream<'a> {
text: &'a str,
chars: Peekable<CharIndices<'a>>,
@@ -43,7 +45,7 @@ pub struct CharStream<'a> {
impl<'a> CharStream<'a> {
pub fn new(text: &'a str) -> Self {
- CharStream {
+ Self {
text,
chars: text.char_indices().peekable(),
current_position: Position::new(0, 0),
@@ -149,21 +151,13 @@ 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;
+ use crate::protocol::RangeExt;
#[test]
- fn test_peek() {
+ fn peek() {
let mut stream = CharStream::new("ab\nc");
assert_eq!(Some('a'), stream.peek());
assert_eq!(Some('a'), stream.next());
@@ -178,7 +172,7 @@ mod tests {
}
#[test]
- fn test_span() {
+ fn span() {
let mut stream = CharStream::new("abc\ndef");
stream.next();
stream.start_span();
@@ -194,7 +188,7 @@ mod tests {
}
#[test]
- fn test_span_unicode() {
+ fn span_unicode() {
let mut stream = CharStream::new("😀😃😄😁");
stream.next();
stream.start_span();
@@ -208,7 +202,7 @@ mod tests {
}
#[test]
- fn test_satifies() {
+ fn satifies() {
let mut stream = CharStream::new("aBc");
assert_eq!(true, stream.satifies(|c| c.is_lowercase()));
stream.next();
@@ -216,7 +210,7 @@ mod tests {
}
#[test]
- fn test_skip_rest_of_line() {
+ fn skip_rest_of_line() {
let mut stream = CharStream::new("abc\ndef");
stream.skip_rest_of_line();
assert_eq!(Some('d'), stream.next());
@@ -227,7 +221,7 @@ mod tests {
}
#[test]
- fn test_seek() {
+ fn seek() {
let mut stream = CharStream::new("abc\ndefghi");
let pos = Position::new(1, 2);
stream.seek(pos);
@@ -235,7 +229,7 @@ mod tests {
}
#[test]
- fn test_command_basic() {
+ fn command_basic() {
let mut stream = CharStream::new("\\foo@bar");
let span = stream.command();
assert_eq!(
@@ -245,7 +239,7 @@ mod tests {
}
#[test]
- fn test_command_star() {
+ fn command_star() {
let mut stream = CharStream::new("\\foo*");
let span = stream.command();
assert_eq!(
@@ -255,7 +249,7 @@ mod tests {
}
#[test]
- fn test_command_escape() {
+ fn command_escape() {
let mut stream = CharStream::new("\\**");
let span = stream.command();
assert_eq!(