rustik-highlight 0.1.0

Rustik code highlighter.
Documentation
//! Grammar and theme registry.
//!
//! A registry is the lookup layer between user-facing names, file paths, first
//! lines, and the compiled grammars/themes used by the highlighter. It keeps
//! inference policy out of tokenization so callers can load syntaxes once and
//! cheaply select the right one for each buffer.

use std::path::Path;
use std::str::FromStr;

use crate::Error;
use crate::grammar::{Grammar, PLAIN_TEXT_NAME};
use crate::theme::Theme;

/// A reusable registry of compiled grammars and themes.
#[derive(Debug, Default)]
pub struct Registry {
    /// Registered grammars.
    pub grammars: Vec<Grammar>,
    /// Registered themes.
    pub themes: Vec<Theme>,
}

/// Inputs used to select a grammar from a registry.
#[derive(Clone, Copy, Debug, Default)]
pub struct GrammarQuery<'a> {
    /// Explicit syntax name, root scope, extension, or file-type alias.
    pub syntax: Option<&'a str>,
    /// Source path used for file-name and extension inference.
    pub path: Option<&'a Path>,
    /// First source line used for shebang and modeline inference.
    pub first_line: Option<&'a str>,
}

impl Registry {
    /// Creates an empty registry.
    pub fn new() -> Self {
        Self::default()
    }

    /// Creates an empty registry with a plain-text fallback grammar.
    pub fn with_plain_text() -> Self {
        let mut registry = Self::new();
        registry.add_grammar(Grammar::plain_text());
        registry
    }

    /// Creates an empty registry with the built-in fast grammars.
    pub fn with_builtin_syntaxes() -> Self {
        let mut registry = Self::with_plain_text();
        registry.add_grammar(Grammar::json());
        registry
    }

    /// Adds a compiled grammar.
    pub fn add_grammar(&mut self, grammar: Grammar) {
        self.grammars.push(grammar);
    }

    /// Adds the dedicated fast JSON grammar.
    pub fn add_json(&mut self) {
        self.add_grammar(Grammar::json());
    }

    /// Parses, compiles, and adds a grammar from JSON.
    pub fn add_grammar_json(&mut self, input: &str) -> Result<(), Error> {
        let grammar = Grammar::from_str(input)?;
        self.add_grammar(grammar);
        Ok(())
    }

    /// Adds a compiled theme.
    pub fn add_theme(&mut self, theme: Theme) {
        self.themes.push(theme);
    }

    /// Parses, compiles, and adds a theme from JSON.
    pub fn add_theme_json(&mut self, input: &str) -> Result<(), Error> {
        let theme = Theme::from_str(input)?;
        self.add_theme(theme);
        Ok(())
    }

    /// Finds a grammar by name, root scope, extension, or file-type alias.
    pub fn grammar_by_name(&self, name: &str) -> Result<&Grammar, Error> {
        self.grammars
            .iter()
            .find(|grammar| grammar.matches_name(name))
            .ok_or_else(|| Error::UnknownSyntax(name.to_owned()))
    }

    /// Finds a theme by name.
    pub fn theme_by_name(&self, name: &str) -> Result<&Theme, Error> {
        self.themes
            .iter()
            .find(|theme| theme.name.eq_ignore_ascii_case(name))
            .ok_or_else(|| Error::UnknownTheme(name.to_owned()))
    }

    /// Infers a grammar from explicit syntax, path, extension, and first line.
    pub fn query(&self, query: GrammarQuery<'_>) -> Result<&Grammar, Error> {
        if let Some(syntax) = query.syntax {
            return self.grammar_by_name(syntax);
        }
        if let Some(path) = query.path
            && let Some(grammar) = self
                .grammars
                .iter()
                .find(|grammar| grammar.matches_path(path))
        {
            return Ok(grammar);
        }
        if let Some(first_line) = query.first_line
            && let Some(grammar) = self
                .grammars
                .iter()
                .find(|grammar| grammar.matches_first_line(first_line))
        {
            return Ok(grammar);
        }
        self.grammar_by_name(PLAIN_TEXT_NAME)
    }
}