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