Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--helix-core/src/object.rs88
-rw-r--r--helix-core/src/syntax/tree_cursor.rs118
-rw-r--r--helix-term/src/commands.rs53
-rw-r--r--helix-term/tests/test/commands/movement.rs40
4 files changed, 183 insertions, 116 deletions
diff --git a/helix-core/src/object.rs b/helix-core/src/object.rs
index ff810489..28629235 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<Node> {
- 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<<Vec<Range> 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<Range> {
+ let children = cursor
+ .named_children()
+ .map(|child| Range::from_node(child, text, range.direction()))
.collect::<Vec<_>>();
if !children.is_empty() {
- Some(children.into_iter())
+ children
} else {
- None
+ vec![range]
}
}
-fn find_sibling_recursive<F>(node: Node, sibling_fn: F) -> Option<Node>
-where
- F: Fn(Node) -> Option<Node>,
-{
- 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 d9d140c9..692d5890 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<P>(&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<usize>) -> Option<LayerId> {
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<Self::Item> {
+ // 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 9b203092..6cda93dc 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<F>(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<Range>, 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<Range>, 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<Range>, 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 84806d5f..1f33b394 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)|]#;
- "##}),
+ "##},
"<A-I>",
- 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,
]|]#;
- "##}),
+ "##},
"<A-I>",
- 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,
]]#;
- "##}),
+ "##},
"<A-I>",
- 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|)#,
];
- "##}),
+ "##},
"<A-I>",
- 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",
]|)#;
- "##}),
+ "##},
"<A-I>",
- helpers::platform_line(indoc! {r##"
+ indoc! {r##"
let a = [
#[|1]#,
#(|2)#,
@@ -716,7 +716,7 @@ async fn select_all_children() -> anyhow::Result<()> {
#("four"|)#,
#("five"|)#,
];
- "##}),
+ "##},
),
];