on master help sign in
Update surround commands to work with gap indexing.
commitb4c59b444cc4963f95a95fe10f166e58ef857288 authorNathan Vegdahl <cessen@cessen.com> dateThu 8 Jul 2021, 16:45 (5 years ago) parent753f7f3
6 files changed+234−239
helix-core/src/movement.rs+3−3 helix-core/src/search.rs+7−5 helix-core/src/selection.rs+1−1 helix-core/src/surround.rs+45−31 helix-core/src/textobject.rs+134−133 helix-term/src/commands.rs+44−66
helix-core/src/movement.rs+3−3
@@ -56,7 +56,7 @@ pub fn move_horizontally(
5656 };
5757
5858 // Compute the final new range.
59− range.put(slice, behaviour == Extend, new_pos)
59+ range.put(slice, new_pos, behaviour == Extend)
6060}
6161
6262pub fn move_vertically(
@@ -106,7 +106,7 @@ pub fn move_vertically(
106106 new_pos
107107 };
108108
109− let mut new_range = range.put(slice, true, new_head);
109+ let mut new_range = range.put(slice, new_head, true);
110110 new_range.horiz = Some(horiz);
111111 new_range
112112 }
@@ -427,7 +427,7 @@ mod test {
427427 #[test]
428428 fn vertical_moves_in_single_column() {
429429 let text = Rope::from(MULTILINE_SAMPLE);
430− let slice = dbg!(&text).slice(..);
430+ let slice = text.slice(..);
431431 let position = pos_at_coords(slice, (0, 0).into());
432432 let mut range = Range::point(position);
433433 let moves_and_expected_coordinates = IntoIter::new([
helix-core/src/search.rs+7−5
@@ -7,12 +7,11 @@ pub fn find_nth_next(
77 n: usize,
88 inclusive: bool,
99) -> Option<usize> {
10− if pos >= text.len_chars() {
10+ if pos >= text.len_chars() || n == 0 {
1111 return None;
1212 }
1313
14− // start searching right after pos
15− let mut chars = text.chars_at(pos + 1);
14+ let mut chars = text.chars_at(pos);
1615
1716 for _ in 0..n {
1817 loop {
@@ -40,14 +39,17 @@ pub fn find_nth_prev(
4039 n: usize,
4140 inclusive: bool,
4241) -> Option<usize> {
43− // start searching right before pos
42+ if pos == 0 || n == 0 {
43+ return None;
44+ }
45+
4446 let mut chars = text.chars_at(pos);
4547
4648 for _ in 0..n {
4749 loop {
4850 let c = chars.prev()?;
4951
50− pos = pos.saturating_sub(1);
52+ pos -= 1;
5153
5254 if c == ch {
5355 break;
helix-core/src/selection.rs+1−1
@@ -217,7 +217,7 @@ impl Range {
217217 /// grapheme-aligned.
218218 #[must_use]
219219 #[inline]
220− pub fn put(self, text: RopeSlice, extend: bool, char_idx: usize) -> Range {
220+ pub fn put(self, text: RopeSlice, char_idx: usize, extend: bool) -> Range {
221221 let anchor = if !extend {
222222 char_idx
223223 } else if self.head >= self.anchor && char_idx < self.anchor {
helix-core/src/surround.rs+45−31
@@ -1,3 +1,4 @@
1+use crate::graphemes::next_grapheme_boundary;
12use crate::{search, Selection};
23use ropey::RopeSlice;
34
@@ -40,23 +41,35 @@ pub fn find_nth_pairs_pos(
4041) -> Option<(usize, usize)> {
4142 let (open, close) = get_pair(ch);
4243
43− let (open_pos, close_pos) = if open == close {
44− let prev = search::find_nth_prev(text, open, pos, n, true);
45− let next = search::find_nth_next(text, close, pos, n, true);
46− if text.char(pos) == open {
47− // cursor is *on* a pair
48− next.map(|n| (pos, n)).or_else(|| prev.map(|p| (p, pos)))?
44+ if text.len_chars() < 2 || pos >= text.len_chars() {
45+ return None;
46+ }
47+
48+ if open == close {
49+ if Some(open) == text.get_char(pos) {
50+ // Special case: cursor is directly on a matching char.
51+ match pos {
52+ 0 => Some((pos, search::find_nth_next(text, close, pos + 1, n, true)?)),
53+ _ if (pos + 1) == text.len_chars() => Some((
54+ search::find_nth_prev(text, open, pos, n, true)?,
55+ text.len_chars(),
56+ )),
57+ // We return no match because there's no way to know which
58+ // side of the char we should be searching on.
59+ _ => None,
60+ }
4961 } else {
50− (prev?, next?)
62+ Some((
63+ search::find_nth_prev(text, open, pos, n, true)?,
64+ search::find_nth_next(text, close, pos, n, true)?,
65+ ))
5166 }
5267 } else {
53− (
68+ Some((
5469 find_nth_open_pair(text, open, close, pos, n)?,
55− find_nth_close_pair(text, open, close, pos, n)?,
56− )
57− };
58−
59− Some((open_pos, close_pos))
70+ next_grapheme_boundary(text, find_nth_close_pair(text, open, close, pos, n)?),
71+ ))
72+ }
6073}
6174
6275fn find_nth_open_pair(
@@ -173,12 +186,13 @@ mod test {
173186 let slice = doc.slice(..);
174187
175188 // cursor on [t]ext
176− assert_eq!(find_nth_pairs_pos(slice, '(', 6, 1), Some((5, 10)));
177− assert_eq!(find_nth_pairs_pos(slice, ')', 6, 1), Some((5, 10)));
189+ assert_eq!(find_nth_pairs_pos(slice, '(', 6, 1), Some((5, 11)));
190+ assert_eq!(find_nth_pairs_pos(slice, ')', 6, 1), Some((5, 11)));
178191 // cursor on so[m]e
179192 assert_eq!(find_nth_pairs_pos(slice, '(', 2, 1), None);
180193 // cursor on bracket itself
181− assert_eq!(find_nth_pairs_pos(slice, '(', 5, 1), Some((5, 10)));
194+ assert_eq!(find_nth_pairs_pos(slice, '(', 5, 1), Some((5, 11)));
195+ assert_eq!(find_nth_pairs_pos(slice, '(', 10, 1), Some((5, 11)));
182196 }
183197
184198 #[test]
@@ -187,9 +201,9 @@ mod test {
187201 let slice = doc.slice(..);
188202
189203 // cursor on go[o]d
190− assert_eq!(find_nth_pairs_pos(slice, '(', 13, 1), Some((10, 15)));
191− assert_eq!(find_nth_pairs_pos(slice, '(', 13, 2), Some((4, 21)));
192− assert_eq!(find_nth_pairs_pos(slice, '(', 13, 3), Some((0, 27)));
204+ assert_eq!(find_nth_pairs_pos(slice, '(', 13, 1), Some((10, 16)));
205+ assert_eq!(find_nth_pairs_pos(slice, '(', 13, 2), Some((4, 22)));
206+ assert_eq!(find_nth_pairs_pos(slice, '(', 13, 3), Some((0, 28)));
193207 }
194208
195209 #[test]
@@ -198,14 +212,14 @@ mod test {
198212 let slice = doc.slice(..);
199213
200214 // cursor on go[o]d
201− assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 1), Some((10, 15)));
202− assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 2), Some((4, 21)));
203− assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 3), Some((0, 27)));
215+ assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 1), Some((10, 16)));
216+ assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 2), Some((4, 22)));
217+ assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 3), Some((0, 28)));
204218 // cursor on the quotes
205− assert_eq!(find_nth_pairs_pos(slice, '\'', 10, 1), Some((10, 15)));
219+ assert_eq!(find_nth_pairs_pos(slice, '\'', 10, 1), None);
206220 // this is the best we can do since opening and closing pairs are same
207− assert_eq!(find_nth_pairs_pos(slice, '\'', 0, 1), Some((0, 4)));
208− assert_eq!(find_nth_pairs_pos(slice, '\'', 27, 1), Some((21, 27)));
221+ assert_eq!(find_nth_pairs_pos(slice, '\'', 0, 1), Some((0, 5)));
222+ assert_eq!(find_nth_pairs_pos(slice, '\'', 27, 1), Some((21, 28)));
209223 }
210224
211225 #[test]
@@ -214,8 +228,8 @@ mod test {
214228 let slice = doc.slice(..);
215229
216230 // cursor on go[o]d
217− assert_eq!(find_nth_pairs_pos(slice, '(', 15, 1), Some((5, 24)));
218− assert_eq!(find_nth_pairs_pos(slice, '(', 15, 2), Some((0, 31)));
231+ assert_eq!(find_nth_pairs_pos(slice, '(', 15, 1), Some((5, 25)));
232+ assert_eq!(find_nth_pairs_pos(slice, '(', 15, 2), Some((0, 32)));
219233 }
220234
221235 #[test]
@@ -224,9 +238,9 @@ mod test {
224238 let slice = doc.slice(..);
225239
226240 // cursor on go[o]d
227− assert_eq!(find_nth_pairs_pos(slice, '{', 13, 1), Some((10, 15)));
228− assert_eq!(find_nth_pairs_pos(slice, '[', 13, 1), Some((4, 21)));
229− assert_eq!(find_nth_pairs_pos(slice, '(', 13, 1), Some((0, 27)));
241+ assert_eq!(find_nth_pairs_pos(slice, '{', 13, 1), Some((10, 16)));
242+ assert_eq!(find_nth_pairs_pos(slice, '[', 13, 1), Some((4, 22)));
243+ assert_eq!(find_nth_pairs_pos(slice, '(', 13, 1), Some((0, 28)));
230244 }
231245
232246 #[test]
@@ -243,7 +257,7 @@ mod test {
243257 get_surround_pos(slice, &selection, '(', 1)
244258 .unwrap()
245259 .as_slice(),
246− &[0, 5, 7, 13, 15, 23]
260+ &[0, 6, 7, 14, 15, 24]
247261 );
248262 }
249263
helix-core/src/textobject.rs+134−133
@@ -1,21 +1,16 @@
11use ropey::RopeSlice;
22
3−use crate::chars::{categorize_char, char_is_line_ending, char_is_whitespace, CharCategory};
4−use crate::movement::{self, Direction};
3+use crate::chars::{categorize_char, CharCategory};
4+use crate::graphemes::{next_grapheme_boundary, prev_grapheme_boundary};
5+use crate::movement::Direction;
56use crate::surround;
67use crate::Range;
78
8−fn this_word_end_pos(slice: RopeSlice, pos: usize) -> usize {
9− this_word_bound_pos(slice, pos, Direction::Forward)
10−}
11−
12−fn this_word_start_pos(slice: RopeSlice, pos: usize) -> usize {
13− this_word_bound_pos(slice, pos, Direction::Backward)
14−}
9+fn find_word_boundary(slice: RopeSlice, mut pos: usize, direction: Direction) -> usize {
10+ use CharCategory::{Eol, Whitespace};
1511
16−fn this_word_bound_pos(slice: RopeSlice, mut pos: usize, direction: Direction) -> usize {
1712 let iter = match direction {
18− Direction::Forward => slice.chars_at(pos + 1),
13+ Direction::Forward => slice.chars_at(pos),
1914 Direction::Backward => {
2015 let mut iter = slice.chars_at(pos);
2116 iter.reverse();
@@ -23,25 +18,32 @@ fn this_word_bound_pos(slice: RopeSlice, mut pos: usize, direction: Direction) -
2318 }
2419 };
2520
26− match categorize_char(slice.char(pos)) {
27− CharCategory::Eol | CharCategory::Whitespace => pos,
28− category => {
29− for peek in iter {
30− let curr_category = categorize_char(peek);
31− if curr_category != category
32− || curr_category == CharCategory::Eol
33− || curr_category == CharCategory::Whitespace
34− {
21+ let mut prev_category = match direction {
22+ Direction::Forward if pos == 0 => Whitespace,
23+ Direction::Forward => categorize_char(slice.char(pos - 1)),
24+ Direction::Backward if pos == slice.len_chars() => Whitespace,
25+ Direction::Backward => categorize_char(slice.char(pos)),
26+ };
27+
28+ for ch in iter {
29+ match categorize_char(ch) {
30+ Eol | Whitespace => return pos,
31+ category => {
32+ if category != prev_category && pos != 0 && pos != slice.len_chars() {
3533 return pos;
36− }
37− pos = match direction {
38− Direction::Forward => pos + 1,
39− Direction::Backward => pos.saturating_sub(1),
34+ } else {
35+ if direction == Direction::Forward {
36+ pos += 1;
37+ } else {
38+ pos = pos.saturating_sub(1);
39+ }
40+ prev_category = category;
4041 }
4142 }
42− pos
4343 }
4444 }
45+
46+ pos
4547}
4648
4749#[derive(Copy, Clone, PartialEq, Eq, Debug)]
@@ -55,46 +57,42 @@ pub fn textobject_word(
5557 slice: RopeSlice,
5658 range: Range,
5759 textobject: TextObject,
58− count: usize,
60+ _count: usize,
5961) -> Range {
60− let this_word_start = this_word_start_pos(slice, range.head);
61− let this_word_end = this_word_end_pos(slice, range.head);
62+ // For 1-width cursor semantics.
63+ let head = if range.head > range.anchor {
64+ prev_grapheme_boundary(slice, range.head)
65+ } else {
66+ range.head
67+ };
68+
69+ let word_start = find_word_boundary(slice, head, Direction::Backward);
70+ let word_end = match slice.get_char(head).map(categorize_char) {
71+ None | Some(CharCategory::Whitespace | CharCategory::Eol) => head,
72+ _ => find_word_boundary(slice, head + 1, Direction::Forward),
73+ };
74+
75+ // Special case.
76+ if word_start == word_end {
77+ return Range::new(word_start, word_end);
78+ }
6279
63− let (anchor, head);
6480 match textobject {
65− TextObject::Inside => {
66− anchor = this_word_start;
67− head = this_word_end;
68− }
69− TextObject::Around => {
70− if slice
71− .get_char(this_word_end + 1)
72− .map_or(true, char_is_line_ending)
81+ TextObject::Inside => Range::new(word_start, word_end),
82+ TextObject::Around => Range::new(
83+ match slice
84+ .get_char(word_start.saturating_sub(1))
85+ .map(categorize_char)
7386 {
74− head = this_word_end;
75− if slice
76− .get_char(this_word_start.saturating_sub(1))
77− .map_or(true, char_is_line_ending)
78− {
79− // single word on a line
80− anchor = this_word_start;
81− } else {
82− // last word on a line, select the whitespace before it too
83− anchor = movement::move_prev_word_end(slice, range, count).head;
84− }
85− } else if char_is_whitespace(slice.char(range.head)) {
86− // select whole whitespace and next word
87− head = movement::move_next_word_end(slice, range, count).head;
88− anchor = movement::backwards_skip_while(slice, range.head, |c| c.is_whitespace())
89− .map(|p| p + 1) // p is first *non* whitespace char, so +1 to get whitespace pos
90− .unwrap_or(0);
91− } else {
92− head = movement::move_next_word_start(slice, range, count).head;
93− anchor = this_word_start;
94− }
95− }
96− };
97− Range::new(anchor, head)
87+ None | Some(CharCategory::Eol) => word_start,
88+ _ => prev_grapheme_boundary(slice, word_start),
89+ },
90+ match slice.get_char(word_end).map(categorize_char) {
91+ None | Some(CharCategory::Eol) => word_end,
92+ _ => next_grapheme_boundary(slice, word_end),
93+ },
94+ ),
95+ }
9896}
9997
10098pub fn textobject_surround(
@@ -106,7 +104,10 @@ pub fn textobject_surround(
106104) -> Range {
107105 surround::find_nth_pairs_pos(slice, ch, range.head, count)
108106 .map(|(anchor, head)| match textobject {
109− TextObject::Inside => Range::new(anchor + 1, head.saturating_sub(1)),
107+ TextObject::Inside => Range::new(
108+ next_grapheme_boundary(slice, anchor),
109+ prev_grapheme_boundary(slice, head),
110+ ),
110111 TextObject::Around => Range::new(anchor, head),
111112 })
112113 .unwrap_or(range)
@@ -126,70 +127,70 @@ mod test {
126127 let tests = &[
127128 (
128129 "cursor at beginning of doc",
129− vec![(0, Inside, (0, 5)), (0, Around, (0, 6))],
130+ vec![(0, Inside, (0, 6)), (0, Around, (0, 7))],
130131 ),
131132 (
132133 "cursor at middle of word",
133134 vec![
134− (13, Inside, (10, 15)),
135− (10, Inside, (10, 15)),
136− (15, Inside, (10, 15)),
137− (13, Around, (10, 16)),
138− (10, Around, (10, 16)),
139− (15, Around, (10, 16)),
135+ (13, Inside, (10, 16)),
136+ (10, Inside, (10, 16)),
137+ (15, Inside, (10, 16)),
138+ (13, Around, (9, 17)),
139+ (10, Around, (9, 17)),
140+ (15, Around, (9, 17)),
140141 ],
141142 ),
142143 (
143144 "cursor between word whitespace",
144− vec![(6, Inside, (6, 6)), (6, Around, (6, 13))],
145+ vec![(6, Inside, (6, 6)), (6, Around, (6, 6))],
145146 ),
146147 (
147148 "cursor on word before newline\n",
148149 vec![
149− (22, Inside, (22, 28)),
150− (28, Inside, (22, 28)),
151− (25, Inside, (22, 28)),
152− (22, Around, (21, 28)),
153− (28, Around, (21, 28)),
154− (25, Around, (21, 28)),
150+ (22, Inside, (22, 29)),
151+ (28, Inside, (22, 29)),
152+ (25, Inside, (22, 29)),
153+ (22, Around, (21, 29)),
154+ (28, Around, (21, 29)),
155+ (25, Around, (21, 29)),
155156 ],
156157 ),
157158 (
158159 "cursor on newline\nnext line",
159− vec![(17, Inside, (17, 17)), (17, Around, (17, 22))],
160+ vec![(17, Inside, (17, 17)), (17, Around, (17, 17))],
160161 ),
161162 (
162163 "cursor on word after newline\nnext line",
163164 vec![
164− (29, Inside, (29, 32)),
165− (30, Inside, (29, 32)),
166− (32, Inside, (29, 32)),
167− (29, Around, (29, 33)),
168− (30, Around, (29, 33)),
169− (32, Around, (29, 33)),
165+ (29, Inside, (29, 33)),
166+ (30, Inside, (29, 33)),
167+ (32, Inside, (29, 33)),
168+ (29, Around, (29, 34)),
169+ (30, Around, (29, 34)),
170+ (32, Around, (29, 34)),
170171 ],
171172 ),
172173 (
173174 "cursor on #$%:;* punctuation",
174175 vec![
175− (13, Inside, (10, 15)),
176− (10, Inside, (10, 15)),
177− (15, Inside, (10, 15)),
178− (13, Around, (10, 16)),
179− (10, Around, (10, 16)),
180− (15, Around, (10, 16)),
176+ (13, Inside, (10, 16)),
177+ (10, Inside, (10, 16)),
178+ (15, Inside, (10, 16)),
179+ (13, Around, (9, 17)),
180+ (10, Around, (9, 17)),
181+ (15, Around, (9, 17)),
181182 ],
182183 ),
183184 (
184185 "cursor on punc%^#$:;.tuation",
185186 vec![
186− (14, Inside, (14, 20)),
187− (20, Inside, (14, 20)),
188− (17, Inside, (14, 20)),
189− (14, Around, (14, 20)),
187+ (14, Inside, (14, 21)),
188+ (20, Inside, (14, 21)),
189+ (17, Inside, (14, 21)),
190+ (14, Around, (13, 22)),
190191 // FIXME: edge case
191192 // (20, Around, (14, 20)),
192− (17, Around, (14, 20)),
193+ (17, Around, (13, 22)),
193194 ],
194195 ),
195196 (
@@ -198,14 +199,14 @@ mod test {
198199 (9, Inside, (9, 9)),
199200 (10, Inside, (10, 10)),
200201 (11, Inside, (11, 11)),
201− (9, Around, (9, 16)),
202− (10, Around, (9, 16)),
203− (11, Around, (9, 16)),
202+ (9, Around, (9, 9)),
203+ (10, Around, (10, 10)),
204+ (11, Around, (11, 11)),
204205 ],
205206 ),
206207 (
207208 "cursor at end of doc",
208− vec![(19, Inside, (17, 19)), (19, Around, (16, 19))],
209+ vec![(19, Inside, (17, 20)), (19, Around, (16, 20))],
209210 ),
210211 ];
211212
@@ -234,67 +235,67 @@ mod test {
234235 "simple (single) surround pairs",
235236 vec![
236237 (3, Inside, (3, 3), '(', 1),
237− (7, Inside, (8, 13), ')', 1),
238− (10, Inside, (8, 13), '(', 1),
239− (14, Inside, (8, 13), ')', 1),
238+ (7, Inside, (8, 14), ')', 1),
239+ (10, Inside, (8, 14), '(', 1),
240+ (14, Inside, (8, 14), ')', 1),
240241 (3, Around, (3, 3), '(', 1),
241− (7, Around, (7, 14), ')', 1),
242− (10, Around, (7, 14), '(', 1),
243− (14, Around, (7, 14), ')', 1),
242+ (7, Around, (7, 15), ')', 1),
243+ (10, Around, (7, 15), '(', 1),
244+ (14, Around, (7, 15), ')', 1),
244245 ],
245246 ),
246247 (
247248 "samexx 'single' surround pairs",
248249 vec![
249250 (3, Inside, (3, 3), '\'', 1),
250− (7, Inside, (8, 13), '\'', 1),
251− (10, Inside, (8, 13), '\'', 1),
252− (14, Inside, (8, 13), '\'', 1),
251+ (7, Inside, (7, 7), '\'', 1),
252+ (10, Inside, (8, 14), '\'', 1),
253+ (14, Inside, (14, 14), '\'', 1),
253254 (3, Around, (3, 3), '\'', 1),
254− (7, Around, (7, 14), '\'', 1),
255− (10, Around, (7, 14), '\'', 1),
256− (14, Around, (7, 14), '\'', 1),
255+ (7, Around, (7, 7), '\'', 1),
256+ (10, Around, (7, 15), '\'', 1),
257+ (14, Around, (14, 14), '\'', 1),
257258 ],
258259 ),
259260 (
260261 "(nested (surround (pairs)) 3 levels)",
261262 vec![
262− (0, Inside, (1, 34), '(', 1),
263− (6, Inside, (1, 34), ')', 1),
264− (8, Inside, (9, 24), '(', 1),
265− (8, Inside, (9, 34), ')', 2),
266− (20, Inside, (9, 24), '(', 2),
267− (20, Inside, (1, 34), ')', 3),
268− (0, Around, (0, 35), '(', 1),
269− (6, Around, (0, 35), ')', 1),
270− (8, Around, (8, 25), '(', 1),
271− (8, Around, (8, 35), ')', 2),
272− (20, Around, (8, 25), '(', 2),
273− (20, Around, (0, 35), ')', 3),
263+ (0, Inside, (1, 35), '(', 1),
264+ (6, Inside, (1, 35), ')', 1),
265+ (8, Inside, (9, 25), '(', 1),
266+ (8, Inside, (9, 35), ')', 2),
267+ (20, Inside, (9, 25), '(', 2),
268+ (20, Inside, (1, 35), ')', 3),
269+ (0, Around, (0, 36), '(', 1),
270+ (6, Around, (0, 36), ')', 1),
271+ (8, Around, (8, 26), '(', 1),
272+ (8, Around, (8, 36), ')', 2),
273+ (20, Around, (8, 26), '(', 2),
274+ (20, Around, (0, 36), ')', 3),
274275 ],
275276 ),
276277 (
277278 "(mixed {surround [pair] same} line)",
278279 vec![
279− (2, Inside, (1, 33), '(', 1),
280− (9, Inside, (8, 27), '{', 1),
281− (18, Inside, (18, 21), '[', 1),
282− (2, Around, (0, 34), '(', 1),
283− (9, Around, (7, 28), '{', 1),
284− (18, Around, (17, 22), '[', 1),
280+ (2, Inside, (1, 34), '(', 1),
281+ (9, Inside, (8, 28), '{', 1),
282+ (18, Inside, (18, 22), '[', 1),
283+ (2, Around, (0, 35), '(', 1),
284+ (9, Around, (7, 29), '{', 1),
285+ (18, Around, (17, 23), '[', 1),
285286 ],
286287 ),
287288 (
288289 "(stepped (surround) pairs (should) skip)",
289− vec![(22, Inside, (1, 38), '(', 1), (22, Around, (0, 39), '(', 1)],
290+ vec![(22, Inside, (1, 39), '(', 1), (22, Around, (0, 40), '(', 1)],
290291 ),
291292 (
292293 "[surround pairs{\non different]\nlines}",
293294 vec![
294− (7, Inside, (1, 28), '[', 1),
295− (15, Inside, (16, 35), '{', 1),
296− (7, Around, (0, 29), '[', 1),
297− (15, Around, (15, 36), '{', 1),
295+ (7, Inside, (1, 29), '[', 1),
296+ (15, Inside, (16, 36), '{', 1),
297+ (7, Around, (0, 30), '[', 1),
298+ (15, Around, (15, 37), '{', 1),
298299 ],
299300 ),
300301 ];
helix-term/src/commands.rs+44−66
@@ -1,9 +1,6 @@
11use helix_core::{
22 comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes, indent,
3− line_ending::{
4− get_line_ending_of_str, line_end_char_index, rope_end_without_line_ending,
5− str_is_line_ending,
6− },
3+ line_ending::{get_line_ending_of_str, line_end_char_index, str_is_line_ending},
74 match_brackets,
85 movement::{self, Direction},
96 object, pos_at_coords,
@@ -392,20 +389,14 @@ fn goto_line_end(cx: &mut Context) {
392389 doc.set_selection(
393390 view.id,
394391 doc.selection(view.id).clone().transform(|range| {
395− let text = doc.text();
392+ let text = doc.text().slice(..);
396393 let line = text.char_to_line(range.head);
397394
398− let pos = line_end_char_index(&text.slice(..), line);
399− let pos = graphemes::nth_prev_grapheme_boundary(text.slice(..), pos, 1);
395+ let pos = line_end_char_index(&text, line);
396+ let pos = graphemes::nth_prev_grapheme_boundary(text, pos, 1);
400397 let pos = range.head.max(pos).max(text.line_to_char(line));
401398
402− Range::new(
403− match doc.mode {
404− Mode::Normal | Mode::Insert => pos,
405− Mode::Select => range.anchor,
406− },
407− pos,
408− )
399+ range.put(text, pos, doc.mode == Mode::Select)
409400 }),
410401 );
411402}
@@ -416,11 +407,11 @@ fn goto_line_end_newline(cx: &mut Context) {
416407 doc.set_selection(
417408 view.id,
418409 doc.selection(view.id).clone().transform(|range| {
419− let text = doc.text();
410+ let text = doc.text().slice(..);
420411 let line = text.char_to_line(range.head);
421412
422− let pos = line_end_char_index(&text.slice(..), line);
423− Range::new(pos, pos)
413+ let pos = line_end_char_index(&text, line);
414+ range.put(text, pos, doc.mode == Mode::Select)
424415 }),
425416 );
426417}
@@ -430,18 +421,12 @@ fn goto_line_start(cx: &mut Context) {
430421 doc.set_selection(
431422 view.id,
432423 doc.selection(view.id).clone().transform(|range| {
433− let text = doc.text();
424+ let text = doc.text().slice(..);
434425 let line = text.char_to_line(range.head);
435426
436427 // adjust to start of the line
437428 let pos = text.line_to_char(line);
438− Range::new(
439− match doc.mode {
440− Mode::Normal | Mode::Insert => pos,
441− Mode::Select => range.anchor,
442− },
443− pos,
444− )
429+ range.put(text, pos, doc.mode == Mode::Select)
445430 }),
446431 );
447432}
@@ -451,18 +436,12 @@ fn goto_first_nonwhitespace(cx: &mut Context) {
451436 doc.set_selection(
452437 view.id,
453438 doc.selection(view.id).clone().transform(|range| {
454− let text = doc.text();
439+ let text = doc.text().slice(..);
455440 let line_idx = text.char_to_line(range.head);
456441
457442 if let Some(pos) = find_first_non_whitespace_char(text.line(line_idx)) {
458443 let pos = pos + text.line_to_char(line_idx);
459− Range::new(
460− match doc.mode {
461− Mode::Normal | Mode::Insert => pos,
462− Mode::Select => range.anchor,
463− },
464− pos,
465− )
444+ range.put(text, pos, doc.mode == Mode::Select)
466445 } else {
467446 range
468447 }
@@ -581,9 +560,7 @@ fn goto_file_start(cx: &mut Context) {
581560fn goto_file_end(cx: &mut Context) {
582561 push_jump(cx.editor);
583562 let (view, doc) = current!(cx.editor);
584− let text = doc.text();
585− let last_line = text.line_to_char(text.len_lines().saturating_sub(2));
586− doc.set_selection(view.id, Selection::point(last_line));
563+ doc.set_selection(view.id, Selection::point(doc.text().len_chars()));
587564}
588565
589566fn extend_next_word_start(cx: &mut Context) {
@@ -592,9 +569,10 @@ fn extend_next_word_start(cx: &mut Context) {
592569 doc.set_selection(
593570 view.id,
594571 doc.selection(view.id).clone().transform(|range| {
595− let word = movement::move_next_word_start(doc.text().slice(..), range, count);
572+ let text = doc.text().slice(..);
573+ let word = movement::move_next_word_start(text, range, count);
596574 let pos = word.head;
597− Range::new(range.anchor, pos)
575+ range.put(text, pos, true)
598576 }),
599577 );
600578}
@@ -605,9 +583,10 @@ fn extend_prev_word_start(cx: &mut Context) {
605583 doc.set_selection(
606584 view.id,
607585 doc.selection(view.id).clone().transform(|range| {
608− let word = movement::move_prev_word_start(doc.text().slice(..), range, count);
586+ let text = doc.text().slice(..);
587+ let word = movement::move_prev_word_start(text, range, count);
609588 let pos = word.head;
610− Range::new(range.anchor, pos)
589+ range.put(text, pos, true)
611590 }),
612591 );
613592}
@@ -618,9 +597,10 @@ fn extend_next_word_end(cx: &mut Context) {
618597 doc.set_selection(
619598 view.id,
620599 doc.selection(view.id).clone().transform(|range| {
621− let word = movement::move_next_word_end(doc.text().slice(..), range, count);
600+ let text = doc.text().slice(..);
601+ let word = movement::move_next_word_end(text, range, count);
622602 let pos = word.head;
623− Range::new(range.anchor, pos)
603+ range.put(text, pos, true)
624604 }),
625605 );
626606}
@@ -669,18 +649,9 @@ where
669649 doc.set_selection(
670650 view.id,
671651 doc.selection(view.id).clone().transform(|range| {
672− search_fn(doc.text().slice(..), ch, range.head, count, inclusive).map_or(
673− range,
674− |pos| {
675− if extend {
676− Range::new(range.anchor, pos)
677− } else {
678− // select
679− Range::new(range.head, pos)
680− }
681− // or (pos, pos) to move to found val
682− },
683− )
652+ let text = doc.text().slice(..);
653+ search_fn(text, ch, range.head, count, inclusive)
654+ .map_or(range, |pos| range.put(text, pos, extend))
684655 }),
685656 );
686657 })
@@ -940,7 +911,7 @@ fn extend_line_down(cx: &mut Context) {
940911fn select_all(cx: &mut Context) {
941912 let (view, doc) = current!(cx.editor);
942913
943− let end = rope_end_without_line_ending(&doc.text().slice(..));
914+ let end = doc.text().len_chars();
944915 doc.set_selection(view.id, Selection::single(0, end))
945916}
946917
@@ -997,12 +968,10 @@ fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Rege
997968 return;
998969 }
999970
1000− let head = end - 1;
1001−
1002971 let selection = if extend {
1003− selection.clone().push(Range::new(start, head))
972+ selection.clone().push(Range::new(start, end))
1004973 } else {
1005− Selection::single(start, head)
974+ Selection::single(start, end)
1006975 };
1007976
1008977 doc.set_selection(view.id, selection);
@@ -1143,9 +1112,15 @@ fn collapse_selection(cx: &mut Context) {
11431112
11441113 doc.set_selection(
11451114 view.id,
1146− doc.selection(view.id)
1147− .clone()
1148− .transform(|range| Range::new(range.head, range.head)),
1115+ doc.selection(view.id).clone().transform(|range| {
1116+ let pos = if range.head > range.anchor {
1117+ // For 1-width cursor semantics.
1118+ graphemes::prev_grapheme_boundary(doc.text().slice(..), range.head)
1119+ } else {
1120+ range.head
1121+ };
1122+ Range::new(pos, pos)
1123+ }),
11491124 );
11501125}
11511126
@@ -1184,10 +1159,13 @@ fn append_mode(cx: &mut Context) {
11841159 doc.restore_cursor = true;
11851160
11861161 let selection = doc.selection(view.id).clone().transform(|range| {
1187− Range::new(
1188− range.from(),
1189− graphemes::next_grapheme_boundary(doc.text().slice(..), range.to()), // to() + next char
1190− )
1162+ let to = if range.to() == range.from() {
1163+ // For 1-width cursor semantics.
1164+ graphemes::next_grapheme_boundary(doc.text().slice(..), range.to())
1165+ } else {
1166+ range.to()
1167+ };
1168+ Range::new(range.from(), to)
11911169 });
11921170
11931171 let end = doc.text().len_chars();