Skip to main content

markdown/edit/
text.rs

1//! Edits within one run of text: splicing, and marks over a range.
2
3use super::*;
4
5impl Text {
6    /// Insert at a byte offset, moving the marks with it.
7    pub fn insert(&mut self, at: usize, s: &str) {
8        let at = at.min(self.text.len());
9        if s.is_empty() {
10            return;
11        }
12        let n = s.len();
13        self.text.insert_str(at, s);
14        for span in &mut self.marks {
15            if at <= span.range.start {
16                span.range.start += n;
17                span.range.end += n;
18            } else if at < span.range.end
19                || (at == span.range.end && !matches!(span.mark, Mark::Mention { .. }))
20            {
21                // Inside, or exactly at the end — the left-sticky rule, which a
22                // mention does not follow.
23                span.range.end += n;
24            }
25        }
26        self.normalize_marks();
27    }
28
29    /// Remove a byte range, collapsing any mark that covered it.
30    pub fn remove(&mut self, range: Range<usize>) {
31        let range = self.clamp(range);
32        if range.is_empty() {
33            return;
34        }
35        self.text.replace_range(range.clone(), "");
36        let shift = |offset: usize| {
37            if offset <= range.start {
38                offset
39            } else if offset >= range.end {
40                offset - range.len()
41            } else {
42                range.start
43            }
44        };
45        for span in &mut self.marks {
46            span.range = shift(span.range.start)..shift(span.range.end);
47        }
48        self.normalize_marks();
49    }
50
51    /// Add `mark` over `range`, or take it away if the whole range has it.
52    pub fn toggle(&mut self, range: Range<usize>, mark: Mark) {
53        let range = self.clamp(range);
54        if range.is_empty() {
55            return;
56        }
57        if self.covered_by(&range, &mark) {
58            self.marks = std::mem::take(&mut self.marks)
59                .into_iter()
60                .flat_map(|span| subtract(span, &range, &mark))
61                .collect();
62        } else {
63            self.marks.push(MarkSpan { range, mark });
64        }
65        self.normalize_marks();
66    }
67
68    /// Whether every byte of `range` already carries `mark`.
69    pub fn covered_by(&self, range: &Range<usize>, mark: &Mark) -> bool {
70        !range.is_empty()
71            && self.marks.iter().any(|span| {
72                span.mark == *mark && span.range.start <= range.start && span.range.end >= range.end
73            })
74    }
75
76    /// Cut at `at`, returning the tail. The head keeps this [`Text`].
77    pub fn split_off(&mut self, at: usize) -> Text {
78        let at = at.min(self.text.len());
79        let mut tail = Text {
80            text: self.text.split_off(at),
81            marks: Vec::new(),
82        };
83        let mut head = Vec::new();
84        for span in std::mem::take(&mut self.marks) {
85            if span.range.start < at {
86                head.push(MarkSpan {
87                    range: span.range.start..span.range.end.min(at),
88                    mark: span.mark.clone(),
89                });
90            }
91            if span.range.end > at {
92                tail.marks.push(MarkSpan {
93                    range: span.range.start.saturating_sub(at)..span.range.end - at,
94                    mark: span.mark,
95                });
96            }
97        }
98        self.marks = head;
99        self.normalize_marks();
100        tail.normalize_marks();
101        tail
102    }
103
104    /// Append `other`, shifting its marks onto the end of this text.
105    pub fn append(&mut self, other: Text) {
106        let offset = self.text.len();
107        self.text.push_str(&other.text);
108        self.marks
109            .extend(other.marks.into_iter().map(|span| MarkSpan {
110                range: span.range.start + offset..span.range.end + offset,
111                mark: span.mark,
112            }));
113        self.normalize_marks();
114    }
115
116    pub(super) fn clamp(&self, range: Range<usize>) -> Range<usize> {
117        let start = range.start.min(self.text.len());
118        let end = range.end.clamp(start, self.text.len());
119        start..end
120    }
121
122    /// Drop marks that cover nothing and merge ones that touch.
123    ///
124    /// Both matter to the round trip rather than to tidiness: an empty bold
125    /// span serializes to `****`, which is literal text, and two abutting bold
126    /// spans serialize to `**a****b**`, which is not one bold run.
127    pub(crate) fn normalize_marks(&mut self) {
128        // Emphasis cannot open or close against whitespace — `* t*` is two
129        // literal asterisks, not italic — so a mark reaching over a space has
130        // no spelling that survives a round trip. Shrinking it to the text it
131        // can actually cover is also what a user means when a drag-selection
132        // catches the trailing space.
133        for ix in 0..self.marks.len() {
134            if !matches!(
135                self.marks[ix].mark,
136                Mark::Bold | Mark::Italic | Mark::Strike | Mark::Code
137            ) {
138                continue;
139            }
140            let range = self.marks[ix].range.clone();
141            if range.end > self.text.len() {
142                continue;
143            }
144            let slice = &self.text[range.clone()];
145            let start = range.start + (slice.len() - slice.trim_start().len());
146            let end = (range.end - (slice.len() - slice.trim_end().len())).max(start);
147            self.marks[ix].range = start..end;
148        }
149
150        let len = self.text.len();
151        self.marks.retain(|span| {
152            span.range.end <= len && (!span.range.is_empty() || matches!(span.mark, Mark::Image(_)))
153        });
154
155        // A code span is atomic: nothing can start or stop inside one. A mark
156        // that only half covers it has no spelling, so it grows to take the
157        // whole span — which is also what the markdown for it reads back as.
158        let code: Vec<Range<usize>> = self
159            .marks
160            .iter()
161            .filter(|span| span.mark == Mark::Code)
162            .map(|span| span.range.clone())
163            .collect();
164        for span in &mut self.marks {
165            if span.mark == Mark::Code {
166                continue;
167            }
168            for range in &code {
169                let crosses = span.range.start > range.start && span.range.start < range.end
170                    || span.range.end > range.start && span.range.end < range.end;
171                if crosses {
172                    span.range.start = span.range.start.min(range.start);
173                    span.range.end = span.range.end.max(range.end);
174                }
175            }
176        }
177
178        // Emphasis nests or it is disjoint; it cannot cross. `**a*b**c*` is
179        // not bold-then-italic overlapping, it is a parse error waiting to
180        // happen — so when two spans cross, the one that opened first grows to
181        // contain the other. Growing rather than clipping keeps every mark the
182        // user applied; only its reach changes, and only where markdown left
183        // no alternative.
184        for _ in 0..self.marks.len().max(1) {
185            let mut crossed = false;
186            for a in 0..self.marks.len() {
187                for b in 0..self.marks.len() {
188                    let (first, second) = (&self.marks[a].range, &self.marks[b].range);
189                    if second.start > first.start
190                        && second.start < first.end
191                        && second.end > first.end
192                    {
193                        let end = second.end;
194                        self.marks[a].range.end = end;
195                        crossed = true;
196                    }
197                }
198            }
199            if !crossed {
200                break;
201            }
202        }
203
204        // Two marks that end at the same offset close as two delimiter runs
205        // back to back — `**b**~~`. CommonMark will not let the outer one close
206        // there if a letter follows: a run preceded by punctuation has to be
207        // followed by whitespace or punctuation to be right-flanking, so
208        // `~~a **b**~~c` cannot be written at all. Nudging the outer end past
209        // the word separates the two runs and it can.
210        for _ in 0..self.marks.len().max(1) {
211            let mut nudged = false;
212            for a in 0..self.marks.len() {
213                let end = self.marks[a].range.end;
214                let followed_by_word = self.text[end..]
215                    .chars()
216                    .next()
217                    .is_some_and(char::is_alphanumeric);
218                let shared = self.marks.iter().enumerate().any(|(b, other)| {
219                    b != a
220                        && other.range.end == end
221                        && other.range.start > self.marks[a].range.start
222                });
223                if followed_by_word && shared {
224                    let extra = self.text[end..]
225                        .find(|c: char| !c.is_alphanumeric())
226                        .unwrap_or(self.text.len() - end);
227                    self.marks[a].range.end = end + extra;
228                    nudged = true;
229                }
230            }
231            if !nudged {
232                break;
233            }
234        }
235
236        let mut ix = 0;
237        while ix < self.marks.len() {
238            let mut merged = None;
239            for other in ix + 1..self.marks.len() {
240                let (a, b) = (&self.marks[ix], &self.marks[other]);
241                if a.mark == b.mark
242                    && a.range.start <= b.range.end
243                    && b.range.start <= a.range.end
244                    && !matches!(a.mark, Mark::Image(_) | Mark::Mention { .. })
245                {
246                    merged = Some((
247                        other,
248                        a.range.start.min(b.range.start),
249                        a.range.end.max(b.range.end),
250                    ));
251                    break;
252                }
253            }
254            match merged {
255                Some((other, start, end)) => {
256                    self.marks[ix].range = start..end;
257                    self.marks.remove(other);
258                }
259                None => ix += 1,
260            }
261        }
262
263        // Document order, outermost first — the order a parse produces, so an
264        // edited document compares equal to the same document read from disk.
265        // The sort is stable, which is what keeps `**_x_**` and `_**x**_`
266        // apart: their spans are identical and only their order differs.
267        self.marks.sort_by(|a, b| {
268            a.range
269                .start
270                .cmp(&b.range.start)
271                .then(b.range.end.cmp(&a.range.end))
272        });
273    }
274}
275
276/// `span` minus `range`, when they share a mark — zero, one or two pieces.
277fn subtract(span: MarkSpan, range: &Range<usize>, mark: &Mark) -> Vec<MarkSpan> {
278    if span.mark != *mark || span.range.end <= range.start || span.range.start >= range.end {
279        return vec![span];
280    }
281    let mut out = Vec::new();
282    if span.range.start < range.start {
283        out.push(MarkSpan {
284            range: span.range.start..range.start,
285            mark: span.mark.clone(),
286        });
287    }
288    if span.range.end > range.end {
289        out.push(MarkSpan {
290            range: range.end..span.range.end,
291            mark: span.mark,
292        });
293    }
294    out
295}