1use super::*;
4
5pub fn parse_at(source: &str, offset: usize, marks: &Marks) -> (Doc, Cursor) {
15 let (doc, at) = parse_at_many(source, &[offset], marks);
16 let at = at[0].unwrap_or_else(|| Cursor::default().clamp(&doc));
17 (doc, at)
18}
19
20pub fn parse_at_many(source: &str, offsets: &[usize], marks: &Marks) -> (Doc, Vec<Option<Cursor>>) {
24 let plain = parse_with(source, marks);
25 let mut at = vec![None; offsets.len()];
26 if !source
27 .chars()
28 .any(|c| crate::serialize::sentinel_index(c).is_some())
29 {
30 let offset = |ix: usize| offsets[ix].min(source.len());
31 let mut which: Vec<usize> = (0..offsets.len())
32 .filter(|&ix| {
33 source.is_char_boundary(offset(ix)) && crate::serialize::sentinel(ix).is_some()
34 })
35 .collect();
36 which.sort_by_key(|&ix| offset(ix));
37 place(source, &offset, &which, &plain, marks, &mut at);
38 }
39 (plain, at)
40}
41
42fn place(
46 source: &str,
47 offset: &impl Fn(usize) -> usize,
48 which: &[usize],
49 plain: &Doc,
50 marks: &Marks,
51 out: &mut [Option<Cursor>],
52) {
53 if which.is_empty() {
54 return;
55 }
56 if let Some(found) = lifted(source, offset, which, plain, marks) {
57 for (ix, at) in found {
58 out[ix] = Some(at);
59 }
60 } else if which.len() > 1 {
61 let (left, right) = which.split_at(which.len() / 2);
62 place(source, offset, left, plain, marks, out);
63 place(source, offset, right, plain, marks, out);
64 }
65}
66
67fn lifted(
70 source: &str,
71 offset: &impl Fn(usize) -> usize,
72 which: &[usize],
73 plain: &Doc,
74 marks: &Marks,
75) -> Option<Vec<(usize, Cursor)>> {
76 let mut marked = String::with_capacity(source.len() + 4 * which.len());
77 let mut from = 0;
78 for &ix in which {
79 marked.push_str(&source[from..offset(ix)]);
80 marked.push(crate::serialize::sentinel(ix)?);
81 from = offset(ix);
82 }
83 marked.push_str(&source[from..]);
84
85 let mut doc = parse_with(&marked, marks);
86 let mut found = Vec::with_capacity(which.len());
87 for (block_ix, block) in doc.blocks.iter_mut().enumerate() {
88 for part in block.parts() {
89 let Some(text) = block.text_at_mut(part) else {
90 continue;
91 };
92 let hits: Vec<(usize, char)> = text
93 .text
94 .char_indices()
95 .filter(|(_, c)| crate::serialize::sentinel_index(*c).is_some())
96 .collect();
97 let mut removed = 0;
98 for &(at, c) in &hits {
99 let ix = crate::serialize::sentinel_index(c)?;
100 found.push((ix, Cursor::new(block_ix, part, at - removed)));
101 removed += c.len_utf8();
102 }
103 for &(at, c) in hits.iter().rev() {
104 text.remove(at..at + c.len_utf8());
105 }
106 }
107 }
108 (found.len() == which.len() && doc == *plain).then_some(found)
111}
112
113pub(super) const OPEN: char = '\u{E010}';
120pub(super) const CLOSE: char = '\u{E011}';
121
122pub(super) fn tag(ix: usize) -> Option<char> {
125 char::from_u32(0xE020 + u32::try_from(ix).ok()?).filter(|_| ix < 0x100)
126}
127
128pub(super) fn tag_index(c: char) -> Option<usize> {
129 (0xE020..0xE120)
130 .contains(&(c as u32))
131 .then(|| c as usize - 0xE020)
132}
133
134pub(super) fn lift(source: &str, marks: &Marks) -> String {
136 let skipped = literal(source);
137 let entries = marks.sorted();
138 let mut out = String::with_capacity(source.len());
139 let mut open: Vec<(usize, &str)> = Vec::new();
140 let mut at = 0usize;
141
142 while at < source.len() {
143 if let Some(range) = skipped.iter().find(|range| range.contains(&at)) {
146 out.push_str(&source[at..range.end]);
147 at = range.end;
148 continue;
149 }
150 let rest = &source[at..];
151 if let Some(escaped) = rest.strip_prefix('\\') {
153 let width = escaped.chars().next().map_or(1, |c| 1 + c.len_utf8());
154 out.push_str(&rest[..width.min(rest.len())]);
155 at += width.min(rest.len());
156 continue;
157 }
158 let found = entries
159 .iter()
160 .find(|entry| rest.starts_with(entry.delimiter.as_ref()));
161 if let Some(entry) = found {
162 let delimiter: &str = entry.delimiter.as_ref();
163 let closes = open.last().is_some_and(|(_, open)| *open == delimiter);
164 if closes && !source[..at].ends_with(char::is_whitespace) {
165 out.push(CLOSE);
166 open.pop();
167 at += delimiter.len();
168 continue;
169 }
170 if !closes
171 && let Some(ix) = marks.position(entry)
172 && let Some(tag) = tag(ix)
173 && closing(
174 source,
175 at + delimiter.len(),
176 delimiter,
177 &skipped,
178 line_end(source, at),
179 )
180 {
181 out.push(OPEN);
182 out.push(tag);
183 open.push((ix, delimiter));
184 at += delimiter.len();
185 continue;
186 }
187 }
188 let c = rest.chars().next().unwrap_or_default();
189 out.push(c);
190 at += c.len_utf8();
191 }
192 out
193}
194
195pub(super) fn closing(
206 source: &str,
207 from: usize,
208 delimiter: &str,
209 skipped: &[Range<usize>],
210 line_end: usize,
211) -> bool {
212 if source[from..].starts_with(char::is_whitespace) {
213 return false;
214 }
215 let mut at = from;
216 while let Some(found) = source[at..line_end.max(at)].find(delimiter) {
217 let found = at + found;
218 let escaped = source[..found].ends_with('\\');
219 let literal = skipped.iter().any(|range| range.contains(&found));
220 let spaced = source[..found].ends_with(char::is_whitespace);
221 if !escaped && !literal && !spaced && found > from {
222 return true;
223 }
224 at = found + delimiter.len();
225 }
226 false
227}
228
229pub(super) fn line_end(source: &str, at: usize) -> usize {
231 source[at..].find('\n').map_or(source.len(), |ix| at + ix)
232}
233
234pub(super) fn literal(source: &str) -> Vec<Range<usize>> {
237 let mut out = Vec::new();
238 for (event, range) in Parser::new_ext(source, OPTIONS).into_offset_iter() {
239 match event {
240 Event::Code(_) | Event::Html(_) | Event::InlineHtml(_) => out.push(range),
241 Event::Start(Tag::CodeBlock(_)) => out.push(range),
242 Event::Start(Tag::Link { .. }) => {
246 let at = source[range.clone()]
247 .rfind("](")
248 .map_or(range.start, |ix| range.start + ix);
249 out.push(at..range.end);
250 }
251 Event::Start(Tag::Image { .. }) => out.push(range),
255 _ => {}
256 }
257 }
258 out
259}
260
261pub(super) fn settle(text: &mut Text, marks: &Marks) {
265 if !text.text.contains(OPEN) {
266 return;
267 }
268 let mut settled = String::with_capacity(text.text.len());
269 let mut cut: Vec<(usize, usize)> = Vec::new();
271 let mut open: Vec<(usize, usize, usize)> = Vec::new();
275 let mut found: Vec<(usize, MarkSpan)> = Vec::new();
276 let mut opened = 0usize;
277 let mut chars = text.text.char_indices();
278
279 while let Some((at, c)) = chars.next() {
280 match c {
281 OPEN => {
282 let width = match chars.next() {
283 Some((_, tag)) => {
284 if let Some(ix) = tag_index(tag) {
285 open.push((ix, settled.len(), opened));
286 opened += 1;
287 }
288 OPEN.len_utf8() + tag.len_utf8()
289 }
290 None => OPEN.len_utf8(),
291 };
292 cut.push((at, width));
293 }
294 CLOSE => {
295 if let Some((ix, from, seq)) = open.pop()
296 && let Some(entry) = marks.index(ix)
297 {
298 found.push((
299 seq,
300 MarkSpan {
301 range: from..settled.len(),
302 mark: Mark::Custom(entry.name.to_string()),
303 },
304 ));
305 }
306 cut.push((at, CLOSE.len_utf8()));
307 }
308 _ => settled.push(c),
309 }
310 }
311
312 let moved = |offset: usize| {
313 offset
314 - cut
315 .iter()
316 .filter(|(at, _)| *at < offset)
317 .map(|(_, width)| width)
318 .sum::<usize>()
319 };
320 for span in &mut text.marks {
321 span.range = moved(span.range.start)..moved(span.range.end);
322 }
323 text.text = settled;
324 found.sort_by_key(|(seq, _)| *seq);
325 text.marks.extend(found.into_iter().map(|(_, span)| span));
326 text.marks
329 .sort_by_key(|span| (span.range.start, std::cmp::Reverse(span.range.end)));
330 text.marks.retain(|span| !span.range.is_empty());
331}