rat_markdown/
styles.rs

1use crate::parser::parse_md_item;
2use pulldown_cmark::{Event, HeadingLevel, Options, Parser, Tag};
3use std::num::ParseIntError;
4use std::ops::Range;
5
6/// Markdown styles.
7///
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum MDStyle {
10    Heading1 = 0,
11    Heading2,
12    Heading3,
13    Heading4,
14    Heading5,
15    Heading6,
16
17    Paragraph,
18    BlockQuote,
19    CodeBlock,
20    MathDisplay,
21    Rule = 10,
22    Html,
23
24    Link,
25    LinkDef,
26    Image,
27    FootnoteDefinition,
28    FootnoteReference,
29
30    List,
31    Item,
32    TaskListMarker,
33    ItemTag = 20,
34    DefinitionList,
35    DefinitionListTitle,
36    DefinitionListDefinition,
37
38    Table,
39    TableHead,
40    TableRow,
41    TableCell,
42
43    Emphasis,
44    Strong,
45    Strikethrough = 30,
46    CodeInline,
47    MathInline,
48
49    MetadataBlock,
50}
51
52impl From<MDStyle> for usize {
53    fn from(value: MDStyle) -> Self {
54        value as usize
55    }
56}
57
58impl TryFrom<usize> for MDStyle {
59    type Error = ParseIntError;
60
61    fn try_from(value: usize) -> Result<Self, Self::Error> {
62        use MDStyle::*;
63        Ok(match value {
64            0 => Heading1,
65            1 => Heading2,
66            2 => Heading3,
67            3 => Heading4,
68            4 => Heading5,
69            5 => Heading6,
70
71            6 => Paragraph,
72            7 => BlockQuote,
73            8 => CodeBlock,
74            9 => MathDisplay,
75            10 => Rule,
76            11 => Html,
77
78            12 => Link,
79            13 => LinkDef,
80            14 => Image,
81            15 => FootnoteDefinition,
82            16 => FootnoteReference,
83
84            17 => List,
85            18 => Item,
86            19 => TaskListMarker,
87            20 => ItemTag,
88            21 => DefinitionList,
89            22 => DefinitionListTitle,
90            23 => DefinitionListDefinition,
91
92            24 => Table,
93            25 => TableHead,
94            26 => TableRow,
95            27 => TableCell,
96
97            28 => Emphasis,
98            29 => Strong,
99            30 => Strikethrough,
100            31 => CodeInline,
101            32 => MathInline,
102
103            33 => MetadataBlock,
104            _ => return Err("256".parse::<u8>().expect_err("should fail")),
105        })
106    }
107}
108
109/// Parse the text and return the byte ranges for each structural element.
110///
111/// Returns a format suitable for TextArea: byte-range->style-idx.
112/// The style-idx is convertible to a MDStyle.
113///
114pub fn parse_md_styles(txt: &str) -> Vec<(Range<usize>, usize)> {
115    let mut styles = Vec::new();
116
117    let p = Parser::new_ext(
118        txt,
119        Options::ENABLE_MATH
120            | Options::ENABLE_TASKLISTS
121            | Options::ENABLE_TABLES
122            | Options::ENABLE_STRIKETHROUGH
123            | Options::ENABLE_SMART_PUNCTUATION
124            | Options::ENABLE_FOOTNOTES
125            | Options::ENABLE_GFM
126            | Options::ENABLE_DEFINITION_LIST,
127    )
128    .into_offset_iter();
129
130    for (_, linkdef) in p.reference_definitions().iter() {
131        styles.push((linkdef.span.clone(), MDStyle::LinkDef as usize));
132    }
133
134    for (e, r) in p {
135        match e {
136            Event::Start(Tag::Heading { level, .. }) => match level {
137                HeadingLevel::H1 => styles.push((r, MDStyle::Heading1 as usize)),
138                HeadingLevel::H2 => styles.push((r, MDStyle::Heading2 as usize)),
139                HeadingLevel::H3 => styles.push((r, MDStyle::Heading3 as usize)),
140                HeadingLevel::H4 => styles.push((r, MDStyle::Heading4 as usize)),
141                HeadingLevel::H5 => styles.push((r, MDStyle::Heading5 as usize)),
142                HeadingLevel::H6 => styles.push((r, MDStyle::Heading6 as usize)),
143            },
144            Event::Start(Tag::BlockQuote(_)) => {
145                styles.push((r, MDStyle::BlockQuote as usize));
146            }
147            Event::Start(Tag::CodeBlock(_)) => {
148                styles.push((r, MDStyle::CodeBlock as usize));
149            }
150            Event::Start(Tag::FootnoteDefinition(_)) => {
151                styles.push((r, MDStyle::FootnoteDefinition as usize));
152            }
153            Event::Start(Tag::Item) => {
154                // only color the marker
155                let item_text = &txt[r.clone()];
156                let item = parse_md_item(r.start, item_text.as_ref()).expect("md item");
157                styles.push((
158                    item.mark_bytes.start..item.mark_bytes.end,
159                    MDStyle::ItemTag as usize,
160                ));
161                styles.push((r, MDStyle::Item as usize));
162            }
163            Event::Start(Tag::Emphasis) => {
164                styles.push((r, MDStyle::Emphasis as usize));
165            }
166            Event::Start(Tag::Strong) => {
167                styles.push((r, MDStyle::Strong as usize));
168            }
169            Event::Start(Tag::Strikethrough) => {
170                styles.push((r, MDStyle::Strikethrough as usize));
171            }
172            Event::Start(Tag::Link { .. }) => {
173                styles.push((r, MDStyle::Link as usize));
174            }
175            Event::Start(Tag::Image { .. }) => {
176                styles.push((r, MDStyle::Image as usize));
177            }
178            Event::Start(Tag::MetadataBlock { .. }) => {
179                styles.push((r, MDStyle::MetadataBlock as usize));
180            }
181            Event::Start(Tag::Paragraph) => {
182                styles.push((r, MDStyle::Paragraph as usize));
183            }
184            Event::Start(Tag::HtmlBlock) => {
185                styles.push((r, MDStyle::Html as usize));
186            }
187            Event::Start(Tag::List(_)) => {
188                styles.push((r, MDStyle::List as usize));
189            }
190            Event::Start(Tag::Table(_)) => {
191                styles.push((r, MDStyle::Table as usize));
192            }
193            Event::Start(Tag::TableHead) => {
194                styles.push((r, MDStyle::TableHead as usize));
195            }
196            Event::Start(Tag::TableRow) => {
197                styles.push((r, MDStyle::TableRow as usize));
198            }
199            Event::Start(Tag::TableCell) => {
200                styles.push((r, MDStyle::TableCell as usize));
201            }
202            Event::Start(Tag::DefinitionList) => {
203                styles.push((r, MDStyle::DefinitionList as usize));
204            }
205            Event::Start(Tag::DefinitionListTitle) => {
206                styles.push((r, MDStyle::DefinitionListTitle as usize));
207            }
208            Event::Start(Tag::DefinitionListDefinition) => {
209                styles.push((r, MDStyle::DefinitionListDefinition as usize));
210            }
211
212            Event::Code(_) => {
213                styles.push((r, MDStyle::CodeInline as usize));
214            }
215            Event::InlineMath(_) => {
216                styles.push((r, MDStyle::MathInline as usize));
217            }
218            Event::DisplayMath(_) => {
219                styles.push((r, MDStyle::MathDisplay as usize));
220            }
221            Event::FootnoteReference(_) => {
222                styles.push((r, MDStyle::FootnoteReference as usize));
223            }
224            Event::Rule => {
225                styles.push((r, MDStyle::Rule as usize));
226            }
227            Event::TaskListMarker(_) => {
228                styles.push((r, MDStyle::TaskListMarker as usize));
229            }
230            Event::Html(_) | Event::InlineHtml(_) => {
231                styles.push((r, MDStyle::Html as usize));
232            }
233
234            Event::End(_) => {}
235            Event::Text(_) => {}
236            Event::SoftBreak => {}
237            Event::HardBreak => {}
238        }
239    }
240
241    styles
242}