Skip to main content

markdown/
edit.rs

1//! Editing a [`Doc`].
2//!
3//! This is the half of a block editor that has nothing to do with gpui: text
4//! goes in and out of a [`Text`], marks move with it, and blocks split, merge
5//! and indent. Keeping it pure is what makes it testable — the guarantee below
6//! is checked over generated edit sequences, not over the handful of cases
7//! anyone thinks to write down.
8//!
9//! **The guarantee is [`crate::serialize`]'s, preserved.** Call
10//! [`Doc::normalize`] and the document round-trips: serialize, parse, and
11//! nothing moves. An editor that can reach a state its own serializer cannot
12//! express is an editor that corrupts the file on save, and no amount of UI
13//! polish recovers from that.
14//!
15//! Normalizing is a *save* step rather than a keystroke step, and deliberately.
16//! Markdown cannot hold a space at the end of a line, but stripping one the
17//! moment it is typed takes it away mid-word — so the model carries it and
18//! sheds it on the way out, which is what every editor that writes markdown
19//! does.
20//!
21//! Marks are **left-sticky**: text typed at the end of a bold run is bold, text
22//! typed at its start is not. The caret inherits formatting from the character
23//! before it, which is what every editor does and what nobody notices until it
24//! is wrong.
25
26use std::ops::Range;
27
28use crate::{
29    doc::{Block, BlockKind, Doc, Mark, MarkSpan, Part, Text},
30    select::{Cursor, Selection},
31};
32
33impl Text {
34    /// Insert at a byte offset, moving the marks with it.
35    pub fn insert(&mut self, at: usize, s: &str) {
36        let at = at.min(self.text.len());
37        if s.is_empty() {
38            return;
39        }
40        let n = s.len();
41        self.text.insert_str(at, s);
42        for span in &mut self.marks {
43            if at <= span.range.start {
44                span.range.start += n;
45                span.range.end += n;
46            } else if at <= span.range.end {
47                // Inside, or exactly at the end — the left-sticky rule.
48                span.range.end += n;
49            }
50        }
51        self.normalize_marks();
52    }
53
54    /// Remove a byte range, collapsing any mark that covered it.
55    pub fn remove(&mut self, range: Range<usize>) {
56        let range = self.clamp(range);
57        if range.is_empty() {
58            return;
59        }
60        self.text.replace_range(range.clone(), "");
61        let shift = |offset: usize| {
62            if offset <= range.start {
63                offset
64            } else if offset >= range.end {
65                offset - range.len()
66            } else {
67                range.start
68            }
69        };
70        for span in &mut self.marks {
71            span.range = shift(span.range.start)..shift(span.range.end);
72        }
73        self.normalize_marks();
74    }
75
76    /// Add `mark` over `range`, or take it away if the whole range has it.
77    pub fn toggle(&mut self, range: Range<usize>, mark: Mark) {
78        let range = self.clamp(range);
79        if range.is_empty() {
80            return;
81        }
82        if self.covered_by(&range, &mark) {
83            self.marks = std::mem::take(&mut self.marks)
84                .into_iter()
85                .flat_map(|span| subtract(span, &range, &mark))
86                .collect();
87        } else {
88            self.marks.push(MarkSpan { range, mark });
89        }
90        self.normalize_marks();
91    }
92
93    /// Whether every byte of `range` already carries `mark`.
94    pub fn covered_by(&self, range: &Range<usize>, mark: &Mark) -> bool {
95        !range.is_empty()
96            && self.marks.iter().any(|span| {
97                span.mark == *mark && span.range.start <= range.start && span.range.end >= range.end
98            })
99    }
100
101    /// Cut at `at`, returning the tail. The head keeps this [`Text`].
102    pub fn split_off(&mut self, at: usize) -> Text {
103        let at = at.min(self.text.len());
104        let mut tail = Text {
105            text: self.text.split_off(at),
106            marks: Vec::new(),
107        };
108        let mut head = Vec::new();
109        for span in std::mem::take(&mut self.marks) {
110            if span.range.start < at {
111                head.push(MarkSpan {
112                    range: span.range.start..span.range.end.min(at),
113                    mark: span.mark.clone(),
114                });
115            }
116            if span.range.end > at {
117                tail.marks.push(MarkSpan {
118                    range: span.range.start.saturating_sub(at)..span.range.end - at,
119                    mark: span.mark,
120                });
121            }
122        }
123        self.marks = head;
124        self.normalize_marks();
125        tail.normalize_marks();
126        tail
127    }
128
129    /// Append `other`, shifting its marks onto the end of this text.
130    pub fn append(&mut self, other: Text) {
131        let offset = self.text.len();
132        self.text.push_str(&other.text);
133        self.marks
134            .extend(other.marks.into_iter().map(|span| MarkSpan {
135                range: span.range.start + offset..span.range.end + offset,
136                mark: span.mark,
137            }));
138        self.normalize_marks();
139    }
140
141    fn clamp(&self, range: Range<usize>) -> Range<usize> {
142        let start = range.start.min(self.text.len());
143        let end = range.end.clamp(start, self.text.len());
144        start..end
145    }
146
147    /// Drop marks that cover nothing and merge ones that touch.
148    ///
149    /// Both matter to the round trip rather than to tidiness: an empty bold
150    /// span serializes to `****`, which is literal text, and two abutting bold
151    /// spans serialize to `**a****b**`, which is not one bold run.
152    pub(crate) fn normalize_marks(&mut self) {
153        // Emphasis cannot open or close against whitespace — `* t*` is two
154        // literal asterisks, not italic — so a mark reaching over a space has
155        // no spelling that survives a round trip. Shrinking it to the text it
156        // can actually cover is also what a user means when a drag-selection
157        // catches the trailing space.
158        for ix in 0..self.marks.len() {
159            if !matches!(
160                self.marks[ix].mark,
161                Mark::Bold | Mark::Italic | Mark::Strike | Mark::Code
162            ) {
163                continue;
164            }
165            let range = self.marks[ix].range.clone();
166            if range.end > self.text.len() {
167                continue;
168            }
169            let slice = &self.text[range.clone()];
170            let start = range.start + (slice.len() - slice.trim_start().len());
171            let end = (range.end - (slice.len() - slice.trim_end().len())).max(start);
172            self.marks[ix].range = start..end;
173        }
174
175        let len = self.text.len();
176        self.marks.retain(|span| {
177            span.range.end <= len && (!span.range.is_empty() || matches!(span.mark, Mark::Image(_)))
178        });
179
180        // A code span is atomic: nothing can start or stop inside one. A mark
181        // that only half covers it has no spelling, so it grows to take the
182        // whole span — which is also what the markdown for it reads back as.
183        let code: Vec<Range<usize>> = self
184            .marks
185            .iter()
186            .filter(|span| span.mark == Mark::Code)
187            .map(|span| span.range.clone())
188            .collect();
189        for span in &mut self.marks {
190            if span.mark == Mark::Code {
191                continue;
192            }
193            for range in &code {
194                let crosses = span.range.start > range.start && span.range.start < range.end
195                    || span.range.end > range.start && span.range.end < range.end;
196                if crosses {
197                    span.range.start = span.range.start.min(range.start);
198                    span.range.end = span.range.end.max(range.end);
199                }
200            }
201        }
202
203        // Emphasis nests or it is disjoint; it cannot cross. `**a*b**c*` is
204        // not bold-then-italic overlapping, it is a parse error waiting to
205        // happen — so when two spans cross, the one that opened first grows to
206        // contain the other. Growing rather than clipping keeps every mark the
207        // user applied; only its reach changes, and only where markdown left
208        // no alternative.
209        for _ in 0..self.marks.len().max(1) {
210            let mut crossed = false;
211            for a in 0..self.marks.len() {
212                for b in 0..self.marks.len() {
213                    let (first, second) = (&self.marks[a].range, &self.marks[b].range);
214                    if second.start > first.start
215                        && second.start < first.end
216                        && second.end > first.end
217                    {
218                        let end = second.end;
219                        self.marks[a].range.end = end;
220                        crossed = true;
221                    }
222                }
223            }
224            if !crossed {
225                break;
226            }
227        }
228
229        // Two marks that end at the same offset close as two delimiter runs
230        // back to back — `**b**~~`. CommonMark will not let the outer one close
231        // there if a letter follows: a run preceded by punctuation has to be
232        // followed by whitespace or punctuation to be right-flanking, so
233        // `~~a **b**~~c` cannot be written at all. Nudging the outer end past
234        // the word separates the two runs and it can.
235        for _ in 0..self.marks.len().max(1) {
236            let mut nudged = false;
237            for a in 0..self.marks.len() {
238                let end = self.marks[a].range.end;
239                let followed_by_word = self.text[end..]
240                    .chars()
241                    .next()
242                    .is_some_and(char::is_alphanumeric);
243                let shared = self.marks.iter().enumerate().any(|(b, other)| {
244                    b != a
245                        && other.range.end == end
246                        && other.range.start > self.marks[a].range.start
247                });
248                if followed_by_word && shared {
249                    let extra = self.text[end..]
250                        .find(|c: char| !c.is_alphanumeric())
251                        .unwrap_or(self.text.len() - end);
252                    self.marks[a].range.end = end + extra;
253                    nudged = true;
254                }
255            }
256            if !nudged {
257                break;
258            }
259        }
260
261        let mut ix = 0;
262        while ix < self.marks.len() {
263            let mut merged = None;
264            for other in ix + 1..self.marks.len() {
265                let (a, b) = (&self.marks[ix], &self.marks[other]);
266                if a.mark == b.mark
267                    && a.range.start <= b.range.end
268                    && b.range.start <= a.range.end
269                    && !matches!(a.mark, Mark::Image(_) | Mark::Mention { .. })
270                {
271                    merged = Some((
272                        other,
273                        a.range.start.min(b.range.start),
274                        a.range.end.max(b.range.end),
275                    ));
276                    break;
277                }
278            }
279            match merged {
280                Some((other, start, end)) => {
281                    self.marks[ix].range = start..end;
282                    self.marks.remove(other);
283                }
284                None => ix += 1,
285            }
286        }
287
288        // Document order, outermost first — the order a parse produces, so an
289        // edited document compares equal to the same document read from disk.
290        // The sort is stable, which is what keeps `**_x_**` and `_**x**_`
291        // apart: their spans are identical and only their order differs.
292        self.marks.sort_by(|a, b| {
293            a.range
294                .start
295                .cmp(&b.range.start)
296                .then(b.range.end.cmp(&a.range.end))
297        });
298    }
299}
300
301/// `span` minus `range`, when they share a mark — zero, one or two pieces.
302fn subtract(span: MarkSpan, range: &Range<usize>, mark: &Mark) -> Vec<MarkSpan> {
303    if span.mark != *mark || span.range.end <= range.start || span.range.start >= range.end {
304        return vec![span];
305    }
306    let mut out = Vec::new();
307    if span.range.start < range.start {
308        out.push(MarkSpan {
309            range: span.range.start..range.start,
310            mark: span.mark.clone(),
311        });
312    }
313    if span.range.end > range.end {
314        out.push(MarkSpan {
315            range: range.end..span.range.end,
316            mark: span.mark,
317        });
318    }
319    out
320}
321
322impl Doc {
323    /// Split block `ix` at byte offset `at`, returning the new block's index.
324    ///
325    /// The tail keeps the block's kind so Enter in a list makes another item —
326    /// except for a heading, where the body that follows a title is body text.
327    pub fn split(&mut self, ix: usize, at: usize) -> usize {
328        if ix >= self.blocks.len() {
329            return ix;
330        }
331        let indent = self.blocks[ix].indent;
332        // Nothing to cut for a block with no body — Enter after an atomic
333        // block opens a paragraph.
334        let tail = self.blocks[ix]
335            .text_at_mut(Part::Body)
336            .map(|text| text.split_off(at))
337            .unwrap_or_default();
338        let kind = match &self.blocks[ix].kind {
339            BlockKind::Bullet(_) => BlockKind::Bullet(tail),
340            BlockKind::Ordered { .. } => BlockKind::Ordered {
341                number: 1,
342                text: tail,
343            },
344            BlockKind::Task { .. } => BlockKind::Task {
345                checked: false,
346                text: tail,
347            },
348            BlockKind::Quote(_) => BlockKind::Quote(tail),
349            // A heading titles what follows it; what follows is body text.
350            _ => BlockKind::Paragraph(tail),
351        };
352        self.blocks.insert(ix + 1, Block::at(kind, indent));
353        self.repair();
354        ix + 1
355    }
356
357    /// Backspace at the start of a block.
358    ///
359    /// Notion's chain, in order: an indented block outdents, an image with
360    /// nothing written under it goes, a block wearing syntax around its text
361    /// gives the syntax up, and only a plain block at the left margin merges
362    /// into the one above it. When that one holds no body there is nothing to
363    /// merge into, so the caret steps into a fence or a table, and a rule —
364    /// which no caret can enter, and so no other key can remove — goes.
365    /// Returns where the caret landed, and `None` when nothing moved.
366    ///
367    /// A table cell is not a position that can swallow its neighbour, so
368    /// backspace at the start of one does nothing rather than eating the table.
369    pub fn merge_back(&mut self, at: Cursor) -> Option<Cursor> {
370        if matches!(at.part, Part::Cell { .. }) {
371            return None;
372        }
373        let block = self.blocks.get(at.block)?;
374        if block.indent > 0 {
375            self.outdent(at.block);
376            return Some(Cursor::new(at.block, at.part, 0));
377        }
378        // A caption is the only handle a caret has on an image, so with the
379        // caption empty there is nothing left to take but the picture.
380        if at.part == Part::Caption && block.text_at(Part::Caption)?.is_empty() {
381            let previous = at.block.checked_sub(1);
382            self.blocks.remove(at.block);
383            self.repair();
384            let Some(previous) = previous else {
385                return Some(Cursor::default().clamp(self));
386            };
387            let part = self.blocks[previous]
388                .parts()
389                .last()
390                .copied()
391                .unwrap_or_default();
392            return Some(Cursor::new(previous, part, 0).end(self));
393        }
394        // Every prefix [`shortcut`] reads is chrome around text; the first
395        // backspace takes the chrome and leaves the text where it was, so what
396        // can be typed in can be typed out.
397        let unwrapped = match &block.kind {
398            kind if is_marker(kind) => block.text_at(Part::Body).cloned(),
399            BlockKind::Heading { text, .. } | BlockKind::Quote(text) => Some(text.clone()),
400            BlockKind::Code { code, .. } => Some(code.clone()),
401            _ => None,
402        };
403        if let Some(text) = unwrapped {
404            self.blocks[at.block].kind = BlockKind::Paragraph(text);
405            self.repair();
406            return Some(Cursor::new(at.block, Part::Body, 0));
407        }
408        if at.block == 0 {
409            return None;
410        }
411        let tail = self.blocks[at.block].text_at(Part::Body)?.clone();
412        let previous = at.block - 1;
413        match self.blocks[previous].parts().last().copied() {
414            // Only two blocks that both hold a body can become one.
415            Some(Part::Body) => {
416                let head = self.blocks[previous].text_at_mut(Part::Body)?;
417                let caret = head.text.len();
418                head.append(tail);
419                self.blocks.remove(at.block);
420                self.repair();
421                Some(Cursor::new(previous, Part::Body, caret))
422            }
423            Some(part) => {
424                let end = self.blocks[previous]
425                    .text_at(part)
426                    .map_or(0, |text| text.text.len());
427                Some(Cursor::new(previous, part, end))
428            }
429            None => {
430                self.blocks.remove(previous);
431                self.repair();
432                Some(Cursor::new(previous, at.part, at.offset))
433            }
434        }
435    }
436
437    /// Apply an edit to the text at `at`, then put the block back in order.
438    ///
439    /// The editor should reach text through here rather than mutating a block
440    /// directly: a heading or a table cell that acquires a newline has no
441    /// spelling, and nothing else is positioned to notice.
442    pub fn edit_at(&mut self, at: Cursor, edit: impl FnOnce(&mut Text)) {
443        let Some(block) = self.blocks.get_mut(at.block) else {
444            return;
445        };
446        let one_line = matches!(block.kind, BlockKind::Heading { .. })
447            || matches!(at.part, Part::Cell { .. } | Part::Caption);
448        let Some(text) = block.text_at_mut(at.part) else {
449            return;
450        };
451        edit(text);
452        if one_line {
453            crate::parse::collapse_to_one_line(text);
454        }
455    }
456
457    /// The blocks nested under `ix`, `ix` included — what a move, a duplicate
458    /// or a drag carries with it.
459    ///
460    /// A flat list makes this a scan for the next block that is not deeper,
461    /// which is the whole argument for the flat list.
462    pub fn subtree(&self, ix: usize) -> Range<usize> {
463        let Some(base) = self.blocks.get(ix).map(|block| block.indent) else {
464            return ix..ix;
465        };
466        let mut end = ix + 1;
467        while self
468            .blocks
469            .get(end)
470            .is_some_and(|block| block.indent > base)
471        {
472            end += 1;
473        }
474        ix..end
475    }
476
477    /// Move a block and its children to sit before or after their neighbour.
478    ///
479    /// `delta` counts *siblings*, not rows: moving down past a bullet with
480    /// three children clears all four, or a block would land inside the run it
481    /// was trying to step over.
482    pub fn move_block(&mut self, ix: usize, delta: isize) -> Option<usize> {
483        let span = self.subtree(ix);
484        if span.is_empty() {
485            return None;
486        }
487        let to = match delta {
488            ..0 => {
489                // The start of whichever subtree ends where this one begins.
490                (0..span.start)
491                    .rev()
492                    .find(|&above| self.subtree(above).end == span.start)?
493            }
494            0.. => {
495                let next = self.subtree(span.end);
496                if next.is_empty() {
497                    return None;
498                }
499                // Landing after the neighbour means landing where it ends,
500                // less the hole this subtree leaves behind.
501                next.end - span.len()
502            }
503        };
504        let moved: Vec<Block> = self.blocks.drain(span.clone()).collect();
505        self.blocks.splice(to..to, moved);
506        self.repair();
507        Some(to)
508    }
509
510    /// Copy a block and its children in below themselves.
511    pub fn duplicate(&mut self, ix: usize) -> Option<usize> {
512        let span = self.subtree(ix);
513        if span.is_empty() {
514            return None;
515        }
516        let copy: Vec<Block> = self.blocks[span.clone()].to_vec();
517        self.blocks.splice(span.end..span.end, copy);
518        self.repair();
519        Some(span.end)
520    }
521
522    /// Delete a block and its children.
523    pub fn remove_block(&mut self, ix: usize) {
524        let span = self.subtree(ix);
525        if span.is_empty() {
526            return;
527        }
528        self.blocks.drain(span);
529        self.repair();
530    }
531
532    /// Turn block `ix` into `kind`, carrying its text across and keeping its
533    /// indent.
534    ///
535    /// The one operation a typed prefix, the slash menu and the block menu all
536    /// perform, so none of them reaches into a block's kind on its own.
537    pub fn set_kind(&mut self, ix: usize, kind: BlockKind) {
538        let Some(block) = self.blocks.get_mut(ix) else {
539            return;
540        };
541        let text = match &block.kind {
542            // A bookmark's text is the link it shows, so turning one back into
543            // prose hands the URL over instead of an empty block.
544            BlockKind::Bookmark { url, .. } => Text::link(url),
545            BlockKind::Image { alt, .. } => alt.clone(),
546            _ => block.text_at(Part::Body).cloned().unwrap_or_default(),
547        };
548        block.kind = kind;
549        match block.text_at_mut(Part::Body) {
550            Some(body) => *body = text,
551            // The two kinds whose text is not a body. Code is also the one the
552            // marks cannot come with.
553            None => match &mut block.kind {
554                BlockKind::Code { code, .. } => *code = Text::plain(text.text),
555                BlockKind::Image { alt, .. } => *alt = text,
556                _ => {}
557            },
558        }
559        self.repair();
560    }
561
562    /// The tag on a fenced block — what the label shows, what the highlighter
563    /// reads, and what the info string carries. Not [`Doc::set_kind`]'s job:
564    /// that carries a *body* across, and a fence has none to give back.
565    pub fn set_language(&mut self, ix: usize, language: Option<String>) {
566        if let Some(BlockKind::Code { language: tag, .. }) =
567            self.blocks.get_mut(ix).map(|block| &mut block.kind)
568        {
569            *tag = language;
570        }
571    }
572
573    /// Turn what a selection covers into one code block, leaving whatever it
574    /// did not cover as blocks of its own.
575    ///
576    /// The fence is what markdown has for code over more than one line. An
577    /// inline span is not: no CommonMark spelling puts a line break inside
578    /// backticks, so one written that way comes back as a space.
579    ///
580    /// Marks are dropped on the way in, the way [`Doc::set_kind`] drops them
581    /// when it turns a block into a fence — code is literal to its closing
582    /// fence, and nothing in it is markup.
583    pub fn fence(&mut self, selection: Selection) -> Cursor {
584        let lines: Vec<String> = self
585            .spans(selection)
586            .iter()
587            .filter(|(at, _)| at.part == Part::Body)
588            .filter_map(|(at, range)| {
589                let text = self.blocks[at.block].text_at(at.part)?;
590                text.text.get(range.clone()).map(str::to_string)
591            })
592            .collect();
593        if lines.is_empty() {
594            return selection.head.clamp(self);
595        }
596        let code = Text::plain(lines.join("\n"));
597
598        // Cutting the selection leaves the head and the tail it did not cover
599        // joined in one block, with the caret at the seam between them — which
600        // is where the fence goes.
601        let at = self.replace(selection, Text::default());
602        let tail = self.split(at.block, at.offset);
603        let indent = self.blocks[at.block].indent;
604        self.blocks.insert(
605            tail,
606            Block::at(
607                BlockKind::Code {
608                    language: None,
609                    code,
610                },
611                indent,
612            ),
613        );
614        // A selection that covered whole blocks leaves nothing on either side,
615        // and an empty paragraph is not what "turn this into code" asked for.
616        let empty = |block: &Block| {
617            block
618                .text_at(Part::Body)
619                .is_some_and(|text| text.text.is_empty())
620        };
621        if self.blocks.get(tail + 1).is_some_and(empty) {
622            self.blocks.remove(tail + 1);
623        }
624        let mut fence = tail;
625        if empty(&self.blocks[at.block]) {
626            self.blocks.remove(at.block);
627            fence -= 1;
628        }
629        self.repair();
630        Cursor::new(fence, Part::Code, 0).clamp(self)
631    }
632
633    /// The way back out of a fence: every line becomes a paragraph. `None` when
634    /// the selection is not all code, which is what makes this the other half
635    /// of a toggle rather than an operation of its own.
636    pub fn unfence(&mut self, selection: Selection) -> Option<Cursor> {
637        let (start, end) = selection.clamp(self).ordered();
638        let blocks = start.block..=end.block;
639        if !blocks
640            .clone()
641            .all(|ix| matches!(self.blocks[ix].kind, BlockKind::Code { .. }))
642        {
643            return None;
644        }
645        for ix in blocks.rev() {
646            let BlockKind::Code { code, .. } = &self.blocks[ix].kind else {
647                continue;
648            };
649            let indent = self.blocks[ix].indent;
650            let paragraphs: Vec<Block> = code
651                .text
652                .split('\n')
653                .map(|line| Block::at(BlockKind::Paragraph(Text::plain(line)), indent))
654                .collect();
655            self.blocks.splice(ix..=ix, paragraphs);
656        }
657        self.repair();
658        Some(Cursor::new(start.block, Part::Body, 0).clamp(self))
659    }
660
661    /// Every text a selection touches, with the slice of it covered.
662    ///
663    /// One selection can reach across paragraphs and table cells, and a mark
664    /// applies to each of them separately — marks live inside a [`Text`] and
665    /// have no way to span two.
666    pub fn spans(&self, selection: Selection) -> Vec<(Cursor, Range<usize>)> {
667        let (start, end) = selection.clamp(self).ordered();
668        let (first, last) = (
669            Cursor::new(start.block, start.part, 0),
670            Cursor::new(end.block, end.part, 0),
671        );
672        let mut out = Vec::new();
673        for block in start.block..=end.block.min(self.blocks.len().saturating_sub(1)) {
674            for part in self.blocks[block].parts() {
675                let here = Cursor::new(block, part, 0);
676                if here < first || here > last {
677                    continue;
678                }
679                let len = here.len_in(self).unwrap_or(0);
680                let from = if here == first { start.offset } else { 0 };
681                let to = if here == last { end.offset } else { len };
682                if from < to.min(len) {
683                    out.push((here, from..to.min(len)));
684                }
685            }
686        }
687        out
688    }
689
690    /// Add `mark` over a selection, or take it away if every part of the
691    /// selection already carries it.
692    ///
693    /// The decision is made across the whole selection before anything moves:
694    /// dragging over a bold word and a plain one and pressing cmd-B should bold
695    /// the rest rather than unbolding the half that was already there.
696    pub fn toggle_mark(&mut self, selection: Selection, mark: Mark) {
697        let spans = self.spans(selection);
698        let remove = self.covered_by(selection, &mark);
699
700        for (at, range) in spans {
701            // Code is literal to its closing fence and a caption has no room
702            // for markup between its brackets; nothing in either is markup.
703            if matches!(at.part, Part::Code | Part::Caption) {
704                continue;
705            }
706            if remove == self.carries(&at, &range, &mark) {
707                let mark = mark.clone();
708                self.edit_at(at, |text| text.toggle(range, mark));
709            }
710        }
711    }
712
713    /// Whether every part of a selection already carries `mark` — what decides
714    /// between adding it and taking it away, and what a toolbar button reads to
715    /// know whether it is lit.
716    pub fn covered_by(&self, selection: Selection, mark: &Mark) -> bool {
717        let spans = self.spans(selection);
718        !spans.is_empty()
719            && spans.iter().all(|(at, range)| {
720                matches!(at.part, Part::Code | Part::Caption) || self.carries(at, range, mark)
721            })
722    }
723
724    fn carries(&self, at: &Cursor, range: &Range<usize>, mark: &Mark) -> bool {
725        self.blocks[at.block]
726            .text_at(at.part)
727            .is_some_and(|text| text.covered_by(range, mark))
728    }
729
730    /// The sub-document a selection covers — what a copy puts on the clipboard.
731    ///
732    /// A table is atomic here for the same reason it is in [`Doc::replace`]:
733    /// half a table has no shape worth keeping, so a selection reaching into
734    /// one takes it whole.
735    pub fn slice(&self, selection: Selection) -> Doc {
736        let (start, end) = selection.clamp(self).ordered();
737        let mut out = Doc {
738            blocks: self.blocks[start.block..=end.block].to_vec(),
739        };
740        let last = end.block - start.block;
741        // Tail first: trimming the head would move the offsets the tail is in.
742        if !matches!(end.part, Part::Cell { .. })
743            && let Some(text) = out.blocks[last].text_at_mut(end.part)
744        {
745            text.split_off(end.offset);
746        }
747        if !matches!(start.part, Part::Cell { .. })
748            && let Some(text) = out.blocks[0].text_at_mut(start.part)
749        {
750            *text = text.split_off(start.offset);
751        }
752        // The slice starts at the left margin whatever depth it was cut from.
753        out.repair();
754        out
755    }
756
757    /// Replace a selection with a whole document — the paste path.
758    ///
759    /// A lone paragraph goes in as inline text, marks and all: pasting a
760    /// sentence into a sentence must not make a new block. Anything else
761    /// arrives as blocks, and the remainder of the caret's block follows them.
762    pub fn splice(&mut self, selection: Selection, other: Doc) -> Cursor {
763        let blocks = other.blocks;
764        let inline = match blocks.as_slice() {
765            [] => Some(Text::default()),
766            [block] => match &block.kind {
767                BlockKind::Paragraph(text) => Some(text.clone()),
768                _ => None,
769            },
770            _ => None,
771        };
772        if let Some(text) = inline {
773            return self.replace(selection, text);
774        }
775
776        let caret = self.replace(selection, Text::default());
777        let base = self.blocks[caret.block].indent;
778        // Split so what followed the caret follows the paste too. An empty
779        // remainder is the blank block a paste at the end would leave behind.
780        let tail = self.split(caret.block, caret.offset);
781        let empty_tail = self.blocks[tail]
782            .text_at(Part::Body)
783            .is_some_and(Text::is_empty);
784
785        let mut at = caret.block;
786        for block in blocks {
787            at += 1;
788            self.blocks
789                .insert(at, Block::at(block.kind, base.saturating_add(block.indent)));
790        }
791        if empty_tail {
792            self.blocks.remove(at + 1);
793        }
794        // And the block the caret opened in, if the paste displaced all of it.
795        let head_empty = self.blocks[caret.block]
796            .text_at(Part::Body)
797            .is_some_and(Text::is_empty);
798        if head_empty && matches!(self.blocks[caret.block].kind, BlockKind::Paragraph(_)) {
799            self.blocks.remove(caret.block);
800            at -= 1;
801        }
802        self.repair();
803        Cursor::new(at, Part::Body, 0).end(self).clamp(self)
804    }
805
806    /// Replace everything a selection covers with `text`, and say where the
807    /// caret lands.
808    ///
809    /// **The one mutation.** Typing, backspace, delete, cut and paste are all
810    /// this call with a different argument, which is why none of them needs to
811    /// know whether a selection was empty, spanned two paragraphs, or swallowed
812    /// a table on the way past.
813    pub fn replace(&mut self, selection: Selection, text: Text) -> Cursor {
814        // An empty document has no block to put anything in; editing one opens
815        // the paragraph every other path then assumes exists.
816        if self.blocks.is_empty() {
817            self.blocks
818                .push(Block::new(BlockKind::Paragraph(Text::default())));
819        }
820        let (start, end) = selection.clamp(self).ordered();
821
822        // Code is literal and a caption is written between brackets, so marks
823        // arriving from a paste have nowhere to go in either.
824        let text = if matches!(start.part, Part::Code | Part::Caption) {
825            Text::plain(text.text)
826        } else {
827            text
828        };
829
830        if start.block == end.block && start.part == end.part {
831            let at = start.offset + text.text.len();
832            self.edit_at(start, |body| {
833                body.remove(start.offset..end.offset);
834                body.insert(start.offset, &text.text);
835                for span in &text.marks {
836                    body.marks.push(MarkSpan {
837                        range: start.offset + span.range.start..start.offset + span.range.end,
838                        mark: span.mark.clone(),
839                    });
840                }
841                body.normalize_marks();
842            });
843            return Cursor {
844                offset: at,
845                ..start
846            }
847            .clamp(self);
848        }
849
850        // Across cells of one table the table itself survives: the covered
851        // cells are emptied and the shape stays, which is what a spreadsheet
852        // selection does and what keeps the columns from collapsing.
853        if start.block == end.block {
854            for part in self.blocks[start.block].parts() {
855                if part < start.part || part > end.part {
856                    continue;
857                }
858                // `remove` clamps, so the open end needs no length.
859                let (from, to) = (
860                    if part == start.part { start.offset } else { 0 },
861                    if part == end.part {
862                        end.offset
863                    } else {
864                        usize::MAX
865                    },
866                );
867                self.edit_at(Cursor::new(start.block, part, 0), |body| {
868                    body.remove(from..to)
869                });
870            }
871            return self.replace(Selection::at(start), text);
872        }
873
874        // Across blocks the head keeps its kind and takes the tail's
875        // remainder, and everything between them goes.
876        //
877        // A **table is atomic** to a selection that leaves it. Half a table has
878        // no shape worth keeping, so an end landing in one takes the whole
879        // block rather than splicing a lone cell into a paragraph.
880        let head_keeps = !matches!(start.part, Part::Cell { .. })
881            && self.blocks[start.block].text_at(start.part).is_some();
882        let tail = match end.part {
883            Part::Cell { .. } => Text::default(),
884            part => self.blocks[end.block]
885                .text_at_mut(part)
886                .map(|body| body.split_off(end.offset))
887                .unwrap_or_default(),
888        };
889
890        let indent = self.blocks[start.block].indent;
891        let first = if head_keeps {
892            start.block + 1
893        } else {
894            start.block
895        };
896        self.blocks.drain(first..=end.block);
897
898        let caret = if head_keeps {
899            self.edit_at(start, |body| body.remove(start.offset..usize::MAX));
900            self.edit_at(start, |body| body.append(tail));
901            start
902        } else {
903            // Everything the selection touched is gone, so the tail arrives as
904            // a paragraph in its place.
905            self.blocks
906                .insert(start.block, Block::at(BlockKind::Paragraph(tail), indent));
907            Cursor::new(start.block, Part::Body, 0)
908        };
909        self.repair();
910        let caret = caret.clamp(self);
911        self.replace(Selection::at(caret), text)
912    }
913
914    /// Put the document into the form markdown can hold — the save step.
915    ///
916    /// Drops the whitespace markdown discards anyway (leading and trailing on
917    /// every line, blank lines at a block's edges), flattens the blocks whose
918    /// output is one line, and renumbers ordered runs. After this,
919    /// `parse(serialize(doc)) == doc`.
920    pub fn normalize(&mut self) {
921        for block in &mut self.blocks {
922            let one_line = matches!(block.kind, BlockKind::Heading { .. });
923            match &mut block.kind {
924                BlockKind::Paragraph(text)
925                | BlockKind::Heading { text, .. }
926                | BlockKind::Bullet(text)
927                | BlockKind::Ordered { text, .. }
928                | BlockKind::Task { text, .. }
929                | BlockKind::Quote(text) => {
930                    *text = crate::parse::normalize(&text.text, &text.marks);
931                    text.normalize_marks();
932                    if one_line {
933                        crate::parse::collapse_to_one_line(text);
934                    }
935                }
936                BlockKind::Table { header, rows, .. } => {
937                    for cell in header.iter_mut().chain(rows.iter_mut().flatten()) {
938                        *cell = crate::parse::normalize(&cell.text, &cell.marks);
939                        cell.normalize_marks();
940                        crate::parse::collapse_to_one_line(cell);
941                    }
942                }
943                // A caption lives between brackets, where a line break has no
944                // spelling at all.
945                BlockKind::Image { alt, .. } => crate::parse::collapse_to_one_line(alt),
946                BlockKind::Code { .. } | BlockKind::Bookmark { .. } | BlockKind::Rule => {}
947            }
948        }
949        // A blank paragraph is the empty line an editor leaves behind, and
950        // markdown has no way to write one down — blank lines there separate
951        // blocks rather than being one. An empty heading or list item is
952        // different: `# ` and `- ` are both real, so those stay.
953        self.blocks.retain(|block| {
954            !matches!(
955                &block.kind,
956                BlockKind::Paragraph(text) | BlockKind::Quote(text) if text.is_empty()
957            )
958        });
959        self.repair();
960
961        // The rules above keep every ordinary edit lossless. They cannot be
962        // complete, and no serializer fix would make them so: whether a mark
963        // boundary can be written depends on CommonMark's flanking rules, and
964        // some marks have no spelling at all. Bold ending on a `~` with a letter
965        // after it is one — a closing delimiter preceded by punctuation and
966        // followed by a letter is not right-flanking, so `Tit**l\~\~**e` does
967        // not close. That is a limit of the format, not a bug in the writer.
968        //
969        // So the last word goes to markdown: adopt the document it can hold.
970        //
971        // This is exact rather than approximate. Anything [`crate::parse`]
972        // returns is a fixed point of the round trip — that is the guarantee the
973        // parser is tested for — so writing this document out and reading it
974        // back yields one by construction. Marks with no spelling are dropped
975        // here, in front of the reader, rather than silently at save time.
976        //
977        // The cheaper rules above still earn their place: they are what keeps
978        // the ordinary edit lossless, so this step has nothing left to take.
979        *self = crate::parse(&crate::serialize(self));
980    }
981
982    /// Tab. A block can go one level deeper than the one above it, and its
983    /// children come with it.
984    pub fn indent(&mut self, ix: usize) -> bool {
985        let Some(block) = self.blocks.get(ix) else {
986            return false;
987        };
988        if block.indent >= self.ceiling(ix) {
989            return false;
990        }
991        self.shift_subtree(ix, 1);
992        self.repair();
993        true
994    }
995
996    /// How deep block `ix` is allowed to sit.
997    ///
998    /// Markdown expresses nesting through list items and nothing else, so a
999    /// block may only go deeper than the one above it when that one is a
1000    /// marker. Indenting a paragraph under a *heading* would serialize to four
1001    /// leading spaces, which reads back as an indented code block.
1002    pub fn ceiling(&self, ix: usize) -> u8 {
1003        match ix.checked_sub(1).map(|previous| &self.blocks[previous]) {
1004            None => 0,
1005            Some(previous) if is_marker(&previous.kind) => previous.indent + 1,
1006            Some(previous) => previous.indent,
1007        }
1008    }
1009
1010    /// Clamp every indent to what the document can actually express, then make
1011    /// ordered runs consecutive. Cheap, total, and called after anything
1012    /// structural — a local rule is not enough, because outdenting one block
1013    /// can leave the block *after* it stranded a level too deep.
1014    ///
1015    /// Public because an editor that changes a block's *kind* has to restore
1016    /// the invariant too, and only this knows what it is.
1017    pub fn repair(&mut self) {
1018        for ix in 0..self.blocks.len() {
1019            let ceiling = self.ceiling(ix);
1020            self.blocks[ix].indent = self.blocks[ix].indent.min(ceiling);
1021        }
1022        self.renumber();
1023    }
1024
1025    /// Shift-Tab, children included.
1026    pub fn outdent(&mut self, ix: usize) -> bool {
1027        if self.blocks.get(ix).is_none_or(|block| block.indent == 0) {
1028            return false;
1029        }
1030        self.shift_subtree(ix, -1);
1031        self.repair();
1032        true
1033    }
1034
1035    /// Move a block and everything nested under it. Children have to travel
1036    /// with the parent or the document invariant breaks the moment a level
1037    /// disappears from under them.
1038    fn shift_subtree(&mut self, ix: usize, by: i8) {
1039        let span = self.subtree(ix);
1040        for block in &mut self.blocks[span] {
1041            block.indent = block.indent.saturating_add_signed(by);
1042        }
1043    }
1044}
1045
1046fn is_marker(kind: &BlockKind) -> bool {
1047    matches!(
1048        kind,
1049        BlockKind::Bullet(_) | BlockKind::Ordered { .. } | BlockKind::Task { .. }
1050    )
1051}
1052
1053/// A markdown prefix typed at the start of a block, and what it turns it into.
1054#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1055pub enum Shortcut {
1056    Heading(u8),
1057    Bullet,
1058    Ordered,
1059    Task(bool),
1060    Quote,
1061    Code,
1062    Rule,
1063}
1064
1065impl Shortcut {
1066    /// The block this shortcut makes, carrying whatever text was left over.
1067    pub fn apply(self, text: Text) -> BlockKind {
1068        match self {
1069            Self::Heading(level) => BlockKind::Heading { level, text },
1070            Self::Bullet => BlockKind::Bullet(text),
1071            Self::Ordered => BlockKind::Ordered { number: 1, text },
1072            Self::Task(checked) => BlockKind::Task { checked, text },
1073            Self::Quote => BlockKind::Quote(text),
1074            // Code is literal, so whatever marks the text carried have no
1075            // meaning inside the fence.
1076            Self::Code => BlockKind::Code {
1077                language: None,
1078                code: Text::plain(text.text),
1079            },
1080            Self::Rule => BlockKind::Rule,
1081        }
1082    }
1083}
1084
1085/// Match a markdown prefix at the start of a block, returning it and how many
1086/// bytes it occupied.
1087///
1088/// This is the input side of the same vocabulary [`crate::parse`] reads: typing
1089/// `## ` makes a heading because pasting `## ` would have. Order matters — a
1090/// task marker is a bullet with more on the end.
1091pub fn shortcut(text: &str) -> Option<(Shortcut, usize)> {
1092    const PREFIXES: &[(&str, Shortcut)] = &[
1093        ("- [ ] ", Shortcut::Task(false)),
1094        ("- [x] ", Shortcut::Task(true)),
1095        ("###### ", Shortcut::Heading(6)),
1096        ("##### ", Shortcut::Heading(5)),
1097        ("#### ", Shortcut::Heading(4)),
1098        ("### ", Shortcut::Heading(3)),
1099        ("## ", Shortcut::Heading(2)),
1100        ("# ", Shortcut::Heading(1)),
1101        ("- ", Shortcut::Bullet),
1102        ("* ", Shortcut::Bullet),
1103        ("+ ", Shortcut::Bullet),
1104        ("1. ", Shortcut::Ordered),
1105        ("> ", Shortcut::Quote),
1106        ("```", Shortcut::Code),
1107        ("---", Shortcut::Rule),
1108    ];
1109    PREFIXES
1110        .iter()
1111        .find(|(prefix, _)| text.starts_with(prefix))
1112        .map(|(prefix, shortcut)| (*shortcut, prefix.len()))
1113}
1114
1115/// A closing inline delimiter just typed, and the run it closes.
1116///
1117/// The inline half of the same vocabulary [`shortcut`] covers: typing the last
1118/// `*` of `**bold**` makes it bold because pasting `**bold**` would have.
1119/// Returns the opening delimiter's range and the text between it and the caret;
1120/// the closing delimiter is `inner.end..caret`.
1121pub fn inline_rule(text: &str, caret: usize) -> Option<(Range<usize>, Range<usize>, Mark)> {
1122    let head = text.get(..caret)?;
1123    // Longest first — `**` is bold, and only what is left of it is italic.
1124    for (delimiter, mark) in [
1125        ("**", Mark::Bold),
1126        ("~~", Mark::Strike),
1127        ("`", Mark::Code),
1128        ("_", Mark::Italic),
1129        ("*", Mark::Italic),
1130    ] {
1131        let Some(closes) = head.strip_suffix(delimiter) else {
1132            continue;
1133        };
1134        let Some(open) = closes.rfind(delimiter) else {
1135            continue;
1136        };
1137        let inner = open + delimiter.len()..closes.len();
1138        let Some(body) = text.get(inner.clone()).filter(|body| !body.is_empty()) else {
1139            continue;
1140        };
1141        // Emphasis cannot open or close against whitespace, so a mark reaching
1142        // over one has no spelling and [`Text::normalize_marks`] would shrink
1143        // it straight back off. A rule that fires and vanishes is worse than
1144        // one that does not fire.
1145        if body.starts_with(char::is_whitespace) || body.ends_with(char::is_whitespace) {
1146            continue;
1147        }
1148        // An underscore inside a word is not emphasis in CommonMark, which is
1149        // the only reason `snake_case_names` survive being typed.
1150        if delimiter == "_"
1151            && text[..open]
1152                .chars()
1153                .next_back()
1154                .is_some_and(char::is_alphanumeric)
1155        {
1156            continue;
1157        }
1158        return Some((open..open + delimiter.len(), inner, mark));
1159    }
1160    None
1161}