Unnamed repository; edit this file 'description' to name the repository.
shrink_selection without history selects first contained child
This changes the behavior of `shrink_selection` to iterate through child
nodes until it finds one that is contained within the selection, with
at least one of the ends of the selection being exclusively inside the
starting selection (though not necessarily both ends). This produces
more intuitive behavior for selecting the "first logical thing" inside
the selection.
| -rw-r--r-- | helix-core/src/object.rs | 111 | ||||
| -rw-r--r-- | helix-core/src/selection.rs | 7 | ||||
| -rw-r--r-- | helix-term/tests/test/commands/movement.rs | 21 |
3 files changed, 68 insertions, 71 deletions
diff --git a/helix-core/src/object.rs b/helix-core/src/object.rs index e0c02d0a..02e72859 100644 --- a/helix-core/src/object.rs +++ b/helix-core/src/object.rs @@ -25,38 +25,59 @@ pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) } pub fn shrink_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection { - select_node_impl( - syntax, - text, - selection, - |cursor| { - cursor.goto_first_child(); - }, - None, - ) + selection.transform(move |range| { + let (from, to) = range.into_byte_range(text); + let mut cursor = syntax.walk(); + cursor.reset_to_byte_range(from, to); + + if let Some(node) = cursor + .into_iter() + .find(|node| node.is_named() && node.is_contained_within(from..to)) + { + return Range::from_node(node, text, range.direction()); + } + + range + }) } pub fn select_next_sibling(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection { - select_node_impl( - syntax, - text, - selection, - |cursor| { - while !cursor.goto_next_sibling() { - if !cursor.goto_parent() { - break; - } + selection.transform(move |range| { + let (from, to) = range.into_byte_range(text); + let mut cursor = syntax.walk(); + cursor.reset_to_byte_range(from, to); + + while !cursor.goto_next_sibling() { + if !cursor.goto_parent() { + return range; } - }, - Some(Direction::Forward), - ) + } + + Range::from_node(cursor.node(), text, Direction::Forward) + }) +} + +pub fn select_prev_sibling(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection { + selection.transform(move |range| { + let (from, to) = range.into_byte_range(text); + let mut cursor = syntax.walk(); + cursor.reset_to_byte_range(from, to); + + while !cursor.goto_previous_sibling() { + if !cursor.goto_parent() { + return range; + } + } + + Range::from_node(cursor.node(), text, Direction::Backward) + }) } pub fn select_all_siblings(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection { let mut cursor = syntax.walk(); selection.transform_iter(move |range| { let (from, to) = range.into_byte_range(text); - cursor.reset_to_byte_range(from as u32, to as u32); + cursor.reset_to_byte_range(from, to); if !cursor.goto_parent_with(|parent| parent.child_count() > 1) { return vec![range].into_iter(); @@ -70,7 +91,7 @@ pub fn select_all_children(syntax: &Syntax, text: RopeSlice, selection: Selectio let mut cursor = syntax.walk(); selection.transform_iter(move |range| { let (from, to) = range.into_byte_range(text); - cursor.reset_to_byte_range(from as u32, to as u32); + cursor.reset_to_byte_range(from, to); select_children(&mut cursor, text, range).into_iter() }) } @@ -88,47 +109,3 @@ fn select_children(cursor: &mut TreeCursor, text: RopeSlice, range: Range) -> Ve vec![range] } } - -pub fn select_prev_sibling(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection { - select_node_impl( - syntax, - text, - selection, - |cursor| { - while !cursor.goto_previous_sibling() { - if !cursor.goto_parent() { - break; - } - } - }, - Some(Direction::Backward), - ) -} - -fn select_node_impl<F>( - syntax: &Syntax, - text: RopeSlice, - selection: Selection, - motion: F, - direction: Option<Direction>, -) -> Selection -where - F: Fn(&mut TreeCursor), -{ - let cursor = &mut syntax.walk(); - - selection.transform(|range| { - let from = text.char_to_byte(range.from()) as u32; - let to = text.char_to_byte(range.to()) as u32; - - cursor.reset_to_byte_range(from, to); - - motion(cursor); - - let node = cursor.node(); - let from = text.byte_to_char(node.start_byte() as usize); - let to = text.byte_to_char(node.end_byte() as usize); - - Range::new(from, to).with_direction(direction.unwrap_or_else(|| range.direction())) - }) -} diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 5bde08e3..1cc64c91 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -387,8 +387,11 @@ impl Range { /// Converts this char range into an in order byte range, discarding /// direction. - pub fn into_byte_range(&self, text: RopeSlice) -> (usize, usize) { - (text.char_to_byte(self.from()), text.char_to_byte(self.to())) + pub fn into_byte_range(&self, text: RopeSlice) -> (u32, u32) { + ( + text.char_to_byte(self.from()) as u32, + text.char_to_byte(self.to()) as u32, + ) } } diff --git a/helix-term/tests/test/commands/movement.rs b/helix-term/tests/test/commands/movement.rs index 93218be8..dd5a3bee 100644 --- a/helix-term/tests/test/commands/movement.rs +++ b/helix-term/tests/test/commands/movement.rs @@ -1039,15 +1039,32 @@ async fn expand_shrink_selection() -> anyhow::Result<()> { // shrink with no expansion history defaults to first child ( indoc! {r##" + #[( + Some(thing), + Some(other_thing), + )|]# + "##}, + "<A-i>", + indoc! {r##" ( #[Some(thing)|]#, Some(other_thing), ) "##}, - "<A-i>", + ), + // any movement cancels selection history and falls back to first child + ( + indoc! {r##" + ( + Some(#[thing|]#), + Some(#(other_thing|)#), + ) + + "##}, + "<A-o><A-o><A-o>jkvkkk<A-i>", indoc! {r##" ( - #[Some|]#(thing), + #[|Some(thing)]#, Some(other_thing), ) "##}, |