on master help sign in
add select_next_sibling and select_prev_sibling commands (#1495)

* add select_next_sibling and select_prev_sibling commands * refactor objects to use higher order functions * address clippy feedback * move selection cloning into commands * add default keybindings under left/right brackets * use [+t,]+t for selecting sibling syntax nodes * setup Alt-{j,k,h,l} default keymaps for syntax selection commands * reduce boilerplate of select_next/prev_sibling in commands * import tree-sitter Node type in commands

commit392dfa0841fb106300eedf26f8628e48a32d0ea4 authorMichael Davis <mcarsondavis@gmail.com> dateThu 20 Jan 2022, 09:52 (5 years ago) parentfd70804
4 files changed+94−43
book/src/keymap.md+4−2 helix-core/src/object.rs+53−37 helix-term/src/commands.rs+32−2 helix-term/src/keymap.rs+5−2
book/src/keymap.md+4−2
@@ -119,6 +119,10 @@
119119| `Alt-K` | Remove selections matching the regex | `remove_selections` |
120120| `$` | Pipe each selection into shell command, keep selections where command returned 0 | `shell_keep_pipe` |
121121| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` |
122+| `Alt-k` | Expand selection to parent syntax node | `expand_selection` |
123+| `Alt-j` | Shrink syntax tree object selection | `shrink_selection` |
124+| `Alt-h` | Select previous sibling node in syntax tree | `select_prev_sibling` |
125+| `Alt-l` | Select next sibling node in syntax tree | `select_next_sibling` |
122126
123127### Search
124128
@@ -262,8 +266,6 @@ Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaire
262266| `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` |
263267| `[space` | Add newline above | `add_newline_above` |
264268| `]space` | Add newline below | `add_newline_below` |
265−| `]o` | Expand syntax tree object selection. | `expand_selection` |
266−| `[o` | Shrink syntax tree object selection. | `shrink_selection` |
267269
268270## Insert Mode
269271
helix-core/src/object.rs+53−37
@@ -1,56 +1,72 @@
11use crate::{Range, RopeSlice, Selection, Syntax};
2+use tree_sitter::Node;
23
3−pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
4− let tree = syntax.tree();
5−
6− selection.clone().transform(|range| {
7− let from = text.char_to_byte(range.from());
8− let to = text.char_to_byte(range.to());
4+pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
5+ select_node_impl(syntax, text, selection, |descendant, from, to| {
6+ if descendant.start_byte() == from && descendant.end_byte() == to {
7+ descendant.parent()
8+ } else {
9+ Some(descendant)
10+ }
11+ })
12+}
913
10− // find parent of a descendant that matches the range
11− let parent = match tree
12− .root_node()
13− .descendant_for_byte_range(from, to)
14− .and_then(|node| {
15− if node.start_byte() == from && node.end_byte() == to {
16− node.parent()
17− } else {
18− Some(node)
19− }
20− }) {
21− Some(parent) => parent,
22− None => return range,
23− };
14+pub fn shrink_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
15+ select_node_impl(syntax, text, selection, |descendant, _from, _to| {
16+ descendant.child(0).or(Some(descendant))
17+ })
18+}
2419
25− let from = text.byte_to_char(parent.start_byte());
26− let to = text.byte_to_char(parent.end_byte());
20+pub fn select_sibling<F>(
21+ syntax: &Syntax,
22+ text: RopeSlice,
23+ selection: Selection,
24+ sibling_fn: &F,
25+) -> Selection
26+where
27+ F: Fn(Node) -> Option<Node>,
28+{
29+ select_node_impl(syntax, text, selection, |descendant, _from, _to| {
30+ find_sibling_recursive(descendant, sibling_fn)
31+ })
32+}
2733
28− if range.head < range.anchor {
29− Range::new(to, from)
30− } else {
31− Range::new(from, to)
32− }
34+fn find_sibling_recursive<F>(node: Node, sibling_fn: F) -> Option<Node>
35+where
36+ F: Fn(Node) -> Option<Node>,
37+{
38+ sibling_fn(node).or_else(|| {
39+ node.parent()
40+ .and_then(|node| find_sibling_recursive(node, sibling_fn))
3341 })
3442}
3543
36−pub fn shrink_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
44+fn select_node_impl<F>(
45+ syntax: &Syntax,
46+ text: RopeSlice,
47+ selection: Selection,
48+ select_fn: F,
49+) -> Selection
50+where
51+ F: Fn(Node, usize, usize) -> Option<Node>,
52+{
3753 let tree = syntax.tree();
3854
39− selection.clone().transform(|range| {
55+ selection.transform(|range| {
4056 let from = text.char_to_byte(range.from());
4157 let to = text.char_to_byte(range.to());
4258
43− let descendant = match tree.root_node().descendant_for_byte_range(from, to) {
44− // find first child, if not possible, fallback to the node that contains selection
45− Some(descendant) => match descendant.child(0) {
46− Some(child) => child,
47− None => descendant,
48− },
59+ let node = match tree
60+ .root_node()
61+ .descendant_for_byte_range(from, to)
62+ .and_then(|node| select_fn(node, from, to))
63+ {
64+ Some(node) => node,
4965 None => return range,
5066 };
5167
52− let from = text.byte_to_char(descendant.start_byte());
53− let to = text.byte_to_char(descendant.end_byte());
68+ let from = text.byte_to_char(node.start_byte());
69+ let to = text.byte_to_char(node.end_byte());
5470
5571 if range.head < range.anchor {
5672 Range::new(to, from)
helix-term/src/commands.rs+32−2
@@ -11,6 +11,7 @@ use helix_core::{
1111 object, pos_at_coords,
1212 regex::{self, Regex, RegexBuilder},
1313 search, selection, shellwords, surround, textobject,
14+ tree_sitter::Node,
1415 unicode::width::UnicodeWidthChar,
1516 LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril,
1617 Transaction,
@@ -363,6 +364,8 @@ impl MappableCommand {
363364 rotate_selection_contents_backward, "Rotate selections contents backward",
364365 expand_selection, "Expand selection to parent syntax node",
365366 shrink_selection, "Shrink selection to previously expanded syntax node",
367+ select_next_sibling, "Select the next sibling in the syntax tree",
368+ select_prev_sibling, "Select the previous sibling in the syntax tree",
366369 jump_forward, "Jump forward on jumplist",
367370 jump_backward, "Jump backward on jumplist",
368371 save_selection, "Save the current selection to the jumplist",
@@ -5502,7 +5505,7 @@ fn expand_selection(cx: &mut Context) {
55025505 // save current selection so it can be restored using shrink_selection
55035506 view.object_selections.push(current_selection.clone());
55045507
5505− let selection = object::expand_selection(syntax, text, current_selection);
5508+ let selection = object::expand_selection(syntax, text, current_selection.clone());
55065509 doc.set_selection(view.id, selection);
55075510 }
55085511 };
@@ -5528,7 +5531,7 @@ fn shrink_selection(cx: &mut Context) {
55285531 // if not previous selection, shrink to first child
55295532 if let Some(syntax) = doc.syntax() {
55305533 let text = doc.text().slice(..);
5531− let selection = object::shrink_selection(syntax, text, current_selection);
5534+ let selection = object::shrink_selection(syntax, text, current_selection.clone());
55325535 doc.set_selection(view.id, selection);
55335536 }
55345537 };
@@ -5536,6 +5539,33 @@ fn shrink_selection(cx: &mut Context) {
55365539 cx.editor.last_motion = Some(Motion(Box::new(motion)));
55375540}
55385541
5542+fn select_sibling_impl<F>(cx: &mut Context, sibling_fn: &'static F)
5543+where
5544+ F: Fn(Node) -> Option<Node>,
5545+{
5546+ let motion = |editor: &mut Editor| {
5547+ let (view, doc) = current!(editor);
5548+
5549+ if let Some(syntax) = doc.syntax() {
5550+ let text = doc.text().slice(..);
5551+ let current_selection = doc.selection(view.id);
5552+ let selection =
5553+ object::select_sibling(syntax, text, current_selection.clone(), sibling_fn);
5554+ doc.set_selection(view.id, selection);
5555+ }
5556+ };
5557+ motion(cx.editor);
5558+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
5559+}
5560+
5561+fn select_next_sibling(cx: &mut Context) {
5562+ select_sibling_impl(cx, &|node| Node::next_sibling(&node))
5563+}
5564+
5565+fn select_prev_sibling(cx: &mut Context) {
5566+ select_sibling_impl(cx, &|node| Node::prev_sibling(&node))
5567+}
5568+
55395569fn match_brackets(cx: &mut Context) {
55405570 let (view, doc) = current!(cx.editor);
55415571
helix-term/src/keymap.rs+5−2
@@ -552,6 +552,11 @@ impl Default for Keymaps {
552552 "S" => split_selection,
553553 ";" => collapse_selection,
554554 "A-;" => flip_selections,
555+ "A-k" => expand_selection,
556+ "A-j" => shrink_selection,
557+ "A-h" => select_prev_sibling,
558+ "A-l" => select_next_sibling,
559+
555560 "%" => select_all,
556561 "x" => extend_line,
557562 "X" => extend_to_line_bounds,
@@ -569,13 +574,11 @@ impl Default for Keymaps {
569574 "d" => goto_prev_diag,
570575 "D" => goto_first_diag,
571576 "space" => add_newline_above,
572− "o" => shrink_selection,
573577 },
574578 "]" => { "Right bracket"
575579 "d" => goto_next_diag,
576580 "D" => goto_last_diag,
577581 "space" => add_newline_below,
578− "o" => expand_selection,
579582 },
580583
581584 "/" => search,