use bezel::gpui::App;
use std::{ops::Range, path::Path, sync::OnceLock};
use bezel::theme::HighlightKind;
use syntax::registry::Known;
mod provider;
pub use provider::{Status, available, start_install, status};
const MARKDOWN: &str = "markdown";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Language {
Ready(&'static str),
Markdown,
Missing(&'static str),
}
fn installed() {
static ONCE: OnceLock<()> = OnceLock::new();
ONCE.get_or_init(provider::initialize);
}
pub fn of(path: &Path) -> Option<Language> {
installed();
let known = syntax::registry::of_path(path)?;
if known.name() == MARKDOWN {
return Some(Language::Markdown);
}
Some(match known {
Known::Ready(lang) => Language::Ready(lang.name),
Known::Named(name) => Language::Missing(name),
})
}
pub fn paintable() -> Vec<&'static str> {
installed();
syntax::registry::ready()
}
pub fn offerable() -> Vec<&'static str> {
installed();
provider::names()
}
pub fn ensure(labels: impl IntoIterator<Item = String>, cx: &mut App) {
installed();
let mut started = false;
for label in labels {
let Some(name) = provider::resolve(&label) else {
continue;
};
if provider::status(name) == Status::Missing {
provider::start_install(name);
started = true;
}
}
if started {
watch(cx);
}
}
fn watch(cx: &mut App) {
if !provider::begin_watch() {
return;
}
cx.spawn(async move |cx| {
loop {
cx.background_executor()
.timer(std::time::Duration::from_millis(150))
.await;
let busy = cx.update(|cx| {
cx.refresh_windows();
provider::working()
});
if !busy {
break;
}
}
provider::end_watch();
})
.detach();
}
pub fn spans(path: &Path, text: &str) -> Option<Vec<(Range<usize>, HighlightKind)>> {
match of(path)? {
Language::Ready(language) => highlight(language, text),
Language::Markdown => Some(markdown::source::spans(text)),
Language::Missing(_) => None,
}
}
pub fn highlight(language: &str, source: &str) -> Option<Vec<(Range<usize>, HighlightKind)>> {
installed();
provider::ensure_runtime().ok()?;
syntax::highlight(source, language)
}