Skip to main content

scrive_core/
verbs.rs

1//! The editing verbs: type, backspace, delete, enter, paste, (out)dent, and
2//! move / copy line.
3//! Each turns the current selections into one atomic multi-range transaction,
4//! then places a caret after each edit — so every verb is multi-cursor-correct
5//! for free (one op per selection). The placement is handed to the commit
6//! itself (`Document::edit_placing`), never assigned once the edit returns, so
7//! history records it as the unit's after-selections and redo restores exactly
8//! the carets the verb left.
9//!
10//! The one catch is deletion: a selection's delete can reach past its
11//! neighbour (a tab-stop backspace, a word, a line block), so delete verbs
12//! merge their ranges first (`merge_delete_ranges`) — the engine rejects
13//! overlapping ops — and every verb commits through `verb_commit`, which turns
14//! an overlap that slipped through into a debug panic instead of a keystroke
15//! that silently does nothing.
16//!
17//! Two load-bearing semantics live here: **typing over a selection replaces
18//! it** (one transaction, caret after), and **Enter's autoindent is the leading
19//! whitespace truncated to the caret column** (never duplicated) — plus the two
20//! brace rules (one unit deeper after a line-opening `{`; `{|}` splits onto
21//! three lines), always computed for *inserted* text only, never re-indenting
22//! existing lines, so autoindent cannot fight the user. Clipboard text is
23//! normalized to LF on the way in; the LF→OS-flavor re-expansion for copy-out
24//! lives in the widget layer.
25
26use core::ops::Range;
27use std::borrow::Cow;
28
29use crate::autoclose;
30use crate::buffer::Buffer;
31use crate::coords::Point;
32use crate::document::Document;
33use crate::history::{GroupingHint, OpClass};
34use crate::patch::Patch;
35use crate::row_layout::tail_start_col;
36use crate::selection::{Selection, SelectionSet};
37use crate::transaction::{Committed, EditOp, TransactionError};
38
39/// Spaces per indent level: the stop spacing Tab, Shift+Tab, the block
40/// (out)dent and backspace's tab-stop delete use on a space-indented line (a
41/// tab-indented line moves by one hard tab instead).
42///
43/// Deliberately a SEPARATE knob from [`crate::display_map::default_tab_size`]
44/// (an *editing* width vs a *display* width — mainstream editors keep both).
45/// They happen to share the value 4; if either ever changes, the widget's
46/// indent guides key on the display knob and Tab/indent verbs on this one.
47/// Space stops after a hard tab count from the tab, so they line up with the
48/// drawn ones while the display width is a multiple of this one.
49#[must_use]
50pub fn default_indent_size() -> u32 {
51    4
52}
53
54impl Document {
55    /// Insert `ch` at every caret, replacing any non-empty selection. Merges
56    /// into the current typing run for undo.
57    pub fn type_char(&mut self, ch: char) {
58        // Auto-close, applied uniformly across ALL carets (a single caret is
59        // just the one-element case). Each helper handles the whole keystroke and
60        // returns true; overtype takes precedence (a quote is opener AND closer).
61        if self.try_overtype(ch) {
62            return; // a closer typed over its own auto-inserted close, at every caret
63        }
64        if self.try_autoclose(ch) {
65            return; // an opener at empty carets → insert the pair (per-caret guard)
66        }
67        if self.try_surround(ch) {
68            return; // an opener over non-empty selections → surround each
69        }
70        // Plain insert (also the mixed / guard-failing / non-pair case). Replaces
71        // every selection with `ch`; `edit_grouped` rebases and validates live
72        // provenance, so typing inside a pair keeps overtype available.
73        let mut s = [0u8; 4];
74        let text: &str = ch.encode_utf8(&mut s);
75        let ops = self.map_selections(|sel_start, sel_end, _| EditOp::new(sel_start..sel_end, text));
76        self.run_edit(ops, GroupingHint::mergeable(OpClass::Type));
77    }
78
79    /// Whether pair-insert of `open` is allowed at an empty `caret` — the guard
80    /// that keeps auto-close from firing where a closer would be unwelcome.
81    fn can_autoclose(&self, open: char, caret: u32) -> bool {
82        // The char after the caret must be whitespace, EOL, or a closer —
83        // otherwise the pair would split a word or an existing token.
84        match self.buffer.char_at(caret) {
85            None => {}
86            Some(c) if c.is_whitespace() || autoclose::is_closer(c) => {}
87            Some(_) => return false,
88        }
89        if autoclose::is_quote(open) {
90            // For a quote, the char before must not be a word char (else it is
91            // an apostrophe or a suffix quote, not an opening string delimiter).
92            if let Some(p) = self.buffer.char_before(caret) {
93                if autoclose::is_word_char(p) {
94                    return false;
95                }
96            }
97            // An odd count of quotes on the line ⇒ this one closes an already
98            // open string; insert it literally rather than pairing.
99            let row = self.buffer.offset_to_point(caret).row;
100            let quotes = self.buffer.line(row).chars().filter(|&c| c == '"').count();
101            if quotes % 2 == 1 {
102                return false;
103            }
104        }
105        true
106    }
107
108    /// Overtype: if `ch` is a closer and EVERY selection is an empty caret
109    /// sitting exactly on a live pair's matching close, skip over each (no
110    /// insert) and consume all provenance. All-or-nothing, so a mixed set falls
111    /// through. Returns whether it handled the keystroke.
112    fn try_overtype(&mut self, ch: char) -> bool {
113        if !autoclose::is_closer(ch) {
114            return false;
115        }
116        let ranges = self.autoclose_ranges(); // ascending, disjoint ⇒ ends ascending
117        let on_close = |caret: u32| {
118            let i = ranges.partition_point(|r| r.end < caret);
119            i < ranges.len() && ranges[i].end == caret
120        };
121        let sels = self.selections.all();
122        let all = !ranges.is_empty()
123            && sels.iter().all(|s| {
124                s.is_empty() && self.buffer.char_at(s.head()) == Some(ch) && on_close(s.head())
125            });
126        if !all {
127            return false;
128        }
129        let step = ch.len_utf8() as u32;
130        let carets: Vec<u32> = sels.iter().map(|s| s.head() + step).collect();
131        self.selections = SelectionSet::from_offsets(&carets);
132        self.clear_autoclose(); // the pairs are consumed
133        true
134    }
135
136    /// Auto-close: if `ch` is an opener and EVERY selection is an empty caret,
137    /// insert `open+close` at each caret that passes the guards and a bare
138    /// `open` at the rest (a per-caret decision, as mainstream editors make it),
139    /// place each caret just after the opener, and record provenance for the
140    /// paired ones. Returns whether it handled the keystroke (false if any
141    /// selection is non-empty).
142    fn try_autoclose(&mut self, ch: char) -> bool {
143        let Some(close) = autoclose::opener_close(ch) else {
144            return false;
145        };
146        if self.selections.all().iter().any(|s| !s.is_empty()) {
147            return false; // a non-empty selection ⇒ surround/plain, not pair-insert
148        }
149        // Per-caret pair-or-plain decision, computed before the edit.
150        let plan: Vec<bool> =
151            self.selections.all().iter().map(|s| self.can_autoclose(ch, s.head())).collect();
152        let mut pair_text = String::with_capacity(ch.len_utf8() + close.len_utf8());
153        pair_text.push(ch);
154        pair_text.push(close);
155        let mut open_buf = [0u8; 4];
156        let open_text: &str = ch.encode_utf8(&mut open_buf);
157        let ops: Vec<EditOp> = self
158            .selections
159            .all()
160            .iter()
161            .zip(&plan)
162            .map(|(s, &pair)| {
163                let at = s.head();
164                EditOp::new(at..at, if pair { pair_text.as_str() } else { open_text })
165            })
166            .collect();
167        let hint = GroupingHint { op: OpClass::Type, seal_before: true, seal_after: false };
168        // A caret just after each opener; provenance for the paired ones.
169        let close_len = close.len_utf8() as u32;
170        let mut pairs = Vec::new();
171        let placed = self.edit_placing(ops, hint, |patch, _| {
172            let edits = patch.edits();
173            let mut carets = Vec::with_capacity(edits.len());
174            for (e, &pair) in edits.iter().zip(&plan) {
175                if pair {
176                    let close_off = e.new.end - close_len; // start of the close char
177                    carets.push(close_off); // caret between open and close
178                    pairs.push((e.new.start, close_off)); // [open, close)
179                } else {
180                    carets.push(e.new.end); // after the bare opener
181                }
182            }
183            Some(SelectionSet::from_offsets(&carets))
184        });
185        if !verb_commit(placed) {
186            return true; // refused: no pair landed, so no provenance to record
187        }
188        if !pairs.is_empty() {
189            // Supersede any prior provenance — e.g. an outer pair whose tracked
190            // range grew to enclose this insertion (nested `(` inside `()`) — so
191            // the live set stays disjoint. Nested pairs must not accumulate, or
192            // overtype's ends-ascending binary search picks the wrong pair and a
193            // closer inserts literally (`(()))`). One generation of provenance,
194            // always.
195            self.clear_autoclose();
196            self.add_autoclose_pairs(pairs);
197        }
198        true
199    }
200
201    /// Surround: if `ch` is an opener and EVERY selection is non-empty, wrap
202    /// each in the pair with the selection staying on the interior. Returns
203    /// whether it handled the keystroke.
204    fn try_surround(&mut self, ch: char) -> bool {
205        let Some(close) = autoclose::opener_close(ch) else {
206            return false;
207        };
208        let sels = self.selections.all();
209        if sels.is_empty() || sels.iter().any(|s| s.is_empty()) {
210            return false;
211        }
212        let (open_len, close_len) = (ch.len_utf8() as u32, close.len_utf8() as u32);
213        // ONE replace op per selection (open + content + close). A single op per
214        // selection keeps TOUCHING selections independent: two zero-width inserts
215        // at the same offset (opener then closer) would be ambiguous to bias, so
216        // a successor selection starting where its predecessor ends could swallow
217        // the inserted opener. One replace has an unambiguous span instead.
218        let ops: Vec<EditOp> = sels
219            .iter()
220            .map(|s| {
221                let content = self.buffer.slice(s.start()..s.end());
222                let mut wrapped = String::with_capacity(content.len() + open_len as usize + close_len as usize);
223                wrapped.push(ch);
224                wrapped.push_str(&content);
225                wrapped.push(close);
226                EditOp::new(s.start()..s.end(), &wrapped)
227            })
228            .collect();
229        // Each selection stays on its interior, read straight from the edits.
230        verb_commit(self.edit_placing(ops, GroupingHint::discrete(), |patch, _| {
231            let ranges: Vec<(u32, u32)> =
232                patch.edits().iter().map(|e| (e.new.start + open_len, e.new.end - close_len)).collect();
233            Some(SelectionSet::from_ranges(&ranges, ranges.len() - 1))
234        }));
235        true
236    }
237
238    /// Insert `text` (e.g. a paste) at every caret, replacing selections. A
239    /// discrete undo step; `\r\n|\r` is normalized to LF.
240    pub fn insert_text(&mut self, text: &str) {
241        let ops = self.map_selections(|sel_start, sel_end, _| {
242            EditOp::new(sel_start..sel_end, text)
243        });
244        self.run_edit(ops, GroupingHint::discrete());
245    }
246
247    /// The text Copy/Cut should put on the clipboard, with the `is_entire_line`
248    /// flag the paste side honors. Each non-empty selection contributes its
249    /// text, joined by newlines. When **every** selection is an empty caret,
250    /// each caret contributes its ENTIRE line — the one `Document::line_unit`
251    /// rule — including the newline (a final line without a terminator still
252    /// exports one so the paste splices cleanly), duplicate lines deduplicated,
253    /// and the flag is `true`. LF-only; the OS-flavor re-expansion and the side
254    /// table live in the widget.
255    #[must_use]
256    pub fn clipboard_payload(&self) -> (String, bool) {
257        let sels = self.selections.all();
258        if sels.iter().all(Selection::is_empty) {
259            let mut units: Vec<(u32, u32)> = sels.iter().map(|s| self.line_unit(s.head())).collect();
260            units.dedup();
261            let lines: String = units
262                .iter()
263                .map(|&(start, end)| {
264                    let slice = self.buffer.slice(start..end);
265                    if slice.ends_with('\n') {
266                        slice.into_owned()
267                    } else {
268                        format!("{slice}\n")
269                    }
270                })
271                .collect();
272            return (lines, true);
273        }
274        let joined = sels
275            .iter()
276            .filter(|s| !s.is_empty())
277            .map(|s| self.buffer.slice(s.start()..s.end()))
278            .collect::<Vec<_>>()
279            .join("\n");
280        (joined, false)
281    }
282
283    /// Cut's edit half: cut is copy-then-delete, so this deletes exactly what
284    /// [`Document::clipboard_payload`] exported — non-empty selections; or,
285    /// when EVERY selection is an empty caret (the payload's whole-line mode,
286    /// same gate), each caret's `line_block`. In a
287    /// mixed set the empty carets reached the clipboard as nothing, so they
288    /// delete nothing — cut never destroys text that was not copied. The
289    /// final-line block takes its PRECEDING newline while the payload
290    /// synthesizes a trailing one: the paste splices a whole line either way,
291    /// and no empty tail line is left behind. Overlapping ranges (two carets on
292    /// one line) merge; one discrete transaction, caret after each deletion.
293    pub fn cut(&mut self) {
294        let all_empty = self.selections.all().iter().all(Selection::is_empty);
295        let ranges: Vec<Range<u32>> = self
296            .selections
297            .all()
298            .iter()
299            .filter_map(|sel| {
300                if sel.is_empty() {
301                    all_empty.then(|| self.line_block(sel))
302                } else {
303                    Some(sel.start()..sel.end())
304                }
305            })
306            .collect();
307        let ops = merge_delete_ranges(ranges.into_iter());
308        self.run_edit(ops, GroupingHint::discrete());
309    }
310
311    /// Paste. Plain: replace each selection, caret after (a discrete step).
312    /// `entire_line` at all-empty carets: insert before each caret's line start
313    /// with **no caret placement** — the carets rebase past the insertion and
314    /// stay put on their own lines, matching mainstream editors.
315    pub fn paste(&mut self, text: &str, entire_line: bool) {
316        if entire_line && self.selections.all().iter().all(Selection::is_empty) {
317            let mut starts: Vec<u32> =
318                self.selections.all().iter().map(|s| self.line_unit(s.head()).0).collect();
319            starts.dedup();
320            let ops = starts.into_iter().map(|at| EditOp::new(at..at, text)).collect();
321            // `edit_grouped` rebases the selections through the patch — never
322            // `run_edit`, whose caret-after would yank the caret off its line.
323            verb_commit(self.edit_grouped(ops, GroupingHint::discrete()));
324            return;
325        }
326        self.insert_text(text);
327    }
328
329    /// Backspace: delete the selection, or the character before an empty caret
330    /// — one indent level when the caret sits in leading whitespace: a hard
331    /// tab, or spaces back to the previous stop (the unit Shift+Tab removes).
332    pub fn backspace(&mut self) {
333        // Pair-backspace: every empty caret strictly between a live *empty*
334        // pair deletes both chars (`open + 1 == close` ⇒ nothing typed between
335        // them). All-or-nothing (symmetric with overtype); a mixed set falls
336        // through to the ordinary per-caret backspace below.
337        let ranges = self.autoclose_ranges();
338        if !ranges.is_empty() {
339            let empty_pair_at = |caret: u32| -> Option<Range<u32>> {
340                let i = ranges.partition_point(|r| r.end < caret);
341                ranges.get(i).filter(|r| r.end == caret && r.start + 1 == r.end).cloned()
342            };
343            let pairs: Option<Vec<Range<u32>>> = self
344                .selections
345                .all()
346                .iter()
347                .map(|s| s.is_empty().then(|| empty_pair_at(s.head())).flatten())
348                .collect();
349            if let Some(pairs) = pairs {
350                // Distinct carets sit in distinct (disjoint) pairs, so these
351                // only ever touch — merged anyway, like every delete verb.
352                let ops = merge_delete_ranges(pairs.iter().map(|r| {
353                    let close_len = self.buffer.char_at(r.end).map_or(1, |c| c.len_utf8() as u32);
354                    r.start..r.end + close_len
355                }));
356                self.clear_autoclose();
357                let hint =
358                    GroupingHint { op: OpClass::Delete, seal_before: true, seal_after: true };
359                self.run_edit(ops, hint);
360                return;
361            }
362        }
363        let buffer = &self.buffer;
364        let ranges = self.selections.all().iter().filter_map(|sel| {
365            if !sel.is_empty() {
366                return Some(sel.start()..sel.end());
367            }
368            let caret = sel.head();
369            if caret == 0 {
370                return None;
371            }
372            Some(backspace_target(buffer, caret)..caret)
373        });
374        // A tab-stop delete reaches back past its own caret's neighbourhood —
375        // carets at cols 3 and 4 both delete to stop 0 (`0..3`, `0..4`), and a
376        // caret deletes straight through a selection inside its indent run —
377        // so the ranges can OVERLAP and must merge before the transaction.
378        let ops = merge_delete_ranges(ranges);
379        self.run_edit(ops, GroupingHint::mergeable(OpClass::Delete));
380    }
381
382    /// Forward delete: delete the selection, or the character after an empty
383    /// caret (merging the next line when at end of line).
384    pub fn delete_forward(&mut self) {
385        let len = self.buffer.len();
386        let buffer = &self.buffer;
387        let ranges = self.selections.all().iter().filter_map(|sel| {
388            if !sel.is_empty() {
389                return Some(sel.start()..sel.end());
390            }
391            let caret = sel.head();
392            if caret >= len {
393                return None;
394            }
395            let next = buffer.char_at(caret).expect("caret < len");
396            Some(caret..caret + next.len_utf8() as u32)
397        });
398        // One char forward from a caret can only TOUCH the next selection (a
399        // caret touching a selection merged into it), never overlap — merged
400        // anyway, so every delete verb builds its ops the same disjoint way.
401        let ops = merge_delete_ranges(ranges);
402        self.run_edit(ops, GroupingHint::mergeable(OpClass::Delete));
403    }
404
405    /// Delete the word before each caret — Ctrl+Backspace. A non-empty
406    /// selection deletes as-is; an empty caret deletes back to the previous word
407    /// start, or *only* the newline at column 0 (never newline + previous word).
408    /// A discrete undo step (word-delete seals the run on both sides).
409    ///
410    /// Two carets inside one word yield *overlapping* delete ranges, which the
411    /// transaction engine rejects — so the ranges are merged first (the shared
412    /// span is deleted once and the carets collapse via the selection merge
413    /// rule), as for every delete verb.
414    pub fn delete_word_back(&mut self) {
415        let buffer = &self.buffer;
416        let ranges = self.selections.all().iter().filter_map(|sel| {
417            if !sel.is_empty() {
418                return Some(sel.start()..sel.end());
419            }
420            let caret = sel.head();
421            let start = crate::movement::word_delete_left(buffer, caret);
422            (start < caret).then_some(start..caret)
423        });
424        let ops = merge_delete_ranges(ranges);
425        self.run_edit(ops, GroupingHint { op: OpClass::Delete, seal_before: true, seal_after: true });
426    }
427
428    /// Delete the word after each caret — Ctrl+Delete, the
429    /// [`delete_word_back`](Self::delete_word_back) mirror (overlapping ranges
430    /// merged the same way).
431    pub fn delete_word_forward(&mut self) {
432        let len = self.buffer.len();
433        let buffer = &self.buffer;
434        let ranges = self.selections.all().iter().filter_map(|sel| {
435            if !sel.is_empty() {
436                return Some(sel.start()..sel.end());
437            }
438            let caret = sel.head();
439            if caret >= len {
440                return None;
441            }
442            let end = crate::movement::word_delete_right(buffer, caret);
443            (end > caret).then_some(caret..end)
444        });
445        let ops = merge_delete_ranges(ranges);
446        self.run_edit(ops, GroupingHint { op: OpClass::Delete, seal_before: true, seal_after: true });
447    }
448
449    /// Enter: split the line at every caret, carrying the current line's
450    /// leading indentation (truncated to the caret column — never duplicated),
451    /// plus the two brace rules (both computed from raw text at insert time —
452    /// existing lines are never re-indented, so autoindent cannot fight the
453    /// user):
454    ///
455    /// - **(a)** the current line's text left of the caret, right-trimmed,
456    ///   ends with `{` → the new line gets one extra `indent_unit`;
457    /// - **(b)** the caret sits exactly between a pair (`{|}`) → insert two
458    ///   lines (`\n indent+unit \n indent`), closer dedented onto its own
459    ///   line, caret at the end of the middle line (one line up from the
460    ///   inserted text's end, indented one unit deeper than the closer).
461    ///
462    /// Its own undo step (seals before; typing after merges into the run).
463    pub fn enter(&mut self) {
464        let buffer = &self.buffer;
465        // Per-op distance from the inserted text's end BACK to the caret —
466        // 0 except for rule (b), where the caret lands one line up.
467        let mut backs: Vec<u32> = Vec::new();
468        let ops: Vec<_> = self
469            .selections
470            .all()
471            .iter()
472            .map(|sel| {
473                let indent = enter_indent(buffer, sel.start());
474                let opens = line_opens_block(buffer, sel.start());
475                let between = opens
476                    && buffer.char_before(sel.start()) == Some('{')
477                    && buffer.char_at(sel.end()) == Some('}');
478                let mut t = String::with_capacity(2 * (1 + indent.len()) + 4);
479                t.push('\n');
480                t.push_str(&indent);
481                if opens {
482                    t.push_str(&indent_unit(&indent));
483                }
484                if between {
485                    t.push('\n');
486                    t.push_str(&indent);
487                    backs.push(1 + indent.len() as u32); // back over "\n" + indent
488                } else {
489                    backs.push(0);
490                }
491                EditOp::new(sel.start()..sel.end(), t)
492            })
493            .collect();
494        let hint = GroupingHint { op: OpClass::Type, seal_before: true, seal_after: false };
495        // `run_edit`'s caret-after, minus each op's rule-(b) pull-back.
496        verb_commit(self.edit_placing(ops, hint, |patch, _| Some(carets_pulled_back(patch, &backs))));
497    }
498
499    /// Tab: at an empty caret insert one indent unit of the caret's line — a
500    /// hard tab on a tab-indented line, else spaces to the next indent stop;
501    /// over a single-line selection type that unit over it; over a multi-line
502    /// selection indent every spanned line one level, each by its own unit
503    /// (the indent-unit rule Enter and Shift+Tab share). The unit goes at the
504    /// caret, or in the selection's place, except from a caret or selection
505    /// ending ahead of a mixed indent's trailing run (the `··` of `··\t`, the
506    /// `\t` of `\t··`): there the selection is deleted and the unit goes where
507    /// block indent puts it, so no Tab interleaves kinds within a line's
508    /// indent. VS Code types the unit in the selection's place even there — a
509    /// `·` selected in `··\t` becomes `\t·\t` — which is exactly the
510    /// interleave; deleting the selection still replaces it with one level,
511    /// just not between chars of the other kind.
512    pub fn tab(&mut self) {
513        if self.selection_spans_multiple_lines() {
514            // Multi-line selection → indent the whole block, selection preserved.
515            self.indent_lines(true);
516        } else {
517            // Carets and single-line selections → insert / replace-with one
518            // unit. A single-line selection *types over* (as mainstream editors
519            // do): it does NOT indent the whole line.
520            let buffer = &self.buffer;
521            let sels = self.selections.all();
522            let mut ops = Vec::with_capacity(sels.len());
523            for sel in sels {
524                let (start, end) = (sel.start(), sel.end());
525                let p = buffer.offset_to_point(start);
526                let line = buffer.line(p.row);
527                let ws = leading_ws(&line);
528                // The LINE's kind (its whole leading whitespace), not the
529                // prefix's: a caret at column 0 of `\tfoo` must add a tab,
530                // where the empty prefix alone would say spaces.
531                let unit = IndentUnit::of(ws);
532                // A caret or selection ENDING ahead of the trailing run of the
533                // unit's char would drop the unit between chars of the OTHER
534                // kind (`\t··\t`). It takes block indent's insertion instead —
535                // the run's start, one level padded as block indent pads it —
536                // and a selection is still typed over: deleted where it lies.
537                // Ending in or at an edge of the run (or past the indent), the
538                // unit's own char joins its own run: in the selection's place,
539                // padded from there (VS Code's caret Tab).
540                let end_col = p.col + (end - start); // single-line: same row
541                if end_col < level_start(ws, unit) {
542                    if start < end {
543                        ops.push(EditOp::delete(start..end));
544                    }
545                    let (at, text) = indent_insert(ws);
546                    let mut at = start - p.col + at;
547                    // A later selection typing over the run's start (it starts
548                    // ahead of the run and ends in it) holds `at` strictly
549                    // inside — an overlap. The run then starts where that
550                    // selection does, so the unit goes there, touching its op.
551                    // Sorted and disjoint ⇒ at most one selection holds `at`.
552                    let over = sels.get(sels.partition_point(|o| o.end() <= at));
553                    if let Some(over) = over.filter(|o| o.start() < at) {
554                        at = over.start();
555                    }
556                    ops.push(EditOp::insert(at, text));
557                } else {
558                    ops.push(EditOp::new(start..end, unit.text(&line[..p.col as usize])));
559                }
560            }
561            // A caret just past each typed unit: every unit is non-empty text,
562            // so the edits that insert are exactly the units — the type-over
563            // deletes (empty post-edit ranges) carry no caret.
564            verb_commit(self.edit_placing(ops, GroupingHint::discrete(), |patch, _| {
565                let carets: Vec<u32> =
566                    patch.edits().iter().filter(|e| !e.new.is_empty()).map(|e| e.new.end).collect();
567                Some(SelectionSet::from_offsets(&carets))
568            }));
569        }
570    }
571
572    /// Shift+Tab: outdent every spanned line by one indent level — its last
573    /// hard tab, or its trailing spaces back to the previous stop. Unlike Tab
574    /// (which types over a single-line selection), Shift+Tab always outdents —
575    /// carets, single-line selections, and multi-line selections alike; the
576    /// selection is preserved.
577    pub fn outdent(&mut self) {
578        self.indent_lines(false);
579    }
580
581    /// Delete each selection's whole-line block (Ctrl+Shift+K) — the one
582    /// `line_block` rule per selection. Overlapping
583    /// blocks (two carets on one line) merge; one discrete step, caret after.
584    pub fn delete_line(&mut self) {
585        let ranges: Vec<Range<u32>> =
586            self.selections.all().iter().map(|sel| self.line_block(sel)).collect();
587        let ops = merge_delete_ranges(ranges.into_iter());
588        self.run_edit(ops, GroupingHint::discrete());
589    }
590
591    /// THE whole-line **deletion block** for a selection: every spanned line
592    /// (a selection ending exactly at a line start does not span it — the
593    /// [`last_spanned_offset`] rule shared with `line_runs`) plus ONE
594    /// bounding newline: the trailing one, or, when the block reaches the
595    /// buffer's final line, the PRECEDING one — so no empty tail line is left
596    /// behind. Shared by [`Document::delete_line`] and the
597    /// whole-line [`Document::cut`]. (Copy's per-line text is the different
598    /// `Document::line_unit` fact — content never takes a preceding `\n`.)
599    fn line_block(&self, sel: &Selection) -> Range<u32> {
600        let buffer = &self.buffer;
601        let first = buffer.offset_to_point(sel.start()).row;
602        let last = buffer.offset_to_point(last_spanned_offset(sel)).row;
603        let start = buffer.point_to_offset(Point::new(first, 0));
604        if last + 1 < buffer.line_count() {
605            start..buffer.point_to_offset(Point::new(last + 1, 0))
606        } else {
607            start.saturating_sub(1)..buffer.len()
608        }
609    }
610
611    /// Open a fresh, indent-carrying line below (`down`, Ctrl+Enter) or above
612    /// (Ctrl+Shift+Enter) each caret's line — without splitting the line the
613    /// caret sat on — and land the caret at the new line's end. A line below
614    /// a block-opening `{` line gains one indent unit, like Enter at the line's
615    /// end. One discrete transaction.
616    pub fn insert_line(&mut self, down: bool) {
617        let buffer = &self.buffer;
618        let mut backs: Vec<u32> = Vec::new();
619        let mut ops: Vec<EditOp> = Vec::new();
620        for sel in self.selections.all() {
621            let row = buffer.offset_to_point(sel.head()).row;
622            let line = buffer.line(row);
623            let indent = leading_ws(&line);
624            if down {
625                let at = buffer.point_to_offset(Point::new(row, buffer.line_len(row)));
626                if ops.last().is_some_and(|op| op.range.start == at) {
627                    continue; // second caret on the same line — one new line
628                }
629                let mut t = String::with_capacity(1 + indent.len() + 4);
630                t.push('\n');
631                t.push_str(indent);
632                if line_opens_block(buffer, at) {
633                    t.push_str(&indent_unit(indent));
634                }
635                ops.push(EditOp::new(at..at, t));
636                backs.push(0);
637            } else {
638                let at = buffer.point_to_offset(Point::new(row, 0));
639                if ops.last().is_some_and(|op| op.range.start == at) {
640                    continue;
641                }
642                // The new line's indent, then the newline that pushes the
643                // current line down — caret pulls back over the `\n`.
644                ops.push(EditOp::new(at..at, format!("{indent}\n")));
645                backs.push(1);
646            }
647        }
648        verb_commit(self.edit_placing(ops, GroupingHint::discrete(), |patch, _| {
649            Some(carets_pulled_back(patch, &backs))
650        }));
651    }
652
653    /// Move every selected line-block up or down one line (Alt+↑/↓). A block is
654    /// a run of whole lines the selections span (`line_runs`: a
655    /// whole-line selection ending at column 0 does not span that row), runs on
656    /// consecutive rows merged — each block swaps with its neighbour line, which
657    /// must therefore belong to no other block. Every selection rides its moved
658    /// text, id and direction kept. A block at the document edge — or, moving
659    /// down, against the trailing empty line — stays put while the others move.
660    /// One undo step; never re-indents.
661    pub fn move_line(&mut self, down: bool) {
662        let line_count = self.buffer.line_count();
663        let ends_nl = self.buffer.char_before(self.buffer.len()) == Some('\n');
664        // The last row a real line occupies (the trailing empty line, if any, is
665        // not movable — moving into it would drop the terminator).
666        let last_movable = if ends_nl { line_count.saturating_sub(2) } else { line_count - 1 };
667        // Each swap rewrites its region (block + neighbour) with text of the same
668        // length, so offsets outside the moved regions never shift: every
669        // destination is the pre-edit one, with nothing to accumulate.
670        self.relocate_line_blocks(true, |buffer, run, _, block| {
671            let (r0, r1) = (run.first, run.last);
672            if (down && r1 >= last_movable) || (!down && r0 == 0) {
673                return None; // at the edge: stays put while the others move
674            }
675            let (a, b) = if down { (r0, r1 + 1) } else { (r0 - 1, r1) };
676            let has_nl = b + 1 < line_count; // row `b` carries a trailing newline
677            let region_start = buffer.point_to_offset(Point::new(a, 0));
678            let region_end =
679                if has_nl { buffer.point_to_offset(Point::new(b + 1, 0)) } else { buffer.len() };
680            let neighbour = buffer.line(if down { b } else { a });
681            let (mut text, dest) = if down {
682                // The line below moves to the front; the block shifts down past
683                // it and its `\n`.
684                (format!("{neighbour}\n{block}"), region_start + neighbour.len() as u32 + 1)
685            } else {
686                // The line above moves to the back; the block takes its place.
687                (format!("{block}\n{neighbour}"), region_start)
688            };
689            if has_nl {
690                text.push('\n');
691            }
692            // A whole-line selection's end sits past the block's `\n`; moved onto
693            // the buffer's final line the block has no `\n`, so that end clamps to
694            // the region (buffer) end.
695            Some(Relocation { op: EditOp::new(region_start..region_end, text), dest, max: Some(region_end) })
696        });
697    }
698
699    /// Duplicate every selected line-block above (`down = false`) or below
700    /// (`down = true`) — Shift+Alt+↑/↓. A block is a run of whole lines the
701    /// selections span (`line_runs`), runs that share a row merged
702    /// so no line is copied twice; runs on consecutive rows stay apart, so
703    /// carets on consecutive lines each copy their own line (VS Code). Every
704    /// selection lands on its block's copy — the lower one copying down, the
705    /// upper one copying up — id and direction kept. One undo step.
706    pub fn copy_line(&mut self, down: bool) {
707        let line_count = self.buffer.line_count();
708        // Bytes the earlier copies insert. The runs — and so the insertion
709        // points — ascend, so everything after an insertion, every later copy
710        // included, lands that much further on post-edit.
711        let mut grown = 0;
712        self.relocate_line_blocks(false, |buffer, run, block_start, block| {
713            // Where the copy is inserted, its text, and where its block starts.
714            let (at, text, copy_start) = if !down {
715                // Above: the copy takes the block's place and pushes it down.
716                (block_start, format!("{block}\n"), block_start)
717            } else if run.last + 1 < line_count {
718                let at = buffer.point_to_offset(Point::new(run.last + 1, 0));
719                (at, format!("{block}\n"), at)
720            } else {
721                // Duplicating the final line (no trailing newline): prepend one.
722                let at = buffer.len();
723                (at, format!("\n{block}"), at + 1)
724            };
725            let dest = copy_start + grown;
726            grown += text.len() as u32;
727            // The copy is the block verbatim, so a whole-line selection's end,
728            // past the block's `\n`, lands past the copy's: nothing clamps.
729            Some(Relocation { op: EditOp::insert(at, text), dest, max: None })
730        });
731    }
732
733    /// Toggle the line comment on every line the selections span (Ctrl+/).
734    /// If ANY spanned non-blank line is uncommented, comment them all —
735    /// `prefix + " "` inserted at the block's minimum indent column,
736    /// so the markers align — otherwise strip each line's prefix (and one
737    /// following space). Blank lines are skipped, unless EVERY spanned line is
738    /// blank (the start-a-comment-here case, which appends the prefix). One
739    /// transaction; the selections rebase through it and survive. A no-op until
740    /// the app injects a prefix ([`Document::set_line_comment`]) — the core
741    /// knows no language.
742    pub fn toggle_line_comment(&mut self) {
743        let Some(prefix) = self.line_comment().map(str::to_owned) else { return };
744        let rows = self.spanned_rows();
745        let ops: Vec<EditOp> = {
746            let buffer = &self.buffer;
747            let lines: Vec<(u32, Cow<str>)> = rows.iter().map(|&r| (r, buffer.line(r))).collect();
748            let non_blank: Vec<(u32, &str)> = lines
749                .iter()
750                .map(|(r, l)| (*r, l.as_ref()))
751                .filter(|(_, l)| !l.trim().is_empty())
752                .collect();
753            if non_blank.is_empty() {
754                // Every spanned line is blank: start a comment on each.
755                lines
756                    .iter()
757                    .map(|&(row, _)| {
758                        let at = buffer.point_to_offset(Point::new(row, buffer.line_len(row)));
759                        EditOp::new(at..at, format!("{prefix} "))
760                    })
761                    .collect()
762            } else if non_blank.iter().any(|(_, l)| !l.trim_start().starts_with(&prefix)) {
763                // Comment: markers aligned at the block's minimum indent.
764                let indent = |l: &str| tail_start_col(l);
765                let min_indent = non_blank.iter().map(|(_, l)| indent(l)).min().unwrap_or(0);
766                non_blank
767                    .iter()
768                    .map(|&(row, _)| {
769                        let at = buffer.point_to_offset(Point::new(row, min_indent));
770                        EditOp::new(at..at, format!("{prefix} "))
771                    })
772                    .collect()
773            } else {
774                // Uncomment: strip the prefix plus one following space.
775                non_blank
776                    .iter()
777                    .map(|&(row, line)| {
778                        let ws = tail_start_col(line) as usize;
779                        let start = buffer.point_to_offset(Point::new(row, ws as u32));
780                        let mut end = start + prefix.len() as u32;
781                        if line[ws + prefix.len()..].starts_with(' ') {
782                            end += 1;
783                        }
784                        EditOp::delete(start..end)
785                    })
786                    .collect()
787            }
788        };
789        // `edit_grouped` rebases the selections through the patch, so the
790        // selection (or caret) survives the toggle in place.
791        verb_commit(self.edit_grouped(ops, GroupingHint::discrete()));
792    }
793
794    /// Whether any selection spans more than one line — the trigger for block
795    /// (out)dent (vs. type-over / caret indent).
796    fn selection_spans_multiple_lines(&self) -> bool {
797        self.selections.all().iter().any(|s| {
798            self.buffer.offset_to_point(s.start()).row != self.buffer.offset_to_point(s.end()).row
799        })
800    }
801
802    /// Indent (`deeper`) or outdent every line the selections span by one
803    /// level of ITS OWN unit ([`indent_insert`] / [`outdent_range`] of its
804    /// leading whitespace — a block mixing tab- and space-indented lines moves
805    /// each in kind), as one transaction. The **selection is preserved** — it
806    /// rebases through the edit (its endpoints shift with the indent), as
807    /// mainstream editors do, rather than collapsing to one caret per line.
808    fn indent_lines(&mut self, deeper: bool) {
809        let rows = self.spanned_rows();
810        let buffer = &self.buffer;
811        let mut ops = Vec::new();
812        for row in rows {
813            let line_start = buffer.point_to_offset(Point::new(row, 0));
814            let line = buffer.line(row);
815            let ws = leading_ws(&line);
816            if deeper {
817                let (at, text) = indent_insert(ws);
818                ops.push(EditOp::insert(line_start + at, text));
819            } else {
820                let r = outdent_range(ws);
821                if !r.is_empty() {
822                    ops.push(EditOp::delete(line_start + r.start..line_start + r.end));
823                }
824            }
825        }
826        // `edit_grouped` rebases the selections through the patch (a range's
827        // start biases Left and its end biases Right), so the selection survives
828        // the (out)dent as a selection — not `run_edit`'s one-caret-per-op, which
829        // would collapse it to a caret.
830        verb_commit(self.edit_grouped(ops, GroupingHint::discrete()));
831    }
832
833    /// The distinct rows any selection touches, ascending (for block (out)dent
834    /// and toggle-comment): the [`line_runs`](Self::line_runs) flattened — runs
835    /// sharing a row are already merged, so no row repeats.
836    fn spanned_rows(&self) -> Vec<u32> {
837        self.line_runs(false).into_iter().flat_map(|run| run.first..=run.last).collect()
838    }
839
840    /// The whole-line **runs** the selections span — the unit move-line and
841    /// copy-line act on. Each selection spans `first..=last` rows by the
842    /// [`last_spanned_offset`] rule (a whole-line selection ending at column 0
843    /// does not span that row); runs that share a row merge, and with
844    /// `merge_adjacent` so do runs on consecutive rows. Every selection lies in
845    /// exactly one run, and the set's sorted, disjoint order makes each run's
846    /// selections a contiguous index range of [`SelectionSet::all`].
847    fn line_runs(&self, merge_adjacent: bool) -> Vec<LineRun> {
848        let buffer = &self.buffer;
849        let reach = u32::from(merge_adjacent);
850        let mut runs: Vec<LineRun> = Vec::new();
851        for (i, sel) in self.selections.all().iter().enumerate() {
852            let first = buffer.offset_to_point(sel.start()).row;
853            let last = buffer.offset_to_point(last_spanned_offset(sel)).row;
854            match runs.last_mut() {
855                // Sorted and disjoint ⇒ `first` and `last` never decrease, so
856                // only the newest run can absorb this selection.
857                Some(run) if first <= run.last + reach => {
858                    run.last = run.last.max(last);
859                    run.sels.end = i + 1;
860                }
861                _ => runs.push(LineRun { first, last, sels: i..i + 1 }),
862            }
863        }
864        runs
865    }
866
867    /// The shared shape of [`move_line`](Self::move_line) and
868    /// [`copy_line`](Self::copy_line). Per line run
869    /// ([`line_runs`](Self::line_runs)`(merge_adjacent)`), `plan` sees the run,
870    /// its block's start offset and its text (rows `first..=last`, without the
871    /// last row's `\n`) and returns the run's [`Relocation`] — or `None` to leave
872    /// the run where it is. Destinations are pre-edit arithmetic, so `plan`
873    /// accounts for any shift earlier runs' edits cause.
874    ///
875    /// Every selection of a relocated run rides its block: the block moves or is
876    /// copied verbatim, so each offset keeps its distance from the block start
877    /// (clamped to the relocation's `max`, if any); a run left in place keeps
878    /// its spans. Ids and directions survive (`moved_to`). One discrete undo
879    /// step, the placement inside the commit so redo restores it; no run
880    /// relocated ⇒ no edit at all.
881    fn relocate_line_blocks(
882        &mut self,
883        merge_adjacent: bool,
884        mut plan: impl FnMut(&Buffer, &LineRun, u32, &str) -> Option<Relocation>,
885    ) {
886        let mut spans: Vec<(u32, u32)> =
887            self.selections.all().iter().map(|s| (s.tail(), s.head())).collect();
888        let mut ops = Vec::new();
889        for run in self.line_runs(merge_adjacent) {
890            let block_start = self.buffer.point_to_offset(Point::new(run.first, 0));
891            let block_end =
892                self.buffer.point_to_offset(Point::new(run.last, self.buffer.line_len(run.last)));
893            let block = self.buffer.slice(block_start..block_end);
894            let Some(Relocation { op, dest, max }) = plan(&self.buffer, &run, block_start, &block)
895            else {
896                continue;
897            };
898            let to = |o: u32| {
899                let landed = dest + (o - block_start);
900                max.map_or(landed, |max| landed.min(max))
901            };
902            for (tail, head) in &mut spans[run.sels] {
903                (*tail, *head) = (to(*tail), to(*head));
904            }
905            ops.push(op);
906        }
907        if ops.is_empty() {
908            return;
909        }
910        let placed = self.selections.moved_to(&spans);
911        verb_commit(self.edit_placing(ops, GroupingHint::discrete(), |_, _| Some(placed)));
912        // Drop provenance through the one owner so the `AutoClosePair`
913        // decoration is removed with it — the store is the single owner of the
914        // pair, with nothing else to keep in sync.
915        self.clear_autoclose();
916    }
917
918    /// Build one op per selection via `f(start, end, buffer)`.
919    fn map_selections(
920        &self,
921        mut f: impl FnMut(u32, u32, &Buffer) -> EditOp,
922    ) -> Vec<EditOp> {
923        self.selections
924            .all()
925            .iter()
926            .map(|sel| f(sel.start(), sel.end(), &self.buffer))
927            .collect()
928    }
929
930    /// Apply verb ops as one transaction and drop a caret after each edit.
931    /// The ops must be disjoint ([`verb_commit`] holds the verb to it).
932    fn run_edit(&mut self, ops: Vec<EditOp>, hint: GroupingHint) {
933        // A caret after each edit: `new.end` is after inserted text, or the
934        // deletion point for a pure delete.
935        verb_commit(self.edit_placing(ops, hint, |patch, _| {
936            let carets: Vec<u32> = patch.edits().iter().map(|e| e.new.end).collect();
937            Some(SelectionSet::from_offsets(&carets))
938        }));
939    }
940}
941
942/// The verbs' one verdict on a commit — no verb drops a [`TransactionError`]
943/// with a bare `let _ =`. Every verb builds DISJOINT ops by construction (one
944/// per selection, or overlapping deletes merged by [`merge_delete_ranges`]),
945/// so an [`Overlap`](TransactionError::Overlap) reaching here is a verb bug —
946/// a keystroke that would silently do nothing (two carets in one indent run
947/// backspacing to the same tab stop). Debug and test builds panic on it,
948/// which turns every verb test, and the seeded multi-caret walks, into an
949/// overlap oracle. [`WouldOverflow`](TransactionError::WouldOverflow) is the
950/// one legitimate refusal — a paste past the u32 offset space, rejected
951/// atomically with the document untouched — and the verbs' `()` surface has
952/// no channel to report it, so it alone passes as a no-op. Returns whether the
953/// transaction was applied.
954pub(crate) fn verb_commit(result: Result<Committed, TransactionError>) -> bool {
955    match result {
956        Ok(_) => true,
957        Err(e) => {
958            debug_assert!(
959                !matches!(e, TransactionError::Overlap { .. }),
960                "a verb built overlapping ops (merge them first): {e}"
961            );
962            false
963        }
964    }
965}
966
967/// A maximal block of whole lines the selections span, with the selections
968/// that span it ([`Document::line_runs`]).
969struct LineRun {
970    /// The run's first row.
971    first: u32,
972    /// The run's last row (inclusive).
973    last: u32,
974    /// The run's selections, as indices into [`SelectionSet::all`].
975    sels: Range<usize>,
976}
977
978/// Where one line run's block goes — [`Document::relocate_line_blocks`]'s
979/// per-run plan.
980struct Relocation {
981    /// The edit that moves or copies the block.
982    op: EditOp,
983    /// Where the block's first byte lands post-edit.
984    dest: u32,
985    /// The furthest a carried offset may land, or `None` when nothing clamps.
986    max: Option<u32>,
987}
988
989/// One caret per edit, `backs[i]` bytes before edit `i`'s inserted end — the
990/// Enter / insert-line placement (0 = just after the text, as `run_edit`; >0
991/// pulls the caret back onto a line the insertion opened). `backs` is
992/// parallel to the ops, which the engine keeps one edit each, in order.
993fn carets_pulled_back(patch: &Patch, backs: &[u32]) -> SelectionSet {
994    let carets: Vec<u32> = patch.edits().iter().zip(backs).map(|(e, back)| e.new.end - back).collect();
995    SelectionSet::from_offsets(&carets)
996}
997
998/// The last offset a selection actually spans: one back from the exclusive
999/// `end`, so a selection ending exactly at a line start does not span that
1000/// line. THE end-exclusivity rule, shared by the row enumeration
1001/// (`line_runs`, and through it `spanned_rows`) and the whole-line deletion
1002/// block (`line_block`).
1003fn last_spanned_offset(sel: &Selection) -> u32 {
1004    if sel.end() > sel.start() {
1005        sel.end() - 1
1006    } else {
1007        sel.end()
1008    }
1009}
1010
1011/// Merge overlapping delete ranges into a minimal non-overlapping set of delete
1012/// ops, because the transaction engine rejects overlapping ops. EVERY
1013/// multi-caret delete verb builds its ops here, since a per-selection delete
1014/// can reach past its neighbour: two carets in one word (word-delete), two
1015/// carets on one line (line-delete, cut), two carets in one indent run backing
1016/// to the same tab stop, or a caret's stop reaching through a selection
1017/// (backspace). Merging deletes the shared region once — the union of what each
1018/// selection would delete alone — and `run_edit`'s one caret per merged edit
1019/// collapses the carets onto its start. Touching ranges (`a.end == b.start`)
1020/// stay separate; the engine accepts those, and their carets coincide and merge.
1021fn merge_delete_ranges(ranges: impl Iterator<Item = Range<u32>>) -> Vec<EditOp> {
1022    let mut sorted: Vec<Range<u32>> = ranges.collect();
1023    sorted.sort_by_key(|r| r.start);
1024    let mut merged: Vec<Range<u32>> = Vec::new();
1025    for r in sorted {
1026        match merged.last_mut() {
1027            Some(last) if r.start < last.end => last.end = last.end.max(r.end),
1028            _ => merged.push(r),
1029        }
1030    }
1031    merged.into_iter().map(EditOp::delete).collect()
1032}
1033
1034/// The byte offset backspace should delete back to from an empty caret: one
1035/// indent level ([`outdent_range`] of the whitespace before the caret) if the
1036/// caret is within the line's leading whitespace, else one character.
1037fn backspace_target(buffer: &Buffer, caret: u32) -> u32 {
1038    let p = buffer.offset_to_point(caret);
1039    let line = buffer.line(p.row);
1040    if p.col > 0 && p.col <= tail_start_col(&line) {
1041        // The whitespace before the caret, read as an indent — the leading
1042        // whitespace truncated to the caret, exactly as Enter copies it.
1043        // Its level is removed from the END of its trailing run (the run is
1044        // one repeated char, so where within it is moot for the text), i.e.
1045        // just before the caret.
1046        let level = outdent_range(&line[..p.col as usize]).len() as u32;
1047        if level > 0 {
1048            return caret - level;
1049        }
1050        // Leading whitespace that is neither a tab nor a space (U+3000…)
1051        // has no level: an ordinary one-char delete.
1052    }
1053    let prev = buffer.char_before(caret).expect("caret > 0");
1054    caret - prev.len_utf8() as u32
1055}
1056
1057/// The kind of one indent level — THE indent-unit rule. Enter's
1058/// block-opening level, Tab, block indent, Shift+Tab, and backspace's
1059/// tab-stop delete all classify a line here, so they cannot disagree about
1060/// it: a tab-indented line grows and shrinks by hard tabs, anything else by
1061/// spaces between stops [`default_indent_size`] apart. (A global
1062/// tabs-vs-spaces preference is a host setting this does not know; an
1063/// unindented line therefore takes spaces.)
1064#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1065enum IndentUnit {
1066    /// One hard tab.
1067    Tab,
1068    /// Spaces to the neighbouring indent stop.
1069    Spaces,
1070}
1071
1072impl IndentUnit {
1073    /// The unit leading whitespace `ws` calls for: the kind of its LAST char —
1074    /// a hard tab ⇒ [`Tab`](Self::Tab), spaces or nothing ⇒
1075    /// [`Spaces`](Self::Spaces). The last char because it is the one a level
1076    /// abuts: `\t··` is tab-indented then space-ALIGNED, and a level added
1077    /// after alignment spaces must be spaces, or the line would interleave
1078    /// kinds (`\t··\t`).
1079    fn of(ws: &str) -> Self {
1080        if ws.ends_with('\t') {
1081            Self::Tab
1082        } else {
1083            Self::Spaces
1084        }
1085    }
1086
1087    /// The char a level of this unit is made of.
1088    fn char(self) -> char {
1089        match self {
1090            Self::Tab => '\t',
1091            Self::Spaces => ' ',
1092        }
1093    }
1094
1095    /// The text of one level inserted right after `prefix` (the line's text
1096    /// before the insertion point): a hard tab, or the spaces to the next
1097    /// stop.
1098    fn text(self, prefix: &str) -> String {
1099        match self {
1100            Self::Tab => "\t".to_owned(),
1101            Self::Spaces => {
1102                let n = default_indent_size();
1103                " ".repeat((n - stop_col(prefix) % n) as usize)
1104            }
1105        }
1106    }
1107}
1108
1109/// The column `prefix`'s end sits at for indent-stop arithmetic: its bytes
1110/// after the last hard tab (or from the line start). A hard tab ends on a tab
1111/// stop, so counting from it keeps the stops where they are drawn while the
1112/// display tab width ([`crate::display_map::default_tab_size`]) is a multiple
1113/// of the indent width — and keeps a level of spaces after a tab inverse to
1114/// its outdent. Byte-based like every column here.
1115fn stop_col(prefix: &str) -> u32 {
1116    (prefix.len() - prefix.rfind('\t').map_or(0, |i| i + 1)) as u32
1117}
1118
1119/// Where one indent level goes in leading whitespace `ws`: the START of its
1120/// trailing run of the [`IndentUnit::of`] char. The run is one repeated char,
1121/// so the text comes out the same anywhere in it; its start is, for a pure
1122/// indent, column 0 — where these verbs always edited, so selections rebase
1123/// as they did (one beginning at an indented line's text stays on the text
1124/// through the (out)dent) — while a mixed line's earlier whitespace (the `\t`
1125/// before alignment spaces) is left in front, untouched.
1126fn level_start(ws: &str, unit: IndentUnit) -> u32 {
1127    ws.trim_end_matches(unit.char()).len() as u32
1128}
1129
1130/// One indent level deeper for leading whitespace `ws`: insert `.1` at byte
1131/// `.0` of the line — a hard tab on a tab-indented line, else spaces padding
1132/// the trailing space run to its next stop.
1133fn indent_insert(ws: &str) -> (u32, String) {
1134    let unit = IndentUnit::of(ws);
1135    (level_start(ws, unit), unit.text(ws))
1136}
1137
1138/// One indent level shallower for leading whitespace `ws` (or the part of it
1139/// before a caret): the byte range to delete — its last tab, or its trailing
1140/// spaces back to the previous stop (at most [`default_indent_size`], never
1141/// past a tab) — empty when `ws` has no level to give.
1142fn outdent_range(ws: &str) -> Range<u32> {
1143    let unit = IndentUnit::of(ws);
1144    let start = level_start(ws, unit);
1145    let run = ws.len() as u32 - start;
1146    let width = match unit {
1147        IndentUnit::Tab => 1, // `of` ⇒ the run holds at least this one tab
1148        IndentUnit::Spaces if run == 0 => 0,
1149        // `run` counts from the last tab or the line start: `stop_col(ws)`.
1150        IndentUnit::Spaces => (run - 1) % default_indent_size() + 1,
1151    };
1152    start..start + width
1153}
1154
1155/// The leading-whitespace indent an Enter at `caret` should carry: the current
1156/// line's leading whitespace, truncated to the caret's column (so pressing
1157/// Enter inside the indentation does not duplicate it).
1158fn enter_indent(buffer: &Buffer, caret: u32) -> String {
1159    let p = buffer.offset_to_point(caret);
1160    let line = buffer.line(p.row);
1161    let ws = leading_ws(&line);
1162    ws[..ws.len().min(p.col as usize)].to_owned()
1163}
1164
1165/// A line's leading whitespace — the indent every indent verb reads (Tab,
1166/// block (out)dent, Enter, insert-line). The one slice of the one
1167/// [`tail_start_col`] rule, so they cannot disagree on where an indent ends.
1168fn leading_ws(line: &str) -> &str {
1169    &line[..tail_start_col(line) as usize]
1170}
1171
1172/// One indent unit matching the KIND of the `copied` leading whitespace
1173/// ([`IndentUnit::of`], the rule Tab and Shift+Tab share): a tab-indented
1174/// prefix grows by one hard tab, anything else (spaces or an empty prefix) by
1175/// [`default_indent_size`] spaces — so an indent-carrying Enter never mixes
1176/// tabs and spaces within a line. A WHOLE level — the unit's
1177/// [`text`](IndentUnit::text) from an indent stop (an empty prefix) — not
1178/// Tab's pad to the next stop from the caret: the two agree on every on-grid
1179/// indent.
1180fn indent_unit(copied: &str) -> String {
1181    IndentUnit::of(copied).text("")
1182}
1183
1184/// Whether the current line opens a block at `caret`: the text left of `caret`,
1185/// right-trimmed, ends with `{` — the caret is entering a freshly opened block,
1186/// so the new line indents one unit deeper.
1187fn line_opens_block(buffer: &Buffer, caret: u32) -> bool {
1188    let p = buffer.offset_to_point(caret);
1189    buffer.line(p.row)[..p.col as usize].trim_end().ends_with('{')
1190}
1191
1192#[cfg(test)]
1193mod tests {
1194    use super::*;
1195
1196    fn doc(s: &str) -> Document {
1197        Document::new(s).unwrap()
1198    }
1199
1200    /// Count of live auto-close provenance pairs, read from the dedicated
1201    /// auto-close store that holds them.
1202    fn ac_count(d: &Document) -> usize {
1203        d.autoclose_ranges().len()
1204    }
1205
1206    #[test]
1207    fn typing_inserts_and_advances_caret() {
1208        let mut d = doc("");
1209        d.type_char('h');
1210        d.type_char('i');
1211        assert_eq!(d.text(), "hi");
1212        assert_eq!(d.selections().all()[0].head(), 2);
1213        // One typing run → one undo.
1214        assert!(d.undo());
1215        assert_eq!(d.text(), "");
1216    }
1217
1218    #[test]
1219    fn typing_over_a_selection_replaces_it() {
1220        // Select then type must replace the selection, leaving one caret after
1221        // the inserted char — no residue of the old selection.
1222        let mut d = doc("hello world");
1223        d.selections = SelectionSet::new(0);
1224        d.selections.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 0, 5));
1225        d.type_char('X');
1226        assert_eq!(d.text(), "X world");
1227        assert_eq!(d.selections().all()[0].head(), 1);
1228        assert!(d.selections().all()[0].is_empty());
1229    }
1230
1231    #[test]
1232    fn autoclose_inserts_pair_then_overtypes() {
1233        let mut d = doc("");
1234        d.type_char('(');
1235        assert_eq!(d.text(), "()");
1236        assert_eq!(d.selections().all()[0].head(), 1); // caret between
1237        d.type_char(')'); // overtype the auto-inserted close
1238        assert_eq!(d.text(), "()", "overtype skips — no second )");
1239        assert_eq!(d.selections().all()[0].head(), 2);
1240    }
1241
1242    #[test]
1243    fn autoclose_survives_typing_inside_the_pair() {
1244        let mut d = doc("");
1245        d.type_char('(');
1246        d.type_char('x'); // "(x)", caret 2
1247        assert_eq!(d.text(), "(x)");
1248        d.type_char(')'); // still overtypes the tracked close
1249        assert_eq!(d.text(), "(x)");
1250        assert_eq!(d.selections().all()[0].head(), 3);
1251    }
1252
1253    #[test]
1254    fn autoclose_guard_skips_pair_before_a_word() {
1255        let mut d = doc("abc"); // caret 0; the next char is a word char (guard fails)
1256        d.type_char('(');
1257        assert_eq!(d.text(), "(abc"); // just the opener
1258        assert_eq!(d.selections().all()[0].head(), 1);
1259    }
1260
1261    #[test]
1262    fn delete_word_back_and_forward_respect_word_and_newline_bounds() {
1263        let mut d = doc("foo bar\nbaz");
1264        d.set_selections(crate::SelectionSet::new(11)); // end of "baz"
1265        d.delete_word_back();
1266        assert_eq!(d.text(), "foo bar\n"); // the word "baz" is gone
1267        d.delete_word_back(); // caret at col 0 now → deletes only the newline
1268        assert_eq!(d.text(), "foo bar");
1269        d.set_selections(crate::SelectionSet::new(0));
1270        d.delete_word_forward();
1271        assert_eq!(d.text(), " bar"); // "foo" gone, the space stays
1272    }
1273
1274    #[test]
1275    fn delete_word_back_eats_only_a_multi_space_run() {
1276        let mut d = doc("foo   bar"); // 3 spaces
1277        d.set_selections(crate::SelectionSet::new(6)); // caret before "bar"
1278        d.delete_word_back();
1279        assert_eq!(d.text(), "foobar", "2+ whitespace → deletes only the run");
1280    }
1281
1282    #[test]
1283    fn multi_caret_word_delete_merges_overlapping_ranges() {
1284        // Two carets in one word yield overlapping delete ranges; merged into a
1285        // single delete, they remove the shared span once instead of being
1286        // rejected as an overlapping transaction (which would delete nothing).
1287        let mut d = doc("hello");
1288        d.set_selections(crate::SelectionSet::from_offsets(&[3, 5]));
1289        d.delete_word_back();
1290        assert_eq!(d.text(), "", "merged into one 0..5 delete, not dropped");
1291        assert_eq!(d.selections().len(), 1);
1292
1293        let mut d = doc("hello");
1294        d.set_selections(crate::SelectionSet::from_offsets(&[0, 2]));
1295        d.delete_word_forward();
1296        assert_eq!(d.text(), "", "forward merge too");
1297    }
1298
1299    #[test]
1300    fn autoclose_pair_backspace_deletes_both() {
1301        let mut d = doc("");
1302        d.type_char('(');
1303        d.backspace();
1304        assert_eq!(d.text(), "");
1305        assert_eq!(d.selections().all()[0].head(), 0);
1306    }
1307
1308    #[test]
1309    fn autoclose_surrounds_a_selection() {
1310        let mut d = doc("foo");
1311        d.selections.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 0, 3));
1312        d.type_char('(');
1313        assert_eq!(d.text(), "(foo)");
1314        let s = d.selections().all()[0];
1315        assert_eq!((s.start(), s.end()), (1, 4)); // still on "foo"
1316    }
1317
1318    #[test]
1319    fn autoclose_provenance_invalidates_on_caret_move() {
1320        let mut d = doc("");
1321        d.type_char('('); // "()", provenance live, caret 1
1322        d.move_carets(crate::Motion::Left, false); // caret 0 — leaves the pair
1323        d.type_char(')'); // no live provenance → literal insert, not overtype
1324        assert_eq!(d.text(), ")()");
1325    }
1326
1327    #[test]
1328    fn autoclose_quote_parity_closes_an_open_string() {
1329        // Prev char is a space (the before-guard is fine), but the line already
1330        // has one quote — an odd count means this quote closes the string:
1331        // insert literally, no pair.
1332        let mut d = doc("\" ");
1333        d.selections = SelectionSet::new(2); // caret after the space
1334        d.type_char('"');
1335        assert_eq!(d.text(), "\" \""); // one quote added, not a pair
1336    }
1337
1338    #[test]
1339    fn autoclose_pairs_at_every_caret() {
1340        // Multi-cursor '{' pairs at EVERY caret, not a lone '{'.
1341        let mut d = doc("a\nb\nc"); // the three line ends are offsets 1, 3, 5
1342        d.set_selections(crate::SelectionSet::from_offsets(&[1, 3, 5]));
1343        d.type_char('{');
1344        assert_eq!(d.text(), "a{}\nb{}\nc{}");
1345        assert_eq!(ac_count(&d), 3, "one live provenance pair per caret");
1346        // Each caret sits between its pair — typing lands inside every one.
1347        d.type_char('x');
1348        assert_eq!(d.text(), "a{x}\nb{x}\nc{x}");
1349    }
1350
1351    #[test]
1352    fn autoclose_overtypes_at_every_caret() {
1353        // Typing the closer at every pair's close skips over each (no second).
1354        let mut d = doc("a\nb\nc");
1355        d.set_selections(crate::SelectionSet::from_offsets(&[1, 3, 5]));
1356        d.type_char('{'); // "a{}\nb{}\nc{}", each caret between
1357        d.type_char('}'); // overtype every one
1358        assert_eq!(d.text(), "a{}\nb{}\nc{}", "overtype skips at every caret — no doubled closer");
1359        assert_eq!(ac_count(&d), 0, "provenance consumed");
1360    }
1361
1362    #[test]
1363    fn autoclose_surrounds_every_selection() {
1364        // '(' over a multi-selection surrounds each — "surround all occurrences".
1365        let mut d = doc("foo bar baz");
1366        d.set_selections(crate::SelectionSet::from_ranges(&[(0, 3), (4, 7), (8, 11)], 0));
1367        d.type_char('(');
1368        assert_eq!(d.text(), "(foo) (bar) (baz)");
1369    }
1370
1371    #[test]
1372    fn autoclose_pair_backspace_at_every_caret() {
1373        // Backspace strictly between empty pairs deletes both at every caret.
1374        let mut d = doc("a\nb\nc");
1375        d.set_selections(crate::SelectionSet::from_offsets(&[1, 3, 5]));
1376        d.type_char('{'); // "a{}\nb{}\nc{}"
1377        d.backspace();
1378        assert_eq!(d.text(), "a\nb\nc", "each empty pair deleted whole");
1379        assert_eq!(ac_count(&d), 0);
1380    }
1381
1382    #[test]
1383    fn autoclose_nested_opener_supersedes_provenance() {
1384        // Typing an opener INSIDE a live pair must supersede the prior provenance
1385        // (the outer pair grew to enclose the insertion), not accumulate it —
1386        // else overtype's ends-ascending search picks the wrong pair and the
1387        // closer inserts literally, yielding "(()))" instead of "(())".
1388        let mut d = doc("");
1389        d.type_char('('); // "()"
1390        d.type_char('('); // "(())", inner caret; prior provenance superseded
1391        assert_eq!(ac_count(&d), 1, "only the innermost pair is live");
1392        d.type_char(')'); // overtypes the inner close, not a literal insert
1393        assert_eq!(d.text(), "(())");
1394        // A different opener nested (brace then paren then close).
1395        let mut e = doc("");
1396        e.type_char('{');
1397        e.type_char('(');
1398        e.type_char(')');
1399        assert_eq!(e.text(), "{()}");
1400    }
1401
1402    #[test]
1403    fn autoclose_nested_pair_backspace_deletes_the_inner() {
1404        // Backspace between the innermost empty pair deletes both (not one).
1405        let mut d = doc("");
1406        d.type_char('(');
1407        d.type_char('('); // "(())", caret between the inner pair
1408        d.backspace();
1409        assert_eq!(d.text(), "()", "the inner empty pair is deleted whole");
1410    }
1411
1412    #[test]
1413    fn autoclose_surrounds_touching_selections() {
1414        // Two TOUCHING non-empty selections (e.g. find-all "ab" in "abab") must
1415        // each keep their own interior — the successor must land on "cd", not
1416        // swallow the opener and land on "(cd".
1417        let mut d = doc("abcd");
1418        d.set_selections(crate::SelectionSet::from_ranges(&[(0, 2), (2, 4)], 0));
1419        d.type_char('(');
1420        assert_eq!(d.text(), "(ab)(cd)");
1421        let sels = d.selections().all();
1422        assert_eq!((sels[0].start(), sels[0].end()), (1, 3), "interior 'ab'");
1423        assert_eq!((sels[1].start(), sels[1].end()), (5, 7), "interior 'cd', not '(cd'");
1424    }
1425
1426    #[test]
1427    fn autoclose_per_caret_pairs_only_where_the_guard_passes() {
1428        // A per-caret decision: a caret before a word char inserts a BARE opener
1429        // (the guard fails), while a caret at EOF pairs.
1430        let mut d = doc("ab");
1431        d.set_selections(crate::SelectionSet::from_offsets(&[0, 2]));
1432        d.type_char('{');
1433        assert_eq!(d.text(), "{ab{}"); // bare '{' before 'a', a pair at EOF
1434        assert_eq!(ac_count(&d), 1, "only the guarded caret paired");
1435    }
1436
1437    #[test]
1438    fn backspace_deletes_char_then_selection() {
1439        let mut d = doc("abc");
1440        d.selections = SelectionSet::new(3);
1441        d.backspace();
1442        assert_eq!(d.text(), "ab");
1443        d.selections = SelectionSet::new(0);
1444        d.selections.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 0, 2));
1445        d.backspace();
1446        assert_eq!(d.text(), "");
1447    }
1448
1449    #[test]
1450    fn backspace_in_indentation_deletes_to_tab_stop() {
1451        let mut d = doc("        x"); // 8 spaces
1452        d.selections = SelectionSet::new(8); // caret after the spaces
1453        d.backspace();
1454        assert_eq!(d.text(), "    x"); // removed 4 (one stop), not 1
1455    }
1456
1457    #[test]
1458    fn two_carets_in_one_indent_run_backspace_to_their_shared_tab_stop() {
1459        // The repro: carets at cols 3 and 4 both target stop 0, so their
1460        // tab-stop deletes are 0..3 and 0..4 — overlapping. Unmerged, the
1461        // transaction rejected them and the keystroke silently did nothing.
1462        let mut d = doc("        x"); // 8 spaces
1463        d.set_selections(SelectionSet::from_offsets(&[3, 4]));
1464        d.backspace();
1465        assert_eq!(d.text(), "    x", "one 0..4 delete — the union of both carets' stops");
1466        let heads: Vec<u32> = d.selections().all().iter().map(Selection::head).collect();
1467        assert_eq!(heads, [0], "both carets collapse onto the shared stop");
1468        assert!(d.undo());
1469        assert_eq!(d.text(), "        x", "one undo step restores the text");
1470        assert!(!d.can_undo(), "…and it was the only step");
1471    }
1472
1473    #[test]
1474    fn tab_stop_backspace_overlapping_a_selection_merges_into_it() {
1475        // The mixed form of the same overlap: a caret at col 3 deletes back to
1476        // stop 0 straight through a selection on cols 1..2 (never touching the
1477        // caret, so the selection set kept them apart).
1478        let mut d = doc("        x");
1479        let mut set = SelectionSet::new(3);
1480        set.add_selection(1, 2);
1481        d.set_selections(set);
1482        d.backspace();
1483        assert_eq!(d.text(), "     x", "0..3 ∪ 1..2 deleted once");
1484        let heads: Vec<u32> = d.selections().all().iter().map(Selection::head).collect();
1485        assert_eq!(heads, [0]);
1486        assert!(d.undo());
1487        assert_eq!(d.text(), "        x");
1488        assert!(!d.can_undo());
1489    }
1490
1491    /// The heads of every selection, ascending.
1492    fn heads(d: &Document) -> Vec<u32> {
1493        d.selections().all().iter().map(Selection::head).collect()
1494    }
1495
1496    /// Undo once and require it restored `text` and emptied the history.
1497    fn assert_one_undo_restores(d: &mut Document, text: &str) {
1498        assert!(d.undo());
1499        assert_eq!(d.text(), text, "one undo restores the text");
1500        assert!(!d.can_undo(), "…and the delete was the only step");
1501    }
1502
1503    #[test]
1504    fn adjacent_carets_char_delete_each_char_once() {
1505        // The char-delete audit: one char per caret can only TOUCH the next
1506        // range (distinct carets; a caret touching a selection merges into it),
1507        // so both carets' chars go and the carets meet.
1508        let mut d = doc("abcd");
1509        d.set_selections(SelectionSet::from_offsets(&[1, 2]));
1510        d.backspace();
1511        assert_eq!((d.text().as_ref(), heads(&d)), ("cd", vec![0]));
1512        assert_one_undo_restores(&mut d, "abcd");
1513        let mut d = doc("abcd");
1514        d.set_selections(SelectionSet::from_offsets(&[1, 2]));
1515        d.delete_forward();
1516        assert_eq!((d.text().as_ref(), heads(&d)), ("ad", vec![1]));
1517        assert_one_undo_restores(&mut d, "abcd");
1518    }
1519
1520    #[test]
1521    fn word_delete_reaching_through_a_selection_merges_into_it() {
1522        // The word-delete audit, mixed form (two carets in one word is
1523        // `multi_caret_word_delete_merges_overlapping_ranges`): the caret's word
1524        // span swallows a selection inside the same word.
1525        let mut d = doc("foo bar");
1526        let mut set = SelectionSet::new(3); // after "foo"
1527        set.add_selection(1, 2);
1528        d.set_selections(set);
1529        d.delete_word_back();
1530        assert_eq!((d.text().as_ref(), heads(&d)), (" bar", vec![0]), "0..3 ∪ 1..2");
1531        assert_one_undo_restores(&mut d, "foo bar");
1532        let mut d = doc("foo bar");
1533        let mut set = SelectionSet::new(0); // before "foo"
1534        set.add_selection(1, 2);
1535        d.set_selections(set);
1536        d.delete_word_forward();
1537        assert_eq!((d.text().as_ref(), heads(&d)), (" bar", vec![0]), "0..3 ∪ 1..2");
1538        assert_one_undo_restores(&mut d, "foo bar");
1539    }
1540
1541    #[test]
1542    fn overlapping_line_blocks_delete_once() {
1543        // The line-delete audit: a two-row selection's block contains the block
1544        // of a caret on its second row.
1545        let mut d = doc("a\nbb\nc");
1546        let mut set = SelectionSet::new(4); // end of "bb", row 1
1547        set.add_selection(0, 3); // rows 0..=1 → block 0..5
1548        d.set_selections(set);
1549        d.delete_line();
1550        assert_eq!((d.text().as_ref(), heads(&d)), ("c", vec![0]), "0..5 ∪ 2..5");
1551        assert_one_undo_restores(&mut d, "a\nbb\nc");
1552        // Cut's whole-line mode: two carets on one line yield the SAME block.
1553        let mut d = doc("aaaa\nbb");
1554        d.set_selections(SelectionSet::from_offsets(&[1, 3]));
1555        d.cut();
1556        assert_eq!((d.text().as_ref(), heads(&d)), ("bb", vec![0]));
1557        assert_one_undo_restores(&mut d, "aaaa\nbb");
1558    }
1559
1560    #[test]
1561    fn multi_caret_deletes_remove_the_union_of_each_selections_lone_delete() {
1562        // The overlap oracle for EVERY multi-caret delete verb. Spec: a
1563        // multi-selection delete removes exactly the union of what each
1564        // selection would delete alone — a shared span once, never a rejected
1565        // transaction — leaves a caret where each of those deletes began, and
1566        // one undo restores the text. Each lone delete is observed through the
1567        // public API only (a pure delete leaves its caret at the range start,
1568        // and the length drop is the width); the union is a per-byte mask, not
1569        // `merge_delete_ranges`' sort-and-sweep. Indent-heavy lines with up to
1570        // four selections make overlapping tab-stop, word and line ranges common.
1571        let mut rng = 0x0010_7AB5_70B5_u64;
1572        let mut next = move |m: u64| {
1573            rng ^= rng << 13;
1574            rng ^= rng >> 7;
1575            rng ^= rng << 17;
1576            rng % m
1577        };
1578        type Verb = fn(&mut Document);
1579        let verbs: [(&str, Verb); 6] = [
1580            ("backspace", Document::backspace),
1581            ("delete_forward", Document::delete_forward),
1582            ("delete_word_back", Document::delete_word_back),
1583            ("delete_word_forward", Document::delete_word_forward),
1584            ("delete_line", Document::delete_line),
1585            ("cut", Document::cut),
1586        ];
1587        const TAILS: &[&str] = &["", "x", "foo bar", "a.b", "(y)", "  z"];
1588        // Per verb: cases run, and cases whose lone deletes overlapped.
1589        let mut runs = [0usize; 6];
1590        let mut overlaps = [0usize; 6];
1591        for case in 0..3_000 {
1592            let text = (0..1 + next(3))
1593                .map(|_| {
1594                    // Mostly spaces, some tabs: pure, tab-then-aligned and
1595                    // interleaved indents all reach the tab-stop backspace.
1596                    let indent: String = (0..next(10))
1597                        .map(|_| if next(8) == 0 { '\t' } else { ' ' })
1598                        .collect();
1599                    format!("{indent}{}", TAILS[next(TAILS.len() as u64) as usize])
1600                })
1601                .collect::<Vec<_>>()
1602                .join("\n");
1603            let len = text.len() as u64; // ASCII: every offset is a char boundary
1604            let ranges: Vec<(u32, u32)> = (0..1 + next(4))
1605                .map(|_| {
1606                    let a = next(len + 1) as u32;
1607                    (a, if next(4) == 0 { next(len + 1) as u32 } else { a })
1608                })
1609                .collect();
1610            let set = SelectionSet::from_ranges(&ranges, 0);
1611            let v = next(verbs.len() as u64) as usize;
1612            let (name, verb) = verbs[v];
1613            // Cut's own rule: in a mixed set an empty caret copied nothing, so
1614            // it deletes nothing (alone it would cut its whole line).
1615            let mixed = set.all().iter().any(|s| !s.is_empty());
1616            let mut mask = vec![false; text.len()];
1617            let (mut starts, mut lone_total) = (Vec::new(), 0);
1618            for sel in set.all() {
1619                if name == "cut" && mixed && sel.is_empty() {
1620                    continue;
1621                }
1622                let mut lone = doc(&text);
1623                lone.set_selections(SelectionSet::from_ranges(&[(sel.tail(), sel.head())], 0));
1624                verb(&mut lone);
1625                let width = text.len() - lone.text().len();
1626                if width > 0 {
1627                    let start = lone.selections().newest().head() as usize;
1628                    mask[start..start + width].iter_mut().for_each(|m| *m = true);
1629                    starts.push(start);
1630                    lone_total += width;
1631                }
1632            }
1633            let union = mask.iter().filter(|&&m| m).count();
1634            runs[v] += 1;
1635            overlaps[v] += usize::from(union < lone_total);
1636            let expected: String =
1637                text.bytes().zip(&mask).filter(|&(_, &m)| !m).map(|(b, _)| char::from(b)).collect();
1638
1639            let mut d = doc(&text);
1640            d.set_selections(set);
1641            verb(&mut d);
1642            let what = format!("case {case}: {name} on {text:?} at {ranges:?}");
1643            assert_eq!(d.text(), expected, "{what}: the union of the lone deletes");
1644            for s in starts {
1645                let at = (s - mask[..s].iter().filter(|&&m| m).count()) as u32;
1646                assert!(
1647                    d.selections().all().iter().any(|c| c.is_empty() && c.head() == at),
1648                    "{what}: a caret where the delete from {s} began (post-edit {at})",
1649                );
1650            }
1651            if union > 0 {
1652                assert!(d.undo(), "{what}: undoable");
1653                assert_eq!(d.text(), text, "{what}: one undo restores the text");
1654            }
1655            assert!(!d.can_undo(), "{what}: the whole delete was ONE undo step");
1656        }
1657        // A one-char forward delete can only TOUCH its neighbour (a caret
1658        // touching a selection merges into it); every other verb must have
1659        // really been driven through overlapping ranges, or the oracle is vacuous.
1660        // (This seed, in `verbs` order: ~500 runs each; overlaps 33 / 0 / 111 /
1661        // 104 / 290 / 155.)
1662        for (v, (name, _)) in verbs.iter().enumerate() {
1663            if *name == "delete_forward" {
1664                assert_eq!(overlaps[v], 0, "delete_forward cannot overlap");
1665            } else {
1666                assert!(
1667                    overlaps[v] > 20,
1668                    "walk too thin for {name}: runs {runs:?}, overlaps {overlaps:?}",
1669                );
1670            }
1671        }
1672    }
1673
1674    #[test]
1675    fn enter_carries_indent_truncated_to_caret() {
1676        let mut d = doc("    hello");
1677        d.selections = SelectionSet::new(9); // end of line
1678        d.enter();
1679        assert_eq!(d.text(), "    hello\n    "); // 4-space indent carried
1680        // Enter at column 2 (inside the indent) carries only 2 spaces of
1681        // autoindent (truncated to the caret); the 2 spaces after the caret
1682        // stay with "hello" on the new line → line 1 has 4 spaces total, not 8
1683        // — the indent is truncated to the caret, never duplicated.
1684        let mut d2 = doc("    hello");
1685        d2.selections = SelectionSet::new(2);
1686        d2.enter();
1687        assert_eq!(d2.text(), "  \n    hello");
1688    }
1689
1690    #[test]
1691    fn delete_line_removes_blocks_and_the_final_line_cleanly() {
1692        let mut d = doc("one\ntwo\nthree");
1693        d.set_selections(crate::SelectionSet::new(5)); // caret in "two"
1694        d.delete_line();
1695        assert_eq!(d.text(), "one\nthree");
1696        // The final line takes its PRECEDING newline — no empty tail remains.
1697        d.set_selections(crate::SelectionSet::new(d.buffer().len()));
1698        d.delete_line();
1699        assert_eq!(d.text(), "one");
1700        // A multi-line selection deletes every spanned line (one ending at a
1701        // line start does not span that line)…
1702        let mut m = doc("a\nb\nc\nd");
1703        let mut set = crate::SelectionSet::new(0);
1704        set.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 0, 4)); // rows 0..=1
1705        m.set_selections(set);
1706        m.delete_line();
1707        assert_eq!(m.text(), "c\nd");
1708        // …and two carets on one line delete it once.
1709        let mut t = doc("xx\nyy");
1710        t.set_selections(crate::SelectionSet::from_offsets(&[0, 1]));
1711        t.delete_line();
1712        assert_eq!(t.text(), "yy");
1713    }
1714
1715    #[test]
1716    fn insert_line_below_and_above_carry_indent() {
1717        // Below: a fresh line under the caret's line, same indent, caret on it
1718        // — the caret's line is NOT split, wherever the caret sat.
1719        let mut d = doc("    a\n    b");
1720        d.set_selections(crate::SelectionSet::new(4)); // before "a"
1721        d.insert_line(true);
1722        assert_eq!(d.text(), "    a\n    \n    b");
1723        assert_eq!(d.selections().newest().head(), 10, "caret at the new line's end");
1724        // Above: the current line is pushed down; caret on the new line.
1725        let mut u = doc("    a");
1726        u.set_selections(crate::SelectionSet::new(5));
1727        u.insert_line(false);
1728        assert_eq!(u.text(), "    \n    a");
1729        assert_eq!(u.selections().newest().head(), 4);
1730        // A line below a block-opening `{` line gains one indent unit.
1731        let mut b = doc("m {");
1732        b.set_selections(crate::SelectionSet::new(0));
1733        b.insert_line(true);
1734        assert_eq!(b.text(), "m {\n    ");
1735        assert_eq!(b.selections().newest().head(), 8);
1736    }
1737
1738    #[test]
1739    fn toggle_line_comment_comments_aligned_then_uncomments() {
1740        // Ctrl+/: markers align at the block's MIN indent; blank lines are
1741        // skipped; a mixed block comments everything; toggling back strips
1742        // prefix + one space; the selection survives (rebase, not re-set).
1743        let mut d = doc("    a\n\n        b");
1744        d.set_line_comment(Some("//"));
1745        d.select_all();
1746        d.toggle_line_comment();
1747        assert_eq!(d.text(), "    // a\n\n    //     b");
1748        assert!(!d.selections().newest().is_empty(), "selection survives the toggle");
1749        d.toggle_line_comment();
1750        assert_eq!(d.text(), "    a\n\n        b");
1751        // A bare caret toggles only its own line…
1752        let mut c = doc("x\ny");
1753        c.set_line_comment(Some("//"));
1754        c.set_selections(crate::SelectionSet::new(0));
1755        c.toggle_line_comment();
1756        assert_eq!(c.text(), "// x\ny");
1757        // …stripping works without the space too.
1758        let mut s = doc("//x");
1759        s.set_line_comment(Some("//"));
1760        s.toggle_line_comment();
1761        assert_eq!(s.text(), "x");
1762        // Mixed commented/uncommented → comment ALL, then one toggle back
1763        // returns to all-commented.
1764        let mut m = doc("// a\nb");
1765        m.set_line_comment(Some("//"));
1766        m.select_all();
1767        m.toggle_line_comment();
1768        assert_eq!(m.text(), "// // a\n// b");
1769    }
1770
1771    #[test]
1772    fn toggle_line_comment_without_a_prefix_is_a_no_op() {
1773        // The core knows no language: nothing injected → nothing happens.
1774        let mut d = doc("abc");
1775        d.toggle_line_comment();
1776        assert_eq!(d.text(), "abc");
1777        // An all-blank span starts a comment ready to type into.
1778        let mut b = doc("");
1779        b.set_line_comment(Some("//"));
1780        b.toggle_line_comment();
1781        assert_eq!(b.text(), "// ");
1782        assert_eq!(b.selections().newest().head(), 3, "caret rebased after the prefix");
1783    }
1784
1785    #[test]
1786    fn copy_and_cut_expand_an_empty_caret_to_its_line() {
1787        // An empty-selection Copy takes the ENTIRE line including \n…
1788        let mut d = doc("one\ntwo\nthree");
1789        d.set_selections(crate::SelectionSet::new(5)); // caret inside "two"
1790        assert_eq!(d.clipboard_payload(), ("two\n".to_string(), true));
1791        // …the final line (no terminator in the buffer) still exports one…
1792        d.set_selections(crate::SelectionSet::new(9)); // inside "three"
1793        assert_eq!(d.clipboard_payload(), ("three\n".to_string(), true));
1794        // …a non-empty selection copies just itself, not the line…
1795        let mut set = crate::SelectionSet::new(0);
1796        set.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 0, 3));
1797        d.set_selections(set);
1798        assert_eq!(d.clipboard_payload(), ("one".to_string(), false));
1799        // …and Cut on a bare caret deletes the whole line (one undo step).
1800        d.set_selections(crate::SelectionSet::new(5));
1801        d.cut();
1802        assert_eq!(d.text(), "one\nthree");
1803        assert!(d.undo());
1804        assert_eq!(d.text(), "one\ntwo\nthree");
1805    }
1806
1807    #[test]
1808    fn mixed_set_cut_never_deletes_uncopied_lines() {
1809        // Cut IS copy+delete. In a mixed set the empty caret exports nothing, so
1810        // it must delete nothing — only the selection goes.
1811        let mut d = doc("foo bar\nbaz\n");
1812        let mut set = crate::SelectionSet::new(0);
1813        set.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 0, 3)); // "foo"
1814        set.add_caret(9); // bare caret inside "baz"
1815        d.set_selections(set);
1816        assert_eq!(d.clipboard_payload(), ("foo".to_string(), false));
1817        d.cut();
1818        assert_eq!(d.text(), " bar\nbaz\n", "the uncopied `baz` line survives");
1819    }
1820
1821    #[test]
1822    fn cut_on_the_final_line_takes_the_preceding_newline() {
1823        // Cut on the final line takes its PRECEDING newline (matching its
1824        // sibling delete_line), so no empty tail line is left behind.
1825        let mut d = doc("one\ntwo");
1826        d.set_selections(crate::SelectionSet::new(5)); // inside "two"
1827        assert_eq!(d.clipboard_payload(), ("two\n".to_string(), true));
1828        d.cut();
1829        assert_eq!(d.text(), "one", "no empty tail line remains");
1830        // Degenerate: a caret on the trailing empty line cuts that line (the
1831        // preceding newline), not nothing.
1832        let mut e = doc("abc\n");
1833        e.set_selections(crate::SelectionSet::new(4));
1834        e.cut();
1835        assert_eq!(e.text(), "abc");
1836    }
1837
1838    #[test]
1839    fn two_carets_on_one_line_cut_it_once() {
1840        let mut d = doc("aaaa\nbb");
1841        d.set_selections(crate::SelectionSet::from_offsets(&[1, 3])); // both on row 0
1842        assert_eq!(d.clipboard_payload(), ("aaaa\n".to_string(), true), "the line exports once");
1843        d.cut();
1844        assert_eq!(d.text(), "bb", "identical ranges merged into one delete");
1845    }
1846
1847    #[test]
1848    fn entire_line_paste_inserts_above_and_keeps_the_caret() {
1849        // An entire-line paste at an empty caret splices ABOVE the caret's
1850        // line; the caret rebases past the insert and stays on its own line.
1851        let mut d = doc("aaa\nbbb");
1852        d.set_selections(crate::SelectionSet::new(5)); // "bbb", col 1
1853        d.paste("two\n", true);
1854        assert_eq!(d.text(), "aaa\ntwo\nbbb");
1855        assert_eq!(d.selections().newest().head(), 9, "still col 1 of `bbb`");
1856        // Over a non-empty selection the flag is moot — plain replace.
1857        let mut set = crate::SelectionSet::new(0);
1858        set.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 0, 3));
1859        d.set_selections(set);
1860        d.paste("X\n", true);
1861        assert_eq!(d.text(), "X\n\ntwo\nbbb");
1862    }
1863
1864    #[test]
1865    fn enter_after_open_brace_indents_one_unit_deeper() {
1866        // Enter after a line-opening `{` gains one indent unit.
1867        let mut d = doc("    if {");
1868        d.selections = SelectionSet::new(8);
1869        d.enter();
1870        assert_eq!(d.text(), "    if {\n        ");
1871        // Trailing whitespace between `{` and the caret still triggers (the
1872        // rule reads the TRIMMED text left of the caret).
1873        let mut t = doc("m {  ");
1874        t.selections = SelectionSet::new(5);
1875        t.enter();
1876        assert_eq!(t.text(), "m {  \n    ");
1877        // …but a caret not after an opener stays keep-style.
1878        let mut p = doc("m { x");
1879        p.selections = SelectionSet::new(5);
1880        p.enter();
1881        assert_eq!(p.text(), "m { x\n");
1882    }
1883
1884    #[test]
1885    fn enter_between_braces_splits_onto_three_lines() {
1886        // Type `{` (auto-close pairs it), Enter → opener line, indented middle
1887        // line holding the caret, closer dedented on its own.
1888        let mut d = doc("");
1889        d.type_char('{');
1890        assert_eq!(d.text(), "{}");
1891        d.enter();
1892        assert_eq!(d.text(), "{\n    \n}");
1893        assert_eq!(d.selections().newest().head(), 6, "caret at the middle line's end");
1894        // In an indented context the closer keeps the ORIGINAL indent.
1895        let mut n = doc("    {}");
1896        n.selections = SelectionSet::new(5);
1897        n.enter();
1898        assert_eq!(n.text(), "    {\n        \n    }");
1899        assert_eq!(n.selections().newest().head(), 14);
1900        // One undo reverts the whole split (a single transaction).
1901        assert!(n.undo());
1902        assert_eq!(n.text(), "    {}");
1903    }
1904
1905    #[test]
1906    fn enter_indent_unit_matches_tab_kind() {
1907        // A hard-tab-indented line grows by a TAB, not spaces — the indent unit
1908        // matches the kind of the copied leading whitespace.
1909        let mut d = doc("\tif {");
1910        d.selections = SelectionSet::new(5);
1911        d.enter();
1912        assert_eq!(d.text(), "\tif {\n\t\t");
1913    }
1914
1915    #[test]
1916    fn tab_inserts_to_next_stop() {
1917        let mut d = doc("ab");
1918        d.selections = SelectionSet::new(2); // col 2 → next stop at 4, insert 2
1919        d.tab();
1920        assert_eq!(d.text(), "ab  ");
1921        d.tab(); // col 4 → next stop 8, insert 4
1922        assert_eq!(d.text(), "ab      ");
1923    }
1924
1925    #[test]
1926    fn tab_over_a_single_line_selection_types_over() {
1927        let mut d = doc("abcdef");
1928        d.selections.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 1, 4)); // "bcd"
1929        d.tab();
1930        // Single-line selection → replace with spaces to the next stop (from
1931        // col 1 that's 3), NOT a whole-line indent.
1932        assert_eq!(d.text(), "a   ef");
1933        assert!(d.selections().newest().is_empty(), "types over → collapses to a caret");
1934    }
1935
1936    #[test]
1937    fn shift_tab_outdents_a_single_line_and_keeps_the_selection() {
1938        let mut d = doc("    indented");
1939        d.selections.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 5, 9)); // "nden"
1940        d.outdent();
1941        assert_eq!(d.text(), "indented"); // the line outdents (unlike Tab)
1942        // the selection rides the removed indent, still on "nden"
1943        assert_eq!((d.selections().newest().start(), d.selections().newest().end()), (1, 5));
1944    }
1945
1946    #[test]
1947    fn block_indent_outdent_preserves_the_selection() {
1948        let mut d = doc("a\nb\nc");
1949        // Select from line 0 into line 2.
1950        d.selections.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 0, 5));
1951        d.tab();
1952        assert_eq!(d.text(), "    a\n    b\n    c");
1953        // The selection is preserved — one selection over the indented block,
1954        // NOT one caret per line.
1955        assert_eq!(d.selections().len(), 1, "no extra cursors");
1956        let s = d.selections().newest();
1957        assert_eq!((s.start(), s.end()), (0, 17));
1958        assert!(!s.is_empty(), "still a selection, not a caret");
1959        // Outdent restores the text and keeps the selection.
1960        d.outdent();
1961        assert_eq!(d.text(), "a\nb\nc");
1962        assert_eq!(d.selections().len(), 1);
1963        assert_eq!(
1964            (d.selections().newest().start(), d.selections().newest().end()),
1965            (0, 5)
1966        );
1967    }
1968
1969    /// Select `anchor..head` as the one selection.
1970    fn select(d: &mut Document, anchor: u32, head: u32) {
1971        d.set_selections(SelectionSet::from_ranges(&[(anchor, head)], 0));
1972    }
1973
1974    /// Run `verb`, require `after`, and require ONE undo step restoring the
1975    /// text `verb` started from.
1976    #[track_caller]
1977    fn assert_one_step(d: &mut Document, verb: fn(&mut Document), after: &str) {
1978        let before = d.text().into_owned();
1979        verb(d);
1980        assert_eq!(d.text(), after, "{before:?}");
1981        assert_one_undo_restores(d, &before);
1982    }
1983
1984    #[test]
1985    fn outdent_removes_a_hard_tab() {
1986        // The repro: Shift+Tab worked in spaces only, so a tab-indented
1987        // line could not be outdented at all.
1988        let mut d = doc("\tfoo");
1989        assert_one_step(&mut d, Document::outdent, "foo");
1990        let mut d = doc("\t\tfoo"); // one level per press, not the whole indent
1991        d.set_selections(SelectionSet::new(4)); // caret inside the text
1992        assert_one_step(&mut d, Document::outdent, "\tfoo");
1993    }
1994
1995    #[test]
1996    fn block_indent_of_a_tab_indented_block_adds_tabs() {
1997        // The repro: Tab over a tab-indented block inserted SPACES ahead of
1998        // each tab (`"    \tfoo"`), mixing kinds within the line.
1999        let mut d = doc("\tfoo\n\tbar");
2000        d.select_all();
2001        assert_one_step(&mut d, Document::tab, "\t\tfoo\n\t\tbar");
2002        // A block selection begun at the text (after Home) stays on the text:
2003        // the new level lands ahead of the old one, never inside the selection.
2004        select(&mut d, 1, 9);
2005        d.tab();
2006        let s = d.selections().newest();
2007        assert_eq!((s.start(), s.end()), (2, 11), "the block selection survives");
2008        let mut d = doc("\t\tfoo\n\t\tbar");
2009        d.select_all();
2010        assert_one_step(&mut d, Document::outdent, "\tfoo\n\tbar");
2011    }
2012
2013    #[test]
2014    fn caret_tab_on_a_tab_indented_line_inserts_a_tab() {
2015        // The caret form of the same mix: Tab at the line start (or anywhere on
2016        // a tab-indented line) inserts the line's own unit.
2017        let mut d = doc("\tfoo");
2018        d.set_selections(SelectionSet::new(0));
2019        assert_one_step(&mut d, Document::tab, "\t\tfoo");
2020        d.set_selections(SelectionSet::new(4)); // end of the line
2021        assert_one_step(&mut d, Document::tab, "\tfoo\t");
2022        // A space-indented (or unindented) line still pads to the next stop.
2023        let mut d = doc("  foo");
2024        d.set_selections(SelectionSet::new(1));
2025        assert_one_step(&mut d, Document::tab, "     foo");
2026    }
2027
2028    #[test]
2029    fn space_indents_snap_to_the_neighbouring_stops() {
2030        // Indent pads to the NEXT stop and outdent cuts back to the PREVIOUS
2031        // one — an off-grid indent is realigned, not shifted by a fixed 4.
2032        let mut d = doc("  foo\n      bar");
2033        select(&mut d, 0, 15);
2034        assert_one_step(&mut d, Document::tab, "    foo\n        bar");
2035        select(&mut d, 0, 15);
2036        assert_one_step(&mut d, Document::outdent, "foo\n    bar");
2037        let mut d = doc("      foo"); // 6 spaces
2038        assert_one_step(&mut d, Document::outdent, "    foo");
2039    }
2040
2041    #[test]
2042    fn mixed_leading_whitespace_moves_one_unit_of_its_last_kind() {
2043        // The indent unit is the kind of the leading whitespace's LAST char
2044        // (Enter's rule): tab-then-alignment-spaces is a SPACES line whose
2045        // stops count from the tab; spaces-then-tab is a TAB line.
2046        let mut d = doc("\t  foo");
2047        assert_one_step(&mut d, Document::outdent, "\tfoo");
2048        let mut d = doc("\t      foo"); // tab + 6: back to the stop at tab + 4
2049        assert_one_step(&mut d, Document::outdent, "\t    foo");
2050        let mut d = doc("\t  foo\nx");
2051        select(&mut d, 0, 8);
2052        assert_one_step(&mut d, Document::tab, "\t    foo\n    x");
2053        let mut d = doc("  \tfoo");
2054        assert_one_step(&mut d, Document::outdent, "  foo");
2055        let mut d = doc("  \tfoo\nx");
2056        select(&mut d, 0, 8);
2057        assert_one_step(&mut d, Document::tab, "  \t\tfoo\n    x");
2058        // A whitespace-only line is all leading whitespace.
2059        let mut d = doc("\t\t");
2060        assert_one_step(&mut d, Document::outdent, "\t");
2061    }
2062
2063    #[test]
2064    fn caret_tab_before_a_mixed_indents_last_run_indents_like_block_indent() {
2065        // Fails-first: a caret Tab dropped the line's unit AT the caret, so at
2066        // column 0 of `··\tfoo` (a tab line: its indent ends in a tab) it gave
2067        // `\t··\tfoo` — interleaving kinds, the very thing the unit rule exists
2068        // to prevent — where block indent gives `··\t\tfoo`. A caret ahead of
2069        // the indent's trailing run now inserts where block indent does, and
2070        // lands just past the new level.
2071        for (line, col, want, caret) in [
2072            ("  \tfoo", 0, "  \t\tfoo", 3),
2073            ("  \tfoo", 1, "  \t\tfoo", 3),
2074            ("\t  foo", 0, "\t    foo", 3), // a spaces line (tab-then-alignment)
2075        ] {
2076            let mut d = doc(line);
2077            d.set_selections(SelectionSet::new(col));
2078            d.tab();
2079            assert_eq!(d.text(), want, "{line:?} @ {col}");
2080            assert_eq!(heads(&d), [caret], "{line:?} @ {col}: the caret lands past the new level");
2081            assert_one_undo_restores(&mut d, line);
2082            // Block indent of the same line agrees.
2083            let mut b = doc(&format!("{line}\nx"));
2084            select(&mut b, 0, line.len() as u32 + 2);
2085            b.tab();
2086            assert_eq!(b.text().split('\n').next(), Some(want), "{line:?}: block indent");
2087        }
2088        // In (or at an edge of) the trailing run, the level still goes at the
2089        // caret — inserting a run's own char into it mixes nothing — and spaces
2090        // still pad from the caret to its next stop (VS Code's caret Tab).
2091        for (line, col, want) in [
2092            ("  \tfoo", 2, "  \t\tfoo"),
2093            ("\t  foo", 3, "\t    foo"),
2094            ("    foo", 2, "      foo"),
2095            ("\t\tfoo", 1, "\t\t\tfoo"),
2096        ] {
2097            let mut d = doc(line);
2098            d.set_selections(SelectionSet::new(col));
2099            assert_one_step(&mut d, Document::tab, want);
2100        }
2101    }
2102
2103    #[test]
2104    fn tab_over_a_selection_ahead_of_a_mixed_indents_last_run_never_interleaves() {
2105        // Fails-first: the caret fix above covered only empty carets, so a
2106        // selection ahead of the trailing run still had the unit typed in its
2107        // place — the `·` selected in `··\tfoo` became a tab, giving `\t·\tfoo`
2108        // where block indent gives `··\t\tfoo`. The selection is still typed
2109        // over (deleted), and the unit goes where block indent puts it.
2110        for (line, (from, to), want, caret) in [
2111            ("  \tfoo", (0, 1), " \t\tfoo", 2),
2112            ("  \tfoo", (1, 0), " \t\tfoo", 2), // either direction
2113            ("\t\t  foo", (0, 1), "\t    foo", 3), // a spaces line: tabs, then the run
2114        ] {
2115            let mut d = doc(line);
2116            select(&mut d, from, to);
2117            d.tab();
2118            assert_eq!(d.text(), want, "{line:?} {from}..{to}");
2119            assert_eq!(heads(&d), [caret], "{line:?} {from}..{to}: the caret lands past the new level");
2120            assert_one_undo_restores(&mut d, line);
2121        }
2122    }
2123
2124    #[test]
2125    fn tab_from_several_selections_in_one_mixed_indent_stays_disjoint() {
2126        // Fails-first: each caret ahead of the run inserted at the run's start
2127        // even when another selection typed over that very point — two
2128        // overlapping ops, a verb bug the debug overlap assert caught (and a
2129        // silent no-op in release). Against a selection reaching into the run,
2130        // the unit goes at that selection's start (where the run starts once
2131        // it is typed over); several selections ahead of the run each delete
2132        // and each add a level there.
2133        for (line, sels, want, carets) in [
2134            // A caret ahead of the run + a selection straddling the run start.
2135            ("  \t\tfoo", &[(0, 0), (1, 3)][..], " \t\t\tfoo", &[2, 3][..]),
2136            // The same with a selection, not a caret, ahead of the run.
2137            ("   \t\tfoo", &[(0, 1), (2, 4)][..], " \t\t\tfoo", &[2, 3][..]),
2138            // Two selections ahead of the run: both deleted, two levels added.
2139            ("    \tfoo", &[(0, 1), (2, 3)][..], "  \t\t\tfoo", &[3, 4][..]),
2140        ] {
2141            let mut d = doc(line);
2142            d.set_selections(SelectionSet::from_ranges(sels, 0));
2143            d.tab();
2144            assert_eq!(d.text(), want, "{line:?} {sels:?}");
2145            assert_eq!(heads(&d), carets, "{line:?} {sels:?}");
2146            assert_one_undo_restores(&mut d, line);
2147        }
2148    }
2149
2150    #[test]
2151    fn backspace_in_mixed_indentation_deletes_one_unit() {
2152        // Backspace-to-stop reads the same unit from the whitespace before the
2153        // caret: spaces after a tab go back to the stop counted from the tab…
2154        let mut d = doc("\t      foo"); // tab + 6
2155        d.set_selections(SelectionSet::new(7));
2156        assert_one_step(&mut d, Document::backspace, "\t    foo");
2157        d.set_selections(SelectionSet::new(7));
2158        d.backspace();
2159        assert_eq!(heads(&d), [5], "the caret lands on the stop");
2160        // …and a tab before the caret goes one tab at a time.
2161        let mut d = doc("  \t\tfoo");
2162        d.set_selections(SelectionSet::new(4));
2163        assert_one_step(&mut d, Document::backspace, "  \tfoo");
2164    }
2165
2166    #[test]
2167    fn block_indent_over_mixed_kind_lines_keeps_each_lines_kind() {
2168        // One multi-line selection over tab-indented, space-indented, mixed and
2169        // unindented lines: every line moves by ITS OWN unit, in one step.
2170        let text = "\ta\n    b\n\t  c\nd\n  \te";
2171        let mut d = doc(text);
2172        d.select_all();
2173        assert_one_step(&mut d, Document::tab, "\t\ta\n        b\n\t    c\n    d\n  \t\te");
2174        d.select_all();
2175        assert_one_step(&mut d, Document::outdent, "a\nb\n\tc\nd\n  e");
2176        // Multi-caret Shift+Tab — one caret per line, mixed kinds — likewise.
2177        let mut d = doc(text);
2178        d.set_selections(SelectionSet::from_offsets(&[2, 8, 13, 15, 20]));
2179        assert_one_step(&mut d, Document::outdent, "a\nb\n\tc\nd\n  e");
2180    }
2181
2182    #[test]
2183    fn multi_cursor_typing_edits_every_caret() {
2184        let mut d = doc("a b c");
2185        d.selections = SelectionSet::new(0);
2186        d.selections.add_caret(2);
2187        d.selections.add_caret(4);
2188        d.type_char('X');
2189        assert_eq!(d.text(), "Xa Xb Xc");
2190        assert_eq!(d.selections().len(), 3);
2191    }
2192
2193    #[test]
2194    fn newline_paste_is_normalized_and_splits_lines() {
2195        let mut d = doc("");
2196        d.insert_text("one\r\ntwo");
2197        assert_eq!(d.text(), "one\ntwo");
2198        assert_eq!(d.buffer().line_count(), 2);
2199    }
2200
2201    #[test]
2202    fn enter_undoes_and_redoes() {
2203        let mut d = doc("ab");
2204        d.selections = SelectionSet::new(1);
2205        d.enter();
2206        assert_eq!(d.text(), "a\nb");
2207        assert!(d.undo());
2208        assert_eq!(d.text(), "ab");
2209        assert!(d.redo());
2210        assert_eq!(d.text(), "a\nb");
2211    }
2212
2213    #[test]
2214    fn caret_tracks_text_after_undo() {
2215        let mut d = doc("");
2216        for ch in "hello".chars() {
2217            d.type_char(ch);
2218        }
2219        d.undo(); // removes the whole run
2220        assert!(d.selections().all()[0].head() <= d.buffer().len(), "caret stays valid");
2221    }
2222
2223    #[test]
2224    fn undo_returns_the_caret_to_the_edit() {
2225        // Undo restores the caret from before the run (the region's start here);
2226        // redo the caret the run left (its end) — as if the edit had just
2227        // (un)happened.
2228        let mut d = doc("");
2229        for ch in "hello".chars() {
2230            d.type_char(ch);
2231        }
2232        assert_eq!(d.selections().all()[0].head(), 5, "caret after typing");
2233        d.undo();
2234        assert_eq!(d.buffer().text(), "", "text undone");
2235        assert_eq!(d.selections().all()[0].head(), 0, "caret returns to the edit site");
2236        d.redo();
2237        assert_eq!(d.buffer().text(), "hello", "text redone");
2238        assert_eq!(d.selections().all()[0].head(), 5, "caret follows the redo");
2239    }
2240
2241    #[test]
2242    fn undo_jumps_the_caret_to_the_edit_from_afar() {
2243        // Edit at the top, then click a caret elsewhere, then undo. The caret
2244        // must JUMP to the reverted edit (so you see what changed), landing at
2245        // the edit site rather than merely rebasing where it was parked.
2246        let mut d = doc("aaa\nbbb\nccc\nddd\n");
2247        d.set_selections(crate::SelectionSet::new(0)); // top of the document
2248        d.type_char('\n'); // an edit at offset 0 (like hitting Enter at the top)
2249        d.set_selections(crate::SelectionSet::new(10)); // click a caret down in the body
2250        assert_eq!(d.selections().all()[0].head(), 10, "caret parked in the body");
2251        assert!(d.undo());
2252        assert_eq!(d.buffer().text(), "aaa\nbbb\nccc\nddd\n", "the newline is gone");
2253        assert_eq!(
2254            d.selections().all()[0].head(),
2255            0,
2256            "undo jumps the caret to the reverted edit, not leaves it at 10"
2257        );
2258    }
2259}