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