markdown_that/generics/inline/
emph_pair.rs1use std::cmp::min;
50
51use crate::common::sourcemap::SourcePos;
52use crate::parser::core::CoreRule;
53use crate::parser::extset::{MarkdownThatExt, NodeExt};
54use crate::parser::inline::builtin::InlineParserRule;
55use crate::parser::inline::{InlineRule, InlineState, Text};
56use crate::{MarkdownThat, Node, NodeValue};
57
58#[derive(Debug, Default)]
59struct PairConfig<const MARKER: char> {
60 inserted: bool,
61 fns: [Option<fn() -> Node>; 3],
62}
63impl<const MARKER: char> MarkdownThatExt for PairConfig<MARKER> {}
64
65#[derive(Debug, Default)]
66struct OpenersBottom<const MARKER: char>([usize; 6]);
67impl<const MARKER: char> NodeExt for OpenersBottom<MARKER> {}
68
69#[derive(Debug, Clone)]
70#[doc(hidden)]
71pub struct EmphMarker {
72 pub marker: char,
74
75 pub length: usize,
77
78 pub remaining: usize,
80
81 pub open: bool,
84 pub close: bool,
85}
86
87impl NodeValue for EmphMarker {}
89
90pub fn add_with<const MARKER: char, const LENGTH: u8, const CAN_SPLIT_WORD: bool>(
91 md: &mut MarkdownThat,
92 f: fn() -> Node,
93) {
94 let pair_config = md.ext.get_or_insert_default::<PairConfig<MARKER>>();
95 pair_config.fns[LENGTH as usize - 1] = Some(f);
96
97 if !pair_config.inserted {
98 pair_config.inserted = true;
99 md.inline
100 .add_rule::<EmphPairScanner<MARKER, CAN_SPLIT_WORD>>();
101 }
102
103 if !md.has_rule::<FragmentsJoin>() {
104 md.add_rule::<FragmentsJoin>()
105 .before_all()
106 .after::<InlineParserRule>();
107 }
108}
109
110#[doc(hidden)]
111pub struct EmphPairScanner<const MARKER: char, const CAN_SPLIT_WORD: bool>;
112impl<const MARKER: char, const CAN_SPLIT_WORD: bool> InlineRule
113 for EmphPairScanner<MARKER, CAN_SPLIT_WORD>
114{
115 const MARKER: char = MARKER;
116
117 fn check(_: &mut InlineState) -> Option<usize> {
120 None
121 }
122
123 fn run(state: &mut InlineState) -> Option<(Node, usize)> {
124 let mut chars = state.src[state.pos..state.pos_max].chars();
125 if chars.next().unwrap() != MARKER {
126 return None;
127 }
128
129 let scanned = state.scan_delims(state.pos, CAN_SPLIT_WORD);
130 let mut node = Node::new(EmphMarker {
131 marker: MARKER,
132 length: scanned.length,
133 remaining: scanned.length,
134 open: scanned.can_open,
135 close: scanned.can_close,
136 });
137 node.srcmap = state.get_map(state.pos, state.pos + scanned.length);
138 node = scan_and_match_delimiters::<MARKER>(state, node);
139 let map = node.srcmap.unwrap().get_byte_offsets();
140 state.pos += scanned.length;
142 let token_len = map.1 - map.0;
143 state.pos -= token_len;
144 Some((node, token_len))
145 }
146}
147
148fn scan_and_match_delimiters<const MARKER: char>(
151 state: &mut InlineState,
152 mut closer_token: Node,
153) -> Node {
154 if state.node.children.is_empty() {
155 return closer_token;
156 } let mut closer = closer_token.cast_mut::<EmphMarker>().unwrap().clone();
159 if !closer.close {
160 return closer_token;
161 }
162
163 let openers_for_marker = state
168 .node
169 .ext
170 .get_or_insert_default::<OpenersBottom<MARKER>>();
171 let openers_parameter = (closer.open as usize) * 3 + closer.length % 3;
172
173 let min_opener_idx = openers_for_marker.0[openers_parameter];
174
175 let mut idx = state.node.children.len() - 1;
176 let mut new_min_opener_idx = idx;
177 while idx > min_opener_idx {
178 idx -= 1;
179
180 let Some(opener) = state.node.children[idx].cast::<EmphMarker>() else {
181 continue;
182 };
183
184 let mut opener = opener.clone();
185 if opener.open && opener.marker == closer.marker && !is_odd_match(&opener, &closer) {
186 while closer.remaining > 0 && opener.remaining > 0 {
187 let max_marker_len = min(3, min(opener.remaining, closer.remaining));
188 let mut matched_rule = None;
189 let fns = &state.md.ext.get::<PairConfig<MARKER>>().unwrap().fns;
190 for marker_len in (1..=max_marker_len).rev() {
191 if let Some(f) = fns[marker_len - 1] {
192 matched_rule = Some((marker_len, f));
193 break;
194 }
195 }
196
197 if matched_rule.is_none() {
201 break;
202 }
203
204 let (marker_len, marker_fn) = matched_rule.unwrap();
205
206 closer.remaining -= marker_len;
207 opener.remaining -= marker_len;
208
209 let mut new_token = marker_fn();
210 new_token.children = state.node.children.split_off(idx + 1);
211
212 let mut end_map_pos = 0;
214 if let Some(map) = closer_token.srcmap {
215 let (start, end) = map.get_byte_offsets();
216 closer_token.srcmap = Some(SourcePos::new(start + marker_len, end));
217 end_map_pos = start + marker_len;
218 }
219
220 let mut start_map_pos = 0;
222 let opener_token = state.node.children.last_mut().unwrap();
223 if let Some(map) = opener_token.srcmap {
224 let (start, end) = map.get_byte_offsets();
225 opener_token.srcmap = Some(SourcePos::new(start, end - marker_len));
226 start_map_pos = end - marker_len;
227 }
228
229 new_token.srcmap = state.get_map(start_map_pos, end_map_pos);
230
231 if opener.remaining == 0 {
233 state.node.children.pop();
234 }
235
236 new_min_opener_idx = 0;
237 state.node.children.push(new_token);
238 }
239 }
240
241 if opener.remaining > 0 {
242 state.node.children[idx].replace(opener);
243 } }
245
246 if new_min_opener_idx != 0 {
247 let openers_for_marker = state
255 .node
256 .ext
257 .get_or_insert_default::<OpenersBottom<MARKER>>();
258 openers_for_marker.0[openers_parameter] = new_min_opener_idx;
259 }
260
261 if closer.remaining > 0 {
263 closer_token.replace(closer);
264 closer_token
265 } else {
266 state.node.children.pop().unwrap()
267 }
268}
269
270fn is_odd_match(opener: &EmphMarker, closer: &EmphMarker) -> bool {
271 #[allow(clippy::collapsible_if)]
279 if opener.close || closer.open {
280 if (opener.length + closer.length) % 3 == 0 {
281 if opener.length % 3 != 0 || closer.length % 3 != 0 {
282 return true;
283 }
284 }
285 }
286
287 false
288}
289
290#[doc(hidden)]
291pub struct FragmentsJoin;
292impl CoreRule for FragmentsJoin {
293 fn run(node: &mut Node, _: &MarkdownThat) {
294 node.walk_mut(|node, _| fragments_join(node));
295 }
296}
297
298fn fragments_join(node: &mut Node) {
307 for token in node.children.iter_mut() {
309 if let Some(data) = token.cast::<EmphMarker>() {
310 let content = data.marker.to_string().repeat(data.remaining);
311 token.replace(Text { content });
312 }
313 }
314
315 for idx in 1..node.children.len() {
317 let (tokens1, tokens2) = node.children.split_at_mut(idx);
318
319 let token1 = tokens1.last_mut().unwrap();
320 let Some(t1_data) = token1.cast_mut::<Text>() else {
321 continue;
322 };
323
324 let token2 = tokens2.first_mut().unwrap();
325 let Some(t2_data) = token2.cast_mut::<Text>() else {
326 continue;
327 };
328
329 let t2_content = std::mem::take(&mut t2_data.content);
331 t1_data.content += &t2_content;
332
333 if let Some(map1) = token1.srcmap {
335 if let Some(map2) = token2.srcmap {
336 token1.srcmap = Some(SourcePos::new(
337 map1.get_byte_offsets().0,
338 map2.get_byte_offsets().1,
339 ));
340 }
341 }
342
343 node.children.swap(idx - 1, idx);
344 }
345
346 node.children.retain(|token| {
348 if let Some(data) = token.cast::<Text>() {
349 !data.content.is_empty()
350 } else {
351 true
352 }
353 });
354}