latex-lsp/texlab

View on GitHub
crates/syntax/src/latex/cst.rs

Summary

Maintainability
Test Coverage
use itertools::{EitherOrBoth, Itertools};
use rowan::{ast::AstNode, TextRange};

use super::{
    LatexLanguage,
    SyntaxKind::{self, *},
    SyntaxNode, SyntaxToken,
};

pub fn small_range(node: &dyn AstNode<Language = LatexLanguage>) -> TextRange {
    let full_range = node.syntax().text_range();
    let start = full_range.start();
    let mut token = node.syntax().last_token();
    while let Some(current) = token {
        if !matches!(current.kind(), LINE_BREAK | WHITESPACE | COMMENT) {
            return TextRange::new(start, current.text_range().end());
        }
        token = current.prev_token();
    }

    TextRange::new(start, start)
}

macro_rules! cst_node {
    ($name:ident, $($kind:pat),+) => {
        #[derive(Clone)]
        #[repr(transparent)]
        pub struct $name(SyntaxNode);

        impl AstNode for $name {
            type Language = LatexLanguage;

            fn can_cast(kind: SyntaxKind) -> bool {
                match kind {
                    $($kind => true,)+
                    _ => false,
                }
            }

            fn cast(node: SyntaxNode) -> Option<Self>
            where
                Self: Sized,
            {
                match node.kind() {
                    $($kind => Some(Self(node)),)+
                    _ => None,
                }
            }

            fn syntax(&self) -> &SyntaxNode {
                &self.0
            }
        }
    };
}

cst_node!(Text, TEXT);

impl Text {
    pub fn words(&self) -> impl Iterator<Item = SyntaxToken> {
        self.syntax()
            .children_with_tokens()
            .filter_map(|node| node.into_token())
            .filter(|node| node.kind() == WORD)
    }
}

pub trait HasCurly: AstNode<Language = LatexLanguage> {
    fn left_curly(&self) -> Option<SyntaxToken> {
        self.syntax()
            .children_with_tokens()
            .filter_map(|node| node.into_token())
            .find(|node| node.kind() == L_CURLY)
    }

    fn right_curly(&self) -> Option<SyntaxToken> {
        self.syntax()
            .children_with_tokens()
            .filter_map(|node| node.into_token())
            .find(|node| node.kind() == R_CURLY)
    }

    fn content_text(&self) -> Option<String> {
        self.left_curly()?;
        self.right_curly()?;
        let mut text = String::new();
        for child in self
            .syntax()
            .descendants_with_tokens()
            .filter_map(|child| child.into_token())
            .filter(|token| !matches!(token.kind(), COMMENT))
        {
            text.push_str(child.text());
        }
        let text = text.trim_end();
        let text = text[1..text.len() - 1].trim().to_string();

        Some(text)
    }
}

cst_node!(CurlyGroup, CURLY_GROUP);

impl HasCurly for CurlyGroup {}

impl CurlyGroup {}

pub trait HasBrack: AstNode<Language = LatexLanguage> {
    fn left_brack(&self) -> Option<SyntaxToken> {
        self.syntax()
            .children_with_tokens()
            .filter_map(|node| node.into_token())
            .find(|node| node.kind() == L_BRACK)
    }

    fn right_brack(&self) -> Option<SyntaxToken> {
        self.syntax()
            .children_with_tokens()
            .filter_map(|node| node.into_token())
            .find(|node| node.kind() == R_BRACK)
    }

    fn content_text(&self) -> Option<String> {
        self.left_brack()?;
        self.right_brack()?;
        let mut text = String::new();
        for child in self
            .syntax()
            .descendants_with_tokens()
            .filter_map(|child| child.into_token())
            .filter(|token| !matches!(token.kind(), COMMENT))
        {
            text.push_str(child.text());
        }
        let text = text.trim_end();
        let text = text[1..text.len() - 1].trim().to_string();

        Some(text)
    }
}

cst_node!(BrackGroup, BRACK_GROUP);

impl BrackGroup {}

impl HasBrack for BrackGroup {}

pub trait HasParen: AstNode<Language = LatexLanguage> {
    fn left_paren(&self) -> Option<SyntaxToken> {
        self.syntax()
            .children_with_tokens()
            .filter_map(|node| node.into_token())
            .find(|node| node.kind() == L_PAREN)
    }

    fn right_paren(&self) -> Option<SyntaxToken> {
        self.syntax()
            .children_with_tokens()
            .filter_map(|node| node.into_token())
            .find(|node| node.kind() == R_PAREN)
    }
}

cst_node!(ParenGroup, PAREN_GROUP);

impl HasParen for ParenGroup {}

