-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add select_next_sibling and select_prev_sibling commands #1495
Changes from all commits
855a0de
7f56104
807b64f
0c74d4e
d1df47f
5549acd
198600c
b337a5b
1ce524b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice idea to recurse up the tree until you find a sibling. 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ^ this, was admiring exactly the same thing |
||
.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) | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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", | ||||||
|
@@ -5490,7 +5493,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); | ||||||
} | ||||||
}; | ||||||
|
@@ -5516,14 +5519,41 @@ 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); | ||||||
} | ||||||
}; | ||||||
motion(cx.editor); | ||||||
cx.editor.last_motion = Some(Motion(Box::new(motion))); | ||||||
} | ||||||
|
||||||
fn select_sibling_impl<F>(cx: &mut Context, sibling_fn: &'static F) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the borrow checker gets upset here without the
and also if we only remove the
Is there a way to make the borrow checker happy in these cases? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From what I understand the |
||||||
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)) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Does this compile? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looks like the typing is slightly off, rustc wants a
But then doing
|
||||||
} | ||||||
|
||||||
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); | ||||||
|
||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seemed a bit weird to me, shouldn't it be hl to expand/shrink and hj to navigate between sibling?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense to me. If you picture the tree vertically, with expand, you are going up the tree, shrink you are going down. Next sibling is moving laterally to the right, and previous is left.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think about it the same way ☝️
I could see it going the other way too because often
A-l
will move your selection downwards (to the next function block for example), but that depends on the size of the selection (e.g.A-l
will move right in a function's argument list if it's all on one line) which can be a little confusing