diff --git a/helix-core/src/object.rs b/helix-core/src/object.rs index ff810489d..28629235f 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); +pub fn select_all_siblings(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection { + selection.transform_iter(|range| { + 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(); } - 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) - .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 d9d140c9f..692d5890a 100644 --- a/helix-core/src/syntax/tree_cursor.rs +++ b/helix-core/src/syntax/tree_cursor.rs @@ -90,6 +90,19 @@ pub fn goto_parent(&mut self) -> bool { 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 @@ fn layer_id_of_byte_range(&self, search_range: Range) -> Option .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 @@ pub fn goto_first_child(&mut self) -> bool { // 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,45 @@ pub fn goto_first_child(&mut self) -> bool { } } + 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 { + false + } + } + pub fn goto_next_sibling(&mut self) -> bool { - if let Some(sibling) = self.cursor.next_sibling() { + 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 { @@ -131,12 +189,11 @@ pub fn goto_next_sibling(&mut self) -> bool { } pub fn goto_prev_sibling(&mut self) -> bool { - if let Some(sibling) = self.cursor.prev_sibling() { - self.cursor = sibling; - true - } else { - false - } + 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. @@ -157,4 +214,51 @@ pub fn reset_to_byte_range(&mut self, start: usize, end: usize) { 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 9b203092c..6cda93dcb 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -38,7 +38,7 @@ 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 84806d5f8..1f33b3944 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"|)#, ]; - "##}), + "##}, ), ];