markdown/highlight.rs
1//! Who colors a fenced block.
2//!
3//! `markdown` names no highlighter. The one this crate would otherwise reach
4//! for is tree-sitter, whose C cannot be built for `wasm32-unknown-unknown` —
5//! there is no libc to compile it against — so a web build would carry a
6//! dependency it can never link. Installed once at boot like the theme
7//! palette, and read at paint.
8
9use std::ops::Range;
10
11use gpui::{App, Global, SharedString};
12use theme::HighlightKind;
13
14/// Spans over `code`, in bytes. `None` for a language the caller cannot color.
15pub type Highlighter = fn(language: &str, code: &str) -> Option<Vec<(Range<usize>, HighlightKind)>>;
16
17struct Installed {
18 highlighter: Highlighter,
19 languages: Vec<SharedString>,
20}
21
22impl Global for Installed {}
23
24/// `markdown::set_highlighter(cx, my_highlighter, my_languages)` — call once at
25/// boot. Without it every fenced block paints in one plain run, which is what a
26/// document looks like before anyone has an opinion about its code.
27///
28/// The names travel with the function because they are the same fact twice: a
29/// picker that offers a language nothing can color is a promise the highlighter
30/// does not keep.
31pub fn set_highlighter(
32 cx: &mut App,
33 highlighter: Highlighter,
34 languages: impl IntoIterator<Item = impl Into<SharedString>>,
35) {
36 cx.set_global(Installed {
37 highlighter,
38 languages: languages.into_iter().map(Into::into).collect(),
39 });
40}
41
42/// What the installed highlighter can color — the list a language picker
43/// offers, empty until someone installs one.
44pub fn languages(cx: &App) -> &[SharedString] {
45 cx.try_global::<Installed>()
46 .map_or(&[], |installed| &installed.languages)
47}
48
49pub(crate) fn spans(
50 cx: &App,
51 language: Option<&str>,
52 code: &str,
53) -> Option<Vec<(Range<usize>, HighlightKind)>> {
54 (cx.try_global::<Installed>()?.highlighter)(language?, code)
55}