1use core::ops::Range;
16use std::borrow::Cow;
17
18use crate::autoclose;
19use crate::buffer::Buffer;
20use crate::coords::Point;
21use crate::document::Document;
22use crate::history::{GroupingHint, OpClass};
23use crate::row_layout::tail_start_col;
24use crate::selection::{Selection, SelectionSet};
25use crate::transaction::EditOp;
26
27#[must_use]
34pub fn default_indent_size() -> u32 {
35 4
36}
37
38impl Document {
39 pub fn type_char(&mut self, ch: char) {
42 if self.try_overtype(ch) {
46 return; }
48 if self.try_autoclose(ch) {
49 return; }
51 if self.try_surround(ch) {
52 return; }
54 let mut s = [0u8; 4];
58 let text: &str = ch.encode_utf8(&mut s);
59 let ops = self.map_selections(|sel_start, sel_end, _| EditOp::new(sel_start..sel_end, text));
60 self.run_edit(ops, GroupingHint::mergeable(OpClass::Type));
61 }
62
63 fn can_autoclose(&self, open: char, caret: u32) -> bool {
66 match self.buffer.char_at(caret) {
69 None => {}
70 Some(c) if c.is_whitespace() || autoclose::is_closer(c) => {}
71 Some(_) => return false,
72 }
73 if autoclose::is_quote(open) {
74 if let Some(p) = self.buffer.char_before(caret) {
77 if autoclose::is_word_char(p) {
78 return false;
79 }
80 }
81 let row = self.buffer.offset_to_point(caret).row;
84 let quotes = self.buffer.line(row).chars().filter(|&c| c == '"').count();
85 if quotes % 2 == 1 {
86 return false;
87 }
88 }
89 true
90 }
91
92 fn try_overtype(&mut self, ch: char) -> bool {
97 if !autoclose::is_closer(ch) {
98 return false;
99 }
100 let ranges = self.autoclose_ranges(); let on_close = |caret: u32| {
102 let i = ranges.partition_point(|r| r.end < caret);
103 i < ranges.len() && ranges[i].end == caret
104 };
105 let sels = self.selections.all();
106 let all = !ranges.is_empty()
107 && sels.iter().all(|s| {
108 s.is_empty() && self.buffer.char_at(s.head()) == Some(ch) && on_close(s.head())
109 });
110 if !all {
111 return false;
112 }
113 let step = ch.len_utf8() as u32;
114 let carets: Vec<u32> = sels.iter().map(|s| s.head() + step).collect();
115 self.selections = SelectionSet::from_offsets(&carets);
116 self.clear_autoclose(); true
118 }
119
120 fn try_autoclose(&mut self, ch: char) -> bool {
127 let Some(close) = autoclose::opener_close(ch) else {
128 return false;
129 };
130 if self.selections.all().iter().any(|s| !s.is_empty()) {
131 return false; }
133 let plan: Vec<bool> =
135 self.selections.all().iter().map(|s| self.can_autoclose(ch, s.head())).collect();
136 let mut pair_text = String::with_capacity(ch.len_utf8() + close.len_utf8());
137 pair_text.push(ch);
138 pair_text.push(close);
139 let mut open_buf = [0u8; 4];
140 let open_text: &str = ch.encode_utf8(&mut open_buf);
141 let ops: Vec<EditOp> = self
142 .selections
143 .all()
144 .iter()
145 .zip(&plan)
146 .map(|(s, &pair)| {
147 let at = s.head();
148 EditOp::new(at..at, if pair { pair_text.as_str() } else { open_text })
149 })
150 .collect();
151 let hint = GroupingHint { op: OpClass::Type, seal_before: true, seal_after: false };
152 let Ok(committed) = self.edit_grouped(ops, hint) else {
153 return true; };
155 let close_len = close.len_utf8() as u32;
157 let edits = committed.patch().edits();
158 let mut carets = Vec::with_capacity(edits.len());
159 let mut pairs = Vec::new();
160 for (e, &pair) in edits.iter().zip(&plan) {
161 if pair {
162 let close_off = e.new.end - close_len; carets.push(close_off); pairs.push((e.new.start, close_off)); } else {
166 carets.push(e.new.end); }
168 }
169 self.selections = SelectionSet::from_offsets(&carets);
170 if !pairs.is_empty() {
171 self.clear_autoclose();
178 self.add_autoclose_pairs(pairs);
179 }
180 true
181 }
182
183 fn try_surround(&mut self, ch: char) -> bool {
187 let Some(close) = autoclose::opener_close(ch) else {
188 return false;
189 };
190 let sels = self.selections.all();
191 if sels.is_empty() || sels.iter().any(|s| s.is_empty()) {
192 return false;
193 }
194 let (open_len, close_len) = (ch.len_utf8() as u32, close.len_utf8() as u32);
195 let ops: Vec<EditOp> = sels
201 .iter()
202 .map(|s| {
203 let content = self.buffer.slice(s.start()..s.end());
204 let mut wrapped = String::with_capacity(content.len() + open_len as usize + close_len as usize);
205 wrapped.push(ch);
206 wrapped.push_str(&content);
207 wrapped.push(close);
208 EditOp::new(s.start()..s.end(), &wrapped)
209 })
210 .collect();
211 let Ok(committed) = self.edit_grouped(ops, GroupingHint::discrete()) else {
212 return true;
213 };
214 let ranges: Vec<(u32, u32)> = committed
216 .patch()
217 .edits()
218 .iter()
219 .map(|e| (e.new.start + open_len, e.new.end - close_len))
220 .collect();
221 let newest = ranges.len() - 1;
222 self.selections = SelectionSet::from_ranges(&ranges, newest);
223 true
224 }
225
226 pub fn insert_text(&mut self, text: &str) {
229 let ops = self.map_selections(|sel_start, sel_end, _| {
230 EditOp::new(sel_start..sel_end, text)
231 });
232 self.run_edit(ops, GroupingHint::discrete());
233 }
234
235 #[must_use]
244 pub fn clipboard_payload(&self) -> (String, bool) {
245 let sels = self.selections.all();
246 if sels.iter().all(Selection::is_empty) {
247 let mut units: Vec<(u32, u32)> = sels.iter().map(|s| self.line_unit(s.head())).collect();
248 units.dedup();
249 let lines: String = units
250 .iter()
251 .map(|&(start, end)| {
252 let slice = self.buffer.slice(start..end);
253 if slice.ends_with('\n') {
254 slice.into_owned()
255 } else {
256 format!("{slice}\n")
257 }
258 })
259 .collect();
260 return (lines, true);
261 }
262 let joined = sels
263 .iter()
264 .filter(|s| !s.is_empty())
265 .map(|s| self.buffer.slice(s.start()..s.end()))
266 .collect::<Vec<_>>()
267 .join("\n");
268 (joined, false)
269 }
270
271 pub fn cut(&mut self) {
282 let all_empty = self.selections.all().iter().all(Selection::is_empty);
283 let ranges: Vec<Range<u32>> = self
284 .selections
285 .all()
286 .iter()
287 .filter_map(|sel| {
288 if sel.is_empty() {
289 all_empty.then(|| self.line_block(sel))
290 } else {
291 Some(sel.start()..sel.end())
292 }
293 })
294 .collect();
295 let ops = merge_delete_ranges(ranges.into_iter());
296 self.run_edit(ops, GroupingHint::discrete());
297 }
298
299 pub fn paste(&mut self, text: &str, entire_line: bool) {
304 if entire_line && self.selections.all().iter().all(Selection::is_empty) {
305 let mut starts: Vec<u32> =
306 self.selections.all().iter().map(|s| self.line_unit(s.head()).0).collect();
307 starts.dedup();
308 let ops = starts.into_iter().map(|at| EditOp::new(at..at, text)).collect();
309 let _ = self.edit_grouped(ops, GroupingHint::discrete());
312 return;
313 }
314 self.insert_text(text);
315 }
316
317 pub fn backspace(&mut self) {
320 let ranges = self.autoclose_ranges();
325 if !ranges.is_empty() {
326 let empty_pair_at = |caret: u32| -> Option<Range<u32>> {
327 let i = ranges.partition_point(|r| r.end < caret);
328 ranges.get(i).filter(|r| r.end == caret && r.start + 1 == r.end).cloned()
329 };
330 let pairs: Option<Vec<Range<u32>>> = self
331 .selections
332 .all()
333 .iter()
334 .map(|s| s.is_empty().then(|| empty_pair_at(s.head())).flatten())
335 .collect();
336 if let Some(pairs) = pairs {
337 let ops: Vec<EditOp> = pairs
338 .iter()
339 .map(|r| {
340 let close_len =
341 self.buffer.char_at(r.end).map_or(1, |c| c.len_utf8() as u32);
342 EditOp::delete(r.start..r.end + close_len)
343 })
344 .collect();
345 self.clear_autoclose();
346 let hint =
347 GroupingHint { op: OpClass::Delete, seal_before: true, seal_after: true };
348 self.run_edit(ops, hint);
349 return;
350 }
351 }
352 let buffer = &self.buffer;
353 let ops = self.selections.all().iter().filter_map(|sel| {
354 if !sel.is_empty() {
355 return Some(EditOp::delete(sel.start()..sel.end()));
356 }
357 let caret = sel.head();
358 if caret == 0 {
359 return None;
360 }
361 let start = backspace_target(buffer, caret);
362 Some(EditOp::delete(start..caret))
363 });
364 let ops: Vec<_> = ops.collect();
365 self.run_edit(ops, GroupingHint::mergeable(OpClass::Delete));
366 }
367
368 pub fn delete_forward(&mut self) {
371 let len = self.buffer.len();
372 let buffer = &self.buffer;
373 let ops: Vec<_> = self
374 .selections
375 .all()
376 .iter()
377 .filter_map(|sel| {
378 if !sel.is_empty() {
379 return Some(EditOp::delete(sel.start()..sel.end()));
380 }
381 let caret = sel.head();
382 if caret >= len {
383 return None;
384 }
385 let next = buffer.char_at(caret).expect("caret < len");
386 Some(EditOp::delete(caret..caret + next.len_utf8() as u32))
387 })
388 .collect();
389 self.run_edit(ops, GroupingHint::mergeable(OpClass::Delete));
390 }
391
392 pub fn delete_word_back(&mut self) {
402 let buffer = &self.buffer;
403 let ranges = self.selections.all().iter().filter_map(|sel| {
404 if !sel.is_empty() {
405 return Some(sel.start()..sel.end());
406 }
407 let caret = sel.head();
408 let start = crate::movement::word_delete_left(buffer, caret);
409 (start < caret).then_some(start..caret)
410 });
411 let ops = merge_delete_ranges(ranges);
412 self.run_edit(ops, GroupingHint { op: OpClass::Delete, seal_before: true, seal_after: true });
413 }
414
415 pub fn delete_word_forward(&mut self) {
419 let len = self.buffer.len();
420 let buffer = &self.buffer;
421 let ranges = self.selections.all().iter().filter_map(|sel| {
422 if !sel.is_empty() {
423 return Some(sel.start()..sel.end());
424 }
425 let caret = sel.head();
426 if caret >= len {
427 return None;
428 }
429 let end = crate::movement::word_delete_right(buffer, caret);
430 (end > caret).then_some(caret..end)
431 });
432 let ops = merge_delete_ranges(ranges);
433 self.run_edit(ops, GroupingHint { op: OpClass::Delete, seal_before: true, seal_after: true });
434 }
435
436 pub fn enter(&mut self) {
451 let buffer = &self.buffer;
452 let mut backs: Vec<u32> = Vec::new();
455 let ops: Vec<_> = self
456 .selections
457 .all()
458 .iter()
459 .map(|sel| {
460 let indent = enter_indent(buffer, sel.start());
461 let opens = line_opens_block(buffer, sel.start());
462 let between = opens
463 && buffer.char_before(sel.start()) == Some('{')
464 && buffer.char_at(sel.end()) == Some('}');
465 let mut t = String::with_capacity(2 * (1 + indent.len()) + 4);
466 t.push('\n');
467 t.push_str(&indent);
468 if opens {
469 t.push_str(&indent_unit(&indent));
470 }
471 if between {
472 t.push('\n');
473 t.push_str(&indent);
474 backs.push(1 + indent.len() as u32); } else {
476 backs.push(0);
477 }
478 EditOp::new(sel.start()..sel.end(), t)
479 })
480 .collect();
481 let hint = GroupingHint { op: OpClass::Type, seal_before: true, seal_after: false };
482 let Ok(committed) = self.edit_grouped(ops, hint) else {
483 return;
484 };
485 if committed.is_empty() {
486 return;
487 }
488 let carets: Vec<u32> = committed
490 .patch()
491 .edits()
492 .iter()
493 .zip(&backs)
494 .map(|(e, back)| e.new.end - back)
495 .collect();
496 self.selections = SelectionSet::from_offsets(&carets);
497 }
498
499 pub fn tab(&mut self) {
502 let indent = default_indent_size();
503 if self.selection_spans_multiple_lines() {
504 self.indent_lines(indent as i32);
506 } else {
507 let ops = self.map_selections(|start, end, buffer| {
511 let col = buffer.offset_to_point(start).col;
512 let n = indent - (col % indent);
513 EditOp::new(start..end, " ".repeat(n as usize))
514 });
515 self.run_edit(ops, GroupingHint::discrete());
516 }
517 }
518
519 pub fn outdent(&mut self) {
524 self.indent_lines(-(default_indent_size() as i32));
525 }
526
527 pub fn delete_line(&mut self) {
531 let ranges: Vec<Range<u32>> =
532 self.selections.all().iter().map(|sel| self.line_block(sel)).collect();
533 let ops = merge_delete_ranges(ranges.into_iter());
534 self.run_edit(ops, GroupingHint::discrete());
535 }
536
537 fn line_block(&self, sel: &Selection) -> Range<u32> {
546 let buffer = &self.buffer;
547 let first = buffer.offset_to_point(sel.start()).row;
548 let last = buffer.offset_to_point(last_spanned_offset(sel)).row;
549 let start = buffer.point_to_offset(Point::new(first, 0));
550 if last + 1 < buffer.line_count() {
551 start..buffer.point_to_offset(Point::new(last + 1, 0))
552 } else {
553 start.saturating_sub(1)..buffer.len()
554 }
555 }
556
557 pub fn insert_line(&mut self, down: bool) {
563 let buffer = &self.buffer;
564 let mut backs: Vec<u32> = Vec::new();
565 let mut ops: Vec<EditOp> = Vec::new();
566 for sel in self.selections.all() {
567 let row = buffer.offset_to_point(sel.head()).row;
568 let line = buffer.line(row);
569 let indent = &line[..tail_start_col(&line) as usize];
570 if down {
571 let at = buffer.point_to_offset(Point::new(row, buffer.line_len(row)));
572 if ops.last().is_some_and(|op| op.range.start == at) {
573 continue; }
575 let mut t = String::with_capacity(1 + indent.len() + 4);
576 t.push('\n');
577 t.push_str(indent);
578 if line_opens_block(buffer, at) {
579 t.push_str(&indent_unit(indent));
580 }
581 ops.push(EditOp::new(at..at, t));
582 backs.push(0);
583 } else {
584 let at = buffer.point_to_offset(Point::new(row, 0));
585 if ops.last().is_some_and(|op| op.range.start == at) {
586 continue;
587 }
588 ops.push(EditOp::new(at..at, format!("{indent}\n")));
591 backs.push(1);
592 }
593 }
594 let Ok(committed) = self.edit_grouped(ops, GroupingHint::discrete()) else {
595 return;
596 };
597 if committed.is_empty() {
598 return;
599 }
600 let carets: Vec<u32> = committed
601 .patch()
602 .edits()
603 .iter()
604 .zip(&backs)
605 .map(|(e, back)| e.new.end - back)
606 .collect();
607 self.selections = SelectionSet::from_offsets(&carets);
608 }
609
610 pub fn toggle_line_comment(&mut self) {
620 let Some(prefix) = self.line_comment().map(str::to_owned) else { return };
621 let rows = self.spanned_rows();
622 let ops: Vec<EditOp> = {
623 let buffer = &self.buffer;
624 let lines: Vec<(u32, Cow<str>)> = rows.iter().map(|&r| (r, buffer.line(r))).collect();
625 let non_blank: Vec<(u32, &str)> = lines
626 .iter()
627 .map(|(r, l)| (*r, l.as_ref()))
628 .filter(|(_, l)| !l.trim().is_empty())
629 .collect();
630 if non_blank.is_empty() {
631 lines
633 .iter()
634 .map(|&(row, _)| {
635 let at = buffer.point_to_offset(Point::new(row, buffer.line_len(row)));
636 EditOp::new(at..at, format!("{prefix} "))
637 })
638 .collect()
639 } else if non_blank.iter().any(|(_, l)| !l.trim_start().starts_with(&prefix)) {
640 let indent = |l: &str| tail_start_col(l);
642 let min_indent = non_blank.iter().map(|(_, l)| indent(l)).min().unwrap_or(0);
643 non_blank
644 .iter()
645 .map(|&(row, _)| {
646 let at = buffer.point_to_offset(Point::new(row, min_indent));
647 EditOp::new(at..at, format!("{prefix} "))
648 })
649 .collect()
650 } else {
651 non_blank
653 .iter()
654 .map(|&(row, line)| {
655 let ws = tail_start_col(line) as usize;
656 let start = buffer.point_to_offset(Point::new(row, ws as u32));
657 let mut end = start + prefix.len() as u32;
658 if line[ws + prefix.len()..].starts_with(' ') {
659 end += 1;
660 }
661 EditOp::delete(start..end)
662 })
663 .collect()
664 }
665 };
666 let _ = self.edit_grouped(ops, GroupingHint::discrete());
669 }
670
671 fn selection_spans_multiple_lines(&self) -> bool {
674 self.selections.all().iter().any(|s| {
675 self.buffer.offset_to_point(s.start()).row != self.buffer.offset_to_point(s.end()).row
676 })
677 }
678
679 fn indent_lines(&mut self, n: i32) {
684 let rows = self.spanned_rows();
685 let buffer = &self.buffer;
686 let mut ops = Vec::new();
687 for row in rows {
688 let line_start = buffer.point_to_offset(Point::new(row, 0));
689 if n > 0 {
690 ops.push(EditOp::insert(line_start, " ".repeat(n as usize)));
691 } else {
692 let line = buffer.line(row);
693 let removable =
694 line.bytes().take((-n) as usize).take_while(|&b| b == b' ').count() as u32;
695 if removable > 0 {
696 ops.push(EditOp::delete(line_start..line_start + removable));
697 }
698 }
699 }
700 let _ = self.edit_grouped(ops, GroupingHint::discrete());
705 }
706
707 fn spanned_rows(&self) -> Vec<u32> {
710 let buffer = &self.buffer;
711 let mut rows = Vec::new();
712 for sel in self.selections.all() {
713 let first = buffer.offset_to_point(sel.start()).row;
714 let last = buffer.offset_to_point(last_spanned_offset(sel)).row;
715 for r in first..=last {
716 if rows.last() != Some(&r) {
717 rows.push(r);
718 }
719 }
720 }
721 rows.dedup();
722 rows
723 }
724
725 fn map_selections(
727 &self,
728 mut f: impl FnMut(u32, u32, &Buffer) -> EditOp,
729 ) -> Vec<EditOp> {
730 self.selections
731 .all()
732 .iter()
733 .map(|sel| f(sel.start(), sel.end(), &self.buffer))
734 .collect()
735 }
736
737 fn run_edit(&mut self, ops: Vec<EditOp>, hint: GroupingHint) {
739 let Ok(committed) = self.edit_grouped(ops, hint) else {
740 return; };
742 if committed.is_empty() {
743 return;
744 }
745 let carets: Vec<u32> = committed.patch().edits().iter().map(|e| e.new.end).collect();
748 self.selections = SelectionSet::from_offsets(&carets);
749 }
750}
751
752fn last_spanned_offset(sel: &Selection) -> u32 {
757 if sel.end() > sel.start() {
758 sel.end() - 1
759 } else {
760 sel.end()
761 }
762}
763
764fn merge_delete_ranges(ranges: impl Iterator<Item = Range<u32>>) -> Vec<EditOp> {
770 let mut sorted: Vec<Range<u32>> = ranges.collect();
771 sorted.sort_by_key(|r| r.start);
772 let mut merged: Vec<Range<u32>> = Vec::new();
773 for r in sorted {
774 match merged.last_mut() {
775 Some(last) if r.start < last.end => last.end = last.end.max(r.end),
776 _ => merged.push(r),
777 }
778 }
779 merged.into_iter().map(EditOp::delete).collect()
780}
781
782fn backspace_target(buffer: &Buffer, caret: u32) -> u32 {
785 let p = buffer.offset_to_point(caret);
786 let line = buffer.line(p.row);
787 let in_indent = p.col > 0 && line.as_bytes()[..p.col as usize].iter().all(|&b| b == b' ');
788 if in_indent {
789 let indent = default_indent_size();
790 let target_col = ((p.col - 1) / indent) * indent;
791 buffer.point_to_offset(Point::new(p.row, target_col))
792 } else {
793 let prev = buffer.char_before(caret).expect("caret > 0");
794 caret - prev.len_utf8() as u32
795 }
796}
797
798fn enter_indent(buffer: &Buffer, caret: u32) -> String {
802 let p = buffer.offset_to_point(caret);
803 let line = buffer.line(p.row);
804 let indent_len = tail_start_col(&line) as usize;
805 let take = indent_len.min(p.col as usize);
806 line[..take].to_owned()
807}
808
809fn indent_unit(copied: &str) -> String {
814 if copied.ends_with('\t') {
815 "\t".to_owned()
816 } else {
817 " ".repeat(default_indent_size() as usize)
818 }
819}
820
821fn line_opens_block(buffer: &Buffer, caret: u32) -> bool {
825 let p = buffer.offset_to_point(caret);
826 buffer.line(p.row)[..p.col as usize].trim_end().ends_with('{')
827}
828
829#[cfg(test)]
830mod tests {
831 use super::*;
832
833 fn doc(s: &str) -> Document {
834 Document::new(s).unwrap()
835 }
836
837 fn ac_count(d: &Document) -> usize {
840 d.autoclose_ranges().len()
841 }
842
843 #[test]
844 fn typing_inserts_and_advances_caret() {
845 let mut d = doc("");
846 d.type_char('h');
847 d.type_char('i');
848 assert_eq!(d.text(), "hi");
849 assert_eq!(d.selections().all()[0].head(), 2);
850 assert!(d.undo());
852 assert_eq!(d.text(), "");
853 }
854
855 #[test]
856 fn typing_over_a_selection_replaces_it() {
857 let mut d = doc("hello world");
860 d.selections = SelectionSet::new(0);
861 d.selections.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 0, 5));
862 d.type_char('X');
863 assert_eq!(d.text(), "X world");
864 assert_eq!(d.selections().all()[0].head(), 1);
865 assert!(d.selections().all()[0].is_empty());
866 }
867
868 #[test]
869 fn autoclose_inserts_pair_then_overtypes() {
870 let mut d = doc("");
871 d.type_char('(');
872 assert_eq!(d.text(), "()");
873 assert_eq!(d.selections().all()[0].head(), 1); d.type_char(')'); assert_eq!(d.text(), "()", "overtype skips — no second )");
876 assert_eq!(d.selections().all()[0].head(), 2);
877 }
878
879 #[test]
880 fn autoclose_survives_typing_inside_the_pair() {
881 let mut d = doc("");
882 d.type_char('(');
883 d.type_char('x'); assert_eq!(d.text(), "(x)");
885 d.type_char(')'); assert_eq!(d.text(), "(x)");
887 assert_eq!(d.selections().all()[0].head(), 3);
888 }
889
890 #[test]
891 fn autoclose_guard_skips_pair_before_a_word() {
892 let mut d = doc("abc"); d.type_char('(');
894 assert_eq!(d.text(), "(abc"); assert_eq!(d.selections().all()[0].head(), 1);
896 }
897
898 #[test]
899 fn delete_word_back_and_forward_respect_word_and_newline_bounds() {
900 let mut d = doc("foo bar\nbaz");
901 d.set_selections(crate::SelectionSet::new(11)); d.delete_word_back();
903 assert_eq!(d.text(), "foo bar\n"); d.delete_word_back(); assert_eq!(d.text(), "foo bar");
906 d.set_selections(crate::SelectionSet::new(0));
907 d.delete_word_forward();
908 assert_eq!(d.text(), " bar"); }
910
911 #[test]
912 fn delete_word_back_eats_only_a_multi_space_run() {
913 let mut d = doc("foo bar"); d.set_selections(crate::SelectionSet::new(6)); d.delete_word_back();
916 assert_eq!(d.text(), "foobar", "2+ whitespace → deletes only the run");
917 }
918
919 #[test]
920 fn multi_caret_word_delete_merges_overlapping_ranges() {
921 let mut d = doc("hello");
925 d.set_selections(crate::SelectionSet::from_offsets(&[3, 5]));
926 d.delete_word_back();
927 assert_eq!(d.text(), "", "merged into one 0..5 delete, not dropped");
928 assert_eq!(d.selections().len(), 1);
929
930 let mut d = doc("hello");
931 d.set_selections(crate::SelectionSet::from_offsets(&[0, 2]));
932 d.delete_word_forward();
933 assert_eq!(d.text(), "", "forward merge too");
934 }
935
936 #[test]
937 fn autoclose_pair_backspace_deletes_both() {
938 let mut d = doc("");
939 d.type_char('(');
940 d.backspace();
941 assert_eq!(d.text(), "");
942 assert_eq!(d.selections().all()[0].head(), 0);
943 }
944
945 #[test]
946 fn autoclose_surrounds_a_selection() {
947 let mut d = doc("foo");
948 d.selections.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 0, 3));
949 d.type_char('(');
950 assert_eq!(d.text(), "(foo)");
951 let s = d.selections().all()[0];
952 assert_eq!((s.start(), s.end()), (1, 4)); }
954
955 #[test]
956 fn autoclose_provenance_invalidates_on_caret_move() {
957 let mut d = doc("");
958 d.type_char('('); d.move_carets(crate::Motion::Left, false); d.type_char(')'); assert_eq!(d.text(), ")()");
962 }
963
964 #[test]
965 fn autoclose_quote_parity_closes_an_open_string() {
966 let mut d = doc("\" ");
970 d.selections = SelectionSet::new(2); d.type_char('"');
972 assert_eq!(d.text(), "\" \""); }
974
975 #[test]
976 fn autoclose_pairs_at_every_caret() {
977 let mut d = doc("a\nb\nc"); d.set_selections(crate::SelectionSet::from_offsets(&[1, 3, 5]));
980 d.type_char('{');
981 assert_eq!(d.text(), "a{}\nb{}\nc{}");
982 assert_eq!(ac_count(&d), 3, "one live provenance pair per caret");
983 d.type_char('x');
985 assert_eq!(d.text(), "a{x}\nb{x}\nc{x}");
986 }
987
988 #[test]
989 fn autoclose_overtypes_at_every_caret() {
990 let mut d = doc("a\nb\nc");
992 d.set_selections(crate::SelectionSet::from_offsets(&[1, 3, 5]));
993 d.type_char('{'); d.type_char('}'); assert_eq!(d.text(), "a{}\nb{}\nc{}", "overtype skips at every caret — no doubled closer");
996 assert_eq!(ac_count(&d), 0, "provenance consumed");
997 }
998
999 #[test]
1000 fn autoclose_surrounds_every_selection() {
1001 let mut d = doc("foo bar baz");
1003 d.set_selections(crate::SelectionSet::from_ranges(&[(0, 3), (4, 7), (8, 11)], 0));
1004 d.type_char('(');
1005 assert_eq!(d.text(), "(foo) (bar) (baz)");
1006 }
1007
1008 #[test]
1009 fn autoclose_pair_backspace_at_every_caret() {
1010 let mut d = doc("a\nb\nc");
1012 d.set_selections(crate::SelectionSet::from_offsets(&[1, 3, 5]));
1013 d.type_char('{'); d.backspace();
1015 assert_eq!(d.text(), "a\nb\nc", "each empty pair deleted whole");
1016 assert_eq!(ac_count(&d), 0);
1017 }
1018
1019 #[test]
1020 fn autoclose_nested_opener_supersedes_provenance() {
1021 let mut d = doc("");
1026 d.type_char('('); d.type_char('('); assert_eq!(ac_count(&d), 1, "only the innermost pair is live");
1029 d.type_char(')'); assert_eq!(d.text(), "(())");
1031 let mut e = doc("");
1033 e.type_char('{');
1034 e.type_char('(');
1035 e.type_char(')');
1036 assert_eq!(e.text(), "{()}");
1037 }
1038
1039 #[test]
1040 fn autoclose_nested_pair_backspace_deletes_the_inner() {
1041 let mut d = doc("");
1043 d.type_char('(');
1044 d.type_char('('); d.backspace();
1046 assert_eq!(d.text(), "()", "the inner empty pair is deleted whole");
1047 }
1048
1049 #[test]
1050 fn autoclose_surrounds_touching_selections() {
1051 let mut d = doc("abcd");
1055 d.set_selections(crate::SelectionSet::from_ranges(&[(0, 2), (2, 4)], 0));
1056 d.type_char('(');
1057 assert_eq!(d.text(), "(ab)(cd)");
1058 let sels = d.selections().all();
1059 assert_eq!((sels[0].start(), sels[0].end()), (1, 3), "interior 'ab'");
1060 assert_eq!((sels[1].start(), sels[1].end()), (5, 7), "interior 'cd', not '(cd'");
1061 }
1062
1063 #[test]
1064 fn autoclose_per_caret_pairs_only_where_the_guard_passes() {
1065 let mut d = doc("ab");
1068 d.set_selections(crate::SelectionSet::from_offsets(&[0, 2]));
1069 d.type_char('{');
1070 assert_eq!(d.text(), "{ab{}"); assert_eq!(ac_count(&d), 1, "only the guarded caret paired");
1072 }
1073
1074 #[test]
1075 fn backspace_deletes_char_then_selection() {
1076 let mut d = doc("abc");
1077 d.selections = SelectionSet::new(3);
1078 d.backspace();
1079 assert_eq!(d.text(), "ab");
1080 d.selections = SelectionSet::new(0);
1081 d.selections.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 0, 2));
1082 d.backspace();
1083 assert_eq!(d.text(), "");
1084 }
1085
1086 #[test]
1087 fn backspace_in_indentation_deletes_to_tab_stop() {
1088 let mut d = doc(" x"); d.selections = SelectionSet::new(8); d.backspace();
1091 assert_eq!(d.text(), " x"); }
1093
1094 #[test]
1095 fn enter_carries_indent_truncated_to_caret() {
1096 let mut d = doc(" hello");
1097 d.selections = SelectionSet::new(9); d.enter();
1099 assert_eq!(d.text(), " hello\n "); let mut d2 = doc(" hello");
1105 d2.selections = SelectionSet::new(2);
1106 d2.enter();
1107 assert_eq!(d2.text(), " \n hello");
1108 }
1109
1110 #[test]
1111 fn delete_line_removes_blocks_and_the_final_line_cleanly() {
1112 let mut d = doc("one\ntwo\nthree");
1113 d.set_selections(crate::SelectionSet::new(5)); d.delete_line();
1115 assert_eq!(d.text(), "one\nthree");
1116 d.set_selections(crate::SelectionSet::new(d.buffer().len()));
1118 d.delete_line();
1119 assert_eq!(d.text(), "one");
1120 let mut m = doc("a\nb\nc\nd");
1123 let mut set = crate::SelectionSet::new(0);
1124 set.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 0, 4)); m.set_selections(set);
1126 m.delete_line();
1127 assert_eq!(m.text(), "c\nd");
1128 let mut t = doc("xx\nyy");
1130 t.set_selections(crate::SelectionSet::from_offsets(&[0, 1]));
1131 t.delete_line();
1132 assert_eq!(t.text(), "yy");
1133 }
1134
1135 #[test]
1136 fn insert_line_below_and_above_carry_indent() {
1137 let mut d = doc(" a\n b");
1140 d.set_selections(crate::SelectionSet::new(4)); d.insert_line(true);
1142 assert_eq!(d.text(), " a\n \n b");
1143 assert_eq!(d.selections().newest().head(), 10, "caret at the new line's end");
1144 let mut u = doc(" a");
1146 u.set_selections(crate::SelectionSet::new(5));
1147 u.insert_line(false);
1148 assert_eq!(u.text(), " \n a");
1149 assert_eq!(u.selections().newest().head(), 4);
1150 let mut b = doc("m {");
1152 b.set_selections(crate::SelectionSet::new(0));
1153 b.insert_line(true);
1154 assert_eq!(b.text(), "m {\n ");
1155 assert_eq!(b.selections().newest().head(), 8);
1156 }
1157
1158 #[test]
1159 fn toggle_line_comment_comments_aligned_then_uncomments() {
1160 let mut d = doc(" a\n\n b");
1164 d.set_line_comment(Some("//"));
1165 d.select_all();
1166 d.toggle_line_comment();
1167 assert_eq!(d.text(), " // a\n\n // b");
1168 assert!(!d.selections().newest().is_empty(), "selection survives the toggle");
1169 d.toggle_line_comment();
1170 assert_eq!(d.text(), " a\n\n b");
1171 let mut c = doc("x\ny");
1173 c.set_line_comment(Some("//"));
1174 c.set_selections(crate::SelectionSet::new(0));
1175 c.toggle_line_comment();
1176 assert_eq!(c.text(), "// x\ny");
1177 let mut s = doc("//x");
1179 s.set_line_comment(Some("//"));
1180 s.toggle_line_comment();
1181 assert_eq!(s.text(), "x");
1182 let mut m = doc("// a\nb");
1185 m.set_line_comment(Some("//"));
1186 m.select_all();
1187 m.toggle_line_comment();
1188 assert_eq!(m.text(), "// // a\n// b");
1189 }
1190
1191 #[test]
1192 fn toggle_line_comment_without_a_prefix_is_a_no_op() {
1193 let mut d = doc("abc");
1195 d.toggle_line_comment();
1196 assert_eq!(d.text(), "abc");
1197 let mut b = doc("");
1199 b.set_line_comment(Some("//"));
1200 b.toggle_line_comment();
1201 assert_eq!(b.text(), "// ");
1202 assert_eq!(b.selections().newest().head(), 3, "caret rebased after the prefix");
1203 }
1204
1205 #[test]
1206 fn copy_and_cut_expand_an_empty_caret_to_its_line() {
1207 let mut d = doc("one\ntwo\nthree");
1209 d.set_selections(crate::SelectionSet::new(5)); assert_eq!(d.clipboard_payload(), ("two\n".to_string(), true));
1211 d.set_selections(crate::SelectionSet::new(9)); assert_eq!(d.clipboard_payload(), ("three\n".to_string(), true));
1214 let mut set = crate::SelectionSet::new(0);
1216 set.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 0, 3));
1217 d.set_selections(set);
1218 assert_eq!(d.clipboard_payload(), ("one".to_string(), false));
1219 d.set_selections(crate::SelectionSet::new(5));
1221 d.cut();
1222 assert_eq!(d.text(), "one\nthree");
1223 assert!(d.undo());
1224 assert_eq!(d.text(), "one\ntwo\nthree");
1225 }
1226
1227 #[test]
1228 fn mixed_set_cut_never_deletes_uncopied_lines() {
1229 let mut d = doc("foo bar\nbaz\n");
1232 let mut set = crate::SelectionSet::new(0);
1233 set.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 0, 3)); set.add_caret(9); d.set_selections(set);
1236 assert_eq!(d.clipboard_payload(), ("foo".to_string(), false));
1237 d.cut();
1238 assert_eq!(d.text(), " bar\nbaz\n", "the uncopied `baz` line survives");
1239 }
1240
1241 #[test]
1242 fn cut_on_the_final_line_takes_the_preceding_newline() {
1243 let mut d = doc("one\ntwo");
1246 d.set_selections(crate::SelectionSet::new(5)); assert_eq!(d.clipboard_payload(), ("two\n".to_string(), true));
1248 d.cut();
1249 assert_eq!(d.text(), "one", "no empty tail line remains");
1250 let mut e = doc("abc\n");
1253 e.set_selections(crate::SelectionSet::new(4));
1254 e.cut();
1255 assert_eq!(e.text(), "abc");
1256 }
1257
1258 #[test]
1259 fn two_carets_on_one_line_cut_it_once() {
1260 let mut d = doc("aaaa\nbb");
1261 d.set_selections(crate::SelectionSet::from_offsets(&[1, 3])); assert_eq!(d.clipboard_payload(), ("aaaa\n".to_string(), true), "the line exports once");
1263 d.cut();
1264 assert_eq!(d.text(), "bb", "identical ranges merged into one delete");
1265 }
1266
1267 #[test]
1268 fn entire_line_paste_inserts_above_and_keeps_the_caret() {
1269 let mut d = doc("aaa\nbbb");
1272 d.set_selections(crate::SelectionSet::new(5)); d.paste("two\n", true);
1274 assert_eq!(d.text(), "aaa\ntwo\nbbb");
1275 assert_eq!(d.selections().newest().head(), 9, "still col 1 of `bbb`");
1276 let mut set = crate::SelectionSet::new(0);
1278 set.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 0, 3));
1279 d.set_selections(set);
1280 d.paste("X\n", true);
1281 assert_eq!(d.text(), "X\n\ntwo\nbbb");
1282 }
1283
1284 #[test]
1285 fn enter_after_open_brace_indents_one_unit_deeper() {
1286 let mut d = doc(" if {");
1288 d.selections = SelectionSet::new(8);
1289 d.enter();
1290 assert_eq!(d.text(), " if {\n ");
1291 let mut t = doc("m { ");
1294 t.selections = SelectionSet::new(5);
1295 t.enter();
1296 assert_eq!(t.text(), "m { \n ");
1297 let mut p = doc("m { x");
1299 p.selections = SelectionSet::new(5);
1300 p.enter();
1301 assert_eq!(p.text(), "m { x\n");
1302 }
1303
1304 #[test]
1305 fn enter_between_braces_splits_onto_three_lines() {
1306 let mut d = doc("");
1309 d.type_char('{');
1310 assert_eq!(d.text(), "{}");
1311 d.enter();
1312 assert_eq!(d.text(), "{\n \n}");
1313 assert_eq!(d.selections().newest().head(), 6, "caret at the middle line's end");
1314 let mut n = doc(" {}");
1316 n.selections = SelectionSet::new(5);
1317 n.enter();
1318 assert_eq!(n.text(), " {\n \n }");
1319 assert_eq!(n.selections().newest().head(), 14);
1320 assert!(n.undo());
1322 assert_eq!(n.text(), " {}");
1323 }
1324
1325 #[test]
1326 fn enter_indent_unit_matches_tab_kind() {
1327 let mut d = doc("\tif {");
1330 d.selections = SelectionSet::new(5);
1331 d.enter();
1332 assert_eq!(d.text(), "\tif {\n\t\t");
1333 }
1334
1335 #[test]
1336 fn tab_inserts_to_next_stop() {
1337 let mut d = doc("ab");
1338 d.selections = SelectionSet::new(2); d.tab();
1340 assert_eq!(d.text(), "ab ");
1341 d.tab(); assert_eq!(d.text(), "ab ");
1343 }
1344
1345 #[test]
1346 fn tab_over_a_single_line_selection_types_over() {
1347 let mut d = doc("abcdef");
1348 d.selections.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 1, 4)); d.tab();
1350 assert_eq!(d.text(), "a ef");
1353 assert!(d.selections().newest().is_empty(), "types over → collapses to a caret");
1354 }
1355
1356 #[test]
1357 fn shift_tab_outdents_a_single_line_and_keeps_the_selection() {
1358 let mut d = doc(" indented");
1359 d.selections.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 5, 9)); d.outdent();
1361 assert_eq!(d.text(), "indented"); assert_eq!((d.selections().newest().start(), d.selections().newest().end()), (1, 5));
1364 }
1365
1366 #[test]
1367 fn block_indent_outdent_preserves_the_selection() {
1368 let mut d = doc("a\nb\nc");
1369 d.selections.set_single(crate::Selection::from_anchor(crate::SelectionId(0), 0, 5));
1371 d.tab();
1372 assert_eq!(d.text(), " a\n b\n c");
1373 assert_eq!(d.selections().len(), 1, "no extra cursors");
1376 let s = d.selections().newest();
1377 assert_eq!((s.start(), s.end()), (0, 17));
1378 assert!(!s.is_empty(), "still a selection, not a caret");
1379 d.outdent();
1381 assert_eq!(d.text(), "a\nb\nc");
1382 assert_eq!(d.selections().len(), 1);
1383 assert_eq!(
1384 (d.selections().newest().start(), d.selections().newest().end()),
1385 (0, 5)
1386 );
1387 }
1388
1389 #[test]
1390 fn multi_cursor_typing_edits_every_caret() {
1391 let mut d = doc("a b c");
1392 d.selections = SelectionSet::new(0);
1393 d.selections.add_caret(2);
1394 d.selections.add_caret(4);
1395 d.type_char('X');
1396 assert_eq!(d.text(), "Xa Xb Xc");
1397 assert_eq!(d.selections().len(), 3);
1398 }
1399
1400 #[test]
1401 fn newline_paste_is_normalized_and_splits_lines() {
1402 let mut d = doc("");
1403 d.insert_text("one\r\ntwo");
1404 assert_eq!(d.text(), "one\ntwo");
1405 assert_eq!(d.buffer().line_count(), 2);
1406 }
1407
1408 #[test]
1409 fn enter_undoes_and_redoes() {
1410 let mut d = doc("ab");
1411 d.selections = SelectionSet::new(1);
1412 d.enter();
1413 assert_eq!(d.text(), "a\nb");
1414 assert!(d.undo());
1415 assert_eq!(d.text(), "ab");
1416 assert!(d.redo());
1417 assert_eq!(d.text(), "a\nb");
1418 }
1419
1420 #[test]
1421 fn caret_tracks_text_after_undo() {
1422 let mut d = doc("");
1423 for ch in "hello".chars() {
1424 d.type_char(ch);
1425 }
1426 d.undo(); assert!(d.selections().all()[0].head() <= d.buffer().len(), "caret stays valid");
1428 }
1429
1430 #[test]
1431 fn undo_returns_the_caret_to_the_edit() {
1432 let mut d = doc("");
1435 for ch in "hello".chars() {
1436 d.type_char(ch);
1437 }
1438 assert_eq!(d.selections().all()[0].head(), 5, "caret after typing");
1439 d.undo();
1440 assert_eq!(d.buffer().text(), "", "text undone");
1441 assert_eq!(d.selections().all()[0].head(), 0, "caret returns to the edit site");
1442 d.redo();
1443 assert_eq!(d.buffer().text(), "hello", "text redone");
1444 assert_eq!(d.selections().all()[0].head(), 5, "caret follows the redo");
1445 }
1446
1447 #[test]
1448 fn undo_jumps_the_caret_to_the_edit_from_afar() {
1449 let mut d = doc("aaa\nbbb\nccc\nddd\n");
1453 d.set_selections(crate::SelectionSet::new(0)); d.type_char('\n'); d.set_selections(crate::SelectionSet::new(10)); assert_eq!(d.selections().all()[0].head(), 10, "caret parked in the body");
1457 assert!(d.undo());
1458 assert_eq!(d.buffer().text(), "aaa\nbbb\nccc\nddd\n", "the newline is gone");
1459 assert_eq!(
1460 d.selections().all()[0].head(),
1461 0,
1462 "undo jumps the caret to the reverted edit, not leaves it at 10"
1463 );
1464 }
1465}