on master help sign in
Enforce cursor/selection invariants in one place.
cd7302fNathan Vegdahl
helix-core/src/selection.rs+9−4
@@ -455,13 +455,18 @@ impl Selection {
455 for range in self.ranges.iter_mut() {
455 for range in self.ranges.iter_mut() {
456 *range = f(*range)
456 *range = f(*range)
457 }
457 }
458−
459 self.normalize()
458 self.normalize()
460 }
459 }
461
460
462− /// A convenience short-cut for `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.
463− pub fn min_width_1(self, text: RopeSlice) -> Self {
467+ pub fn ensure_invariants(self, text: RopeSlice) -> Self {
464− self.transform(|r| r.min_width_1(text))
468+ self.transform(|r| r.min_width_1(text).grapheme_aligned(text))
469+ .normalize()
465 }
470 }
466
471
467 /// Transforms the selection into all of the left-side head positions,
472 /// 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) {
483 let selection = doc
483 let selection = doc
484 .selection(view.id)
484 .selection(view.id)
485 .clone()
485 .clone()
486− .min_width_1(text)
487 .transform(|range| movement::move_next_word_start(text, range, count));
486 .transform(|range| movement::move_next_word_start(text, range, count));
488 doc.set_selection(view.id, selection);
487 doc.set_selection(view.id, selection);
489}
488}
@@ -496,7 +495,6 @@ fn move_prev_word_start(cx: &mut Context) {
496 let selection = doc
495 let selection = doc
497 .selection(view.id)
496 .selection(view.id)
498 .clone()
497 .clone()
499− .min_width_1(text)
500 .transform(|range| movement::move_prev_word_start(text, range, count));
498 .transform(|range| movement::move_prev_word_start(text, range, count));
501 doc.set_selection(view.id, selection);
499 doc.set_selection(view.id, selection);
502}
500}
@@ -509,7 +507,6 @@ fn move_next_word_end(cx: &mut Context) {
509 let selection = doc
507 let selection = doc
510 .selection(view.id)
508 .selection(view.id)
511 .clone()
509 .clone()
512− .min_width_1(text)
513 .transform(|range| movement::move_next_word_end(text, range, count));
510 .transform(|range| movement::move_next_word_end(text, range, count));
514 doc.set_selection(view.id, selection);
511 doc.set_selection(view.id, selection);
515}
512}
@@ -522,7 +519,6 @@ fn move_next_long_word_start(cx: &mut Context) {
522 let selection = doc
519 let selection = doc
523 .selection(view.id)
520 .selection(view.id)
524 .clone()
521 .clone()
525− .min_width_1(text)
526 .transform(|range| movement::move_next_long_word_start(text, range, count));
522 .transform(|range| movement::move_next_long_word_start(text, range, count));
527 doc.set_selection(view.id, selection);
523 doc.set_selection(view.id, selection);
528}
524}
@@ -535,7 +531,6 @@ fn move_prev_long_word_start(cx: &mut Context) {
535 let selection = doc
531 let selection = doc
536 .selection(view.id)
532 .selection(view.id)
537 .clone()
533 .clone()
538− .min_width_1(text)
539 .transform(|range| movement::move_prev_long_word_start(text, range, count));
534 .transform(|range| movement::move_prev_long_word_start(text, range, count));
540 doc.set_selection(view.id, selection);
535 doc.set_selection(view.id, selection);
541}
536}
@@ -548,7 +543,6 @@ fn move_next_long_word_end(cx: &mut Context) {
548 let selection = doc
543 let selection = doc
549 .selection(view.id)
544 .selection(view.id)
550 .clone()
545 .clone()
551− .min_width_1(text)
552 .transform(|range| movement::move_next_long_word_end(text, range, count));
546 .transform(|range| movement::move_next_long_word_end(text, range, count));
553 doc.set_selection(view.id, selection);
547 doc.set_selection(view.id, selection);
554}
548}
@@ -574,15 +568,11 @@ fn extend_next_word_start(cx: &mut Context) {
574 let (view, doc) = current!(cx.editor);
568 let (view, doc) = current!(cx.editor);
575 let text = doc.text().slice(..);
569 let text = doc.text().slice(..);
576
570
577− let selection = doc
571+ let selection = doc.selection(view.id).clone().transform(|range| {
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);
572+ let word = movement::move_next_word_start(text, range, count);
583− let pos = word.cursor(text);
573+ let pos = word.cursor(text);
584− range.put_cursor(text, pos, true)
574+ range.put_cursor(text, pos, true)
585− });
575+ });
586 doc.set_selection(view.id, selection);
576 doc.set_selection(view.id, selection);
587}
577}
588
578
@@ -591,15 +581,11 @@ fn extend_prev_word_start(cx: &mut Context) {
591 let (view, doc) = current!(cx.editor);
581 let (view, doc) = current!(cx.editor);
592 let text = doc.text().slice(..);
582 let text = doc.text().slice(..);
593
583
594− let selection = doc
584+ let selection = doc.selection(view.id).clone().transform(|range| {
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);
585+ let word = movement::move_prev_word_start(text, range, count);
600− let pos = word.cursor(text);
586+ let pos = word.cursor(text);
601− range.put_cursor(text, pos, true)
587+ range.put_cursor(text, pos, true)
602− });
588+ });
603 doc.set_selection(view.id, selection);
589 doc.set_selection(view.id, selection);
604}
590}
605
591
@@ -608,15 +594,11 @@ fn extend_next_word_end(cx: &mut Context) {
608 let (view, doc) = current!(cx.editor);
594 let (view, doc) = current!(cx.editor);
609 let text = doc.text().slice(..);
595 let text = doc.text().slice(..);
610
596
611− let selection = doc
597+ let selection = doc.selection(view.id).clone().transform(|range| {
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);
598+ let word = movement::move_next_word_end(text, range, count);
617− let pos = word.cursor(text);
599+ let pos = word.cursor(text);
618− range.put_cursor(text, pos, true)
600+ range.put_cursor(text, pos, true)
619− });
601+ });
620 doc.set_selection(view.id, selection);
602 doc.set_selection(view.id, selection);
621}
603}
622
604
@@ -663,13 +645,16 @@ where
663 let text = doc.text().slice(..);
645 let text = doc.text().slice(..);
664
646
665 let selection = doc.selection(view.id).clone().transform(|range| {
647 let selection = doc.selection(view.id).clone().transform(|range| {
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.
666− let range = if range.anchor < range.head {
651+ let search_start_pos = if range.anchor < range.head {
667− // For block-cursor semantics.
668− Range::new(range.anchor, range.head - 1)
652+ range.head - 1
669 } else {
653 } else {
670− range
654+ range.head
671 };
655 };
656+
672− search_fn(text, ch, range.head, count, inclusive)
657+ search_fn(text, ch, search_start_pos, count, inclusive)
673 .map_or(range, |pos| range.put_cursor(text, pos, extend))
658 .map_or(range, |pos| range.put_cursor(text, pos, extend))
674 });
659 });
675 doc.set_selection(view.id, selection);
660 doc.set_selection(view.id, selection);
@@ -795,11 +780,10 @@ fn replace(cx: &mut Context) {
795 _ => None,
780 _ => None,
796 };
781 };
797
782
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);
800
784
801 if let Some(ch) = ch {
785 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| {
803 if !range.is_empty() {
787 if !range.is_empty() {
804 let text: String =
788 let text: String =
805 RopeGraphemes::new(doc.text().slice(range.from()..range.to()))
789 RopeGraphemes::new(doc.text().slice(range.from()..range.to()))
@@ -828,11 +812,8 @@ fn replace(cx: &mut Context) {
828
812
829fn switch_case(cx: &mut Context) {
813fn switch_case(cx: &mut Context) {
830 let (view, doc) = current!(cx.editor);
814 let (view, doc) = current!(cx.editor);
831− let selection = doc
815+ let selection = doc.selection(view.id);
832− .selection(view.id)
833− .clone()
834− .min_width_1(doc.text().slice(..));
835− let transaction = Transaction::change_by_selection(doc.text(), &selection, |range| {
816+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
836 let text: Tendril = range
817 let text: Tendril = range
837 .fragment(doc.text().slice(..))
818 .fragment(doc.text().slice(..))
838 .chars()
819 .chars()
@@ -856,11 +837,8 @@ fn switch_case(cx: &mut Context) {
856
837
857fn switch_to_uppercase(cx: &mut Context) {
838fn switch_to_uppercase(cx: &mut Context) {
858 let (view, doc) = current!(cx.editor);
839 let (view, doc) = current!(cx.editor);
859− let selection = doc
840+ let selection = doc.selection(view.id);
860− .selection(view.id)
861− .clone()
862− .min_width_1(doc.text().slice(..));
863− let transaction = Transaction::change_by_selection(doc.text(), &selection, |range| {
841+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
864 let text: Tendril = range.fragment(doc.text().slice(..)).to_uppercase().into();
842 let text: Tendril = range.fragment(doc.text().slice(..)).to_uppercase().into();
865
843
866 (range.from(), range.to(), Some(text))
844 (range.from(), range.to(), Some(text))
@@ -872,11 +850,8 @@ fn switch_to_uppercase(cx: &mut Context) {
872
850
873fn switch_to_lowercase(cx: &mut Context) {
851fn switch_to_lowercase(cx: &mut Context) {
874 let (view, doc) = current!(cx.editor);
852 let (view, doc) = current!(cx.editor);
875− let selection = doc
853+ let selection = doc.selection(view.id);
876− .selection(view.id)
877− .clone()
878− .min_width_1(doc.text().slice(..));
879− let transaction = Transaction::change_by_selection(doc.text(), &selection, |range| {
854+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
880 let text: Tendril = range.fragment(doc.text().slice(..)).to_lowercase().into();
855 let text: Tendril = range.fragment(doc.text().slice(..)).to_lowercase().into();
881
856
882 (range.from(), range.to(), Some(text))
857 (range.from(), range.to(), Some(text))
@@ -1040,15 +1015,9 @@ fn split_selection_on_newline(cx: &mut Context) {
1040fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Regex, extend: bool) {
1015fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Regex, extend: bool) {
1041 let text = doc.text().slice(..);
1016 let text = doc.text().slice(..);
1042 let selection = doc.selection(view.id);
1017 let selection = doc.selection(view.id);
1043− let start = {
1018+
1044− // Get the right side of the block cursor.
1019+ // Get the right side of the primary 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)
1020+ let start = graphemes::next_grapheme_boundary(text, selection.primary().cursor(text));
1050− }
1051− };
1052
1021
1053 // use find_at to find the next match after the cursor, loop around the end
1022 // use find_at to find the next match after the cursor, loop around the end
1054 // Careful, `Regex` uses `bytes` as offsets, not character indices!
1023 // Careful, `Regex` uses `bytes` as offsets, not character indices!
@@ -1132,7 +1101,7 @@ fn extend_line(cx: &mut Context) {
1132 let (view, doc) = current!(cx.editor);
1101 let (view, doc) = current!(cx.editor);
1133
1102
1134 let text = doc.text();
1103 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();
1136
1105
1137 let (start_line, end_line) = range.line_range(text.slice(..));
1106 let (start_line, end_line) = range.line_range(text.slice(..));
1138 let start = text.line_to_char(start_line);
1107 let start = text.line_to_char(start_line);
@@ -1168,14 +1137,14 @@ fn extend_to_line_bounds(cx: &mut Context) {
1168
1137
1169fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId) {
1138fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId) {
1170 let text = doc.text().slice(..);
1139 let text = doc.text().slice(..);
1171− let selection = doc.selection(view_id).clone().min_width_1(text);
1140+ let selection = doc.selection(view_id);
1172
1141
1173 // first yank the selection
1142 // first yank the selection
1174 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
1143 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
1175 reg.write(values);
1144 reg.write(values);
1176
1145
1177 // then delete
1146 // then delete
1178− let transaction = Transaction::change_by_selection(doc.text(), &selection, |range| {
1147+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
1179 (range.from(), range.to(), None)
1148 (range.from(), range.to(), None)
1180 });
1149 });
1181 doc.apply(&transaction, view_id);
1150 doc.apply(&transaction, view_id);
@@ -1247,12 +1216,10 @@ fn append_mode(cx: &mut Context) {
1247 doc.restore_cursor = true;
1216 doc.restore_cursor = true;
1248 let text = doc.text().slice(..);
1217 let text = doc.text().slice(..);
1249
1218
1250− let selection = doc.selection(view.id).clone().min_width_1(text);
1251−
1252 // Make sure there's room at the end of the document if the last
1219 // Make sure there's room at the end of the document if the last
1253 // selection butts up against it.
1220 // selection butts up against it.
1254 let end = text.len_chars();
1221 let end = text.len_chars();
1255− let last_range = selection.iter().last().unwrap();
1222+ let last_range = doc.selection(view.id).iter().last().unwrap();
1256 if !last_range.is_empty() && last_range.head == end {
1223 if !last_range.is_empty() && last_range.head == end {
1257 let transaction = Transaction::change(
1224 let transaction = Transaction::change(
1258 doc.text(),
1225 doc.text(),
@@ -1261,7 +1228,7 @@ fn append_mode(cx: &mut Context) {
1261 doc.apply(&transaction, view.id);
1228 doc.apply(&transaction, view.id);
1262 }
1229 }
1263
1230
1264− let selection = selection.transform(|range| {
1231+ let selection = doc.selection(view.id).clone().transform(|range| {
1265 Range::new(
1232 Range::new(
1266 range.from(),
1233 range.from(),
1267 graphemes::next_grapheme_boundary(doc.text().slice(..), range.to()),
1234 graphemes::next_grapheme_boundary(doc.text().slice(..), range.to()),
@@ -1689,10 +1656,7 @@ mod cmd {
1689
1656
1690 match cx.editor.clipboard_provider.get_contents() {
1657 match cx.editor.clipboard_provider.get_contents() {
1691 Ok(contents) => {
1658 Ok(contents) => {
1692− let selection = doc
1659+ let selection = doc.selection(view.id);
1693− .selection(view.id)
1694− .clone()
1695− .min_width_1(doc.text().slice(..));
1696 let transaction =
1660 let transaction =
1697 Transaction::change_by_selection(doc.text(), &selection, |range| {
1661 Transaction::change_by_selection(doc.text(), &selection, |range| {
1698 (range.from(), range.to(), Some(contents.as_str().into()))
1662 (range.from(), range.to(), Some(contents.as_str().into()))
@@ -2471,7 +2435,7 @@ fn select_mode(cx: &mut Context) {
2471 let (view, doc) = current!(cx.editor);
2435 let (view, doc) = current!(cx.editor);
2472 let text = doc.text().slice(..);
2436 let text = doc.text().slice(..);
2473
2437
2474− // Make sure all selections are at least 1-wide.
2438+ // Make sure end-of-document selections are also 1-width.
2475 // (With the exception of being in an empty document, of course.)
2439 // (With the exception of being in an empty document, of course.)
2476 let selection = doc.selection(view.id).clone().transform(|range| {
2440 let selection = doc.selection(view.id).clone().transform(|range| {
2477 if range.is_empty() && range.head == text.len_chars() {
2441 if range.is_empty() && range.head == text.len_chars() {
@@ -2480,7 +2444,7 @@ fn select_mode(cx: &mut Context) {
2480 range.head,
2444 range.head,
2481 )
2445 )
2482 } else {
2446 } else {
2483− range.min_width_1(text)
2447+ range
2484 }
2448 }
2485 });
2449 });
2486 doc.set_selection(view.id, selection);
2450 doc.set_selection(view.id, selection);
@@ -3037,9 +3001,10 @@ pub mod insert {
3037 let text = doc.text().slice(..);
3001 let text = doc.text().slice(..);
3038 let transaction =
3002 let transaction =
3039 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3003 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3004+ let pos = range.cursor(text);
3040 (
3005 (
3041− graphemes::nth_prev_grapheme_boundary(text, range.head, count),
3006+ graphemes::nth_prev_grapheme_boundary(text, pos, count),
3042− range.head,
3007+ pos,
3043 None,
3008 None,
3044 )
3009 )
3045 });
3010 });
@@ -3052,9 +3017,10 @@ pub mod insert {
3052 let text = doc.text().slice(..);
3017 let text = doc.text().slice(..);
3053 let transaction =
3018 let transaction =
3054 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3019 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3020+ let pos = range.cursor(text);
3055 (
3021 (
3056− range.head,
3022+ pos,
3057− graphemes::nth_next_grapheme_boundary(text, range.head, count),
3023+ graphemes::nth_next_grapheme_boundary(text, pos, count),
3058 None,
3024 None,
3059 )
3025 )
3060 });
3026 });
@@ -3100,8 +3066,6 @@ fn yank(cx: &mut Context) {
3100
3066
3101 let values: Vec<String> = doc
3067 let values: Vec<String> = doc
3102 .selection(view.id)
3068 .selection(view.id)
3103− .clone()
3104− .min_width_1(text)
3105 .fragments(text)
3069 .fragments(text)
3106 .map(Cow::into_owned)
3070 .map(Cow::into_owned)
3107 .collect();
3071 .collect();
@@ -3125,8 +3089,6 @@ fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) -> anyhow
3125
3089
3126 let values: Vec<String> = doc
3090 let values: Vec<String> = doc
3127 .selection(view.id)
3091 .selection(view.id)
3128− .clone()
3129− .min_width_1(text)
3130 .fragments(text)
3092 .fragments(text)
3131 .map(Cow::into_owned)
3093 .map(Cow::into_owned)
3132 .collect();
3094 .collect();
@@ -3157,11 +3119,7 @@ fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) -> anyhow::Result<
3157 let (view, doc) = current!(editor);
3119 let (view, doc) = current!(editor);
3158 let text = doc.text().slice(..);
3120 let text = doc.text().slice(..);
3159
3121
3160− let value = doc
3161− .selection(view.id)
3122+ let value = doc.selection(view.id).primary().fragment(text);
3162− .primary()
3163− .min_width_1(text)
3164− .fragment(text);
3165
3123
3166 if let Err(e) = editor.clipboard_provider.set_contents(value.into_owned()) {
3124 if let Err(e) = editor.clipboard_provider.set_contents(value.into_owned()) {
3167 bail!("Couldn't set system clipboard content: {:?}", e);
3125 bail!("Couldn't set system clipboard content: {:?}", e);
@@ -3202,9 +3160,9 @@ fn paste_impl(
3202 let mut values = values.iter().cloned().map(Tendril::from).chain(repeat);
3160 let mut values = values.iter().cloned().map(Tendril::from).chain(repeat);
3203
3161
3204 let text = doc.text();
3162 let text = doc.text();
3205− let selection = doc.selection(view.id).clone().min_width_1(text.slice(..));
3163+ let selection = doc.selection(view.id);
3206
3164
3207− let transaction = Transaction::change_by_selection(text, &selection, |range| {
3165+ let transaction = Transaction::change_by_selection(text, selection, |range| {
3208 let pos = match (action, linewise) {
3166 let pos = match (action, linewise) {
3209 // paste linewise before
3167 // paste linewise before
3210 (Paste::Before, true) => text.line_to_char(text.char_to_line(range.from())),
3168 (Paste::Before, true) => text.line_to_char(text.char_to_line(range.from())),
@@ -3257,11 +3215,8 @@ fn replace_with_yanked(cx: &mut Context) {
3257
3215
3258 if let Some(values) = registers.read(reg_name) {
3216 if let Some(values) = registers.read(reg_name) {
3259 if let Some(yank) = values.first() {
3217 if let Some(yank) = values.first() {
3260− let selection = doc
3218+ let selection = doc.selection(view.id);
3261− .selection(view.id)
3262− .clone()
3263− .min_width_1(doc.text().slice(..));
3264− let transaction = Transaction::change_by_selection(doc.text(), &selection, |range| {
3219+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
3265 if !range.is_empty() {
3220 if !range.is_empty() {
3266 (range.from(), range.to(), Some(yank.as_str().into()))
3221 (range.from(), range.to(), Some(yank.as_str().into()))
3267 } else {
3222 } else {
@@ -3280,11 +3235,8 @@ fn replace_selections_with_clipboard_impl(editor: &mut Editor) -> anyhow::Result
3280
3235
3281 match editor.clipboard_provider.get_contents() {
3236 match editor.clipboard_provider.get_contents() {
3282 Ok(contents) => {
3237 Ok(contents) => {
3283− let selection = doc
3238+ let selection = doc.selection(view.id);
3284− .selection(view.id)
3285− .clone()
3286− .min_width_1(doc.text().slice(..));
3287− let transaction = Transaction::change_by_selection(doc.text(), &selection, |range| {
3239+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
3288 (range.from(), range.to(), Some(contents.as_str().into()))
3240 (range.from(), range.to(), Some(contents.as_str().into()))
3289 });
3241 });
3290
3242
@@ -3857,8 +3809,7 @@ fn surround_add(cx: &mut Context) {
3857 } = event
3809 } = event
3858 {
3810 {
3859 let (view, doc) = current!(cx.editor);
3811 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);
3862 let (open, close) = surround::get_pair(ch);
3813 let (open, close) = surround::get_pair(ch);
3863
3814
3864 let mut changes = Vec::new();
3815 let mut changes = Vec::new();
@@ -3890,9 +3841,9 @@ fn surround_replace(cx: &mut Context) {
3890 {
3841 {
3891 let (view, doc) = current!(cx.editor);
3842 let (view, doc) = current!(cx.editor);
3892 let text = doc.text().slice(..);
3843 let text = doc.text().slice(..);
3893− let selection = doc.selection(view.id).clone().min_width_1(text);
3844+ let selection = doc.selection(view.id);
3894
3845
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)
3896 {
3847 {
3897 Some(c) => c,
3848 Some(c) => c,
3898 None => return,
3849 None => return,
@@ -3927,9 +3878,9 @@ fn surround_delete(cx: &mut Context) {
3927 {
3878 {
3928 let (view, doc) = current!(cx.editor);
3879 let (view, doc) = current!(cx.editor);
3929 let text = doc.text().slice(..);
3880 let text = doc.text().slice(..);
3930− let selection = doc.selection(view.id).clone().min_width_1(text);
3881+ let selection = doc.selection(view.id);
3931
3882
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) {
3933 Some(c) => c,
3884 Some(c) => c,
3934 None => return,
3885 None => return,
3935 };
3886 };
helix-view/src/document.rs+8−2
@@ -792,7 +792,8 @@ impl Document {
792
792
793 pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
793 pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
794 // TODO: use a transaction?
794 // TODO: use a transaction?
795− self.selections.insert(view_id, selection);
795+ self.selections
796+ .insert(view_id, selection.ensure_invariants(self.text().slice(..)));
796 }
797 }
797
798
798 fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
799 fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
@@ -807,7 +808,12 @@ impl Document {
807 .selection()
808 .selection()
808 .cloned()
809 .cloned()
809 .unwrap_or_else(|| self.selection(view_id).clone().map(transaction.changes()));
810 .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+ }
811 }
817 }
812
818
813 if !transaction.changes().is_empty() {
819 if !transaction.changes().is_empty() {