diff --git a/helix-core/src/object.rs b/helix-core/src/object.rs
index 0df105f1..9593b882 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<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() {
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 99e7608f..7618fd0a 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 498a9a3e..90088e99 100644
--- a/helix-term/src/keymap/default.rs
+++ b/helix-term/src/keymap/default.rs
@@ -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,
diff --git a/helix-term/tests/test/commands/movement.rs b/helix-term/tests/test/commands/movement.rs
index 34c9d23b..f263fbac 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);
+            "##},
+            "<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(())
+}