Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-core/src/movement.rs')
-rw-r--r--helix-core/src/movement.rs1117
1 files changed, 51 insertions, 1066 deletions
diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs
index 09a99db2..e559f1ea 100644
--- a/helix-core/src/movement.rs
+++ b/helix-core/src/movement.rs
@@ -1,22 +1,19 @@
-use std::{borrow::Cow, cmp::Reverse, iter};
+use std::iter;
use ropey::iter::Chars;
+use tree_sitter::{Node, QueryCursor};
use crate::{
- char_idx_at_visual_offset,
chars::{categorize_char, char_is_line_ending, CharCategory},
- doc_formatter::TextFormat,
+ coords_at_pos,
graphemes::{
next_grapheme_boundary, nth_next_grapheme_boundary, nth_prev_grapheme_boundary,
prev_grapheme_boundary,
},
- line_ending::rope_is_line_ending,
- position::char_idx_at_visual_block_offset,
- syntax,
- text_annotations::TextAnnotations,
+ pos_at_coords,
+ syntax::LanguageConfiguration,
textobject::TextObject,
- tree_sitter::Node,
- visual_offset_from_block, Range, RopeSlice, Selection, Syntax,
+ Position, Range, RopeSlice,
};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -37,8 +34,6 @@ pub fn move_horizontally(
dir: Direction,
count: usize,
behaviour: Movement,
- _: &TextFormat,
- _: &mut TextAnnotations,
) -> Range {
let pos = range.cursor(slice);
@@ -52,116 +47,38 @@ pub fn move_horizontally(
range.put_cursor(slice, new_pos, behaviour == Movement::Extend)
}
-pub fn move_vertically_visual(
- slice: RopeSlice,
- range: Range,
- dir: Direction,
- count: usize,
- behaviour: Movement,
- text_fmt: &TextFormat,
- annotations: &mut TextAnnotations,
-) -> Range {
- if !text_fmt.soft_wrap {
- return move_vertically(slice, range, dir, count, behaviour, text_fmt, annotations);
- }
- annotations.clear_line_annotations();
- let pos = range.cursor(slice);
-
- // Compute the current position's 2d coordinates.
- let (visual_pos, block_off) = visual_offset_from_block(slice, pos, pos, text_fmt, annotations);
- let new_col = range
- .old_visual_position
- .map_or(visual_pos.col as u32, |(_, col)| col);
-
- // Compute the new position.
- let mut row_off = match dir {
- Direction::Forward => count as isize,
- Direction::Backward => -(count as isize),
- };
-
- // Compute visual offset relative to block start to avoid trasversing the block twice
- row_off += visual_pos.row as isize;
- let (mut new_pos, virtual_rows) = char_idx_at_visual_offset(
- slice,
- block_off,
- row_off,
- new_col as usize,
- text_fmt,
- annotations,
- );
- if dir == Direction::Forward {
- new_pos += (virtual_rows != 0) as usize;
- }
-
- // Special-case to avoid moving to the end of the last non-empty line.
- if behaviour == Movement::Extend && slice.line(slice.char_to_line(new_pos)).len_chars() == 0 {
- return range;
- }
-
- let mut new_range = range.put_cursor(slice, new_pos, behaviour == Movement::Extend);
- new_range.old_visual_position = Some((0, new_col));
- new_range
-}
-
pub fn move_vertically(
slice: RopeSlice,
range: Range,
dir: Direction,
count: usize,
behaviour: Movement,
- text_fmt: &TextFormat,
- annotations: &mut TextAnnotations,
) -> Range {
- annotations.clear_line_annotations();
let pos = range.cursor(slice);
- let line_idx = slice.char_to_line(pos);
- let line_start = slice.line_to_char(line_idx);
// Compute the current position's 2d coordinates.
- let visual_pos = visual_offset_from_block(slice, line_start, pos, text_fmt, annotations).0;
- let (mut new_row, new_col) = range
- .old_visual_position
- .map_or((visual_pos.row as u32, visual_pos.col as u32), |pos| pos);
- new_row = new_row.max(visual_pos.row as u32);
- let line_idx = slice.char_to_line(pos);
+ // TODO: switch this to use `visual_coords_at_pos` rather than
+ // `coords_at_pos` as this will cause a jerky movement when the visual
+ // position does not match, like moving from a line with tabs/CJK to
+ // a line without
+ let Position { row, col } = coords_at_pos(slice, pos);
+ let horiz = range.horiz.unwrap_or(col as u32);
// Compute the new position.
- let mut new_line_idx = match dir {
- Direction::Forward => line_idx.saturating_add(count),
- Direction::Backward => line_idx.saturating_sub(count),
- };
-
- let line = if new_line_idx >= slice.len_lines() - 1 {
- // there is no line terminator for the last line
- // so the logic below is not necessary here
- new_line_idx = slice.len_lines() - 1;
- slice
- } else {
- // char_idx_at_visual_block_offset returns a one-past-the-end index
- // in case it reaches the end of the slice
- // to avoid moving to the nextline in that case the line terminator is removed from the line
- let new_line_end = prev_grapheme_boundary(slice, slice.line_to_char(new_line_idx + 1));
- slice.slice(..new_line_end)
+ let new_row = match dir {
+ Direction::Forward => (row + count).min(slice.len_lines().saturating_sub(1)),
+ Direction::Backward => row.saturating_sub(count),
};
-
- let new_line_start = line.line_to_char(new_line_idx);
-
- let (new_pos, _) = char_idx_at_visual_block_offset(
- line,
- new_line_start,
- new_row as usize,
- new_col as usize,
- text_fmt,
- annotations,
- );
+ let new_col = col.max(horiz as usize);
+ let new_pos = pos_at_coords(slice, Position::new(new_row, new_col), true);
// Special-case to avoid moving to the end of the last non-empty line.
- if behaviour == Movement::Extend && slice.line(new_line_idx).len_chars() == 0 {
+ if behaviour == Movement::Extend && slice.line(new_row).len_chars() == 0 {
return range;
}
let mut new_range = range.put_cursor(slice, new_pos, behaviour == Movement::Extend);
- new_range.old_visual_position = Some((new_row, new_col));
+ new_range.horiz = Some(horiz);
new_range
}
@@ -177,10 +94,6 @@ pub fn move_prev_word_start(slice: RopeSlice, range: Range, count: usize) -> Ran
word_move(slice, range, count, WordMotionTarget::PrevWordStart)
}
-pub fn move_prev_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
- word_move(slice, range, count, WordMotionTarget::PrevWordEnd)
-}
-
pub fn move_next_long_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
word_move(slice, range, count, WordMotionTarget::NextLongWordStart)
}
@@ -193,24 +106,8 @@ pub fn move_prev_long_word_start(slice: RopeSlice, range: Range, count: usize) -
word_move(slice, range, count, WordMotionTarget::PrevLongWordStart)
}
-pub fn move_prev_long_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
- word_move(slice, range, count, WordMotionTarget::PrevLongWordEnd)
-}
-
-pub fn move_next_sub_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
- word_move(slice, range, count, WordMotionTarget::NextSubWordStart)
-}
-
-pub fn move_next_sub_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
- word_move(slice, range, count, WordMotionTarget::NextSubWordEnd)
-}
-
-pub fn move_prev_sub_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
- word_move(slice, range, count, WordMotionTarget::PrevSubWordStart)
-}
-
-pub fn move_prev_sub_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
- word_move(slice, range, count, WordMotionTarget::PrevSubWordEnd)
+pub fn move_prev_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
+ word_move(slice, range, count, WordMotionTarget::PrevWordEnd)
}
fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTarget) -> Range {
@@ -218,10 +115,7 @@ fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTar
target,
WordMotionTarget::PrevWordStart
| WordMotionTarget::PrevLongWordStart
- | WordMotionTarget::PrevSubWordStart
| WordMotionTarget::PrevWordEnd
- | WordMotionTarget::PrevLongWordEnd
- | WordMotionTarget::PrevSubWordEnd
);
// Special-case early-out.
@@ -250,107 +144,9 @@ fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTar
};
// Do the main work.
- let mut range = start_range;
- for _ in 0..count {
- let next_range = slice.chars_at(range.head).range_to_target(target, range);
- if range == next_range {
- break;
- }
- range = next_range;
- }
- range
-}
-
-pub fn move_prev_paragraph(
- slice: RopeSlice,
- range: Range,
- count: usize,
- behavior: Movement,
-) -> Range {
- let mut line = range.cursor_line(slice);
- let first_char = slice.line_to_char(line) == range.cursor(slice);
- let prev_line_empty = rope_is_line_ending(slice.line(line.saturating_sub(1)));
- let curr_line_empty = rope_is_line_ending(slice.line(line));
- let prev_empty_to_line = prev_line_empty && !curr_line_empty;
-
- // skip character before paragraph boundary
- if prev_empty_to_line && !first_char {
- line += 1;
- }
- let mut lines = slice.lines_at(line);
- lines.reverse();
- let mut lines = lines.map(rope_is_line_ending).peekable();
- let mut last_line = line;
- for _ in 0..count {
- while lines.next_if(|&e| e).is_some() {
- line -= 1;
- }
- while lines.next_if(|&e| !e).is_some() {
- line -= 1;
- }
- if line == last_line {
- break;
- }
- last_line = line;
- }
-
- let head = slice.line_to_char(line);
- let anchor = if behavior == Movement::Move {
- // exclude first character after paragraph boundary
- if prev_empty_to_line && first_char {
- range.cursor(slice)
- } else {
- range.head
- }
- } else {
- range.put_cursor(slice, head, true).anchor
- };
- Range::new(anchor, head)
-}
-
-pub fn move_next_paragraph(
- slice: RopeSlice,
- range: Range,
- count: usize,
- behavior: Movement,
-) -> Range {
- let mut line = range.cursor_line(slice);
- let last_char =
- prev_grapheme_boundary(slice, slice.line_to_char(line + 1)) == range.cursor(slice);
- let curr_line_empty = rope_is_line_ending(slice.line(line));
- let next_line_empty =
- rope_is_line_ending(slice.line(slice.len_lines().saturating_sub(1).min(line + 1)));
- let curr_empty_to_line = curr_line_empty && !next_line_empty;
-
- // skip character after paragraph boundary
- if curr_empty_to_line && last_char {
- line += 1;
- }
- let mut lines = slice.lines_at(line).map(rope_is_line_ending).peekable();
- let mut last_line = line;
- for _ in 0..count {
- while lines.next_if(|&e| !e).is_some() {
- line += 1;
- }
- while lines.next_if(|&e| e).is_some() {
- line += 1;
- }
- if line == last_line {
- break;
- }
- last_line = line;
- }
- let head = slice.line_to_char(line);
- let anchor = if behavior == Movement::Move {
- if curr_empty_to_line && last_char {
- range.head
- } else {
- range.cursor(slice)
- }
- } else {
- range.put_cursor(slice, head, true).anchor
- };
- Range::new(anchor, head)
+ (0..count).fold(start_range, |r, _| {
+ slice.chars_at(r.head).range_to_target(target, r)
+ })
}
// ---- util ------------
@@ -394,19 +190,12 @@ pub enum WordMotionTarget {
NextWordEnd,
PrevWordStart,
PrevWordEnd,
- // A "Long word" (also known as a WORD in Vim/Kakoune) is strictly
+ // A "Long word" (also known as a WORD in vim/kakoune) is strictly
// delimited by whitespace, and can consist of punctuation as well
// as alphanumerics.
NextLongWordStart,
NextLongWordEnd,
PrevLongWordStart,
- PrevLongWordEnd,
- // A sub word is similar to a regular word, except it is also delimited by
- // underscores and transitions from lowercase to uppercase.
- NextSubWordStart,
- NextSubWordEnd,
- PrevSubWordStart,
- PrevSubWordEnd,
}
pub trait CharHelpers {
@@ -422,10 +211,7 @@ impl CharHelpers for Chars<'_> {
target,
WordMotionTarget::PrevWordStart
| WordMotionTarget::PrevLongWordStart
- | WordMotionTarget::PrevSubWordStart
| WordMotionTarget::PrevWordEnd
- | WordMotionTarget::PrevLongWordEnd
- | WordMotionTarget::PrevSubWordEnd
);
// Reverse the iterator if needed for the motion direction.
@@ -502,25 +288,6 @@ fn is_long_word_boundary(a: char, b: char) -> bool {
}
}
-fn is_sub_word_boundary(a: char, b: char, dir: Direction) -> bool {
- match (categorize_char(a), categorize_char(b)) {
- (CharCategory::Word, CharCategory::Word) => {
- if (a == '_') != (b == '_') {
- return true;
- }
-
- // Subword boundaries are directional: in 'fooBar', there is a
- // boundary between 'o' and 'B', but not between 'B' and 'a'.
- match dir {
- Direction::Forward => a.is_lowercase() && b.is_uppercase(),
- Direction::Backward => a.is_uppercase() && b.is_lowercase(),
- }
- }
- (a, b) if a != b => true,
- _ => false,
- }
-}
-
fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> bool {
match target {
WordMotionTarget::NextWordStart | WordMotionTarget::PrevWordEnd => {
@@ -531,7 +298,7 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo
is_word_boundary(prev_ch, next_ch)
&& (!prev_ch.is_whitespace() || char_is_line_ending(next_ch))
}
- WordMotionTarget::NextLongWordStart | WordMotionTarget::PrevLongWordEnd => {
+ WordMotionTarget::NextLongWordStart => {
is_long_word_boundary(prev_ch, next_ch)
&& (char_is_line_ending(next_ch) || !next_ch.is_whitespace())
}
@@ -539,44 +306,24 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo
is_long_word_boundary(prev_ch, next_ch)
&& (!prev_ch.is_whitespace() || char_is_line_ending(next_ch))
}
- WordMotionTarget::NextSubWordStart => {
- is_sub_word_boundary(prev_ch, next_ch, Direction::Forward)
- && (char_is_line_ending(next_ch) || !(next_ch.is_whitespace() || next_ch == '_'))
- }
- WordMotionTarget::PrevSubWordEnd => {
- is_sub_word_boundary(prev_ch, next_ch, Direction::Backward)
- && (char_is_line_ending(next_ch) || !(next_ch.is_whitespace() || next_ch == '_'))
- }
- WordMotionTarget::NextSubWordEnd => {
- is_sub_word_boundary(prev_ch, next_ch, Direction::Forward)
- && (!(prev_ch.is_whitespace() || prev_ch == '_') || char_is_line_ending(next_ch))
- }
- WordMotionTarget::PrevSubWordStart => {
- is_sub_word_boundary(prev_ch, next_ch, Direction::Backward)
- && (!(prev_ch.is_whitespace() || prev_ch == '_') || char_is_line_ending(next_ch))
- }
}
}
-/// Finds the range of the next or previous textobject in the syntax sub-tree of `node`.
-/// Returns the range in the forwards direction.
-#[allow(clippy::too_many_arguments)]
pub fn goto_treesitter_object(
slice: RopeSlice,
range: Range,
object_name: &str,
dir: Direction,
- slice_tree: &Node,
- syntax: &Syntax,
- loader: &syntax::Loader,
- count: usize,
+ slice_tree: Node,
+ lang_config: &LanguageConfiguration,
+ _count: usize,
) -> Range {
- let textobject_query = loader.textobject_query(syntax.root_language());
- let get_range = move |range: Range| -> Option<Range> {
+ let get_range = move || -> Option<Range> {
let byte_pos = slice.char_to_byte(range.cursor(slice));
let cap_name = |t: TextObject| format!("{}.{}", object_name, t);
- let nodes = textobject_query?.capture_nodes_any(
+ let mut cursor = QueryCursor::new();
+ let nodes = lang_config.textobject_query()?.capture_nodes_any(
&[
&cap_name(TextObject::Movement),
&cap_name(TextObject::Around),
@@ -584,15 +331,16 @@ pub fn goto_treesitter_object(
],
slice_tree,
slice,
+ &mut cursor,
)?;
let node = match dir {
Direction::Forward => nodes
.filter(|n| n.start_byte() > byte_pos)
- .min_by_key(|n| (n.start_byte(), Reverse(n.end_byte())))?,
+ .min_by_key(|n| n.start_byte())?,
Direction::Backward => nodes
- .filter(|n| n.end_byte() < byte_pos)
- .max_by_key(|n| (n.end_byte(), Reverse(n.start_byte())))?,
+ .filter(|n| n.start_byte() < byte_pos)
+ .max_by_key(|n| n.start_byte())?,
};
let len = slice.len_bytes();
@@ -606,99 +354,15 @@ pub fn goto_treesitter_object(
let end_char = slice.byte_to_char(end_byte);
// head of range should be at beginning
- Some(Range::new(start_char, end_char))
+ Some(Range::new(end_char, start_char))
};
- let mut last_range = range;
- for _ in 0..count {
- match get_range(last_range) {
- Some(r) if r != last_range => last_range = r,
- _ => break,
- }
- }
- last_range
-}
-
-fn find_parent_start<'tree>(node: &Node<'tree>) -> Option<Node<'tree>> {
- let start = node.start_byte();
- let mut node = Cow::Borrowed(node);
-
- while node.start_byte() >= start || !node.is_named() {
- node = Cow::Owned(node.parent()?);
- }
-
- Some(node.into_owned())
-}
-
-pub fn move_parent_node_end(
- syntax: &Syntax,
- text: RopeSlice,
- selection: Selection,
- dir: Direction,
- movement: Movement,
-) -> Selection {
- selection.transform(|range| {
- let start_from = text.char_to_byte(range.from()) as u32;
- let start_to = text.char_to_byte(range.to()) as u32;
-
- let mut node = match syntax.named_descendant_for_byte_range(start_from, start_to) {
- Some(node) => node,
- None => {
- log::debug!(
- "no descendant found for byte range: {} - {}",
- start_from,
- start_to
- );
- return range;
- }
- };
-
- let mut end_head = match dir {
- // moving forward, we always want to move one past the end of the
- // current node, so use the end byte of the current node, which is an exclusive
- // end of the range
- Direction::Forward => text.byte_to_char(node.end_byte() as usize),
-
- // moving backward, we want the cursor to land on the start char of
- // the current node, or if it is already at the start of a node, to traverse up to
- // the parent
- Direction::Backward => {
- let end_head = text.byte_to_char(node.start_byte() as usize);
-
- // if we're already on the beginning, look up to the parent
- if end_head == range.cursor(text) {
- node = find_parent_start(&node).unwrap_or(node);
- text.byte_to_char(node.start_byte() as usize)
- } else {
- end_head
- }
- }
- };
-
- if movement == Movement::Move {
- // preserve direction of original range
- if range.direction() == Direction::Forward {
- Range::new(end_head, end_head + 1)
- } else {
- Range::new(end_head + 1, end_head)
- }
- } else {
- // if we end up with a forward range, then adjust it to be one past
- // where we want
- if end_head >= range.anchor {
- end_head += 1;
- }
-
- Range::new(range.anchor, end_head)
- }
- })
+ get_range().unwrap_or(range)
}
#[cfg(test)]
mod test {
use ropey::Rope;
- use crate::{coords_at_pos, pos_at_coords};
-
use super::*;
const SINGLE_LINE_SAMPLE: &str = "This is a simple alphabetic line";
@@ -725,16 +389,7 @@ mod test {
assert_eq!(
coords_at_pos(
slice,
- move_vertically_visual(
- slice,
- range,
- Direction::Forward,
- 1,
- Movement::Move,
- &TextFormat::default(),
- &mut TextAnnotations::default(),
- )
- .head
+ move_vertically(slice, range, Direction::Forward, 1, Movement::Move).head
),
(1, 3).into()
);
@@ -758,15 +413,7 @@ mod test {
];
for ((direction, amount), coordinates) in moves_and_expected_coordinates {
- range = move_horizontally(
- slice,
- range,
- direction,
- amount,
- Movement::Move,
- &TextFormat::default(),
- &mut TextAnnotations::default(),
- );
+ range = move_horizontally(slice, range, direction, amount, Movement::Move);
assert_eq!(coords_at_pos(slice, range.head), coordinates.into())
}
}
@@ -792,15 +439,7 @@ mod test {
];
for ((direction, amount), coordinates) in moves_and_expected_coordinates {
- range = move_horizontally(
- slice,
- range,
- direction,
- amount,
- Movement::Move,
- &TextFormat::default(),
- &mut TextAnnotations::default(),
- );
+ range = move_horizontally(slice, range, direction, amount, Movement::Move);
assert_eq!(coords_at_pos(slice, range.head), coordinates.into());
assert_eq!(range.head, range.anchor);
}
@@ -822,15 +461,7 @@ mod test {
];
for (direction, amount) in moves {
- range = move_horizontally(
- slice,
- range,
- direction,
- amount,
- Movement::Extend,
- &TextFormat::default(),
- &mut TextAnnotations::default(),
- );
+ range = move_horizontally(slice, range, direction, amount, Movement::Extend);
assert_eq!(range.anchor, original_anchor);
}
}
@@ -854,15 +485,7 @@ mod test {
];
for ((direction, amount), coordinates) in moves_and_expected_coordinates {
- range = move_vertically_visual(
- slice,
- range,
- direction,
- amount,
- Movement::Move,
- &TextFormat::default(),
- &mut TextAnnotations::default(),
- );
+ range = move_vertically(slice, range, direction, amount, Movement::Move);
assert_eq!(coords_at_pos(slice, range.head), coordinates.into());
assert_eq!(range.head, range.anchor);
}
@@ -896,24 +519,8 @@ mod test {
for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates {
range = match axis {
- Axis::H => move_horizontally(
- slice,
- range,
- direction,
- amount,
- Movement::Move,
- &TextFormat::default(),
- &mut TextAnnotations::default(),
- ),
- Axis::V => move_vertically_visual(
- slice,
- range,
- direction,
- amount,
- Movement::Move,
- &TextFormat::default(),
- &mut TextAnnotations::default(),
- ),
+ Axis::H => move_horizontally(slice, range, direction, amount, Movement::Move),
+ Axis::V => move_vertically(slice, range, direction, amount, Movement::Move),
};
assert_eq!(coords_at_pos(slice, range.head), coordinates.into());
assert_eq!(range.head, range.anchor);
@@ -937,34 +544,18 @@ mod test {
let moves_and_expected_coordinates = [
// Places cursor at the fourth kana.
((Axis::H, Direction::Forward, 4), (0, 4)),
- // Descent places cursor at the 8th character.
- ((Axis::V, Direction::Forward, 1usize), (1, 8)),
- // Moving back 2 characters.
- ((Axis::H, Direction::Backward, 2usize), (1, 6)),
+ // Descent places cursor at the 4th character.
+ ((Axis::V, Direction::Forward, 1usize), (1, 4)),
+ // Moving back 1 character.
+ ((Axis::H, Direction::Backward, 1usize), (1, 3)),
// Jumping back up 1 line.
((Axis::V, Direction::Backward, 1usize), (0, 3)),
];
for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates {
range = match axis {
- Axis::H => move_horizontally(
- slice,
- range,
- direction,
- amount,
- Movement::Move,
- &TextFormat::default(),
- &mut TextAnnotations::default(),
- ),
- Axis::V => move_vertically_visual(
- slice,
- range,
- direction,
- amount,
- Movement::Move,
- &TextFormat::default(),
- &mut TextAnnotations::default(),
- ),
+ Axis::H => move_horizontally(slice, range, direction, amount, Movement::Move),
+ Axis::V => move_vertically(slice, range, direction, amount, Movement::Move),
};
assert_eq!(coords_at_pos(slice, range.head), coordinates.into());
assert_eq!(range.head, range.anchor);
@@ -1076,178 +667,6 @@ mod test {
}
#[test]
- fn test_behaviour_when_moving_to_start_of_next_sub_words() {
- let tests = [
- (
- "NextSubwordStart",
- vec![
- (1, Range::new(0, 0), Range::new(0, 4)),
- (1, Range::new(4, 4), Range::new(4, 11)),
- ],
- ),
- (
- "next_subword_start",
- vec![
- (1, Range::new(0, 0), Range::new(0, 5)),
- (1, Range::new(4, 4), Range::new(5, 13)),
- ],
- ),
- (
- "Next_Subword_Start",
- vec![
- (1, Range::new(0, 0), Range::new(0, 5)),
- (1, Range::new(4, 4), Range::new(5, 13)),
- ],
- ),
- (
- "NEXT_SUBWORD_START",
- vec![
- (1, Range::new(0, 0), Range::new(0, 5)),
- (1, Range::new(4, 4), Range::new(5, 13)),
- ],
- ),
- (
- "next subword start",
- vec![
- (1, Range::new(0, 0), Range::new(0, 5)),
- (1, Range::new(4, 4), Range::new(5, 13)),
- ],
- ),
- (
- "Next Subword Start",
- vec![
- (1, Range::new(0, 0), Range::new(0, 5)),
- (1, Range::new(4, 4), Range::new(5, 13)),
- ],
- ),
- (
- "NEXT SUBWORD START",
- vec![
- (1, Range::new(0, 0), Range::new(0, 5)),
- (1, Range::new(4, 4), Range::new(5, 13)),
- ],
- ),
- (
- "next__subword__start",
- vec![
- (1, Range::new(0, 0), Range::new(0, 6)),
- (1, Range::new(4, 4), Range::new(4, 6)),
- (1, Range::new(5, 5), Range::new(6, 15)),
- ],
- ),
- (
- "Next__Subword__Start",
- vec![
- (1, Range::new(0, 0), Range::new(0, 6)),
- (1, Range::new(4, 4), Range::new(4, 6)),
- (1, Range::new(5, 5), Range::new(6, 15)),
- ],
- ),
- (
- "NEXT__SUBWORD__START",
- vec![
- (1, Range::new(0, 0), Range::new(0, 6)),
- (1, Range::new(4, 4), Range::new(4, 6)),
- (1, Range::new(5, 5), Range::new(6, 15)),
- ],
- ),
- ];
-
- for (sample, scenario) in tests {
- for (count, begin, expected_end) in scenario.into_iter() {
- let range = move_next_sub_word_start(Rope::from(sample).slice(..), begin, count);
- assert_eq!(range, expected_end, "Case failed: [{}]", sample);
- }
- }
- }
-
- #[test]
- fn test_behaviour_when_moving_to_end_of_next_sub_words() {
- let tests = [
- (
- "NextSubwordEnd",
- vec![
- (1, Range::new(0, 0), Range::new(0, 4)),
- (1, Range::new(4, 4), Range::new(4, 11)),
- ],
- ),
- (
- "next subword end",
- vec![
- (1, Range::new(0, 0), Range::new(0, 4)),
- (1, Range::new(4, 4), Range::new(4, 12)),
- ],
- ),
- (
- "Next Subword End",
- vec![
- (1, Range::new(0, 0), Range::new(0, 4)),
- (1, Range::new(4, 4), Range::new(4, 12)),
- ],
- ),
- (
- "NEXT SUBWORD END",
- vec![
- (1, Range::new(0, 0), Range::new(0, 4)),
- (1, Range::new(4, 4), Range::new(4, 12)),
- ],
- ),
- (
- "next_subword_end",
- vec![
- (1, Range::new(0, 0), Range::new(0, 4)),
- (1, Range::new(4, 4), Range::new(4, 12)),
- ],
- ),
- (
- "Next_Subword_End",
- vec![
- (1, Range::new(0, 0), Range::new(0, 4)),
- (1, Range::new(4, 4), Range::new(4, 12)),
- ],
- ),
- (
- "NEXT_SUBWORD_END",
- vec![
- (1, Range::new(0, 0), Range::new(0, 4)),
- (1, Range::new(4, 4), Range::new(4, 12)),
- ],
- ),
- (
- "next__subword__end",
- vec![
- (1, Range::new(0, 0), Range::new(0, 4)),
- (1, Range::new(4, 4), Range::new(4, 13)),
- (1, Range::new(5, 5), Range::new(5, 13)),
- ],
- ),
- (
- "Next__Subword__End",
- vec![
- (1, Range::new(0, 0), Range::new(0, 4)),
- (1, Range::new(4, 4), Range::new(4, 13)),
- (1, Range::new(5, 5), Range::new(5, 13)),
- ],
- ),
- (
- "NEXT__SUBWORD__END",
- vec![
- (1, Range::new(0, 0), Range::new(0, 4)),
- (1, Range::new(4, 4), Range::new(4, 13)),
- (1, Range::new(5, 5), Range::new(5, 13)),
- ],
- ),
- ];
-
- for (sample, scenario) in tests {
- for (count, begin, expected_end) in scenario.into_iter() {
- let range = move_next_sub_word_end(Rope::from(sample).slice(..), begin, count);
- assert_eq!(range, expected_end, "Case failed: [{}]", sample);
- }
- }
- }
-
- #[test]
fn test_behaviour_when_moving_to_start_of_next_long_words() {
let tests = [
("Basic forward motion stops at the first space",
@@ -1417,92 +836,6 @@ mod test {
}
#[test]
- fn test_behaviour_when_moving_to_start_of_previous_sub_words() {
- let tests = [
- (
- "PrevSubwordEnd",
- vec![
- (1, Range::new(13, 13), Range::new(14, 11)),
- (1, Range::new(11, 11), Range::new(11, 4)),
- ],
- ),
- (
- "prev subword end",
- vec![
- (1, Range::new(15, 15), Range::new(16, 13)),
- (1, Range::new(12, 12), Range::new(13, 5)),
- ],
- ),
- (
- "Prev Subword End",
- vec![
- (1, Range::new(15, 15), Range::new(16, 13)),
- (1, Range::new(12, 12), Range::new(13, 5)),
- ],
- ),
- (
- "PREV SUBWORD END",
- vec![
- (1, Range::new(15, 15), Range::new(16, 13)),
- (1, Range::new(12, 12), Range::new(13, 5)),
- ],
- ),
- (
- "prev_subword_end",
- vec![
- (1, Range::new(15, 15), Range::new(16, 13)),
- (1, Range::new(12, 12), Range::new(13, 5)),
- ],
- ),
- (
- "Prev_Subword_End",
- vec![
- (1, Range::new(15, 15), Range::new(16, 13)),
- (1, Range::new(12, 12), Range::new(13, 5)),
- ],
- ),
- (
- "PREV_SUBWORD_END",
- vec![
- (1, Range::new(15, 15), Range::new(16, 13)),
- (1, Range::new(12, 12), Range::new(13, 5)),
- ],
- ),
- (
- "prev__subword__end",
- vec![
- (1, Range::new(17, 17), Range::new(18, 15)),
- (1, Range::new(13, 13), Range::new(14, 6)),
- (1, Range::new(14, 14), Range::new(15, 6)),
- ],
- ),
- (
- "Prev__Subword__End",
- vec![
- (1, Range::new(17, 17), Range::new(18, 15)),
- (1, Range::new(13, 13), Range::new(14, 6)),
- (1, Range::new(14, 14), Range::new(15, 6)),
- ],
- ),
- (
- "PREV__SUBWORD__END",
- vec![
- (1, Range::new(17, 17), Range::new(18, 15)),
- (1, Range::new(13, 13), Range::new(14, 6)),
- (1, Range::new(14, 14), Range::new(15, 6)),
- ],
- ),
- ];
-
- for (sample, scenario) in tests {
- for (count, begin, expected_end) in scenario.into_iter() {
- let range = move_prev_sub_word_start(Rope::from(sample).slice(..), begin, count);
- assert_eq!(range, expected_end, "Case failed: [{}]", sample);
- }
- }
- }
-
- #[test]
fn test_behaviour_when_moving_to_start_of_previous_long_words() {
let tests = [
(
@@ -1766,92 +1099,6 @@ mod test {
}
#[test]
- fn test_behaviour_when_moving_to_end_of_previous_sub_words() {
- let tests = [
- (
- "PrevSubwordEnd",
- vec![
- (1, Range::new(13, 13), Range::new(14, 11)),
- (1, Range::new(11, 11), Range::new(11, 4)),
- ],
- ),
- (
- "prev subword end",
- vec![
- (1, Range::new(15, 15), Range::new(16, 12)),
- (1, Range::new(12, 12), Range::new(12, 4)),
- ],
- ),
- (
- "Prev Subword End",
- vec![
- (1, Range::new(15, 15), Range::new(16, 12)),
- (1, Range::new(12, 12), Range::new(12, 4)),
- ],
- ),
- (
- "PREV SUBWORD END",
- vec![
- (1, Range::new(15, 15), Range::new(16, 12)),
- (1, Range::new(12, 12), Range::new(12, 4)),
- ],
- ),
- (
- "prev_subword_end",
- vec![
- (1, Range::new(15, 15), Range::new(16, 12)),
- (1, Range::new(12, 12), Range::new(12, 4)),
- ],
- ),
- (
- "Prev_Subword_End",
- vec![
- (1, Range::new(15, 15), Range::new(16, 12)),
- (1, Range::new(12, 12), Range::new(12, 4)),
- ],
- ),
- (
- "PREV_SUBWORD_END",
- vec![
- (1, Range::new(15, 15), Range::new(16, 12)),
- (1, Range::new(12, 12), Range::new(12, 4)),
- ],
- ),
- (
- "prev__subword__end",
- vec![
- (1, Range::new(17, 17), Range::new(18, 13)),
- (1, Range::new(13, 13), Range::new(13, 4)),
- (1, Range::new(14, 14), Range::new(15, 13)),
- ],
- ),
- (
- "Prev__Subword__End",
- vec![
- (1, Range::new(17, 17), Range::new(18, 13)),
- (1, Range::new(13, 13), Range::new(13, 4)),
- (1, Range::new(14, 14), Range::new(15, 13)),
- ],
- ),
- (
- "PREV__SUBWORD__END",
- vec![
- (1, Range::new(17, 17), Range::new(18, 13)),
- (1, Range::new(13, 13), Range::new(13, 4)),
- (1, Range::new(14, 14), Range::new(15, 13)),
- ],
- ),
- ];
-
- for (sample, scenario) in tests {
- for (count, begin, expected_end) in scenario.into_iter() {
- let range = move_prev_sub_word_end(Rope::from(sample).slice(..), begin, count);
- assert_eq!(range, expected_end, "Case failed: [{}]", sample);
- }
- }
- }
-
- #[test]
fn test_behaviour_when_moving_to_end_of_next_long_words() {
let tests = [
("Basic forward motion from the start of a word to the end of it",
@@ -1932,266 +1179,4 @@ mod test {
}
}
}
-
- #[test]
- fn test_behaviour_when_moving_to_end_of_prev_long_words() {
- let tests = [
- (
- "Basic backward motion from the middle of a word",
- vec![(1, Range::new(3, 3), Range::new(4, 0))],
- ),
- ("Starting from after boundary retreats the anchor",
- vec![(1, Range::new(0, 9), Range::new(8, 0))],
- ),
- (
- "Jump to end of a word succeeded by whitespace",
- vec![(1, Range::new(10, 10), Range::new(10, 4))],
- ),
- (
- " Jump to start of line from end of word preceded by whitespace",
- vec![(1, Range::new(3, 4), Range::new(4, 0))],
- ),
- ("Previous anchor is irrelevant for backward motions",
- vec![(1, Range::new(12, 5), Range::new(6, 0))]),
- (
- " Starting from whitespace moves to first space in sequence",
- vec![(1, Range::new(0, 4), Range::new(4, 0))],
- ),
- ("Identifiers_with_underscores are considered a single word",
- vec![(1, Range::new(0, 20), Range::new(20, 0))]),
- (
- "Jumping\n \nback through a newline selects whitespace",
- vec![(1, Range::new(0, 13), Range::new(12, 8))],
- ),
- (
- "Jumping to start of word from the end selects the word",
- vec![(1, Range::new(6, 7), Range::new(7, 0))],
- ),
- (
- "alphanumeric.!,and.?=punctuation are treated exactly the same",
- vec![(1, Range::new(29, 30), Range::new(30, 0))],
- ),
- (
- "... ... punctuation and spaces behave as expected",
- vec![
- (1, Range::new(0, 10), Range::new(9, 3)),
- (1, Range::new(10, 6), Range::new(7, 3)),
- ],
- ),
- (".._.._ punctuation is joined by underscores into a single block",
- vec![(1, Range::new(0, 6), Range::new(6, 0))]),
- (
- "Newlines\n\nare bridged seamlessly.",
- vec![(1, Range::new(0, 10), Range::new(8, 0))],
- ),
- (
- "Jumping \n\n\n\n\nback from within a newline group selects previous block",
- vec![(1, Range::new(0, 13), Range::new(11, 7))],
- ),
- (
- "Failed motions do not modify the range",
- vec![(0, Range::new(3, 0), Range::new(3, 0))],
- ),
- (
- "Multiple motions at once resolve correctly",
- vec![(3, Range::new(19, 19), Range::new(8, 0))],
- ),
- (
- "Excessive motions are performed partially",
- vec![(999, Range::new(40, 40), Range::new(9, 0))],
- ),
- (
- "", // Edge case of moving backwards in empty string
- vec![(1, Range::new(0, 0), Range::new(0, 0))],
- ),
- (
- "\n\n\n\n\n", // Edge case of moving backwards in all newlines
- vec![(1, Range::new(5, 5), Range::new(0, 0))],
- ),
- (" \n \nJumping back through alternated space blocks and newlines selects the space blocks",
- vec![
- (1, Range::new(0, 8), Range::new(7, 4)),
- (1, Range::new(7, 4), Range::new(3, 0)),
- ]),
- ("ヒーリ..クス multibyte characters behave as normal characters, including when interacting with punctuation",
- vec![
- (1, Range::new(0, 8), Range::new(7, 0)),
- ]),
- ];
-
- for (sample, scenario) in tests {
- for (count, begin, expected_end) in scenario.into_iter() {
- let range = move_prev_long_word_end(Rope::from(sample).slice(..), begin, count);
- assert_eq!(range, expected_end, "Case failed: [{}]", sample);
- }
- }
- }
-
- #[test]
- fn test_behaviour_when_moving_to_prev_paragraph_single() {
- let tests = [
- ("#[|]#", "#[|]#"),
- ("#[s|]#tart at\nfirst char\n", "#[|s]#tart at\nfirst char\n"),
- ("start at\nlast char#[\n|]#", "#[|start at\nlast char\n]#"),
- (
- "goto\nfirst\n\n#[p|]#aragraph",
- "#[|goto\nfirst\n\n]#paragraph",
- ),
- (
- "goto\nfirst\n#[\n|]#paragraph",
- "#[|goto\nfirst\n\n]#paragraph",
- ),
- (
- "goto\nsecond\n\np#[a|]#ragraph",
- "goto\nsecond\n\n#[|pa]#ragraph",
- ),
- (
- "here\n\nhave\nmultiple\nparagraph\n\n\n\n\n#[|]#",
- "here\n\n#[|have\nmultiple\nparagraph\n\n\n\n\n]#",
- ),
- ];
-
- for (before, expected) in tests {
- let (s, selection) = crate::test::print(before);
- let text = Rope::from(s.as_str());
- let selection =
- selection.transform(|r| move_prev_paragraph(text.slice(..), r, 1, Movement::Move));
- let actual = crate::test::plain(s.as_ref(), &selection);
- assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
- }
- }
-
- #[test]
- fn test_behaviour_when_moving_to_prev_paragraph_double() {
- let tests = [
- (
- "on#[e|]#\n\ntwo\n\nthree\n\n",
- "#[|one]#\n\ntwo\n\nthree\n\n",
- ),
- (
- "one\n\ntwo\n\nth#[r|]#ee\n\n",
- "one\n\n#[|two\n\nthr]#ee\n\n",
- ),
- ];
-
- for (before, expected) in tests {
- let (s, selection) = crate::test::print(before);
- let text = Rope::from(s.as_str());
- let selection =
- selection.transform(|r| move_prev_paragraph(text.slice(..), r, 2, Movement::Move));
- let actual = crate::test::plain(s.as_ref(), &selection);
- assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
- }
- }
-
- #[test]
- fn test_behaviour_when_moving_to_prev_paragraph_extend() {
- let tests = [
- (
- "one\n\n#[|two\n\n]#three\n\n",
- "#[|one\n\ntwo\n\n]#three\n\n",
- ),
- (
- "#[|one\n\ntwo\n\n]#three\n\n",
- "#[|one\n\ntwo\n\n]#three\n\n",
- ),
- ];
-
- for (before, expected) in tests {
- let (s, selection) = crate::test::print(before);
- let text = Rope::from(s.as_str());
- let selection = selection
- .transform(|r| move_prev_paragraph(text.slice(..), r, 1, Movement::Extend));
- let actual = crate::test::plain(s.as_ref(), &selection);
- assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
- }
- }
-
- #[test]
- fn test_behaviour_when_moving_to_next_paragraph_single() {
- let tests = [
- ("#[|]#", "#[|]#"),
- ("#[s|]#tart at\nfirst char\n", "#[start at\nfirst char\n|]#"),
- ("start at\nlast char#[\n|]#", "start at\nlast char#[\n|]#"),
- (
- "a\nb\n\n#[g|]#oto\nthird\n\nparagraph",
- "a\nb\n\n#[goto\nthird\n\n|]#paragraph",
- ),
- (
- "a\nb\n#[\n|]#goto\nthird\n\nparagraph",
- "a\nb\n\n#[goto\nthird\n\n|]#paragraph",
- ),
- (
- "a\nb#[\n|]#\n\ngoto\nsecond\n\nparagraph",
- "a\nb#[\n\n|]#goto\nsecond\n\nparagraph",
- ),
- (
- "here\n\nhave\n#[m|]#ultiple\nparagraph\n\n\n\n\n",
- "here\n\nhave\n#[multiple\nparagraph\n\n\n\n\n|]#",
- ),
- (
- "#[t|]#ext\n\n\nafter two blank lines\n\nmore text\n",
- "#[text\n\n\n|]#after two blank lines\n\nmore text\n",
- ),
- (
- "#[text\n\n\n|]#after two blank lines\n\nmore text\n",
- "text\n\n\n#[after two blank lines\n\n|]#more text\n",
- ),
- ];
-
- for (before, expected) in tests {
- let (s, selection) = crate::test::print(before);
- let text = Rope::from(s.as_str());
- let selection =
- selection.transform(|r| move_next_paragraph(text.slice(..), r, 1, Movement::Move));
- let actual = crate::test::plain(s.as_ref(), &selection);
- assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
- }
- }
-
- #[test]
- fn test_behaviour_when_moving_to_next_paragraph_double() {
- let tests = [
- (
- "one\n\ntwo\n\nth#[r|]#ee\n\n",
- "one\n\ntwo\n\nth#[ree\n\n|]#",
- ),
- (
- "on#[e|]#\n\ntwo\n\nthree\n\n",
- "on#[e\n\ntwo\n\n|]#three\n\n",
- ),
- ];
-
- for (before, expected) in tests {
- let (s, selection) = crate::test::print(before);
- let text = Rope::from(s.as_str());
- let selection =
- selection.transform(|r| move_next_paragraph(text.slice(..), r, 2, Movement::Move));
- let actual = crate::test::plain(s.as_ref(), &selection);
- assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
- }
- }
-
- #[test]
- fn test_behaviour_when_moving_to_next_paragraph_extend() {
- let tests = [
- (
- "one\n\n#[two\n\n|]#three\n\n",
- "one\n\n#[two\n\nthree\n\n|]#",
- ),
- (
- "one\n\n#[two\n\nthree\n\n|]#",
- "one\n\n#[two\n\nthree\n\n|]#",
- ),
- ];
-
- for (before, expected) in tests {
- let (s, selection) = crate::test::print(before);
- let text = Rope::from(s.as_str());
- let selection = selection
- .transform(|r| move_next_paragraph(text.slice(..), r, 1, Movement::Extend));
- let actual = crate::test::plain(s.as_ref(), &selection);
- assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
- }
- }
}