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.
Skyler Hawthorne 5 months ago
parent 9816696 · commit d49c8ef
-rw-r--r--helix-core/src/object.rs111
-rw-r--r--helix-core/src/selection.rs7
-rw-r--r--helix-term/tests/test/commands/movement.rs21
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),
)
"##},