Skip to main content

markdown/
edit.rs

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