Acord/core/src/highlight.rs

253 lines
8.0 KiB
Rust

use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter};
use tree_sitter::Language;
/// Convert an old-API tree-sitter Language (v0.19/v0.20) to current v0.24.
/// Both are newtype wrappers around `*const TSLanguage`; the C ABI is identical.
unsafe fn lang_compat<T>(old: T) -> Language {
std::mem::transmute_copy(&old)
}
const HIGHLIGHT_NAMES: &[&str] = &[
"keyword",
"function",
"function.builtin",
"type",
"type.builtin",
"constructor",
"constant",
"constant.builtin",
"string",
"number",
"comment",
"variable",
"variable.builtin",
"variable.parameter",
"operator",
"punctuation",
"punctuation.bracket",
"punctuation.delimiter",
"property",
"tag",
"attribute",
"label",
"escape",
"embedded",
];
#[derive(serde::Serialize)]
pub struct HighlightSpan {
pub start: usize,
pub end: usize,
pub kind: u8,
}
struct LangDef {
language: Language,
highlights: &'static str,
injections: &'static str,
locals: &'static str,
}
fn lang_def(lang_id: &str) -> Option<LangDef> {
let ld = match lang_id {
"rust" => LangDef {
language: tree_sitter_rust::LANGUAGE.into(),
highlights: tree_sitter_rust::HIGHLIGHTS_QUERY,
injections: tree_sitter_rust::INJECTIONS_QUERY,
locals: "",
},
"c" => LangDef {
language: tree_sitter_c::LANGUAGE.into(),
highlights: tree_sitter_c::HIGHLIGHT_QUERY,
injections: "",
locals: "",
},
"cpp" => LangDef {
language: tree_sitter_cpp::LANGUAGE.into(),
highlights: tree_sitter_cpp::HIGHLIGHT_QUERY,
injections: "",
locals: "",
},
"javascript" | "jsx" => LangDef {
language: tree_sitter_javascript::LANGUAGE.into(),
highlights: tree_sitter_javascript::HIGHLIGHT_QUERY,
injections: tree_sitter_javascript::INJECTIONS_QUERY,
locals: tree_sitter_javascript::LOCALS_QUERY,
},
"typescript" => LangDef {
language: tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
highlights: tree_sitter_typescript::HIGHLIGHTS_QUERY,
injections: "",
locals: tree_sitter_typescript::LOCALS_QUERY,
},
"tsx" => LangDef {
language: tree_sitter_typescript::LANGUAGE_TSX.into(),
highlights: tree_sitter_typescript::HIGHLIGHTS_QUERY,
injections: "",
locals: tree_sitter_typescript::LOCALS_QUERY,
},
"python" => LangDef {
language: tree_sitter_python::LANGUAGE.into(),
highlights: tree_sitter_python::HIGHLIGHTS_QUERY,
injections: "",
locals: "",
},
"go" => LangDef {
language: tree_sitter_go::LANGUAGE.into(),
highlights: tree_sitter_go::HIGHLIGHTS_QUERY,
injections: "",
locals: "",
},
"ruby" => LangDef {
language: tree_sitter_ruby::LANGUAGE.into(),
highlights: tree_sitter_ruby::HIGHLIGHTS_QUERY,
injections: "",
locals: tree_sitter_ruby::LOCALS_QUERY,
},
"bash" | "shell" => LangDef {
language: tree_sitter_bash::LANGUAGE.into(),
highlights: tree_sitter_bash::HIGHLIGHT_QUERY,
injections: "",
locals: "",
},
"java" => LangDef {
language: tree_sitter_java::LANGUAGE.into(),
highlights: tree_sitter_java::HIGHLIGHTS_QUERY,
injections: "",
locals: "",
},
"html" => LangDef {
language: tree_sitter_html::LANGUAGE.into(),
highlights: tree_sitter_html::HIGHLIGHTS_QUERY,
injections: tree_sitter_html::INJECTIONS_QUERY,
locals: "",
},
"css" | "scss" | "less" => LangDef {
language: tree_sitter_css::LANGUAGE.into(),
highlights: tree_sitter_css::HIGHLIGHTS_QUERY,
injections: "",
locals: "",
},
"json" => LangDef {
language: tree_sitter_json::LANGUAGE.into(),
highlights: tree_sitter_json::HIGHLIGHTS_QUERY,
injections: "",
locals: "",
},
"lua" => LangDef {
language: tree_sitter_lua::LANGUAGE.into(),
highlights: tree_sitter_lua::HIGHLIGHTS_QUERY,
injections: tree_sitter_lua::INJECTIONS_QUERY,
locals: tree_sitter_lua::LOCALS_QUERY,
},
"php" => LangDef {
language: tree_sitter_php::LANGUAGE_PHP.into(),
highlights: tree_sitter_php::HIGHLIGHTS_QUERY,
injections: tree_sitter_php::INJECTIONS_QUERY,
locals: "",
},
"toml" => LangDef {
language: unsafe { lang_compat(tree_sitter_toml::language()) },
highlights: tree_sitter_toml::HIGHLIGHT_QUERY,
injections: "",
locals: "",
},
"yaml" => LangDef {
language: unsafe { lang_compat(tree_sitter_yaml::language()) },
highlights: tree_sitter_yaml::HIGHLIGHTS_QUERY,
injections: "",
locals: "",
},
"swift" => LangDef {
language: tree_sitter_swift::LANGUAGE.into(),
highlights: tree_sitter_swift::HIGHLIGHTS_QUERY,
injections: "",
locals: "",
},
"zig" => LangDef {
language: tree_sitter_zig::LANGUAGE.into(),
highlights: tree_sitter_zig::HIGHLIGHTS_QUERY,
injections: tree_sitter_zig::INJECTIONS_QUERY,
locals: "",
},
"sql" => LangDef {
language: unsafe { lang_compat(tree_sitter_sql::language()) },
highlights: include_str!("../queries/sql-highlights.scm"),
injections: "",
locals: "",
},
"make" | "makefile" => LangDef {
language: tree_sitter_make::LANGUAGE.into(),
highlights: tree_sitter_make::HIGHLIGHTS_QUERY,
injections: "",
locals: "",
},
"dockerfile" => LangDef {
language: unsafe { lang_compat(tree_sitter_dockerfile::language()) },
highlights: include_str!("../queries/dockerfile-highlights.scm"),
injections: "",
locals: "",
},
"kotlin" => LangDef {
language: unsafe { lang_compat(tree_sitter_kotlin::language()) },
highlights: include_str!("../queries/kotlin-highlights.scm"),
injections: "",
locals: "",
},
_ => return None,
};
Some(ld)
}
fn make_config(def: LangDef, name: &str) -> Option<HighlightConfiguration> {
let mut config = HighlightConfiguration::new(
def.language,
name,
def.highlights,
def.injections,
def.locals,
).ok()?;
config.configure(HIGHLIGHT_NAMES);
Some(config)
}
pub fn highlight_source(source: &str, lang_id: &str) -> Vec<HighlightSpan> {
let def = match lang_def(lang_id) {
Some(d) => d,
None => return Vec::new(),
};
let config = match make_config(def, lang_id) {
Some(c) => c,
None => return Vec::new(),
};
let mut highlighter = Highlighter::new();
let events = match highlighter.highlight(&config, source.as_bytes(), None, |_| None) {
Ok(e) => e,
Err(_) => return Vec::new(),
};
let mut spans = Vec::new();
let mut stack: Vec<u8> = Vec::new();
for event in events {
match event {
Ok(HighlightEvent::Source { start, end }) => {
if let Some(&kind) = stack.last() {
spans.push(HighlightSpan { start, end, kind });
}
}
Ok(HighlightEvent::HighlightStart(h)) => {
stack.push(h.0 as u8);
}
Ok(HighlightEvent::HighlightEnd) => {
stack.pop();
}
Err(_) => break,
}
}
spans
}