@@ -1,7 +1,5 @@
11use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
2−use crate::syntax::LOADER;
3−use crate::{ChangeSet, Diagnostic, Position, Range, Rope, RopeSlice, Selection, Syntax};
4−use anyhow::Error;
2+use crate::{coords_at_pos, pos_at_coords, ChangeSet, Position, Range, Rope, RopeSlice, Selection};
53
64/// A state represents the current editor state of a single buffer.
75#[derive(Clone)]
@@ -83,96 +81,6 @@ impl State {
8381 Range::new(if extend { range.anchor } else { pos }, pos)
8482 }
8583
86− pub fn move_next_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
87− for _ in 0..count {
88− if pos + 1 == slice.len_chars() {
89− return pos;
90− }
91−
92− let mut ch = slice.char(pos);
93− let next = slice.char(pos + 1);
94−
95− // if we're at the end of a word, or on whitespce right before new one
96− if categorize(ch) != categorize(next) {
97− pos += 1;
98− ch = next;
99− }
100−
101− if is_word(ch) {
102− skip_over_next(slice, &mut pos, is_word);
103− } else if ch.is_ascii_punctuation() {
104− skip_over_next(slice, &mut pos, |ch| ch.is_ascii_punctuation());
105− }
106−
107− // TODO: don't include newline?
108− skip_over_next(slice, &mut pos, |ch| ch.is_ascii_whitespace());
109− }
110−
111− pos
112− }
113−
114− pub fn move_prev_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
115− for _ in 0..count {
116− if pos == 0 {
117− return pos;
118− }
119−
120− let ch = slice.char(pos);
121− let prev = slice.char(pos - 1);
122−
123− if categorize(ch) != categorize(prev) {
124− pos -= 1;
125− }
126−
127− // TODO: skip while eol
128−
129− // TODO: don't include newline?
130− skip_over_prev(slice, &mut pos, |ch| ch.is_ascii_whitespace());
131−
132− // refetch
133− let ch = slice.char(pos);
134−
135− if is_word(ch) {
136− skip_over_prev(slice, &mut pos, is_word);
137− } else if ch.is_ascii_punctuation() {
138− skip_over_prev(slice, &mut pos, |ch| ch.is_ascii_punctuation());
139− }
140− pos = pos.saturating_add(1)
141− }
142−
143− pos
144− }
145−
146− pub fn move_next_word_end(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
147− for _ in 0..count {
148− if pos + 1 == slice.len_chars() {
149− return pos;
150− }
151−
152− let ch = slice.char(pos);
153− let next = slice.char(pos + 1);
154−
155− if categorize(ch) != categorize(next) {
156− pos += 1;
157− }
158−
159− // TODO: don't include newline?
160− skip_over_next(slice, &mut pos, |ch| ch.is_ascii_whitespace());
161−
162− // refetch
163− let ch = slice.char(pos);
164−
165− if is_word(ch) {
166− skip_over_next(slice, &mut pos, is_word);
167− } else if ch.is_ascii_punctuation() {
168− skip_over_next(slice, &mut pos, |ch| ch.is_ascii_punctuation());
169− }
170− pos -= 1
171− }
172−
173− pos
174− }
175−
17684 pub fn move_selection(
17785 &self,
17886 dir: Direction,
@@ -194,22 +102,6 @@ impl State {
194102 }
195103}
196104
197−/// Convert a character index to (line, column) coordinates.
198−pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position {
199− let line = text.char_to_line(pos);
200− let line_start = text.line_to_char(line);
201− let col = RopeGraphemes::new(text.slice(line_start..pos)).count();
202− Position::new(line, col)
203−}
204−
205−/// Convert (line, column) coordinates to a character index.
206−pub fn pos_at_coords(text: RopeSlice, coords: Position) -> usize {
207− let Position { row, col } = coords;
208− let line_start = text.line_to_char(row);
209− // line_start + col
210− nth_next_grapheme_boundary(text, line_start, col)
211−}
212−
213105fn move_vertically(
214106 text: RopeSlice,
215107 dir: Direction,
@@ -238,122 +130,10 @@ fn move_vertically(
238130 range
239131}
240132
241−// used for by-word movement
242−
243−fn is_word(ch: char) -> bool {
244− ch.is_alphanumeric() || ch == '_'
245−}
246−
247−#[derive(Debug, Eq, PartialEq)]
248−enum Category {
249− Whitespace,
250− Eol,
251− Word,
252− Punctuation,
253−}
254−fn categorize(ch: char) -> Category {
255− if ch == '\n' {
256− Category::Eol
257− } else if ch.is_ascii_whitespace() {
258− Category::Whitespace
259− } else if ch.is_ascii_punctuation() {
260− Category::Punctuation
261− } else if ch.is_ascii_alphanumeric() {
262− Category::Word
263− } else {
264− unreachable!()
265− }
266−}
267−
268−#[inline]
269−pub fn skip_over_next<F>(slice: RopeSlice, pos: &mut usize, fun: F)
270−where
271− F: Fn(char) -> bool,
272−{
273− let mut chars = slice.chars_at(*pos);
274−
275− for ch in chars {
276− if !fun(ch) {
277− break;
278− }
279− *pos += 1;
280− }
281−}
282−
283−#[inline]
284−pub fn skip_over_prev<F>(slice: RopeSlice, pos: &mut usize, fun: F)
285−where
286− F: Fn(char) -> bool,
287−{
288− // need to +1 so that prev() includes current char
289− let mut chars = slice.chars_at(*pos + 1);
290−
291− while let Some(ch) = chars.prev() {
292− if !fun(ch) {
293− break;
294− }
295− *pos = pos.saturating_sub(1);
296− }
297−}
298−
299133#[cfg(test)]
300134mod test {
301135 use super::*;
302136
303− #[test]
304− fn test_coords_at_pos() {
305− let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
306− let slice = text.slice(..);
307− // assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
308− // assert_eq!(coords_at_pos(slice, 5), (0, 5).into()); // position on \n
309− // assert_eq!(coords_at_pos(slice, 6), (1, 0).into()); // position on w
310− // assert_eq!(coords_at_pos(slice, 7), (1, 1).into()); // position on o
311− // assert_eq!(coords_at_pos(slice, 10), (1, 4).into()); // position on d
312−
313− // test with grapheme clusters
314− let text = Rope::from("a̐éö̲\r\n");
315− let slice = text.slice(..);
316− assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
317− assert_eq!(coords_at_pos(slice, 2), (0, 1).into());
318− assert_eq!(coords_at_pos(slice, 4), (0, 2).into());
319− assert_eq!(coords_at_pos(slice, 7), (0, 3).into());
320−
321− let text = Rope::from("किमपि");
322− let slice = text.slice(..);
323− assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
324− assert_eq!(coords_at_pos(slice, 2), (0, 1).into());
325− assert_eq!(coords_at_pos(slice, 3), (0, 2).into());
326− assert_eq!(coords_at_pos(slice, 5), (0, 3).into());
327− }
328−
329− #[test]
330− fn test_pos_at_coords() {
331− let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
332− let slice = text.slice(..);
333− assert_eq!(pos_at_coords(slice, (0, 0).into()), 0);
334− assert_eq!(pos_at_coords(slice, (0, 5).into()), 5); // position on \n
335− assert_eq!(pos_at_coords(slice, (1, 0).into()), 6); // position on w
336− assert_eq!(pos_at_coords(slice, (1, 1).into()), 7); // position on o
337− assert_eq!(pos_at_coords(slice, (1, 4).into()), 10); // position on d
338−
339− // test with grapheme clusters
340− let text = Rope::from("a̐éö̲\r\n");
341− let slice = text.slice(..);
342− assert_eq!(pos_at_coords(slice, (0, 0).into()), 0);
343− assert_eq!(pos_at_coords(slice, (0, 1).into()), 2);
344− assert_eq!(pos_at_coords(slice, (0, 2).into()), 4);
345− assert_eq!(pos_at_coords(slice, (0, 3).into()), 7); // \r\n is one char here
346− assert_eq!(pos_at_coords(slice, (0, 4).into()), 9);
347− let text = Rope::from("किमपि");
348− // 2 - 1 - 2 codepoints
349− // TODO: delete handling as per https://news.ycombinator.com/item?id=20058454
350− let slice = text.slice(..);
351− assert_eq!(pos_at_coords(slice, (0, 0).into()), 0);
352− assert_eq!(pos_at_coords(slice, (0, 1).into()), 2);
353− assert_eq!(pos_at_coords(slice, (0, 2).into()), 3);
354− assert_eq!(pos_at_coords(slice, (0, 3).into()), 5); // eol
355− }
356−
357137 #[test]
358138 fn test_vertical_move() {
359139 let text = Rope::from("abcd\nefg\nwrs");