cst_node!(MixedGroup, MIXED_GROUP);

impl MixedGroup {
    pub fn left_delim(&self) -> Option<SyntaxToken> {
        self.syntax()
            .children_with_tokens()
            .filter_map(|node| node.into_token())
            .find(|node| matches!(node.kind(), L_BRACK | L_PAREN))
    }

    pub fn right_delim(&self) -> Option<SyntaxToken> {
        self.syntax()
            .children_with_tokens()
            .filter_map(|node| node.into_token())
            .find(|node| matches!(node.kind(), R_BRACK | R_PAREN))
    }
}

cst_node!(CurlyGroupWord, CURLY_GROUP_WORD);

impl HasCurly for CurlyGroupWord {}

impl CurlyGroupWord {
    pub fn key(&self) -> Option<Key> {
        self.syntax().children().find_map(Key::cast)
    }
}

cst_node!(BrackGroupWord, BRACK_GROUP_WORD);

impl HasBrack for BrackGroupWord {}

impl BrackGroupWord {
    pub fn key(&self) -> Option<Key> {
        self.syntax().children().find_map(Key::cast)
    }
}

cst_node!(CurlyGroupWordList, CURLY_GROUP_WORD_LIST);

impl HasCurly for CurlyGroupWordList {}

impl CurlyGroupWordList {
    pub fn keys(&self) -> impl Iterator<Item = Key> {
        self.syntax().children().filter_map(Key::cast)
    }
}

cst_node!(CurlyGroupCommand, CURLY_GROUP_COMMAND);

impl HasCurly for CurlyGroupCommand {}

impl CurlyGroupCommand {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax()
            .children_with_tokens()
            .filter_map(|node| node.into_token())
            .find(|node| node.kind() == COMMAND_NAME)
    }
}

cst_node!(Key, KEY);

impl Key {
    pub fn words(&self) -> impl Iterator<Item = SyntaxToken> {
        self.syntax()
            .children_with_tokens()
            .filter_map(|node| node.into_token())
            .filter(|node| !matches!(node.kind(), WHITESPACE | LINE_BREAK | COMMENT))
    }
}

impl PartialEq for Key {
    fn eq(&self, other: &Self) -> bool {
        self.words()
            .zip_longest(other.words())
            .all(|result| match result {
                EitherOrBoth::Both(left, right) => left.text() == right.text(),
                EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => false,
            })
    }
}

impl Eq for Key {}

impl ToString for Key {
    fn to_string(&self) -> String {
        let mut buf = String::new();
        for token in self
            .syntax()
            .children_with_tokens()
            .filter_map(|node| node.into_token())
        {
            if matches!(token.kind(), WHITESPACE | LINE_BREAK | COMMENT) {
                buf.push(' ');
            } else {
                buf.push_str(token.text());
            }
        }

        buf = String::from(buf.trim());
        buf
    }
}

cst_node!(Value, VALUE);

impl Value {
    pub fn text(&self) -> Option<String> {
        match CurlyGroup::cast(self.syntax().clone()) {
            Some(group) => group.content_text(),
            None => Some(self.syntax().text().to_string()),
        }
    }
}

cst_node!(KeyValuePair, KEY_VALUE_PAIR);

impl KeyValuePair {
    pub fn key(&self) -> Option<Key> {
        self.syntax().children().find_map(Key::cast)
    }

    pub fn value(&self) -> Option<Value> {
        self.syntax().children().find_map(Value::cast)
    }
}

cst_node!(KeyValueBody, KEY_VALUE_BODY);

impl KeyValueBody {
    pub fn pairs(&self) -> impl Iterator<Item = KeyValuePair> {
        self.syntax().children().filter_map(KeyValuePair::cast)
    }
}

pub trait HasKeyValueBody: AstNode<Language = LatexLanguage> {
    fn body(&self) -> Option<KeyValueBody> {
        self.syntax().children().find_map(KeyValueBody::cast)
    }
}

cst_node!(CurlyGroupKeyValue, CURLY_GROUP_KEY_VALUE);

impl HasCurly for CurlyGroupKeyValue {}

impl HasKeyValueBody for CurlyGroupKeyValue {}

cst_node!(BrackGroupKeyValue, BRACK_GROUP_KEY_VALUE);

impl HasBrack for BrackGroupKeyValue {}

impl HasKeyValueBody for BrackGroupKeyValue {}

cst_node!(Formula, FORMULA);

cst_node!(GenericCommand, GENERIC_COMMAND);

impl GenericCommand {
    pub fn name(&self) -> Option<SyntaxToken> {
        self.syntax()
            .children_with_tokens()
            .filter_map(|node| node.into_token())
            .find(|node| node.kind() == COMMAND_NAME)
    }
}

