@@ -31,26 +31,6 @@ impl State {
34− // TODO: doc/selection accessors
36− // TODO: be able to take either Rope or RopeSlice
38− // pub fn doc(&self) -> &Rope {
43− // pub fn selection(&self) -> &Selection {
47− // pub fn doc<R>(&self, range: R) -> RopeSlice
49− // R: std::ops::RangeBounds<usize>,
51− // self.doc.slice(range)
54 // update/transact:
34 // update/transact:
55 // update(desc) => transaction ? transaction.doc() for applied doc
35 // update(desc) => transaction ? transaction.doc() for applied doc
56 // transaction.apply(doc)
36 // transaction.apply(doc)
@@ -74,17 +54,6 @@ impl State {
74 // foldable
54 // foldable
75 // changeFilter/transactionFilter
55 // changeFilter/transactionFilter
77− // TODO: move that accepts a boundary matcher fn/list, we keep incrementing until we hit
80− // TODO: edits, does each keypress trigger a full command? I guess it's adding to the same
82− // There should be three pieces of the state: current transaction, the original doc, "preview"
84− // 1. apply the newly generated keypress as a transaction
85− // 2. compose onto a ongoing transaction
86− // 3. on insert mode leave, that transaction gets stored into undo history
88 pub fn move_range(
57 pub fn move_range(
90 range: Range,
59 range: Range,
@@ -93,47 +62,42 @@ impl State {
93 count: usize,
62 count: usize,
94 extend: bool,
63 extend: bool,
95 ) -> Range {
64 ) -> Range {
96− let text = &self.doc;
65+ let text = self.doc.slice(..);
97 let pos = range.head;
66 let pos = range.head;
67+ let line = text.char_to_line(pos);
68+ // TODO: we can optimize clamping by passing in RopeSlice limited to current line. that way
69+ // we stop calculating past start/end of line.
98− match (dir, granularity) {
70+ let pos = match (dir, granularity) {
99 (Direction::Backward, Granularity::Character) => {
71 (Direction::Backward, Granularity::Character) => {
101− let line = text.char_to_line(pos);
102 let start = text.line_to_char(line);
72 let start = text.line_to_char(line);
103− let pos = std::cmp::max(
104− nth_prev_grapheme_boundary(text.slice(..), pos, count),
73+ nth_prev_grapheme_boundary(text, pos, count).max(start)
107− Range::new(if extend { range.anchor } else { pos }, pos)
109 (Direction::Forward, Granularity::Character) => {
75 (Direction::Forward, Granularity::Character) => {
111− let line = text.char_to_line(pos);
112 // Line end is pos at the start of next line - 1
76 // Line end is pos at the start of next line - 1
113 // subtract another 1 because the line ends with \n
77 // subtract another 1 because the line ends with \n
114 let end = text.line_to_char(line + 1).saturating_sub(2);
78 let end = text.line_to_char(line + 1).saturating_sub(2);
116− std::cmp::min(nth_next_grapheme_boundary(text.slice(..), pos, count), end);
79+ nth_next_grapheme_boundary(text, pos, count).min(end)
117− Range::new(if extend { range.anchor } else { pos }, pos)
119− (_, Granularity::Line) => move_vertically(text.slice(..), dir, range, count, extend),
81+ (_, Granularity::Line) => return move_vertically(text, dir, range, count, extend),
83+ Range::new(if extend { range.anchor } else { pos }, pos)
123 pub fn move_next_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
86 pub fn move_next_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
124− // TODO: confirm it's fine without using graphemes, I think it should be
125 for _ in 0..count {
87 for _ in 0..count {
88+ if pos + 1 == slice.len_chars() {
126− // TODO: if end return end
89+ return pos;
128− let ch = slice.char(pos);
92+ let mut ch = slice.char(pos);
129− let next = slice.char(pos.saturating_add(1));
93+ let next = slice.char(pos + 1);
95+ // if we're at the end of a word, or on whitespce right before new one
130 if categorize(ch) != categorize(next) {
96 if categorize(ch) != categorize(next) {
131 pos += 1;
97 pos += 1;
135− let ch = slice.char(pos);
137 if is_word(ch) {
101 if is_word(ch) {
138 skip_over_next(slice, &mut pos, is_word);
102 skip_over_next(slice, &mut pos, is_word);
139 } else if ch.is_ascii_punctuation() {
103 } else if ch.is_ascii_punctuation() {
@@ -148,7 +112,6 @@ impl State {
150 pub fn move_prev_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
114 pub fn move_prev_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
151− // TODO: confirm it's fine without using graphemes, I think it should be
152 for _ in 0..count {
115 for _ in 0..count {
153 if pos == 0 {
116 if pos == 0 {
154 return pos;
117 return pos;
@@ -182,11 +145,13 @@ impl State {
183 pub fn move_next_word_end(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
146 pub fn move_next_word_end(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
184 for _ in 0..count {
147 for _ in 0..count {
148+ if pos + 1 == slice.len_chars() {
185− // TODO: if end return end
149+ return pos;
187− // TODO: confirm it's fine without using graphemes, I think it should be
188 let ch = slice.char(pos);
152 let ch = slice.char(pos);
189− let next = slice.char(pos.saturating_add(1));
153+ let next = slice.char(pos + 1);
190 if categorize(ch) != categorize(next) {
155 if categorize(ch) != categorize(next) {
191 pos += 1;
156 pos += 1;
@@ -202,7 +167,7 @@ impl State {
202 } else if ch.is_ascii_punctuation() {
167 } else if ch.is_ascii_punctuation() {
203 skip_over_next(slice, &mut pos, |ch| ch.is_ascii_punctuation());
168 skip_over_next(slice, &mut pos, |ch| ch.is_ascii_punctuation());
205− pos = pos.saturating_sub(1)
170+ pos -= 1
@@ -311,7 +276,7 @@ where
311 if !fun(ch) {
276 if !fun(ch) {
314− *pos += 1; // TODO: can go 1 over end of doc
279+ *pos += 1;
@@ -327,7 +292,7 @@ where
327 if !fun(ch) {
292 if !fun(ch) {
330− *pos -= pos.saturating_sub(1);
295+ *pos = pos.saturating_sub(1);