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