@@ -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 ];