feat: load language grammars
This commit is contained in:
parent
d5ecc05345
commit
308dcf2911
129
src/syntax.rs
129
src/syntax.rs
@ -1,24 +1,143 @@
|
|||||||
use std::{path::PathBuf, sync::OnceLock};
|
use std::{borrow::Cow, collections::HashMap, error::Error, fs, path::Path, sync::OnceLock};
|
||||||
|
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
use tree_house::{
|
||||||
|
InjectionLanguageMarker, Language, LanguageConfig, LanguageLoader, highlighter::Highlight,
|
||||||
|
read_query,
|
||||||
|
};
|
||||||
|
use tree_house_bindings::Grammar;
|
||||||
use typst::foundations::{Func, NativeFunc, func};
|
use typst::foundations::{Func, NativeFunc, func};
|
||||||
|
|
||||||
use crate::AppConfig;
|
use crate::AppConfig;
|
||||||
|
|
||||||
static REGISTRY: OnceLock<LanguageRegistry> = OnceLock::new();
|
static REGISTRY: OnceLock<LanguageRegistry> = OnceLock::new();
|
||||||
|
|
||||||
struct LanguageRegistry {}
|
/// Class names used in highlighted code.
|
||||||
|
const HIGHLIGHT_NAMES: &[&str] = &[
|
||||||
|
"attribute",
|
||||||
|
"comment",
|
||||||
|
"comment.block",
|
||||||
|
"comment.line",
|
||||||
|
"constant",
|
||||||
|
"constant.builtin",
|
||||||
|
"constructor",
|
||||||
|
"function",
|
||||||
|
"function.builtin",
|
||||||
|
"function.macro",
|
||||||
|
"function.method",
|
||||||
|
"keyword",
|
||||||
|
"keyword.control",
|
||||||
|
"keyword.operator",
|
||||||
|
"label",
|
||||||
|
"namespace",
|
||||||
|
"number",
|
||||||
|
"operator",
|
||||||
|
"property",
|
||||||
|
"punctuation",
|
||||||
|
"punctuation.bracket",
|
||||||
|
"punctuation.delimiter",
|
||||||
|
"special",
|
||||||
|
"string",
|
||||||
|
"string.special",
|
||||||
|
"tag",
|
||||||
|
"type",
|
||||||
|
"type.builtin",
|
||||||
|
"variable",
|
||||||
|
"variable.builtin",
|
||||||
|
"variable.parameter",
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Get the longest matching highlight name for a given scope.
|
||||||
|
fn highlight_for_scope(scope: &str) -> Option<Highlight> {
|
||||||
|
let mut candidate = scope;
|
||||||
|
loop {
|
||||||
|
if let Some(i) = HIGHLIGHT_NAMES.iter().position(|&n| n == candidate) {
|
||||||
|
return Some(Highlight::new(i as u32));
|
||||||
|
}
|
||||||
|
match candidate.rfind('.') {
|
||||||
|
Some(dot) => candidate = &candidate[..dot],
|
||||||
|
None => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LanguageRegistry {
|
||||||
|
by_name: HashMap<String, Language>,
|
||||||
|
configs: Vec<LanguageConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
impl LanguageRegistry {
|
impl LanguageRegistry {
|
||||||
fn new(syntax_root: &PathBuf) -> Self {
|
fn new(syntax_root: &Path) -> Self {
|
||||||
Self {}
|
let mut by_name = HashMap::new();
|
||||||
|
let mut configs = Vec::new();
|
||||||
|
|
||||||
|
for entry in fs::read_dir(syntax_root).into_iter().flatten().flatten() {
|
||||||
|
let name = entry.file_name().to_string_lossy().into_owned();
|
||||||
|
let so_path = entry.path().join(format!("{name}.so"));
|
||||||
|
if !so_path.exists() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match load_language(&name, &entry.path(), syntax_root) {
|
||||||
|
Ok(config) => {
|
||||||
|
by_name.insert(name, Language::new(configs.len() as u32));
|
||||||
|
configs.push(config);
|
||||||
|
}
|
||||||
|
Err(e) => warn!("Failed to load grammar {name}: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Loaded {} syntax highlighting grammars", configs.len());
|
||||||
|
Self { by_name, configs }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_language(
|
||||||
|
name: &str,
|
||||||
|
lang_dir: &Path,
|
||||||
|
syntax_root: &Path,
|
||||||
|
) -> Result<LanguageConfig, Box<dyn Error>> {
|
||||||
|
let grammar = unsafe { Grammar::new(name, &lang_dir.join(format!("{name}.so")))? };
|
||||||
|
|
||||||
|
let read = |kind: &str| {
|
||||||
|
read_query(name, |lang| {
|
||||||
|
fs::read_to_string(syntax_root.join(lang).join(format!("{kind}.scm")))
|
||||||
|
.unwrap_or_default()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = LanguageConfig::new(
|
||||||
|
grammar,
|
||||||
|
&read("highlights"),
|
||||||
|
&read("injections"),
|
||||||
|
&read("locals"),
|
||||||
|
)?;
|
||||||
|
config.configure(highlight_for_scope);
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LanguageLoader for LanguageRegistry {
|
||||||
|
fn language_for_marker(&self, marker: InjectionLanguageMarker<'_>) -> Option<Language> {
|
||||||
|
match marker {
|
||||||
|
InjectionLanguageMarker::Name(name) => self.by_name.get(name).copied(),
|
||||||
|
InjectionLanguageMarker::Match(text) => {
|
||||||
|
let name: Cow<str> = text.into();
|
||||||
|
self.by_name.get(name.as_ref()).copied()
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_config(&self, lang: Language) -> Option<&LanguageConfig> {
|
||||||
|
self.configs.get(lang.idx())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(config: &AppConfig) {
|
pub fn init(config: &AppConfig) {
|
||||||
match &config.syntax_root {
|
match &config.syntax_root {
|
||||||
Some(syntax_root) => {
|
Some(syntax_root) => {
|
||||||
info!("Loading syntax files");
|
info!("Loading syntax files from {}", syntax_root.display());
|
||||||
REGISTRY.get_or_init(|| LanguageRegistry::new(syntax_root));
|
REGISTRY.get_or_init(|| LanguageRegistry::new(syntax_root));
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user