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