markdown_it/generics/inline/
emph_pair.rs1use std::cmp::min;
50
51use crate::common::sourcemap::SourcePos;
52use crate::parser::core::CoreRule;
53use crate::parser::extset::{MarkdownItExt, NodeExt};
54use crate::parser::inline::builtin::InlineParserRule;
55use crate::parser::inline::{InlineRule, InlineState, Text};
56use crate::{MarkdownIt, 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> MarkdownItExt 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>(md: &mut MarkdownIt, f: fn () -> Node) {
91 let pair_config = md.ext.get_or_insert_default::<PairConfig<MARKER>>();
92 pair_config.fns[LENGTH as usize - 1] = Some(f);
93
94 if !pair_config.inserted {
95 pair_config.inserted = true;
96 md.inline.add_rule::<EmphPairScanner<MARKER, CAN_SPLIT_WORD>>();
97 }
98
99 if !md.has_rule::<FragmentsJoin>() {
100 md.add_rule::<FragmentsJoin>()
101 .before_all()
102 .after::<InlineParserRule>();
103 }
104}
105
106#[doc(hidden)]
107pub struct EmphPairScanner<const MARKER: char, const CAN_SPLIT_WORD: bool>;
108impl<const MARKER: char, const CAN_SPLIT_WORD: bool> InlineRule for EmphPairScanner<MARKER, CAN_SPLIT_WORD> {
109 const MARKER: char = MARKER;
110
111 fn check(_: &mut InlineState) -> Option<usize> { None }
114
115 fn run(state: &mut InlineState) -> Option<(Node, usize)> {
116 let mut chars = state.src[state.pos..state.pos_max].chars();
117 if chars.next().unwrap() != MARKER { return None; }
118
119 let scanned = state.scan_delims(state.pos, CAN_SPLIT_WORD);
120 let mut node = Node::new(EmphMarker {
121 marker: MARKER,
122 length: scanned.length,
123 remaining: scanned.length,
124 open: scanned.can_open,
125 close: scanned.can_close,
126 });
127 node.srcmap = state.get_map(state.pos, state.pos + scanned.length);
128 node = scan_and_match_delimiters::<MARKER>(state, node);
129 let map = node.srcmap.unwrap().get_byte_offsets();
130 state.pos += scanned.length;
132 let token_len = map.1 - map.0;
133 state.pos -= token_len;
134 Some((node, token_len))
135 }
136}
137
138fn scan_and_match_delimiters<const MARKER: char>(state: &mut InlineState, mut closer_token: Node) -> Node {
141 if state.node.children.is_empty() { return closer_token; } let mut closer = closer_token.cast_mut::<EmphMarker>().unwrap().clone();
144 if !closer.close { return closer_token; }
145
146 let openers_for_marker = state.node.ext.get_or_insert_default::<OpenersBottom<MARKER>>();
151 let openers_parameter = (closer.open as usize) * 3 + closer.length % 3;
152
153 let min_opener_idx = openers_for_marker.0[openers_parameter];
154
155 let mut idx = state.node.children.len() - 1;
156 let mut new_min_opener_idx = idx;
157 while idx > min_opener_idx {
158 idx -= 1;
159
160 let Some(opener) = state.node.children[idx].cast::<EmphMarker>() else { continue; };
161
162 let mut opener = opener.clone();
163 if opener.open && opener.marker == closer.marker && !is_odd_match(&opener, &closer) {
164 while closer.remaining > 0 && opener.remaining > 0 {
165 let max_marker_len = min(3, min(opener.remaining, closer.remaining));
166 let mut matched_rule = None;
167 let fns = &state.md.ext.get::<PairConfig<MARKER>>().unwrap().fns;
168 for marker_len in (1..=max_marker_len).rev() {
169 if let Some(f) = fns[marker_len-1] {
170 matched_rule = Some((marker_len, f));
171 break;
172 }
173 }
174
175 if matched_rule.is_none() { break; }
179
180 let (marker_len, marker_fn) = matched_rule.unwrap();
181
182 closer.remaining -= marker_len;
183 opener.remaining -= marker_len;
184
185 let mut new_token = marker_fn();
186 new_token.children = state.node.children.split_off(idx + 1);
187
188 let mut end_map_pos = 0;
190 if let Some(map) = closer_token.srcmap {
191 let (start, end) = map.get_byte_offsets();
192 closer_token.srcmap = Some(SourcePos::new(start + marker_len, end));
193 end_map_pos = start + marker_len;
194 }
195
196 let mut start_map_pos = 0;
198 let opener_token = state.node.children.last_mut().unwrap();
199 if let Some(map) = opener_token.srcmap {
200 let (start, end) = map.get_byte_offsets();
201 opener_token.srcmap = Some(SourcePos::new(start, end - marker_len));
202 start_map_pos = end - marker_len;
203 }
204
205 new_token.srcmap = state.get_map(start_map_pos, end_map_pos);
206
207 if opener.remaining == 0 { state.node.children.pop(); }
209
210 new_min_opener_idx = 0;
211 state.node.children.push(new_token);
212
213 }
214 }
215
216 if opener.remaining > 0 {
217 state.node.children[idx].replace(opener);
218 } }
220
221 if new_min_opener_idx != 0 {
222 let openers_for_marker = state.node.ext.get_or_insert_default::<OpenersBottom<MARKER>>();
230 openers_for_marker.0[openers_parameter] = new_min_opener_idx;
231 }
232
233 if closer.remaining > 0 {
235 closer_token.replace(closer);
236 closer_token
237 } else {
238 state.node.children.pop().unwrap()
239 }
240}
241
242
243fn is_odd_match(opener: &EmphMarker, closer: &EmphMarker) -> bool {
244 #[allow(clippy::collapsible_if)]
252 if opener.close || closer.open {
253 if (opener.length + closer.length) % 3 == 0 {
254 if opener.length % 3 != 0 || closer.length % 3 != 0 {
255 return true;
256 }
257 }
258 }
259
260 false
261}
262
263
264#[doc(hidden)]
265pub struct FragmentsJoin;
266impl CoreRule for FragmentsJoin {
267 fn run(node: &mut Node, _: &MarkdownIt) {
268 node.walk_mut(|node, _| fragments_join(node));
269 }
270}
271
272
273fn fragments_join(node: &mut Node) {
282 for token in node.children.iter_mut() {
284 if let Some(data) = token.cast::<EmphMarker>() {
285 let content = data.marker.to_string().repeat(data.remaining);
286 token.replace(Text { content });
287 }
288 }
289
290 for idx in 1..node.children.len() {
292 let ( tokens1, tokens2 ) = node.children.split_at_mut(idx);
293
294 let token1 = tokens1.last_mut().unwrap();
295 let Some(t1_data) = token1.cast_mut::<Text>() else { continue; };
296
297 let token2 = tokens2.first_mut().unwrap();
298 let Some(t2_data) = token2.cast_mut::<Text>() else { continue; };
299
300 let t2_content = std::mem::take(&mut t2_data.content);
302 t1_data.content += &t2_content;
303
304 if let Some(map1) = token1.srcmap {
306 if let Some(map2) = token2.srcmap {
307 token1.srcmap = Some(SourcePos::new(
308 map1.get_byte_offsets().0,
309 map2.get_byte_offsets().1
310 ));
311 }
312 }
313
314 node.children.swap(idx - 1, idx);
315 }
316
317 node.children.retain(|token| {
319 if let Some(data) = token.cast::<Text>() {
320 !data.content.is_empty()
321 } else {
322 true
323 }
324 });
325}