@@ -61,99 +61,110 @@ pub fn move_vertically(
6161 range
6262}
6363
64−pub fn move_next_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
64+pub fn move_next_word_start(slice: RopeSlice, mut begin: usize, count: usize) -> Option<Range> {
65+ let mut end = begin;
66+
6567 for _ in 0..count {
66− if pos + 1 == slice.len_chars() {
67− return pos;
68+ if begin + 1 == slice.len_chars() {
69+ return None;
6870 }
6971
70− let mut ch = slice.char(pos);
71− let next = slice.char(pos + 1);
72+ let mut ch = slice.char(begin);
73+ let next = slice.char(begin + 1);
7274
7375 // if we're at the end of a word, or on whitespce right before new one
7476 if categorize(ch) != categorize(next) {
75− pos += 1;
76− ch = next;
77+ begin += 1;
7778 }
7879
80+ // return if not skip while?
81+ skip_over_next(slice, &mut begin, |ch| ch == '\n');
82+ ch = slice.char(begin);
83+
84+ end = begin + 1;
85+
7986 if is_word(ch) {
80− skip_over_next(slice, &mut pos, is_word);
87+ skip_over_next(slice, &mut end, is_word);
8188 } else if ch.is_ascii_punctuation() {
82− skip_over_next(slice, &mut pos, |ch| ch.is_ascii_punctuation());
89+ skip_over_next(slice, &mut end, |ch| ch.is_ascii_punctuation());
8390 }
8491
85− // TODO: don't include newline?
86− skip_over_next(slice, &mut pos, |ch| ch.is_ascii_whitespace());
92+ skip_over_next(slice, &mut end, is_horiz_blank);
8793 }
8894
89− pos
95+ Some(Range::new(begin, end - 1))
9096}
9197
92−pub fn move_prev_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
98+pub fn move_prev_word_start(slice: RopeSlice, mut begin: usize, count: usize) -> Option<Range> {
99+ let mut with_end = false;
100+ let mut end = begin;
101+
93102 for _ in 0..count {
94− if pos == 0 {
95− return pos;
103+ if begin == 0 {
104+ return None;
96105 }
97106
98− let ch = slice.char(pos);
99− let prev = slice.char(pos - 1);
107+ let ch = slice.char(begin);
108+ let prev = slice.char(begin - 1);
100109
101110 if categorize(ch) != categorize(prev) {
102− pos -= 1;
111+ begin -= 1;
103112 }
104113
105− // match (category c1, category c2) => {
106− // if c1 != c2 {
107− // }
108− // }
114+ // return if not skip while?
115+ skip_over_prev(slice, &mut begin, |ch| ch == '\n');
109116
110− // TODO: skip while eol
117+ end = begin;
111118
112− // TODO: don't include newline?
113− skip_over_prev(slice, &mut pos, |ch| ch.is_ascii_whitespace());
119+ with_end = skip_over_prev(slice, &mut end, is_horiz_blank);
114120
115121 // refetch
116− let ch = slice.char(pos);
122+ let ch = slice.char(end);
117123
118124 if is_word(ch) {
119− skip_over_prev(slice, &mut pos, is_word);
125+ with_end = skip_over_prev(slice, &mut end, is_word);
120126 } else if ch.is_ascii_punctuation() {
121− skip_over_prev(slice, &mut pos, |ch| ch.is_ascii_punctuation());
127+ with_end = skip_over_prev(slice, &mut end, |ch| ch.is_ascii_punctuation());
122128 }
123− pos = pos.saturating_add(1)
124129 }
125130
126− pos
131+ // we want to include begin
132+ Some(Range::new(begin + 1, if with_end { end } else { end + 1 }))
127133}
128134
129−pub fn move_next_word_end(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
135+pub fn move_next_word_end(slice: RopeSlice, mut begin: usize, count: usize) -> Option<Range> {
136+ let mut end = begin;
137+
130138 for _ in 0..count {
131− if pos + 1 == slice.len_chars() {
132− return pos;
139+ if begin + 1 == slice.len_chars() {
140+ return None;
133141 }
134142
135− let ch = slice.char(pos);
136− let next = slice.char(pos + 1);
143+ let ch = slice.char(begin);
144+ let next = slice.char(begin + 1);
137145
138146 if categorize(ch) != categorize(next) {
139− pos += 1;
147+ begin += 1;
140148 }
141149
142− // TODO: don't include newline?
143− skip_over_next(slice, &mut pos, |ch| ch.is_ascii_whitespace());
150+ // return if not skip while?
151+ skip_over_next(slice, &mut begin, |ch| ch == '\n');
152+
153+ end = begin;
154+
155+ skip_over_next(slice, &mut end, is_horiz_blank);
144156
145157 // refetch
146− let ch = slice.char(pos);
158+ let ch = slice.char(end);
147159
148160 if is_word(ch) {
149− skip_over_next(slice, &mut pos, is_word);
161+ skip_over_next(slice, &mut end, is_word);
150162 } else if ch.is_ascii_punctuation() {
151− skip_over_next(slice, &mut pos, |ch| ch.is_ascii_punctuation());
163+ skip_over_next(slice, &mut end, |ch| ch.is_ascii_punctuation());
152164 }
153− pos -= 1
154165 }
155166
156− pos
167+ Some(Range::new(begin, end - 1))
157168}
158169
159170// ---- util ------------
@@ -164,6 +175,10 @@ fn is_word(ch: char) -> bool {
164175 ch.is_alphanumeric() || ch == '_'
165176}
166177
178+fn is_horiz_blank(ch: char) -> bool {
179+ matches!(ch, ' ' | '\t')
180+}
181+
167182#[derive(Debug, Eq, PartialEq)]
168183enum Category {
169184 Whitespace,
@@ -201,7 +216,8 @@ where
201216}
202217
203218#[inline]
204−pub fn skip_over_prev<F>(slice: RopeSlice, pos: &mut usize, fun: F)
219+/// Returns true if the final pos matches the predicate.
220+pub fn skip_over_prev<F>(slice: RopeSlice, pos: &mut usize, fun: F) -> bool
205221where
206222 F: Fn(char) -> bool,
207223{
@@ -214,6 +230,7 @@ where
214230 }
215231 *pos = pos.saturating_sub(1);
216232 }
233+ return fun(slice.char(*pos));
217234}
218235
219236#[cfg(test)]