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, Vec<(usize, usize)>) {
139 let skipped = literal(source);
140 let mut aligned = Vec::new();
141 let entries = marks.sorted();
142 let mut out = String::with_capacity(source.len());
143 let mut open: Vec<(usize, &str)> = Vec::new();
144 let mut at = 0usize;
145
146 while at < source.len() {
147 if let Some(range) = covering(&skipped, at) {
150 out.push_str(&source[at..range.end]);
151 at = range.end;
152 continue;
153 }
154 let rest = &source[at..];
155 if let Some(escaped) = rest.strip_prefix('\\') {
157 let width = escaped.chars().next().map_or(1, |c| 1 + c.len_utf8());
158 out.push_str(&rest[..width.min(rest.len())]);
159 at += width.min(rest.len());
160 continue;
161 }
162 let found = entries
163 .iter()
164 .find(|entry| rest.starts_with(entry.delimiter.as_ref()));
165 if let Some(entry) = found {
166 let delimiter: &str = entry.delimiter.as_ref();
167 let closes = open.last().is_some_and(|(_, open)| *open == delimiter);
168 if closes && !source[..at].ends_with(char::is_whitespace) {
169 out.push(CLOSE);
170 open.pop();
171 at += delimiter.len();
172 aligned.push((out.len(), at));
173 continue;
174 }
175 if !closes
176 && let Some(ix) = marks.position(entry)
177 && let Some(tag) = tag(ix)
178 && closing(
179 source,
180 at + delimiter.len(),
181 delimiter,
182 &skipped,
183 line_end(source, at),
184 )
185 {
186 out.push(OPEN);
187 out.push(tag);
188 open.push((ix, delimiter));
189 at += delimiter.len();
190 aligned.push((out.len(), at));
191 continue;
192 }
193 }
194 let c = rest.chars().next().unwrap_or_default();
195 out.push(c);
196 at += c.len_utf8();
197 }
198 (out, aligned)
199}
200
201pub(super) fn unlift(aligned: &[(usize, usize)], lifted: usize) -> usize {
203 let ix = aligned.partition_point(|(at, _)| *at <= lifted);
204 match ix.checked_sub(1) {
205 Some(ix) => aligned[ix].1 + (lifted - aligned[ix].0),
206 None => lifted,
207 }
208}
209
210pub(super) fn closing(
221 source: &str,
222 from: usize,
223 delimiter: &str,
224 skipped: &[Range<usize>],
225 line_end: usize,
226) -> bool {
227 if source[from..].starts_with(char::is_whitespace) {
228 return false;
229 }
230 let mut at = from;
231 while let Some(found) = source[at..line_end.max(at)].find(delimiter) {
232 let found = at + found;
233 let escaped = source[..found].ends_with('\\');
234 let literal = covering(skipped, found).is_some();
235 let spaced = source[..found].ends_with(char::is_whitespace);
236 if !escaped && !literal && !spaced && found > from {
237 return true;
238 }
239 at = found + delimiter.len();
240 }
241 false
242}
243
244pub(super) fn line_end(source: &str, at: usize) -> usize {
246 source[at..].find('\n').map_or(source.len(), |ix| at + ix)
247}
248
249fn covering(ranges: &[Range<usize>], at: usize) -> Option<&Range<usize>> {
252 let ix = ranges.partition_point(|range| range.end <= at);
253 ranges.get(ix).filter(|range| range.start <= at)
254}
255
256pub(super) fn literal(source: &str) -> Vec<Range<usize>> {
260 let mut out: Vec<Range<usize>> = Vec::new();
261 for (event, range) in Parser::new_ext(source, OPTIONS).into_offset_iter() {
262 match event {
263 Event::Code(_) | Event::Html(_) | Event::InlineHtml(_) => out.push(range),
264 Event::Start(Tag::CodeBlock(_)) => out.push(range),
265 Event::Start(Tag::Link { .. }) => {
269 let at = source[range.clone()]
270 .rfind("](")
271 .map_or(range.start, |ix| range.start + ix);
272 out.push(at..range.end);
273 }
274 Event::Start(Tag::Image { .. }) => out.push(range),
278 _ => {}
279 }
280 }
281 out.retain(|range| !range.is_empty());
282 out.sort_by_key(|range| range.start);
283 let mut merged: Vec<Range<usize>> = Vec::with_capacity(out.len());
284 for range in out {
285 match merged.last_mut() {
286 Some(last) if range.start < last.end => last.end = last.end.max(range.end),
287 _ => merged.push(range),
288 }
289 }
290 merged
291}
292
293pub(super) fn settle(text: &mut Text, marks: &Marks) {
297 if !text.text.contains(OPEN) {
298 return;
299 }
300 let mut settled = String::with_capacity(text.text.len());
301 let mut cut: Vec<(usize, usize)> = Vec::new();
303 let mut open: Vec<(usize, usize, usize)> = Vec::new();
307 let mut found: Vec<(usize, MarkSpan)> = Vec::new();
308 let mut opened = 0usize;
309 let mut chars = text.text.char_indices();
310
311 while let Some((at, c)) = chars.next() {
312 match c {
313 OPEN => {
314 let width = match chars.next() {
315 Some((_, tag)) => {
316 if let Some(ix) = tag_index(tag) {
317 open.push((ix, settled.len(), opened));
318 opened += 1;
319 }
320 OPEN.len_utf8() + tag.len_utf8()
321 }
322 None => OPEN.len_utf8(),
323 };
324 cut.push((at, width));
325 }
326 CLOSE => {
327 if let Some((ix, from, seq)) = open.pop()
328 && let Some(entry) = marks.index(ix)
329 {
330 found.push((
331 seq,
332 MarkSpan {
333 range: from..settled.len(),
334 mark: Mark::Custom(entry.name.to_string()),
335 },
336 ));
337 }
338 cut.push((at, CLOSE.len_utf8()));
339 }
340 _ => settled.push(c),
341 }
342 }
343
344 let moved = |offset: usize| {
345 offset
346 - cut
347 .iter()
348 .filter(|(at, _)| *at < offset)
349 .map(|(_, width)| width)
350 .sum::<usize>()
351 };
352 for span in &mut text.marks {
353 span.range = moved(span.range.start)..moved(span.range.end);
354 }
355 text.text = settled;
356 found.sort_by_key(|(seq, _)| *seq);
357 text.marks.extend(found.into_iter().map(|(_, span)| span));
358 text.marks
361 .sort_by_key(|span| (span.range.start, std::cmp::Reverse(span.range.end)));
362 text.marks.retain(|span| !span.range.is_empty());
363}