1use std::ops::Range;
13
14use pulldown_cmark::{Event, Options, 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 options =
28 Options::ENABLE_TABLES | Options::ENABLE_STRIKETHROUGH | Options::ENABLE_TASKLISTS;
29 let mut map: Vec<Option<HighlightKind>> = vec![None; source.len()];
30 let mut paint = |range: Range<usize>, kind: HighlightKind| {
31 for slot in &mut map[range.start.min(source.len())..range.end.min(source.len())] {
32 *slot = Some(kind);
33 }
34 };
35
36 for (event, range) in Parser::new_ext(source, options).into_offset_iter() {
37 match event {
38 Event::Start(Tag::Heading { .. }) => paint(range, HighlightKind::Keyword),
39 Event::Start(Tag::BlockQuote(_)) => paint(range, HighlightKind::Comment),
40 Event::Start(Tag::CodeBlock(_)) | Event::Code(_) => paint(range, HighlightKind::String),
41 Event::Start(Tag::Strong | Tag::Emphasis | Tag::Strikethrough) => {
42 paint(range, HighlightKind::Constant);
43 }
44 Event::Start(Tag::Link { .. } | Tag::Image { .. }) => {
47 let at = source[range.clone()]
48 .rfind("](")
49 .map_or(range.start, |ix| range.start + ix);
50 paint(at..range.end, HighlightKind::Attribute);
51 }
52 Event::Start(Tag::Item) => paint(marker(source, range), HighlightKind::Punctuation),
54 Event::Start(Tag::Table(_)) => {
55 for (ix, _) in source[range.clone()].match_indices('|') {
56 paint(
57 range.start + ix..range.start + ix + 1,
58 HighlightKind::Punctuation,
59 );
60 }
61 }
62 Event::TaskListMarker(_) => paint(range, HighlightKind::Boolean),
63 Event::Rule => paint(range, HighlightKind::Punctuation),
64 Event::Html(_) | Event::InlineHtml(_) => paint(range, HighlightKind::Tag),
65 _ => {}
66 }
67 }
68
69 let mut spans: Vec<(Range<usize>, HighlightKind)> = Vec::new();
72 for (at, kind) in map.into_iter().enumerate() {
73 let Some(kind) = kind else { continue };
74 match spans.last_mut() {
75 Some((range, last)) if *last == kind && range.end == at => range.end = at + 1,
76 _ => spans.push((at..at + 1, kind)),
77 }
78 }
79 spans
80}
81
82fn marker(source: &str, range: Range<usize>) -> Range<usize> {
85 let item = &source[range.clone()];
86 let text = item.trim_start();
87 let start = range.start + (item.len() - text.len());
88 let width = text
89 .find(char::is_whitespace)
90 .map_or(text.len(), |ix| ix + 1);
91 start..start + width
92}