cst_node!(Equation, EQUATION);

cst_node!(Begin, BEGIN);

impl Begin {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn name(&self) -> Option<CurlyGroupWord> {
        self.syntax().children().find_map(CurlyGroupWord::cast)
    }

    pub fn options(&self) -> Option<BrackGroup> {
        self.syntax().children().find_map(BrackGroup::cast)
    }
}

cst_node!(End, END);

impl End {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn name(&self) -> Option<CurlyGroupWord> {
        self.syntax().children().find_map(CurlyGroupWord::cast)
    }
}

cst_node!(Environment, ENVIRONMENT);

impl Environment {
    pub fn begin(&self) -> Option<Begin> {
        self.syntax().children().find_map(Begin::cast)
    }

    pub fn end(&self) -> Option<End> {
        self.syntax().children().find_map(End::cast)
    }
}

cst_node!(
    Section,
    PART,
    CHAPTER,
    SECTION,
    SUBSECTION,
    SUBSUBSECTION,
    PARAGRAPH,
    SUBPARAGRAPH
);

impl Section {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn name(&self) -> Option<CurlyGroup> {
        self.syntax().children().find_map(CurlyGroup::cast)
    }
}

cst_node!(EnumItem, ENUM_ITEM);

impl EnumItem {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn label(&self) -> Option<BrackGroup> {
        self.syntax().children().find_map(BrackGroup::cast)
    }
}

cst_node!(Caption, CAPTION);

impl Caption {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn short(&self) -> Option<BrackGroup> {
        self.syntax().children().find_map(BrackGroup::cast)
    }

    pub fn long(&self) -> Option<CurlyGroup> {
        self.syntax().children().find_map(CurlyGroup::cast)
    }
}

cst_node!(Citation, CITATION);

impl Citation {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn prenote(&self) -> Option<BrackGroup> {
        self.syntax().children().find_map(BrackGroup::cast)
    }

    pub fn postnote(&self) -> Option<BrackGroup> {
        self.syntax().children().filter_map(BrackGroup::cast).nth(1)
    }

    pub fn key_list(&self) -> Option<CurlyGroupWordList> {
        self.syntax().children().find_map(CurlyGroupWordList::cast)
    }
}

cst_node!(
    Include,
    PACKAGE_INCLUDE,
    CLASS_INCLUDE,
    LATEX_INCLUDE,
    BIBLATEX_INCLUDE,
    BIBTEX_INCLUDE,
    GRAPHICS_INCLUDE,
    SVG_INCLUDE,
    INKSCAPE_INCLUDE,
    VERBATIM_INCLUDE
);

impl Include {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn path_list(&self) -> Option<CurlyGroupWordList> {
        self.syntax().children().find_map(CurlyGroupWordList::cast)
    }
}

cst_node!(Import, IMPORT);

impl Import {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn directory(&self) -> Option<CurlyGroupWord> {
        self.syntax().children().find_map(CurlyGroupWord::cast)
    }

    pub fn file(&self) -> Option<CurlyGroupWord> {
        self.syntax()
            .children()
            .filter_map(CurlyGroupWord::cast)
            .nth(1)
    }
}

cst_node!(LabelDefinition, LABEL_DEFINITION);

impl LabelDefinition {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn name(&self) -> Option<CurlyGroupWord> {
        self.syntax().children().find_map(CurlyGroupWord::cast)
    }
}

cst_node!(LabelReference, LABEL_REFERENCE);

impl LabelReference {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn name_list(&self) -> Option<CurlyGroupWordList> {
        self.syntax().children().find_map(CurlyGroupWordList::cast)
    }
}

cst_node!(LabelReferenceRange, LABEL_REFERENCE_RANGE);

impl LabelReferenceRange {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn from(&self) -> Option<CurlyGroupWord> {
        self.syntax().children().find_map(CurlyGroupWord::cast)
    }

    pub fn to(&self) -> Option<CurlyGroupWord> {
        self.syntax()
            .children()
            .filter_map(CurlyGroupWord::cast)
            .nth(1)
    }
}

cst_node!(LabelNumber, LABEL_NUMBER);

impl LabelNumber {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn name(&self) -> Option<CurlyGroupWord> {
        self.syntax().children().find_map(CurlyGroupWord::cast)
    }

    pub fn text(&self) -> Option<CurlyGroup> {
        self.syntax().children().find_map(CurlyGroup::cast)
    }
}

cst_node!(
    TheoremDefinition,
    THEOREM_DEFINITION_AMSTHM,
    THEOREM_DEFINITION_THMTOOLS
);

