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.rs451
1 files changed, 22 insertions, 429 deletions
diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs
index 09a99db2..54eb02fd 100644
--- a/helix-core/src/movement.rs
+++ b/helix-core/src/movement.rs
@@ -1,6 +1,7 @@
-use std::{borrow::Cow, cmp::Reverse, iter};
+use std::{cmp::Reverse, iter};
use ropey::iter::Chars;
+use tree_sitter::{Node, QueryCursor};
use crate::{
char_idx_at_visual_offset,
@@ -12,10 +13,9 @@ use crate::{
},
line_ending::rope_is_line_ending,
position::char_idx_at_visual_block_offset,
- syntax,
+ syntax::LanguageConfiguration,
text_annotations::TextAnnotations,
textobject::TextObject,
- tree_sitter::Node,
visual_offset_from_block, Range, RopeSlice, Selection, Syntax,
};
@@ -79,19 +79,19 @@ pub fn move_vertically_visual(
Direction::Backward => -(count as isize),
};
+ // TODO how to handle inline annotations that span an entire visual line (very unlikely).
+
// 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(
+ let new_pos = 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;
- }
+ )
+ .0;
// 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 {
@@ -197,31 +197,13 @@ pub fn move_prev_long_word_end(slice: RopeSlice, range: Range, count: usize) ->
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)
-}
-
fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTarget) -> Range {
let is_prev = matches!(
target,
WordMotionTarget::PrevWordStart
| WordMotionTarget::PrevLongWordStart
- | WordMotionTarget::PrevSubWordStart
| WordMotionTarget::PrevWordEnd
| WordMotionTarget::PrevLongWordEnd
- | WordMotionTarget::PrevSubWordEnd
);
// Special-case early-out.
@@ -401,12 +383,6 @@ pub enum WordMotionTarget {
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 +398,8 @@ 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 +476,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 => {
@@ -539,44 +494,26 @@ 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,
+ 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 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,6 +521,7 @@ pub fn goto_treesitter_object(
],
slice_tree,
slice,
+ &mut cursor,
)?;
let node = match dir {
@@ -618,15 +556,14 @@ pub fn goto_treesitter_object(
last_range
}
-fn find_parent_start<'tree>(node: &Node<'tree>) -> Option<Node<'tree>> {
+fn find_parent_start(mut node: Node) -> Option<Node> {
let start = node.start_byte();
- let mut node = Cow::Borrowed(node);
while node.start_byte() >= start || !node.is_named() {
- node = Cow::Owned(node.parent()?);
+ node = node.parent()?;
}
- Some(node.into_owned())
+ Some(node)
}
pub fn move_parent_node_end(
@@ -637,8 +574,8 @@ pub fn move_parent_node_end(
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 start_from = text.char_to_byte(range.from());
+ let start_to = text.char_to_byte(range.to());
let mut node = match syntax.named_descendant_for_byte_range(start_from, start_to) {
Some(node) => node,
@@ -656,18 +593,18 @@ pub fn move_parent_node_end(
// 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),
+ Direction::Forward => text.byte_to_char(node.end_byte()),
// 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);
+ let end_head = text.byte_to_char(node.start_byte());
// 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)
+ node = find_parent_start(node).unwrap_or(node);
+ text.byte_to_char(node.start_byte())
} else {
end_head
}
@@ -1076,178 +1013,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 +1182,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 +1445,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",