1use crate::note_editor::rich_text::RichText;
2
3#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
4pub enum HeadingLevel {
5 H1 = 1,
6 H2,
7 H3,
8 H4,
9 H5,
10 H6,
11}
12
13impl From<pulldown_cmark::HeadingLevel> for HeadingLevel {
14 fn from(value: pulldown_cmark::HeadingLevel) -> Self {
15 match value {
16 pulldown_cmark::HeadingLevel::H1 => HeadingLevel::H1,
17 pulldown_cmark::HeadingLevel::H2 => HeadingLevel::H2,
18 pulldown_cmark::HeadingLevel::H3 => HeadingLevel::H3,
19 pulldown_cmark::HeadingLevel::H4 => HeadingLevel::H4,
20 pulldown_cmark::HeadingLevel::H5 => HeadingLevel::H5,
21 pulldown_cmark::HeadingLevel::H6 => HeadingLevel::H6,
22 }
23 }
24}
25
26#[derive(Clone, Debug, PartialEq)]
27pub enum BlockQuoteKind {
28 Note,
29 Tip,
30 Important,
31 Warning,
32 Caution,
33}
34
35impl From<pulldown_cmark::BlockQuoteKind> for BlockQuoteKind {
36 fn from(value: pulldown_cmark::BlockQuoteKind) -> Self {
37 match value {
38 pulldown_cmark::BlockQuoteKind::Tip => BlockQuoteKind::Tip,
39 pulldown_cmark::BlockQuoteKind::Note => BlockQuoteKind::Note,
40 pulldown_cmark::BlockQuoteKind::Warning => BlockQuoteKind::Warning,
41 pulldown_cmark::BlockQuoteKind::Caution => BlockQuoteKind::Caution,
42 pulldown_cmark::BlockQuoteKind::Important => BlockQuoteKind::Important,
43 }
44 }
45}
46
47#[derive(Clone, Debug, PartialEq)]
49pub enum ItemKind {
50 Ordered(u64),
52 Unordered,
54}
55
56#[derive(Clone, Debug, PartialEq)]
58pub enum TaskKind {
59 Checked,
61 Unchecked,
63 LooselyChecked,
66}
67
68pub type SourceRange<Idx> = std::ops::Range<Idx>;
69
70#[derive(Clone, Debug, PartialEq)]
72pub enum Node {
73 Heading {
74 level: HeadingLevel,
75 text: RichText,
76 source_range: SourceRange<usize>,
77 },
78 Paragraph {
79 text: RichText,
80 source_range: SourceRange<usize>,
81 },
82 CodeBlock {
83 lang: Option<String>,
84 text: RichText,
85 source_range: SourceRange<usize>,
86 },
87 BlockQuote {
88 kind: Option<BlockQuoteKind>,
89 nodes: Vec<Node>,
90 source_range: SourceRange<usize>,
91 },
92 List {
93 nodes: Vec<Node>,
94 source_range: SourceRange<usize>,
95 },
96 Item {
97 kind: ItemKind,
98 nodes: Vec<Node>,
99 source_range: SourceRange<usize>,
100 },
101 Task {
102 kind: TaskKind,
103 nodes: Vec<Node>,
104 source_range: SourceRange<usize>,
105 },
106}
107
108impl Node {
109 pub fn source_range(&self) -> &SourceRange<usize> {
110 match self {
111 Self::Heading { source_range, .. }
112 | Self::CodeBlock { source_range, .. }
113 | Self::Paragraph { source_range, .. }
114 | Self::List { source_range, .. }
115 | Self::BlockQuote { source_range, .. }
116 | Self::Item { source_range, .. }
117 | Self::Task { source_range, .. } => source_range,
118 }
119 }
120
121 pub fn set_source_range(&mut self, new_range: SourceRange<usize>) {
122 match self {
123 Self::Heading { source_range, .. }
124 | Self::CodeBlock { source_range, .. }
125 | Self::Paragraph { source_range, .. }
126 | Self::List { source_range, .. }
127 | Self::BlockQuote { source_range, .. }
128 | Self::Item { source_range, .. }
129 | Self::Task { source_range, .. } => *source_range = new_range,
130 }
131 }
132
133 pub fn rich_text(&self) -> Option<&RichText> {
134 match self {
135 Self::Heading { text, .. }
136 | Self::Paragraph { text, .. }
137 | Self::CodeBlock { text, .. } => Some(text),
138 _ => None,
139 }
140 }
141}
142
143pub fn nodes_to_sexp(nodes: &[Node], indent_level: usize) -> String {
144 nodes
145 .iter()
146 .map(|node| node_to_sexp(node, indent_level))
147 .collect::<Vec<_>>()
148 .join("\n")
149}
150
151pub fn node_to_sexp(node: &Node, indent_level: usize) -> String {
152 let indent_increment = 2;
153
154 match node {
155 Node::Heading {
156 level,
157 text,
158 source_range,
159 } => {
160 format!(
161 "{:indent$}(heading {:?} @{:?}\n{})",
162 "",
163 level,
164 source_range,
165 rich_text_to_sexp(text, indent_level + indent_increment),
166 indent = indent_level
167 )
168 }
169 Node::Paragraph { text, source_range } => {
170 format!(
171 "{:indent$}(paragraph @{:?}\n{})",
172 "",
173 source_range,
174 rich_text_to_sexp(text, indent_level + indent_increment),
175 indent = indent_level
176 )
177 }
178 Node::BlockQuote {
179 kind,
180 nodes,
181 source_range,
182 } => {
183 format!(
184 "{:indent$}(blockquote {:?} @{:?}\n{})",
185 "",
186 kind,
187 source_range,
188 nodes_to_sexp(nodes, indent_level + indent_increment),
189 indent = indent_level
190 )
191 }
192 Node::CodeBlock {
193 lang,
194 text,
195 source_range,
196 } => {
197 format!(
198 "{:indent$}(codeblock {} @{:?}\n{})",
199 "",
200 lang.clone().unwrap_or(String::new()),
201 source_range,
202 rich_text_to_sexp(text, indent_level + indent_increment),
203 indent = indent_level,
204 )
205 }
206 Node::List {
207 nodes,
208 source_range,
209 } => {
210 format!(
211 "{:indent$}(list @{:?}\n{})",
212 "",
213 source_range,
214 nodes_to_sexp(nodes, indent_level + indent_increment),
215 indent = indent_level
216 )
217 }
218 Node::Item {
219 kind,
220 nodes,
221 source_range,
222 } => {
223 format!(
224 "{:indent$}(item {:?} @{:?}\n{})",
225 "",
226 kind,
227 source_range,
228 nodes_to_sexp(nodes, indent_level + indent_increment),
229 indent = indent_level
230 )
231 }
232 Node::Task {
233 kind,
234 nodes,
235 source_range,
236 } => {
237 format!(
238 "{:indent$}(task {:?} @{:?}\n{})",
239 "",
240 kind,
241 source_range,
242 nodes_to_sexp(nodes, indent_level + indent_increment),
243 indent = indent_level
244 )
245 }
246 }
247}
248
249pub fn rich_text_to_sexp(rich_text: &RichText, indent_level: usize) -> String {
250 rich_text
251 .segments()
252 .iter()
253 .map(|segment| match &segment.style {
254 Some(style) => format!(
255 "{:indent$}({} \"{}\")",
256 "",
257 style,
258 segment,
259 indent = indent_level
260 ),
261 None => format!("{:indent$}\"{}\"", "", segment, indent = indent_level),
262 })
263 .collect::<Vec<_>>()
264 .join("\n")
265}