use std::ops::Range;
use pulldown_cmark::{Event, Parser, Tag};
use theme::HighlightKind;
pub const LANGUAGES: [&str; 2] = ["md", "markdown"];
pub fn is_markdown(language: &str) -> bool {
LANGUAGES.contains(&language)
}
pub fn spans(source: &str) -> Vec<(Range<usize>, HighlightKind)> {
let mut map: Vec<Option<HighlightKind>> = vec![None; source.len()];
let mut paint = |range: Range<usize>, kind: HighlightKind| {
for slot in &mut map[range.start.min(source.len())..range.end.min(source.len())] {
*slot = Some(kind);
}
};
for (event, range) in Parser::new_ext(source, crate::parse::OPTIONS).into_offset_iter() {
match event {
Event::Start(Tag::Heading { .. }) => paint(range, HighlightKind::Keyword),
Event::Start(Tag::BlockQuote(_)) => paint(range, HighlightKind::Comment),
Event::Start(Tag::CodeBlock(_)) | Event::Code(_) => paint(range, HighlightKind::String),
Event::Start(Tag::Strong | Tag::Emphasis | Tag::Strikethrough) => {
paint(range, HighlightKind::Constant);
}
Event::Start(Tag::Link { .. } | Tag::Image { .. }) => {
let at = source[range.clone()]
.rfind("](")
.map_or(range.start, |ix| range.start + ix);
paint(at..range.end, HighlightKind::Attribute);
}
Event::Start(Tag::Item) => paint(marker(source, range), HighlightKind::Punctuation),
Event::Start(Tag::Table(_)) => {
for (ix, _) in source[range.clone()].match_indices('|') {
paint(
range.start + ix..range.start + ix + 1,
HighlightKind::Punctuation,
);
}
}
Event::TaskListMarker(_) => paint(range, HighlightKind::Boolean),
Event::Rule => paint(range, HighlightKind::Punctuation),
Event::Html(_) | Event::InlineHtml(_) => paint(range, HighlightKind::Tag),
_ => {}
}
}
let mut spans: Vec<(Range<usize>, HighlightKind)> = Vec::new();
for (at, kind) in map.into_iter().enumerate() {
let Some(kind) = kind else { continue };
match spans.last_mut() {
Some((range, last)) if *last == kind && range.end == at => range.end = at + 1,
_ => spans.push((at..at + 1, kind)),
}
}
spans
}
fn marker(source: &str, range: Range<usize>) -> Range<usize> {
let item = &source[range.clone()];
let text = item.trim_start();
let start = range.start + (item.len() - text.len());
let width = text
.find(char::is_whitespace)
.map_or(text.len(), |ix| ix + 1);
start..start + width
}