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