1use std::ops::Range;
13
14use pulldown_cmark::{Event, Parser, Tag};
15use theme::HighlightKind;
16
17pub const LANGUAGES: [&str; 2] = ["md", "markdown"];
19
20pub fn is_markdown(language: &str) -> bool {
22 LANGUAGES.contains(&language)
23}
24
25pub fn spans(source: &str) -> Vec<(Range<usize>, HighlightKind)> {
27 let mut map: Vec<Option<HighlightKind>> = vec![None; source.len()];
28 let mut paint = |range: Range<usize>, kind: HighlightKind| {
29 for slot in &mut map[range.start.min(source.len())..range.end.min(source.len())] {
30 *slot = Some(kind);
31 }
32 };
33
34 for (event, range) in Parser::new_ext(source, crate::parse::OPTIONS).into_offset_iter() {
35 match event {
36 Event::Start(Tag::Heading { .. }) => paint(range, HighlightKind::Keyword),
37 Event::Start(Tag::BlockQuote(_)) => paint(range, HighlightKind::Comment),
38 Event::Start(Tag::CodeBlock(_)) | Event::Code(_) => paint(range, HighlightKind::String),
39 Event::Start(Tag::Strong | Tag::Emphasis | Tag::Strikethrough) => {
40 paint(range, HighlightKind::Constant);
41 }
42 Event::Start(Tag::Link { .. } | Tag::Image { .. }) => {
45 let at = source[range.clone()]
46 .rfind("](")
47 .map_or(range.start, |ix| range.start + ix);
48 paint(at..range.end, HighlightKind::Attribute);
49 }
50 Event::Start(Tag::Item) => paint(marker(source, range), HighlightKind::Punctuation),
52 Event::Start(Tag::Table(_)) => {
53 for (ix, _) in source[range.clone()].match_indices('|') {
54 paint(
55 range.start + ix..range.start + ix + 1,
56 HighlightKind::Punctuation,
57 );
58 }
59 }
60 Event::TaskListMarker(_) => paint(range, HighlightKind::Boolean),
61 Event::Rule => paint(range, HighlightKind::Punctuation),
62 Event::Html(_) | Event::InlineHtml(_) => paint(range, HighlightKind::Tag),
63 _ => {}
64 }
65 }
66
67 let mut spans: Vec<(Range<usize>, HighlightKind)> = Vec::new();
70 for (at, kind) in map.into_iter().enumerate() {
71 let Some(kind) = kind else { continue };
72 match spans.last_mut() {
73 Some((range, last)) if *last == kind && range.end == at => range.end = at + 1,
74 _ => spans.push((at..at + 1, kind)),
75 }
76 }
77 spans
78}
79
80fn marker(source: &str, range: Range<usize>) -> Range<usize> {
83 let item = &source[range.clone()];
84 let text = item.trim_start();
85 let start = range.start + (item.len() - text.len());
86 let width = text
87 .find(char::is_whitespace)
88 .map_or(text.len(), |ix| ix + 1);
89 start..start + width
90}