-
-
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
add select_next_sibling and select_prev_sibling commands #1495
Conversation
Seemed interesting, do you find you using it frequently and needs to keep holding down alt? If that's the case something like our extend state would be nice, otherwise the keys you show are quite good already. Or maybe |
helix-core/src/object.rs
Outdated
selection.clone().transform(|range| { | ||
let from = text.char_to_byte(range.from()); | ||
let to = text.char_to_byte(range.to()); | ||
|
||
let sibling = match tree | ||
.root_node() | ||
.descendant_for_byte_range(from, to) | ||
.and_then(find_prev_sibling) | ||
{ | ||
Some(sibling) => sibling, | ||
None => return range, | ||
}; | ||
|
||
let from = text.byte_to_char(sibling.start_byte()); | ||
let to = text.byte_to_char(sibling.end_byte()); | ||
|
||
if range.head < range.anchor { | ||
Range::new(to, from) | ||
} else { | ||
Range::new(from, to) | ||
} | ||
}) |
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 whole thing seemed like it is duplicated, probably can reuse.
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.
Yep it's all duplicated. I tried out refactoring it to take the find_prev_sibling
/find_next_sibling
function as an argument but I couldn't figure out the borrowing rules. I'll keep looking into it 🤔
(ofc if you'd like to push a commit to cut down on that boilerplate, I'd be grateful 🙂)
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 figured out how to do higher order functions but now I think I may have overdone it 😄
Ah that's a good idea, an extended state would be great That's not something we can setup in the config toml currently, right? I'd love a built-in keybinding for that but idk how many others would find this way of doing motion useful |
Yeah, extended state is not possible with current config mechanism. Not sure if we want this also, I think as of now integrating to the editor is easier since cases like this is rare. cc @sudormrfbin |
(By extended state I assume you mean "sticky" mode, like in |
err yep, "sticky" I haven't really thought this through, but one way I'm thinking that could work: things like [keys.normal]
"T" = "enter_mode_once treejump" # not sticky
"A-t" = "enter_mode treejump" # sticky
[keys.treejump]
"j" = "shrink_selection"
"k" = "expand_selection"
"h" = "select_prev_sibling"
"l" = "select_next_sibling" but for this to work you'd need to be able to bind keys to commands that take arguments which I think is not currently possible, right? |
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.
Looks great to me! As for bindings, I just went with what I thought was consistent with the current expand/shrink bindings, but I personally plan to bind them to C-{h,j,k,l} in my own config. I find these extremely useful, so I want them almost as easily accessible as moving the cursor itself.
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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
^ this, was admiring exactly the same thing
helix-term/src/commands.rs
Outdated
} | ||
|
||
fn select_prev_sibling(cx: &mut Context) { | ||
let motion = |editor: &mut 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.
I tried to factor these too so it's not duplicated everywhere except the sibling call, but I couldn't figure out how to make the borrow checker happy. I tried making a fn(&Syntax, RopeSlice, &Selection)
argument, but it didn't like that the function argument gets captured by the motion closure.
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'm a little afraid of the borrow checker 😅, I'll give this a shot but I'm not optimistic
edit: it didn't go well 🙃
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.
Could do something like this:
helix/helix-term/src/commands.rs
Lines 488 to 535 in 5549acd
fn move_impl<F>(cx: &mut Context, move_fn: F, dir: Direction, behaviour: Movement) | |
where | |
F: Fn(RopeSlice, Range, Direction, usize, Movement) -> Range, | |
{ | |
let count = cx.count(); | |
let (view, doc) = current!(cx.editor); | |
let text = doc.text().slice(..); | |
let selection = doc | |
.selection(view.id) | |
.clone() | |
.transform(|range| move_fn(text, range, dir, count, behaviour)); | |
doc.set_selection(view.id, selection); | |
} | |
use helix_core::movement::{move_horizontally, move_vertically}; | |
fn move_char_left(cx: &mut Context) { | |
move_impl(cx, move_horizontally, Direction::Backward, Movement::Move) | |
} | |
fn move_char_right(cx: &mut Context) { | |
move_impl(cx, move_horizontally, Direction::Forward, Movement::Move) | |
} | |
fn move_line_up(cx: &mut Context) { | |
move_impl(cx, move_vertically, Direction::Backward, Movement::Move) | |
} | |
fn move_line_down(cx: &mut Context) { | |
move_impl(cx, move_vertically, Direction::Forward, Movement::Move) | |
} | |
fn extend_char_left(cx: &mut Context) { | |
move_impl(cx, move_horizontally, Direction::Backward, Movement::Extend) | |
} | |
fn extend_char_right(cx: &mut Context) { | |
move_impl(cx, move_horizontally, Direction::Forward, Movement::Extend) | |
} | |
fn extend_line_up(cx: &mut Context) { | |
move_impl(cx, move_vertically, Direction::Backward, Movement::Extend) | |
} | |
fn extend_line_down(cx: &mut Context) { | |
move_impl(cx, move_vertically, Direction::Forward, Movement::Extend) | |
} |
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.
Looks good to me, tested and it works.
yeah looks like |
Hmm, I wonder if we should not use |
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.
Looks good to me. @dead10ck what do you think about the key? If it's good then I guess I can merge this.
That seems fine to me. It seems consistent with the default bindings. I'm going to rebind it anyway, so I don't have big concerns about the default. |
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'd be okay with adding the Alt-
mappings and we can experiment with them in master
. It makes it easy to repeat without a sticky mode since you can just hold down Alt
helix-term/src/commands.rs
Outdated
} | ||
|
||
fn select_prev_sibling(cx: &mut Context) { | ||
let motion = |editor: &mut 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.
Could do something like this:
helix/helix-term/src/commands.rs
Lines 488 to 535 in 5549acd
fn move_impl<F>(cx: &mut Context, move_fn: F, dir: Direction, behaviour: Movement) | |
where | |
F: Fn(RopeSlice, Range, Direction, usize, Movement) -> Range, | |
{ | |
let count = cx.count(); | |
let (view, doc) = current!(cx.editor); | |
let text = doc.text().slice(..); | |
let selection = doc | |
.selection(view.id) | |
.clone() | |
.transform(|range| move_fn(text, range, dir, count, behaviour)); | |
doc.set_selection(view.id, selection); | |
} | |
use helix_core::movement::{move_horizontally, move_vertically}; | |
fn move_char_left(cx: &mut Context) { | |
move_impl(cx, move_horizontally, Direction::Backward, Movement::Move) | |
} | |
fn move_char_right(cx: &mut Context) { | |
move_impl(cx, move_horizontally, Direction::Forward, Movement::Move) | |
} | |
fn move_line_up(cx: &mut Context) { | |
move_impl(cx, move_vertically, Direction::Backward, Movement::Move) | |
} | |
fn move_line_down(cx: &mut Context) { | |
move_impl(cx, move_vertically, Direction::Forward, Movement::Move) | |
} | |
fn extend_char_left(cx: &mut Context) { | |
move_impl(cx, move_horizontally, Direction::Backward, Movement::Extend) | |
} | |
fn extend_char_right(cx: &mut Context) { | |
move_impl(cx, move_horizontally, Direction::Forward, Movement::Extend) | |
} | |
fn extend_line_up(cx: &mut Context) { | |
move_impl(cx, move_vertically, Direction::Backward, Movement::Extend) | |
} | |
fn extend_line_down(cx: &mut Context) { | |
move_impl(cx, move_vertically, Direction::Forward, Movement::Extend) | |
} |
a0d0129
to
198600c
Compare
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.
Nicely done with the refactor!
| `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` | |
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
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 comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can remove the &'static
and just leave F
. It seemed weird to pass the function as reference. In the meantime, I think we can just do select_sibling_impl(cx, Node::next_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.
the borrow checker gets upset here without the &
:
error[E0507]: cannot move out of `sibling_fn`, a captured variable in an `Fn` closure
--> helix-core/src/object.rs:30:44
|
24 | sibling_fn: F,
| ---------- captured outer variable
...
29 | select_node_impl(syntax, text, selection, |descendant, _from, _to| {
| _______________________________________________-
30 | | find_sibling_recursive(descendant, sibling_fn)
| | ^^^^^^^^^^ move occurs because `sibling_fn` has type `F`, which does not implement the `Copy` trait
31 | | })
| |_____- captured by this `Fn` closure
For more information about this error, try `rustc --explain E0507`.
and also if we only remove the 'static
lifetime bounds:
error[E0310]: the parameter type `F` may not live long enough
--> helix-term/src/commands.rs:5546:41
|
5530 | fn select_sibling_impl<F>(cx: &mut Context, sibling_fn: &F)
| - help: consider adding an explicit lifetime bound...: `F: 'static`
...
5546 | cx.editor.last_motion = Some(Motion(Box::new(motion)));
| ^^^^^^^^ ...so that the reference type `&F` does not outlive the data it points at
error[E0310]: the parameter type `F` may not live long enough
--> helix-term/src/commands.rs:5546:41
|
5530 | fn select_sibling_impl<F>(cx: &mut Context, sibling_fn: &F)
| - help: consider adding an explicit lifetime bound...: `F: 'static`
...
5546 | cx.editor.last_motion = Some(Motion(Box::new(motion)));
| ^^^^^^^^^^^^^^^^ ...so that the type `[closure@helix-term/src/commands.rs:5534:18: 5544:6]` will meet its required lifetime bounds
For more information about this error, try `rustc --explain E0310`.
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 comment
The reason will be displayed to describe this comment to others. Learn more.
From what I understand the 'static
bound is necessary for functions and there isn't such a thing as a non-'static
fn. I can't find a decent explainer article for it anymore though
} | ||
|
||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
select_sibling_impl(cx, &|node| Node::next_sibling(&node)) | |
select_sibling_impl(cx, Node::next_sibling) |
Does this compile?
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.
looks like the typing is slightly off, rustc wants a &Node::next_sibling
:
error[E0308]: mismatched types
--> helix-term/src/commands.rs:5550:29
|
5550 | select_sibling_impl(cx, Node::next_sibling)
| ^^^^^^^^^^^^^^^^^^
| |
| expected reference, found fn item
| help: consider borrowing here: `&Node::next_sibling`
|
= note: expected reference `&'static _`
found fn item `for<'r> fn(&'r helix_core::tree_sitter::Node<'_>) -> std::option::Option<helix_core::tree_sitter::Node<'_>> {helix_core::tree_sitter::Node::<'_>::next_sibling}`
error[E0308]: mismatched types
--> helix-term/src/commands.rs:5554:29
|
5554 | select_sibling_impl(cx, Node::prev_sibling)
| ^^^^^^^^^^^^^^^^^^
| |
| expected reference, found fn item
| help: consider borrowing here: `&Node::prev_sibling`
|
= note: expected reference `&'static _`
found fn item `for<'r> fn(&'r helix_core::tree_sitter::Node<'_>) -> std::option::Option<helix_core::tree_sitter::Node<'_>> {helix_core::tree_sitter::Node::<'_>::prev_sibling}`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `helix-term` due to 2 previous errors
But then doing &Node::next_sibling
:
error[E0631]: type mismatch in function arguments
--> helix-term/src/commands.rs:5550:29
|
5550 | select_sibling_impl(cx, &Node::next_sibling)
| ------------------- ^^^^^^^^^^^^^^^^^^^
| | |
| | expected signature of `for<'r> fn(helix_core::tree_sitter::Node<'r>) -> _`
| | found signature of `for<'r> fn(&'r helix_core::tree_sitter::Node<'_>) -> _`
| required by a bound introduced by this call
|
note: required by a bound in `select_sibling_impl`
--> helix-term/src/commands.rs:5532:8
|
5530 | fn select_sibling_impl<F>(cx: &mut Context, sibling_fn: &'static F)
| ------------------- required by a bound in this
5531 | where
5532 | F: Fn(Node) -> Option<Node>,
| ^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `select_sibling_impl`
builds on expand_selection and shrink_selection to move to next and previous sibling nodes in the syntax tree
mostly I'm interested in all four of those together an alternate means of motion, like so:
select-movement.mp4
Where I have
A-j
shrink selectionA-k
expand selectionA-h
select prev siblingA-l
select next siblingin my personal config. I don't know if these commands are worthy of a default keybind 🤔
What do you think?