on master help sign in
Update surround commands to work with gap indexing.
b4c59b4Nathan Vegdahl
helix-core/src/movement.rs+3−3
@@ -56,7 +56,7 @@ pub fn move_horizontally(
56 };
56 };
57
57
58 // Compute the final new range.
58 // Compute the final new range.
59− range.put(slice, behaviour == Extend, new_pos)
59+ range.put(slice, new_pos, behaviour == Extend)
60}
60}
61
61
62pub fn move_vertically(
62pub fn move_vertically(
@@ -106,7 +106,7 @@ pub fn move_vertically(
106 new_pos
106 new_pos
107 };
107 };
108
108
109− let mut new_range = range.put(slice, true, new_head);
109+ let mut new_range = range.put(slice, new_head, true);
110 new_range.horiz = Some(horiz);
110 new_range.horiz = Some(horiz);
111 new_range
111 new_range
112 }
112 }
@@ -427,7 +427,7 @@ mod test {
427 #[test]
427 #[test]
428 fn vertical_moves_in_single_column() {
428 fn vertical_moves_in_single_column() {
429 let text = Rope::from(MULTILINE_SAMPLE);
429 let text = Rope::from(MULTILINE_SAMPLE);
430− let slice = dbg!(&text).slice(..);
430+ let slice = text.slice(..);
431 let position = pos_at_coords(slice, (0, 0).into());
431 let position = pos_at_coords(slice, (0, 0).into());
432 let mut range = Range::point(position);
432 let mut range = Range::point(position);
433 let moves_and_expected_coordinates = IntoIter::new([
433 let moves_and_expected_coordinates = IntoIter::new([
helix-core/src/search.rs+7−5
@@ -7,12 +7,11 @@ pub fn find_nth_next(
7 n: usize,
7 n: usize,
8 inclusive: bool,
8 inclusive: bool,
9) -> Option<usize> {
9) -> Option<usize> {
10− if pos >= text.len_chars() {
10+ if pos >= text.len_chars() || n == 0 {
11 return None;
11 return None;
12 }
12 }
13
13
14− // start searching right after pos
15− let mut chars = text.chars_at(pos + 1);
14+ let mut chars = text.chars_at(pos);
16
15
17 for _ in 0..n {
16 for _ in 0..n {
18 loop {
17 loop {
@@ -40,14 +39,17 @@ pub fn find_nth_prev(
40 n: usize,
39 n: usize,
41 inclusive: bool,
40 inclusive: bool,
42) -> Option<usize> {
41) -> Option<usize> {
43− // start searching right before pos
42+ if pos == 0 || n == 0 {
43+ return None;
44+ }
45+
44 let mut chars = text.chars_at(pos);
46 let mut chars = text.chars_at(pos);
45
47
46 for _ in 0..n {
48 for _ in 0..n {
47 loop {
49 loop {
48 let c = chars.prev()?;
50 let c = chars.prev()?;
49
51
50− pos = pos.saturating_sub(1);
52+ pos -= 1;
51
53
52 if c == ch {
54 if c == ch {
53 break;
55 break;
helix-core/src/selection.rs+1−1
@@ -217,7 +217,7 @@ impl Range {
217 /// grapheme-aligned.
217 /// grapheme-aligned.
218 #[must_use]
218 #[must_use]
219 #[inline]
219 #[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 {
221 let anchor = if !extend {
221 let anchor = if !extend {
222 char_idx
222 char_idx
223 } else if self.head >= self.anchor && char_idx < self.anchor {
223 } 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;
1use crate::{search, Selection};
2use crate::{search, Selection};
2use ropey::RopeSlice;
3use ropey::RopeSlice;
3
4
@@ -40,23 +41,35 @@ pub fn find_nth_pairs_pos(
40) -> Option<(usize, usize)> {
41) -> Option<(usize, usize)> {
41 let (open, close) = get_pair(ch);
42 let (open, close) = get_pair(ch);
42
43
43− let (open_pos, close_pos) = if open == close {
44+ if text.len_chars() < 2 || pos >= text.len_chars() {
44− let prev = search::find_nth_prev(text, open, pos, n, true);
45+ return None;
45− let next = search::find_nth_next(text, close, pos, n, true);
46+ }
46− if text.char(pos) == open {
47+
47− // cursor is *on* a pair
48+ if open == close {
48− next.map(|n| (pos, n)).or_else(|| prev.map(|p| (p, pos)))?
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+ }
49 } else {
61 } 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+ ))
51 }
66 }
52 } else {
67 } else {
53− (
68+ Some((
54 find_nth_open_pair(text, open, close, pos, n)?,
69 find_nth_open_pair(text, open, close, pos, n)?,
55− find_nth_close_pair(text, open, close, pos, n)?,
70+ next_grapheme_boundary(text, find_nth_close_pair(text, open, close, pos, n)?),
56− )
71+ ))
57− };
72+ }
58−
59− Some((open_pos, close_pos))
60}
73}
61
74
62fn find_nth_open_pair(
75fn find_nth_open_pair(
@@ -173,12 +186,13 @@ mod test {
173 let slice = doc.slice(..);
186 let slice = doc.slice(..);
174
187
175 // cursor on [t]ext
188 // cursor on [t]ext
176− 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)));
177− assert_eq!(find_nth_pairs_pos(slice, ')', 6, 1), Some((5, 10)));
190+ assert_eq!(find_nth_pairs_pos(slice, ')', 6, 1), Some((5, 11)));
178 // cursor on so[m]e
191 // cursor on so[m]e
179 assert_eq!(find_nth_pairs_pos(slice, '(', 2, 1), None);
192 assert_eq!(find_nth_pairs_pos(slice, '(', 2, 1), None);
180 // cursor on bracket itself
193 // 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)));
182 }
196 }
183
197
184 #[test]
198 #[test]
@@ -187,9 +201,9 @@ mod test {
187 let slice = doc.slice(..);
201 let slice = doc.slice(..);
188
202
189 // cursor on go[o]d
203 // cursor on go[o]d
190− assert_eq!(find_nth_pairs_pos(slice, '(', 13, 1), Some((10, 15)));
204+ assert_eq!(find_nth_pairs_pos(slice, '(', 13, 1), Some((10, 16)));
191− assert_eq!(find_nth_pairs_pos(slice, '(', 13, 2), Some((4, 21)));
205+ assert_eq!(find_nth_pairs_pos(slice, '(', 13, 2), Some((4, 22)));
192− assert_eq!(find_nth_pairs_pos(slice, '(', 13, 3), Some((0, 27)));
206+ assert_eq!(find_nth_pairs_pos(slice, '(', 13, 3), Some((0, 28)));
193 }
207 }
194
208
195 #[test]
209 #[test]
@@ -198,14 +212,14 @@ mod test {
198 let slice = doc.slice(..);
212 let slice = doc.slice(..);
199
213
200 // cursor on go[o]d
214 // cursor on go[o]d
201− assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 1), Some((10, 15)));
215+ assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 1), Some((10, 16)));
202− assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 2), Some((4, 21)));
216+ assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 2), Some((4, 22)));
203− assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 3), Some((0, 27)));
217+ assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 3), Some((0, 28)));
204 // cursor on the quotes
218 // 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);
206 // this is the best we can do since opening and closing pairs are same
220 // 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)));
221+ assert_eq!(find_nth_pairs_pos(slice, '\'', 0, 1), Some((0, 5)));
208− assert_eq!(find_nth_pairs_pos(slice, '\'', 27, 1), Some((21, 27)));
222+ assert_eq!(find_nth_pairs_pos(slice, '\'', 27, 1), Some((21, 28)));
209 }
223 }
210
224
211 #[test]
225 #[test]
@@ -214,8 +228,8 @@ mod test {
214 let slice = doc.slice(..);
228 let slice = doc.slice(..);
215
229
216 // cursor on go[o]d
230 // cursor on go[o]d
217− assert_eq!(find_nth_pairs_pos(slice, '(', 15, 1), Some((5, 24)));
231+ assert_eq!(find_nth_pairs_pos(slice, '(', 15, 1), Some((5, 25)));
218− assert_eq!(find_nth_pairs_pos(slice, '(', 15, 2), Some((0, 31)));
232+ assert_eq!(find_nth_pairs_pos(slice, '(', 15, 2), Some((0, 32)));
219 }
233 }
220
234
221 #[test]
235 #[test]
@@ -224,9 +238,9 @@ mod test {
224 let slice = doc.slice(..);
238 let slice = doc.slice(..);
225
239
226 // cursor on go[o]d
240 // cursor on go[o]d
227− assert_eq!(find_nth_pairs_pos(slice, '{', 13, 1), Some((10, 15)));
241+ assert_eq!(find_nth_pairs_pos(slice, '{', 13, 1), Some((10, 16)));
228− assert_eq!(find_nth_pairs_pos(slice, '[', 13, 1), Some((4, 21)));
242+ assert_eq!(find_nth_pairs_pos(slice, '[', 13, 1), Some((4, 22)));
229− assert_eq!(find_nth_pairs_pos(slice, '(', 13, 1), Some((0, 27)));
243+ assert_eq!(find_nth_pairs_pos(slice, '(', 13, 1), Some((0, 28)));
230 }
244 }
231
245
232 #[test]
246 #[test]
@@ -243,7 +257,7 @@ mod test {
243 get_surround_pos(slice, &selection, '(', 1)
257 get_surround_pos(slice, &selection, '(', 1)
244 .unwrap()
258 .unwrap()
245 .as_slice(),
259 .as_slice(),
246− &[0, 5, 7, 13, 15, 23]
260+ &[0, 6, 7, 14, 15, 24]
247 );
261 );
248 }
262 }
249
263
helix-core/src/textobject.rs+134−133
@@ -1,21 +1,16 @@
1use ropey::RopeSlice;
1use ropey::RopeSlice;
2
2
3−use crate::chars::{categorize_char, char_is_line_ending, char_is_whitespace, CharCategory};
3+use crate::chars::{categorize_char, CharCategory};
4+use crate::graphemes::{next_grapheme_boundary, prev_grapheme_boundary};
4−use crate::movement::{self, Direction};
5+use crate::movement::Direction;
5use crate::surround;
6use crate::surround;
6use crate::Range;
7use crate::Range;
7
8
8−fn this_word_end_pos(slice: RopeSlice, pos: usize) -> usize {
9+fn find_word_boundary(slice: RopeSlice, mut pos: usize, direction: Direction) -> usize {
9− this_word_bound_pos(slice, pos, Direction::Forward)
10+ use CharCategory::{Eol, Whitespace};
10−}
11−
12−fn this_word_start_pos(slice: RopeSlice, pos: usize) -> usize {
13− this_word_bound_pos(slice, pos, Direction::Backward)
14−}
15
11
16−fn this_word_bound_pos(slice: RopeSlice, mut pos: usize, direction: Direction) -> usize {
17 let iter = match direction {
12 let iter = match direction {
18− Direction::Forward => slice.chars_at(pos + 1),
13+ Direction::Forward => slice.chars_at(pos),
19 Direction::Backward => {
14 Direction::Backward => {
20 let mut iter = slice.chars_at(pos);
15 let mut iter = slice.chars_at(pos);
21 iter.reverse();
16 iter.reverse();
@@ -23,25 +18,32 @@ fn this_word_bound_pos(slice: RopeSlice, mut pos: usize, direction: Direction) -
23 }
18 }
24 };
19 };
25
20
26− match categorize_char(slice.char(pos)) {
21+ let mut prev_category = match direction {
27− CharCategory::Eol | CharCategory::Whitespace => pos,
22+ Direction::Forward if pos == 0 => Whitespace,
28− category => {
23+ Direction::Forward => categorize_char(slice.char(pos - 1)),
29− for peek in iter {
24+ Direction::Backward if pos == slice.len_chars() => Whitespace,
30− let curr_category = categorize_char(peek);
25+ Direction::Backward => categorize_char(slice.char(pos)),
31− if curr_category != category
26+ };
32− || curr_category == CharCategory::Eol
27+
33− || curr_category == CharCategory::Whitespace
28+ for ch in iter {
34− {
29+ match categorize_char(ch) {
30+ Eol | Whitespace => return pos,
31+ category => {
32+ if category != prev_category && pos != 0 && pos != slice.len_chars() {
35 return pos;
33 return pos;
36− }
34+ } else {
37− pos = match direction {
38− Direction::Forward => pos + 1,
35+ if direction == Direction::Forward {
36+ pos += 1;
37+ } else {
39− Direction::Backward => pos.saturating_sub(1),
38+ pos = pos.saturating_sub(1);
39+ }
40+ prev_category = category;
40 }
41 }
41 }
42 }
42− pos
43 }
43 }
44 }
44 }
45+
46+ pos
45}
47}
46
48
47#[derive(Copy, Clone, PartialEq, Eq, Debug)]
49#[derive(Copy, Clone, PartialEq, Eq, Debug)]
@@ -55,46 +57,42 @@ pub fn textobject_word(
55 slice: RopeSlice,
57 slice: RopeSlice,
56 range: Range,
58 range: Range,
57 textobject: TextObject,
59 textobject: TextObject,
58− count: usize,
60+ _count: usize,
59) -> Range {
61) -> Range {
60− let this_word_start = this_word_start_pos(slice, range.head);
62+ // For 1-width cursor semantics.
61− let this_word_end = this_word_end_pos(slice, range.head);
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+ }
62
79
63− let (anchor, head);
64 match textobject {
80 match textobject {
65− TextObject::Inside => {
81+ TextObject::Inside => Range::new(word_start, word_end),
66− anchor = this_word_start;
67− head = this_word_end;
68− }
69− TextObject::Around => {
82+ TextObject::Around => Range::new(
70− if slice
83+ match slice
71− .get_char(this_word_end + 1)
84+ .get_char(word_start.saturating_sub(1))
72− .map_or(true, char_is_line_ending)
85+ .map(categorize_char)
73 {
86 {
74− head = this_word_end;
87+ None | Some(CharCategory::Eol) => word_start,
75− if slice
88+ _ => prev_grapheme_boundary(slice, word_start),
76− .get_char(this_word_start.saturating_sub(1))
89+ },
77− .map_or(true, char_is_line_ending)
90+ match slice.get_char(word_end).map(categorize_char) {
78− {
91+ None | Some(CharCategory::Eol) => word_end,
79− // single word on a line
92+ _ => next_grapheme_boundary(slice, word_end),
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− }
93+ },
95− }
94+ ),
96− };
95+ }
97− Range::new(anchor, head)
98}
96}
99
97
100pub fn textobject_surround(
98pub fn textobject_surround(
@@ -106,7 +104,10 @@ pub fn textobject_surround(
106) -> Range {
104) -> Range {
107 surround::find_nth_pairs_pos(slice, ch, range.head, count)
105 surround::find_nth_pairs_pos(slice, ch, range.head, count)
108 .map(|(anchor, head)| match textobject {
106 .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+ ),
110 TextObject::Around => Range::new(anchor, head),
111 TextObject::Around => Range::new(anchor, head),
111 })
112 })
112 .unwrap_or(range)
113 .unwrap_or(range)
@@ -126,70 +127,70 @@ mod test {
126 let tests = &[
127 let tests = &[
127 (
128 (
128 "cursor at beginning of doc",
129 "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))],
130 ),
131 ),
131 (
132 (
132 "cursor at middle of word",
133 "cursor at middle of word",
133 vec![
134 vec![
134− (13, Inside, (10, 15)),
135+ (13, Inside, (10, 16)),
135− (10, Inside, (10, 15)),
136+ (10, Inside, (10, 16)),
136− (15, Inside, (10, 15)),
137+ (15, Inside, (10, 16)),
137− (13, Around, (10, 16)),
138+ (13, Around, (9, 17)),
138− (10, Around, (10, 16)),
139+ (10, Around, (9, 17)),
139− (15, Around, (10, 16)),
140+ (15, Around, (9, 17)),
140 ],
141 ],
141 ),
142 ),
142 (
143 (
143 "cursor between word whitespace",
144 "cursor between word whitespace",
144− vec![(6, Inside, (6, 6)), (6, Around, (6, 13))],
145+ vec![(6, Inside, (6, 6)), (6, Around, (6, 6))],
145 ),
146 ),
146 (
147 (
147 "cursor on word before newline\n",
148 "cursor on word before newline\n",
148 vec![
149 vec![
149− (22, Inside, (22, 28)),
150+ (22, Inside, (22, 29)),
150− (28, Inside, (22, 28)),
151+ (28, Inside, (22, 29)),
151− (25, Inside, (22, 28)),
152+ (25, Inside, (22, 29)),
152− (22, Around, (21, 28)),
153+ (22, Around, (21, 29)),
153− (28, Around, (21, 28)),
154+ (28, Around, (21, 29)),
154− (25, Around, (21, 28)),
155+ (25, Around, (21, 29)),
155 ],
156 ],
156 ),
157 ),
157 (
158 (
158 "cursor on newline\nnext line",
159 "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))],
160 ),
161 ),
161 (
162 (
162 "cursor on word after newline\nnext line",
163 "cursor on word after newline\nnext line",
163 vec![
164 vec![
164− (29, Inside, (29, 32)),
165+ (29, Inside, (29, 33)),
165− (30, Inside, (29, 32)),
166+ (30, Inside, (29, 33)),
166− (32, Inside, (29, 32)),
167+ (32, Inside, (29, 33)),
167− (29, Around, (29, 33)),
168+ (29, Around, (29, 34)),
168− (30, Around, (29, 33)),
169+ (30, Around, (29, 34)),
169− (32, Around, (29, 33)),
170+ (32, Around, (29, 34)),
170 ],
171 ],
171 ),
172 ),
172 (
173 (
173 "cursor on #$%:;* punctuation",
174 "cursor on #$%:;* punctuation",
174 vec![
175 vec![
175− (13, Inside, (10, 15)),
176+ (13, Inside, (10, 16)),
176− (10, Inside, (10, 15)),
177+ (10, Inside, (10, 16)),
177− (15, Inside, (10, 15)),
178+ (15, Inside, (10, 16)),
178− (13, Around, (10, 16)),
179+ (13, Around, (9, 17)),
179− (10, Around, (10, 16)),
180+ (10, Around, (9, 17)),
180− (15, Around, (10, 16)),
181+ (15, Around, (9, 17)),
181 ],
182 ],
182 ),
183 ),
183 (
184 (
184 "cursor on punc%^#$:;.tuation",
185 "cursor on punc%^#$:;.tuation",
185 vec![
186 vec![
186− (14, Inside, (14, 20)),
187+ (14, Inside, (14, 21)),
187− (20, Inside, (14, 20)),
188+ (20, Inside, (14, 21)),
188− (17, Inside, (14, 20)),
189+ (17, Inside, (14, 21)),
189− (14, Around, (14, 20)),
190+ (14, Around, (13, 22)),
190 // FIXME: edge case
191 // FIXME: edge case
191 // (20, Around, (14, 20)),
192 // (20, Around, (14, 20)),
192− (17, Around, (14, 20)),
193+ (17, Around, (13, 22)),
193 ],
194 ],
194 ),
195 ),
195 (
196 (
@@ -198,14 +199,14 @@ mod test {
198 (9, Inside, (9, 9)),
199 (9, Inside, (9, 9)),
199 (10, Inside, (10, 10)),
200 (10, Inside, (10, 10)),
200 (11, Inside, (11, 11)),
201 (11, Inside, (11, 11)),
201− (9, Around, (9, 16)),
202+ (9, Around, (9, 9)),
202− (10, Around, (9, 16)),
203+ (10, Around, (10, 10)),
203− (11, Around, (9, 16)),
204+ (11, Around, (11, 11)),
204 ],
205 ],
205 ),
206 ),
206 (
207 (
207 "cursor at end of doc",
208 "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))],
209 ),
210 ),
210 ];
211 ];
211
212
@@ -234,67 +235,67 @@ mod test {
234 "simple (single) surround pairs",
235 "simple (single) surround pairs",
235 vec![
236 vec![
236 (3, Inside, (3, 3), '(', 1),
237 (3, Inside, (3, 3), '(', 1),
237− (7, Inside, (8, 13), ')', 1),
238+ (7, Inside, (8, 14), ')', 1),
238− (10, Inside, (8, 13), '(', 1),
239+ (10, Inside, (8, 14), '(', 1),
239− (14, Inside, (8, 13), ')', 1),
240+ (14, Inside, (8, 14), ')', 1),
240 (3, Around, (3, 3), '(', 1),
241 (3, Around, (3, 3), '(', 1),
241− (7, Around, (7, 14), ')', 1),
242+ (7, Around, (7, 15), ')', 1),
242− (10, Around, (7, 14), '(', 1),
243+ (10, Around, (7, 15), '(', 1),
243− (14, Around, (7, 14), ')', 1),
244+ (14, Around, (7, 15), ')', 1),
244 ],
245 ],
245 ),
246 ),
246 (
247 (
247 "samexx 'single' surround pairs",
248 "samexx 'single' surround pairs",
248 vec![
249 vec![
249 (3, Inside, (3, 3), '\'', 1),
250 (3, Inside, (3, 3), '\'', 1),
250− (7, Inside, (8, 13), '\'', 1),
251+ (7, Inside, (7, 7), '\'', 1),
251− (10, Inside, (8, 13), '\'', 1),
252+ (10, Inside, (8, 14), '\'', 1),
252− (14, Inside, (8, 13), '\'', 1),
253+ (14, Inside, (14, 14), '\'', 1),
253 (3, Around, (3, 3), '\'', 1),
254 (3, Around, (3, 3), '\'', 1),
254− (7, Around, (7, 14), '\'', 1),
255+ (7, Around, (7, 7), '\'', 1),
255− (10, Around, (7, 14), '\'', 1),
256+ (10, Around, (7, 15), '\'', 1),
256− (14, Around, (7, 14), '\'', 1),
257+ (14, Around, (14, 14), '\'', 1),
257 ],
258 ],
258 ),
259 ),
259 (
260 (
260 "(nested (surround (pairs)) 3 levels)",
261 "(nested (surround (pairs)) 3 levels)",
261 vec![
262 vec![
262− (0, Inside, (1, 34), '(', 1),
263+ (0, Inside, (1, 35), '(', 1),
263− (6, Inside, (1, 34), ')', 1),
264+ (6, Inside, (1, 35), ')', 1),
264− (8, Inside, (9, 24), '(', 1),
265+ (8, Inside, (9, 25), '(', 1),
265− (8, Inside, (9, 34), ')', 2),
266+ (8, Inside, (9, 35), ')', 2),
266− (20, Inside, (9, 24), '(', 2),
267+ (20, Inside, (9, 25), '(', 2),
267− (20, Inside, (1, 34), ')', 3),
268+ (20, Inside, (1, 35), ')', 3),
268− (0, Around, (0, 35), '(', 1),
269+ (0, Around, (0, 36), '(', 1),
269− (6, Around, (0, 35), ')', 1),
270+ (6, Around, (0, 36), ')', 1),
270− (8, Around, (8, 25), '(', 1),
271+ (8, Around, (8, 26), '(', 1),
271− (8, Around, (8, 35), ')', 2),
272+ (8, Around, (8, 36), ')', 2),
272− (20, Around, (8, 25), '(', 2),
273+ (20, Around, (8, 26), '(', 2),
273− (20, Around, (0, 35), ')', 3),
274+ (20, Around, (0, 36), ')', 3),
274 ],
275 ],
275 ),
276 ),
276 (
277 (
277 "(mixed {surround [pair] same} line)",
278 "(mixed {surround [pair] same} line)",
278 vec![
279 vec![
279− (2, Inside, (1, 33), '(', 1),
280+ (2, Inside, (1, 34), '(', 1),
280− (9, Inside, (8, 27), '{', 1),
281+ (9, Inside, (8, 28), '{', 1),
281− (18, Inside, (18, 21), '[', 1),
282+ (18, Inside, (18, 22), '[', 1),
282− (2, Around, (0, 34), '(', 1),
283+ (2, Around, (0, 35), '(', 1),
283− (9, Around, (7, 28), '{', 1),
284+ (9, Around, (7, 29), '{', 1),
284− (18, Around, (17, 22), '[', 1),
285+ (18, Around, (17, 23), '[', 1),
285 ],
286 ],
286 ),
287 ),
287 (
288 (
288 "(stepped (surround) pairs (should) skip)",
289 "(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)],
290 ),
291 ),
291 (
292 (
292 "[surround pairs{\non different]\nlines}",
293 "[surround pairs{\non different]\nlines}",
293 vec![
294 vec![
294− (7, Inside, (1, 28), '[', 1),
295+ (7, Inside, (1, 29), '[', 1),
295− (15, Inside, (16, 35), '{', 1),
296+ (15, Inside, (16, 36), '{', 1),
296− (7, Around, (0, 29), '[', 1),
297+ (7, Around, (0, 30), '[', 1),
297− (15, Around, (15, 36), '{', 1),
298+ (15, Around, (15, 37), '{', 1),
298 ],
299 ],
299 ),
300 ),
300 ];
301 ];
helix-term/src/commands.rs+44−66
@@ -1,9 +1,6 @@
1use helix_core::{
1use helix_core::{
2 comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes, indent,
2 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,
3+ line_ending::{get_line_ending_of_str, line_end_char_index, str_is_line_ending},
5− str_is_line_ending,
6− },
7 match_brackets,
4 match_brackets,
8 movement::{self, Direction},
5 movement::{self, Direction},
9 object, pos_at_coords,
6 object, pos_at_coords,
@@ -392,20 +389,14 @@ fn goto_line_end(cx: &mut Context) {
392 doc.set_selection(
389 doc.set_selection(
393 view.id,
390 view.id,
394 doc.selection(view.id).clone().transform(|range| {
391 doc.selection(view.id).clone().transform(|range| {
395− let text = doc.text();
392+ let text = doc.text().slice(..);
396 let line = text.char_to_line(range.head);
393 let line = text.char_to_line(range.head);
397
394
398− let pos = line_end_char_index(&text.slice(..), line);
395+ let pos = line_end_char_index(&text, line);
399− let pos = graphemes::nth_prev_grapheme_boundary(text.slice(..), pos, 1);
396+ let pos = graphemes::nth_prev_grapheme_boundary(text, pos, 1);
400 let pos = range.head.max(pos).max(text.line_to_char(line));
397 let pos = range.head.max(pos).max(text.line_to_char(line));
401
398
402− Range::new(
399+ range.put(text, pos, doc.mode == Mode::Select)
403− match doc.mode {
404− Mode::Normal | Mode::Insert => pos,
405− Mode::Select => range.anchor,
406− },
407− pos,
408− )
409 }),
400 }),
410 );
401 );
411}
402}
@@ -416,11 +407,11 @@ fn goto_line_end_newline(cx: &mut Context) {
416 doc.set_selection(
407 doc.set_selection(
417 view.id,
408 view.id,
418 doc.selection(view.id).clone().transform(|range| {
409 doc.selection(view.id).clone().transform(|range| {
419− let text = doc.text();
410+ let text = doc.text().slice(..);
420 let line = text.char_to_line(range.head);
411 let line = text.char_to_line(range.head);
421
412
422− let pos = line_end_char_index(&text.slice(..), line);
413+ let pos = line_end_char_index(&text, line);
423− Range::new(pos, pos)
414+ range.put(text, pos, doc.mode == Mode::Select)
424 }),
415 }),
425 );
416 );
426}
417}
@@ -430,18 +421,12 @@ fn goto_line_start(cx: &mut Context) {
430 doc.set_selection(
421 doc.set_selection(
431 view.id,
422 view.id,
432 doc.selection(view.id).clone().transform(|range| {
423 doc.selection(view.id).clone().transform(|range| {
433− let text = doc.text();
424+ let text = doc.text().slice(..);
434 let line = text.char_to_line(range.head);
425 let line = text.char_to_line(range.head);
435
426
436 // adjust to start of the line
427 // adjust to start of the line
437 let pos = text.line_to_char(line);
428 let pos = text.line_to_char(line);
438− Range::new(
429+ range.put(text, pos, doc.mode == Mode::Select)
439− match doc.mode {
440− Mode::Normal | Mode::Insert => pos,
441− Mode::Select => range.anchor,
442− },
443− pos,
444− )
445 }),
430 }),
446 );
431 );
447}
432}
@@ -451,18 +436,12 @@ fn goto_first_nonwhitespace(cx: &mut Context) {
451 doc.set_selection(
436 doc.set_selection(
452 view.id,
437 view.id,
453 doc.selection(view.id).clone().transform(|range| {
438 doc.selection(view.id).clone().transform(|range| {
454− let text = doc.text();
439+ let text = doc.text().slice(..);
455 let line_idx = text.char_to_line(range.head);
440 let line_idx = text.char_to_line(range.head);
456
441
457 if let Some(pos) = find_first_non_whitespace_char(text.line(line_idx)) {
442 if let Some(pos) = find_first_non_whitespace_char(text.line(line_idx)) {
458 let pos = pos + text.line_to_char(line_idx);
443 let pos = pos + text.line_to_char(line_idx);
459− Range::new(
444+ range.put(text, pos, doc.mode == Mode::Select)
460− match doc.mode {
461− Mode::Normal | Mode::Insert => pos,
462− Mode::Select => range.anchor,
463− },
464− pos,
465− )
466 } else {
445 } else {
467 range
446 range
468 }
447 }
@@ -581,9 +560,7 @@ fn goto_file_start(cx: &mut Context) {
581fn goto_file_end(cx: &mut Context) {
560fn goto_file_end(cx: &mut Context) {
582 push_jump(cx.editor);
561 push_jump(cx.editor);
583 let (view, doc) = current!(cx.editor);
562 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()));
587}
564}
588
565
589fn extend_next_word_start(cx: &mut Context) {
566fn extend_next_word_start(cx: &mut Context) {
@@ -592,9 +569,10 @@ fn extend_next_word_start(cx: &mut Context) {
592 doc.set_selection(
569 doc.set_selection(
593 view.id,
570 view.id,
594 doc.selection(view.id).clone().transform(|range| {
571 doc.selection(view.id).clone().transform(|range| {
572+ let text = doc.text().slice(..);
595− let word = movement::move_next_word_start(doc.text().slice(..), range, count);
573+ let word = movement::move_next_word_start(text, range, count);
596 let pos = word.head;
574 let pos = word.head;
597− Range::new(range.anchor, pos)
575+ range.put(text, pos, true)
598 }),
576 }),
599 );
577 );
600}
578}
@@ -605,9 +583,10 @@ fn extend_prev_word_start(cx: &mut Context) {
605 doc.set_selection(
583 doc.set_selection(
606 view.id,
584 view.id,
607 doc.selection(view.id).clone().transform(|range| {
585 doc.selection(view.id).clone().transform(|range| {
586+ let text = doc.text().slice(..);
608− let word = movement::move_prev_word_start(doc.text().slice(..), range, count);
587+ let word = movement::move_prev_word_start(text, range, count);
609 let pos = word.head;
588 let pos = word.head;
610− Range::new(range.anchor, pos)
589+ range.put(text, pos, true)
611 }),
590 }),
612 );
591 );
613}
592}
@@ -618,9 +597,10 @@ fn extend_next_word_end(cx: &mut Context) {
618 doc.set_selection(
597 doc.set_selection(
619 view.id,
598 view.id,
620 doc.selection(view.id).clone().transform(|range| {
599 doc.selection(view.id).clone().transform(|range| {
600+ let text = doc.text().slice(..);
621− let word = movement::move_next_word_end(doc.text().slice(..), range, count);
601+ let word = movement::move_next_word_end(text, range, count);
622 let pos = word.head;
602 let pos = word.head;
623− Range::new(range.anchor, pos)
603+ range.put(text, pos, true)
624 }),
604 }),
625 );
605 );
626}
606}
@@ -669,18 +649,9 @@ where
669 doc.set_selection(
649 doc.set_selection(
670 view.id,
650 view.id,
671 doc.selection(view.id).clone().transform(|range| {
651 doc.selection(view.id).clone().transform(|range| {
652+ let text = doc.text().slice(..);
672− search_fn(doc.text().slice(..), ch, range.head, count, inclusive).map_or(
653+ search_fn(text, ch, range.head, count, inclusive)
673− range,
654+ .map_or(range, |pos| range.put(text, pos, extend))
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− )
684 }),
655 }),
685 );
656 );
686 })
657 })
@@ -940,7 +911,7 @@ fn extend_line_down(cx: &mut Context) {
940fn select_all(cx: &mut Context) {
911fn select_all(cx: &mut Context) {
941 let (view, doc) = current!(cx.editor);
912 let (view, doc) = current!(cx.editor);
942
913
943− let end = rope_end_without_line_ending(&doc.text().slice(..));
914+ let end = doc.text().len_chars();
944 doc.set_selection(view.id, Selection::single(0, end))
915 doc.set_selection(view.id, Selection::single(0, end))
945}
916}
946
917
@@ -997,12 +968,10 @@ fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Rege
997 return;
968 return;
998 }
969 }
999
970
1000− let head = end - 1;
1001−
1002 let selection = if extend {
971 let selection = if extend {
1003− selection.clone().push(Range::new(start, head))
972+ selection.clone().push(Range::new(start, end))
1004 } else {
973 } else {
1005− Selection::single(start, head)
974+ Selection::single(start, end)
1006 };
975 };
1007
976
1008 doc.set_selection(view.id, selection);
977 doc.set_selection(view.id, selection);
@@ -1143,9 +1112,15 @@ fn collapse_selection(cx: &mut Context) {
1143
1112
1144 doc.set_selection(
1113 doc.set_selection(
1145 view.id,
1114 view.id,
1146− doc.selection(view.id)
1115+ doc.selection(view.id).clone().transform(|range| {
1147− .clone()
1116+ let pos = if range.head > range.anchor {
1148− .transform(|range| Range::new(range.head, range.head)),
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+ }),
1149 );
1124 );
1150}
1125}
1151
1126
@@ -1184,10 +1159,13 @@ fn append_mode(cx: &mut Context) {
1184 doc.restore_cursor = true;
1159 doc.restore_cursor = true;
1185
1160
1186 let selection = doc.selection(view.id).clone().transform(|range| {
1161 let selection = doc.selection(view.id).clone().transform(|range| {
1187− Range::new(
1188− range.from(),
1162+ let to = if range.to() == range.from() {
1163+ // For 1-width cursor semantics.
1189− graphemes::next_grapheme_boundary(doc.text().slice(..), range.to()), // to() + next char
1164+ graphemes::next_grapheme_boundary(doc.text().slice(..), range.to())
1190− )
1165+ } else {
1166+ range.to()
1167+ };
1168+ Range::new(range.from(), to)
1191 });
1169 });
1192
1170
1193 let end = doc.text().len_chars();
1171 let end = doc.text().len_chars();