impl TheoremDefinition {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn names(&self) -> impl Iterator<Item = Key> {
        self.syntax()
            .children()
            .find_map(CurlyGroupWordList::cast)
            .into_iter()
            .flat_map(|group| group.keys())
            .chain(
                self.syntax()
                    .children()
                    .find_map(CurlyGroupWord::cast)
                    .into_iter()
                    .filter_map(|group| group.key()),
            )
    }

    pub fn heading(&self) -> Option<String> {
        if self.0.kind() == THEOREM_DEFINITION_THMTOOLS {
            let options = self
                .syntax()
                .children()
                .find_map(BrackGroupKeyValue::cast)
                .and_then(|group| group.body())?;

            options
                .pairs()
                .find(|pair| pair.key().map_or(false, |key| key.to_string() == "name"))
                .and_then(|pair| pair.value())
                .and_then(|name| name.text())
        } else {
            self.syntax()
                .children()
                .find_map(CurlyGroup::cast)
                .and_then(|group| group.content_text())
        }
    }
}

cst_node!(OldCommandDefinition, OLD_COMMAND_DEFINITION);

impl OldCommandDefinition {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn name(&self) -> Option<SyntaxToken> {
        self.syntax()
            .children_with_tokens()
            .skip(1)
            .filter_map(|elem| elem.into_token())
            .find(|token| token.kind() == COMMAND_NAME)
    }
}

cst_node!(NewCommandDefinition, NEW_COMMAND_DEFINITION, MATH_OPERATOR);

impl NewCommandDefinition {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn name(&self) -> Option<CurlyGroupCommand> {
        self.syntax().children().find_map(CurlyGroupCommand::cast)
    }

    pub fn implementation(&self) -> Option<CurlyGroup> {
        self.syntax().children().find_map(CurlyGroup::cast)
    }
}

cst_node!(AcronymReference, ACRONYM_REFERENCE);

impl AcronymReference {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }
}

cst_node!(AcronymDefinition, ACRONYM_DEFINITION);

impl AcronymDefinition {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn name(&self) -> Option<CurlyGroupWord> {
        self.syntax().children().find_map(CurlyGroupWord::cast)
    }
}

cst_node!(AcronymDeclaration, ACRONYM_DECLARATION);

impl AcronymDeclaration {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn name(&self) -> Option<CurlyGroupWord> {
        self.syntax().children().find_map(CurlyGroupWord::cast)
    }
}

cst_node!(ColorDefinition, COLOR_DEFINITION);

impl ColorDefinition {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn name(&self) -> Option<CurlyGroupWord> {
        self.syntax().children().find_map(CurlyGroupWord::cast)
    }

    pub fn model(&self) -> Option<CurlyGroupWord> {
        self.syntax()
            .children()
            .filter_map(CurlyGroupWord::cast)
            .nth(1)
    }

    pub fn spec(&self) -> Option<CurlyGroup> {
        self.syntax().children().find_map(CurlyGroup::cast)
    }
}

cst_node!(ColorSetDefinition, COLOR_SET_DEFINITION);

impl ColorSetDefinition {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn model_list(&self) -> Option<CurlyGroupWordList> {
        self.syntax().children().find_map(CurlyGroupWordList::cast)
    }
}

cst_node!(ColorReference, COLOR_REFERENCE);

impl ColorReference {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn name(&self) -> Option<CurlyGroupWord> {
        self.syntax().children().find_map(CurlyGroupWord::cast)
    }
}

cst_node!(GlossaryEntryReference, GLOSSARY_ENTRY_REFERENCE);

impl GlossaryEntryReference {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn name(&self) -> Option<CurlyGroupWord> {
        self.syntax().children().find_map(CurlyGroupWord::cast)
    }
}

cst_node!(GlossaryEntryDefinition, GLOSSARY_ENTRY_DEFINITION);

impl GlossaryEntryDefinition {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn name(&self) -> Option<CurlyGroupWord> {
        self.syntax().children().find_map(CurlyGroupWord::cast)
    }
}

cst_node!(TikzLibraryImport, TIKZ_LIBRARY_IMPORT);

impl TikzLibraryImport {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn name_list(&self) -> Option<CurlyGroupWordList> {
        self.syntax().children().find_map(CurlyGroupWordList::cast)
    }
}

cst_node!(GraphicsPath, GRAPHICS_PATH);

impl GraphicsPath {
    pub fn command(&self) -> Option<SyntaxToken> {
        self.syntax().first_token()
    }

    pub fn path_list(&self) -> impl Iterator<Item = CurlyGroupWord> {
        self.syntax().descendants().filter_map(CurlyGroupWord::cast)
    }
}