on master help sign in
Switch to a cleaner range-head moving abstraction.
f96b8b7Nathan Vegdahl
helix-core/src/movement.rs+17−18
@@ -42,20 +42,18 @@ pub fn move_horizontally(
42 };
42 };
43
43
44 // Compute the new position.
44 // Compute the new position.
45− let mut new_pos = if dir == Direction::Backward {
45+ let new_pos = if dir == Direction::Backward {
46 nth_prev_grapheme_boundary(slice, pos, count)
46 nth_prev_grapheme_boundary(slice, pos, count)
47 } else {
47 } else {
48 nth_next_grapheme_boundary(slice, pos, count)
48 nth_next_grapheme_boundary(slice, pos, count)
49 };
49 };
50
50
51− // Shift forward one grapheme if needed, for the
52− // visual 1-width cursor.
53− if behaviour == Extend && new_pos >= range.anchor {
54− new_pos = next_grapheme_boundary(slice, new_pos);
55− };
56−
57 // Compute the final new range.
51 // Compute the final new range.
52+ if behaviour == Extend {
58− range.put(slice, new_pos, behaviour == Extend)
53+ range.move_head(slice, new_pos, true)
54+ } else {
55+ Range::point(new_pos)
56+ }
59}
57}
60
58
61pub fn move_vertically(
59pub fn move_vertically(
@@ -78,14 +76,17 @@ pub fn move_vertically(
78 let horiz = range.horiz.unwrap_or(col as u32);
76 let horiz = range.horiz.unwrap_or(col as u32);
79
77
80 // Compute the new position.
78 // Compute the new position.
81− let new_pos = {
79+ let (new_pos, new_row) = {
82 let new_row = if dir == Direction::Backward {
80 let new_row = if dir == Direction::Backward {
83 row.saturating_sub(count)
81 row.saturating_sub(count)
84 } else {
82 } else {
85 (row + count).min(slice.len_lines().saturating_sub(1))
83 (row + count).min(slice.len_lines().saturating_sub(1))
86 };
84 };
87 let new_col = col.max(horiz as usize);
85 let new_col = col.max(horiz as usize);
86+ (
88− pos_at_coords(slice, Position::new(new_row, new_col), true)
87+ pos_at_coords(slice, Position::new(new_row, new_col), true),
88+ new_row,
89+ )
89 };
90 };
90
91
91 // Compute the new range according to the type of movement.
92 // Compute the new range according to the type of movement.
@@ -97,15 +98,13 @@ pub fn move_vertically(
97 },
98 },
98
99
99 Movement::Extend => {
100 Movement::Extend => {
100− let new_head = if new_pos >= range.anchor {
101+ if slice.line(new_row).len_chars() > 0 {
101− next_grapheme_boundary(slice, new_pos)
102+ let mut new_range = range.move_head(slice, new_pos, true);
103+ new_range.horiz = Some(horiz);
104+ new_range
102 } else {
105 } else {
103− new_pos
106+ range
104− };
107+ }
105−
106− let mut new_range = range.put(slice, new_head, true);
107− new_range.horiz = Some(horiz);
108− new_range
109 }
108 }
110 }
109 }
111}
110}
helix-core/src/search.rs+3−23
@@ -1,12 +1,6 @@
1use crate::RopeSlice;
1use crate::RopeSlice;
2
2
3−pub fn find_nth_next(
3+pub fn find_nth_next(text: RopeSlice, ch: char, mut pos: usize, n: usize) -> Option<usize> {
4− text: RopeSlice,
5− ch: char,
6− mut pos: usize,
7− n: usize,
8− inclusive: bool,
9−) -> Option<usize> {
10 if pos >= text.len_chars() || n == 0 {
4 if pos >= text.len_chars() || n == 0 {
11 return None;
5 return None;
12 }
6 }
@@ -25,20 +19,10 @@ pub fn find_nth_next(
25 }
19 }
26 }
20 }
27
21
28− if !inclusive {
29− pos -= 1;
30− }
31−
32− Some(pos)
22+ Some(pos - 1)
33}
23}
34
24
35−pub fn find_nth_prev(
25+pub fn find_nth_prev(text: RopeSlice, ch: char, mut pos: usize, n: usize) -> Option<usize> {
36− text: RopeSlice,
37− ch: char,
38− mut pos: usize,
39− n: usize,
40− inclusive: bool,
41−) -> Option<usize> {
42 if pos == 0 || n == 0 {
26 if pos == 0 || n == 0 {
43 return None;
27 return None;
44 }
28 }
@@ -57,9 +41,5 @@ pub fn find_nth_prev(
57 }
41 }
58 }
42 }
59
43
60− if !inclusive {
61− pos += 1;
62− }
63−
64 Some(pos)
44 Some(pos)
65}
45}
helix-core/src/selection.rs+12−8
@@ -248,18 +248,18 @@ impl Range {
248 }
248 }
249 }
249 }
250
250
251− /// Moves the `Range` to `char_idx`. If `extend == true`, then only the head
251+ /// Moves the head of the `Range` to `char_idx`, adjusting the anchor
252− /// is moved to `char_idx`, and the anchor is adjusted only as needed to
253− /// preserve 1-width range semantics.
252+ /// as needed to preserve 1-width range semantics.
253+ ///
254+ /// `block_cursor` specifies whether it should treat `char_idx` as a block
255+ /// cursor position or as a range-end position.
254 ///
256 ///
255 /// This method assumes that the range and `char_idx` are already properly
257 /// This method assumes that the range and `char_idx` are already properly
256 /// grapheme-aligned.
258 /// grapheme-aligned.
257 #[must_use]
259 #[must_use]
258 #[inline]
260 #[inline]
259− pub fn put(self, text: RopeSlice, char_idx: usize, extend: bool) -> Range {
261+ pub fn move_head(self, text: RopeSlice, char_idx: usize, block_cursor: bool) -> Range {
260− let anchor = if !extend {
261− char_idx
262− } else if self.head >= self.anchor && char_idx < self.anchor {
262+ let anchor = if self.head >= self.anchor && char_idx < self.anchor {
263 next_grapheme_boundary(text, self.anchor)
263 next_grapheme_boundary(text, self.anchor)
264 } else if self.head < self.anchor && char_idx >= self.anchor {
264 } else if self.head < self.anchor && char_idx >= self.anchor {
265 prev_grapheme_boundary(text, self.anchor)
265 prev_grapheme_boundary(text, self.anchor)
@@ -267,7 +267,11 @@ impl Range {
267 self.anchor
267 self.anchor
268 };
268 };
269
269
270+ if block_cursor && anchor <= char_idx {
271+ Range::new(anchor, next_grapheme_boundary(text, char_idx))
272+ } else {
270− Range::new(anchor, char_idx)
273+ Range::new(anchor, char_idx)
274+ }
271 }
275 }
272
276
273 // groupAt
277 // groupAt
helix-core/src/surround.rs+6−7
@@ -49,19 +49,18 @@ pub fn find_nth_pairs_pos(
49 if Some(open) == text.get_char(pos) {
49 if Some(open) == text.get_char(pos) {
50 // Special case: cursor is directly on a matching char.
50 // Special case: cursor is directly on a matching char.
51 match pos {
51 match pos {
52− 0 => Some((pos, search::find_nth_next(text, close, pos + 1, n, true)?)),
52+ 0 => Some((pos, search::find_nth_next(text, close, pos + 1, n)? + 1)),
53− _ if (pos + 1) == text.len_chars() => Some((
53+ _ if (pos + 1) == text.len_chars() => {
54− search::find_nth_prev(text, open, pos, n, true)?,
54+ Some((search::find_nth_prev(text, open, pos, n)?, text.len_chars()))
55− text.len_chars(),
55+ }
56− )),
57 // We return no match because there's no way to know which
56 // We return no match because there's no way to know which
58 // side of the char we should be searching on.
57 // side of the char we should be searching on.
59 _ => None,
58 _ => None,
60 }
59 }
61 } else {
60 } else {
62 Some((
61 Some((
63− search::find_nth_prev(text, open, pos, n, true)?,
62+ search::find_nth_prev(text, open, pos, n)?,
64− search::find_nth_next(text, close, pos, n, true)?,
63+ search::find_nth_next(text, close, pos, n)? + 1,
65 ))
64 ))
66 }
65 }
67 } else {
66 } else {
helix-term/src/commands.rs+73−28
@@ -373,15 +373,16 @@ fn goto_line_end(cx: &mut Context) {
373
373
374 let selection = doc.selection(view.id).clone().transform(|range| {
374 let selection = doc.selection(view.id).clone().transform(|range| {
375 let line = range.head_line(text);
375 let line = range.head_line(text);
376+ let line_start = text.line_to_char(line);
376
377
377− let mut pos = line_end_char_index(&text, line);
378+ let pos = graphemes::prev_grapheme_boundary(text, line_end_char_index(&text, line))
378− if doc.mode != Mode::Select {
379+ .max(line_start);
379− pos = graphemes::prev_grapheme_boundary(text, pos);
380− }
381−
382− pos = range.head.max(pos).max(text.line_to_char(line));
383
380
384− range.put(text, pos, doc.mode == Mode::Select)
381+ if doc.mode == Mode::Select {
382+ range.move_head(text, pos, true)
383+ } else {
384+ Range::point(pos)
385+ }
385 });
386 });
386 doc.set_selection(view.id, selection);
387 doc.set_selection(view.id, selection);
387}
388}
@@ -392,12 +393,13 @@ fn goto_line_end_newline(cx: &mut Context) {
392
393
393 let selection = doc.selection(view.id).clone().transform(|range| {
394 let selection = doc.selection(view.id).clone().transform(|range| {
394 let line = range.head_line(text);
395 let line = range.head_line(text);
396+ let pos = line_end_char_index(&text, line);
395
397
396− let mut pos = text.line_to_char((line + 1).min(text.len_lines()));
397− if doc.mode != Mode::Select {
398+ if doc.mode == Mode::Select {
398− pos = graphemes::prev_grapheme_boundary(text, pos);
399+ range.move_head(text, pos, true)
400+ } else {
401+ Range::point(pos)
399 }
402 }
400− range.put(text, pos, doc.mode == Mode::Select)
401 });
403 });
402 doc.set_selection(view.id, selection);
404 doc.set_selection(view.id, selection);
403}
405}
@@ -411,7 +413,11 @@ fn goto_line_start(cx: &mut Context) {
411
413
412 // adjust to start of the line
414 // adjust to start of the line
413 let pos = text.line_to_char(line);
415 let pos = text.line_to_char(line);
414− range.put(text, pos, doc.mode == Mode::Select)
416+ if doc.mode == Mode::Select {
417+ range.move_head(text, pos, true)
418+ } else {
419+ Range::point(pos)
420+ }
415 });
421 });
416 doc.set_selection(view.id, selection);
422 doc.set_selection(view.id, selection);
417}
423}
@@ -425,7 +431,11 @@ fn goto_first_nonwhitespace(cx: &mut Context) {
425
431
426 if let Some(pos) = find_first_non_whitespace_char(text.line(line)) {
432 if let Some(pos) = find_first_non_whitespace_char(text.line(line)) {
427 let pos = pos + text.line_to_char(line);
433 let pos = pos + text.line_to_char(line);
428− range.put(text, pos, doc.mode == Mode::Select)
434+ if doc.mode == Mode::Select {
435+ range.move_head(text, pos, true)
436+ } else {
437+ Range::point(pos)
438+ }
429 } else {
439 } else {
430 range
440 range
431 }
441 }
@@ -569,8 +579,8 @@ fn extend_next_word_start(cx: &mut Context) {
569 .min_width_1(text)
579 .min_width_1(text)
570 .transform(|range| {
580 .transform(|range| {
571 let word = movement::move_next_word_start(text, range, count);
581 let word = movement::move_next_word_start(text, range, count);
572− let pos = word.head;
582+ let pos = graphemes::prev_grapheme_boundary(text, word.head);
573− range.put(text, pos, true)
583+ range.move_head(text, pos, true)
574 });
584 });
575 doc.set_selection(view.id, selection);
585 doc.set_selection(view.id, selection);
576}
586}
@@ -587,7 +597,7 @@ fn extend_prev_word_start(cx: &mut Context) {
587 .transform(|range| {
597 .transform(|range| {
588 let word = movement::move_prev_word_start(text, range, count);
598 let word = movement::move_prev_word_start(text, range, count);
589 let pos = word.head;
599 let pos = word.head;
590− range.put(text, pos, true)
600+ range.move_head(text, pos, true)
591 });
601 });
592 doc.set_selection(view.id, selection);
602 doc.set_selection(view.id, selection);
593}
603}
@@ -603,8 +613,8 @@ fn extend_next_word_end(cx: &mut Context) {
603 .min_width_1(text)
613 .min_width_1(text)
604 .transform(|range| {
614 .transform(|range| {
605 let word = movement::move_next_word_end(text, range, count);
615 let word = movement::move_next_word_end(text, range, count);
606− let pos = word.head;
616+ let pos = graphemes::prev_grapheme_boundary(text, word.head);
607− range.put(text, pos, true)
617+ range.move_head(text, pos, true)
608 });
618 });
609 doc.set_selection(view.id, selection);
619 doc.set_selection(view.id, selection);
610}
620}
@@ -652,11 +662,17 @@ where
652 let text = doc.text().slice(..);
662 let text = doc.text().slice(..);
653
663
654 let selection = doc.selection(view.id).clone().transform(|range| {
664 let selection = doc.selection(view.id).clone().transform(|range| {
665+ let range = if range.anchor < range.head {
666+ // For 1-width cursor semantics.
667+ Range::new(range.anchor, range.head - 1)
668+ } else {
669+ range
670+ };
655 search_fn(text, ch, range.head, count, inclusive).map_or(range, |pos| {
671 search_fn(text, ch, range.head, count, inclusive).map_or(range, |pos| {
656 if extend {
672 if extend {
657− range.put(text, pos, true)
673+ range.move_head(text, pos, true)
658 } else {
674 } else {
659− range.put(text, pos.saturating_sub(1), false)
675+ Range::point(pos)
660 }
676 }
661 })
677 })
662 });
678 });
@@ -664,10 +680,39 @@ where
664 })
680 })
665}
681}
666
682
683+fn find_next_char_impl(
684+ text: RopeSlice,
685+ ch: char,
686+ pos: usize,
687+ n: usize,
688+ inclusive: bool,
689+) -> Option<usize> {
690+ let pos = (pos + 1).min(text.len_chars());
691+ if inclusive {
692+ search::find_nth_next(text, ch, pos, n)
693+ } else {
694+ search::find_nth_next(text, ch, pos, n).map(|n| n.saturating_sub(1))
695+ }
696+}
697+
698+fn find_prev_char_impl(
699+ text: RopeSlice,
700+ ch: char,
701+ pos: usize,
702+ n: usize,
703+ inclusive: bool,
704+) -> Option<usize> {
705+ if inclusive {
706+ search::find_nth_prev(text, ch, pos, n)
707+ } else {
708+ search::find_nth_prev(text, ch, pos, n).map(|n| (n + 1).min(text.len_chars()))
709+ }
710+}
711+
667fn find_till_char(cx: &mut Context) {
712fn find_till_char(cx: &mut Context) {
668 find_char_impl(
713 find_char_impl(
669 cx,
714 cx,
670− search::find_nth_next,
715+ find_next_char_impl,
671 false, /* inclusive */
716 false, /* inclusive */
672 false, /* extend */
717 false, /* extend */
673 )
718 )
@@ -676,7 +721,7 @@ fn find_till_char(cx: &mut Context) {
676fn find_next_char(cx: &mut Context) {
721fn find_next_char(cx: &mut Context) {
677 find_char_impl(
722 find_char_impl(
678 cx,
723 cx,
679− search::find_nth_next,
724+ find_next_char_impl,
680 true, /* inclusive */
725 true, /* inclusive */
681 false, /* extend */
726 false, /* extend */
682 )
727 )
@@ -685,7 +730,7 @@ fn find_next_char(cx: &mut Context) {
685fn extend_till_char(cx: &mut Context) {
730fn extend_till_char(cx: &mut Context) {
686 find_char_impl(
731 find_char_impl(
687 cx,
732 cx,
688− search::find_nth_next,
733+ find_next_char_impl,
689 false, /* inclusive */
734 false, /* inclusive */
690 true, /* extend */
735 true, /* extend */
691 )
736 )
@@ -694,7 +739,7 @@ fn extend_till_char(cx: &mut Context) {
694fn extend_next_char(cx: &mut Context) {
739fn extend_next_char(cx: &mut Context) {
695 find_char_impl(
740 find_char_impl(
696 cx,
741 cx,
697− search::find_nth_next,
742+ find_next_char_impl,
698 true, /* inclusive */
743 true, /* inclusive */
699 true, /* extend */
744 true, /* extend */
700 )
745 )
@@ -703,7 +748,7 @@ fn extend_next_char(cx: &mut Context) {
703fn till_prev_char(cx: &mut Context) {
748fn till_prev_char(cx: &mut Context) {
704 find_char_impl(
749 find_char_impl(
705 cx,
750 cx,
706− search::find_nth_prev,
751+ find_prev_char_impl,
707 false, /* inclusive */
752 false, /* inclusive */
708 false, /* extend */
753 false, /* extend */
709 )
754 )
@@ -712,7 +757,7 @@ fn till_prev_char(cx: &mut Context) {
712fn find_prev_char(cx: &mut Context) {
757fn find_prev_char(cx: &mut Context) {
713 find_char_impl(
758 find_char_impl(
714 cx,
759 cx,
715− search::find_nth_prev,
760+ find_prev_char_impl,
716 true, /* inclusive */
761 true, /* inclusive */
717 false, /* extend */
762 false, /* extend */
718 )
763 )
@@ -721,7 +766,7 @@ fn find_prev_char(cx: &mut Context) {
721fn extend_till_prev_char(cx: &mut Context) {
766fn extend_till_prev_char(cx: &mut Context) {
722 find_char_impl(
767 find_char_impl(
723 cx,
768 cx,
724− search::find_nth_prev,
769+ find_prev_char_impl,
725 false, /* inclusive */
770 false, /* inclusive */
726 true, /* extend */
771 true, /* extend */
727 )
772 )
@@ -730,7 +775,7 @@ fn extend_till_prev_char(cx: &mut Context) {
730fn extend_prev_char(cx: &mut Context) {
775fn extend_prev_char(cx: &mut Context) {
731 find_char_impl(
776 find_char_impl(
732 cx,
777 cx,
733− search::find_nth_prev,
778+ find_prev_char_impl,
734 true, /* inclusive */
779 true, /* inclusive */
735 true, /* extend */
780 true, /* extend */
736 )
781 )