From eb9c8d7ce1ed4e4c0e70f60086a18f80d3670a78 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Sun, 7 Apr 2024 14:15:29 -0400 Subject: [PATCH 1/4] add Range::{from_node,into_byte_range} --- helix-core/src/selection.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 579499de5e4a..6526128722bb 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -14,6 +14,7 @@ use crate::{ use helix_stdx::rope::{self, RopeSliceExt}; use smallvec::{smallvec, SmallVec}; use std::borrow::Cow; +use tree_sitter::Node; /// A single selection range. /// @@ -73,6 +74,12 @@ impl Range { Self::new(head, head) } + pub fn from_node(node: Node, text: RopeSlice, direction: Direction) -> Self { + let from = text.byte_to_char(node.start_byte()); + let to = text.byte_to_char(node.end_byte()); + Range::new(from, to).with_direction(direction) + } + /// Start of the range. #[inline] #[must_use] @@ -376,6 +383,12 @@ impl Range { let second = graphemes.next(); first.is_some() && second.is_none() } + + /// 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())) + } } impl From<(usize, usize)> for Range { @@ -783,7 +796,9 @@ pub fn split_on_newline(text: RopeSlice, selection: &Selection) -> Selection { let mut start = sel_start; for line in sel.slice(text).lines() { - let Some(line_ending) = get_line_ending(&line) else { break }; + let Some(line_ending) = get_line_ending(&line) else { + break; + }; let line_end = start + line.len_chars(); // TODO: retain range direction result.push(Range::new(start, line_end - line_ending.len_chars())); From 61b31d0b345016436b1aee506ceb7ce08d4f2037 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Thu, 22 Jun 2023 00:21:02 -0400 Subject: [PATCH 2/4] feat(command): select_all_siblings --- helix-core/src/object.rs | 62 ++++++++- helix-term/src/commands.rs | 21 ++- helix-term/src/keymap/default.rs | 1 + helix-term/tests/test/commands/movement.rs | 151 +++++++++++++++++++++ 4 files changed, 232 insertions(+), 3 deletions(-) diff --git a/helix-core/src/object.rs b/helix-core/src/object.rs index 0df105f1a517..9593b882f6d3 100644 --- a/helix-core/src/object.rs +++ b/helix-core/src/object.rs @@ -1,4 +1,5 @@ -use crate::{syntax::TreeCursor, Range, RopeSlice, Selection, Syntax}; +use crate::{movement::Direction, syntax::TreeCursor, Range, RopeSlice, Selection, Syntax}; +use tree_sitter::{Node, Tree}; pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection { let cursor = &mut syntax.walk(); @@ -40,6 +41,65 @@ pub fn select_next_sibling(syntax: &Syntax, text: RopeSlice, selection: Selectio }) } +fn find_parent_with_more_children(mut node: Node) -> Option { + while let Some(parent) = node.parent() { + if parent.child_count() > 1 { + return Some(parent); + } + + node = parent; + } + + None +} + +pub fn select_all_siblings(tree: &Tree, text: RopeSlice, selection: Selection) -> Selection { + let root_node = &tree.root_node(); + + selection.transform_iter(|range| { + let from = text.char_to_byte(range.from()); + let to = text.char_to_byte(range.to()); + + root_node + .descendant_for_byte_range(from, to) + .and_then(find_parent_with_more_children) + .map(|parent| select_children(parent, text, range.direction())) + .unwrap_or_else(|| vec![range].into_iter()) + }) +} + +fn select_children( + node: Node, + text: RopeSlice, + direction: Direction, +) -> as std::iter::IntoIterator>::IntoIter { + let mut cursor = node.walk(); + + node.named_children(&mut cursor) + .map(|child| { + let from = text.byte_to_char(child.start_byte()); + let to = text.byte_to_char(child.end_byte()); + + if direction == Direction::Backward { + Range::new(to, from) + } else { + Range::new(from, to) + } + }) + .collect::>() + .into_iter() +} + +fn find_sibling_recursive(node: Node, sibling_fn: F) -> Option +where + F: Fn(Node) -> Option, +{ + sibling_fn(node).or_else(|| { + node.parent() + .and_then(|node| find_sibling_recursive(node, sibling_fn)) + }) +} + pub fn select_prev_sibling(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection { select_node_impl(syntax, text, selection, |cursor| { while !cursor.goto_prev_sibling() { diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 99e7608fcb5a..7618fd0ab617 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -438,8 +438,9 @@ impl MappableCommand { reverse_selection_contents, "Reverse selections contents", expand_selection, "Expand selection to parent syntax node", shrink_selection, "Shrink selection to previously expanded syntax node", - select_next_sibling, "Select next sibling in syntax tree", - select_prev_sibling, "Select previous sibling in syntax tree", + select_next_sibling, "Select next sibling in the syntax tree", + select_prev_sibling, "Select previous sibling the in syntax tree", + select_all_siblings, "Select all siblings in the syntax tree", jump_forward, "Jump forward on jumplist", jump_backward, "Jump backward on jumplist", save_selection, "Save current selection to jumplist", @@ -4974,6 +4975,22 @@ pub fn extend_parent_node_start(cx: &mut Context) { move_node_bound_impl(cx, Direction::Backward, Movement::Extend) } +fn select_all_siblings(cx: &mut Context) { + let motion = |editor: &mut Editor| { + let (view, doc) = current!(editor); + + if let Some(syntax) = doc.syntax() { + let text = doc.text().slice(..); + let current_selection = doc.selection(view.id); + let selection = + object::select_all_siblings(syntax.tree(), text, current_selection.clone()); + doc.set_selection(view.id, selection); + } + }; + + cx.editor.apply_motion(motion); +} + fn match_brackets(cx: &mut Context) { let (view, doc) = current!(cx.editor); let is_select = cx.editor.mode == Mode::Select; diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 498a9a3e71ef..90088e991eae 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -91,6 +91,7 @@ pub fn default() -> HashMap { "A-n" | "A-right" => select_next_sibling, "A-e" => move_parent_node_end, "A-b" => move_parent_node_start, + "A-a" => select_all_siblings, "%" => select_all, "x" => extend_line_below, diff --git a/helix-term/tests/test/commands/movement.rs b/helix-term/tests/test/commands/movement.rs index 34c9d23b2b8e..f263fbac4ca0 100644 --- a/helix-term/tests/test/commands/movement.rs +++ b/helix-term/tests/test/commands/movement.rs @@ -450,3 +450,154 @@ async fn test_smart_tab_move_parent_node_end() -> anyhow::Result<()> { Ok(()) } + +#[tokio::test(flavor = "multi_thread")] +async fn select_all_siblings() -> anyhow::Result<()> { + let tests = vec![ + // basic tests + ( + indoc! {r##" + let foo = bar(#[a|]#, b, c); + "##}, + "", + indoc! {r##" + let foo = bar(#[a|]#, #(b|)#, #(c|)#); + "##}, + ), + ( + indoc! {r##" + let a = [ + #[1|]#, + 2, + 3, + 4, + 5, + ]; + "##}, + "", + indoc! {r##" + let a = [ + #[1|]#, + #(2|)#, + #(3|)#, + #(4|)#, + #(5|)#, + ]; + "##}, + ), + // direction is preserved + ( + indoc! {r##" + let a = [ + #[|1]#, + 2, + 3, + 4, + 5, + ]; + "##}, + "", + indoc! {r##" + let a = [ + #[|1]#, + #(|2)#, + #(|3)#, + #(|4)#, + #(|5)#, + ]; + "##}, + ), + // can't pick any more siblings - selection stays the same + ( + indoc! {r##" + let a = [ + #[1|]#, + #(2|)#, + #(3|)#, + #(4|)#, + #(5|)#, + ]; + "##}, + "", + indoc! {r##" + let a = [ + #[1|]#, + #(2|)#, + #(3|)#, + #(4|)#, + #(5|)#, + ]; + "##}, + ), + // each cursor does the sibling select independently + ( + indoc! {r##" + let a = [ + #[1|]#, + 2, + 3, + 4, + 5, + ]; + + let b = [ + #("one"|)#, + "two", + "three", + "four", + "five", + ]; + "##}, + "", + indoc! {r##" + let a = [ + #[1|]#, + #(2|)#, + #(3|)#, + #(4|)#, + #(5|)#, + ]; + + let b = [ + #("one"|)#, + #("two"|)#, + #("three"|)#, + #("four"|)#, + #("five"|)#, + ]; + "##}, + ), + // conflicting sibling selections get normalized. Here, the primary + // selection would choose every list item, but because the secondary + // range covers more than one item, the descendent is the entire list, + // which means the sibling is the assignment. The list item ranges just + // get normalized out since the list itself becomes selected. + ( + indoc! {r##" + let a = [ + #[1|]#, + 2, + #(3, + 4|)#, + 5, + ]; + "##}, + "", + indoc! {r##" + let #(a|)# = #[[ + 1, + 2, + 3, + 4, + 5, + ]|]#; + "##}, + ), + ]; + + for test in tests { + test_with_config(AppBuilder::new().with_file("foo.rs", None), test).await?; + } + + Ok(()) +} From 01dfa121937825679baba49775245e9c77ea3d7f Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Thu, 22 Jun 2023 21:33:40 -0400 Subject: [PATCH 3/4] feat(command): select_all_children --- helix-core/src/object.rs | 30 ++++- helix-term/src/commands.rs | 20 +++- helix-term/src/keymap/default.rs | 1 + helix-term/tests/test/commands/movement.rs | 125 +++++++++++++++++++++ 4 files changed, 170 insertions(+), 6 deletions(-) diff --git a/helix-core/src/object.rs b/helix-core/src/object.rs index 9593b882f6d3..ff810489de38 100644 --- a/helix-core/src/object.rs +++ b/helix-core/src/object.rs @@ -63,7 +63,21 @@ pub fn select_all_siblings(tree: &Tree, text: RopeSlice, selection: Selection) - root_node .descendant_for_byte_range(from, to) .and_then(find_parent_with_more_children) - .map(|parent| select_children(parent, text, range.direction())) + .and_then(|parent| select_children(parent, text, range.direction())) + .unwrap_or_else(|| vec![range].into_iter()) + }) +} + +pub fn select_all_children(tree: &Tree, text: RopeSlice, selection: Selection) -> Selection { + let root_node = &tree.root_node(); + + selection.transform_iter(|range| { + let from = text.char_to_byte(range.from()); + let to = text.char_to_byte(range.to()); + + root_node + .descendant_for_byte_range(from, to) + .and_then(|parent| select_children(parent, text, range.direction())) .unwrap_or_else(|| vec![range].into_iter()) }) } @@ -72,10 +86,11 @@ fn select_children( node: Node, text: RopeSlice, direction: Direction, -) -> as std::iter::IntoIterator>::IntoIter { +) -> Option< as std::iter::IntoIterator>::IntoIter> { let mut cursor = node.walk(); - node.named_children(&mut cursor) + let children = node + .named_children(&mut cursor) .map(|child| { let from = text.byte_to_char(child.start_byte()); let to = text.byte_to_char(child.end_byte()); @@ -86,8 +101,13 @@ fn select_children( Range::new(from, to) } }) - .collect::>() - .into_iter() + .collect::>(); + + if !children.is_empty() { + Some(children.into_iter()) + } else { + None + } } fn find_sibling_recursive(node: Node, sibling_fn: F) -> Option diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 7618fd0ab617..9b203092ca53 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -440,7 +440,8 @@ impl MappableCommand { shrink_selection, "Shrink selection to previously expanded syntax node", select_next_sibling, "Select next sibling in the syntax tree", select_prev_sibling, "Select previous sibling the in syntax tree", - select_all_siblings, "Select all siblings in the syntax tree", + select_all_siblings, "Select all siblings of the current node", + select_all_children, "Select all children of the current node", jump_forward, "Jump forward on jumplist", jump_backward, "Jump backward on jumplist", save_selection, "Save current selection to jumplist", @@ -4991,6 +4992,23 @@ fn select_all_siblings(cx: &mut Context) { cx.editor.apply_motion(motion); } +fn select_all_children(cx: &mut Context) { + let motion = |editor: &mut Editor| { + let (view, doc) = current!(editor); + + if let Some(syntax) = doc.syntax() { + let text = doc.text().slice(..); + let current_selection = doc.selection(view.id); + let selection = + object::select_all_children(syntax.tree(), text, current_selection.clone()); + doc.set_selection(view.id, selection); + } + }; + + motion(cx.editor); + cx.editor.last_motion = Some(Motion(Box::new(motion))); +} + fn match_brackets(cx: &mut Context) { let (view, doc) = current!(cx.editor); let is_select = cx.editor.mode == Mode::Select; diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 90088e991eae..5a3e8eed42da 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -87,6 +87,7 @@ pub fn default() -> HashMap { "A-;" => flip_selections, "A-o" | "A-up" => expand_selection, "A-i" | "A-down" => shrink_selection, + "A-I" | "A-S-down" => select_all_children, "A-p" | "A-left" => select_prev_sibling, "A-n" | "A-right" => select_next_sibling, "A-e" => move_parent_node_end, diff --git a/helix-term/tests/test/commands/movement.rs b/helix-term/tests/test/commands/movement.rs index f263fbac4ca0..84806d5f8b8f 100644 --- a/helix-term/tests/test/commands/movement.rs +++ b/helix-term/tests/test/commands/movement.rs @@ -601,3 +601,128 @@ async fn select_all_siblings() -> anyhow::Result<()> { Ok(()) } + +#[tokio::test(flavor = "multi_thread")] +async fn select_all_children() -> anyhow::Result<()> { + let tests = vec![ + // basic tests + ( + helpers::platform_line(indoc! {r##" + let foo = bar#[(a, b, c)|]#; + "##}), + "", + helpers::platform_line(indoc! {r##" + let foo = bar(#[a|]#, #(b|)#, #(c|)#); + "##}), + ), + ( + helpers::platform_line(indoc! {r##" + let a = #[[ + 1, + 2, + 3, + 4, + 5, + ]|]#; + "##}), + "", + helpers::platform_line(indoc! {r##" + let a = [ + #[1|]#, + #(2|)#, + #(3|)#, + #(4|)#, + #(5|)#, + ]; + "##}), + ), + // direction is preserved + ( + helpers::platform_line(indoc! {r##" + let a = #[|[ + 1, + 2, + 3, + 4, + 5, + ]]#; + "##}), + "", + helpers::platform_line(indoc! {r##" + let a = [ + #[|1]#, + #(|2)#, + #(|3)#, + #(|4)#, + #(|5)#, + ]; + "##}), + ), + // can't pick any more children - selection stays the same + ( + helpers::platform_line(indoc! {r##" + let a = [ + #[1|]#, + #(2|)#, + #(3|)#, + #(4|)#, + #(5|)#, + ]; + "##}), + "", + helpers::platform_line(indoc! {r##" + let a = [ + #[1|]#, + #(2|)#, + #(3|)#, + #(4|)#, + #(5|)#, + ]; + "##}), + ), + // each cursor does the sibling select independently + ( + helpers::platform_line(indoc! {r##" + let a = #[|[ + 1, + 2, + 3, + 4, + 5, + ]]#; + + let b = #([ + "one", + "two", + "three", + "four", + "five", + ]|)#; + "##}), + "", + helpers::platform_line(indoc! {r##" + let a = [ + #[|1]#, + #(|2)#, + #(|3)#, + #(|4)#, + #(|5)#, + ]; + + let b = [ + #("one"|)#, + #("two"|)#, + #("three"|)#, + #("four"|)#, + #("five"|)#, + ]; + "##}), + ), + ]; + + for test in tests { + test_with_config(AppBuilder::new().with_file("foo.rs", None), test).await?; + } + + Ok(()) +} From 77b7d94b1e79b7618c86f5def94eb18250245d98 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Sun, 25 Jun 2023 13:50:37 -0400 Subject: [PATCH 4/4] Use new in-crate TreeCursor --- helix-core/src/object.rs | 88 +++++---------- helix-core/src/syntax/tree_cursor.rs | 118 +++++++++++++++++++-- helix-term/src/commands.rs | 53 ++++----- helix-term/tests/test/commands/movement.rs | 40 +++---- 4 files changed, 183 insertions(+), 116 deletions(-) diff --git a/helix-core/src/object.rs b/helix-core/src/object.rs index ff810489de38..28629235fa58 100644 --- a/helix-core/src/object.rs +++ b/helix-core/src/object.rs @@ -1,5 +1,4 @@ -use crate::{movement::Direction, syntax::TreeCursor, Range, RopeSlice, Selection, Syntax}; -use tree_sitter::{Node, Tree}; +use crate::{syntax::TreeCursor, Range, RopeSlice, Selection, Syntax}; pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection { let cursor = &mut syntax.walk(); @@ -41,85 +40,46 @@ pub fn select_next_sibling(syntax: &Syntax, text: RopeSlice, selection: Selectio }) } -fn find_parent_with_more_children(mut node: Node) -> Option { - while let Some(parent) = node.parent() { - if parent.child_count() > 1 { - return Some(parent); - } - - node = parent; - } - - None -} - -pub fn select_all_siblings(tree: &Tree, text: RopeSlice, selection: Selection) -> Selection { - let root_node = &tree.root_node(); - +pub fn select_all_siblings(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection { selection.transform_iter(|range| { - let from = text.char_to_byte(range.from()); - let to = text.char_to_byte(range.to()); + let mut cursor = syntax.walk(); + let (from, to) = range.into_byte_range(text); + cursor.reset_to_byte_range(from, to); + + if !cursor.goto_parent_with(|parent| parent.child_count() > 1) { + return vec![range].into_iter(); + } - root_node - .descendant_for_byte_range(from, to) - .and_then(find_parent_with_more_children) - .and_then(|parent| select_children(parent, text, range.direction())) - .unwrap_or_else(|| vec![range].into_iter()) + select_children(&mut cursor, text, range).into_iter() }) } -pub fn select_all_children(tree: &Tree, text: RopeSlice, selection: Selection) -> Selection { - let root_node = &tree.root_node(); - +pub fn select_all_children(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection { selection.transform_iter(|range| { - let from = text.char_to_byte(range.from()); - let to = text.char_to_byte(range.to()); - - root_node - .descendant_for_byte_range(from, to) - .and_then(|parent| select_children(parent, text, range.direction())) - .unwrap_or_else(|| vec![range].into_iter()) + let mut cursor = syntax.walk(); + let (from, to) = range.into_byte_range(text); + cursor.reset_to_byte_range(from, to); + select_children(&mut cursor, text, range).into_iter() }) } -fn select_children( - node: Node, +fn select_children<'n>( + cursor: &'n mut TreeCursor<'n>, text: RopeSlice, - direction: Direction, -) -> Option< as std::iter::IntoIterator>::IntoIter> { - let mut cursor = node.walk(); - - let children = node - .named_children(&mut cursor) - .map(|child| { - let from = text.byte_to_char(child.start_byte()); - let to = text.byte_to_char(child.end_byte()); - - if direction == Direction::Backward { - Range::new(to, from) - } else { - Range::new(from, to) - } - }) + range: Range, +) -> Vec { + let children = cursor + .named_children() + .map(|child| Range::from_node(child, text, range.direction())) .collect::>(); if !children.is_empty() { - Some(children.into_iter()) + children } else { - None + vec![range] } } -fn find_sibling_recursive(node: Node, sibling_fn: F) -> Option -where - F: Fn(Node) -> Option, -{ - sibling_fn(node).or_else(|| { - node.parent() - .and_then(|node| find_sibling_recursive(node, sibling_fn)) - }) -} - pub fn select_prev_sibling(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection { select_node_impl(syntax, text, selection, |cursor| { while !cursor.goto_prev_sibling() { diff --git a/helix-core/src/syntax/tree_cursor.rs b/helix-core/src/syntax/tree_cursor.rs index d9d140c9f747..692d5890a7a0 100644 --- a/helix-core/src/syntax/tree_cursor.rs +++ b/helix-core/src/syntax/tree_cursor.rs @@ -90,6 +90,19 @@ impl<'a> TreeCursor<'a> { true } + pub fn goto_parent_with

(&mut self, predicate: P) -> bool + where + P: Fn(&Node) -> bool, + { + while self.goto_parent() { + if predicate(&self.node()) { + return true; + } + } + + false + } + /// Finds the injection layer that has exactly the same range as the given `range`. fn layer_id_of_byte_range(&self, search_range: Range) -> Option { let start_idx = self @@ -102,7 +115,7 @@ impl<'a> TreeCursor<'a> { .find_map(|range| (range.start == search_range.start).then_some(range.layer_id)) } - pub fn goto_first_child(&mut self) -> bool { + fn goto_first_child_impl(&mut self, named: bool) -> bool { // Check if the current node's range is an exact injection layer range. if let Some(layer_id) = self .layer_id_of_byte_range(self.node().byte_range()) @@ -111,8 +124,16 @@ impl<'a> TreeCursor<'a> { // Switch to the child layer. self.current = layer_id; self.cursor = self.layers[self.current].tree().root_node(); - true - } else if let Some(child) = self.cursor.child(0) { + return true; + } + + let child = if named { + self.cursor.named_child(0) + } else { + self.cursor.child(0) + }; + + if let Some(child) = child { // Otherwise descend in the current tree. self.cursor = child; true @@ -121,8 +142,22 @@ impl<'a> TreeCursor<'a> { } } - pub fn goto_next_sibling(&mut self) -> bool { - if let Some(sibling) = self.cursor.next_sibling() { + pub fn goto_first_child(&mut self) -> bool { + self.goto_first_child_impl(false) + } + + pub fn goto_first_named_child(&mut self) -> bool { + self.goto_first_child_impl(true) + } + + fn goto_next_sibling_impl(&mut self, named: bool) -> bool { + let sibling = if named { + self.cursor.next_named_sibling() + } else { + self.cursor.next_sibling() + }; + + if let Some(sibling) = sibling { self.cursor = sibling; true } else { @@ -130,8 +165,22 @@ impl<'a> TreeCursor<'a> { } } - pub fn goto_prev_sibling(&mut self) -> bool { - if let Some(sibling) = self.cursor.prev_sibling() { + pub fn goto_next_sibling(&mut self) -> bool { + self.goto_next_sibling_impl(false) + } + + pub fn goto_next_named_sibling(&mut self) -> bool { + self.goto_next_sibling_impl(true) + } + + fn goto_prev_sibling_impl(&mut self, named: bool) -> bool { + let sibling = if named { + self.cursor.prev_named_sibling() + } else { + self.cursor.prev_sibling() + }; + + if let Some(sibling) = sibling { self.cursor = sibling; true } else { @@ -139,6 +188,14 @@ impl<'a> TreeCursor<'a> { } } + pub fn goto_prev_sibling(&mut self) -> bool { + self.goto_prev_sibling_impl(false) + } + + pub fn goto_prev_named_sibling(&mut self) -> bool { + self.goto_prev_sibling_impl(true) + } + /// Finds the injection layer that contains the given start-end range. fn layer_id_containing_byte_range(&self, start: usize, end: usize) -> LayerId { let start_idx = self @@ -157,4 +214,51 @@ impl<'a> TreeCursor<'a> { let root = self.layers[self.current].tree().root_node(); self.cursor = root.descendant_for_byte_range(start, end).unwrap_or(root); } + + /// Returns an iterator over the children of the node the TreeCursor is on + /// at the time this is called. + pub fn children(&'a mut self) -> ChildIter { + let parent = self.node(); + + ChildIter { + cursor: self, + parent, + named: false, + } + } + + /// Returns an iterator over the named children of the node the TreeCursor is on + /// at the time this is called. + pub fn named_children(&'a mut self) -> ChildIter { + let parent = self.node(); + + ChildIter { + cursor: self, + parent, + named: true, + } + } +} + +pub struct ChildIter<'n> { + cursor: &'n mut TreeCursor<'n>, + parent: Node<'n>, + named: bool, +} + +impl<'n> Iterator for ChildIter<'n> { + type Item = Node<'n>; + + fn next(&mut self) -> Option { + // first iteration, just visit the first child + if self.cursor.node() == self.parent { + self.cursor + .goto_first_child_impl(self.named) + .then(|| self.cursor.node()) + } else { + self.cursor + .goto_next_sibling_impl(self.named) + .then(|| self.cursor.node()) + } + } } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 9b203092ca53..6cda93dcb73c 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -38,7 +38,7 @@ use helix_core::{ textobject, unicode::width::UnicodeWidthChar, visual_offset_from_block, Deletion, LineEnding, Position, Range, Rope, RopeGraphemes, - RopeReader, RopeSlice, Selection, SmallVec, Tendril, Transaction, + RopeReader, RopeSlice, Selection, SmallVec, Syntax, Tendril, Transaction, }; use helix_view::{ document::{FormatterError, Mode, SCRATCH_BUFFER_NAME}, @@ -4976,17 +4976,23 @@ pub fn extend_parent_node_start(cx: &mut Context) { move_node_bound_impl(cx, Direction::Backward, Movement::Extend) } +fn select_all_impl(editor: &mut Editor, select_fn: F) +where + F: Fn(&Syntax, RopeSlice, Selection) -> Selection, +{ + let (view, doc) = current!(editor); + + if let Some(syntax) = doc.syntax() { + let text = doc.text().slice(..); + let current_selection = doc.selection(view.id); + let selection = select_fn(syntax, text, current_selection.clone()); + doc.set_selection(view.id, selection); + } +} + fn select_all_siblings(cx: &mut Context) { let motion = |editor: &mut Editor| { - let (view, doc) = current!(editor); - - if let Some(syntax) = doc.syntax() { - let text = doc.text().slice(..); - let current_selection = doc.selection(view.id); - let selection = - object::select_all_siblings(syntax.tree(), text, current_selection.clone()); - doc.set_selection(view.id, selection); - } + select_all_impl(editor, object::select_all_siblings); }; cx.editor.apply_motion(motion); @@ -4994,19 +5000,10 @@ fn select_all_siblings(cx: &mut Context) { fn select_all_children(cx: &mut Context) { let motion = |editor: &mut Editor| { - let (view, doc) = current!(editor); - - if let Some(syntax) = doc.syntax() { - let text = doc.text().slice(..); - let current_selection = doc.selection(view.id); - let selection = - object::select_all_children(syntax.tree(), text, current_selection.clone()); - doc.set_selection(view.id, selection); - } + select_all_impl(editor, object::select_all_children); }; - motion(cx.editor); - cx.editor.last_motion = Some(Motion(Box::new(motion))); + cx.editor.apply_motion(motion); } fn match_brackets(cx: &mut Context) { @@ -6040,7 +6037,10 @@ fn jump_to_label(cx: &mut Context, labels: Vec, behaviour: Movement) { let doc = doc.id(); cx.on_next_key(move |cx, event| { let alphabet = &cx.editor.config().jump_label_alphabet; - let Some(i ) = event.char().and_then(|ch| alphabet.iter().position(|&it| it == ch)) else { + let Some(i) = event + .char() + .and_then(|ch| alphabet.iter().position(|&it| it == ch)) + else { doc_mut!(cx.editor, &doc).remove_jump_labels(view); return; }; @@ -6053,7 +6053,10 @@ fn jump_to_label(cx: &mut Context, labels: Vec, behaviour: Movement) { cx.on_next_key(move |cx, event| { doc_mut!(cx.editor, &doc).remove_jump_labels(view); let alphabet = &cx.editor.config().jump_label_alphabet; - let Some(inner ) = event.char().and_then(|ch| alphabet.iter().position(|&it| it == ch)) else { + let Some(inner) = event + .char() + .and_then(|ch| alphabet.iter().position(|&it| it == ch)) + else { return; }; if let Some(mut range) = labels.get(outer + inner).copied() { @@ -6073,8 +6076,8 @@ fn jump_to_label(cx: &mut Context, labels: Vec, behaviour: Movement) { to } }; - Range::new(anchor, range.head) - }else{ + Range::new(anchor, range.head) + } else { range.with_direction(Direction::Forward) }; doc_mut!(cx.editor, &doc).set_selection(view, range.into()); diff --git a/helix-term/tests/test/commands/movement.rs b/helix-term/tests/test/commands/movement.rs index 84806d5f8b8f..1f33b3944200 100644 --- a/helix-term/tests/test/commands/movement.rs +++ b/helix-term/tests/test/commands/movement.rs @@ -607,16 +607,16 @@ async fn select_all_children() -> anyhow::Result<()> { let tests = vec![ // basic tests ( - helpers::platform_line(indoc! {r##" + indoc! {r##" let foo = bar#[(a, b, c)|]#; - "##}), + "##}, "", - helpers::platform_line(indoc! {r##" + indoc! {r##" let foo = bar(#[a|]#, #(b|)#, #(c|)#); - "##}), + "##}, ), ( - helpers::platform_line(indoc! {r##" + indoc! {r##" let a = #[[ 1, 2, @@ -624,9 +624,9 @@ async fn select_all_children() -> anyhow::Result<()> { 4, 5, ]|]#; - "##}), + "##}, "", - helpers::platform_line(indoc! {r##" + indoc! {r##" let a = [ #[1|]#, #(2|)#, @@ -634,11 +634,11 @@ async fn select_all_children() -> anyhow::Result<()> { #(4|)#, #(5|)#, ]; - "##}), + "##}, ), // direction is preserved ( - helpers::platform_line(indoc! {r##" + indoc! {r##" let a = #[|[ 1, 2, @@ -646,9 +646,9 @@ async fn select_all_children() -> anyhow::Result<()> { 4, 5, ]]#; - "##}), + "##}, "", - helpers::platform_line(indoc! {r##" + indoc! {r##" let a = [ #[|1]#, #(|2)#, @@ -656,11 +656,11 @@ async fn select_all_children() -> anyhow::Result<()> { #(|4)#, #(|5)#, ]; - "##}), + "##}, ), // can't pick any more children - selection stays the same ( - helpers::platform_line(indoc! {r##" + indoc! {r##" let a = [ #[1|]#, #(2|)#, @@ -668,9 +668,9 @@ async fn select_all_children() -> anyhow::Result<()> { #(4|)#, #(5|)#, ]; - "##}), + "##}, "", - helpers::platform_line(indoc! {r##" + indoc! {r##" let a = [ #[1|]#, #(2|)#, @@ -678,11 +678,11 @@ async fn select_all_children() -> anyhow::Result<()> { #(4|)#, #(5|)#, ]; - "##}), + "##}, ), // each cursor does the sibling select independently ( - helpers::platform_line(indoc! {r##" + indoc! {r##" let a = #[|[ 1, 2, @@ -698,9 +698,9 @@ async fn select_all_children() -> anyhow::Result<()> { "four", "five", ]|)#; - "##}), + "##}, "", - helpers::platform_line(indoc! {r##" + indoc! {r##" let a = [ #[|1]#, #(|2)#, @@ -716,7 +716,7 @@ async fn select_all_children() -> anyhow::Result<()> { #("four"|)#, #("five"|)#, ]; - "##}), + "##}, ), ];