use html_escape::encode_text; use lazy_static::lazy_static; use std::collections::HashMap; use std::{env::args, fs::read_to_string}; use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter}; const HIGHLIGHT_NAMES: &[&str; 19] = &[ "attribute", "constant", "function.builtin", "function", "keyword", "operator", "property", "punctuation", "punctuation.bracket", "punctuation.delimiter", "string", "string.special", "tag", "type", "type.builtin", "comment", "variable", "variable.builtin", "variable.parameter", ]; lazy_static! { static ref FILETYPES: HashMap<&'static str, &'static str> = HashMap::from([ ("md", "markdown"), ("markdown", "markdown"), ("rs", "rust"), ("toml", "toml"), ("js", "javascript"), ("ts", "javascript"), ("html", "html"), ("vue", "html"), ("tera", "html"), ("css", "css"), ("c", "c"), ("cc", "c"), ("cpp", "cpp"), ("sh", "shells"), ("bash", "shells"), ("zsh", "shells"), ("lua", "lua"), ("py", "python"), ("yml", "yaml"), ("go", "go"), ("haskell", "haskell"), ("d", "d"), ("java", "java"), ("vim", "vim"), ]); static ref CONFIGS: HashMap<&'static str, HighlightConfiguration> = HashMap::from( [ ("vim", pepegsitter::vim::highlight()), ("rust", pepegsitter::rust::highlight()), ("toml", pepegsitter::toml::highlight()), ("javascript", pepegsitter::javascript::highlight()), ("typescript", pepegsitter::typescript::highlight()), ("html", pepegsitter::html::highlight()), ("css", pepegsitter::css::highlight()), ("c", pepegsitter::c::highlight()), ("cpp", pepegsitter::cpp::highlight()), ("shells", pepegsitter::bash::highlight()), ("shells", pepegsitter::bash::highlight()), ("lua", pepegsitter::lua::highlight()), ("python", pepegsitter::python::highlight()), ("yaml", pepegsitter::yaml::highlight()), ("go", pepegsitter::go::highlight()), ("haskell", pepegsitter::haskell::highlight()), ("d", pepegsitter::d::highlight()), ("java", pepegsitter::java::highlight()), ("markdown", pepegsitter::markdown::highlight()), ] .map(|(key, mut val)| { val.configure(HIGHLIGHT_NAMES); (key, val) }) ); } /// An example file highlighter supporting [CONFIGS] filetypes. Run eg on itself with : /// `cargo r --example=highlighter -- examples/highlighter.rs > highlighter.html` fn main() { let arguments: Vec<_> = args().into_iter().collect(); if arguments.len() != 2 { panic!("\nSyntax: highlighter text_file"); } let file_name = arguments[1].clone(); let text_content = read_to_string(&file_name).expect("readable file in text_file"); let mut highlighted_text = highlight(&file_name, &text_content); highlighted_text = format!( r#"
{highlighted_text}"# ); println!("{highlighted_text}"); } /// Highlight `text` using `filename`'s extension to guess content type. /// The output is some html using [HIGHLIGHT_NAMES] span classes. /// See [STYLE] for some basic styling. pub fn highlight(filename: &str, text: &str) -> String { let mut highlighter = Highlighter::new(); let extension = filename.split(".").last().unwrap(); let Some(filetype) = FILETYPES.get(extension) else { eprintln!( " > highlighting: unrecognized extension '{}' with file '{}'.", extension, filename ); return encode_text(&text).into_owned(); }; eprintln!(" > highlighting file {filename:?} with type {filetype:?}"); let highlights = highlighter .highlight( CONFIGS.get(filetype).unwrap(), text.as_bytes(), None, |injected| { eprintln!(" > highlighting injected content with type {injected:?}"); CONFIGS.get(injected) }, ) .unwrap(); let mut highlighted_text = String::new(); for event in highlights { match event.unwrap() { HighlightEvent::Source { start, end } => { highlighted_text = format!("{}{}", highlighted_text, encode_text(&text[start..end])); } HighlightEvent::HighlightStart(s) => { highlighted_text = format!( "{}", highlighted_text, HIGHLIGHT_NAMES[s.0].replace(".", " ") ); } HighlightEvent::HighlightEnd => { highlighted_text = format!("{}", highlighted_text); } } } highlighted_text } const STYLE: &str = r#" "#;