mirror of
https://github.com/helix-editor/helix.git
synced 2024-08-17 06:40:47 +03:00
feat(command): select_all_siblings
This commit is contained in:
parent
cf9b88f9bd
commit
87c4161732
@ -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<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();
|
||||
|
||||
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,
|
||||
) -> <Vec<Range> 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::<Vec<_>>()
|
||||
.into_iter()
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -438,8 +438,9 @@ pub fn doc(&self) -> &str {
|
||||
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;
|
||||
|
@ -91,6 +91,7 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
|
||||
"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,
|
||||
|
@ -450,3 +450,154 @@ fn foo() {
|
||||
|
||||
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);
|
||||
"##},
|
||||
"<A-a>",
|
||||
indoc! {r##"
|
||||
let foo = bar(#[a|]#, #(b|)#, #(c|)#);
|
||||
"##},
|
||||
),
|
||||
(
|
||||
indoc! {r##"
|
||||
let a = [
|
||||
#[1|]#,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
];
|
||||
"##},
|
||||
"<A-a>",
|
||||
indoc! {r##"
|
||||
let a = [
|
||||
#[1|]#,
|
||||
#(2|)#,
|
||||
#(3|)#,
|
||||
#(4|)#,
|
||||
#(5|)#,
|
||||
];
|
||||
"##},
|
||||
),
|
||||
// direction is preserved
|
||||
(
|
||||
indoc! {r##"
|
||||
let a = [
|
||||
#[|1]#,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
];
|
||||
"##},
|
||||
"<A-a>",
|
||||
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|)#,
|
||||
];
|
||||
"##},
|
||||
"<A-a>",
|
||||
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",
|
||||
];
|
||||
"##},
|
||||
"<A-a>",
|
||||
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,
|
||||
];
|
||||
"##},
|
||||
"<A-a>",
|
||||
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(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user