on master help sign in
add select_next_sibling and select_prev_sibling commands (#1495)
392dfa0Michael Davis
book/src/keymap.md+4−2
@@ -119,6 +119,10 @@
119| `Alt-K` | Remove selections matching the regex | `remove_selections` |
119| `Alt-K` | Remove selections matching the regex | `remove_selections` |
120| `$` | Pipe each selection into shell command, keep selections where command returned 0 | `shell_keep_pipe` |
120| `$` | Pipe each selection into shell command, keep selections where command returned 0 | `shell_keep_pipe` |
121| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` |
121| `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` |
122
126
123### Search
127### Search
124
128
@@ -262,8 +266,6 @@ Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaire
262| `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` |
266| `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` |
263| `[space` | Add newline above | `add_newline_above` |
267| `[space` | Add newline above | `add_newline_above` |
264| `]space` | Add newline below | `add_newline_below` |
268| `]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` |
267
269
268## Insert Mode
270## Insert Mode
269
271
helix-core/src/object.rs+53−37
@@ -1,56 +1,72 @@
1use crate::{Range, RopeSlice, Selection, Syntax};
1use crate::{Range, RopeSlice, Selection, Syntax};
2+use tree_sitter::Node;
2
3
3−pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
4+pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
4− let tree = syntax.tree();
5+ select_node_impl(syntax, text, selection, |descendant, from, to| {
5−
6+ if descendant.start_byte() == from && descendant.end_byte() == to {
6− selection.clone().transform(|range| {
7+ descendant.parent()
7− let from = text.char_to_byte(range.from());
8+ } else {
8− let to = text.char_to_byte(range.to());
9+ Some(descendant)
10+ }
11+ })
12+}
9
13
10− // find parent of a descendant that matches the range
14+pub fn shrink_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
11− let parent = match tree
15+ select_node_impl(syntax, text, selection, |descendant, _from, _to| {
12− .root_node()
16+ descendant.child(0).or(Some(descendant))
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− }) {
17+ })
21− Some(parent) => parent,
22− None => return range,
23− };
18+}
24
19
25− let from = text.byte_to_char(parent.start_byte());
20+pub fn select_sibling<F>(
26− let to = text.byte_to_char(parent.end_byte());
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+}
27
33
28− if range.head < range.anchor {
34+fn find_sibling_recursive<F>(node: Node, sibling_fn: F) -> Option<Node>
29− Range::new(to, from)
35+where
30− } else {
36+ F: Fn(Node) -> Option<Node>,
31− Range::new(from, to)
37+{
32− }
38+ sibling_fn(node).or_else(|| {
39+ node.parent()
40+ .and_then(|node| find_sibling_recursive(node, sibling_fn))
33 })
41 })
34}
42}
35
43
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+{
37 let tree = syntax.tree();
53 let tree = syntax.tree();
38
54
39− selection.clone().transform(|range| {
55+ selection.transform(|range| {
40 let from = text.char_to_byte(range.from());
56 let from = text.char_to_byte(range.from());
41 let to = text.char_to_byte(range.to());
57 let to = text.char_to_byte(range.to());
42
58
59+ let node = match tree
60+ .root_node()
43− let descendant = match tree.root_node().descendant_for_byte_range(from, to) {
61+ .descendant_for_byte_range(from, to)
44− // find first child, if not possible, fallback to the node that contains selection
62+ .and_then(|node| select_fn(node, from, to))
45− Some(descendant) => match descendant.child(0) {
63+ {
46− Some(child) => child,
64+ Some(node) => node,
47− None => descendant,
48− },
49 None => return range,
65 None => return range,
50 };
66 };
51
67
52− let from = text.byte_to_char(descendant.start_byte());
68+ let from = text.byte_to_char(node.start_byte());
53− let to = text.byte_to_char(descendant.end_byte());
69+ let to = text.byte_to_char(node.end_byte());
54
70
55 if range.head < range.anchor {
71 if range.head < range.anchor {
56 Range::new(to, from)
72 Range::new(to, from)
helix-term/src/commands.rs+32−2
@@ -11,6 +11,7 @@ use helix_core::{
11 object, pos_at_coords,
11 object, pos_at_coords,
12 regex::{self, Regex, RegexBuilder},
12 regex::{self, Regex, RegexBuilder},
13 search, selection, shellwords, surround, textobject,
13 search, selection, shellwords, surround, textobject,
14+ tree_sitter::Node,
14 unicode::width::UnicodeWidthChar,
15 unicode::width::UnicodeWidthChar,
15 LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril,
16 LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril,
16 Transaction,
17 Transaction,
@@ -363,6 +364,8 @@ impl MappableCommand {
363 rotate_selection_contents_backward, "Rotate selections contents backward",
364 rotate_selection_contents_backward, "Rotate selections contents backward",
364 expand_selection, "Expand selection to parent syntax node",
365 expand_selection, "Expand selection to parent syntax node",
365 shrink_selection, "Shrink selection to previously expanded syntax node",
366 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",
366 jump_forward, "Jump forward on jumplist",
369 jump_forward, "Jump forward on jumplist",
367 jump_backward, "Jump backward on jumplist",
370 jump_backward, "Jump backward on jumplist",
368 save_selection, "Save the current selection to the jumplist",
371 save_selection, "Save the current selection to the jumplist",
@@ -5502,7 +5505,7 @@ fn expand_selection(cx: &mut Context) {
5502 // save current selection so it can be restored using shrink_selection
5505 // save current selection so it can be restored using shrink_selection
5503 view.object_selections.push(current_selection.clone());
5506 view.object_selections.push(current_selection.clone());
5504
5507
5505− let selection = object::expand_selection(syntax, text, current_selection);
5508+ let selection = object::expand_selection(syntax, text, current_selection.clone());
5506 doc.set_selection(view.id, selection);
5509 doc.set_selection(view.id, selection);
5507 }
5510 }
5508 };
5511 };
@@ -5528,7 +5531,7 @@ fn shrink_selection(cx: &mut Context) {
5528 // if not previous selection, shrink to first child
5531 // if not previous selection, shrink to first child
5529 if let Some(syntax) = doc.syntax() {
5532 if let Some(syntax) = doc.syntax() {
5530 let text = doc.text().slice(..);
5533 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());
5532 doc.set_selection(view.id, selection);
5535 doc.set_selection(view.id, selection);
5533 }
5536 }
5534 };
5537 };
@@ -5536,6 +5539,33 @@ fn shrink_selection(cx: &mut Context) {
5536 cx.editor.last_motion = Some(Motion(Box::new(motion)));
5539 cx.editor.last_motion = Some(Motion(Box::new(motion)));
5537}
5540}
5538
5541
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+
5539fn match_brackets(cx: &mut Context) {
5569fn match_brackets(cx: &mut Context) {
5540 let (view, doc) = current!(cx.editor);
5570 let (view, doc) = current!(cx.editor);
5541
5571
helix-term/src/keymap.rs+5−2
@@ -552,6 +552,11 @@ impl Default for Keymaps {
552 "S" => split_selection,
552 "S" => split_selection,
553 ";" => collapse_selection,
553 ";" => collapse_selection,
554 "A-;" => flip_selections,
554 "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+
555 "%" => select_all,
560 "%" => select_all,
556 "x" => extend_line,
561 "x" => extend_line,
557 "X" => extend_to_line_bounds,
562 "X" => extend_to_line_bounds,
@@ -569,13 +574,11 @@ impl Default for Keymaps {
569 "d" => goto_prev_diag,
574 "d" => goto_prev_diag,
570 "D" => goto_first_diag,
575 "D" => goto_first_diag,
571 "space" => add_newline_above,
576 "space" => add_newline_above,
572− "o" => shrink_selection,
573 },
577 },
574 "]" => { "Right bracket"
578 "]" => { "Right bracket"
575 "d" => goto_next_diag,
579 "d" => goto_next_diag,
576 "D" => goto_last_diag,
580 "D" => goto_last_diag,
577 "space" => add_newline_below,
581 "space" => add_newline_below,
578− "o" => expand_selection,
579 },
582 },
580
583
581 "/" => search,
584 "/" => search,