on master help sign in
Enforce cursor/selection invariants in one place.

Rather than per-command like before.

commitcd7302ffd35c4972e7b0414a31a08f4e3c672df5 authorNathan Vegdahl <cessen@cessen.com> dateWed 28 Jul 2021, 15:57 (5 years ago) parenta873e71
3 files changed+77−115
helix-core/src/selection.rs+9−4 helix-term/src/commands.rs+60−109 helix-view/src/document.rs+8−2
helix-core/src/selection.rs+9−4
@@ -455,13 +455,18 @@ impl Selection {
455455 for range in self.ranges.iter_mut() {
456456 *range = f(*range)
457457 }
458−
459458 self.normalize()
460459 }
461460
462− /// A convenience short-cut for `transform(|r| r.min_width_1(text))`.
463− pub fn min_width_1(self, text: RopeSlice) -> Self {
464− self.transform(|r| r.min_width_1(text))
461+ // Ensures the selection adheres to the following invariants:
462+ // 1. All ranges are grapheme aligned.
463+ // 2. All ranges are at least 1 character wide, unless at the
464+ // very end of the document.
465+ // 3. Ranges are non-overlapping.
466+ // 4. Ranges are sorted by their position in the text.
467+ pub fn ensure_invariants(self, text: RopeSlice) -> Self {
468+ self.transform(|r| r.min_width_1(text).grapheme_aligned(text))
469+ .normalize()
465470 }
466471
467472 /// Transforms the selection into all of the left-side head positions,
helix-term/src/commands.rs+60−109
@@ -483,7 +483,6 @@ fn move_next_word_start(cx: &mut Context) {
483483 let selection = doc
484484 .selection(view.id)
485485 .clone()
486− .min_width_1(text)
487486 .transform(|range| movement::move_next_word_start(text, range, count));
488487 doc.set_selection(view.id, selection);
489488}
@@ -496,7 +495,6 @@ fn move_prev_word_start(cx: &mut Context) {
496495 let selection = doc
497496 .selection(view.id)
498497 .clone()
499− .min_width_1(text)
500498 .transform(|range| movement::move_prev_word_start(text, range, count));
501499 doc.set_selection(view.id, selection);
502500}
@@ -509,7 +507,6 @@ fn move_next_word_end(cx: &mut Context) {
509507 let selection = doc
510508 .selection(view.id)
511509 .clone()
512− .min_width_1(text)
513510 .transform(|range| movement::move_next_word_end(text, range, count));
514511 doc.set_selection(view.id, selection);
515512}
@@ -522,7 +519,6 @@ fn move_next_long_word_start(cx: &mut Context) {
522519 let selection = doc
523520 .selection(view.id)
524521 .clone()
525− .min_width_1(text)
526522 .transform(|range| movement::move_next_long_word_start(text, range, count));
527523 doc.set_selection(view.id, selection);
528524}
@@ -535,7 +531,6 @@ fn move_prev_long_word_start(cx: &mut Context) {
535531 let selection = doc
536532 .selection(view.id)
537533 .clone()
538− .min_width_1(text)
539534 .transform(|range| movement::move_prev_long_word_start(text, range, count));
540535 doc.set_selection(view.id, selection);
541536}
@@ -548,7 +543,6 @@ fn move_next_long_word_end(cx: &mut Context) {
548543 let selection = doc
549544 .selection(view.id)
550545 .clone()
551− .min_width_1(text)
552546 .transform(|range| movement::move_next_long_word_end(text, range, count));
553547 doc.set_selection(view.id, selection);
554548}
@@ -574,15 +568,11 @@ fn extend_next_word_start(cx: &mut Context) {
574568 let (view, doc) = current!(cx.editor);
575569 let text = doc.text().slice(..);
576570
577− let selection = doc
578− .selection(view.id)
579− .clone()
580− .min_width_1(text)
581− .transform(|range| {
582− let word = movement::move_next_word_start(text, range, count);
583− let pos = word.cursor(text);
584− range.put_cursor(text, pos, true)
585− });
571+ let selection = doc.selection(view.id).clone().transform(|range| {
572+ let word = movement::move_next_word_start(text, range, count);
573+ let pos = word.cursor(text);
574+ range.put_cursor(text, pos, true)
575+ });
586576 doc.set_selection(view.id, selection);
587577}
588578
@@ -591,15 +581,11 @@ fn extend_prev_word_start(cx: &mut Context) {
591581 let (view, doc) = current!(cx.editor);
592582 let text = doc.text().slice(..);
593583
594− let selection = doc
595− .selection(view.id)
596− .clone()
597− .min_width_1(text)
598− .transform(|range| {
599− let word = movement::move_prev_word_start(text, range, count);
600− let pos = word.cursor(text);
601− range.put_cursor(text, pos, true)
602− });
584+ let selection = doc.selection(view.id).clone().transform(|range| {
585+ let word = movement::move_prev_word_start(text, range, count);
586+ let pos = word.cursor(text);
587+ range.put_cursor(text, pos, true)
588+ });
603589 doc.set_selection(view.id, selection);
604590}
605591
@@ -608,15 +594,11 @@ fn extend_next_word_end(cx: &mut Context) {
608594 let (view, doc) = current!(cx.editor);
609595 let text = doc.text().slice(..);
610596
611− let selection = doc
612− .selection(view.id)
613− .clone()
614− .min_width_1(text)
615− .transform(|range| {
616− let word = movement::move_next_word_end(text, range, count);
617− let pos = word.cursor(text);
618− range.put_cursor(text, pos, true)
619− });
597+ let selection = doc.selection(view.id).clone().transform(|range| {
598+ let word = movement::move_next_word_end(text, range, count);
599+ let pos = word.cursor(text);
600+ range.put_cursor(text, pos, true)
601+ });
620602 doc.set_selection(view.id, selection);
621603}
622604
@@ -663,13 +645,16 @@ where
663645 let text = doc.text().slice(..);
664646
665647 let selection = doc.selection(view.id).clone().transform(|range| {
666− let range = if range.anchor < range.head {
667− // For block-cursor semantics.
668− Range::new(range.anchor, range.head - 1)
648+ // TODO: use `Range::cursor()` here instead. However, that works in terms of
649+ // graphemes, wheras this function does yet. So we're doing the same logic
650+ // here, but just in terms of chars instead.
651+ let search_start_pos = if range.anchor < range.head {
652+ range.head - 1
669653 } else {
670− range
654+ range.head
671655 };
672− search_fn(text, ch, range.head, count, inclusive)
656+
657+ search_fn(text, ch, search_start_pos, count, inclusive)
673658 .map_or(range, |pos| range.put_cursor(text, pos, extend))
674659 });
675660 doc.set_selection(view.id, selection);
@@ -795,11 +780,10 @@ fn replace(cx: &mut Context) {
795780 _ => None,
796781 };
797782
798− let text = doc.text().slice(..);
799− let selection = doc.selection(view.id).clone().min_width_1(text);
783+ let selection = doc.selection(view.id);
800784
801785 if let Some(ch) = ch {
802− let transaction = Transaction::change_by_selection(doc.text(), &selection, |range| {
786+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
803787 if !range.is_empty() {
804788 let text: String =
805789 RopeGraphemes::new(doc.text().slice(range.from()..range.to()))
@@ -828,11 +812,8 @@ fn replace(cx: &mut Context) {
828812
829813fn switch_case(cx: &mut Context) {
830814 let (view, doc) = current!(cx.editor);
831− let selection = doc
832− .selection(view.id)
833− .clone()
834− .min_width_1(doc.text().slice(..));
835− let transaction = Transaction::change_by_selection(doc.text(), &selection, |range| {
815+ let selection = doc.selection(view.id);
816+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
836817 let text: Tendril = range
837818 .fragment(doc.text().slice(..))
838819 .chars()
@@ -856,11 +837,8 @@ fn switch_case(cx: &mut Context) {
856837
857838fn switch_to_uppercase(cx: &mut Context) {
858839 let (view, doc) = current!(cx.editor);
859− let selection = doc
860− .selection(view.id)
861− .clone()
862− .min_width_1(doc.text().slice(..));
863− let transaction = Transaction::change_by_selection(doc.text(), &selection, |range| {
840+ let selection = doc.selection(view.id);
841+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
864842 let text: Tendril = range.fragment(doc.text().slice(..)).to_uppercase().into();
865843
866844 (range.from(), range.to(), Some(text))
@@ -872,11 +850,8 @@ fn switch_to_uppercase(cx: &mut Context) {
872850
873851fn switch_to_lowercase(cx: &mut Context) {
874852 let (view, doc) = current!(cx.editor);
875− let selection = doc
876− .selection(view.id)
877− .clone()
878− .min_width_1(doc.text().slice(..));
879− let transaction = Transaction::change_by_selection(doc.text(), &selection, |range| {
853+ let selection = doc.selection(view.id);
854+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
880855 let text: Tendril = range.fragment(doc.text().slice(..)).to_lowercase().into();
881856
882857 (range.from(), range.to(), Some(text))
@@ -1040,15 +1015,9 @@ fn split_selection_on_newline(cx: &mut Context) {
10401015fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Regex, extend: bool) {
10411016 let text = doc.text().slice(..);
10421017 let selection = doc.selection(view.id);
1043− let start = {
1044− // Get the right side of the block cursor.
1045− let range = selection.primary();
1046− if range.anchor < range.head {
1047− range.head
1048− } else {
1049− graphemes::next_grapheme_boundary(text, range.head)
1050− }
1051− };
1018+
1019+ // Get the right side of the primary block cursor.
1020+ let start = graphemes::next_grapheme_boundary(text, selection.primary().cursor(text));
10521021
10531022 // use find_at to find the next match after the cursor, loop around the end
10541023 // Careful, `Regex` uses `bytes` as offsets, not character indices!
@@ -1132,7 +1101,7 @@ fn extend_line(cx: &mut Context) {
11321101 let (view, doc) = current!(cx.editor);
11331102
11341103 let text = doc.text();
1135− let range = doc.selection(view.id).primary().min_width_1(text.slice(..));
1104+ let range = doc.selection(view.id).primary();
11361105
11371106 let (start_line, end_line) = range.line_range(text.slice(..));
11381107 let start = text.line_to_char(start_line);
@@ -1168,14 +1137,14 @@ fn extend_to_line_bounds(cx: &mut Context) {
11681137
11691138fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId) {
11701139 let text = doc.text().slice(..);
1171− let selection = doc.selection(view_id).clone().min_width_1(text);
1140+ let selection = doc.selection(view_id);
11721141
11731142 // first yank the selection
11741143 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
11751144 reg.write(values);
11761145
11771146 // then delete
1178− let transaction = Transaction::change_by_selection(doc.text(), &selection, |range| {
1147+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
11791148 (range.from(), range.to(), None)
11801149 });
11811150 doc.apply(&transaction, view_id);
@@ -1247,12 +1216,10 @@ fn append_mode(cx: &mut Context) {
12471216 doc.restore_cursor = true;
12481217 let text = doc.text().slice(..);
12491218
1250− let selection = doc.selection(view.id).clone().min_width_1(text);
1251−
12521219 // Make sure there's room at the end of the document if the last
12531220 // selection butts up against it.
12541221 let end = text.len_chars();
1255− let last_range = selection.iter().last().unwrap();
1222+ let last_range = doc.selection(view.id).iter().last().unwrap();
12561223 if !last_range.is_empty() && last_range.head == end {
12571224 let transaction = Transaction::change(
12581225 doc.text(),
@@ -1261,7 +1228,7 @@ fn append_mode(cx: &mut Context) {
12611228 doc.apply(&transaction, view.id);
12621229 }
12631230
1264− let selection = selection.transform(|range| {
1231+ let selection = doc.selection(view.id).clone().transform(|range| {
12651232 Range::new(
12661233 range.from(),
12671234 graphemes::next_grapheme_boundary(doc.text().slice(..), range.to()),
@@ -1689,10 +1656,7 @@ mod cmd {
16891656
16901657 match cx.editor.clipboard_provider.get_contents() {
16911658 Ok(contents) => {
1692− let selection = doc
1693− .selection(view.id)
1694− .clone()
1695− .min_width_1(doc.text().slice(..));
1659+ let selection = doc.selection(view.id);
16961660 let transaction =
16971661 Transaction::change_by_selection(doc.text(), &selection, |range| {
16981662 (range.from(), range.to(), Some(contents.as_str().into()))
@@ -2471,7 +2435,7 @@ fn select_mode(cx: &mut Context) {
24712435 let (view, doc) = current!(cx.editor);
24722436 let text = doc.text().slice(..);
24732437
2474− // Make sure all selections are at least 1-wide.
2438+ // Make sure end-of-document selections are also 1-width.
24752439 // (With the exception of being in an empty document, of course.)
24762440 let selection = doc.selection(view.id).clone().transform(|range| {
24772441 if range.is_empty() && range.head == text.len_chars() {
@@ -2480,7 +2444,7 @@ fn select_mode(cx: &mut Context) {
24802444 range.head,
24812445 )
24822446 } else {
2483− range.min_width_1(text)
2447+ range
24842448 }
24852449 });
24862450 doc.set_selection(view.id, selection);
@@ -3037,9 +3001,10 @@ pub mod insert {
30373001 let text = doc.text().slice(..);
30383002 let transaction =
30393003 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3004+ let pos = range.cursor(text);
30403005 (
3041− graphemes::nth_prev_grapheme_boundary(text, range.head, count),
3042− range.head,
3006+ graphemes::nth_prev_grapheme_boundary(text, pos, count),
3007+ pos,
30433008 None,
30443009 )
30453010 });
@@ -3052,9 +3017,10 @@ pub mod insert {
30523017 let text = doc.text().slice(..);
30533018 let transaction =
30543019 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3020+ let pos = range.cursor(text);
30553021 (
3056− range.head,
3057− graphemes::nth_next_grapheme_boundary(text, range.head, count),
3022+ pos,
3023+ graphemes::nth_next_grapheme_boundary(text, pos, count),
30583024 None,
30593025 )
30603026 });
@@ -3100,8 +3066,6 @@ fn yank(cx: &mut Context) {
31003066
31013067 let values: Vec<String> = doc
31023068 .selection(view.id)
3103− .clone()
3104− .min_width_1(text)
31053069 .fragments(text)
31063070 .map(Cow::into_owned)
31073071 .collect();
@@ -3125,8 +3089,6 @@ fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) -> anyhow
31253089
31263090 let values: Vec<String> = doc
31273091 .selection(view.id)
3128− .clone()
3129− .min_width_1(text)
31303092 .fragments(text)
31313093 .map(Cow::into_owned)
31323094 .collect();
@@ -3157,11 +3119,7 @@ fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) -> anyhow::Result<
31573119 let (view, doc) = current!(editor);
31583120 let text = doc.text().slice(..);
31593121
3160− let value = doc
3161− .selection(view.id)
3162− .primary()
3163− .min_width_1(text)
3164− .fragment(text);
3122+ let value = doc.selection(view.id).primary().fragment(text);
31653123
31663124 if let Err(e) = editor.clipboard_provider.set_contents(value.into_owned()) {
31673125 bail!("Couldn't set system clipboard content: {:?}", e);
@@ -3202,9 +3160,9 @@ fn paste_impl(
32023160 let mut values = values.iter().cloned().map(Tendril::from).chain(repeat);
32033161
32043162 let text = doc.text();
3205− let selection = doc.selection(view.id).clone().min_width_1(text.slice(..));
3163+ let selection = doc.selection(view.id);
32063164
3207− let transaction = Transaction::change_by_selection(text, &selection, |range| {
3165+ let transaction = Transaction::change_by_selection(text, selection, |range| {
32083166 let pos = match (action, linewise) {
32093167 // paste linewise before
32103168 (Paste::Before, true) => text.line_to_char(text.char_to_line(range.from())),
@@ -3257,11 +3215,8 @@ fn replace_with_yanked(cx: &mut Context) {
32573215
32583216 if let Some(values) = registers.read(reg_name) {
32593217 if let Some(yank) = values.first() {
3260− let selection = doc
3261− .selection(view.id)
3262− .clone()
3263− .min_width_1(doc.text().slice(..));
3264− let transaction = Transaction::change_by_selection(doc.text(), &selection, |range| {
3218+ let selection = doc.selection(view.id);
3219+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
32653220 if !range.is_empty() {
32663221 (range.from(), range.to(), Some(yank.as_str().into()))
32673222 } else {
@@ -3280,11 +3235,8 @@ fn replace_selections_with_clipboard_impl(editor: &mut Editor) -> anyhow::Result
32803235
32813236 match editor.clipboard_provider.get_contents() {
32823237 Ok(contents) => {
3283− let selection = doc
3284− .selection(view.id)
3285− .clone()
3286− .min_width_1(doc.text().slice(..));
3287− let transaction = Transaction::change_by_selection(doc.text(), &selection, |range| {
3238+ let selection = doc.selection(view.id);
3239+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
32883240 (range.from(), range.to(), Some(contents.as_str().into()))
32893241 });
32903242
@@ -3857,8 +3809,7 @@ fn surround_add(cx: &mut Context) {
38573809 } = event
38583810 {
38593811 let (view, doc) = current!(cx.editor);
3860− let text = doc.text().slice(..);
3861− let selection = doc.selection(view.id).clone().min_width_1(text);
3812+ let selection = doc.selection(view.id);
38623813 let (open, close) = surround::get_pair(ch);
38633814
38643815 let mut changes = Vec::new();
@@ -3890,9 +3841,9 @@ fn surround_replace(cx: &mut Context) {
38903841 {
38913842 let (view, doc) = current!(cx.editor);
38923843 let text = doc.text().slice(..);
3893− let selection = doc.selection(view.id).clone().min_width_1(text);
3844+ let selection = doc.selection(view.id);
38943845
3895− let change_pos = match surround::get_surround_pos(text, &selection, from, count)
3846+ let change_pos = match surround::get_surround_pos(text, selection, from, count)
38963847 {
38973848 Some(c) => c,
38983849 None => return,
@@ -3927,9 +3878,9 @@ fn surround_delete(cx: &mut Context) {
39273878 {
39283879 let (view, doc) = current!(cx.editor);
39293880 let text = doc.text().slice(..);
3930− let selection = doc.selection(view.id).clone().min_width_1(text);
3881+ let selection = doc.selection(view.id);
39313882
3932− let change_pos = match surround::get_surround_pos(text, &selection, ch, count) {
3883+ let change_pos = match surround::get_surround_pos(text, selection, ch, count) {
39333884 Some(c) => c,
39343885 None => return,
39353886 };
helix-view/src/document.rs+8−2
@@ -792,7 +792,8 @@ impl Document {
792792
793793 pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
794794 // TODO: use a transaction?
795− self.selections.insert(view_id, selection);
795+ self.selections
796+ .insert(view_id, selection.ensure_invariants(self.text().slice(..)));
796797 }
797798
798799 fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
@@ -807,7 +808,12 @@ impl Document {
807808 .selection()
808809 .cloned()
809810 .unwrap_or_else(|| self.selection(view_id).clone().map(transaction.changes()));
810− self.set_selection(view_id, selection);
811+ self.selections.insert(view_id, selection);
812+
813+ // Ensure all selections accross all views still adhere to invariants.
814+ for selection in self.selections.values_mut() {
815+ *selection = selection.clone().ensure_invariants(self.text.slice(..));
816+ }
811817 }
812818
813819 if !transaction.changes().is_empty() {