on master help sign in
Merge pull request #376 from cessen/great_line_ending_and_cursor_range_cleanup
05d20e1Blaž Hrastnik
helix-core/src/comment.rs+4−2
@@ -64,8 +64,10 @@ pub fn toggle_line_comments(doc: &Rope, selection: &Selection, token: Option<&st
64
64
65 let mut min_next_line = 0;
65 let mut min_next_line = 0;
66 for selection in selection {
66 for selection in selection {
67− let start = text.char_to_line(selection.from()).max(min_next_line);
67+ let (start, end) = selection.line_range(text);
68− let end = text.char_to_line(selection.to()) + 1;
68+ let start = start.max(min_next_line).min(text.len_lines());
69+ let end = (end + 1).min(text.len_lines());
70+
69 lines.extend(start..end);
71 lines.extend(start..end);
70 min_next_line = end + 1;
72 min_next_line = end + 1;
71 }
73 }
helix-core/src/graphemes.rs+21−2
@@ -71,6 +71,8 @@ pub fn nth_prev_grapheme_boundary(slice: RopeSlice, char_idx: usize, n: usize) -
71}
71}
72
72
73/// Finds the previous grapheme boundary before the given char position.
73/// Finds the previous grapheme boundary before the given char position.
74+#[must_use]
75+#[inline(always)]
74pub fn prev_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> usize {
76pub fn prev_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> usize {
75 nth_prev_grapheme_boundary(slice, char_idx, 1)
77 nth_prev_grapheme_boundary(slice, char_idx, 1)
76}
78}
@@ -117,21 +119,38 @@ pub fn nth_next_grapheme_boundary(slice: RopeSlice, char_idx: usize, n: usize) -
117}
119}
118
120
119/// Finds the next grapheme boundary after the given char position.
121/// Finds the next grapheme boundary after the given char position.
122+#[must_use]
123+#[inline(always)]
120pub fn next_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> usize {
124pub fn next_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> usize {
121 nth_next_grapheme_boundary(slice, char_idx, 1)
125 nth_next_grapheme_boundary(slice, char_idx, 1)
122}
126}
123
127
124/// Returns the passed char index if it's already a grapheme boundary,
128/// Returns the passed char index if it's already a grapheme boundary,
125/// or the next grapheme boundary char index if not.
129/// or the next grapheme boundary char index if not.
130+#[must_use]
131+#[inline]
126−pub fn ensure_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> usize {
132+pub fn ensure_grapheme_boundary_next(slice: RopeSlice, char_idx: usize) -> usize {
127 if char_idx == 0 {
133 if char_idx == 0 {
128− 0
134+ char_idx
129 } else {
135 } else {
130 next_grapheme_boundary(slice, char_idx - 1)
136 next_grapheme_boundary(slice, char_idx - 1)
131 }
137 }
132}
138}
133
139
140+/// Returns the passed char index if it's already a grapheme boundary,
141+/// or the prev grapheme boundary char index if not.
142+#[must_use]
143+#[inline]
144+pub fn ensure_grapheme_boundary_prev(slice: RopeSlice, char_idx: usize) -> usize {
145+ if char_idx == slice.len_chars() {
146+ char_idx
147+ } else {
148+ prev_grapheme_boundary(slice, char_idx + 1)
149+ }
150+}
151+
134/// Returns whether the given char position is a grapheme boundary.
152/// Returns whether the given char position is a grapheme boundary.
153+#[must_use]
135pub fn is_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> bool {
154pub fn is_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> bool {
136 // Bounds check
155 // Bounds check
137 debug_assert!(char_idx <= slice.len_chars());
156 debug_assert!(char_idx <= slice.len_chars());
helix-core/src/line_ending.rs+7
@@ -159,6 +159,13 @@ pub fn line_end_char_index(slice: &RopeSlice, line: usize) -> usize {
159 .unwrap_or(0)
159 .unwrap_or(0)
160}
160}
161
161
162+/// Fetches line `line_idx` from the passed rope slice, sans any line ending.
163+pub fn line_without_line_ending<'a>(slice: &'a RopeSlice, line_idx: usize) -> RopeSlice<'a> {
164+ let start = slice.line_to_char(line_idx);
165+ let end = line_end_char_index(slice, line_idx);
166+ slice.slice(start..end)
167+}
168+
162/// Returns the char index of the end of the given RopeSlice, not including
169/// Returns the char index of the end of the given RopeSlice, not including
163/// any final line ending.
170/// any final line ending.
164pub fn rope_end_without_line_ending(slice: &RopeSlice) -> usize {
171pub fn rope_end_without_line_ending(slice: &RopeSlice) -> usize {
helix-core/src/match_brackets.rs+4−3
@@ -24,12 +24,13 @@ pub fn find(syntax: &Syntax, doc: &Rope, pos: usize) -> Option<usize> {
24 return None;
24 return None;
25 }
25 }
26
26
27− let start_byte = node.start_byte();
28 let len = doc.len_bytes();
27 let len = doc.len_bytes();
28+ let start_byte = node.start_byte();
29+ let end_byte = node.end_byte() - 1; // it's end exclusive
29− if start_byte >= len {
30+ if start_byte >= len || end_byte >= len {
30 return None;
31 return None;
31 }
32 }
32− let end_byte = node.end_byte() - 1; // it's end exclusive
33+
33 let start_char = doc.byte_to_char(start_byte);
34 let start_char = doc.byte_to_char(start_byte);
34 let end_char = doc.byte_to_char(end_byte);
35 let end_char = doc.byte_to_char(end_byte);
35
36
helix-core/src/movement.rs+391−348
@@ -1,12 +1,14 @@
1−use std::iter::{self, from_fn};
1+use std::iter;
2
2
3use ropey::iter::Chars;
3use ropey::iter::Chars;
4
4
5use crate::{
5use crate::{
6 chars::{categorize_char, char_is_line_ending, CharCategory},
6 chars::{categorize_char, char_is_line_ending, CharCategory},
7 coords_at_pos,
7 coords_at_pos,
8+ graphemes::{
8− graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary},
9+ next_grapheme_boundary, nth_next_grapheme_boundary, nth_prev_grapheme_boundary,
9− line_ending::{get_line_ending, line_end_char_index},
10+ prev_grapheme_boundary,
11+ },
10 pos_at_coords, Position, Range, RopeSlice,
12 pos_at_coords, Position, Range, RopeSlice,
11};
13};
12
14
@@ -29,25 +31,16 @@ pub fn move_horizontally(
29 count: usize,
31 count: usize,
30 behaviour: Movement,
32 behaviour: Movement,
31) -> Range {
33) -> Range {
32− let pos = range.head;
34+ let pos = range.cursor(slice);
33− let line = slice.char_to_line(pos);
35+
34− // TODO: we can optimize clamping by passing in RopeSlice limited to current line. that way
36+ // Compute the new position.
35− // we stop calculating past start/end of line.
36− let pos = match dir {
37+ let new_pos = match dir {
37− Direction::Backward => {
38+ Direction::Forward => nth_next_grapheme_boundary(slice, pos, count),
38− let start = slice.line_to_char(line);
39− nth_prev_grapheme_boundary(slice, pos, count).max(start)
39+ Direction::Backward => nth_prev_grapheme_boundary(slice, pos, count),
40− }
41− Direction::Forward => {
42− let end_char_idx = line_end_char_index(&slice, line);
43− nth_next_grapheme_boundary(slice, pos, count).min(end_char_idx)
44− }
45− };
46− let anchor = match behaviour {
47− Movement::Extend => range.anchor,
48− Movement::Move => pos,
49 };
40 };
50− Range::new(anchor, pos)
41+
42+ // Compute the final new range.
43+ range.put_cursor(slice, new_pos, behaviour == Movement::Extend)
51}
44}
52
45
53pub fn move_vertically(
46pub fn move_vertically(
@@ -57,36 +50,28 @@ pub fn move_vertically(
57 count: usize,
50 count: usize,
58 behaviour: Movement,
51 behaviour: Movement,
59) -> Range {
52) -> Range {
60− let Position { row, col } = coords_at_pos(slice, range.head);
53+ let pos = range.cursor(slice);
61
54
55+ // Compute the current position's 2d coordinates.
56+ let Position { row, col } = coords_at_pos(slice, pos);
62 let horiz = range.horiz.unwrap_or(col as u32);
57 let horiz = range.horiz.unwrap_or(col as u32);
63
58
59+ // Compute the new position.
64− let new_line = match dir {
60+ let new_row = match dir {
61+ Direction::Forward => (row + count).min(slice.len_lines().saturating_sub(1)),
65 Direction::Backward => row.saturating_sub(count),
62 Direction::Backward => row.saturating_sub(count),
66− Direction::Forward => std::cmp::min(
67− row.saturating_add(count),
68− slice.len_lines().saturating_sub(2),
69− ),
70 };
63 };
64+ let new_col = col.max(horiz as usize);
65+ let new_pos = pos_at_coords(slice, Position::new(new_row, new_col), true);
71
66
72− // Length of the line sans line-ending.
67+ // Special-case to avoid moving to the end of the last non-empty line.
73− let new_line_len = {
68+ if behaviour == Movement::Extend && slice.line(new_row).len_chars() == 0 {
74− let line = slice.line(new_line);
69+ return range;
75− line.len_chars() - get_line_ending(&line).map(|le| le.len_chars()).unwrap_or(0)
76− };
77−
78− let new_col = std::cmp::min(horiz as usize, new_line_len);
79−
80− let pos = pos_at_coords(slice, Position::new(new_line, new_col));
81−
82− let anchor = match behaviour {
83− Movement::Extend => range.anchor,
84− Movement::Move => pos,
85− };
70+ }
86
71
87− let mut range = Range::new(anchor, pos);
72+ let mut new_range = range.put_cursor(slice, new_pos, behaviour == Movement::Extend);
88− range.horiz = Some(horiz);
73+ new_range.horiz = Some(horiz);
89− range
74+ new_range
90}
75}
91
76
92pub fn move_next_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
77pub fn move_next_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
@@ -118,8 +103,41 @@ pub fn move_prev_word_end(slice: RopeSlice, range: Range, count: usize) -> Range
118}
103}
119
104
120fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTarget) -> Range {
105fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTarget) -> Range {
106+ let is_prev = matches!(
107+ target,
108+ WordMotionTarget::PrevWordStart
109+ | WordMotionTarget::PrevLongWordStart
110+ | WordMotionTarget::PrevWordEnd
111+ );
112+
113+ // Special-case early-out.
114+ if (is_prev && range.head == 0) || (!is_prev && range.head == slice.len_chars()) {
115+ return range;
116+ }
117+
118+ // Prepare the range appropriately based on the target movement
119+ // direction. This is addressing two things at once:
120+ //
121+ // 1. Block-cursor semantics.
122+ // 2. The anchor position being irrelevant to the output result.
123+ #[allow(clippy::collapsible_else_if)] // Makes the structure clearer in this case.
124+ let start_range = if is_prev {
125+ if range.anchor < range.head {
126+ Range::new(range.head, prev_grapheme_boundary(slice, range.head))
127+ } else {
128+ Range::new(next_grapheme_boundary(slice, range.head), range.head)
129+ }
130+ } else {
131+ if range.anchor < range.head {
132+ Range::new(prev_grapheme_boundary(slice, range.head), range.head)
133+ } else {
134+ Range::new(range.head, next_grapheme_boundary(slice, range.head))
135+ }
136+ };
137+
138+ // Do the main work.
121− (0..count).fold(range, |range, _| {
139+ (0..count).fold(start_range, |r, _| {
122− slice.chars_at(range.head).range_to_target(target, range)
140+ slice.chars_at(r.head).range_to_target(target, r)
123 })
141 })
124}
142}
125
143
@@ -176,79 +194,75 @@ pub trait CharHelpers {
176 fn range_to_target(&mut self, target: WordMotionTarget, origin: Range) -> Range;
194 fn range_to_target(&mut self, target: WordMotionTarget, origin: Range) -> Range;
177}
195}
178
196
179−enum WordMotionPhase {
180− Start,
181− SkipNewlines,
182− ReachTarget,
183−}
184−
185impl CharHelpers for Chars<'_> {
197impl CharHelpers for Chars<'_> {
198+ /// Note: this only changes the anchor of the range if the head is effectively
199+ /// starting on a boundary (either directly or after skipping newline characters).
200+ /// Any other changes to the anchor should be handled by the calling code.
186 fn range_to_target(&mut self, target: WordMotionTarget, origin: Range) -> Range {
201 fn range_to_target(&mut self, target: WordMotionTarget, origin: Range) -> Range {
187− // Characters are iterated forward or backwards depending on the motion direction.
202+ let is_prev = matches!(
188− let characters: Box<dyn Iterator<Item = char>> = match target {
203+ target,
189 WordMotionTarget::PrevWordStart
204 WordMotionTarget::PrevWordStart
190− | WordMotionTarget::PrevLongWordStart
205+ | WordMotionTarget::PrevLongWordStart
191− | WordMotionTarget::PrevWordEnd => {
206+ | WordMotionTarget::PrevWordEnd
207+ );
208+
209+ // Reverse the iterator if needed for the motion direction.
210+ if is_prev {
211+ self.reverse();
212+ }
213+
214+ // Function to advance index in the appropriate motion direction.
215+ let advance: &dyn Fn(&mut usize) = if is_prev {
216+ &|idx| *idx = idx.saturating_sub(1)
217+ } else {
218+ &|idx| *idx += 1
219+ };
220+
221+ // Initialize state variables.
222+ let mut anchor = origin.anchor;
223+ let mut head = origin.head;
224+ let mut prev_ch = {
225+ let ch = self.prev();
226+ if ch.is_some() {
192 self.next();
227 self.next();
193− Box::new(from_fn(|| self.prev()))
194 }
228 }
195− _ => Box::new(self),
229+ ch
196 };
230 };
197
231
198− // Index advancement also depends on the direction.
232+ // Skip any initial newline characters.
233+ while let Some(ch) = self.next() {
234+ if char_is_line_ending(ch) {
235+ prev_ch = Some(ch);
199− let advance: &dyn Fn(&mut usize) = match target {
236+ advance(&mut head);
200− WordMotionTarget::PrevWordStart
237+ } else {
201− | WordMotionTarget::PrevLongWordStart
238+ self.prev();
202− | WordMotionTarget::PrevWordEnd => &|u| *u = u.saturating_sub(1),
239+ break;
203− _ => &|u| *u += 1,
240+ }
241+ }
242+ if prev_ch.map(char_is_line_ending).unwrap_or(false) {
243+ anchor = head;
204− };
244+ }
205
245
206− let mut characters = characters.peekable();
246+ // Find our target position(s).
207− let mut phase = WordMotionPhase::Start;
247+ let head_start = head;
208− let mut head = origin.head;
248+ while let Some(next_ch) = self.next() {
209− let mut anchor: Option<usize> = None;
210− let is_boundary =
211− |a: char, b: Option<char>| categorize_char(a) != categorize_char(b.unwrap_or(a));
212− while let Some(peek) = characters.peek().copied() {
213− phase = match phase {
214− WordMotionPhase::Start => {
215− characters.next();
216− if characters.peek().is_none() {
217− break; // We're at the end, so there's nothing to do.
218− }
219− // Anchor may remain here if the head wasn't at a boundary
220− if !is_boundary(peek, characters.peek().copied()) && !char_is_line_ending(peek)
221− {
222− anchor = Some(head);
223− }
224− // First character is always skipped by the head
225− advance(&mut head);
226− WordMotionPhase::SkipNewlines
227− }
228− WordMotionPhase::SkipNewlines => {
229− if char_is_line_ending(peek) {
230− characters.next();
231− if characters.peek().is_some() {
232− advance(&mut head);
233− }
234− WordMotionPhase::SkipNewlines
235− } else {
236− WordMotionPhase::ReachTarget
237− }
238− }
239− WordMotionPhase::ReachTarget => {
240− characters.next();
241− anchor = anchor.or(Some(head));
242− if reached_target(target, peek, characters.peek()) {
249+ if prev_ch.is_none() || reached_target(target, prev_ch.unwrap(), next_ch) {
243− break;
250+ if head == head_start {
244− } else {
251+ anchor = head;
245− advance(&mut head);
252+ } else {
246− }
253+ break;
247− WordMotionPhase::ReachTarget
248 }
254 }
249 }
255 }
256+ prev_ch = Some(next_ch);
257+ advance(&mut head);
258+ }
259+
260+ // Un-reverse the iterator if needed.
261+ if is_prev {
262+ self.reverse();
250 }
263 }
264+
251− Range::new(anchor.unwrap_or(origin.anchor), head)
265+ Range::new(anchor, head)
252 }
266 }
253}
267}
254
268
@@ -265,28 +279,23 @@ fn is_long_word_boundary(a: char, b: char) -> bool {
265 }
279 }
266}
280}
267
281
268−fn reached_target(target: WordMotionTarget, peek: char, next_peek: Option<&char>) -> bool {
282+fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> bool {
269− let next_peek = match next_peek {
270− Some(next_peek) => next_peek,
271− None => return true,
272− };
273−
274 match target {
283 match target {
275 WordMotionTarget::NextWordStart | WordMotionTarget::PrevWordEnd => {
284 WordMotionTarget::NextWordStart | WordMotionTarget::PrevWordEnd => {
276− is_word_boundary(peek, *next_peek)
285+ is_word_boundary(prev_ch, next_ch)
277− && (char_is_line_ending(*next_peek) || !next_peek.is_whitespace())
286+ && (char_is_line_ending(next_ch) || !next_ch.is_whitespace())
278 }
287 }
279 WordMotionTarget::NextWordEnd | WordMotionTarget::PrevWordStart => {
288 WordMotionTarget::NextWordEnd | WordMotionTarget::PrevWordStart => {
280− is_word_boundary(peek, *next_peek)
289+ is_word_boundary(prev_ch, next_ch)
281− && (!peek.is_whitespace() || char_is_line_ending(*next_peek))
290+ && (!prev_ch.is_whitespace() || char_is_line_ending(next_ch))
282 }
291 }
283 WordMotionTarget::NextLongWordStart => {
292 WordMotionTarget::NextLongWordStart => {
284− is_long_word_boundary(peek, *next_peek)
293+ is_long_word_boundary(prev_ch, next_ch)
285− && (char_is_line_ending(*next_peek) || !next_peek.is_whitespace())
294+ && (char_is_line_ending(next_ch) || !next_ch.is_whitespace())
286 }
295 }
287 WordMotionTarget::NextLongWordEnd | WordMotionTarget::PrevLongWordStart => {
296 WordMotionTarget::NextLongWordEnd | WordMotionTarget::PrevLongWordStart => {
288− is_long_word_boundary(peek, *next_peek)
297+ is_long_word_boundary(prev_ch, next_ch)
289− && (!peek.is_whitespace() || char_is_line_ending(*next_peek))
298+ && (!prev_ch.is_whitespace() || char_is_line_ending(next_ch))
290 }
299 }
291 }
300 }
292}
301}
@@ -317,7 +326,7 @@ mod test {
317 fn test_vertical_move() {
326 fn test_vertical_move() {
318 let text = Rope::from("abcd\nefg\nwrs");
327 let text = Rope::from("abcd\nefg\nwrs");
319 let slice = text.slice(..);
328 let slice = text.slice(..);
320− let pos = pos_at_coords(slice, (0, 4).into());
329+ let pos = pos_at_coords(slice, (0, 4).into(), true);
321
330
322 let range = Range::new(pos, pos);
331 let range = Range::new(pos, pos);
323 assert_eq!(
332 assert_eq!(
@@ -330,10 +339,10 @@ mod test {
330 }
339 }
331
340
332 #[test]
341 #[test]
333− fn horizontal_moves_through_single_line_in_single_line_text() {
342+ fn horizontal_moves_through_single_line_text() {
334 let text = Rope::from(SINGLE_LINE_SAMPLE);
343 let text = Rope::from(SINGLE_LINE_SAMPLE);
335 let slice = text.slice(..);
344 let slice = text.slice(..);
336− let position = pos_at_coords(slice, (0, 0).into());
345+ let position = pos_at_coords(slice, (0, 0).into(), true);
337
346
338 let mut range = Range::point(position);
347 let mut range = Range::point(position);
339
348
@@ -353,23 +362,23 @@ mod test {
353 }
362 }
354
363
355 #[test]
364 #[test]
356− fn horizontal_moves_through_single_line_in_multiline_text() {
365+ fn horizontal_moves_through_multiline_text() {
357 let text = Rope::from(MULTILINE_SAMPLE);
366 let text = Rope::from(MULTILINE_SAMPLE);
358 let slice = text.slice(..);
367 let slice = text.slice(..);
359− let position = pos_at_coords(slice, (0, 0).into());
368+ let position = pos_at_coords(slice, (0, 0).into(), true);
360
369
361 let mut range = Range::point(position);
370 let mut range = Range::point(position);
362
371
363 let moves_and_expected_coordinates = IntoIter::new([
372 let moves_and_expected_coordinates = IntoIter::new([
364− ((Direction::Forward, 1usize), (0, 1)), // M|ultiline\n
373+ ((Direction::Forward, 11usize), (1, 1)), // Multiline\nt|ext sample\n...
365− ((Direction::Forward, 2usize), (0, 3)), // Mul|tiline\n
374+ ((Direction::Backward, 1usize), (1, 0)), // Multiline\n|text sample\n...
366− ((Direction::Backward, 6usize), (0, 0)), // |Multiline\n
375+ ((Direction::Backward, 5usize), (0, 5)), // Multi|line\ntext sample\n...
367− ((Direction::Backward, 999usize), (0, 0)), // |Multiline\n
376+ ((Direction::Backward, 999usize), (0, 0)), // |Multiline\ntext sample\n...
368− ((Direction::Forward, 3usize), (0, 3)), // Mul|tiline\n
377+ ((Direction::Forward, 3usize), (0, 3)), // Mul|tiline\ntext sample\n...
369− ((Direction::Forward, 0usize), (0, 3)), // Mul|tiline\n
378+ ((Direction::Forward, 0usize), (0, 3)), // Mul|tiline\ntext sample\n...
370− ((Direction::Backward, 0usize), (0, 3)), // Mul|tiline\n
379+ ((Direction::Backward, 0usize), (0, 3)), // Mul|tiline\ntext sample\n...
371− ((Direction::Forward, 999usize), (0, 9)), // Multiline|\n
380+ ((Direction::Forward, 999usize), (5, 0)), // ...and whitespaced\n|
372− ((Direction::Forward, 999usize), (0, 9)), // Multiline|\n
381+ ((Direction::Forward, 999usize), (5, 0)), // ...and whitespaced\n|
373 ]);
382 ]);
374
383
375 for ((direction, amount), coordinates) in moves_and_expected_coordinates {
384 for ((direction, amount), coordinates) in moves_and_expected_coordinates {
@@ -383,7 +392,7 @@ mod test {
383 fn selection_extending_moves_in_single_line_text() {
392 fn selection_extending_moves_in_single_line_text() {
384 let text = Rope::from(SINGLE_LINE_SAMPLE);
393 let text = Rope::from(SINGLE_LINE_SAMPLE);
385 let slice = text.slice(..);
394 let slice = text.slice(..);
386− let position = pos_at_coords(slice, (0, 0).into());
395+ let position = pos_at_coords(slice, (0, 0).into(), true);
387
396
388 let mut range = Range::point(position);
397 let mut range = Range::point(position);
389 let original_anchor = range.anchor;
398 let original_anchor = range.anchor;
@@ -403,18 +412,19 @@ mod test {
403 #[test]
412 #[test]
404 fn vertical_moves_in_single_column() {
413 fn vertical_moves_in_single_column() {
405 let text = Rope::from(MULTILINE_SAMPLE);
414 let text = Rope::from(MULTILINE_SAMPLE);
406− let slice = dbg!(&text).slice(..);
415+ let slice = text.slice(..);
407− let position = pos_at_coords(slice, (0, 0).into());
416+ let position = pos_at_coords(slice, (0, 0).into(), true);
408 let mut range = Range::point(position);
417 let mut range = Range::point(position);
409 let moves_and_expected_coordinates = IntoIter::new([
418 let moves_and_expected_coordinates = IntoIter::new([
410 ((Direction::Forward, 1usize), (1, 0)),
419 ((Direction::Forward, 1usize), (1, 0)),
411 ((Direction::Forward, 2usize), (3, 0)),
420 ((Direction::Forward, 2usize), (3, 0)),
421+ ((Direction::Forward, 1usize), (4, 0)),
412 ((Direction::Backward, 999usize), (0, 0)),
422 ((Direction::Backward, 999usize), (0, 0)),
413− ((Direction::Forward, 3usize), (3, 0)),
423+ ((Direction::Forward, 4usize), (4, 0)),
414− ((Direction::Forward, 0usize), (3, 0)),
424+ ((Direction::Forward, 0usize), (4, 0)),
415− ((Direction::Backward, 0usize), (3, 0)),
425+ ((Direction::Backward, 0usize), (4, 0)),
416− ((Direction::Forward, 5), (4, 0)),
426+ ((Direction::Forward, 5), (5, 0)),
417− ((Direction::Forward, 999usize), (4, 0)),
427+ ((Direction::Forward, 999usize), (5, 0)),
418 ]);
428 ]);
419
429
420 for ((direction, amount), coordinates) in moves_and_expected_coordinates {
430 for ((direction, amount), coordinates) in moves_and_expected_coordinates {
@@ -428,7 +438,7 @@ mod test {
428 fn vertical_moves_jumping_column() {
438 fn vertical_moves_jumping_column() {
429 let text = Rope::from(MULTILINE_SAMPLE);
439 let text = Rope::from(MULTILINE_SAMPLE);
430 let slice = text.slice(..);
440 let slice = text.slice(..);
431− let position = pos_at_coords(slice, (0, 0).into());
441+ let position = pos_at_coords(slice, (0, 0).into(), true);
432 let mut range = Range::point(position);
442 let mut range = Range::point(position);
433
443
434 enum Axis {
444 enum Axis {
@@ -446,7 +456,8 @@ mod test {
446 ((Axis::V, Direction::Forward, 1usize), (3, 8)),
456 ((Axis::V, Direction::Forward, 1usize), (3, 8)),
447 // Behaviour is preserved even through long jumps
457 // Behaviour is preserved even through long jumps
448 ((Axis::V, Direction::Backward, 999usize), (0, 8)),
458 ((Axis::V, Direction::Backward, 999usize), (0, 8)),
449− ((Axis::V, Direction::Forward, 999usize), (4, 8)),
459+ ((Axis::V, Direction::Forward, 4usize), (4, 8)),
460+ ((Axis::V, Direction::Forward, 999usize), (5, 0)),
450 ]);
461 ]);
451
462
452 for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates {
463 for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates {
@@ -460,10 +471,10 @@ mod test {
460 }
471 }
461
472
462 #[test]
473 #[test]
463− fn multibyte_character_column_jumps() {
474+ fn multibyte_character_wide_column_jumps() {
464 let text = Rope::from(MULTIBYTE_CHARACTER_SAMPLE);
475 let text = Rope::from(MULTIBYTE_CHARACTER_SAMPLE);
465 let slice = text.slice(..);
476 let slice = text.slice(..);
466− let position = pos_at_coords(slice, (0, 0).into());
477+ let position = pos_at_coords(slice, (0, 0).into(), true);
467 let mut range = Range::point(position);
478 let mut range = Range::point(position);
468
479
469 // FIXME: The behaviour captured in this test diverges from both Kakoune and Vim. These
480 // FIXME: The behaviour captured in this test diverges from both Kakoune and Vim. These
@@ -474,10 +485,14 @@ mod test {
474 V,
485 V,
475 }
486 }
476 let moves_and_expected_coordinates = IntoIter::new([
487 let moves_and_expected_coordinates = IntoIter::new([
477− // Places cursor at the fourth kana
488+ // Places cursor at the fourth kana.
478 ((Axis::H, Direction::Forward, 4), (0, 4)),
489 ((Axis::H, Direction::Forward, 4), (0, 4)),
479− // Descent places cursor at the fourth character.
490+ // Descent places cursor at the 4th character.
480 ((Axis::V, Direction::Forward, 1usize), (1, 4)),
491 ((Axis::V, Direction::Forward, 1usize), (1, 4)),
492+ // Moving back 1 character.
493+ ((Axis::H, Direction::Backward, 1usize), (1, 3)),
494+ // Jumping back up 1 line.
495+ ((Axis::V, Direction::Backward, 1usize), (0, 3)),
481 ]);
496 ]);
482
497
483 for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates {
498 for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates {
@@ -512,42 +527,42 @@ mod test {
512 fn test_behaviour_when_moving_to_start_of_next_words() {
527 fn test_behaviour_when_moving_to_start_of_next_words() {
513 let tests = array::IntoIter::new([
528 let tests = array::IntoIter::new([
514 ("Basic forward motion stops at the first space",
529 ("Basic forward motion stops at the first space",
515− vec![(1, Range::new(0, 0), Range::new(0, 5))]),
530+ vec![(1, Range::new(0, 0), Range::new(0, 6))]),
516 (" Starting from a boundary advances the anchor",
531 (" Starting from a boundary advances the anchor",
517− vec![(1, Range::new(0, 0), Range::new(1, 9))]),
532+ vec![(1, Range::new(0, 0), Range::new(1, 10))]),
518 ("Long whitespace gap is bridged by the head",
533 ("Long whitespace gap is bridged by the head",
519− vec![(1, Range::new(0, 0), Range::new(0, 10))]),
534+ vec![(1, Range::new(0, 0), Range::new(0, 11))]),
520 ("Previous anchor is irrelevant for forward motions",
535 ("Previous anchor is irrelevant for forward motions",
521− vec![(1, Range::new(12, 0), Range::new(0, 8))]),
536+ vec![(1, Range::new(12, 0), Range::new(0, 9))]),
522 (" Starting from whitespace moves to last space in sequence",
537 (" Starting from whitespace moves to last space in sequence",
523− vec![(1, Range::new(0, 0), Range::new(0, 3))]),
538+ vec![(1, Range::new(0, 0), Range::new(0, 4))]),
524 ("Starting from mid-word leaves anchor at start position and moves head",
539 ("Starting from mid-word leaves anchor at start position and moves head",
525− vec![(1, Range::new(3, 3), Range::new(3, 8))]),
540+ vec![(1, Range::new(3, 3), Range::new(3, 9))]),
526 ("Identifiers_with_underscores are considered a single word",
541 ("Identifiers_with_underscores are considered a single word",
527− vec![(1, Range::new(0, 0), Range::new(0, 28))]),
542+ vec![(1, Range::new(0, 0), Range::new(0, 29))]),
528 ("Jumping\n into starting whitespace selects the spaces before 'into'",
543 ("Jumping\n into starting whitespace selects the spaces before 'into'",
529− vec![(1, Range::new(0, 6), Range::new(8, 11))]),
544+ vec![(1, Range::new(0, 7), Range::new(8, 12))]),
530 ("alphanumeric.!,and.?=punctuation are considered 'words' for the purposes of word motion",
545 ("alphanumeric.!,and.?=punctuation are considered 'words' for the purposes of word motion",
531 vec![
546 vec![
532− (1, Range::new(0, 0), Range::new(0, 11)),
547+ (1, Range::new(0, 0), Range::new(0, 12)),
533− (1, Range::new(0, 11), Range::new(12, 14)),
548+ (1, Range::new(0, 12), Range::new(12, 15)),
534− (1, Range::new(12, 14), Range::new(15, 17))
549+ (1, Range::new(12, 15), Range::new(15, 18))
535 ]),
550 ]),
536 ("... ... punctuation and spaces behave as expected",
551 ("... ... punctuation and spaces behave as expected",
537 vec![
552 vec![
538− (1, Range::new(0, 0), Range::new(0, 5)),
553+ (1, Range::new(0, 0), Range::new(0, 6)),
539− (1, Range::new(0, 5), Range::new(6, 9)),
554+ (1, Range::new(0, 6), Range::new(6, 10)),
540 ]),
555 ]),
541 (".._.._ punctuation is not joined by underscores into a single block",
556 (".._.._ punctuation is not joined by underscores into a single block",
542− vec![(1, Range::new(0, 0), Range::new(0, 1))]),
557+ vec![(1, Range::new(0, 0), Range::new(0, 2))]),
543 ("Newlines\n\nare bridged seamlessly.",
558 ("Newlines\n\nare bridged seamlessly.",
544 vec![
559 vec![
545− (1, Range::new(0, 0), Range::new(0, 7)),
560+ (1, Range::new(0, 0), Range::new(0, 8)),
546− (1, Range::new(0, 7), Range::new(10, 13)),
561+ (1, Range::new(0, 8), Range::new(10, 14)),
547 ]),
562 ]),
548 ("Jumping\n\n\n\n\n\n from newlines to whitespace selects whitespace.",
563 ("Jumping\n\n\n\n\n\n from newlines to whitespace selects whitespace.",
549 vec![
564 vec![
550− (1, Range::new(0, 8), Range::new(13, 15)),
565+ (1, Range::new(0, 9), Range::new(13, 16)),
551 ]),
566 ]),
552 ("A failed motion does not modify the range",
567 ("A failed motion does not modify the range",
553 vec![
568 vec![
@@ -555,17 +570,17 @@ mod test {
555 ]),
570 ]),
556 ("oh oh oh two character words!",
571 ("oh oh oh two character words!",
557 vec![
572 vec![
558− (1, Range::new(0, 0), Range::new(0, 2)),
573+ (1, Range::new(0, 0), Range::new(0, 3)),
559− (1, Range::new(0, 2), Range::new(3, 5)),
574+ (1, Range::new(0, 3), Range::new(3, 6)),
560− (1, Range::new(0, 1), Range::new(2, 2)),
575+ (1, Range::new(0, 2), Range::new(1, 3)),
561 ]),
576 ]),
562 ("Multiple motions at once resolve correctly",
577 ("Multiple motions at once resolve correctly",
563 vec![
578 vec![
564− (3, Range::new(0, 0), Range::new(17, 19)),
579+ (3, Range::new(0, 0), Range::new(17, 20)),
565 ]),
580 ]),
566 ("Excessive motions are performed partially",
581 ("Excessive motions are performed partially",
567 vec![
582 vec![
568− (999, Range::new(0, 0), Range::new(32, 40)),
583+ (999, Range::new(0, 0), Range::new(32, 41)),
569 ]),
584 ]),
570 ("", // Edge case of moving forward in empty string
585 ("", // Edge case of moving forward in empty string
571 vec![
586 vec![
@@ -573,16 +588,16 @@ mod test {
573 ]),
588 ]),
574 ("\n\n\n\n\n", // Edge case of moving forward in all newlines
589 ("\n\n\n\n\n", // Edge case of moving forward in all newlines
575 vec![
590 vec![
576− (1, Range::new(0, 0), Range::new(0, 4)),
591+ (1, Range::new(0, 0), Range::new(5, 5)),
577 ]),
592 ]),
578 ("\n \n \n Jumping through alternated space blocks and newlines selects the space blocks",
593 ("\n \n \n Jumping through alternated space blocks and newlines selects the space blocks",
579 vec![
594 vec![
580− (1, Range::new(0, 0), Range::new(1, 3)),
595+ (1, Range::new(0, 0), Range::new(1, 4)),
581− (1, Range::new(1, 3), Range::new(5, 7)),
596+ (1, Range::new(1, 4), Range::new(5, 8)),
582 ]),
597 ]),
583 ("ヒーリクス multibyte characters behave as normal characters",
598 ("ヒーリクス multibyte characters behave as normal characters",
584 vec![
599 vec![
585− (1, Range::new(0, 0), Range::new(0, 5)),
600+ (1, Range::new(0, 0), Range::new(0, 6)),
586 ]),
601 ]),
587 ]);
602 ]);
588
603
@@ -598,40 +613,40 @@ mod test {
598 fn test_behaviour_when_moving_to_start_of_next_long_words() {
613 fn test_behaviour_when_moving_to_start_of_next_long_words() {
599 let tests = array::IntoIter::new([
614 let tests = array::IntoIter::new([
600 ("Basic forward motion stops at the first space",
615 ("Basic forward motion stops at the first space",
601− vec![(1, Range::new(0, 0), Range::new(0, 5))]),
616+ vec![(1, Range::new(0, 0), Range::new(0, 6))]),
602 (" Starting from a boundary advances the anchor",
617 (" Starting from a boundary advances the anchor",
603− vec![(1, Range::new(0, 0), Range::new(1, 9))]),
618+ vec![(1, Range::new(0, 0), Range::new(1, 10))]),
604 ("Long whitespace gap is bridged by the head",
619 ("Long whitespace gap is bridged by the head",
605− vec![(1, Range::new(0, 0), Range::new(0, 10))]),
620+ vec![(1, Range::new(0, 0), Range::new(0, 11))]),
606 ("Previous anchor is irrelevant for forward motions",
621 ("Previous anchor is irrelevant for forward motions",
607− vec![(1, Range::new(12, 0), Range::new(0, 8))]),
622+ vec![(1, Range::new(12, 0), Range::new(0, 9))]),
608 (" Starting from whitespace moves to last space in sequence",
623 (" Starting from whitespace moves to last space in sequence",
609− vec![(1, Range::new(0, 0), Range::new(0, 3))]),
624+ vec![(1, Range::new(0, 0), Range::new(0, 4))]),
610 ("Starting from mid-word leaves anchor at start position and moves head",
625 ("Starting from mid-word leaves anchor at start position and moves head",
611− vec![(1, Range::new(3, 3), Range::new(3, 8))]),
626+ vec![(1, Range::new(3, 3), Range::new(3, 9))]),
612 ("Identifiers_with_underscores are considered a single word",
627 ("Identifiers_with_underscores are considered a single word",
613− vec![(1, Range::new(0, 0), Range::new(0, 28))]),
628+ vec![(1, Range::new(0, 0), Range::new(0, 29))]),
614 ("Jumping\n into starting whitespace selects the spaces before 'into'",
629 ("Jumping\n into starting whitespace selects the spaces before 'into'",
615− vec![(1, Range::new(0, 6), Range::new(8, 11))]),
630+ vec![(1, Range::new(0, 7), Range::new(8, 12))]),
616 ("alphanumeric.!,and.?=punctuation are not treated any differently than alphanumerics",
631 ("alphanumeric.!,and.?=punctuation are not treated any differently than alphanumerics",
617 vec![
632 vec![
618− (1, Range::new(0, 0), Range::new(0, 32)),
633+ (1, Range::new(0, 0), Range::new(0, 33)),
619 ]),
634 ]),
620 ("... ... punctuation and spaces behave as expected",
635 ("... ... punctuation and spaces behave as expected",
621 vec![
636 vec![
622− (1, Range::new(0, 0), Range::new(0, 5)),
637+ (1, Range::new(0, 0), Range::new(0, 6)),
623− (1, Range::new(0, 5), Range::new(6, 9)),
638+ (1, Range::new(0, 6), Range::new(6, 10)),
624 ]),
639 ]),
625 (".._.._ punctuation is joined by underscores into a single word, as it behaves like alphanumerics",
640 (".._.._ punctuation is joined by underscores into a single word, as it behaves like alphanumerics",
626− vec![(1, Range::new(0, 0), Range::new(0, 6))]),
641+ vec![(1, Range::new(0, 0), Range::new(0, 7))]),
627 ("Newlines\n\nare bridged seamlessly.",
642 ("Newlines\n\nare bridged seamlessly.",
628 vec![
643 vec![
629− (1, Range::new(0, 0), Range::new(0, 7)),
644+ (1, Range::new(0, 0), Range::new(0, 8)),
630− (1, Range::new(0, 7), Range::new(10, 13)),
645+ (1, Range::new(0, 8), Range::new(10, 14)),
631 ]),
646 ]),
632 ("Jumping\n\n\n\n\n\n from newlines to whitespace selects whitespace.",
647 ("Jumping\n\n\n\n\n\n from newlines to whitespace selects whitespace.",
633 vec![
648 vec![
634− (1, Range::new(0, 8), Range::new(13, 15)),
649+ (1, Range::new(0, 9), Range::new(13, 16)),
635 ]),
650 ]),
636 ("A failed motion does not modify the range",
651 ("A failed motion does not modify the range",
637 vec![
652 vec![
@@ -639,17 +654,17 @@ mod test {
639 ]),
654 ]),
640 ("oh oh oh two character words!",
655 ("oh oh oh two character words!",
641 vec![
656 vec![
642− (1, Range::new(0, 0), Range::new(0, 2)),
657+ (1, Range::new(0, 0), Range::new(0, 3)),
643− (1, Range::new(0, 2), Range::new(3, 5)),
658+ (1, Range::new(0, 3), Range::new(3, 6)),
644− (1, Range::new(0, 1), Range::new(2, 2)),
659+ (1, Range::new(0, 1), Range::new(0, 3)),
645 ]),
660 ]),
646 ("Multiple motions at once resolve correctly",
661 ("Multiple motions at once resolve correctly",
647 vec![
662 vec![
648− (3, Range::new(0, 0), Range::new(17, 19)),
663+ (3, Range::new(0, 0), Range::new(17, 20)),
649 ]),
664 ]),
650 ("Excessive motions are performed partially",
665 ("Excessive motions are performed partially",
651 vec![
666 vec![
652− (999, Range::new(0, 0), Range::new(32, 40)),
667+ (999, Range::new(0, 0), Range::new(32, 41)),
653 ]),
668 ]),
654 ("", // Edge case of moving forward in empty string
669 ("", // Edge case of moving forward in empty string
655 vec![
670 vec![
@@ -657,16 +672,16 @@ mod test {
657 ]),
672 ]),
658 ("\n\n\n\n\n", // Edge case of moving forward in all newlines
673 ("\n\n\n\n\n", // Edge case of moving forward in all newlines
659 vec![
674 vec![
660− (1, Range::new(0, 0), Range::new(0, 4)),
675+ (1, Range::new(0, 0), Range::new(5, 5)),
661 ]),
676 ]),
662 ("\n \n \n Jumping through alternated space blocks and newlines selects the space blocks",
677 ("\n \n \n Jumping through alternated space blocks and newlines selects the space blocks",
663 vec![
678 vec![
664− (1, Range::new(0, 0), Range::new(1, 3)),
679+ (1, Range::new(0, 0), Range::new(1, 4)),
665− (1, Range::new(1, 3), Range::new(5, 7)),
680+ (1, Range::new(1, 4), Range::new(5, 8)),
666 ]),
681 ]),
667 ("ヒー..リクス multibyte characters behave as normal characters, including their interaction with punctuation",
682 ("ヒー..リクス multibyte characters behave as normal characters, including their interaction with punctuation",
668 vec![
683 vec![
669− (1, Range::new(0, 0), Range::new(0, 7)),
684+ (1, Range::new(0, 0), Range::new(0, 8)),
670 ]),
685 ]),
671 ]);
686 ]);
672
687
@@ -682,44 +697,47 @@ mod test {
682 fn test_behaviour_when_moving_to_start_of_previous_words() {
697 fn test_behaviour_when_moving_to_start_of_previous_words() {
683 let tests = array::IntoIter::new([
698 let tests = array::IntoIter::new([
684 ("Basic backward motion from the middle of a word",
699 ("Basic backward motion from the middle of a word",
685− vec![(1, Range::new(3, 3), Range::new(3, 0))]),
700+ vec![(1, Range::new(3, 3), Range::new(4, 0))]),
701+
702+ // // Why do we want this behavior? The current behavior fails this
703+ // // test, but seems better and more consistent.
686− ("Starting from after boundary retreats the anchor",
704+ // ("Starting from after boundary retreats the anchor",
687− vec![(1, Range::new(0, 8), Range::new(7, 0))]),
705+ // vec![(1, Range::new(0, 9), Range::new(8, 0))]),
706+
688 (" Jump to start of a word preceded by whitespace",
707 (" Jump to start of a word preceded by whitespace",
689− vec![(1, Range::new(5, 5), Range::new(5, 4))]),
708+ vec![(1, Range::new(5, 5), Range::new(6, 4))]),
690 (" Jump to start of line from start of word preceded by whitespace",
709 (" Jump to start of line from start of word preceded by whitespace",
691− vec![(1, Range::new(4, 4), Range::new(3, 0))]),
710+ vec![(1, Range::new(4, 4), Range::new(4, 0))]),
692 ("Previous anchor is irrelevant for backward motions",
711 ("Previous anchor is irrelevant for backward motions",
693− vec![(1, Range::new(12, 5), Range::new(5, 0))]),
712+ vec![(1, Range::new(12, 5), Range::new(6, 0))]),
694 (" Starting from whitespace moves to first space in sequence",
713 (" Starting from whitespace moves to first space in sequence",
695− vec![(1, Range::new(0, 3), Range::new(3, 0))]),
714+ vec![(1, Range::new(0, 4), Range::new(4, 0))]),
696 ("Identifiers_with_underscores are considered a single word",
715 ("Identifiers_with_underscores are considered a single word",
697 vec![(1, Range::new(0, 20), Range::new(20, 0))]),
716 vec![(1, Range::new(0, 20), Range::new(20, 0))]),
698 ("Jumping\n \nback through a newline selects whitespace",
717 ("Jumping\n \nback through a newline selects whitespace",
699− vec![(1, Range::new(0, 13), Range::new(11, 8))]),
718+ vec![(1, Range::new(0, 13), Range::new(12, 8))]),
700 ("Jumping to start of word from the end selects the word",
719 ("Jumping to start of word from the end selects the word",
701− vec![(1, Range::new(6, 6), Range::new(6, 0))]),
720+ vec![(1, Range::new(6, 7), Range::new(7, 0))]),
702 ("alphanumeric.!,and.?=punctuation are considered 'words' for the purposes of word motion",
721 ("alphanumeric.!,and.?=punctuation are considered 'words' for the purposes of word motion",
703 vec![
722 vec![
704− (1, Range::new(30, 30), Range::new(30, 21)),
723+ (1, Range::new(29, 30), Range::new(30, 21)),
705− (1, Range::new(30, 21), Range::new(20, 18)),
724+ (1, Range::new(30, 21), Range::new(21, 18)),
706− (1, Range::new(20, 18), Range::new(17, 15))
725+ (1, Range::new(21, 18), Range::new(18, 15))
707 ]),
726 ]),
708−
709 ("... ... punctuation and spaces behave as expected",
727 ("... ... punctuation and spaces behave as expected",
710 vec![
728 vec![
711− (1, Range::new(0, 10), Range::new(9, 6)),
729+ (1, Range::new(0, 10), Range::new(10, 6)),
712− (1, Range::new(9, 6), Range::new(5, 0)),
730+ (1, Range::new(10, 6), Range::new(6, 0)),
713 ]),
731 ]),
714 (".._.._ punctuation is not joined by underscores into a single block",
732 (".._.._ punctuation is not joined by underscores into a single block",
715− vec![(1, Range::new(0, 5), Range::new(4, 3))]),
733+ vec![(1, Range::new(0, 6), Range::new(5, 3))]),
716 ("Newlines\n\nare bridged seamlessly.",
734 ("Newlines\n\nare bridged seamlessly.",
717 vec![
735 vec![
718− (1, Range::new(0, 10), Range::new(7, 0)),
736+ (1, Range::new(0, 10), Range::new(8, 0)),
719 ]),
737 ]),
720 ("Jumping \n\n\n\n\nback from within a newline group selects previous block",
738 ("Jumping \n\n\n\n\nback from within a newline group selects previous block",
721 vec![
739 vec![
722− (1, Range::new(0, 13), Range::new(10, 0)),
740+ (1, Range::new(0, 13), Range::new(11, 0)),
723 ]),
741 ]),
724 ("Failed motions do not modify the range",
742 ("Failed motions do not modify the range",
725 vec![
743 vec![
@@ -727,11 +745,11 @@ mod test {
727 ]),
745 ]),
728 ("Multiple motions at once resolve correctly",
746 ("Multiple motions at once resolve correctly",
729 vec![
747 vec![
730− (3, Range::new(18, 18), Range::new(8, 0)),
748+ (3, Range::new(18, 18), Range::new(9, 0)),
731 ]),
749 ]),
732 ("Excessive motions are performed partially",
750 ("Excessive motions are performed partially",
733 vec![
751 vec![
734− (999, Range::new(40, 40), Range::new(9, 0)),
752+ (999, Range::new(40, 40), Range::new(10, 0)),
735 ]),
753 ]),
736 ("", // Edge case of moving backwards in empty string
754 ("", // Edge case of moving backwards in empty string
737 vec![
755 vec![
@@ -739,16 +757,16 @@ mod test {
739 ]),
757 ]),
740 ("\n\n\n\n\n", // Edge case of moving backwards in all newlines
758 ("\n\n\n\n\n", // Edge case of moving backwards in all newlines
741 vec![
759 vec![
742− (1, Range::new(0, 0), Range::new(0, 0)),
760+ (1, Range::new(5, 5), Range::new(0, 0)),
743 ]),
761 ]),
744 (" \n \nJumping back through alternated space blocks and newlines selects the space blocks",
762 (" \n \nJumping back through alternated space blocks and newlines selects the space blocks",
745 vec![
763 vec![
746− (1, Range::new(0, 7), Range::new(6, 4)),
764+ (1, Range::new(0, 8), Range::new(7, 4)),
747− (1, Range::new(6, 4), Range::new(2, 0)),
765+ (1, Range::new(7, 4), Range::new(3, 0)),
748 ]),
766 ]),
749 ("ヒーリクス multibyte characters behave as normal characters",
767 ("ヒーリクス multibyte characters behave as normal characters",
750 vec![
768 vec![
751− (1, Range::new(0, 5), Range::new(4, 0)),
769+ (1, Range::new(0, 6), Range::new(6, 0)),
752 ]),
770 ]),
753 ]);
771 ]);
754
772
@@ -763,72 +781,89 @@ mod test {
763 #[test]
781 #[test]
764 fn test_behaviour_when_moving_to_start_of_previous_long_words() {
782 fn test_behaviour_when_moving_to_start_of_previous_long_words() {
765 let tests = array::IntoIter::new([
783 let tests = array::IntoIter::new([
784+ (
766− ("Basic backward motion from the middle of a word",
785+ "Basic backward motion from the middle of a word",
767− vec![(1, Range::new(3, 3), Range::new(3, 0))]),
786+ vec![(1, Range::new(3, 3), Range::new(4, 0))],
787+ ),
788+
789+ // // Why do we want this behavior? The current behavior fails this
790+ // // test, but seems better and more consistent.
768− ("Starting from after boundary retreats the anchor",
791+ // ("Starting from after boundary retreats the anchor",
769− vec![(1, Range::new(0, 8), Range::new(7, 0))]),
792+ // vec![(1, Range::new(0, 9), Range::new(8, 0))]),
793+
794+ (
770− (" Jump to start of a word preceded by whitespace",
795+ " Jump to start of a word preceded by whitespace",
771− vec![(1, Range::new(5, 5), Range::new(5, 4))]),
796+ vec![(1, Range::new(5, 5), Range::new(6, 4))],
797+ ),
798+ (
772− (" Jump to start of line from start of word preceded by whitespace",
799+ " Jump to start of line from start of word preceded by whitespace",
773− vec![(1, Range::new(4, 4), Range::new(3, 0))]),
800+ vec![(1, Range::new(3, 4), Range::new(4, 0))],
801+ ),
774 ("Previous anchor is irrelevant for backward motions",
802 ("Previous anchor is irrelevant for backward motions",
775− vec![(1, Range::new(12, 5), Range::new(5, 0))]),
803+ vec![(1, Range::new(12, 5), Range::new(6, 0))]),
804+ (
776− (" Starting from whitespace moves to first space in sequence",
805+ " Starting from whitespace moves to first space in sequence",
777− vec![(1, Range::new(0, 3), Range::new(3, 0))]),
806+ vec![(1, Range::new(0, 4), Range::new(4, 0))],
807+ ),
778 ("Identifiers_with_underscores are considered a single word",
808 ("Identifiers_with_underscores are considered a single word",
779 vec![(1, Range::new(0, 20), Range::new(20, 0))]),
809 vec![(1, Range::new(0, 20), Range::new(20, 0))]),
810+ (
780− ("Jumping\n \nback through a newline selects whitespace",
811+ "Jumping\n \nback through a newline selects whitespace",
781− vec![(1, Range::new(0, 13), Range::new(11, 8))]),
812+ vec![(1, Range::new(0, 13), Range::new(12, 8))],
813+ ),
814+ (
782− ("Jumping to start of word from the end selects the word",
815+ "Jumping to start of word from the end selects the word",
783− vec![(1, Range::new(6, 6), Range::new(6, 0))]),
816+ vec![(1, Range::new(6, 7), Range::new(7, 0))],
817+ ),
818+ (
784− ("alphanumeric.!,and.?=punctuation are treated exactly the same",
819+ "alphanumeric.!,and.?=punctuation are treated exactly the same",
785− vec![
786− (1, Range::new(30, 30), Range::new(30, 0)),
820+ vec![(1, Range::new(29, 30), Range::new(30, 0))],
787− ]),
821+ ),
788−
822+ (
789− ("... ... punctuation and spaces behave as expected",
823+ "... ... punctuation and spaces behave as expected",
790 vec![
824 vec![
791− (1, Range::new(0, 10), Range::new(9, 6)),
825+ (1, Range::new(0, 10), Range::new(10, 6)),
792− (1, Range::new(9, 6), Range::new(5, 0)),
826+ (1, Range::new(10, 6), Range::new(6, 0)),
827+ ],
793− ]),
828+ ),
794 (".._.._ punctuation is joined by underscores into a single block",
829 (".._.._ punctuation is joined by underscores into a single block",
795− vec![(1, Range::new(0, 5), Range::new(4, 0))]),
830+ vec![(1, Range::new(0, 6), Range::new(6, 0))]),
831+ (
796− ("Newlines\n\nare bridged seamlessly.",
832+ "Newlines\n\nare bridged seamlessly.",
797− vec![
798− (1, Range::new(0, 10), Range::new(7, 0)),
833+ vec![(1, Range::new(0, 10), Range::new(8, 0))],
799− ]),
834+ ),
835+ (
800− ("Jumping \n\n\n\n\nback from within a newline group selects previous block",
836+ "Jumping \n\n\n\n\nback from within a newline group selects previous block",
801− vec![
802− (1, Range::new(0, 13), Range::new(10, 0)),
837+ vec![(1, Range::new(0, 13), Range::new(11, 0))],
803− ]),
838+ ),
839+ (
804− ("Failed motions do not modify the range",
840+ "Failed motions do not modify the range",
805− vec![
806− (0, Range::new(3, 0), Range::new(3, 0)),
841+ vec![(0, Range::new(3, 0), Range::new(3, 0))],
807− ]),
842+ ),
843+ (
808− ("Multiple motions at once resolve correctly",
844+ "Multiple motions at once resolve correctly",
809− vec![
810− (3, Range::new(18, 18), Range::new(8, 0)),
845+ vec![(3, Range::new(19, 19), Range::new(9, 0))],
811− ]),
846+ ),
847+ (
812− ("Excessive motions are performed partially",
848+ "Excessive motions are performed partially",
813− vec![
814− (999, Range::new(40, 40), Range::new(9, 0)),
849+ vec![(999, Range::new(40, 40), Range::new(10, 0))],
815− ]),
850+ ),
851+ (
816− ("", // Edge case of moving backwards in empty string
852+ "", // Edge case of moving backwards in empty string
817− vec![
818− (1, Range::new(0, 0), Range::new(0, 0)),
853+ vec![(1, Range::new(0, 0), Range::new(0, 0))],
819− ]),
854+ ),
855+ (
820− ("\n\n\n\n\n", // Edge case of moving backwards in all newlines
856+ "\n\n\n\n\n", // Edge case of moving backwards in all newlines
821− vec![
822− (1, Range::new(0, 0), Range::new(0, 0)),
857+ vec![(1, Range::new(5, 5), Range::new(0, 0))],
823− ]),
858+ ),
824 (" \n \nJumping back through alternated space blocks and newlines selects the space blocks",
859 (" \n \nJumping back through alternated space blocks and newlines selects the space blocks",
825 vec![
860 vec![
826− (1, Range::new(0, 7), Range::new(6, 4)),
861+ (1, Range::new(0, 8), Range::new(7, 4)),
827− (1, Range::new(6, 4), Range::new(2, 0)),
862+ (1, Range::new(7, 4), Range::new(3, 0)),
828 ]),
863 ]),
829 ("ヒーリ..クス multibyte characters behave as normal characters, including when interacting with punctuation",
864 ("ヒーリ..クス multibyte characters behave as normal characters, including when interacting with punctuation",
830 vec![
865 vec![
831− (1, Range::new(0, 7), Range::new(6, 0)),
866+ (1, Range::new(0, 8), Range::new(8, 0)),
832 ]),
867 ]),
833 ]);
868 ]);
834
869
@@ -844,42 +879,46 @@ mod test {
844 fn test_behaviour_when_moving_to_end_of_next_words() {
879 fn test_behaviour_when_moving_to_end_of_next_words() {
845 let tests = array::IntoIter::new([
880 let tests = array::IntoIter::new([
846 ("Basic forward motion from the start of a word to the end of it",
881 ("Basic forward motion from the start of a word to the end of it",
847− vec![(1, Range::new(0, 0), Range::new(0, 4))]),
882+ vec![(1, Range::new(0, 0), Range::new(0, 5))]),
848 ("Basic forward motion from the end of a word to the end of the next",
883 ("Basic forward motion from the end of a word to the end of the next",
849− vec![(1, Range::new(0, 4), Range::new(5, 12))]),
884+ vec![(1, Range::new(0, 5), Range::new(5, 13))]),
850 ("Basic forward motion from the middle of a word to the end of it",
885 ("Basic forward motion from the middle of a word to the end of it",
851− vec![(1, Range::new(2, 2), Range::new(2, 4))]),
886+ vec![(1, Range::new(2, 2), Range::new(2, 5))]),
852 (" Jumping to end of a word preceded by whitespace",
887 (" Jumping to end of a word preceded by whitespace",
853− vec![(1, Range::new(0, 0), Range::new(0, 10))]),
888+ vec![(1, Range::new(0, 0), Range::new(0, 11))]),
889+
890+ // // Why do we want this behavior? The current behavior fails this
891+ // // test, but seems better and more consistent.
854− (" Starting from a boundary advances the anchor",
892+ // (" Starting from a boundary advances the anchor",
855− vec![(1, Range::new(0, 0), Range::new(1, 8))]),
893+ // vec![(1, Range::new(0, 0), Range::new(1, 9))]),
894+
856 ("Previous anchor is irrelevant for end of word motion",
895 ("Previous anchor is irrelevant for end of word motion",
857− vec![(1, Range::new(12, 2), Range::new(2, 7))]),
896+ vec![(1, Range::new(12, 2), Range::new(2, 8))]),
858 ("Identifiers_with_underscores are considered a single word",
897 ("Identifiers_with_underscores are considered a single word",
859− vec![(1, Range::new(0, 0), Range::new(0, 27))]),
898+ vec![(1, Range::new(0, 0), Range::new(0, 28))]),
860 ("Jumping\n into starting whitespace selects up to the end of next word",
899 ("Jumping\n into starting whitespace selects up to the end of next word",
861− vec![(1, Range::new(0, 6), Range::new(8, 15))]),
900+ vec![(1, Range::new(0, 7), Range::new(8, 16))]),
862 ("alphanumeric.!,and.?=punctuation are considered 'words' for the purposes of word motion",
901 ("alphanumeric.!,and.?=punctuation are considered 'words' for the purposes of word motion",
863 vec![
902 vec![
864− (1, Range::new(0, 0), Range::new(0, 11)),
903+ (1, Range::new(0, 0), Range::new(0, 12)),
865− (1, Range::new(0, 11), Range::new(12, 14)),
904+ (1, Range::new(0, 12), Range::new(12, 15)),
866− (1, Range::new(12, 14), Range::new(15, 17))
905+ (1, Range::new(12, 15), Range::new(15, 18))
867 ]),
906 ]),
868 ("... ... punctuation and spaces behave as expected",
907 ("... ... punctuation and spaces behave as expected",
869 vec![
908 vec![
870− (1, Range::new(0, 0), Range::new(0, 2)),
909+ (1, Range::new(0, 0), Range::new(0, 3)),
871− (1, Range::new(0, 2), Range::new(3, 8)),
910+ (1, Range::new(0, 3), Range::new(3, 9)),
872 ]),
911 ]),
873 (".._.._ punctuation is not joined by underscores into a single block",
912 (".._.._ punctuation is not joined by underscores into a single block",
874− vec![(1, Range::new(0, 0), Range::new(0, 1))]),
913+ vec![(1, Range::new(0, 0), Range::new(0, 2))]),
875 ("Newlines\n\nare bridged seamlessly.",
914 ("Newlines\n\nare bridged seamlessly.",
876 vec![
915 vec![
877− (1, Range::new(0, 0), Range::new(0, 7)),
916+ (1, Range::new(0, 0), Range::new(0, 8)),
878− (1, Range::new(0, 7), Range::new(10, 12)),
917+ (1, Range::new(0, 8), Range::new(10, 13)),
879 ]),
918 ]),
880 ("Jumping\n\n\n\n\n\n from newlines to whitespace selects to end of next word.",
919 ("Jumping\n\n\n\n\n\n from newlines to whitespace selects to end of next word.",
881 vec![
920 vec![
882− (1, Range::new(0, 8), Range::new(13, 19)),
921+ (1, Range::new(0, 8), Range::new(13, 20)),
883 ]),
922 ]),
884 ("A failed motion does not modify the range",
923 ("A failed motion does not modify the range",
885 vec![
924 vec![
@@ -887,11 +926,11 @@ mod test {
887 ]),
926 ]),
888 ("Multiple motions at once resolve correctly",
927 ("Multiple motions at once resolve correctly",
889 vec![
928 vec![
890− (3, Range::new(0, 0), Range::new(16, 18)),
929+ (3, Range::new(0, 0), Range::new(16, 19)),
891 ]),
930 ]),
892 ("Excessive motions are performed partially",
931 ("Excessive motions are performed partially",
893 vec![
932 vec![
894− (999, Range::new(0, 0), Range::new(31, 40)),
933+ (999, Range::new(0, 0), Range::new(31, 41)),
895 ]),
934 ]),
896 ("", // Edge case of moving forward in empty string
935 ("", // Edge case of moving forward in empty string
897 vec![
936 vec![
@@ -899,16 +938,16 @@ mod test {
899 ]),
938 ]),
900 ("\n\n\n\n\n", // Edge case of moving forward in all newlines
939 ("\n\n\n\n\n", // Edge case of moving forward in all newlines
901 vec![
940 vec![
902− (1, Range::new(0, 0), Range::new(0, 4)),
941+ (1, Range::new(0, 0), Range::new(5, 5)),
903 ]),
942 ]),
904 ("\n \n \n Jumping through alternated space blocks and newlines selects the space blocks",
943 ("\n \n \n Jumping through alternated space blocks and newlines selects the space blocks",
905 vec![
944 vec![
906− (1, Range::new(0, 0), Range::new(1, 3)),
945+ (1, Range::new(0, 0), Range::new(1, 4)),
907− (1, Range::new(1, 3), Range::new(5, 7)),
946+ (1, Range::new(1, 4), Range::new(5, 8)),
908 ]),
947 ]),
909 ("ヒーリクス multibyte characters behave as normal characters",
948 ("ヒーリクス multibyte characters behave as normal characters",
910 vec![
949 vec![
911− (1, Range::new(0, 0), Range::new(0, 4)),
950+ (1, Range::new(0, 0), Range::new(0, 5)),
912 ]),
951 ]),
913 ]);
952 ]);
914
953
@@ -924,44 +963,44 @@ mod test {
924 fn test_behaviour_when_moving_to_end_of_previous_words() {
963 fn test_behaviour_when_moving_to_end_of_previous_words() {
925 let tests = array::IntoIter::new([
964 let tests = array::IntoIter::new([
926 ("Basic backward motion from the middle of a word",
965 ("Basic backward motion from the middle of a word",
927− vec![(1, Range::new(9, 9), Range::new(9, 5))]),
966+ vec![(1, Range::new(9, 9), Range::new(10, 5))]),
928 ("Starting from after boundary retreats the anchor",
967 ("Starting from after boundary retreats the anchor",
929− vec![(1, Range::new(0, 13), Range::new(12, 8))]),
968+ vec![(1, Range::new(0, 14), Range::new(13, 8))]),
930 ("Jump to end of a word succeeded by whitespace",
969 ("Jump to end of a word succeeded by whitespace",
931− vec![(1, Range::new(10, 10), Range::new(10, 4))]),
970+ vec![(1, Range::new(11, 11), Range::new(11, 4))]),
932 (" Jump to start of line from end of word preceded by whitespace",
971 (" Jump to start of line from end of word preceded by whitespace",
933− vec![(1, Range::new(7, 7), Range::new(7, 0))]),
972+ vec![(1, Range::new(8, 8), Range::new(8, 0))]),
934 ("Previous anchor is irrelevant for backward motions",
973 ("Previous anchor is irrelevant for backward motions",
935− vec![(1, Range::new(26, 12), Range::new(12, 8))]),
974+ vec![(1, Range::new(26, 12), Range::new(13, 8))]),
936 (" Starting from whitespace moves to first space in sequence",
975 (" Starting from whitespace moves to first space in sequence",
937− vec![(1, Range::new(0, 3), Range::new(3, 0))]),
976+ vec![(1, Range::new(0, 4), Range::new(4, 0))]),
938 ("Test identifiers_with_underscores are considered a single word",
977 ("Test identifiers_with_underscores are considered a single word",
939 vec![(1, Range::new(0, 25), Range::new(25, 4))]),
978 vec![(1, Range::new(0, 25), Range::new(25, 4))]),
940 ("Jumping\n \nback through a newline selects whitespace",
979 ("Jumping\n \nback through a newline selects whitespace",
941− vec![(1, Range::new(0, 13), Range::new(11, 8))]),
980+ vec![(1, Range::new(0, 13), Range::new(12, 8))]),
942 ("Jumping to start of word from the end selects the whole word",
981 ("Jumping to start of word from the end selects the whole word",
943− vec![(1, Range::new(15, 15), Range::new(15, 10))]),
982+ vec![(1, Range::new(16, 16), Range::new(16, 10))]),
944 ("alphanumeric.!,and.?=punctuation are considered 'words' for the purposes of word motion",
983 ("alphanumeric.!,and.?=punctuation are considered 'words' for the purposes of word motion",
945 vec![
984 vec![
946− (1, Range::new(30, 30), Range::new(30, 21)),
985+ (1, Range::new(30, 30), Range::new(31, 21)),
947− (1, Range::new(30, 21), Range::new(20, 18)),
986+ (1, Range::new(31, 21), Range::new(21, 18)),
948− (1, Range::new(20, 18), Range::new(17, 15))
987+ (1, Range::new(21, 18), Range::new(18, 15))
949 ]),
988 ]),
950
989
951 ("... ... punctuation and spaces behave as expected",
990 ("... ... punctuation and spaces behave as expected",
952 vec![
991 vec![
953− (1, Range::new(0, 10), Range::new(9, 9)),
992+ (1, Range::new(0, 10), Range::new(9, 3)),
954− (1, Range::new(9, 6), Range::new(5, 3)),
993+ (1, Range::new(9, 3), Range::new(3, 0)),
955 ]),
994 ]),
956 (".._.._ punctuation is not joined by underscores into a single block",
995 (".._.._ punctuation is not joined by underscores into a single block",
957− vec![(1, Range::new(0, 5), Range::new(4, 3))]),
996+ vec![(1, Range::new(0, 5), Range::new(5, 3))]),
958 ("Newlines\n\nare bridged seamlessly.",
997 ("Newlines\n\nare bridged seamlessly.",
959 vec![
998 vec![
960− (1, Range::new(0, 10), Range::new(7, 0)),
999+ (1, Range::new(0, 10), Range::new(8, 0)),
961 ]),
1000 ]),
962 ("Jumping \n\n\n\n\nback from within a newline group selects previous block",
1001 ("Jumping \n\n\n\n\nback from within a newline group selects previous block",
963 vec![
1002 vec![
964− (1, Range::new(0, 13), Range::new(10, 7)),
1003+ (1, Range::new(0, 13), Range::new(11, 7)),
965 ]),
1004 ]),
966 ("Failed motions do not modify the range",
1005 ("Failed motions do not modify the range",
967 vec![
1006 vec![
@@ -969,11 +1008,11 @@ mod test {
969 ]),
1008 ]),
970 ("Multiple motions at once resolve correctly",
1009 ("Multiple motions at once resolve correctly",
971 vec![
1010 vec![
972− (3, Range::new(23, 23), Range::new(15, 8)),
1011+ (3, Range::new(24, 24), Range::new(16, 8)),
973 ]),
1012 ]),
974 ("Excessive motions are performed partially",
1013 ("Excessive motions are performed partially",
975 vec![
1014 vec![
976− (999, Range::new(40, 40), Range::new(8, 0)),
1015+ (999, Range::new(40, 40), Range::new(9, 0)),
977 ]),
1016 ]),
978 ("", // Edge case of moving backwards in empty string
1017 ("", // Edge case of moving backwards in empty string
979 vec![
1018 vec![
@@ -981,16 +1020,16 @@ mod test {
981 ]),
1020 ]),
982 ("\n\n\n\n\n", // Edge case of moving backwards in all newlines
1021 ("\n\n\n\n\n", // Edge case of moving backwards in all newlines
983 vec![
1022 vec![
984− (1, Range::new(0, 0), Range::new(0, 0)),
1023+ (1, Range::new(5, 5), Range::new(0, 0)),
985 ]),
1024 ]),
986 (" \n \nJumping back through alternated space blocks and newlines selects the space blocks",
1025 (" \n \nJumping back through alternated space blocks and newlines selects the space blocks",
987 vec![
1026 vec![
988− (1, Range::new(0, 7), Range::new(6, 4)),
1027+ (1, Range::new(0, 8), Range::new(7, 4)),
989− (1, Range::new(6, 4), Range::new(2, 0)),
1028+ (1, Range::new(7, 4), Range::new(3, 0)),
990 ]),
1029 ]),
991 ("Test ヒーリクス multibyte characters behave as normal characters",
1030 ("Test ヒーリクス multibyte characters behave as normal characters",
992 vec![
1031 vec![
993− (1, Range::new(0, 9), Range::new(9, 4)),
1032+ (1, Range::new(0, 10), Range::new(10, 4)),
994 ]),
1033 ]),
995 ]);
1034 ]);
996
1035
@@ -1006,40 +1045,44 @@ mod test {
1006 fn test_behaviour_when_moving_to_end_of_next_long_words() {
1045 fn test_behaviour_when_moving_to_end_of_next_long_words() {
1007 let tests = array::IntoIter::new([
1046 let tests = array::IntoIter::new([
1008 ("Basic forward motion from the start of a word to the end of it",
1047 ("Basic forward motion from the start of a word to the end of it",
1009− vec![(1, Range::new(0, 0), Range::new(0, 4))]),
1048+ vec![(1, Range::new(0, 0), Range::new(0, 5))]),
1010 ("Basic forward motion from the end of a word to the end of the next",
1049 ("Basic forward motion from the end of a word to the end of the next",
1011− vec![(1, Range::new(0, 4), Range::new(5, 12))]),
1050+ vec![(1, Range::new(0, 5), Range::new(5, 13))]),
1012 ("Basic forward motion from the middle of a word to the end of it",
1051 ("Basic forward motion from the middle of a word to the end of it",
1013− vec![(1, Range::new(2, 2), Range::new(2, 4))]),
1052+ vec![(1, Range::new(2, 2), Range::new(2, 5))]),
1014 (" Jumping to end of a word preceded by whitespace",
1053 (" Jumping to end of a word preceded by whitespace",
1015− vec![(1, Range::new(0, 0), Range::new(0, 10))]),
1054+ vec![(1, Range::new(0, 0), Range::new(0, 11))]),
1055+
1056+ // // Why do we want this behavior? The current behavior fails this
1057+ // // test, but seems better and more consistent.
1016− (" Starting from a boundary advances the anchor",
1058+ // (" Starting from a boundary advances the anchor",
1017− vec![(1, Range::new(0, 0), Range::new(1, 8))]),
1059+ // vec![(1, Range::new(0, 0), Range::new(1, 9))]),
1060+
1018 ("Previous anchor is irrelevant for end of word motion",
1061 ("Previous anchor is irrelevant for end of word motion",
1019− vec![(1, Range::new(12, 2), Range::new(2, 7))]),
1062+ vec![(1, Range::new(12, 2), Range::new(2, 8))]),
1020 ("Identifiers_with_underscores are considered a single word",
1063 ("Identifiers_with_underscores are considered a single word",
1021− vec![(1, Range::new(0, 0), Range::new(0, 27))]),
1064+ vec![(1, Range::new(0, 0), Range::new(0, 28))]),
1022 ("Jumping\n into starting whitespace selects up to the end of next word",
1065 ("Jumping\n into starting whitespace selects up to the end of next word",
1023− vec![(1, Range::new(0, 6), Range::new(8, 15))]),
1066+ vec![(1, Range::new(0, 7), Range::new(8, 16))]),
1024 ("alphanumeric.!,and.?=punctuation are treated the same way",
1067 ("alphanumeric.!,and.?=punctuation are treated the same way",
1025 vec![
1068 vec![
1026− (1, Range::new(0, 0), Range::new(0, 31)),
1069+ (1, Range::new(0, 0), Range::new(0, 32)),
1027 ]),
1070 ]),
1028 ("... ... punctuation and spaces behave as expected",
1071 ("... ... punctuation and spaces behave as expected",
1029 vec![
1072 vec![
1030− (1, Range::new(0, 0), Range::new(0, 2)),
1073+ (1, Range::new(0, 0), Range::new(0, 3)),
1031− (1, Range::new(0, 2), Range::new(3, 8)),
1074+ (1, Range::new(0, 3), Range::new(3, 9)),
1032 ]),
1075 ]),
1033 (".._.._ punctuation is joined by underscores into a single block",
1076 (".._.._ punctuation is joined by underscores into a single block",
1034− vec![(1, Range::new(0, 0), Range::new(0, 5))]),
1077+ vec![(1, Range::new(0, 0), Range::new(0, 6))]),
1035 ("Newlines\n\nare bridged seamlessly.",
1078 ("Newlines\n\nare bridged seamlessly.",
1036 vec![
1079 vec![
1037− (1, Range::new(0, 0), Range::new(0, 7)),
1080+ (1, Range::new(0, 0), Range::new(0, 8)),
1038− (1, Range::new(0, 7), Range::new(10, 12)),
1081+ (1, Range::new(0, 8), Range::new(10, 13)),
1039 ]),
1082 ]),
1040 ("Jumping\n\n\n\n\n\n from newlines to whitespace selects to end of next word.",
1083 ("Jumping\n\n\n\n\n\n from newlines to whitespace selects to end of next word.",
1041 vec![
1084 vec![
1042− (1, Range::new(0, 8), Range::new(13, 19)),
1085+ (1, Range::new(0, 9), Range::new(13, 20)),
1043 ]),
1086 ]),
1044 ("A failed motion does not modify the range",
1087 ("A failed motion does not modify the range",
1045 vec![
1088 vec![
@@ -1047,11 +1090,11 @@ mod test {
1047 ]),
1090 ]),
1048 ("Multiple motions at once resolve correctly",
1091 ("Multiple motions at once resolve correctly",
1049 vec![
1092 vec![
1050− (3, Range::new(0, 0), Range::new(16, 18)),
1093+ (3, Range::new(0, 0), Range::new(16, 19)),
1051 ]),
1094 ]),
1052 ("Excessive motions are performed partially",
1095 ("Excessive motions are performed partially",
1053 vec![
1096 vec![
1054− (999, Range::new(0, 0), Range::new(31, 40)),
1097+ (999, Range::new(0, 0), Range::new(31, 41)),
1055 ]),
1098 ]),
1056 ("", // Edge case of moving forward in empty string
1099 ("", // Edge case of moving forward in empty string
1057 vec![
1100 vec![
@@ -1059,16 +1102,16 @@ mod test {
1059 ]),
1102 ]),
1060 ("\n\n\n\n\n", // Edge case of moving forward in all newlines
1103 ("\n\n\n\n\n", // Edge case of moving forward in all newlines
1061 vec![
1104 vec![
1062− (1, Range::new(0, 0), Range::new(0, 4)),
1105+ (1, Range::new(0, 0), Range::new(5, 5)),
1063 ]),
1106 ]),
1064 ("\n \n \n Jumping through alternated space blocks and newlines selects the space blocks",
1107 ("\n \n \n Jumping through alternated space blocks and newlines selects the space blocks",
1065 vec![
1108 vec![
1066− (1, Range::new(0, 0), Range::new(1, 3)),
1109+ (1, Range::new(0, 0), Range::new(1, 4)),
1067− (1, Range::new(1, 3), Range::new(5, 7)),
1110+ (1, Range::new(1, 4), Range::new(5, 8)),
1068 ]),
1111 ]),
1069 ("ヒーリ..クス multibyte characters behave as normal characters, including when they interact with punctuation",
1112 ("ヒーリ..クス multibyte characters behave as normal characters, including when they interact with punctuation",
1070 vec![
1113 vec![
1071− (1, Range::new(0, 0), Range::new(0, 6)),
1114+ (1, Range::new(0, 0), Range::new(0, 7)),
1072 ]),
1115 ]),
1073 ]);
1116 ]);
1074
1117
helix-core/src/object.rs+1−1
@@ -5,7 +5,7 @@ use crate::{Range, RopeSlice, Selection, Syntax};
5pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
5pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
6 let tree = syntax.tree();
6 let tree = syntax.tree();
7
7
8− selection.transform(|range| {
8+ selection.clone().transform(|range| {
9 let from = text.char_to_byte(range.from());
9 let from = text.char_to_byte(range.from());
10 let to = text.char_to_byte(range.to());
10 let to = text.char_to_byte(range.to());
11
11
helix-core/src/position.rs+115−29
@@ -1,6 +1,7 @@
1use crate::{
1use crate::{
2 chars::char_is_line_ending,
2 chars::char_is_line_ending,
3− graphemes::{nth_next_grapheme_boundary, RopeGraphemes},
3+ graphemes::{ensure_grapheme_boundary_prev, RopeGraphemes},
4+ line_ending::line_end_char_index,
4 RopeSlice,
5 RopeSlice,
5};
6};
6
7
@@ -52,19 +53,50 @@ impl From<Position> for tree_sitter::Point {
52 }
53 }
53}
54}
54/// Convert a character index to (line, column) coordinates.
55/// Convert a character index to (line, column) coordinates.
56+///
57+/// TODO: this should be split into two methods: one for visual
58+/// row/column, and one for "objective" row/column (possibly with
59+/// the column specified in `char`s). The former would be used
60+/// for cursor movement, and the latter would be used for e.g. the
61+/// row:column display in the status line.
55pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position {
62pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position {
56 let line = text.char_to_line(pos);
63 let line = text.char_to_line(pos);
64+
57 let line_start = text.line_to_char(line);
65 let line_start = text.line_to_char(line);
66+ let pos = ensure_grapheme_boundary_prev(text, pos);
58 let col = RopeGraphemes::new(text.slice(line_start..pos)).count();
67 let col = RopeGraphemes::new(text.slice(line_start..pos)).count();
68+
59 Position::new(line, col)
69 Position::new(line, col)
60}
70}
61
71
62/// Convert (line, column) coordinates to a character index.
72/// Convert (line, column) coordinates to a character index.
73+///
74+/// `is_1_width` specifies whether the position should be treated
75+/// as a block cursor or not. This effects how line-ends are handled.
76+/// `false` corresponds to properly round-tripping with `coords_at_pos()`,
77+/// whereas `true` will ensure that block cursors don't jump off the
78+/// end of the line.
79+///
80+/// TODO: this should be changed to work in terms of visual row/column, not
81+/// graphemes.
63−pub fn pos_at_coords(text: RopeSlice, coords: Position) -> usize {
82+pub fn pos_at_coords(text: RopeSlice, coords: Position, is_1_width: bool) -> usize {
64 let Position { row, col } = coords;
83 let Position { row, col } = coords;
65 let line_start = text.line_to_char(row);
84 let line_start = text.line_to_char(row);
66− // line_start + col
85+ let line_end = if is_1_width {
67− nth_next_grapheme_boundary(text, line_start, col)
86+ line_end_char_index(&text, row)
87+ } else {
88+ text.line_to_char((row + 1).min(text.len_lines()))
89+ };
90+
91+ let mut col_char_offset = 0;
92+ for (i, g) in RopeGraphemes::new(text.slice(line_start..line_end)).enumerate() {
93+ if i == col {
94+ break;
95+ }
96+ col_char_offset += g.chars().count();
97+ }
98+
99+ line_start + col_char_offset
68}
100}
69
101
70#[cfg(test)]
102#[cfg(test)]
@@ -80,55 +112,109 @@ mod test {
80
112
81 #[test]
113 #[test]
82 fn test_coords_at_pos() {
114 fn test_coords_at_pos() {
83− // let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
115+ let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
84− // let slice = text.slice(..);
116+ let slice = text.slice(..);
85− // assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
117+ assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
86− // assert_eq!(coords_at_pos(slice, 5), (0, 5).into()); // position on \n
118+ assert_eq!(coords_at_pos(slice, 5), (0, 5).into()); // position on \n
87− // assert_eq!(coords_at_pos(slice, 6), (1, 0).into()); // position on w
119+ assert_eq!(coords_at_pos(slice, 6), (1, 0).into()); // position on w
88− // assert_eq!(coords_at_pos(slice, 7), (1, 1).into()); // position on o
120+ assert_eq!(coords_at_pos(slice, 7), (1, 1).into()); // position on o
89− // assert_eq!(coords_at_pos(slice, 10), (1, 4).into()); // position on d
121+ assert_eq!(coords_at_pos(slice, 10), (1, 4).into()); // position on d
90−
122+
123+ // Test with wide characters.
124+ // TODO: account for character width.
125+ let text = Rope::from("今日はいい\n");
126+ let slice = text.slice(..);
127+ assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
128+ assert_eq!(coords_at_pos(slice, 1), (0, 1).into());
129+ assert_eq!(coords_at_pos(slice, 2), (0, 2).into());
130+ assert_eq!(coords_at_pos(slice, 3), (0, 3).into());
131+ assert_eq!(coords_at_pos(slice, 4), (0, 4).into());
132+ assert_eq!(coords_at_pos(slice, 5), (0, 5).into());
133+ assert_eq!(coords_at_pos(slice, 6), (1, 0).into());
134+
91− // test with grapheme clusters
135+ // Test with grapheme clusters.
92 let text = Rope::from("a̐éö̲\r\n");
136 let text = Rope::from("a̐éö̲\r\n");
93 let slice = text.slice(..);
137 let slice = text.slice(..);
94 assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
138 assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
95 assert_eq!(coords_at_pos(slice, 2), (0, 1).into());
139 assert_eq!(coords_at_pos(slice, 2), (0, 1).into());
96 assert_eq!(coords_at_pos(slice, 4), (0, 2).into());
140 assert_eq!(coords_at_pos(slice, 4), (0, 2).into());
97 assert_eq!(coords_at_pos(slice, 7), (0, 3).into());
141 assert_eq!(coords_at_pos(slice, 7), (0, 3).into());
142+ assert_eq!(coords_at_pos(slice, 9), (1, 0).into());
98
143
144+ // Test with wide-character grapheme clusters.
145+ // TODO: account for character width.
99− let text = Rope::from("किमपि");
146+ let text = Rope::from("किमपि\n");
100 let slice = text.slice(..);
147 let slice = text.slice(..);
101 assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
148 assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
102 assert_eq!(coords_at_pos(slice, 2), (0, 1).into());
149 assert_eq!(coords_at_pos(slice, 2), (0, 1).into());
103 assert_eq!(coords_at_pos(slice, 3), (0, 2).into());
150 assert_eq!(coords_at_pos(slice, 3), (0, 2).into());
104 assert_eq!(coords_at_pos(slice, 5), (0, 3).into());
151 assert_eq!(coords_at_pos(slice, 5), (0, 3).into());
152+ assert_eq!(coords_at_pos(slice, 6), (1, 0).into());
153+
154+ // Test with tabs.
155+ // Todo: account for tab stops.
156+ let text = Rope::from("\tHello\n");
157+ let slice = text.slice(..);
158+ assert_eq!(coords_at_pos(slice, 0), (0, 0).into());
159+ assert_eq!(coords_at_pos(slice, 1), (0, 1).into());
160+ assert_eq!(coords_at_pos(slice, 2), (0, 2).into());
105 }
161 }
106
162
107 #[test]
163 #[test]
108 fn test_pos_at_coords() {
164 fn test_pos_at_coords() {
109 let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
165 let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
110 let slice = text.slice(..);
166 let slice = text.slice(..);
111− assert_eq!(pos_at_coords(slice, (0, 0).into()), 0);
167+ assert_eq!(pos_at_coords(slice, (0, 0).into(), false), 0);
112− assert_eq!(pos_at_coords(slice, (0, 5).into()), 5); // position on \n
168+ assert_eq!(pos_at_coords(slice, (0, 5).into(), false), 5); // position on \n
113− assert_eq!(pos_at_coords(slice, (1, 0).into()), 6); // position on w
169+ assert_eq!(pos_at_coords(slice, (0, 6).into(), false), 6); // position after \n
170+ assert_eq!(pos_at_coords(slice, (0, 6).into(), true), 5); // position after \n
171+ assert_eq!(pos_at_coords(slice, (1, 0).into(), false), 6); // position on w
114− assert_eq!(pos_at_coords(slice, (1, 1).into()), 7); // position on o
172+ assert_eq!(pos_at_coords(slice, (1, 1).into(), false), 7); // position on o
115− assert_eq!(pos_at_coords(slice, (1, 4).into()), 10); // position on d
173+ assert_eq!(pos_at_coords(slice, (1, 4).into(), false), 10); // position on d
116
174
175+ // Test with wide characters.
176+ // TODO: account for character width.
177+ let text = Rope::from("今日はいい\n");
178+ let slice = text.slice(..);
179+ assert_eq!(pos_at_coords(slice, (0, 0).into(), false), 0);
180+ assert_eq!(pos_at_coords(slice, (0, 1).into(), false), 1);
181+ assert_eq!(pos_at_coords(slice, (0, 2).into(), false), 2);
182+ assert_eq!(pos_at_coords(slice, (0, 3).into(), false), 3);
183+ assert_eq!(pos_at_coords(slice, (0, 4).into(), false), 4);
184+ assert_eq!(pos_at_coords(slice, (0, 5).into(), false), 5);
185+ assert_eq!(pos_at_coords(slice, (0, 6).into(), false), 6);
186+ assert_eq!(pos_at_coords(slice, (0, 6).into(), true), 5);
187+ assert_eq!(pos_at_coords(slice, (1, 0).into(), false), 6);
188+
117− // test with grapheme clusters
189+ // Test with grapheme clusters.
118 let text = Rope::from("a̐éö̲\r\n");
190 let text = Rope::from("a̐éö̲\r\n");
119 let slice = text.slice(..);
191 let slice = text.slice(..);
120− assert_eq!(pos_at_coords(slice, (0, 0).into()), 0);
192+ assert_eq!(pos_at_coords(slice, (0, 0).into(), false), 0);
121− assert_eq!(pos_at_coords(slice, (0, 1).into()), 2);
193+ assert_eq!(pos_at_coords(slice, (0, 1).into(), false), 2);
122− assert_eq!(pos_at_coords(slice, (0, 2).into()), 4);
194+ assert_eq!(pos_at_coords(slice, (0, 2).into(), false), 4);
123− assert_eq!(pos_at_coords(slice, (0, 3).into()), 7); // \r\n is one char here
195+ assert_eq!(pos_at_coords(slice, (0, 3).into(), false), 7); // \r\n is one char here
124− assert_eq!(pos_at_coords(slice, (0, 4).into()), 9);
196+ assert_eq!(pos_at_coords(slice, (0, 4).into(), false), 9);
197+ assert_eq!(pos_at_coords(slice, (0, 4).into(), true), 7);
198+ assert_eq!(pos_at_coords(slice, (1, 0).into(), false), 9);
199+
200+ // Test with wide-character grapheme clusters.
201+ // TODO: account for character width.
125 let text = Rope::from("किमपि");
202 let text = Rope::from("किमपि");
126 // 2 - 1 - 2 codepoints
203 // 2 - 1 - 2 codepoints
127 // TODO: delete handling as per https://news.ycombinator.com/item?id=20058454
204 // TODO: delete handling as per https://news.ycombinator.com/item?id=20058454
128 let slice = text.slice(..);
205 let slice = text.slice(..);
129− assert_eq!(pos_at_coords(slice, (0, 0).into()), 0);
206+ assert_eq!(pos_at_coords(slice, (0, 0).into(), false), 0);
130− assert_eq!(pos_at_coords(slice, (0, 1).into()), 2);
207+ assert_eq!(pos_at_coords(slice, (0, 1).into(), false), 2);
131− assert_eq!(pos_at_coords(slice, (0, 2).into()), 3);
208+ assert_eq!(pos_at_coords(slice, (0, 2).into(), false), 3);
132− assert_eq!(pos_at_coords(slice, (0, 3).into()), 5); // eol
209+ assert_eq!(pos_at_coords(slice, (0, 3).into(), false), 5);
210+ assert_eq!(pos_at_coords(slice, (0, 3).into(), true), 5);
211+
212+ // Test with tabs.
213+ // Todo: account for tab stops.
214+ let text = Rope::from("\tHello\n");
215+ let slice = text.slice(..);
216+ assert_eq!(pos_at_coords(slice, (0, 0).into(), false), 0);
217+ assert_eq!(pos_at_coords(slice, (0, 1).into(), false), 1);
218+ assert_eq!(pos_at_coords(slice, (0, 2).into(), false), 2);
133 }
219 }
134}
220}
helix-core/src/search.rs+10−28
@@ -1,18 +1,11 @@
1use crate::RopeSlice;
1use crate::RopeSlice;
2
2
3−pub fn find_nth_next(
3+pub fn find_nth_next(text: RopeSlice, ch: char, mut pos: usize, n: usize) -> Option<usize> {
4− text: RopeSlice,
5− ch: char,
6− mut pos: usize,
7− n: usize,
8− inclusive: bool,
9−) -> Option<usize> {
10− if pos >= text.len_chars() {
4+ if pos >= text.len_chars() || n == 0 {
11 return None;
5 return None;
12 }
6 }
13
7
14− // start searching right after pos
15− let mut chars = text.chars_at(pos + 1);
8+ let mut chars = text.chars_at(pos);
16
9
17 for _ in 0..n {
10 for _ in 0..n {
18 loop {
11 loop {
@@ -26,28 +19,21 @@ pub fn find_nth_next(
26 }
19 }
27 }
20 }
28
21
29− if !inclusive {
30− pos -= 1;
31− }
32−
33− Some(pos)
22+ Some(pos - 1)
34}
23}
35
24
36−pub fn find_nth_prev(
25+pub fn find_nth_prev(text: RopeSlice, ch: char, mut pos: usize, n: usize) -> Option<usize> {
37− text: RopeSlice,
26+ if pos == 0 || n == 0 {
38− ch: char,
27+ return None;
39− mut pos: usize,
28+ }
40− n: usize,
29+
41− inclusive: bool,
42−) -> Option<usize> {
43− // start searching right before pos
44 let mut chars = text.chars_at(pos);
30 let mut chars = text.chars_at(pos);
45
31
46 for _ in 0..n {
32 for _ in 0..n {
47 loop {
33 loop {
48 let c = chars.prev()?;
34 let c = chars.prev()?;
49
35
50− pos = pos.saturating_sub(1);
36+ pos -= 1;
51
37
52 if c == ch {
38 if c == ch {
53 break;
39 break;
@@ -55,9 +41,5 @@ pub fn find_nth_prev(
55 }
41 }
56 }
42 }
57
43
58− if !inclusive {
59− pos += 1;
60− }
61−
62 Some(pos)
44 Some(pos)
63}
45}
helix-core/src/selection.rs+529−133
@@ -1,30 +1,59 @@
1−//! Selections are the primary editing construct. Even a single cursor is defined as an empty
1+//! Selections are the primary editing construct. Even cursors are
2−//! single selection range.
2+//! defined as a selection range.
3//!
3//!
4//! All positioning is done via `char` offsets into the buffer.
4//! All positioning is done via `char` offsets into the buffer.
5+use crate::{
6+ graphemes::{
7+ ensure_grapheme_boundary_next, ensure_grapheme_boundary_prev, next_grapheme_boundary,
8+ prev_grapheme_boundary,
9+ },
5−use crate::{Assoc, ChangeSet, RopeSlice};
10+ Assoc, ChangeSet, RopeSlice,
11+};
6use smallvec::{smallvec, SmallVec};
12use smallvec::{smallvec, SmallVec};
7use std::borrow::Cow;
13use std::borrow::Cow;
8
14
9−#[inline]
15+/// A single selection range.
10−fn abs_difference(x: usize, y: usize) -> usize {
16+///
11− if x < y {
17+/// A range consists of an "anchor" and "head" position in
12− y - x
18+/// the text. The head is the part that the user moves when
13− } else {
19+/// directly extending a selection. The head and anchor
14− x - y
20+/// can be in any order, or even share the same position.
15− }
21+///
16−}
22+/// The anchor and head positions use gap indexing, meaning
17−
23+/// that their indices represent the the gaps *between* `char`s
18−/// A single selection range. Anchor-inclusive, head-exclusive.
24+/// rather than the `char`s themselves. For example, 1
25+/// represents the position between the first and second `char`.
26+///
27+/// Below are some example `Range` configurations to better
28+/// illustrate. The anchor and head indices are show as
29+/// "(anchor, head)", followed by example text with "[" and "]"
30+/// inserted to represent the anchor and head positions:
31+///
32+/// - (0, 3): [Som]e text.
33+/// - (3, 0): ]Som[e text.
34+/// - (2, 7): So[me te]xt.
35+/// - (1, 1): S[]ome text.
36+///
37+/// Ranges are considered to be inclusive on the left and
38+/// exclusive on the right, regardless of anchor-head ordering.
39+/// This means, for example, that non-zero-width ranges that
40+/// are directly adjecent, sharing an edge, do not overlap.
41+/// However, a zero-width range will overlap with the shared
42+/// left-edge of another range.
43+///
44+/// By convention, user-facing ranges are considered to have
45+/// a block cursor on the head-side of the range that spans a
46+/// single grapheme inward from the range's edge. There are a
47+/// variety of helper methods on `Range` for working in terms of
48+/// that block cursor, all of which have `cursor` in their name.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct Range {
50pub struct Range {
21− // TODO: optimize into u32
22 /// The anchor of the range: the side that doesn't move when extending.
51 /// The anchor of the range: the side that doesn't move when extending.
23 pub anchor: usize,
52 pub anchor: usize,
24 /// The head of the range, moved when extending.
53 /// The head of the range, moved when extending.
25 pub head: usize,
54 pub head: usize,
26 pub horiz: Option<u32>,
55 pub horiz: Option<u32>,
27−} // TODO: might be cheaper to store normalized as from/to and an inverted flag
56+}
28
57
29impl Range {
58impl Range {
30 pub fn new(anchor: usize, head: usize) -> Self {
59 pub fn new(anchor: usize, head: usize) -> Self {
@@ -53,6 +82,20 @@ impl Range {
53 std::cmp::max(self.anchor, self.head)
82 std::cmp::max(self.anchor, self.head)
54 }
83 }
55
84
85+ /// The (inclusive) range of lines that the range overlaps.
86+ #[inline]
87+ #[must_use]
88+ pub fn line_range(&self, text: RopeSlice) -> (usize, usize) {
89+ let from = self.from();
90+ let to = if self.is_empty() {
91+ self.to()
92+ } else {
93+ prev_grapheme_boundary(text, self.to()).max(from)
94+ };
95+
96+ (text.char_to_line(from), text.char_to_line(to))
97+ }
98+
56 /// `true` when head and anchor are at the same position.
99 /// `true` when head and anchor are at the same position.
57 #[inline]
100 #[inline]
58 pub fn is_empty(&self) -> bool {
101 pub fn is_empty(&self) -> bool {
@@ -62,37 +105,39 @@ impl Range {
62 /// Check two ranges for overlap.
105 /// Check two ranges for overlap.
63 #[must_use]
106 #[must_use]
64 pub fn overlaps(&self, other: &Self) -> bool {
107 pub fn overlaps(&self, other: &Self) -> bool {
65− // cursor overlap is checked differently
108+ // To my eye, it's non-obvious why this works, but I arrived
66− if self.is_empty() {
109+ // at it after transforming the slower version that explicitly
67− let pos = self.head;
110+ // enumerated more cases. The unit tests are thorough.
68− pos >= other.from() && other.to() >= pos
69− } else {
70− self.to() > other.from() && other.to() > self.from()
111+ self.from() == other.from() || (self.to() > other.from() && other.to() > self.from())
71− }
72 }
112 }
73
113
74 pub fn contains(&self, pos: usize) -> bool {
114 pub fn contains(&self, pos: usize) -> bool {
75− if self.is_empty() {
76− return false;
77− }
78−
79− if self.anchor < self.head {
80− self.anchor <= pos && pos < self.head
115+ self.from() <= pos && pos < self.to()
81− } else {
82− self.head < pos && pos <= self.anchor
83− }
84 }
116 }
85
117
86 /// Map a range through a set of changes. Returns a new range representing the same position
118 /// Map a range through a set of changes. Returns a new range representing the same position
87 /// after the changes are applied.
119 /// after the changes are applied.
88 pub fn map(self, changes: &ChangeSet) -> Self {
120 pub fn map(self, changes: &ChangeSet) -> Self {
89− let anchor = changes.map_pos(self.anchor, Assoc::After);
121+ use std::cmp::Ordering;
90− let head = changes.map_pos(self.head, Assoc::After);
91−
92− // TODO: possibly unnecessary
93− if self.anchor == anchor && self.head == head {
122+ let (anchor, head) = match self.anchor.cmp(&self.head) {
94− return self;
123+ Ordering::Equal => (
95− }
124+ changes.map_pos(self.anchor, Assoc::After),
125+ changes.map_pos(self.head, Assoc::After),
126+ ),
127+ Ordering::Less => (
128+ changes.map_pos(self.anchor, Assoc::After),
129+ changes.map_pos(self.head, Assoc::Before),
130+ ),
131+ Ordering::Greater => (
132+ changes.map_pos(self.anchor, Assoc::Before),
133+ changes.map_pos(self.head, Assoc::After),
134+ ),
135+ };
136+
137+ // We want to return a new `Range` with `horiz == None` every time,
138+ // even if the anchor and head haven't changed, because we don't
139+ // know if the *visual* position hasn't changed due to
140+ // character-width or grapheme changes earlier in the text.
96 Self {
141 Self {
97 anchor,
142 anchor,
98 head,
143 head,
@@ -103,22 +148,41 @@ impl Range {
103 /// Extend the range to cover at least `from` `to`.
148 /// Extend the range to cover at least `from` `to`.
104 #[must_use]
149 #[must_use]
105 pub fn extend(&self, from: usize, to: usize) -> Self {
150 pub fn extend(&self, from: usize, to: usize) -> Self {
151+ debug_assert!(from <= to);
152+
106− if from <= self.anchor && to >= self.anchor {
153+ if self.anchor <= self.head {
107− return Self {
154+ Self {
108− anchor: from,
155+ anchor: self.anchor.min(from),
109− head: to,
156+ head: self.head.max(to),
110 horiz: None,
157 horiz: None,
111− };
158+ }
159+ } else {
160+ Self {
161+ anchor: self.anchor.max(to),
162+ head: self.head.min(from),
163+ horiz: None,
164+ }
112 }
165 }
166+ }
113
167
114− Self {
168+ /// Returns a range that encompasses both input ranges.
115− anchor: self.anchor,
169+ ///
116− head: if abs_difference(from, self.anchor) > abs_difference(to, self.anchor) {
170+ /// This is like `extend()`, but tries to negotiate the
117− from
171+ /// anchor/head ordering between the two input ranges.
172+ #[must_use]
173+ pub fn merge(&self, other: Self) -> Self {
174+ if self.anchor > self.head && other.anchor > other.head {
175+ Range {
176+ anchor: self.anchor.max(other.anchor),
177+ head: self.head.min(other.head),
178+ horiz: None,
179+ }
118− } else {
180+ } else {
119− to
181+ Range {
120− },
182+ anchor: self.from().min(other.from()),
183+ head: self.to().max(other.to()),
121− horiz: None,
184+ horiz: None,
185+ }
122 }
186 }
123 }
187 }
124
188
@@ -126,7 +190,120 @@ impl Range {
126
190
127 #[inline]
191 #[inline]
128 pub fn fragment<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> Cow<'b, str> {
192 pub fn fragment<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> Cow<'b, str> {
129− Cow::from(text.slice(self.from()..self.to() + 1))
193+ text.slice(self.from()..self.to()).into()
194+ }
195+
196+ //--------------------------------
197+ // Alignment methods.
198+
199+ /// Compute a possibly new range from this range, with its ends
200+ /// shifted as needed to align with grapheme boundaries.
201+ ///
202+ /// Zero-width ranges will always stay zero-width, and non-zero-width
203+ /// ranges will never collapse to zero-width.
204+ #[must_use]
205+ pub fn grapheme_aligned(&self, slice: RopeSlice) -> Self {
206+ use std::cmp::Ordering;
207+ let (new_anchor, new_head) = match self.anchor.cmp(&self.head) {
208+ Ordering::Equal => {
209+ let pos = ensure_grapheme_boundary_prev(slice, self.anchor);
210+ (pos, pos)
211+ }
212+ Ordering::Less => (
213+ ensure_grapheme_boundary_prev(slice, self.anchor),
214+ ensure_grapheme_boundary_next(slice, self.head),
215+ ),
216+ Ordering::Greater => (
217+ ensure_grapheme_boundary_next(slice, self.anchor),
218+ ensure_grapheme_boundary_prev(slice, self.head),
219+ ),
220+ };
221+ Range {
222+ anchor: new_anchor,
223+ head: new_head,
224+ horiz: if new_anchor == self.anchor {
225+ self.horiz
226+ } else {
227+ None
228+ },
229+ }
230+ }
231+
232+ /// Compute a possibly new range from this range, attempting to ensure
233+ /// a minimum range width of 1 char by shifting the head in the forward
234+ /// direction as needed.
235+ ///
236+ /// This method will never shift the anchor, and will only shift the
237+ /// head in the forward direction. Therefore, this method can fail
238+ /// at ensuring the minimum width if and only if the passed range is
239+ /// both zero-width and at the end of the `RopeSlice`.
240+ ///
241+ /// If the input range is grapheme-boundary aligned, the returned range
242+ /// will also be. Specifically, if the head needs to shift to achieve
243+ /// the minimum width, it will shift to the next grapheme boundary.
244+ #[must_use]
245+ #[inline]
246+ pub fn min_width_1(&self, slice: RopeSlice) -> Self {
247+ if self.anchor == self.head {
248+ Range {
249+ anchor: self.anchor,
250+ head: next_grapheme_boundary(slice, self.head),
251+ horiz: self.horiz,
252+ }
253+ } else {
254+ *self
255+ }
256+ }
257+
258+ //--------------------------------
259+ // Block-cursor methods.
260+
261+ /// Gets the left-side position of the block cursor.
262+ #[must_use]
263+ #[inline]
264+ pub fn cursor(self, text: RopeSlice) -> usize {
265+ if self.head > self.anchor {
266+ prev_grapheme_boundary(text, self.head)
267+ } else {
268+ self.head
269+ }
270+ }
271+
272+ /// Puts the left side of the block cursor at `char_idx`, optionally extending.
273+ ///
274+ /// This follows "1-width" semantics, and therefore does a combination of anchor
275+ /// and head moves to behave as if both the front and back of the range are 1-width
276+ /// blocks
277+ ///
278+ /// This method assumes that the range and `char_idx` are already properly
279+ /// grapheme-aligned.
280+ #[must_use]
281+ #[inline]
282+ pub fn put_cursor(self, text: RopeSlice, char_idx: usize, extend: bool) -> Range {
283+ if extend {
284+ let anchor = if self.head >= self.anchor && char_idx < self.anchor {
285+ next_grapheme_boundary(text, self.anchor)
286+ } else if self.head < self.anchor && char_idx >= self.anchor {
287+ prev_grapheme_boundary(text, self.anchor)
288+ } else {
289+ self.anchor
290+ };
291+
292+ if anchor <= char_idx {
293+ Range::new(anchor, next_grapheme_boundary(text, char_idx))
294+ } else {
295+ Range::new(anchor, char_idx)
296+ }
297+ } else {
298+ Range::point(char_idx)
299+ }
300+ }
301+
302+ /// The line number that the block-cursor is on.
303+ #[inline]
304+ #[must_use]
305+ pub fn cursor_line(&self, text: RopeSlice) -> usize {
306+ text.char_to_line(self.cursor(text))
130 }
307 }
131}
308}
132
309
@@ -157,11 +334,6 @@ impl Selection {
157 self.ranges[self.primary_index]
334 self.ranges[self.primary_index]
158 }
335 }
159
336
160− #[must_use]
161− pub fn cursor(&self) -> usize {
162− self.primary().head
163− }
164−
165 /// Ensure selection containing only the primary selection.
337 /// Ensure selection containing only the primary selection.
166 pub fn into_single(self) -> Self {
338 pub fn into_single(self) -> Self {
167 if self.ranges.len() == 1 {
339 if self.ranges.len() == 1 {
@@ -174,13 +346,12 @@ impl Selection {
174 }
346 }
175 }
347 }
176
348
349+ /// Adds a new range to the selection and makes it the primary range.
177 pub fn push(mut self, range: Range) -> Self {
350 pub fn push(mut self, range: Range) -> Self {
178− let index = self.ranges.len();
179 self.ranges.push(range);
351 self.ranges.push(range);
180−
352+ self.set_primary_index(self.ranges().len() - 1);
181− Self::normalize(self.ranges, index)
353+ self.normalize()
182 }
354 }
183− // replace_range
184
355
185 /// Map selections over a set of changes. Useful for adjusting the selection position after
356 /// Map selections over a set of changes. Useful for adjusting the selection position after
186 /// applying changes to a document.
357 /// applying changes to a document.
@@ -206,6 +377,11 @@ impl Selection {
206 self.primary_index
377 self.primary_index
207 }
378 }
208
379
380+ pub fn set_primary_index(&mut self, idx: usize) {
381+ assert!(idx < self.ranges.len());
382+ self.primary_index = idx;
383+ }
384+
209 #[must_use]
385 #[must_use]
210 /// Constructs a selection holding a single range.
386 /// Constructs a selection holding a single range.
211 pub fn single(anchor: usize, head: usize) -> Self {
387 pub fn single(anchor: usize, head: usize) -> Self {
@@ -224,80 +400,79 @@ impl Selection {
224 Self::single(pos, pos)
400 Self::single(pos, pos)
225 }
401 }
226
402
403+ /// Normalizes a `Selection`.
227− fn normalize(mut ranges: SmallVec<[Range; 1]>, mut primary_index: usize) -> Self {
404+ fn normalize(mut self) -> Self {
228− let primary = ranges[primary_index];
405+ let primary = self.ranges[self.primary_index];
229− ranges.sort_unstable_by_key(Range::from);
406+ self.ranges.sort_unstable_by_key(Range::from);
407+ self.primary_index = self
408+ .ranges
409+ .iter()
230− primary_index = ranges.iter().position(|&range| range == primary).unwrap();
410+ .position(|&range| range == primary)
231−
411+ .unwrap();
232− let mut result = SmallVec::with_capacity(ranges.len()); // approx
412+
233−
413+ let mut prev_i = 0;
234− // TODO: we could do with one vec by removing elements as we mutate
235−
236− let mut i = 0;
237−
238− for range in ranges.into_iter() {
414+ for i in 1..self.ranges.len() {
239− // if previous value exists
415+ if self.ranges[prev_i].overlaps(&self.ranges[i]) {
240− if let Some(prev) = result.last_mut() {
416+ self.ranges[prev_i] = self.ranges[prev_i].merge(self.ranges[i]);
241− // and we overlap it
242−
243− // TODO: we used to simply check range.from() <(=) prev.to()
244− // avoiding two comparisons
245− if range.overlaps(prev) {
246− let from = prev.from();
247− let to = std::cmp::max(range.to(), prev.to());
248−
249− if i <= primary_index {
250− primary_index -= 1
251− }
252−
253− // merge into previous
254− if range.anchor > range.head {
255− prev.anchor = to;
256− prev.head = from;
257− } else {
417+ } else {
258− prev.anchor = from;
418+ prev_i += 1;
259− prev.head = to;
419+ self.ranges[prev_i] = self.ranges[i];
260− }
420+ }
261− continue;
421+ if i == self.primary_index {
262− }
422+ self.primary_index = prev_i;
263 }
423 }
264−
265− result.push(range);
266− i += 1
267 }
424 }
268
425
269− Self {
426+ self.ranges.truncate(prev_i + 1);
270− ranges: result,
427+
271− primary_index,
428+ self
272− }
273 }
429 }
274
430
275 // TODO: consume an iterator or a vec to reduce allocations?
431 // TODO: consume an iterator or a vec to reduce allocations?
276 #[must_use]
432 #[must_use]
277 pub fn new(ranges: SmallVec<[Range; 1]>, primary_index: usize) -> Self {
433 pub fn new(ranges: SmallVec<[Range; 1]>, primary_index: usize) -> Self {
278 assert!(!ranges.is_empty());
434 assert!(!ranges.is_empty());
435+ debug_assert!(primary_index < ranges.len());
279
436
280− // fast path for a single selection (cursor)
437+ let mut selection = Self {
281− if ranges.len() == 1 {
282− return Self {
283− ranges,
438+ ranges,
284− primary_index: 0,
439+ primary_index,
285− };
440+ };
441+
442+ if selection.ranges.len() > 1 {
443+ // TODO: only normalize if needed (any ranges out of order)
444+ selection = selection.normalize();
286 }
445 }
287
446
288− // TODO: only normalize if needed (any ranges out of order)
447+ selection
289− Self::normalize(ranges, primary_index)
290 }
448 }
291
449
292− /// Takes a closure and maps each selection over the closure.
450+ /// Takes a closure and maps each `Range` over the closure.
293− pub fn transform<F>(&self, f: F) -> Self
451+ pub fn transform<F>(mut self, f: F) -> Self
294 where
452 where
295 F: Fn(Range) -> Range,
453 F: Fn(Range) -> Range,
296 {
454 {
297− Self::new(
455+ for range in self.ranges.iter_mut() {
298− self.ranges.iter().copied().map(f).collect(),
456+ *range = f(*range)
299− self.primary_index,
457+ }
300− )
458+ self.normalize()
459+ }
460+
461+ // Ensures the selection adheres to the following invariants:
462+ // 1. All ranges are grapheme aligned.
463+ // 2. All ranges are at least 1 character wide, unless at the
464+ // very end of the document.
465+ // 3. Ranges are non-overlapping.
466+ // 4. Ranges are sorted by their position in the text.
467+ pub fn ensure_invariants(self, text: RopeSlice) -> Self {
468+ self.transform(|r| r.min_width_1(text).grapheme_aligned(text))
469+ .normalize()
470+ }
471+
472+ /// Transforms the selection into all of the left-side head positions,
473+ /// using block-cursor semantics.
474+ pub fn cursors(self, text: RopeSlice) -> Self {
475+ self.transform(|range| Range::point(range.cursor(text)))
301 }
476 }
302
477
303 pub fn fragments<'a>(&'a self, text: RopeSlice<'a>) -> impl Iterator<Item = Cow<str>> + 'a {
478 pub fn fragments<'a>(&'a self, text: RopeSlice<'a>) -> impl Iterator<Item = Cow<str>> + 'a {
@@ -363,7 +538,7 @@ pub fn select_on_matches(
363
538
364 let start = text.byte_to_char(start_byte + mat.start());
539 let start = text.byte_to_char(start_byte + mat.start());
365 let end = text.byte_to_char(start_byte + mat.end());
540 let end = text.byte_to_char(start_byte + mat.end());
366− result.push(Range::new(start, end.saturating_sub(1)));
541+ result.push(Range::new(start, end));
367 }
542 }
368 }
543 }
369
544
@@ -384,6 +559,12 @@ pub fn split_on_matches(
384 let mut result = SmallVec::with_capacity(selection.len());
559 let mut result = SmallVec::with_capacity(selection.len());
385
560
386 for sel in selection {
561 for sel in selection {
562+ // Special case: zero-width selection.
563+ if sel.from() == sel.to() {
564+ result.push(*sel);
565+ continue;
566+ }
567+
387 // TODO: can't avoid occasional allocations since Regex can't operate on chunks yet
568 // TODO: can't avoid occasional allocations since Regex can't operate on chunks yet
388 let fragment = sel.fragment(text);
569 let fragment = sel.fragment(text);
389
570
@@ -396,13 +577,12 @@ pub fn split_on_matches(
396
577
397 for mat in regex.find_iter(&fragment) {
578 for mat in regex.find_iter(&fragment) {
398 // TODO: retain range direction
579 // TODO: retain range direction
399−
400 let end = text.byte_to_char(start_byte + mat.start());
580 let end = text.byte_to_char(start_byte + mat.start());
401− result.push(Range::new(start, end.saturating_sub(1)));
581+ result.push(Range::new(start, end));
402 start = text.byte_to_char(start_byte + mat.end());
582 start = text.byte_to_char(start_byte + mat.end());
403 }
583 }
404
584
405− if start <= sel_end {
585+ if start < sel_end {
406 result.push(Range::new(start, sel_end));
586 result.push(Range::new(start, sel_end));
407 }
587 }
408 }
588 }
@@ -484,7 +664,7 @@ mod test {
484 .collect::<Vec<String>>()
664 .collect::<Vec<String>>()
485 .join(",");
665 .join(",");
486
666
487− assert_eq!(res, "8/10,10/12");
667+ assert_eq!(res, "8/10,10/12,12/12");
488 }
668 }
489
669
490 #[test]
670 #[test]
@@ -498,35 +678,251 @@ mod test {
498 assert_eq!(range.contains(13), false);
678 assert_eq!(range.contains(13), false);
499
679
500 let range = Range::new(9, 6);
680 let range = Range::new(9, 6);
501− assert_eq!(range.contains(9), true);
681+ assert_eq!(range.contains(9), false);
502 assert_eq!(range.contains(7), true);
682 assert_eq!(range.contains(7), true);
503− assert_eq!(range.contains(6), false);
683+ assert_eq!(range.contains(6), true);
684+ }
685+
686+ #[test]
687+ fn test_overlaps() {
688+ fn overlaps(a: (usize, usize), b: (usize, usize)) -> bool {
689+ Range::new(a.0, a.1).overlaps(&Range::new(b.0, b.1))
690+ }
691+
692+ // Two non-zero-width ranges, no overlap.
693+ assert!(!overlaps((0, 3), (3, 6)));
694+ assert!(!overlaps((0, 3), (6, 3)));
695+ assert!(!overlaps((3, 0), (3, 6)));
696+ assert!(!overlaps((3, 0), (6, 3)));
697+ assert!(!overlaps((3, 6), (0, 3)));
698+ assert!(!overlaps((3, 6), (3, 0)));
699+ assert!(!overlaps((6, 3), (0, 3)));
700+ assert!(!overlaps((6, 3), (3, 0)));
701+
702+ // Two non-zero-width ranges, overlap.
703+ assert!(overlaps((0, 4), (3, 6)));
704+ assert!(overlaps((0, 4), (6, 3)));
705+ assert!(overlaps((4, 0), (3, 6)));
706+ assert!(overlaps((4, 0), (6, 3)));
707+ assert!(overlaps((3, 6), (0, 4)));
708+ assert!(overlaps((3, 6), (4, 0)));
709+ assert!(overlaps((6, 3), (0, 4)));
710+ assert!(overlaps((6, 3), (4, 0)));
711+
712+ // Zero-width and non-zero-width range, no overlap.
713+ assert!(!overlaps((0, 3), (3, 3)));
714+ assert!(!overlaps((3, 0), (3, 3)));
715+ assert!(!overlaps((3, 3), (0, 3)));
716+ assert!(!overlaps((3, 3), (3, 0)));
717+
718+ // Zero-width and non-zero-width range, overlap.
719+ assert!(overlaps((1, 4), (1, 1)));
720+ assert!(overlaps((4, 1), (1, 1)));
721+ assert!(overlaps((1, 1), (1, 4)));
722+ assert!(overlaps((1, 1), (4, 1)));
723+
724+ assert!(overlaps((1, 4), (3, 3)));
725+ assert!(overlaps((4, 1), (3, 3)));
726+ assert!(overlaps((3, 3), (1, 4)));
727+ assert!(overlaps((3, 3), (4, 1)));
728+
729+ // Two zero-width ranges, no overlap.
730+ assert!(!overlaps((0, 0), (1, 1)));
731+ assert!(!overlaps((1, 1), (0, 0)));
732+
733+ // Two zero-width ranges, overlap.
734+ assert!(overlaps((1, 1), (1, 1)));
735+ }
736+
737+ #[test]
738+ fn test_graphem_aligned() {
739+ let r = Rope::from_str("\r\nHi\r\n");
740+ let s = r.slice(..);
741+
742+ // Zero-width.
743+ assert_eq!(Range::new(0, 0).grapheme_aligned(s), Range::new(0, 0));
744+ assert_eq!(Range::new(1, 1).grapheme_aligned(s), Range::new(0, 0));
745+ assert_eq!(Range::new(2, 2).grapheme_aligned(s), Range::new(2, 2));
746+ assert_eq!(Range::new(3, 3).grapheme_aligned(s), Range::new(3, 3));
747+ assert_eq!(Range::new(4, 4).grapheme_aligned(s), Range::new(4, 4));
748+ assert_eq!(Range::new(5, 5).grapheme_aligned(s), Range::new(4, 4));
749+ assert_eq!(Range::new(6, 6).grapheme_aligned(s), Range::new(6, 6));
750+
751+ // Forward.
752+ assert_eq!(Range::new(0, 1).grapheme_aligned(s), Range::new(0, 2));
753+ assert_eq!(Range::new(1, 2).grapheme_aligned(s), Range::new(0, 2));
754+ assert_eq!(Range::new(2, 3).grapheme_aligned(s), Range::new(2, 3));
755+ assert_eq!(Range::new(3, 4).grapheme_aligned(s), Range::new(3, 4));
756+ assert_eq!(Range::new(4, 5).grapheme_aligned(s), Range::new(4, 6));
757+ assert_eq!(Range::new(5, 6).grapheme_aligned(s), Range::new(4, 6));
758+
759+ assert_eq!(Range::new(0, 2).grapheme_aligned(s), Range::new(0, 2));
760+ assert_eq!(Range::new(1, 3).grapheme_aligned(s), Range::new(0, 3));
761+ assert_eq!(Range::new(2, 4).grapheme_aligned(s), Range::new(2, 4));
762+ assert_eq!(Range::new(3, 5).grapheme_aligned(s), Range::new(3, 6));
763+ assert_eq!(Range::new(4, 6).grapheme_aligned(s), Range::new(4, 6));
764+
765+ // Reverse.
766+ assert_eq!(Range::new(1, 0).grapheme_aligned(s), Range::new(2, 0));
767+ assert_eq!(Range::new(2, 1).grapheme_aligned(s), Range::new(2, 0));
768+ assert_eq!(Range::new(3, 2).grapheme_aligned(s), Range::new(3, 2));
769+ assert_eq!(Range::new(4, 3).grapheme_aligned(s), Range::new(4, 3));
770+ assert_eq!(Range::new(5, 4).grapheme_aligned(s), Range::new(6, 4));
771+ assert_eq!(Range::new(6, 5).grapheme_aligned(s), Range::new(6, 4));
772+
773+ assert_eq!(Range::new(2, 0).grapheme_aligned(s), Range::new(2, 0));
774+ assert_eq!(Range::new(3, 1).grapheme_aligned(s), Range::new(3, 0));
775+ assert_eq!(Range::new(4, 2).grapheme_aligned(s), Range::new(4, 2));
776+ assert_eq!(Range::new(5, 3).grapheme_aligned(s), Range::new(6, 3));
777+ assert_eq!(Range::new(6, 4).grapheme_aligned(s), Range::new(6, 4));
778+ }
779+
780+ #[test]
781+ fn test_min_width_1() {
782+ let r = Rope::from_str("\r\nHi\r\n");
783+ let s = r.slice(..);
784+
785+ // Zero-width.
786+ assert_eq!(Range::new(0, 0).min_width_1(s), Range::new(0, 2));
787+ assert_eq!(Range::new(1, 1).min_width_1(s), Range::new(1, 2));
788+ assert_eq!(Range::new(2, 2).min_width_1(s), Range::new(2, 3));
789+ assert_eq!(Range::new(3, 3).min_width_1(s), Range::new(3, 4));
790+ assert_eq!(Range::new(4, 4).min_width_1(s), Range::new(4, 6));
791+ assert_eq!(Range::new(5, 5).min_width_1(s), Range::new(5, 6));
792+ assert_eq!(Range::new(6, 6).min_width_1(s), Range::new(6, 6));
793+
794+ // Forward.
795+ assert_eq!(Range::new(0, 1).min_width_1(s), Range::new(0, 1));
796+ assert_eq!(Range::new(1, 2).min_width_1(s), Range::new(1, 2));
797+ assert_eq!(Range::new(2, 3).min_width_1(s), Range::new(2, 3));
798+ assert_eq!(Range::new(3, 4).min_width_1(s), Range::new(3, 4));
799+ assert_eq!(Range::new(4, 5).min_width_1(s), Range::new(4, 5));
800+ assert_eq!(Range::new(5, 6).min_width_1(s), Range::new(5, 6));
801+
802+ // Reverse.
803+ assert_eq!(Range::new(1, 0).min_width_1(s), Range::new(1, 0));
804+ assert_eq!(Range::new(2, 1).min_width_1(s), Range::new(2, 1));
805+ assert_eq!(Range::new(3, 2).min_width_1(s), Range::new(3, 2));
806+ assert_eq!(Range::new(4, 3).min_width_1(s), Range::new(4, 3));
807+ assert_eq!(Range::new(5, 4).min_width_1(s), Range::new(5, 4));
808+ assert_eq!(Range::new(6, 5).min_width_1(s), Range::new(6, 5));
809+ }
810+
811+ #[test]
812+ fn test_line_range() {
813+ let r = Rope::from_str("\r\nHi\r\nthere!");
814+ let s = r.slice(..);
815+
816+ // Zero-width ranges.
817+ assert_eq!(Range::new(0, 0).line_range(s), (0, 0));
818+ assert_eq!(Range::new(1, 1).line_range(s), (0, 0));
819+ assert_eq!(Range::new(2, 2).line_range(s), (1, 1));
820+ assert_eq!(Range::new(3, 3).line_range(s), (1, 1));
821+
822+ // Forward ranges.
823+ assert_eq!(Range::new(0, 1).line_range(s), (0, 0));
824+ assert_eq!(Range::new(0, 2).line_range(s), (0, 0));
825+ assert_eq!(Range::new(0, 3).line_range(s), (0, 1));
826+ assert_eq!(Range::new(1, 2).line_range(s), (0, 0));
827+ assert_eq!(Range::new(2, 3).line_range(s), (1, 1));
828+ assert_eq!(Range::new(3, 8).line_range(s), (1, 2));
829+ assert_eq!(Range::new(0, 12).line_range(s), (0, 2));
830+
831+ // Reverse ranges.
832+ assert_eq!(Range::new(1, 0).line_range(s), (0, 0));
833+ assert_eq!(Range::new(2, 0).line_range(s), (0, 0));
834+ assert_eq!(Range::new(3, 0).line_range(s), (0, 1));
835+ assert_eq!(Range::new(2, 1).line_range(s), (0, 0));
836+ assert_eq!(Range::new(3, 2).line_range(s), (1, 1));
837+ assert_eq!(Range::new(8, 3).line_range(s), (1, 2));
838+ assert_eq!(Range::new(12, 0).line_range(s), (0, 2));
839+ }
840+
841+ #[test]
842+ fn test_cursor() {
843+ let r = Rope::from_str("\r\nHi\r\nthere!");
844+ let s = r.slice(..);
845+
846+ // Zero-width ranges.
847+ assert_eq!(Range::new(0, 0).cursor(s), 0);
848+ assert_eq!(Range::new(2, 2).cursor(s), 2);
849+ assert_eq!(Range::new(3, 3).cursor(s), 3);
850+
851+ // Forward ranges.
852+ assert_eq!(Range::new(0, 2).cursor(s), 0);
853+ assert_eq!(Range::new(0, 3).cursor(s), 2);
854+ assert_eq!(Range::new(3, 6).cursor(s), 4);
855+
856+ // Reverse ranges.
857+ assert_eq!(Range::new(2, 0).cursor(s), 0);
858+ assert_eq!(Range::new(6, 2).cursor(s), 2);
859+ assert_eq!(Range::new(6, 3).cursor(s), 3);
860+ }
861+
862+ #[test]
863+ fn test_put_cursor() {
864+ let r = Rope::from_str("\r\nHi\r\nthere!");
865+ let s = r.slice(..);
866+
867+ // Zero-width ranges.
868+ assert_eq!(Range::new(0, 0).put_cursor(s, 0, true), Range::new(0, 2));
869+ assert_eq!(Range::new(0, 0).put_cursor(s, 2, true), Range::new(0, 3));
870+ assert_eq!(Range::new(2, 3).put_cursor(s, 4, true), Range::new(2, 6));
871+ assert_eq!(Range::new(2, 8).put_cursor(s, 4, true), Range::new(2, 6));
872+ assert_eq!(Range::new(8, 8).put_cursor(s, 4, true), Range::new(9, 4));
873+
874+ // Forward ranges.
875+ assert_eq!(Range::new(3, 6).put_cursor(s, 0, true), Range::new(4, 0));
876+ assert_eq!(Range::new(3, 6).put_cursor(s, 2, true), Range::new(4, 2));
877+ assert_eq!(Range::new(3, 6).put_cursor(s, 3, true), Range::new(3, 4));
878+ assert_eq!(Range::new(3, 6).put_cursor(s, 4, true), Range::new(3, 6));
879+ assert_eq!(Range::new(3, 6).put_cursor(s, 6, true), Range::new(3, 7));
880+ assert_eq!(Range::new(3, 6).put_cursor(s, 8, true), Range::new(3, 9));
881+
882+ // Reverse ranges.
883+ assert_eq!(Range::new(6, 3).put_cursor(s, 0, true), Range::new(6, 0));
884+ assert_eq!(Range::new(6, 3).put_cursor(s, 2, true), Range::new(6, 2));
885+ assert_eq!(Range::new(6, 3).put_cursor(s, 3, true), Range::new(6, 3));
886+ assert_eq!(Range::new(6, 3).put_cursor(s, 4, true), Range::new(6, 4));
887+ assert_eq!(Range::new(6, 3).put_cursor(s, 6, true), Range::new(4, 7));
888+ assert_eq!(Range::new(6, 3).put_cursor(s, 8, true), Range::new(4, 9));
504 }
889 }
505
890
506 #[test]
891 #[test]
507 fn test_split_on_matches() {
892 fn test_split_on_matches() {
508 use crate::regex::Regex;
893 use crate::regex::Regex;
509
894
510− let text = Rope::from("abcd efg wrs xyz 123 456");
895+ let text = Rope::from(" abcd efg wrs xyz 123 456");
511
896
512− let selection = Selection::new(smallvec![Range::new(0, 8), Range::new(10, 19),], 0);
897+ let selection = Selection::new(smallvec![Range::new(0, 9), Range::new(11, 20),], 0);
513
898
514 let result = split_on_matches(text.slice(..), &selection, &Regex::new(r"\s+").unwrap());
899 let result = split_on_matches(text.slice(..), &selection, &Regex::new(r"\s+").unwrap());
515
900
516 assert_eq!(
901 assert_eq!(
517 result.ranges(),
902 result.ranges(),
518 &[
903 &[
904+ // TODO: rather than this behavior, maybe we want it
905+ // to be based on which side is the anchor?
906+ //
907+ // We get a leading zero-width range when there's
908+ // a leading match because ranges are inclusive on
909+ // the left. Imagine, for example, if the entire
910+ // selection range were matched: you'd still want
911+ // at least one range to remain after the split.
519− Range::new(0, 3),
912+ Range::new(0, 0),
520− Range::new(5, 7),
913+ Range::new(1, 5),
914+ Range::new(6, 9),
521− Range::new(10, 11),
915+ Range::new(11, 13),
522− Range::new(15, 17),
916+ Range::new(16, 19),
523− Range::new(19, 19),
917+ // In contrast to the comment above, there is no
918+ // _trailing_ zero-width range despite the trailing
919+ // match, because ranges are exclusive on the right.
524 ]
920 ]
525 );
921 );
526
922
527 assert_eq!(
923 assert_eq!(
528 result.fragments(text.slice(..)).collect::<Vec<_>>(),
924 result.fragments(text.slice(..)).collect::<Vec<_>>(),
529− &["abcd", "efg", "rs", "xyz", "1"]
925+ &["", "abcd", "efg", "rs", "xyz"]
530 );
926 );
531 }
927 }
532}
928}
helix-core/src/surround.rs+44−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,34 @@ 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)? + 1)),
53+ _ if (pos + 1) == text.len_chars() => {
54+ Some((search::find_nth_prev(text, open, pos, n)?, text.len_chars()))
55+ }
56+ // We return no match because there's no way to know which
57+ // side of the char we should be searching on.
58+ _ => None,
59+ }
49 } else {
60 } else {
50− (prev?, next?)
61+ Some((
62+ search::find_nth_prev(text, open, pos, n)?,
63+ search::find_nth_next(text, close, pos, n)? + 1,
64+ ))
51 }
65 }
52 } else {
66 } else {
53− (
67+ Some((
54 find_nth_open_pair(text, open, close, pos, n)?,
68 find_nth_open_pair(text, open, close, pos, n)?,
55− find_nth_close_pair(text, open, close, pos, n)?,
69+ next_grapheme_boundary(text, find_nth_close_pair(text, open, close, pos, n)?),
56− )
70+ ))
57− };
71+ }
58−
59− Some((open_pos, close_pos))
60}
72}
61
73
62fn find_nth_open_pair(
74fn find_nth_open_pair(
@@ -173,12 +185,13 @@ mod test {
173 let slice = doc.slice(..);
185 let slice = doc.slice(..);
174
186
175 // cursor on [t]ext
187 // cursor on [t]ext
176− assert_eq!(find_nth_pairs_pos(slice, '(', 6, 1), Some((5, 10)));
188+ 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)));
189+ assert_eq!(find_nth_pairs_pos(slice, ')', 6, 1), Some((5, 11)));
178 // cursor on so[m]e
190 // cursor on so[m]e
179 assert_eq!(find_nth_pairs_pos(slice, '(', 2, 1), None);
191 assert_eq!(find_nth_pairs_pos(slice, '(', 2, 1), None);
180 // cursor on bracket itself
192 // cursor on bracket itself
181− assert_eq!(find_nth_pairs_pos(slice, '(', 5, 1), Some((5, 10)));
193+ assert_eq!(find_nth_pairs_pos(slice, '(', 5, 1), Some((5, 11)));
194+ assert_eq!(find_nth_pairs_pos(slice, '(', 10, 1), Some((5, 11)));
182 }
195 }
183
196
184 #[test]
197 #[test]
@@ -187,9 +200,9 @@ mod test {
187 let slice = doc.slice(..);
200 let slice = doc.slice(..);
188
201
189 // cursor on go[o]d
202 // cursor on go[o]d
190− assert_eq!(find_nth_pairs_pos(slice, '(', 13, 1), Some((10, 15)));
203+ 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)));
204+ 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)));
205+ assert_eq!(find_nth_pairs_pos(slice, '(', 13, 3), Some((0, 28)));
193 }
206 }
194
207
195 #[test]
208 #[test]
@@ -198,14 +211,14 @@ mod test {
198 let slice = doc.slice(..);
211 let slice = doc.slice(..);
199
212
200 // cursor on go[o]d
213 // cursor on go[o]d
201− assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 1), Some((10, 15)));
214+ 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)));
215+ 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)));
216+ assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 3), Some((0, 28)));
204 // cursor on the quotes
217 // cursor on the quotes
205− assert_eq!(find_nth_pairs_pos(slice, '\'', 10, 1), Some((10, 15)));
218+ 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
219 // 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)));
220+ 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)));
221+ assert_eq!(find_nth_pairs_pos(slice, '\'', 27, 1), Some((21, 28)));
209 }
222 }
210
223
211 #[test]
224 #[test]
@@ -214,8 +227,8 @@ mod test {
214 let slice = doc.slice(..);
227 let slice = doc.slice(..);
215
228
216 // cursor on go[o]d
229 // cursor on go[o]d
217− assert_eq!(find_nth_pairs_pos(slice, '(', 15, 1), Some((5, 24)));
230+ 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)));
231+ assert_eq!(find_nth_pairs_pos(slice, '(', 15, 2), Some((0, 32)));
219 }
232 }
220
233
221 #[test]
234 #[test]
@@ -224,9 +237,9 @@ mod test {
224 let slice = doc.slice(..);
237 let slice = doc.slice(..);
225
238
226 // cursor on go[o]d
239 // cursor on go[o]d
227− assert_eq!(find_nth_pairs_pos(slice, '{', 13, 1), Some((10, 15)));
240+ 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)));
241+ 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)));
242+ assert_eq!(find_nth_pairs_pos(slice, '(', 13, 1), Some((0, 28)));
230 }
243 }
231
244
232 #[test]
245 #[test]
@@ -243,7 +256,7 @@ mod test {
243 get_surround_pos(slice, &selection, '(', 1)
256 get_surround_pos(slice, &selection, '(', 1)
244 .unwrap()
257 .unwrap()
245 .as_slice(),
258 .as_slice(),
246− &[0, 5, 7, 13, 15, 23]
259+ &[0, 6, 7, 14, 15, 24]
247 );
260 );
248 }
261 }
249
262
helix-core/src/syntax.rs+14−4
@@ -1760,10 +1760,20 @@ impl<I: Iterator<Item = HighlightEvent>> Iterator for Merge<I> {
1760 self.next_event = self.iter.next();
1760 self.next_event = self.iter.next();
1761 Some(event)
1761 Some(event)
1762 }
1762 }
1763− // can happen if deleting and cursor at EOF, and diagnostic reaches past the end
1763+ // Can happen if cursor at EOF and/or diagnostic reaches past the end.
1764+ // We need to actually emit events for the cursor-at-EOF situation,
1765+ // even though the range is past the end of the text. This needs to be
1766+ // handled appropriately by the drawing code by not assuming that
1767+ // all `Source` events point to valid indices in the rope.
1764− (None, Some((_, _))) => {
1768+ (None, Some((span, range))) => {
1769+ let event = HighlightStart(Highlight(*span));
1770+ self.queue.push(HighlightEnd);
1771+ self.queue.push(Source {
1772+ start: range.start,
1773+ end: range.end,
1774+ });
1765− self.next_span = None;
1775+ self.next_span = self.spans.next();
1766− None
1776+ Some(event)
1767 }
1777 }
1768 (None, None) => None,
1778 (None, None) => None,
1769 e => unreachable!("{:?}", e),
1779 e => unreachable!("{:?}", e),
helix-core/src/textobject.rs+128−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,31 @@ 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 {
35+ match direction {
38− Direction::Forward => pos + 1,
36+ Direction::Forward => pos += 1,
39− Direction::Backward => pos.saturating_sub(1),
37+ Direction::Backward => pos = pos.saturating_sub(1),
38+ }
39+ prev_category = category;
40 }
40 }
41 }
41 }
42− pos
43 }
42 }
44 }
43 }
44+
45+ pos
45}
46}
46
47
47#[derive(Copy, Clone, PartialEq, Eq, Debug)]
48#[derive(Copy, Clone, PartialEq, Eq, Debug)]
@@ -55,46 +56,37 @@ pub fn textobject_word(
55 slice: RopeSlice,
56 slice: RopeSlice,
56 range: Range,
57 range: Range,
57 textobject: TextObject,
58 textobject: TextObject,
58− count: usize,
59+ _count: usize,
59) -> Range {
60) -> Range {
60− let this_word_start = this_word_start_pos(slice, range.head);
61+ let pos = range.cursor(slice);
61− let this_word_end = this_word_end_pos(slice, range.head);
62+
63+ let word_start = find_word_boundary(slice, pos, Direction::Backward);
64+ let word_end = match slice.get_char(pos).map(categorize_char) {
65+ None | Some(CharCategory::Whitespace | CharCategory::Eol) => pos,
66+ _ => find_word_boundary(slice, pos + 1, Direction::Forward),
67+ };
68+
69+ // Special case.
70+ if word_start == word_end {
71+ return Range::new(word_start, word_end);
72+ }
62
73
63− let (anchor, head);
64 match textobject {
74 match textobject {
65− TextObject::Inside => {
75+ TextObject::Inside => Range::new(word_start, word_end),
66− anchor = this_word_start;
67− head = this_word_end;
68− }
69− TextObject::Around => {
76+ TextObject::Around => Range::new(
70− if slice
77+ match slice
71− .get_char(this_word_end + 1)
78+ .get_char(word_start.saturating_sub(1))
72− .map_or(true, char_is_line_ending)
79+ .map(categorize_char)
73 {
80 {
74− head = this_word_end;
81+ None | Some(CharCategory::Eol) => word_start,
75− if slice
82+ _ => prev_grapheme_boundary(slice, word_start),
76− .get_char(this_word_start.saturating_sub(1))
83+ },
77− .map_or(true, char_is_line_ending)
84+ match slice.get_char(word_end).map(categorize_char) {
78− {
85+ None | Some(CharCategory::Eol) => word_end,
79− // single word on a line
86+ _ => 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− }
87+ },
95− }
88+ ),
96− };
89+ }
97− Range::new(anchor, head)
98}
90}
99
91
100pub fn textobject_surround(
92pub fn textobject_surround(
@@ -106,7 +98,10 @@ pub fn textobject_surround(
106) -> Range {
98) -> Range {
107 surround::find_nth_pairs_pos(slice, ch, range.head, count)
99 surround::find_nth_pairs_pos(slice, ch, range.head, count)
108 .map(|(anchor, head)| match textobject {
100 .map(|(anchor, head)| match textobject {
109− TextObject::Inside => Range::new(anchor + 1, head.saturating_sub(1)),
101+ TextObject::Inside => Range::new(
102+ next_grapheme_boundary(slice, anchor),
103+ prev_grapheme_boundary(slice, head),
104+ ),
110 TextObject::Around => Range::new(anchor, head),
105 TextObject::Around => Range::new(anchor, head),
111 })
106 })
112 .unwrap_or(range)
107 .unwrap_or(range)
@@ -126,70 +121,70 @@ mod test {
126 let tests = &[
121 let tests = &[
127 (
122 (
128 "cursor at beginning of doc",
123 "cursor at beginning of doc",
129− vec![(0, Inside, (0, 5)), (0, Around, (0, 6))],
124+ vec![(0, Inside, (0, 6)), (0, Around, (0, 7))],
130 ),
125 ),
131 (
126 (
132 "cursor at middle of word",
127 "cursor at middle of word",
133 vec![
128 vec![
134− (13, Inside, (10, 15)),
129+ (13, Inside, (10, 16)),
135− (10, Inside, (10, 15)),
130+ (10, Inside, (10, 16)),
136− (15, Inside, (10, 15)),
131+ (15, Inside, (10, 16)),
137− (13, Around, (10, 16)),
132+ (13, Around, (9, 17)),
138− (10, Around, (10, 16)),
133+ (10, Around, (9, 17)),
139− (15, Around, (10, 16)),
134+ (15, Around, (9, 17)),
140 ],
135 ],
141 ),
136 ),
142 (
137 (
143 "cursor between word whitespace",
138 "cursor between word whitespace",
144− vec![(6, Inside, (6, 6)), (6, Around, (6, 13))],
139+ vec![(6, Inside, (6, 6)), (6, Around, (6, 6))],
145 ),
140 ),
146 (
141 (
147 "cursor on word before newline\n",
142 "cursor on word before newline\n",
148 vec![
143 vec![
149− (22, Inside, (22, 28)),
144+ (22, Inside, (22, 29)),
150− (28, Inside, (22, 28)),
145+ (28, Inside, (22, 29)),
151− (25, Inside, (22, 28)),
146+ (25, Inside, (22, 29)),
152− (22, Around, (21, 28)),
147+ (22, Around, (21, 29)),
153− (28, Around, (21, 28)),
148+ (28, Around, (21, 29)),
154− (25, Around, (21, 28)),
149+ (25, Around, (21, 29)),
155 ],
150 ],
156 ),
151 ),
157 (
152 (
158 "cursor on newline\nnext line",
153 "cursor on newline\nnext line",
159− vec![(17, Inside, (17, 17)), (17, Around, (17, 22))],
154+ vec![(17, Inside, (17, 17)), (17, Around, (17, 17))],
160 ),
155 ),
161 (
156 (
162 "cursor on word after newline\nnext line",
157 "cursor on word after newline\nnext line",
163 vec![
158 vec![
164− (29, Inside, (29, 32)),
159+ (29, Inside, (29, 33)),
165− (30, Inside, (29, 32)),
160+ (30, Inside, (29, 33)),
166− (32, Inside, (29, 32)),
161+ (32, Inside, (29, 33)),
167− (29, Around, (29, 33)),
162+ (29, Around, (29, 34)),
168− (30, Around, (29, 33)),
163+ (30, Around, (29, 34)),
169− (32, Around, (29, 33)),
164+ (32, Around, (29, 34)),
170 ],
165 ],
171 ),
166 ),
172 (
167 (
173 "cursor on #$%:;* punctuation",
168 "cursor on #$%:;* punctuation",
174 vec![
169 vec![
175− (13, Inside, (10, 15)),
170+ (13, Inside, (10, 16)),
176− (10, Inside, (10, 15)),
171+ (10, Inside, (10, 16)),
177− (15, Inside, (10, 15)),
172+ (15, Inside, (10, 16)),
178− (13, Around, (10, 16)),
173+ (13, Around, (9, 17)),
179− (10, Around, (10, 16)),
174+ (10, Around, (9, 17)),
180− (15, Around, (10, 16)),
175+ (15, Around, (9, 17)),
181 ],
176 ],
182 ),
177 ),
183 (
178 (
184 "cursor on punc%^#$:;.tuation",
179 "cursor on punc%^#$:;.tuation",
185 vec![
180 vec![
186− (14, Inside, (14, 20)),
181+ (14, Inside, (14, 21)),
187− (20, Inside, (14, 20)),
182+ (20, Inside, (14, 21)),
188− (17, Inside, (14, 20)),
183+ (17, Inside, (14, 21)),
189− (14, Around, (14, 20)),
184+ (14, Around, (13, 22)),
190 // FIXME: edge case
185 // FIXME: edge case
191 // (20, Around, (14, 20)),
186 // (20, Around, (14, 20)),
192− (17, Around, (14, 20)),
187+ (17, Around, (13, 22)),
193 ],
188 ],
194 ),
189 ),
195 (
190 (
@@ -198,14 +193,14 @@ mod test {
198 (9, Inside, (9, 9)),
193 (9, Inside, (9, 9)),
199 (10, Inside, (10, 10)),
194 (10, Inside, (10, 10)),
200 (11, Inside, (11, 11)),
195 (11, Inside, (11, 11)),
201− (9, Around, (9, 16)),
196+ (9, Around, (9, 9)),
202− (10, Around, (9, 16)),
197+ (10, Around, (10, 10)),
203− (11, Around, (9, 16)),
198+ (11, Around, (11, 11)),
204 ],
199 ],
205 ),
200 ),
206 (
201 (
207 "cursor at end of doc",
202 "cursor at end of doc",
208− vec![(19, Inside, (17, 19)), (19, Around, (16, 19))],
203+ vec![(19, Inside, (17, 20)), (19, Around, (16, 20))],
209 ),
204 ),
210 ];
205 ];
211
206
@@ -234,67 +229,67 @@ mod test {
234 "simple (single) surround pairs",
229 "simple (single) surround pairs",
235 vec![
230 vec![
236 (3, Inside, (3, 3), '(', 1),
231 (3, Inside, (3, 3), '(', 1),
237− (7, Inside, (8, 13), ')', 1),
232+ (7, Inside, (8, 14), ')', 1),
238− (10, Inside, (8, 13), '(', 1),
233+ (10, Inside, (8, 14), '(', 1),
239− (14, Inside, (8, 13), ')', 1),
234+ (14, Inside, (8, 14), ')', 1),
240 (3, Around, (3, 3), '(', 1),
235 (3, Around, (3, 3), '(', 1),
241− (7, Around, (7, 14), ')', 1),
236+ (7, Around, (7, 15), ')', 1),
242− (10, Around, (7, 14), '(', 1),
237+ (10, Around, (7, 15), '(', 1),
243− (14, Around, (7, 14), ')', 1),
238+ (14, Around, (7, 15), ')', 1),
244 ],
239 ],
245 ),
240 ),
246 (
241 (
247 "samexx 'single' surround pairs",
242 "samexx 'single' surround pairs",
248 vec![
243 vec![
249 (3, Inside, (3, 3), '\'', 1),
244 (3, Inside, (3, 3), '\'', 1),
250− (7, Inside, (8, 13), '\'', 1),
245+ (7, Inside, (7, 7), '\'', 1),
251− (10, Inside, (8, 13), '\'', 1),
246+ (10, Inside, (8, 14), '\'', 1),
252− (14, Inside, (8, 13), '\'', 1),
247+ (14, Inside, (14, 14), '\'', 1),
253 (3, Around, (3, 3), '\'', 1),
248 (3, Around, (3, 3), '\'', 1),
254− (7, Around, (7, 14), '\'', 1),
249+ (7, Around, (7, 7), '\'', 1),
255− (10, Around, (7, 14), '\'', 1),
250+ (10, Around, (7, 15), '\'', 1),
256− (14, Around, (7, 14), '\'', 1),
251+ (14, Around, (14, 14), '\'', 1),
257 ],
252 ],
258 ),
253 ),
259 (
254 (
260 "(nested (surround (pairs)) 3 levels)",
255 "(nested (surround (pairs)) 3 levels)",
261 vec![
256 vec![
262− (0, Inside, (1, 34), '(', 1),
257+ (0, Inside, (1, 35), '(', 1),
263− (6, Inside, (1, 34), ')', 1),
258+ (6, Inside, (1, 35), ')', 1),
264− (8, Inside, (9, 24), '(', 1),
259+ (8, Inside, (9, 25), '(', 1),
265− (8, Inside, (9, 34), ')', 2),
260+ (8, Inside, (9, 35), ')', 2),
266− (20, Inside, (9, 24), '(', 2),
261+ (20, Inside, (9, 25), '(', 2),
267− (20, Inside, (1, 34), ')', 3),
262+ (20, Inside, (1, 35), ')', 3),
268− (0, Around, (0, 35), '(', 1),
263+ (0, Around, (0, 36), '(', 1),
269− (6, Around, (0, 35), ')', 1),
264+ (6, Around, (0, 36), ')', 1),
270− (8, Around, (8, 25), '(', 1),
265+ (8, Around, (8, 26), '(', 1),
271− (8, Around, (8, 35), ')', 2),
266+ (8, Around, (8, 36), ')', 2),
272− (20, Around, (8, 25), '(', 2),
267+ (20, Around, (8, 26), '(', 2),
273− (20, Around, (0, 35), ')', 3),
268+ (20, Around, (0, 36), ')', 3),
274 ],
269 ],
275 ),
270 ),
276 (
271 (
277 "(mixed {surround [pair] same} line)",
272 "(mixed {surround [pair] same} line)",
278 vec![
273 vec![
279− (2, Inside, (1, 33), '(', 1),
274+ (2, Inside, (1, 34), '(', 1),
280− (9, Inside, (8, 27), '{', 1),
275+ (9, Inside, (8, 28), '{', 1),
281− (18, Inside, (18, 21), '[', 1),
276+ (18, Inside, (18, 22), '[', 1),
282− (2, Around, (0, 34), '(', 1),
277+ (2, Around, (0, 35), '(', 1),
283− (9, Around, (7, 28), '{', 1),
278+ (9, Around, (7, 29), '{', 1),
284− (18, Around, (17, 22), '[', 1),
279+ (18, Around, (17, 23), '[', 1),
285 ],
280 ],
286 ),
281 ),
287 (
282 (
288 "(stepped (surround) pairs (should) skip)",
283 "(stepped (surround) pairs (should) skip)",
289− vec![(22, Inside, (1, 38), '(', 1), (22, Around, (0, 39), '(', 1)],
284+ vec![(22, Inside, (1, 39), '(', 1), (22, Around, (0, 40), '(', 1)],
290 ),
285 ),
291 (
286 (
292 "[surround pairs{\non different]\nlines}",
287 "[surround pairs{\non different]\nlines}",
293 vec![
288 vec![
294− (7, Inside, (1, 28), '[', 1),
289+ (7, Inside, (1, 29), '[', 1),
295− (15, Inside, (16, 35), '{', 1),
290+ (15, Inside, (16, 36), '{', 1),
296− (7, Around, (0, 29), '[', 1),
291+ (7, Around, (0, 30), '[', 1),
297− (15, Around, (15, 36), '{', 1),
292+ (15, Around, (15, 37), '{', 1),
298 ],
293 ],
299 ),
294 ),
300 ];
295 ];
helix-term/src/commands.rs+356−265
@@ -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,
@@ -110,7 +107,10 @@ enum Align {
110}
107}
111
108
112fn align_view(doc: &Document, view: &mut View, align: Align) {
109fn align_view(doc: &Document, view: &mut View, align: Align) {
110+ let pos = doc
113− let pos = doc.selection(view.id).cursor();
111+ .selection(view.id)
112+ .primary()
113+ .cursor(doc.text().slice(..));
114 let line = doc.text().char_to_line(pos);
114 let line = doc.text().char_to_line(pos);
115
115
116 let relative = match align {
116 let relative = match align {
@@ -340,7 +340,8 @@ fn move_char_left(cx: &mut Context) {
340 let count = cx.count();
340 let count = cx.count();
341 let (view, doc) = current!(cx.editor);
341 let (view, doc) = current!(cx.editor);
342 let text = doc.text().slice(..);
342 let text = doc.text().slice(..);
343+
343− let selection = doc.selection(view.id).transform(|range| {
344+ let selection = doc.selection(view.id).clone().transform(|range| {
344 movement::move_horizontally(text, range, Direction::Backward, count, Movement::Move)
345 movement::move_horizontally(text, range, Direction::Backward, count, Movement::Move)
345 });
346 });
346 doc.set_selection(view.id, selection);
347 doc.set_selection(view.id, selection);
@@ -350,7 +351,8 @@ fn move_char_right(cx: &mut Context) {
350 let count = cx.count();
351 let count = cx.count();
351 let (view, doc) = current!(cx.editor);
352 let (view, doc) = current!(cx.editor);
352 let text = doc.text().slice(..);
353 let text = doc.text().slice(..);
354+
353− let selection = doc.selection(view.id).transform(|range| {
355+ let selection = doc.selection(view.id).clone().transform(|range| {
354 movement::move_horizontally(text, range, Direction::Forward, count, Movement::Move)
356 movement::move_horizontally(text, range, Direction::Forward, count, Movement::Move)
355 });
357 });
356 doc.set_selection(view.id, selection);
358 doc.set_selection(view.id, selection);
@@ -360,7 +362,8 @@ fn move_line_up(cx: &mut Context) {
360 let count = cx.count();
362 let count = cx.count();
361 let (view, doc) = current!(cx.editor);
363 let (view, doc) = current!(cx.editor);
362 let text = doc.text().slice(..);
364 let text = doc.text().slice(..);
365+
363− let selection = doc.selection(view.id).transform(|range| {
366+ let selection = doc.selection(view.id).clone().transform(|range| {
364 movement::move_vertically(text, range, Direction::Backward, count, Movement::Move)
367 movement::move_vertically(text, range, Direction::Backward, count, Movement::Move)
365 });
368 });
366 doc.set_selection(view.id, selection);
369 doc.set_selection(view.id, selection);
@@ -370,7 +373,8 @@ fn move_line_down(cx: &mut Context) {
370 let count = cx.count();
373 let count = cx.count();
371 let (view, doc) = current!(cx.editor);
374 let (view, doc) = current!(cx.editor);
372 let text = doc.text().slice(..);
375 let text = doc.text().slice(..);
376+
373− let selection = doc.selection(view.id).transform(|range| {
377+ let selection = doc.selection(view.id).clone().transform(|range| {
374 movement::move_vertically(text, range, Direction::Forward, count, Movement::Move)
378 movement::move_vertically(text, range, Direction::Forward, count, Movement::Move)
375 });
379 });
376 doc.set_selection(view.id, selection);
380 doc.set_selection(view.id, selection);
@@ -378,84 +382,61 @@ fn move_line_down(cx: &mut Context) {
378
382
379fn goto_line_end(cx: &mut Context) {
383fn goto_line_end(cx: &mut Context) {
380 let (view, doc) = current!(cx.editor);
384 let (view, doc) = current!(cx.editor);
385+ let text = doc.text().slice(..);
381
386
382− let selection = doc.selection(view.id).transform(|range| {
387+ let selection = doc.selection(view.id).clone().transform(|range| {
383− let text = doc.text();
388+ let line = range.cursor_line(text);
384− let line = text.char_to_line(range.head);
389+ let line_start = text.line_to_char(line);
385
390
386− let pos = line_end_char_index(&text.slice(..), line);
391+ let pos = graphemes::prev_grapheme_boundary(text, line_end_char_index(&text, line))
387− let pos = graphemes::nth_prev_grapheme_boundary(text.slice(..), pos, 1);
392+ .max(line_start);
388− let pos = range.head.max(pos).max(text.line_to_char(line));
389
393
390− Range::new(
394+ range.put_cursor(text, pos, doc.mode == Mode::Select)
391− match doc.mode {
392− Mode::Normal | Mode::Insert => pos,
393− Mode::Select => range.anchor,
394− },
395− pos,
396− )
397 });
395 });
398−
399 doc.set_selection(view.id, selection);
396 doc.set_selection(view.id, selection);
400}
397}
401
398
402fn goto_line_end_newline(cx: &mut Context) {
399fn goto_line_end_newline(cx: &mut Context) {
403 let (view, doc) = current!(cx.editor);
400 let (view, doc) = current!(cx.editor);
401+ let text = doc.text().slice(..);
404
402
405− let selection = doc.selection(view.id).transform(|range| {
403+ let selection = doc.selection(view.id).clone().transform(|range| {
406− let text = doc.text();
404+ let line = range.cursor_line(text);
407− let line = text.char_to_line(range.head);
405+ let pos = line_end_char_index(&text, line);
408
406
409− let pos = line_end_char_index(&text.slice(..), line);
407+ range.put_cursor(text, pos, doc.mode == Mode::Select)
410− Range::new(pos, pos)
411 });
408 });
412−
413 doc.set_selection(view.id, selection);
409 doc.set_selection(view.id, selection);
414}
410}
415
411
416fn goto_line_start(cx: &mut Context) {
412fn goto_line_start(cx: &mut Context) {
417 let (view, doc) = current!(cx.editor);
413 let (view, doc) = current!(cx.editor);
414+ let text = doc.text().slice(..);
418
415
419− let selection = doc.selection(view.id).transform(|range| {
416+ let selection = doc.selection(view.id).clone().transform(|range| {
420− let text = doc.text();
417+ let line = range.cursor_line(text);
421− let line = text.char_to_line(range.head);
422
418
423 // adjust to start of the line
419 // adjust to start of the line
424 let pos = text.line_to_char(line);
420 let pos = text.line_to_char(line);
425− Range::new(
421+ range.put_cursor(text, pos, doc.mode == Mode::Select)
426− match doc.mode {
427− Mode::Normal => range.anchor,
428− Mode::Select | Mode::Insert => pos,
429− },
430− pos,
431− )
432 });
422 });
433−
434 doc.set_selection(view.id, selection);
423 doc.set_selection(view.id, selection);
435}
424}
436
425
437fn goto_first_nonwhitespace(cx: &mut Context) {
426fn goto_first_nonwhitespace(cx: &mut Context) {
438 let (view, doc) = current!(cx.editor);
427 let (view, doc) = current!(cx.editor);
428+ let text = doc.text().slice(..);
439
429
440− let selection = doc.selection(view.id).transform(|range| {
430+ let selection = doc.selection(view.id).clone().transform(|range| {
441− let text = doc.text();
431+ let line = range.cursor_line(text);
442− let line_idx = text.char_to_line(range.head);
443
432
444− if let Some(pos) = find_first_non_whitespace_char(text.line(line_idx)) {
433+ if let Some(pos) = find_first_non_whitespace_char(text.line(line)) {
445− let pos = pos + text.line_to_char(line_idx);
434+ let pos = pos + text.line_to_char(line);
446− Range::new(
435+ range.put_cursor(text, pos, doc.mode == Mode::Select)
447− match doc.mode {
448− Mode::Normal => pos,
449− Mode::Select => range.anchor,
450− Mode::Insert => unreachable!(),
451− },
452− pos,
453− )
454 } else {
436 } else {
455 range
437 range
456 }
438 }
457 });
439 });
458−
459 doc.set_selection(view.id, selection);
440 doc.set_selection(view.id, selection);
460}
441}
461
442
@@ -501,8 +482,8 @@ fn move_next_word_start(cx: &mut Context) {
501
482
502 let selection = doc
483 let selection = doc
503 .selection(view.id)
484 .selection(view.id)
485+ .clone()
504 .transform(|range| movement::move_next_word_start(text, range, count));
486 .transform(|range| movement::move_next_word_start(text, range, count));
505−
506 doc.set_selection(view.id, selection);
487 doc.set_selection(view.id, selection);
507}
488}
508
489
@@ -513,8 +494,8 @@ fn move_prev_word_start(cx: &mut Context) {
513
494
514 let selection = doc
495 let selection = doc
515 .selection(view.id)
496 .selection(view.id)
497+ .clone()
516 .transform(|range| movement::move_prev_word_start(text, range, count));
498 .transform(|range| movement::move_prev_word_start(text, range, count));
517−
518 doc.set_selection(view.id, selection);
499 doc.set_selection(view.id, selection);
519}
500}
520
501
@@ -525,8 +506,8 @@ fn move_next_word_end(cx: &mut Context) {
525
506
526 let selection = doc
507 let selection = doc
527 .selection(view.id)
508 .selection(view.id)
509+ .clone()
528 .transform(|range| movement::move_next_word_end(text, range, count));
510 .transform(|range| movement::move_next_word_end(text, range, count));
529−
530 doc.set_selection(view.id, selection);
511 doc.set_selection(view.id, selection);
531}
512}
532
513
@@ -537,8 +518,8 @@ fn move_next_long_word_start(cx: &mut Context) {
537
518
538 let selection = doc
519 let selection = doc
539 .selection(view.id)
520 .selection(view.id)
521+ .clone()
540 .transform(|range| movement::move_next_long_word_start(text, range, count));
522 .transform(|range| movement::move_next_long_word_start(text, range, count));
541−
542 doc.set_selection(view.id, selection);
523 doc.set_selection(view.id, selection);
543}
524}
544
525
@@ -549,8 +530,8 @@ fn move_prev_long_word_start(cx: &mut Context) {
549
530
550 let selection = doc
531 let selection = doc
551 .selection(view.id)
532 .selection(view.id)
533+ .clone()
552 .transform(|range| movement::move_prev_long_word_start(text, range, count));
534 .transform(|range| movement::move_prev_long_word_start(text, range, count));
553−
554 doc.set_selection(view.id, selection);
535 doc.set_selection(view.id, selection);
555}
536}
556
537
@@ -561,8 +542,8 @@ fn move_next_long_word_end(cx: &mut Context) {
561
542
562 let selection = doc
543 let selection = doc
563 .selection(view.id)
544 .selection(view.id)
545+ .clone()
564 .transform(|range| movement::move_next_long_word_end(text, range, count));
546 .transform(|range| movement::move_next_long_word_end(text, range, count));
565−
566 doc.set_selection(view.id, selection);
547 doc.set_selection(view.id, selection);
567}
548}
568
549
@@ -579,9 +560,7 @@ fn goto_file_start(cx: &mut Context) {
579fn goto_file_end(cx: &mut Context) {
560fn goto_file_end(cx: &mut Context) {
580 push_jump(cx.editor);
561 push_jump(cx.editor);
581 let (view, doc) = current!(cx.editor);
562 let (view, doc) = current!(cx.editor);
582− let text = doc.text();
583− let last_line = text.line_to_char(text.len_lines().saturating_sub(2));
584− doc.set_selection(view.id, Selection::point(last_line));
563+ doc.set_selection(view.id, Selection::point(doc.text().len_chars()));
585}
564}
586
565
587fn extend_next_word_start(cx: &mut Context) {
566fn extend_next_word_start(cx: &mut Context) {
@@ -589,12 +568,11 @@ fn extend_next_word_start(cx: &mut Context) {
589 let (view, doc) = current!(cx.editor);
568 let (view, doc) = current!(cx.editor);
590 let text = doc.text().slice(..);
569 let text = doc.text().slice(..);
591
570
592− let selection = doc.selection(view.id).transform(|range| {
571+ let selection = doc.selection(view.id).clone().transform(|range| {
593 let word = movement::move_next_word_start(text, range, count);
572 let word = movement::move_next_word_start(text, range, count);
594− let pos = word.head;
573+ let pos = word.cursor(text);
595− Range::new(range.anchor, pos)
574+ range.put_cursor(text, pos, true)
596 });
575 });
597−
598 doc.set_selection(view.id, selection);
576 doc.set_selection(view.id, selection);
599}
577}
600
578
@@ -603,10 +581,10 @@ fn extend_prev_word_start(cx: &mut Context) {
603 let (view, doc) = current!(cx.editor);
581 let (view, doc) = current!(cx.editor);
604 let text = doc.text().slice(..);
582 let text = doc.text().slice(..);
605
583
606− let selection = doc.selection(view.id).transform(|range| {
584+ let selection = doc.selection(view.id).clone().transform(|range| {
607 let word = movement::move_prev_word_start(text, range, count);
585 let word = movement::move_prev_word_start(text, range, count);
608− let pos = word.head;
586+ let pos = word.cursor(text);
609− Range::new(range.anchor, pos)
587+ range.put_cursor(text, pos, true)
610 });
588 });
611 doc.set_selection(view.id, selection);
589 doc.set_selection(view.id, selection);
612}
590}
@@ -616,12 +594,11 @@ fn extend_next_word_end(cx: &mut Context) {
616 let (view, doc) = current!(cx.editor);
594 let (view, doc) = current!(cx.editor);
617 let text = doc.text().slice(..);
595 let text = doc.text().slice(..);
618
596
619− let selection = doc.selection(view.id).transform(|range| {
597+ let selection = doc.selection(view.id).clone().transform(|range| {
620 let word = movement::move_next_word_end(text, range, count);
598 let word = movement::move_next_word_end(text, range, count);
621− let pos = word.head;
599+ let pos = word.cursor(text);
622− Range::new(range.anchor, pos)
600+ range.put_cursor(text, pos, true)
623 });
601 });
624−
625 doc.set_selection(view.id, selection);
602 doc.set_selection(view.id, selection);
626}
603}
627
604
@@ -667,26 +644,56 @@ where
667 let (view, doc) = current!(cx.editor);
644 let (view, doc) = current!(cx.editor);
668 let text = doc.text().slice(..);
645 let text = doc.text().slice(..);
669
646
670− let selection = doc.selection(view.id).transform(|range| {
647+ let selection = doc.selection(view.id).clone().transform(|range| {
671− search_fn(text, ch, range.head, count, inclusive).map_or(range, |pos| {
648+ // TODO: use `Range::cursor()` here instead. However, that works in terms of
672− if extend {
649+ // graphemes, whereas this function doesn't yet. So we're doing the same logic
673− Range::new(range.anchor, pos)
650+ // here, but just in terms of chars instead.
651+ let search_start_pos = if range.anchor < range.head {
652+ range.head - 1
674− } else {
653+ } else {
675− // select
676− Range::new(range.head, pos)
654+ range.head
677− }
678− // or (pos, pos) to move to found val
679− })
655+ };
680− });
681
656
657+ search_fn(text, ch, search_start_pos, count, inclusive)
658+ .map_or(range, |pos| range.put_cursor(text, pos, extend))
659+ });
682 doc.set_selection(view.id, selection);
660 doc.set_selection(view.id, selection);
683 })
661 })
684}
662}
685
663
664+fn find_next_char_impl(
665+ text: RopeSlice,
666+ ch: char,
667+ pos: usize,
668+ n: usize,
669+ inclusive: bool,
670+) -> Option<usize> {
671+ let pos = (pos + 1).min(text.len_chars());
672+ if inclusive {
673+ search::find_nth_next(text, ch, pos, n)
674+ } else {
675+ search::find_nth_next(text, ch, pos, n).map(|n| n.saturating_sub(1))
676+ }
677+}
678+
679+fn find_prev_char_impl(
680+ text: RopeSlice,
681+ ch: char,
682+ pos: usize,
683+ n: usize,
684+ inclusive: bool,
685+) -> Option<usize> {
686+ if inclusive {
687+ search::find_nth_prev(text, ch, pos, n)
688+ } else {
689+ search::find_nth_prev(text, ch, pos, n).map(|n| (n + 1).min(text.len_chars()))
690+ }
691+}
692+
686fn find_till_char(cx: &mut Context) {
693fn find_till_char(cx: &mut Context) {
687 find_char_impl(
694 find_char_impl(
688 cx,
695 cx,
689− search::find_nth_next,
696+ find_next_char_impl,
690 false, /* inclusive */
697 false, /* inclusive */
691 false, /* extend */
698 false, /* extend */
692 )
699 )
@@ -695,7 +702,7 @@ fn find_till_char(cx: &mut Context) {
695fn find_next_char(cx: &mut Context) {
702fn find_next_char(cx: &mut Context) {
696 find_char_impl(
703 find_char_impl(
697 cx,
704 cx,
698− search::find_nth_next,
705+ find_next_char_impl,
699 true, /* inclusive */
706 true, /* inclusive */
700 false, /* extend */
707 false, /* extend */
701 )
708 )
@@ -704,7 +711,7 @@ fn find_next_char(cx: &mut Context) {
704fn extend_till_char(cx: &mut Context) {
711fn extend_till_char(cx: &mut Context) {
705 find_char_impl(
712 find_char_impl(
706 cx,
713 cx,
707− search::find_nth_next,
714+ find_next_char_impl,
708 false, /* inclusive */
715 false, /* inclusive */
709 true, /* extend */
716 true, /* extend */
710 )
717 )
@@ -713,7 +720,7 @@ fn extend_till_char(cx: &mut Context) {
713fn extend_next_char(cx: &mut Context) {
720fn extend_next_char(cx: &mut Context) {
714 find_char_impl(
721 find_char_impl(
715 cx,
722 cx,
716− search::find_nth_next,
723+ find_next_char_impl,
717 true, /* inclusive */
724 true, /* inclusive */
718 true, /* extend */
725 true, /* extend */
719 )
726 )
@@ -722,7 +729,7 @@ fn extend_next_char(cx: &mut Context) {
722fn till_prev_char(cx: &mut Context) {
729fn till_prev_char(cx: &mut Context) {
723 find_char_impl(
730 find_char_impl(
724 cx,
731 cx,
725− search::find_nth_prev,
732+ find_prev_char_impl,
726 false, /* inclusive */
733 false, /* inclusive */
727 false, /* extend */
734 false, /* extend */
728 )
735 )
@@ -731,7 +738,7 @@ fn till_prev_char(cx: &mut Context) {
731fn find_prev_char(cx: &mut Context) {
738fn find_prev_char(cx: &mut Context) {
732 find_char_impl(
739 find_char_impl(
733 cx,
740 cx,
734− search::find_nth_prev,
741+ find_prev_char_impl,
735 true, /* inclusive */
742 true, /* inclusive */
736 false, /* extend */
743 false, /* extend */
737 )
744 )
@@ -740,7 +747,7 @@ fn find_prev_char(cx: &mut Context) {
740fn extend_till_prev_char(cx: &mut Context) {
747fn extend_till_prev_char(cx: &mut Context) {
741 find_char_impl(
748 find_char_impl(
742 cx,
749 cx,
743− search::find_nth_prev,
750+ find_prev_char_impl,
744 false, /* inclusive */
751 false, /* inclusive */
745 true, /* extend */
752 true, /* extend */
746 )
753 )
@@ -749,7 +756,7 @@ fn extend_till_prev_char(cx: &mut Context) {
749fn extend_prev_char(cx: &mut Context) {
756fn extend_prev_char(cx: &mut Context) {
750 find_char_impl(
757 find_char_impl(
751 cx,
758 cx,
752− search::find_nth_prev,
759+ find_prev_char_impl,
753 true, /* inclusive */
760 true, /* inclusive */
754 true, /* extend */
761 true, /* extend */
755 )
762 )
@@ -773,24 +780,29 @@ fn replace(cx: &mut Context) {
773 _ => None,
780 _ => None,
774 };
781 };
775
782
776− if let Some(ch) = ch {
777− let transaction =
778− Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
783+ let selection = doc.selection(view.id);
779− let max_to = rope_end_without_line_ending(&doc.text().slice(..));
780− let to = std::cmp::min(max_to, range.to() + 1);
781− let text: String = RopeGraphemes::new(doc.text().slice(range.from()..to))
782− .map(|g| {
783− let cow: Cow<str> = g.into();
784− if str_is_line_ending(&cow) {
785− cow
786− } else {
787− ch.into()
788− }
789− })
790− .collect();
791
784
785+ if let Some(ch) = ch {
786+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
787+ if !range.is_empty() {
788+ let text: String =
789+ RopeGraphemes::new(doc.text().slice(range.from()..range.to()))
790+ .map(|g| {
791+ let cow: Cow<str> = g.into();
792+ if str_is_line_ending(&cow) {
793+ cow
794+ } else {
795+ ch.into()
796+ }
797+ })
798+ .collect();
799+
792− (range.from(), to, Some(text.into()))
800+ (range.from(), range.to(), Some(text.into()))
801+ } else {
802+ // No change.
803+ (range.from(), range.to(), None)
804+ }
793− });
805+ });
794
806
795 doc.apply(&transaction, view.id);
807 doc.apply(&transaction, view.id);
796 doc.append_changes_to_history(view.id);
808 doc.append_changes_to_history(view.id);
@@ -800,24 +812,24 @@ fn replace(cx: &mut Context) {
800
812
801fn switch_case(cx: &mut Context) {
813fn switch_case(cx: &mut Context) {
802 let (view, doc) = current!(cx.editor);
814 let (view, doc) = current!(cx.editor);
803− let transaction =
815+ let selection = doc.selection(view.id);
804− Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
816+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
805− let text: Tendril = range
817+ let text: Tendril = range
806− .fragment(doc.text().slice(..))
818+ .fragment(doc.text().slice(..))
807− .chars()
819+ .chars()
808− .flat_map(|ch| {
820+ .flat_map(|ch| {
809− if ch.is_lowercase() {
821+ if ch.is_lowercase() {
810− ch.to_uppercase().collect()
822+ ch.to_uppercase().collect()
811− } else if ch.is_uppercase() {
823+ } else if ch.is_uppercase() {
812− ch.to_lowercase().collect()
824+ ch.to_lowercase().collect()
813− } else {
825+ } else {
814− vec![ch]
826+ vec![ch]
815− }
827+ }
816− })
828+ })
817− .collect();
829+ .collect();
818
830
819− (range.from(), range.to() + 1, Some(text))
831+ (range.from(), range.to(), Some(text))
820− });
832+ });
821
833
822 doc.apply(&transaction, view.id);
834 doc.apply(&transaction, view.id);
823 doc.append_changes_to_history(view.id);
835 doc.append_changes_to_history(view.id);
@@ -825,12 +837,12 @@ fn switch_case(cx: &mut Context) {
825
837
826fn switch_to_uppercase(cx: &mut Context) {
838fn switch_to_uppercase(cx: &mut Context) {
827 let (view, doc) = current!(cx.editor);
839 let (view, doc) = current!(cx.editor);
828− let transaction =
840+ let selection = doc.selection(view.id);
829− Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
841+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
830− let text: Tendril = range.fragment(doc.text().slice(..)).to_uppercase().into();
842+ let text: Tendril = range.fragment(doc.text().slice(..)).to_uppercase().into();
831
843
832− (range.from(), range.to() + 1, Some(text))
844+ (range.from(), range.to(), Some(text))
833− });
845+ });
834
846
835 doc.apply(&transaction, view.id);
847 doc.apply(&transaction, view.id);
836 doc.append_changes_to_history(view.id);
848 doc.append_changes_to_history(view.id);
@@ -838,12 +850,12 @@ fn switch_to_uppercase(cx: &mut Context) {
838
850
839fn switch_to_lowercase(cx: &mut Context) {
851fn switch_to_lowercase(cx: &mut Context) {
840 let (view, doc) = current!(cx.editor);
852 let (view, doc) = current!(cx.editor);
841− let transaction =
853+ let selection = doc.selection(view.id);
842− Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
854+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
843− let text: Tendril = range.fragment(doc.text().slice(..)).to_lowercase().into();
855+ let text: Tendril = range.fragment(doc.text().slice(..)).to_lowercase().into();
844
856
845− (range.from(), range.to() + 1, Some(text))
857+ (range.from(), range.to(), Some(text))
846− });
858+ });
847
859
848 doc.apply(&transaction, view.id);
860 doc.apply(&transaction, view.id);
849 doc.append_changes_to_history(view.id);
861 doc.append_changes_to_history(view.id);
@@ -852,7 +864,12 @@ fn switch_to_lowercase(cx: &mut Context) {
852fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
864fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
853 use Direction::*;
865 use Direction::*;
854 let (view, doc) = current!(cx.editor);
866 let (view, doc) = current!(cx.editor);
855− let cursor = coords_at_pos(doc.text().slice(..), doc.selection(view.id).cursor());
867+ let cursor = coords_at_pos(
868+ doc.text().slice(..),
869+ doc.selection(view.id)
870+ .primary()
871+ .cursor(doc.text().slice(..)),
872+ );
856 let doc_last_line = doc.text().len_lines() - 1;
873 let doc_last_line = doc.text().len_lines() - 1;
857
874
858 let last_line = view.last_line(doc);
875 let last_line = view.last_line(doc);
@@ -881,7 +898,7 @@ fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
881 .min(last_line.saturating_sub(scrolloff));
898 .min(last_line.saturating_sub(scrolloff));
882
899
883 let text = doc.text().slice(..);
900 let text = doc.text().slice(..);
884− let pos = pos_at_coords(text, Position::new(line, cursor.col)); // this func will properly truncate to line end
901+ let pos = pos_at_coords(text, Position::new(line, cursor.col), true); // this func will properly truncate to line end
885
902
886 // TODO: only manipulate main selection
903 // TODO: only manipulate main selection
887 doc.set_selection(view.id, Selection::point(pos));
904 doc.set_selection(view.id, Selection::point(pos));
@@ -915,7 +932,8 @@ fn extend_char_left(cx: &mut Context) {
915 let count = cx.count();
932 let count = cx.count();
916 let (view, doc) = current!(cx.editor);
933 let (view, doc) = current!(cx.editor);
917 let text = doc.text().slice(..);
934 let text = doc.text().slice(..);
935+
918− let selection = doc.selection(view.id).transform(|range| {
936+ let selection = doc.selection(view.id).clone().transform(|range| {
919 movement::move_horizontally(text, range, Direction::Backward, count, Movement::Extend)
937 movement::move_horizontally(text, range, Direction::Backward, count, Movement::Extend)
920 });
938 });
921 doc.set_selection(view.id, selection);
939 doc.set_selection(view.id, selection);
@@ -925,7 +943,8 @@ fn extend_char_right(cx: &mut Context) {
925 let count = cx.count();
943 let count = cx.count();
926 let (view, doc) = current!(cx.editor);
944 let (view, doc) = current!(cx.editor);
927 let text = doc.text().slice(..);
945 let text = doc.text().slice(..);
946+
928− let selection = doc.selection(view.id).transform(|range| {
947+ let selection = doc.selection(view.id).clone().transform(|range| {
929 movement::move_horizontally(text, range, Direction::Forward, count, Movement::Extend)
948 movement::move_horizontally(text, range, Direction::Forward, count, Movement::Extend)
930 });
949 });
931 doc.set_selection(view.id, selection);
950 doc.set_selection(view.id, selection);
@@ -935,7 +954,8 @@ fn extend_line_up(cx: &mut Context) {
935 let count = cx.count();
954 let count = cx.count();
936 let (view, doc) = current!(cx.editor);
955 let (view, doc) = current!(cx.editor);
937 let text = doc.text().slice(..);
956 let text = doc.text().slice(..);
957+
938− let selection = doc.selection(view.id).transform(|range| {
958+ let selection = doc.selection(view.id).clone().transform(|range| {
939 movement::move_vertically(text, range, Direction::Backward, count, Movement::Extend)
959 movement::move_vertically(text, range, Direction::Backward, count, Movement::Extend)
940 });
960 });
941 doc.set_selection(view.id, selection);
961 doc.set_selection(view.id, selection);
@@ -945,7 +965,8 @@ fn extend_line_down(cx: &mut Context) {
945 let count = cx.count();
965 let count = cx.count();
946 let (view, doc) = current!(cx.editor);
966 let (view, doc) = current!(cx.editor);
947 let text = doc.text().slice(..);
967 let text = doc.text().slice(..);
968+
948− let selection = doc.selection(view.id).transform(|range| {
969+ let selection = doc.selection(view.id).clone().transform(|range| {
949 movement::move_vertically(text, range, Direction::Forward, count, Movement::Extend)
970 movement::move_vertically(text, range, Direction::Forward, count, Movement::Extend)
950 });
971 });
951 doc.set_selection(view.id, selection);
972 doc.set_selection(view.id, selection);
@@ -954,7 +975,7 @@ fn extend_line_down(cx: &mut Context) {
954fn select_all(cx: &mut Context) {
975fn select_all(cx: &mut Context) {
955 let (view, doc) = current!(cx.editor);
976 let (view, doc) = current!(cx.editor);
956
977
957− let end = rope_end_without_line_ending(&doc.text().slice(..));
978+ let end = doc.text().len_chars();
958 doc.set_selection(view.id, Selection::single(0, end))
979 doc.set_selection(view.id, Selection::single(0, end))
959}
980}
960
981
@@ -992,9 +1013,14 @@ fn split_selection_on_newline(cx: &mut Context) {
992}
1013}
993
1014
994fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Regex, extend: bool) {
1015fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Regex, extend: bool) {
995− let text = doc.text();
1016+ let text = doc.text().slice(..);
996 let selection = doc.selection(view.id);
1017 let selection = doc.selection(view.id);
1018+
1019+ // Get the right side of the primary block cursor.
997− let start = text.char_to_byte(selection.cursor());
1020+ let start = text.char_to_byte(graphemes::next_grapheme_boundary(
1021+ text,
1022+ selection.primary().cursor(text),
1023+ ));
998
1024
999 // use find_at to find the next match after the cursor, loop around the end
1025 // use find_at to find the next match after the cursor, loop around the end
1000 // Careful, `Regex` uses `bytes` as offsets, not character indices!
1026 // Careful, `Regex` uses `bytes` as offsets, not character indices!
@@ -1011,12 +1037,10 @@ fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Rege
1011 return;
1037 return;
1012 }
1038 }
1013
1039
1014− let head = end - 1;
1015−
1016 let selection = if extend {
1040 let selection = if extend {
1017− selection.clone().push(Range::new(start, head))
1041+ selection.clone().push(Range::new(start, end))
1018 } else {
1042 } else {
1019− Selection::single(start, head)
1043+ Selection::single(start, end)
1020 };
1044 };
1021
1045
1022 doc.set_selection(view.id, selection);
1046 doc.set_selection(view.id, selection);
@@ -1079,16 +1103,15 @@ fn extend_line(cx: &mut Context) {
1079 let count = cx.count();
1103 let count = cx.count();
1080 let (view, doc) = current!(cx.editor);
1104 let (view, doc) = current!(cx.editor);
1081
1105
1082− let pos = doc.selection(view.id).primary();
1083 let text = doc.text();
1106 let text = doc.text();
1107+ let range = doc.selection(view.id).primary();
1084
1108
1085− let line_start = text.char_to_line(pos.anchor);
1109+ let (start_line, end_line) = range.line_range(text.slice(..));
1086− let start = text.line_to_char(line_start);
1110+ let start = text.line_to_char(start_line);
1087− let line_end = text.char_to_line(pos.head);
1111+ let mut end = text.line_to_char((end_line + count).min(text.len_lines()));
1088− let mut end = line_end_char_index(&text.slice(..), line_end + count.saturating_sub(1));
1089
1112
1090− if pos.anchor == start && pos.head == end && line_end < (text.len_lines() - 2) {
1113+ if range.from() == start && range.to() == end {
1091− end = line_end_char_index(&text.slice(..), line_end + 1);
1114+ end = text.line_to_char((end_line + count + 1).min(text.len_lines()));
1092 }
1115 }
1093
1116
1094 doc.set_selection(view.id, Selection::single(start, end));
1117 doc.set_selection(view.id, Selection::single(start, end));
@@ -1097,41 +1120,36 @@ fn extend_line(cx: &mut Context) {
1097fn extend_to_line_bounds(cx: &mut Context) {
1120fn extend_to_line_bounds(cx: &mut Context) {
1098 let (view, doc) = current!(cx.editor);
1121 let (view, doc) = current!(cx.editor);
1099
1122
1100− let text = doc.text();
1123+ doc.set_selection(
1124+ view.id,
1101− let selection = doc.selection(view.id).transform(|range| {
1125+ doc.selection(view.id).clone().transform(|range| {
1102− let start = text.line_to_char(text.char_to_line(range.from()));
1126+ let text = doc.text();
1103− let end = text
1104− .line_to_char(text.char_to_line(range.to()) + 1)
1105− .saturating_sub(1);
1106−
1107− if range.anchor < range.head {
1108− Range::new(start, end)
1109− } else {
1110− Range::new(end, start)
1111− }
1112− });
1113
1127
1114− doc.set_selection(view.id, selection);
1128+ let (start_line, end_line) = range.line_range(text.slice(..));
1129+ let start = text.line_to_char(start_line);
1130+ let end = text.line_to_char((end_line + 1).min(text.len_lines()));
1131+
1132+ if range.anchor <= range.head {
1133+ Range::new(start, end)
1134+ } else {
1135+ Range::new(end, start)
1136+ }
1137+ }),
1138+ );
1115}
1139}
1116
1140
1117fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId) {
1141fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId) {
1118− // first yank the selection
1119− let values: Vec<String> = doc
1120− .selection(view_id)
1121− .fragments(doc.text().slice(..))
1142+ let text = doc.text().slice(..);
1122− .map(Cow::into_owned)
1143+ let selection = doc.selection(view_id);
1123− .collect();
1124
1144
1145+ // first yank the selection
1146+ let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
1125 reg.write(values);
1147 reg.write(values);
1126
1148
1127 // then delete
1149 // then delete
1128− let transaction =
1129− Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
1150+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
1130− let alltext = doc.text().slice(..);
1131− let max_to = rope_end_without_line_ending(&alltext);
1132− let to = std::cmp::min(max_to, range.to() + 1);
1133− (range.from(), to, None)
1151+ (range.from(), range.to(), None)
1134− });
1152+ });
1135 doc.apply(&transaction, view_id);
1153 doc.apply(&transaction, view_id);
1136}
1154}
1137
1155
@@ -1159,19 +1177,22 @@ fn change_selection(cx: &mut Context) {
1159
1177
1160fn collapse_selection(cx: &mut Context) {
1178fn collapse_selection(cx: &mut Context) {
1161 let (view, doc) = current!(cx.editor);
1179 let (view, doc) = current!(cx.editor);
1162− let selection = doc
1180+ let text = doc.text().slice(..);
1163− .selection(view.id)
1164− .transform(|range| Range::new(range.head, range.head));
1165
1181
1182+ let selection = doc.selection(view.id).clone().transform(|range| {
1183+ let pos = range.cursor(text);
1184+ Range::new(pos, pos)
1185+ });
1166 doc.set_selection(view.id, selection);
1186 doc.set_selection(view.id, selection);
1167}
1187}
1168
1188
1169fn flip_selections(cx: &mut Context) {
1189fn flip_selections(cx: &mut Context) {
1170 let (view, doc) = current!(cx.editor);
1190 let (view, doc) = current!(cx.editor);
1191+
1171 let selection = doc
1192 let selection = doc
1172 .selection(view.id)
1193 .selection(view.id)
1194+ .clone()
1173 .transform(|range| Range::new(range.head, range.anchor));
1195 .transform(|range| Range::new(range.head, range.anchor));
1174−
1175 doc.set_selection(view.id, selection);
1196 doc.set_selection(view.id, selection);
1176}
1197}
1177
1198
@@ -1186,6 +1207,7 @@ fn insert_mode(cx: &mut Context) {
1186
1207
1187 let selection = doc
1208 let selection = doc
1188 .selection(view.id)
1209 .selection(view.id)
1210+ .clone()
1189 .transform(|range| Range::new(range.to(), range.from()));
1211 .transform(|range| Range::new(range.to(), range.from()));
1190 doc.set_selection(view.id, selection);
1212 doc.set_selection(view.id, selection);
1191}
1213}
@@ -1195,18 +1217,13 @@ fn append_mode(cx: &mut Context) {
1195 let (view, doc) = current!(cx.editor);
1217 let (view, doc) = current!(cx.editor);
1196 enter_insert_mode(doc);
1218 enter_insert_mode(doc);
1197 doc.restore_cursor = true;
1219 doc.restore_cursor = true;
1198−
1199 let text = doc.text().slice(..);
1220 let text = doc.text().slice(..);
1200− let selection = doc.selection(view.id).transform(|range| {
1201− Range::new(
1202− range.from(),
1203− graphemes::next_grapheme_boundary(text, range.to()), // to() + next char
1204− )
1205− });
1206
1221
1222+ // Make sure there's room at the end of the document if the last
1223+ // selection butts up against it.
1207 let end = text.len_chars();
1224 let end = text.len_chars();
1208−
1225+ let last_range = doc.selection(view.id).iter().last().unwrap();
1209− if selection.iter().any(|range| range.head == end) {
1226+ if !last_range.is_empty() && last_range.head == end {
1210 let transaction = Transaction::change(
1227 let transaction = Transaction::change(
1211 doc.text(),
1228 doc.text(),
1212 std::array::IntoIter::new([(end, end, Some(doc.line_ending.as_str().into()))]),
1229 std::array::IntoIter::new([(end, end, Some(doc.line_ending.as_str().into()))]),
@@ -1214,6 +1231,12 @@ fn append_mode(cx: &mut Context) {
1214 doc.apply(&transaction, view.id);
1231 doc.apply(&transaction, view.id);
1215 }
1232 }
1216
1233
1234+ let selection = doc.selection(view.id).clone().transform(|range| {
1235+ Range::new(
1236+ range.from(),
1237+ graphemes::next_grapheme_boundary(doc.text().slice(..), range.to()),
1238+ )
1239+ });
1217 doc.set_selection(view.id, selection);
1240 doc.set_selection(view.id, selection);
1218}
1241}
1219
1242
@@ -1636,11 +1659,10 @@ mod cmd {
1636
1659
1637 match cx.editor.clipboard_provider.get_contents() {
1660 match cx.editor.clipboard_provider.get_contents() {
1638 Ok(contents) => {
1661 Ok(contents) => {
1662+ let selection = doc.selection(view.id);
1639 let transaction =
1663 let transaction =
1640− Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
1664+ Transaction::change_by_selection(doc.text(), &selection, |range| {
1641− let max_to = rope_end_without_line_ending(&doc.text().slice(..));
1642− let to = std::cmp::min(max_to, range.to() + 1);
1643− (range.from(), to, Some(contents.as_str().into()))
1665+ (range.from(), range.to(), Some(contents.as_str().into()))
1644 });
1666 });
1645
1667
1646 doc.apply(&transaction, view.id);
1668 doc.apply(&transaction, view.id);
@@ -2236,10 +2258,10 @@ fn append_to_line(cx: &mut Context) {
2236 let (view, doc) = current!(cx.editor);
2258 let (view, doc) = current!(cx.editor);
2237 enter_insert_mode(doc);
2259 enter_insert_mode(doc);
2238
2260
2239− let selection = doc.selection(view.id).transform(|range| {
2261+ let selection = doc.selection(view.id).clone().transform(|range| {
2240− let text = doc.text();
2262+ let text = doc.text().slice(..);
2241− let line = text.char_to_line(range.head);
2263+ let line = range.cursor_line(text);
2242− let pos = line_end_char_index(&text.slice(..), line);
2264+ let pos = line_end_char_index(&text, line);
2243 Range::new(pos, pos)
2265 Range::new(pos, pos)
2244 });
2266 });
2245 doc.set_selection(view.id, selection);
2267 doc.set_selection(view.id, selection);
@@ -2299,7 +2321,7 @@ fn open(cx: &mut Context, open: Open) {
2299 let mut offs = 0;
2321 let mut offs = 0;
2300
2322
2301 let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
2323 let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
2302− let line = text.char_to_line(range.head);
2324+ let line = range.cursor_line(text);
2303
2325
2304 let line = match open {
2326 let line = match open {
2305 // adjust position to the end of the line (next line - 1)
2327 // adjust position to the end of the line (next line - 1)
@@ -2373,7 +2395,7 @@ fn normal_mode(cx: &mut Context) {
2373 // if leaving append mode, move cursor back by 1
2395 // if leaving append mode, move cursor back by 1
2374 if doc.restore_cursor {
2396 if doc.restore_cursor {
2375 let text = doc.text().slice(..);
2397 let text = doc.text().slice(..);
2376− let selection = doc.selection(view.id).transform(|range| {
2398+ let selection = doc.selection(view.id).clone().transform(|range| {
2377 Range::new(
2399 Range::new(
2378 range.from(),
2400 range.from(),
2379 graphemes::prev_grapheme_boundary(text, range.to()),
2401 graphemes::prev_grapheme_boundary(text, range.to()),
@@ -2413,6 +2435,23 @@ fn goto_last_accessed_file(cx: &mut Context) {
2413}
2435}
2414
2436
2415fn select_mode(cx: &mut Context) {
2437fn select_mode(cx: &mut Context) {
2438+ let (view, doc) = current!(cx.editor);
2439+ let text = doc.text().slice(..);
2440+
2441+ // Make sure end-of-document selections are also 1-width.
2442+ // (With the exception of being in an empty document, of course.)
2443+ let selection = doc.selection(view.id).clone().transform(|range| {
2444+ if range.is_empty() && range.head == text.len_chars() {
2445+ Range::new(
2446+ graphemes::prev_grapheme_boundary(text, range.anchor),
2447+ range.head,
2448+ )
2449+ } else {
2450+ range
2451+ }
2452+ });
2453+ doc.set_selection(view.id, selection);
2454+
2416 doc_mut!(cx.editor).mode = Mode::Select;
2455 doc_mut!(cx.editor).mode = Mode::Select;
2417}
2456}
2418
2457
@@ -2485,7 +2524,13 @@ fn goto_definition(cx: &mut Context) {
2485
2524
2486 let offset_encoding = language_server.offset_encoding();
2525 let offset_encoding = language_server.offset_encoding();
2487
2526
2488− let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
2527+ let pos = pos_to_lsp_pos(
2528+ doc.text(),
2529+ doc.selection(view.id)
2530+ .primary()
2531+ .cursor(doc.text().slice(..)),
2532+ offset_encoding,
2533+ );
2489
2534
2490 // TODO: handle fails
2535 // TODO: handle fails
2491 let future = language_server.goto_definition(doc.identifier(), pos, None);
2536 let future = language_server.goto_definition(doc.identifier(), pos, None);
@@ -2522,7 +2567,13 @@ fn goto_type_definition(cx: &mut Context) {
2522
2567
2523 let offset_encoding = language_server.offset_encoding();
2568 let offset_encoding = language_server.offset_encoding();
2524
2569
2525− let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
2570+ let pos = pos_to_lsp_pos(
2571+ doc.text(),
2572+ doc.selection(view.id)
2573+ .primary()
2574+ .cursor(doc.text().slice(..)),
2575+ offset_encoding,
2576+ );
2526
2577
2527 // TODO: handle fails
2578 // TODO: handle fails
2528 let future = language_server.goto_type_definition(doc.identifier(), pos, None);
2579 let future = language_server.goto_type_definition(doc.identifier(), pos, None);
@@ -2559,7 +2610,13 @@ fn goto_implementation(cx: &mut Context) {
2559
2610
2560 let offset_encoding = language_server.offset_encoding();
2611 let offset_encoding = language_server.offset_encoding();
2561
2612
2562− let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
2613+ let pos = pos_to_lsp_pos(
2614+ doc.text(),
2615+ doc.selection(view.id)
2616+ .primary()
2617+ .cursor(doc.text().slice(..)),
2618+ offset_encoding,
2619+ );
2563
2620
2564 // TODO: handle fails
2621 // TODO: handle fails
2565 let future = language_server.goto_implementation(doc.identifier(), pos, None);
2622 let future = language_server.goto_implementation(doc.identifier(), pos, None);
@@ -2596,7 +2653,13 @@ fn goto_reference(cx: &mut Context) {
2596
2653
2597 let offset_encoding = language_server.offset_encoding();
2654 let offset_encoding = language_server.offset_encoding();
2598
2655
2599− let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
2656+ let pos = pos_to_lsp_pos(
2657+ doc.text(),
2658+ doc.selection(view.id)
2659+ .primary()
2660+ .cursor(doc.text().slice(..)),
2661+ offset_encoding,
2662+ );
2600
2663
2601 // TODO: handle fails
2664 // TODO: handle fails
2602 let future = language_server.goto_reference(doc.identifier(), pos, None);
2665 let future = language_server.goto_reference(doc.identifier(), pos, None);
@@ -2655,7 +2718,10 @@ fn goto_next_diag(cx: &mut Context) {
2655 let editor = &mut cx.editor;
2718 let editor = &mut cx.editor;
2656 let (view, doc) = current!(editor);
2719 let (view, doc) = current!(editor);
2657
2720
2658− let cursor_pos = doc.selection(view.id).cursor();
2721+ let cursor_pos = doc
2722+ .selection(view.id)
2723+ .primary()
2724+ .cursor(doc.text().slice(..));
2659 let diag = if let Some(diag) = doc
2725 let diag = if let Some(diag) = doc
2660 .diagnostics()
2726 .diagnostics()
2661 .iter()
2727 .iter()
@@ -2676,7 +2742,10 @@ fn goto_prev_diag(cx: &mut Context) {
2676 let editor = &mut cx.editor;
2742 let editor = &mut cx.editor;
2677 let (view, doc) = current!(editor);
2743 let (view, doc) = current!(editor);
2678
2744
2679− let cursor_pos = doc.selection(view.id).cursor();
2745+ let cursor_pos = doc
2746+ .selection(view.id)
2747+ .primary()
2748+ .cursor(doc.text().slice(..));
2680 let diag = if let Some(diag) = doc
2749 let diag = if let Some(diag) = doc
2681 .diagnostics()
2750 .diagnostics()
2682 .iter()
2751 .iter()
@@ -2704,7 +2773,9 @@ fn signature_help(cx: &mut Context) {
2704
2773
2705 let pos = pos_to_lsp_pos(
2774 let pos = pos_to_lsp_pos(
2706 doc.text(),
2775 doc.text(),
2707− doc.selection(view.id).cursor(),
2776+ doc.selection(view.id)
2777+ .primary()
2778+ .cursor(doc.text().slice(..)),
2708 language_server.offset_encoding(),
2779 language_server.offset_encoding(),
2709 );
2780 );
2710
2781
@@ -2827,11 +2898,11 @@ pub mod insert {
2827 let (view, doc) = current!(cx.editor);
2898 let (view, doc) = current!(cx.editor);
2828
2899
2829 let text = doc.text();
2900 let text = doc.text();
2830− let selection = doc.selection(view.id);
2901+ let selection = doc.selection(view.id).clone().cursors(text.slice(..));
2831
2902
2832 // run through insert hooks, stopping on the first one that returns Some(t)
2903 // run through insert hooks, stopping on the first one that returns Some(t)
2833 for hook in HOOKS {
2904 for hook in HOOKS {
2834− if let Some(transaction) = hook(text, selection, c) {
2905+ if let Some(transaction) = hook(text, &selection, c) {
2835 doc.apply(&transaction, view.id);
2906 doc.apply(&transaction, view.id);
2836 break;
2907 break;
2837 }
2908 }
@@ -2851,7 +2922,11 @@ pub mod insert {
2851 // indent by one to reach 4 spaces).
2922 // indent by one to reach 4 spaces).
2852
2923
2853 let indent = Tendril::from(doc.indent_unit());
2924 let indent = Tendril::from(doc.indent_unit());
2854− let transaction = Transaction::insert(doc.text(), doc.selection(view.id), indent);
2925+ let transaction = Transaction::insert(
2926+ doc.text(),
2927+ &doc.selection(view.id).clone().cursors(doc.text().slice(..)),
2928+ indent,
2929+ );
2855 doc.apply(&transaction, view.id);
2930 doc.apply(&transaction, view.id);
2856 }
2931 }
2857
2932
@@ -2860,13 +2935,13 @@ pub mod insert {
2860 let text = doc.text().slice(..);
2935 let text = doc.text().slice(..);
2861
2936
2862 let contents = doc.text();
2937 let contents = doc.text();
2863− let selection = doc.selection(view.id);
2938+ let selection = doc.selection(view.id).clone().cursors(text);
2864 let mut ranges = SmallVec::with_capacity(selection.len());
2939 let mut ranges = SmallVec::with_capacity(selection.len());
2865
2940
2866 // TODO: this is annoying, but we need to do it to properly calculate pos after edits
2941 // TODO: this is annoying, but we need to do it to properly calculate pos after edits
2867 let mut offs = 0;
2942 let mut offs = 0;
2868
2943
2869− let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
2944+ let mut transaction = Transaction::change_by_selection(contents, &selection, |range| {
2870 let pos = range.head;
2945 let pos = range.head;
2871
2946
2872 let prev = if pos == 0 {
2947 let prev = if pos == 0 {
@@ -2929,9 +3004,10 @@ pub mod insert {
2929 let text = doc.text().slice(..);
3004 let text = doc.text().slice(..);
2930 let transaction =
3005 let transaction =
2931 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3006 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3007+ let pos = range.cursor(text);
2932 (
3008 (
2933− graphemes::nth_prev_grapheme_boundary(text, range.head, count),
3009+ graphemes::nth_prev_grapheme_boundary(text, pos, count),
2934− range.head,
3010+ pos,
2935 None,
3011 None,
2936 )
3012 )
2937 });
3013 });
@@ -2944,9 +3020,10 @@ pub mod insert {
2944 let text = doc.text().slice(..);
3020 let text = doc.text().slice(..);
2945 let transaction =
3021 let transaction =
2946 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3022 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3023+ let pos = range.cursor(text);
2947 (
3024 (
2948− range.head,
3025+ pos,
2949− graphemes::nth_next_grapheme_boundary(text, range.head, count),
3026+ graphemes::nth_next_grapheme_boundary(text, pos, count),
2950 None,
3027 None,
2951 )
3028 )
2952 });
3029 });
@@ -2957,8 +3034,10 @@ pub mod insert {
2957 let count = cx.count();
3034 let count = cx.count();
2958 let (view, doc) = current!(cx.editor);
3035 let (view, doc) = current!(cx.editor);
2959 let text = doc.text().slice(..);
3036 let text = doc.text().slice(..);
3037+
2960 let selection = doc
3038 let selection = doc
2961 .selection(view.id)
3039 .selection(view.id)
3040+ .clone()
2962 .transform(|range| movement::move_prev_word_start(text, range, count));
3041 .transform(|range| movement::move_prev_word_start(text, range, count));
2963 doc.set_selection(view.id, selection);
3042 doc.set_selection(view.id, selection);
2964 delete_selection(cx)
3043 delete_selection(cx)
@@ -2986,9 +3065,11 @@ fn redo(cx: &mut Context) {
2986
3065
2987fn yank(cx: &mut Context) {
3066fn yank(cx: &mut Context) {
2988 let (view, doc) = current!(cx.editor);
3067 let (view, doc) = current!(cx.editor);
3068+ let text = doc.text().slice(..);
3069+
2989 let values: Vec<String> = doc
3070 let values: Vec<String> = doc
2990 .selection(view.id)
3071 .selection(view.id)
2991− .fragments(doc.text().slice(..))
3072+ .fragments(text)
2992 .map(Cow::into_owned)
3073 .map(Cow::into_owned)
2993 .collect();
3074 .collect();
2994
3075
@@ -3007,10 +3088,11 @@ fn yank(cx: &mut Context) {
3007
3088
3008fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) -> anyhow::Result<()> {
3089fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) -> anyhow::Result<()> {
3009 let (view, doc) = current!(editor);
3090 let (view, doc) = current!(editor);
3091+ let text = doc.text().slice(..);
3010
3092
3011 let values: Vec<String> = doc
3093 let values: Vec<String> = doc
3012 .selection(view.id)
3094 .selection(view.id)
3013− .fragments(doc.text().slice(..))
3095+ .fragments(text)
3014 .map(Cow::into_owned)
3096 .map(Cow::into_owned)
3015 .collect();
3097 .collect();
3016
3098
@@ -3038,11 +3120,9 @@ fn yank_joined_to_clipboard(cx: &mut Context) {
3038
3120
3039fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> {
3121fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> {
3040 let (view, doc) = current!(editor);
3122 let (view, doc) = current!(editor);
3123+ let text = doc.text().slice(..);
3041
3124
3042− let value = doc
3043− .selection(view.id)
3125+ let value = doc.selection(view.id).primary().fragment(text);
3044− .primary()
3045− .fragment(doc.text().slice(..));
3046
3126
3047 if let Err(e) = editor.clipboard_provider.set_contents(value.into_owned()) {
3127 if let Err(e) = editor.clipboard_provider.set_contents(value.into_owned()) {
3048 bail!("Couldn't set system clipboard content: {:?}", e);
3128 bail!("Couldn't set system clipboard content: {:?}", e);
@@ -3083,17 +3163,21 @@ fn paste_impl(
3083 let mut values = values.iter().cloned().map(Tendril::from).chain(repeat);
3163 let mut values = values.iter().cloned().map(Tendril::from).chain(repeat);
3084
3164
3085 let text = doc.text();
3165 let text = doc.text();
3166+ let selection = doc.selection(view.id);
3086
3167
3087− let transaction = Transaction::change_by_selection(text, doc.selection(view.id), |range| {
3168+ let transaction = Transaction::change_by_selection(text, selection, |range| {
3088 let pos = match (action, linewise) {
3169 let pos = match (action, linewise) {
3089 // paste linewise before
3170 // paste linewise before
3090 (Paste::Before, true) => text.line_to_char(text.char_to_line(range.from())),
3171 (Paste::Before, true) => text.line_to_char(text.char_to_line(range.from())),
3091 // paste linewise after
3172 // paste linewise after
3092− (Paste::After, true) => text.line_to_char(text.char_to_line(range.to()) + 1),
3173+ (Paste::After, true) => {
3174+ let line = range.line_range(text.slice(..)).1;
3175+ text.line_to_char((line + 1).min(text.len_lines()))
3176+ }
3093 // paste insert
3177 // paste insert
3094 (Paste::Before, false) => range.from(),
3178 (Paste::Before, false) => range.from(),
3095 // paste append
3179 // paste append
3096− (Paste::After, false) => range.to() + 1,
3180+ (Paste::After, false) => range.to(),
3097 };
3181 };
3098 (pos, pos, Some(values.next().unwrap()))
3182 (pos, pos, Some(values.next().unwrap()))
3099 });
3183 });
@@ -3134,12 +3218,14 @@ fn replace_with_yanked(cx: &mut Context) {
3134
3218
3135 if let Some(values) = registers.read(reg_name) {
3219 if let Some(values) = registers.read(reg_name) {
3136 if let Some(yank) = values.first() {
3220 if let Some(yank) = values.first() {
3137− let transaction =
3221+ let selection = doc.selection(view.id);
3138− Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3222+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
3139− let max_to = rope_end_without_line_ending(&doc.text().slice(..));
3223+ if !range.is_empty() {
3140− let to = std::cmp::min(max_to, range.to() + 1);
3141− (range.from(), to, Some(yank.as_str().into()))
3224+ (range.from(), range.to(), Some(yank.as_str().into()))
3225+ } else {
3226+ (range.from(), range.to(), None)
3227+ }
3142− });
3228+ });
3143
3229
3144 doc.apply(&transaction, view.id);
3230 doc.apply(&transaction, view.id);
3145 doc.append_changes_to_history(view.id);
3231 doc.append_changes_to_history(view.id);
@@ -3152,12 +3238,10 @@ fn replace_selections_with_clipboard_impl(editor: &mut Editor) -> anyhow::Result
3152
3238
3153 match editor.clipboard_provider.get_contents() {
3239 match editor.clipboard_provider.get_contents() {
3154 Ok(contents) => {
3240 Ok(contents) => {
3155− let transaction =
3241+ let selection = doc.selection(view.id);
3156− Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3242+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
3157− let max_to = rope_end_without_line_ending(&doc.text().slice(..));
3158− let to = std::cmp::min(max_to, range.to() + 1);
3159− (range.from(), to, Some(contents.as_str().into()))
3243+ (range.from(), range.to(), Some(contents.as_str().into()))
3160− });
3244+ });
3161
3245
3162 doc.apply(&transaction, view.id);
3246 doc.apply(&transaction, view.id);
3163 doc.append_changes_to_history(view.id);
3247 doc.append_changes_to_history(view.id);
@@ -3204,8 +3288,7 @@ fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
3204
3288
3205 // Get all line numbers
3289 // Get all line numbers
3206 for range in doc.selection(view_id) {
3290 for range in doc.selection(view_id) {
3207− let start = doc.text().char_to_line(range.from());
3291+ let (start, end) = range.line_range(doc.text().slice(..));
3208− let end = doc.text().char_to_line(range.to());
3209
3292
3210 for line in start..=end {
3293 for line in start..=end {
3211 lines.push(line)
3294 lines.push(line)
@@ -3333,10 +3416,9 @@ fn join_selections(cx: &mut Context) {
3333 let fragment = Tendril::from(" ");
3416 let fragment = Tendril::from(" ");
3334
3417
3335 for selection in doc.selection(view.id) {
3418 for selection in doc.selection(view.id) {
3336− let start = text.char_to_line(selection.from());
3337− let mut end = text.char_to_line(selection.to());
3419+ let (start, mut end) = selection.line_range(slice);
3338 if start == end {
3420 if start == end {
3339− end += 1
3421+ end = (end + 1).min(text.len_lines() - 1);
3340 }
3422 }
3341 let lines = start..end;
3423 let lines = start..end;
3342
3424
@@ -3433,13 +3515,17 @@ fn completion(cx: &mut Context) {
3433 };
3515 };
3434
3516
3435 let offset_encoding = language_server.offset_encoding();
3517 let offset_encoding = language_server.offset_encoding();
3518+ let cursor = doc
3519+ .selection(view.id)
3520+ .primary()
3521+ .cursor(doc.text().slice(..));
3436
3522
3437− let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
3523+ let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding);
3438
3524
3439 // TODO: handle fails
3525 // TODO: handle fails
3440 let future = language_server.completion(doc.identifier(), pos, None);
3526 let future = language_server.completion(doc.identifier(), pos, None);
3441
3527
3442− let trigger_offset = doc.selection(view.id).cursor();
3528+ let trigger_offset = cursor;
3443
3529
3444 cx.callback(
3530 cx.callback(
3445 future,
3531 future,
@@ -3489,7 +3575,9 @@ fn hover(cx: &mut Context) {
3489
3575
3490 let pos = pos_to_lsp_pos(
3576 let pos = pos_to_lsp_pos(
3491 doc.text(),
3577 doc.text(),
3492− doc.selection(view.id).cursor(),
3578+ doc.selection(view.id)
3579+ .primary()
3580+ .cursor(doc.text().slice(..)),
3493 language_server.offset_encoding(),
3581 language_server.offset_encoding(),
3494 );
3582 );
3495
3583
@@ -3555,7 +3643,10 @@ fn match_brackets(cx: &mut Context) {
3555 let (view, doc) = current!(cx.editor);
3643 let (view, doc) = current!(cx.editor);
3556
3644
3557 if let Some(syntax) = doc.syntax() {
3645 if let Some(syntax) = doc.syntax() {
3646+ let pos = doc
3558− let pos = doc.selection(view.id).cursor();
3647+ .selection(view.id)
3648+ .primary()
3649+ .cursor(doc.text().slice(..));
3559 if let Some(pos) = match_brackets::find(syntax, doc.text(), pos) {
3650 if let Some(pos) = match_brackets::find(syntax, doc.text(), pos) {
3560 let selection = Selection::point(pos);
3651 let selection = Selection::point(pos);
3561 doc.set_selection(view.id, selection);
3652 doc.set_selection(view.id, selection);
@@ -3655,7 +3746,10 @@ fn align_view_bottom(cx: &mut Context) {
3655
3746
3656fn align_view_middle(cx: &mut Context) {
3747fn align_view_middle(cx: &mut Context) {
3657 let (view, doc) = current!(cx.editor);
3748 let (view, doc) = current!(cx.editor);
3749+ let pos = doc
3658− let pos = doc.selection(view.id).cursor();
3750+ .selection(view.id)
3751+ .primary()
3752+ .cursor(doc.text().slice(..));
3659 let pos = coords_at_pos(doc.text().slice(..), pos);
3753 let pos = coords_at_pos(doc.text().slice(..), pos);
3660
3754
3661 const OFFSET: usize = 7; // gutters
3755 const OFFSET: usize = 7; // gutters
@@ -3687,7 +3781,7 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
3687 let (view, doc) = current!(cx.editor);
3781 let (view, doc) = current!(cx.editor);
3688 let text = doc.text().slice(..);
3782 let text = doc.text().slice(..);
3689
3783
3690− let selection = doc.selection(view.id).transform(|range| {
3784+ let selection = doc.selection(view.id).clone().transform(|range| {
3691 match ch {
3785 match ch {
3692 'w' => textobject::textobject_word(text, range, objtype, count),
3786 'w' => textobject::textobject_word(text, range, objtype, count),
3693 // TODO: cancel new ranges if inconsistent surround matches across lines
3787 // TODO: cancel new ranges if inconsistent surround matches across lines
@@ -3697,7 +3791,6 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
3697 _ => range,
3791 _ => range,
3698 }
3792 }
3699 });
3793 });
3700−
3701 doc.set_selection(view.id, selection);
3794 doc.set_selection(view.id, selection);
3702 }
3795 }
3703 })
3796 })
@@ -3707,18 +3800,13 @@ fn surround_add(cx: &mut Context) {
3707 cx.on_next_key(move |cx, event| {
3800 cx.on_next_key(move |cx, event| {
3708 if let Some(ch) = event.char() {
3801 if let Some(ch) = event.char() {
3709 let (view, doc) = current!(cx.editor);
3802 let (view, doc) = current!(cx.editor);
3710− let text = doc.text().slice(..);
3711 let selection = doc.selection(view.id);
3803 let selection = doc.selection(view.id);
3712 let (open, close) = surround::get_pair(ch);
3804 let (open, close) = surround::get_pair(ch);
3713
3805
3714 let mut changes = Vec::new();
3806 let mut changes = Vec::new();
3715 for range in selection.iter() {
3807 for range in selection.iter() {
3716− let from = range.from();
3717− let max_to = rope_end_without_line_ending(&text);
3718− let to = std::cmp::min(range.to() + 1, max_to);
3719−
3720− changes.push((from, from, Some(Tendril::from_char(open))));
3808+ changes.push((range.from(), range.from(), Some(Tendril::from_char(open))));
3721− changes.push((to, to, Some(Tendril::from_char(close))));
3809+ changes.push((range.to(), range.to(), Some(Tendril::from_char(close))));
3722 }
3810 }
3723
3811
3724 let transaction = Transaction::change(doc.text(), changes.into_iter());
3812 let transaction = Transaction::change(doc.text(), changes.into_iter());
@@ -3748,8 +3836,11 @@ fn surround_replace(cx: &mut Context) {
3748 let transaction = Transaction::change(
3836 let transaction = Transaction::change(
3749 doc.text(),
3837 doc.text(),
3750 change_pos.iter().enumerate().map(|(i, &pos)| {
3838 change_pos.iter().enumerate().map(|(i, &pos)| {
3751− let ch = if i % 2 == 0 { open } else { close };
3839+ if i % 2 == 0 {
3752− (pos, pos + 1, Some(Tendril::from_char(ch)))
3840+ (pos, pos + 1, Some(Tendril::from_char(open)))
3841+ } else {
3842+ (pos.saturating_sub(1), pos, Some(Tendril::from_char(close)))
3843+ }
3753 }),
3844 }),
3754 );
3845 );
3755 doc.apply(&transaction, view.id);
3846 doc.apply(&transaction, view.id);
helix-term/src/ui/completion.rs+16−4
@@ -86,7 +86,10 @@ impl Completion {
86 let item = item.unwrap();
86 let item = item.unwrap();
87
87
88 // if more text was entered, remove it
88 // if more text was entered, remove it
89+ let cursor = doc
89− let cursor = doc.selection(view.id).cursor();
90+ .selection(view.id)
91+ .primary()
92+ .cursor(doc.text().slice(..));
90 if trigger_offset < cursor {
93 if trigger_offset < cursor {
91 let remove = Transaction::change(
94 let remove = Transaction::change(
92 doc.text(),
95 doc.text(),
@@ -109,7 +112,10 @@ impl Completion {
109 )
112 )
110 } else {
113 } else {
111 let text = item.insert_text.as_ref().unwrap_or(&item.label);
114 let text = item.insert_text.as_ref().unwrap_or(&item.label);
115+ let cursor = doc
112− let cursor = doc.selection(view.id).cursor();
116+ .selection(view.id)
117+ .primary()
118+ .cursor(doc.text().slice(..));
113 Transaction::change(
119 Transaction::change(
114 doc.text(),
120 doc.text(),
115 vec![(cursor, cursor, Some(text.as_str().into()))].into_iter(),
121 vec![(cursor, cursor, Some(text.as_str().into()))].into_iter(),
@@ -155,7 +161,10 @@ impl Completion {
155 // TODO: hooks should get processed immediately so maybe do it after select!(), before
161 // TODO: hooks should get processed immediately so maybe do it after select!(), before
156 // looping?
162 // looping?
157
163
164+ let cursor = doc
158− let cursor = doc.selection(view.id).cursor();
165+ .selection(view.id)
166+ .primary()
167+ .cursor(doc.text().slice(..));
159 if self.trigger_offset <= cursor {
168 if self.trigger_offset <= cursor {
160 let fragment = doc.text().slice(self.trigger_offset..cursor);
169 let fragment = doc.text().slice(self.trigger_offset..cursor);
161 let text = Cow::from(fragment);
170 let text = Cow::from(fragment);
@@ -212,7 +221,10 @@ impl Component for Completion {
212 .language()
221 .language()
213 .and_then(|scope| scope.strip_prefix("source."))
222 .and_then(|scope| scope.strip_prefix("source."))
214 .unwrap_or("");
223 .unwrap_or("");
215− let cursor_pos = doc.selection(view.id).cursor();
224+ let cursor_pos = doc
225+ .selection(view.id)
226+ .primary()
227+ .cursor(doc.text().slice(..));
216 let cursor_pos = (helix_core::coords_at_pos(doc.text().slice(..), cursor_pos).row
228 let cursor_pos = (helix_core::coords_at_pos(doc.text().slice(..), cursor_pos).row
217 - view.first_line) as u16;
229 - view.first_line) as u16;
218
230
helix-term/src/ui/editor.rs+69−34
@@ -8,7 +8,7 @@ use crate::{
8
8
9use helix_core::{
9use helix_core::{
10 coords_at_pos,
10 coords_at_pos,
11− graphemes::{ensure_grapheme_boundary, next_grapheme_boundary},
11+ graphemes::{ensure_grapheme_boundary_next, next_grapheme_boundary, prev_grapheme_boundary},
12 syntax::{self, HighlightEvent},
12 syntax::{self, HighlightEvent},
13 unicode::segmentation::UnicodeSegmentation,
13 unicode::segmentation::UnicodeSegmentation,
14 unicode::width::UnicodeWidthStr,
14 unicode::width::UnicodeWidthStr,
@@ -166,8 +166,8 @@ impl EditorView {
166 let highlights = highlights.into_iter().map(|event| match event.unwrap() {
166 let highlights = highlights.into_iter().map(|event| match event.unwrap() {
167 // convert byte offsets to char offset
167 // convert byte offsets to char offset
168 HighlightEvent::Source { start, end } => {
168 HighlightEvent::Source { start, end } => {
169− let start = ensure_grapheme_boundary(text, text.byte_to_char(start));
169+ let start = ensure_grapheme_boundary_next(text, text.byte_to_char(start));
170− let end = ensure_grapheme_boundary(text, text.byte_to_char(end));
170+ let end = ensure_grapheme_boundary_next(text, text.byte_to_char(end));
171 HighlightEvent::Source { start, end }
171 HighlightEvent::Source { start, end }
172 }
172 }
173 event => event,
173 event => event,
@@ -191,21 +191,18 @@ impl EditorView {
191 }
191 }
192 .unwrap_or(base_cursor_scope);
192 .unwrap_or(base_cursor_scope);
193
193
194− let primary_selection_scope = theme
195− .find_scope_index("ui.selection.primary")
196− .unwrap_or(selection_scope);
197−
198 let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
194 let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
199− // inject selections as highlight scopes
200− let mut spans: Vec<(usize, std::ops::Range<usize>)> = Vec::new();
201−
202 // TODO: primary + insert mode patching:
195 // TODO: primary + insert mode patching:
203 // (ui.cursor.primary).patch(mode).unwrap_or(cursor)
196 // (ui.cursor.primary).patch(mode).unwrap_or(cursor)
204−
205 let primary_cursor_scope = theme
197 let primary_cursor_scope = theme
206 .find_scope_index("ui.cursor.primary")
198 .find_scope_index("ui.cursor.primary")
207 .unwrap_or(cursor_scope);
199 .unwrap_or(cursor_scope);
200+ let primary_selection_scope = theme
201+ .find_scope_index("ui.selection.primary")
202+ .unwrap_or(selection_scope);
208
203
204+ // inject selections as highlight scopes
205+ let mut spans: Vec<(usize, std::ops::Range<usize>)> = Vec::new();
209 for (i, range) in selections.iter().enumerate() {
206 for (i, range) in selections.iter().enumerate() {
210 let (cursor_scope, selection_scope) = if i == primary_idx {
207 let (cursor_scope, selection_scope) = if i == primary_idx {
211 (primary_cursor_scope, primary_selection_scope)
208 (primary_cursor_scope, primary_selection_scope)
@@ -213,24 +210,23 @@ impl EditorView {
213 (cursor_scope, selection_scope)
210 (cursor_scope, selection_scope)
214 };
211 };
215
212
216− let cursor_end = next_grapheme_boundary(text, range.head); // Used in every case below.
213+ // Special-case: cursor at end of the rope.
217−
218− if range.head == range.anchor {
214+ if range.head == range.anchor && range.head == text.len_chars() {
219− spans.push((cursor_scope, range.head..cursor_end));
215+ spans.push((cursor_scope, range.head..range.head + 1));
220 continue;
216 continue;
221 }
217 }
222
218
223− let reverse = range.head < range.anchor;
219+ let range = range.min_width_1(text);
224−
220+ if range.head > range.anchor {
225− if reverse {
221+ // Standard case.
226− spans.push((cursor_scope, range.head..cursor_end));
222+ let cursor_start = prev_grapheme_boundary(text, range.head);
227− spans.push((
228− selection_scope,
223+ spans.push((selection_scope, range.anchor..cursor_start));
229− cursor_end..next_grapheme_boundary(text, range.anchor),
224+ spans.push((cursor_scope, cursor_start..range.head));
230− ));
231 } else {
225 } else {
232− spans.push((selection_scope, range.anchor..range.head));
226+ // Reverse case.
227+ let cursor_end = next_grapheme_boundary(text, range.head);
233 spans.push((cursor_scope, range.head..cursor_end));
228 spans.push((cursor_scope, range.head..cursor_end));
229+ spans.push((selection_scope, cursor_end..range.anchor));
234 }
230 }
235 }
231 }
236
232
@@ -263,7 +259,10 @@ impl EditorView {
263 spans.pop();
259 spans.pop();
264 }
260 }
265 HighlightEvent::Source { start, end } => {
261 HighlightEvent::Source { start, end } => {
262+ // `unwrap_or_else` part is for off-the-end indices of
263+ // the rope, to allow cursor highlighting at the end
264+ // of the rope.
266− let text = text.slice(start..end);
265+ let text = text.get_slice(start..end).unwrap_or_else(|| " ".into());
267
266
268 use helix_core::graphemes::{grapheme_width, RopeGraphemes};
267 use helix_core::graphemes::{grapheme_width, RopeGraphemes};
269
268
@@ -332,7 +331,11 @@ impl EditorView {
332 let info: Style = theme.get("info");
331 let info: Style = theme.get("info");
333 let hint: Style = theme.get("hint");
332 let hint: Style = theme.get("hint");
334
333
334+ // Whether to draw the line number for the last line of the
335+ // document or not. We only draw it if it's not an empty line.
336+ let draw_last = text.line_to_byte(last_line) < text.len_bytes();
337+
335− for (i, line) in (view.first_line..last_line).enumerate() {
338+ for (i, line) in (view.first_line..(last_line + 1)).enumerate() {
336 use helix_core::diagnostic::Severity;
339 use helix_core::diagnostic::Severity;
337 if let Some(diagnostic) = doc.diagnostics().iter().find(|d| d.line == line) {
340 if let Some(diagnostic) = doc.diagnostics().iter().find(|d| d.line == line) {
338 surface.set_stringn(
341 surface.set_stringn(
@@ -349,11 +352,17 @@ impl EditorView {
349 );
352 );
350 }
353 }
351
354
352− // line numbers having selections are rendered differently
355+ // Line numbers having selections are rendered
356+ // differently, further below.
357+ let line_number_text = if line == last_line && !draw_last {
358+ " ~".into()
359+ } else {
360+ format!("{:>5}", line + 1)
361+ };
353 surface.set_stringn(
362 surface.set_stringn(
354 viewport.x + 1 - OFFSET,
363 viewport.x + 1 - OFFSET,
355 viewport.y + i as u16,
364 viewport.y + i as u16,
356− format!("{:>5}", line + 1),
365+ line_number_text,
357 5,
366 5,
358 linenr,
367 linenr,
359 );
368 );
@@ -367,19 +376,34 @@ impl EditorView {
367 if is_focused {
376 if is_focused {
368 let screen = {
377 let screen = {
369 let start = text.line_to_char(view.first_line);
378 let start = text.line_to_char(view.first_line);
370− let end = text.line_to_char(last_line + 1);
379+ let end = text.line_to_char(last_line + 1) + 1; // +1 for cursor at end of text.
371 Range::new(start, end)
380 Range::new(start, end)
372 };
381 };
373
382
374 let selection = doc.selection(view.id);
383 let selection = doc.selection(view.id);
375
384
376 for selection in selection.iter().filter(|range| range.overlaps(&screen)) {
385 for selection in selection.iter().filter(|range| range.overlaps(&screen)) {
377− let head = view.screen_coords_at_pos(doc, text, selection.head);
386+ let head = view.screen_coords_at_pos(
387+ doc,
388+ text,
389+ if selection.head > selection.anchor {
390+ selection.head - 1
391+ } else {
392+ selection.head
393+ },
394+ );
378 if let Some(head) = head {
395 if let Some(head) = head {
396+ // Draw line number for selected lines.
397+ let line_number = view.first_line + head.row;
398+ let line_number_text = if line_number == last_line && !draw_last {
399+ " ~".into()
400+ } else {
401+ format!("{:>5}", line_number + 1)
402+ };
379 surface.set_stringn(
403 surface.set_stringn(
380 viewport.x + 1 - OFFSET,
404 viewport.x + 1 - OFFSET,
381 viewport.y + head.row as u16,
405 viewport.y + head.row as u16,
382− format!("{:>5}", view.first_line + head.row + 1),
406+ line_number_text,
383 5,
407 5,
384 linenr_select,
408 linenr_select,
385 );
409 );
@@ -387,7 +411,10 @@ impl EditorView {
387 // TODO: set cursor position for IME
411 // TODO: set cursor position for IME
388 if let Some(syntax) = doc.syntax() {
412 if let Some(syntax) = doc.syntax() {
389 use helix_core::match_brackets;
413 use helix_core::match_brackets;
414+ let pos = doc
390− let pos = doc.selection(view.id).cursor();
415+ .selection(view.id)
416+ .primary()
417+ .cursor(doc.text().slice(..));
391 let pos = match_brackets::find(syntax, doc.text(), pos)
418 let pos = match_brackets::find(syntax, doc.text(), pos)
392 .and_then(|pos| view.screen_coords_at_pos(doc, text, pos));
419 .and_then(|pos| view.screen_coords_at_pos(doc, text, pos));
393
420
@@ -432,7 +459,10 @@ impl EditorView {
432 widgets::{Paragraph, Widget},
459 widgets::{Paragraph, Widget},
433 };
460 };
434
461
462+ let cursor = doc
435− let cursor = doc.selection(view.id).cursor();
463+ .selection(view.id)
464+ .primary()
465+ .cursor(doc.text().slice(..));
436
466
437 let diagnostics = doc.diagnostics().iter().filter(|diagnostic| {
467 let diagnostics = doc.diagnostics().iter().filter(|diagnostic| {
438 diagnostic.range.start <= cursor && diagnostic.range.end >= cursor
468 diagnostic.range.start <= cursor && diagnostic.range.end >= cursor
@@ -544,7 +574,12 @@ impl EditorView {
544 // _ => "indent:ERROR",
574 // _ => "indent:ERROR",
545 // };
575 // };
546 let position_info = {
576 let position_info = {
547− let pos = coords_at_pos(doc.text().slice(..), doc.selection(view.id).cursor());
577+ let pos = coords_at_pos(
578+ doc.text().slice(..),
579+ doc.selection(view.id)
580+ .primary()
581+ .cursor(doc.text().slice(..)),
582+ );
548 format!("{}:{}", pos.row + 1, pos.col + 1) // convert to 1-indexing
583 format!("{}:{}", pos.row + 1, pos.col + 1) // convert to 1-indexing
549 };
584 };
550
585
helix-view/src/document.rs+16−29
@@ -306,19 +306,6 @@ pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>(
306 Ok(())
306 Ok(())
307}
307}
308
308
309−/// Inserts the final line ending into `rope` if it's missing. [Why?](https://stackoverflow.com/questions/729692/why-should-text-files-end-with-a-newline)
310−pub fn with_line_ending(rope: &mut Rope) -> LineEnding {
311− // search for line endings
312− let line_ending = auto_detect_line_ending(rope).unwrap_or(DEFAULT_LINE_ENDING);
313−
314− // add missing newline at the end of file
315− if rope.len_bytes() == 0 || !char_is_line_ending(rope.char(rope.len_chars() - 1)) {
316− rope.insert(rope.len_chars(), line_ending.as_str());
317− }
318−
319− line_ending
320−}
321−
322/// Like std::mem::replace() except it allows the replacement value to be mapped from the
309/// Like std::mem::replace() except it allows the replacement value to be mapped from the
323/// original value.
310/// original value.
324fn take_with<T, F>(mut_ref: &mut T, closure: F)
311fn take_with<T, F>(mut_ref: &mut T, closure: F)
@@ -456,7 +443,7 @@ impl Document {
456 theme: Option<&Theme>,
443 theme: Option<&Theme>,
457 config_loader: Option<&syntax::Loader>,
444 config_loader: Option<&syntax::Loader>,
458 ) -> Result<Self, Error> {
445 ) -> Result<Self, Error> {
459− let (mut rope, encoding) = if path.exists() {
446+ let (rope, encoding) = if path.exists() {
460 let mut file =
447 let mut file =
461 std::fs::File::open(&path).context(format!("unable to open {:?}", path))?;
448 std::fs::File::open(&path).context(format!("unable to open {:?}", path))?;
462 from_reader(&mut file, encoding)?
449 from_reader(&mut file, encoding)?
@@ -465,7 +452,6 @@ impl Document {
465 (Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding)
452 (Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding)
466 };
453 };
467
454
468− let line_ending = with_line_ending(&mut rope);
469 let mut doc = Self::from(rope, Some(encoding));
455 let mut doc = Self::from(rope, Some(encoding));
470
456
471 // set the path and try detecting the language
457 // set the path and try detecting the language
@@ -474,9 +460,9 @@ impl Document {
474 doc.detect_language(theme, loader);
460 doc.detect_language(theme, loader);
475 }
461 }
476
462
477− // Detect indentation style and set line ending.
463+ // Detect indentation style and line ending.
478 doc.detect_indent_style();
464 doc.detect_indent_style();
479− doc.line_ending = line_ending;
465+ doc.line_ending = auto_detect_line_ending(&doc.text).unwrap_or(DEFAULT_LINE_ENDING);
480
466
481 Ok(doc)
467 Ok(doc)
482 }
468 }
@@ -605,17 +591,16 @@ impl Document {
605 }
591 }
606
592
607 let mut file = std::fs::File::open(path.unwrap())?;
593 let mut file = std::fs::File::open(path.unwrap())?;
608− let (mut rope, ..) = from_reader(&mut file, Some(encoding))?;
594+ let (rope, ..) = from_reader(&mut file, Some(encoding))?;
609− let line_ending = with_line_ending(&mut rope);
610
595
611 let transaction = helix_core::diff::compare_ropes(self.text(), &rope);
596 let transaction = helix_core::diff::compare_ropes(self.text(), &rope);
612 self.apply(&transaction, view_id);
597 self.apply(&transaction, view_id);
613 self.append_changes_to_history(view_id);
598 self.append_changes_to_history(view_id);
614 self.reset_modified();
599 self.reset_modified();
615
600
616− // Detect indentation style and set line ending.
601+ // Detect indentation style and line ending.
617 self.detect_indent_style();
602 self.detect_indent_style();
618− self.line_ending = line_ending;
603+ self.line_ending = auto_detect_line_ending(&self.text).unwrap_or(DEFAULT_LINE_ENDING);
619
604
620 Ok(())
605 Ok(())
621 }
606 }
@@ -807,7 +792,8 @@ impl Document {
807
792
808 pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
793 pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
809 // TODO: use a transaction?
794 // TODO: use a transaction?
810− self.selections.insert(view_id, selection);
795+ self.selections
796+ .insert(view_id, selection.ensure_invariants(self.text().slice(..)));
811 }
797 }
812
798
813 fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
799 fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
@@ -822,7 +808,12 @@ impl Document {
822 .selection()
808 .selection()
823 .cloned()
809 .cloned()
824 .unwrap_or_else(|| self.selection(view_id).clone().map(transaction.changes()));
810 .unwrap_or_else(|| self.selection(view_id).clone().map(transaction.changes()));
825− self.set_selection(view_id, selection);
811+ self.selections.insert(view_id, selection);
812+
813+ // Ensure all selections accross all views still adhere to invariants.
814+ for selection in self.selections.values_mut() {
815+ *selection = selection.clone().ensure_invariants(self.text.slice(..));
816+ }
826 }
817 }
827
818
828 if !transaction.changes().is_empty() {
819 if !transaction.changes().is_empty() {
@@ -1089,7 +1080,7 @@ impl Document {
1089
1080
1090impl Default for Document {
1081impl Default for Document {
1091 fn default() -> Self {
1082 fn default() -> Self {
1092− let text = Rope::from(DEFAULT_LINE_ENDING.as_str());
1083+ let text = Rope::from("");
1093 Self::from(text, None)
1084 Self::from(text, None)
1094 }
1085 }
1095}
1086}
@@ -1214,11 +1205,7 @@ mod test {
1214
1205
1215 #[test]
1206 #[test]
1216 fn test_line_ending() {
1207 fn test_line_ending() {
1217− if cfg!(windows) {
1218− assert_eq!(Document::default().text().to_string(), "\r\n");
1208+ assert_eq!(Document::default().text().to_string(), "");
1219− } else {
1220− assert_eq!(Document::default().text().to_string(), "\n");
1221− }
1222 }
1209 }
1223
1210
1224 macro_rules! test_decode {
1211 macro_rules! test_decode {
helix-view/src/editor.rs+9−4
@@ -138,12 +138,14 @@ impl Editor {
138 let (view, doc) = current!(self);
138 let (view, doc) = current!(self);
139
139
140 // initialize selection for view
140 // initialize selection for view
141− let selection = doc
142− .selections
141+ doc.selections
143 .entry(view.id)
142 .entry(view.id)
144 .or_insert_with(|| Selection::point(0));
143 .or_insert_with(|| Selection::point(0));
145 // TODO: reuse align_view
144 // TODO: reuse align_view
145+ let pos = doc
146− let pos = selection.cursor();
146+ .selection(view.id)
147+ .primary()
148+ .cursor(doc.text().slice(..));
147 let line = doc.text().char_to_line(pos);
149 let line = doc.text().char_to_line(pos);
148 view.first_line = line.saturating_sub(view.area.height as usize / 2);
150 view.first_line = line.saturating_sub(view.area.height as usize / 2);
149
151
@@ -293,7 +295,10 @@ impl Editor {
293 const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
295 const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
294 let view = view!(self);
296 let view = view!(self);
295 let doc = &self.documents[view.doc];
297 let doc = &self.documents[view.doc];
298+ let cursor = doc
296− let cursor = doc.selection(view.id).cursor();
299+ .selection(view.id)
300+ .primary()
301+ .cursor(doc.text().slice(..));
297 if let Some(mut pos) = view.screen_coords_at_pos(doc, doc.text().slice(..), cursor) {
302 if let Some(mut pos) = view.screen_coords_at_pos(doc, doc.text().slice(..), cursor) {
298 pos.col += view.area.x as usize + OFFSET as usize;
303 pos.col += view.area.x as usize + OFFSET as usize;
299 pos.row += view.area.y as usize;
304 pos.row += view.area.y as usize;
helix-view/src/view.rs+9−5
@@ -84,18 +84,21 @@ impl View {
84 }
84 }
85
85
86 pub fn ensure_cursor_in_view(&mut self, doc: &Document) {
86 pub fn ensure_cursor_in_view(&mut self, doc: &Document) {
87+ let cursor = doc
87− let cursor = doc.selection(self.id).cursor();
88+ .selection(self.id)
89+ .primary()
90+ .cursor(doc.text().slice(..));
88 let pos = coords_at_pos(doc.text().slice(..), cursor);
91 let pos = coords_at_pos(doc.text().slice(..), cursor);
89 let line = pos.row;
92 let line = pos.row;
90 let col = pos.col;
93 let col = pos.col;
91 let height = self.area.height.saturating_sub(1); // - 1 for statusline
94 let height = self.area.height.saturating_sub(1); // - 1 for statusline
92− let last_line = self.first_line + height as usize;
95+ let last_line = (self.first_line + height as usize).saturating_sub(1);
93
96
94 let scrolloff = PADDING.min(self.area.height as usize / 2); // TODO: user pref
97 let scrolloff = PADDING.min(self.area.height as usize / 2); // TODO: user pref
95
98
96 // TODO: not ideal
99 // TODO: not ideal
97 const OFFSET: usize = 7; // 1 diagnostic + 5 linenr + 1 gutter
100 const OFFSET: usize = 7; // 1 diagnostic + 5 linenr + 1 gutter
98− let last_col = self.first_col + (self.area.width as usize - OFFSET);
101+ let last_col = (self.first_col + self.area.width as usize).saturating_sub(OFFSET + 1);
99
102
100 if line > last_line.saturating_sub(scrolloff) {
103 if line > last_line.saturating_sub(scrolloff) {
101 // scroll down
104 // scroll down
@@ -119,8 +122,9 @@ impl View {
119 pub fn last_line(&self, doc: &Document) -> usize {
122 pub fn last_line(&self, doc: &Document) -> usize {
120 let height = self.area.height.saturating_sub(1); // - 1 for statusline
123 let height = self.area.height.saturating_sub(1); // - 1 for statusline
121 std::cmp::min(
124 std::cmp::min(
125+ // Saturating subs to make it inclusive zero indexing.
122− self.first_line + height as usize,
126+ (self.first_line + height as usize).saturating_sub(1),
123− doc.text().len_lines() - 1,
127+ doc.text().len_lines().saturating_sub(1),
124 )
128 )
125 }
129 }
126
130