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