mirror of https://github.com/helix-editor/helix
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 commandspull/1551/head
parent
fd7080498e
commit
392dfa0841
|
@ -119,6 +119,10 @@
|
||||||
| `Alt-K` | Remove selections matching the regex | `remove_selections` |
|
| `Alt-K` | Remove selections matching the regex | `remove_selections` |
|
||||||
| `$` | Pipe each selection into shell command, keep selections where command returned 0 | `shell_keep_pipe` |
|
| `$` | Pipe each selection into shell command, keep selections where command returned 0 | `shell_keep_pipe` |
|
||||||
| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` |
|
| `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
|
### 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` |
|
| `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` |
|
||||||
| `[space` | Add newline above | `add_newline_above` |
|
| `[space` | Add newline above | `add_newline_above` |
|
||||||
| `]space` | Add newline below | `add_newline_below` |
|
| `]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
|
## Insert Mode
|
||||||
|
|
||||||
|
|
|
@ -1,56 +1,72 @@
|
||||||
use crate::{Range, RopeSlice, Selection, Syntax};
|
use crate::{Range, RopeSlice, Selection, Syntax};
|
||||||
|
use tree_sitter::Node;
|
||||||
|
|
||||||
pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
let tree = syntax.tree();
|
||||||
|
|
||||||
selection.clone().transform(|range| {
|
selection.transform(|range| {
|
||||||
let from = text.char_to_byte(range.from());
|
let from = text.char_to_byte(range.from());
|
||||||
let to = text.char_to_byte(range.to());
|
let to = text.char_to_byte(range.to());
|
||||||
|
|
||||||
// find parent of a descendant that matches the range
|
let node = match tree
|
||||||
let parent = match tree
|
|
||||||
.root_node()
|
.root_node()
|
||||||
.descendant_for_byte_range(from, to)
|
.descendant_for_byte_range(from, to)
|
||||||
.and_then(|node| {
|
.and_then(|node| select_fn(node, from, to))
|
||||||
if node.start_byte() == from && node.end_byte() == to {
|
{
|
||||||
node.parent()
|
Some(node) => node,
|
||||||
} else {
|
|
||||||
Some(node)
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Some(parent) => parent,
|
|
||||||
None => return range,
|
None => return range,
|
||||||
};
|
};
|
||||||
|
|
||||||
let from = text.byte_to_char(parent.start_byte());
|
let from = text.byte_to_char(node.start_byte());
|
||||||
let to = text.byte_to_char(parent.end_byte());
|
let to = text.byte_to_char(node.end_byte());
|
||||||
|
|
||||||
if range.head < range.anchor {
|
|
||||||
Range::new(to, from)
|
|
||||||
} else {
|
|
||||||
Range::new(from, to)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn shrink_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());
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
None => return range,
|
|
||||||
};
|
|
||||||
|
|
||||||
let from = text.byte_to_char(descendant.start_byte());
|
|
||||||
let to = text.byte_to_char(descendant.end_byte());
|
|
||||||
|
|
||||||
if range.head < range.anchor {
|
if range.head < range.anchor {
|
||||||
Range::new(to, from)
|
Range::new(to, from)
|
||||||
|
|
|
@ -11,6 +11,7 @@ use helix_core::{
|
||||||
object, pos_at_coords,
|
object, pos_at_coords,
|
||||||
regex::{self, Regex, RegexBuilder},
|
regex::{self, Regex, RegexBuilder},
|
||||||
search, selection, shellwords, surround, textobject,
|
search, selection, shellwords, surround, textobject,
|
||||||
|
tree_sitter::Node,
|
||||||
unicode::width::UnicodeWidthChar,
|
unicode::width::UnicodeWidthChar,
|
||||||
LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril,
|
LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril,
|
||||||
Transaction,
|
Transaction,
|
||||||
|
@ -363,6 +364,8 @@ impl MappableCommand {
|
||||||
rotate_selection_contents_backward, "Rotate selections contents backward",
|
rotate_selection_contents_backward, "Rotate selections contents backward",
|
||||||
expand_selection, "Expand selection to parent syntax node",
|
expand_selection, "Expand selection to parent syntax node",
|
||||||
shrink_selection, "Shrink selection to previously expanded 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_forward, "Jump forward on jumplist",
|
||||||
jump_backward, "Jump backward on jumplist",
|
jump_backward, "Jump backward on jumplist",
|
||||||
save_selection, "Save the current selection to the 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
|
// save current selection so it can be restored using shrink_selection
|
||||||
view.object_selections.push(current_selection.clone());
|
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);
|
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 not previous selection, shrink to first child
|
||||||
if let Some(syntax) = doc.syntax() {
|
if let Some(syntax) = doc.syntax() {
|
||||||
let text = doc.text().slice(..);
|
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);
|
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)));
|
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) {
|
fn match_brackets(cx: &mut Context) {
|
||||||
let (view, doc) = current!(cx.editor);
|
let (view, doc) = current!(cx.editor);
|
||||||
|
|
||||||
|
|
|
@ -552,6 +552,11 @@ impl Default for Keymaps {
|
||||||
"S" => split_selection,
|
"S" => split_selection,
|
||||||
";" => collapse_selection,
|
";" => collapse_selection,
|
||||||
"A-;" => flip_selections,
|
"A-;" => flip_selections,
|
||||||
|
"A-k" => expand_selection,
|
||||||
|
"A-j" => shrink_selection,
|
||||||
|
"A-h" => select_prev_sibling,
|
||||||
|
"A-l" => select_next_sibling,
|
||||||
|
|
||||||
"%" => select_all,
|
"%" => select_all,
|
||||||
"x" => extend_line,
|
"x" => extend_line,
|
||||||
"X" => extend_to_line_bounds,
|
"X" => extend_to_line_bounds,
|
||||||
|
@ -569,13 +574,11 @@ impl Default for Keymaps {
|
||||||
"d" => goto_prev_diag,
|
"d" => goto_prev_diag,
|
||||||
"D" => goto_first_diag,
|
"D" => goto_first_diag,
|
||||||
"space" => add_newline_above,
|
"space" => add_newline_above,
|
||||||
"o" => shrink_selection,
|
|
||||||
},
|
},
|
||||||
"]" => { "Right bracket"
|
"]" => { "Right bracket"
|
||||||
"d" => goto_next_diag,
|
"d" => goto_next_diag,
|
||||||
"D" => goto_last_diag,
|
"D" => goto_last_diag,
|
||||||
"space" => add_newline_below,
|
"space" => add_newline_below,
|
||||||
"o" => expand_selection,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"/" => search,
|
"/" => search,
|
||||||
|
|
Loading…
Reference in New Issue