Initial commit: Rust support only

main
Charlotte Som 2022-04-28 13:45:47 +01:00
commit 57a7c79ca1
4 changed files with 140 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
Cargo.lock

14
Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "chroma-syntaxis"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
html-escape = "0.2.11"
once_cell = "1.10.0"
thiserror = "1.0.30"
tree-sitter = "0.20.6"
tree-sitter-highlight = "0.20.1"
tree-sitter-rust = "0.20.1"

27
src/languages.rs Normal file
View File

@ -0,0 +1,27 @@
use tree_sitter_highlight::HighlightConfiguration;
fn also_configure(mut config: HighlightConfiguration) -> (HighlightConfiguration, Vec<String>) {
let capture_names: Vec<_> = config
.query
.capture_names()
.iter()
.map(String::clone)
.collect();
config.configure(&capture_names);
(config, capture_names)
}
pub fn get_highlight_config(lang: &str) -> Option<(HighlightConfiguration, Vec<String>)> {
match lang {
"rust" => HighlightConfiguration::new(
tree_sitter_rust::language(),
tree_sitter_rust::HIGHLIGHT_QUERY,
"",
"",
)
.map(also_configure)
.ok(),
_ => None,
}
}

97
src/lib.rs Normal file
View File

@ -0,0 +1,97 @@
use html_escape::encode_text;
use tree_sitter_highlight::{HighlightEvent, Highlighter};
mod languages;
pub fn highlight(source: &str) -> String {
let source = source.as_bytes();
let mut highlighter = Highlighter::new();
let (highlight_config, capture_names) =
languages::get_highlight_config("rust").expect("Could not get Rust language config");
let items = highlighter
.highlight(&highlight_config, source, None, |lang| {
languages::get_highlight_config(lang).map(|x| /* ugh */ &*Box::leak(Box::new(x.0)))
})
.unwrap();
let mut highlight_attrs: Vec<&str> = Vec::new();
let mut out = String::new();
// Collapse adjacent identical attribute sets
let mut last_attrs: u64 = 0;
let mut span_is_open = false;
for item in items {
match item.unwrap() {
HighlightEvent::Source { start, end } => {
let source_section_bytes = &source[start..end];
let source_section = String::from_utf8_lossy(source_section_bytes);
let attr_hash = {
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
let mut hasher = DefaultHasher::new();
highlight_attrs.hash(&mut hasher);
hasher.finish()
};
if last_attrs != attr_hash {
if span_is_open {
out.push_str("</span>");
span_is_open = false;
}
if !highlight_attrs.is_empty() {
out.push_str("<span class=\"");
for (i, attr) in highlight_attrs.iter().enumerate() {
if i != 0 {
out.push(' ');
}
out.push_str(&attr.replace('.', " "));
}
out.push_str("\">");
span_is_open = true;
last_attrs = attr_hash;
}
}
out.push_str(&encode_text(&source_section));
}
HighlightEvent::HighlightStart(highlight) => {
let capture_name = &capture_names[highlight.0];
highlight_attrs.push(capture_name);
}
HighlightEvent::HighlightEnd => {
highlight_attrs.pop();
}
}
}
if span_is_open {
out.push_str("</span>");
}
out
}
#[cfg(test)]
mod tests {
use crate::highlight;
#[test]
fn highlight_rust() {
println!(
"{}",
highlight(
r#"fn main() {
println!("Hello, world!");
}"#
)
)
}
}