Skip to content
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

feat(commands): sibling selections #1506

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 63 additions & 22 deletions helix-core/src/object.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
use crate::{Range, RopeSlice, Selection, Syntax};

pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
let tree = syntax.tree();

selection.clone().transform(|range| {
pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
selection.transform(|range| {
let from = text.char_to_byte(range.from());
let to = text.char_to_byte(range.to());

// 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)
}
}) {
let parent = match node_under_range(syntax, text, &range).and_then(|node| {
if node.start_byte() == from && node.end_byte() == to {
node.parent()
} else {
Some(node)
}
}) {
Some(parent) => parent,
None => return range,
};
Expand All @@ -33,14 +28,9 @@ pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection)
})
}

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) {
pub fn shrink_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
selection.transform(|range| {
let descendant = match node_under_range(syntax, text, &range) {
// find first child, if not possible, fallback to the node that contains selection
Some(descendant) => match descendant.child(0) {
Some(child) => child,
Expand All @@ -59,3 +49,54 @@ pub fn shrink_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection)
}
})
}

#[inline(always)]
pub fn next_sibling_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
transform_node_under_selection(syntax, text, selection, tree_sitter::Node::next_sibling)
}

#[inline(always)]
pub fn prev_sibling_selection(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
transform_node_under_selection(syntax, text, selection, tree_sitter::Node::prev_sibling)
}

fn transform_node_under_selection<'a, F>(
syntax: &'a Syntax,
text: RopeSlice,
selection: Selection,
f: F,
) -> Selection
where
F: Fn(&tree_sitter::Node<'a>) -> Option<tree_sitter::Node<'a>>,
{
selection.transform(|range| {
let next_sibling = match node_under_range(syntax, text, &range) {
// find first child, if not possible, fallback to the node that contains selection
Some(descendant) => match f(&descendant) {
Some(sib) => sib,
None => return range,
},
None => return range,
};

let from = text.byte_to_char(next_sibling.start_byte());
let to = text.byte_to_char(next_sibling.end_byte());

if range.head < range.anchor {
Range::new(to, from)
} else {
Range::new(from, to)
}
})
}

pub fn node_under_range<'a>(
syntax: &'a Syntax,
text: RopeSlice,
range: &Range,
) -> Option<tree_sitter::Node<'a>> {
let tree = syntax.tree();
let from = text.char_to_byte(range.from());
let to = text.char_to_byte(range.to());
tree.root_node().descendant_for_byte_range(from, to)
}
36 changes: 34 additions & 2 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,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",
next_sibling_selection, "Move selection to next sibling syntax node",
prev_sibling_selection, "Move selection to previous sibling syntax node",
jump_forward, "Jump forward on jumplist",
jump_backward, "Jump backward on jumplist",
save_selection, "Save the current selection to the jumplist",
Expand Down Expand Up @@ -5490,7 +5492,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);
}
};
Expand All @@ -5516,7 +5518,37 @@ 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 next_sibling_selection(cx: &mut Context) {
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::next_sibling_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 prev_sibling_selection(cx: &mut Context) {
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::prev_sibling_selection(syntax, text, current_selection.clone());
doc.set_selection(view.id, selection);
}
};
Expand Down
2 changes: 2 additions & 0 deletions helix-term/src/keymap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -570,12 +570,14 @@ impl Default for Keymaps {
"D" => goto_first_diag,
"space" => add_newline_above,
"o" => shrink_selection,
"s" => prev_sibling_selection,
},
"]" => { "Right bracket"
"d" => goto_next_diag,
"D" => goto_last_diag,
"space" => add_newline_below,
"o" => expand_selection,
"s" => next_sibling_selection,
},

"/" => search,
Expand Down