summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--book/src/keymap.md6
-rw-r--r--helix-core/src/object.rs90
-rw-r--r--helix-term/src/commands.rs34
-rw-r--r--helix-term/src/keymap.rs7
4 files changed, 94 insertions, 43 deletions
diff --git a/book/src/keymap.md b/book/src/keymap.md
index ea890575..905ec48f 100644
--- a/book/src/keymap.md
+++ b/book/src/keymap.md
@@ -119,6 +119,10 @@
| `Alt-K` | Remove selections matching the regex | `remove_selections` |
| `$` | Pipe each selection into shell command, keep selections where command returned 0 | `shell_keep_pipe` |
| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` |
+| `Alt-k` | Expand selection to parent syntax node | `expand_selection` |
+| `Alt-j` | Shrink syntax tree object selection | `shrink_selection` |
+| `Alt-h` | Select previous sibling node in syntax tree | `select_prev_sibling` |
+| `Alt-l` | Select next sibling node in syntax tree | `select_next_sibling` |
### Search
@@ -262,8 +266,6 @@ Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaire
| `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` |
| `[space` | Add newline above | `add_newline_above` |
| `]space` | Add newline below | `add_newline_below` |
-| `]o` | Expand syntax tree object selection. | `expand_selection` |
-| `[o` | Shrink syntax tree object selection. | `shrink_selection` |
## Insert Mode
diff --git a/helix-core/src/object.rs b/helix-core/src/object.rs
index 3363e20b..b06f4144 100644
--- a/helix-core/src/object.rs
+++ b/helix-core/src/object.rs
@@ -1,56 +1,72 @@
use crate::{Range, RopeSlice, Selection, Syntax};
+use tree_sitter::Node;
-pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
- let tree = syntax.tree();
-
- selection.clone().transform(|range| {
- let from = text.char_to_byte(range.from());
- let to = text.char_to_byte(range.to());
+pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
+ select_node_impl(syntax, text, selection, |descendant, from, to| {
+ if descendant.start_byte() == from && descendant.end_byte() == to {
+ descendant.parent()
+ } else {
+ Some(descendant)
+ }
+ })
+}
- // find parent of a descendant that matches the range
- let parent = match tree
- .root_node()
- .descendant_for_byte_range(from, to)
- .and_then(|node| {
- if node.start_byte() == from && node.end_byte() == to {
- node.parent()
- } else {
- Some(node)
- }
- }) {
- Some(parent) => parent,
- None => return range,
- };
+pub fn shrink_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
+ select_node_impl(syntax, text, selection, |descendant, _from, _to| {
+ descendant.child(0).or(Some(descendant))
+ })
+}
- let from = text.byte_to_char(parent.start_byte());
- let to = text.byte_to_char(parent.end_byte());
+pub fn select_sibling<F>(
+ syntax: &Syntax,
+ text: RopeSlice,
+ selection: Selection,
+ sibling_fn: &F,
+) -> Selection
+where
+ F: Fn(Node) -> Option<Node>,
+{
+ select_node_impl(syntax, text, selection, |descendant, _from, _to| {
+ find_sibling_recursive(descendant, sibling_fn)
+ })
+}
- if range.head < range.anchor {
- Range::new(to, from)
- } else {
- Range::new(from, to)
- }
+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 shrink_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
+fn select_node_impl<F>(
+ syntax: &Syntax,
+ text: RopeSlice,
+ selection: Selection,
+ select_fn: F,
+) -> Selection
+where
+ F: Fn(Node, usize, usize) -> Option<Node>,
+{
let tree = syntax.tree();
- selection.clone().transform(|range| {
+ selection.transform(|range| {
let from = text.char_to_byte(range.from());
let to = text.char_to_byte(range.to());
- let descendant = match tree.root_node().descendant_for_byte_range(from, to) {
- // find first child, if not possible, fallback to the node that contains selection
- Some(descendant) => match descendant.child(0) {
- Some(child) => child,
- None => descendant,
- },
+ let node = match tree
+ .root_node()
+ .descendant_for_byte_range(from, to)
+ .and_then(|node| select_fn(node, from, to))
+ {
+ Some(node) => node,
None => return range,
};
- let from = text.byte_to_char(descendant.start_byte());
- let to = text.byte_to_char(descendant.end_byte());
+ let from = text.byte_to_char(node.start_byte());
+ let to = text.byte_to_char(node.end_byte());
if range.head < range.anchor {
Range::new(to, from)
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 9ac12931..de4bfa49 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -11,6 +11,7 @@ use helix_core::{
object, pos_at_coords,
regex::{self, Regex, RegexBuilder},
search, selection, shellwords, surround, textobject,
+ tree_sitter::Node,
unicode::width::UnicodeWidthChar,
LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril,
Transaction,
@@ -363,6 +364,8 @@ impl MappableCommand {
rotate_selection_contents_backward, "Rotate selections contents backward",
expand_selection, "Expand selection to parent syntax node",
shrink_selection, "Shrink selection to previously expanded syntax node",
+ select_next_sibling, "Select the next sibling in the syntax tree",
+ select_prev_sibling, "Select the previous sibling in the syntax tree",
jump_forward, "Jump forward on jumplist",
jump_backward, "Jump backward on jumplist",
save_selection, "Save the current selection to the jumplist",
@@ -5502,7 +5505,7 @@ fn expand_selection(cx: &mut Context) {
// save current selection so it can be restored using shrink_selection
view.object_selections.push(current_selection.clone());
- let selection = object::expand_selection(syntax, text, current_selection);
+ let selection = object::expand_selection(syntax, text, current_selection.clone());
doc.set_selection(view.id, selection);
}
};
@@ -5528,7 +5531,7 @@ fn shrink_selection(cx: &mut Context) {
// if not previous selection, shrink to first child
if let Some(syntax) = doc.syntax() {
let text = doc.text().slice(..);
- let selection = object::shrink_selection(syntax, text, current_selection);
+ let selection = object::shrink_selection(syntax, text, current_selection.clone());
doc.set_selection(view.id, selection);
}
};
@@ -5536,6 +5539,33 @@ fn shrink_selection(cx: &mut Context) {
cx.editor.last_motion = Some(Motion(Box::new(motion)));
}
+fn select_sibling_impl<F>(cx: &mut Context, sibling_fn: &'static F)
+where
+ F: Fn(Node) -> Option<Node>,
+{
+ 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_sibling(syntax, text, current_selection.clone(), sibling_fn);
+ doc.set_selection(view.id, selection);
+ }
+ };
+ motion(cx.editor);
+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
+}
+
+fn select_next_sibling(cx: &mut Context) {
+ select_sibling_impl(cx, &|node| Node::next_sibling(&node))
+}
+
+fn select_prev_sibling(cx: &mut Context) {
+ select_sibling_impl(cx, &|node| Node::prev_sibling(&node))
+}
+
fn match_brackets(cx: &mut Context) {
let (view, doc) = current!(cx.editor);
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs
index 79a06206..e5990d72 100644
--- a/helix-term/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -552,6 +552,11 @@ impl Default for Keymaps {
"S" => split_selection,
";" => collapse_selection,
"A-;" => flip_selections,
+ "A-k" => expand_selection,
+ "A-j" => shrink_selection,
+ "A-h" => select_prev_sibling,
+ "A-l" => select_next_sibling,
+
"%" => select_all,
"x" => extend_line,
"X" => extend_to_line_bounds,
@@ -569,13 +574,11 @@ impl Default for Keymaps {
"d" => goto_prev_diag,
"D" => goto_first_diag,
"space" => add_newline_above,
- "o" => shrink_selection,
},
"]" => { "Right bracket"
"d" => goto_next_diag,
"D" => goto_last_diag,
"space" => add_newline_below,
- "o" => expand_selection,
},
"/" => search,