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(command): select all siblings and children #7445

Merged
merged 4 commits into from
Apr 9, 2024
Merged
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
40 changes: 40 additions & 0 deletions helix-core/src/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,46 @@ pub fn select_next_sibling(syntax: &Syntax, text: RopeSlice, selection: Selectio
})
}

pub fn select_all_siblings(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
selection.transform_iter(|range| {
let mut cursor = syntax.walk();
let (from, to) = range.into_byte_range(text);
cursor.reset_to_byte_range(from, to);

if !cursor.goto_parent_with(|parent| parent.child_count() > 1) {
return vec![range].into_iter();
}

select_children(&mut cursor, text, range).into_iter()
})
}

pub fn select_all_children(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
selection.transform_iter(|range| {
let mut cursor = syntax.walk();
let (from, to) = range.into_byte_range(text);
cursor.reset_to_byte_range(from, to);
select_children(&mut cursor, text, range).into_iter()
})
}

fn select_children<'n>(
cursor: &'n mut TreeCursor<'n>,
text: RopeSlice,
range: Range,
) -> Vec<Range> {
let children = cursor
.named_children()
.map(|child| Range::from_node(child, text, range.direction()))
.collect::<Vec<_>>();

if !children.is_empty() {
children
} else {
vec![range]
}
}

pub fn select_prev_sibling(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
select_node_impl(syntax, text, selection, |cursor| {
while !cursor.goto_prev_sibling() {
Expand Down
17 changes: 16 additions & 1 deletion helix-core/src/selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::{
use helix_stdx::rope::{self, RopeSliceExt};
use smallvec::{smallvec, SmallVec};
use std::borrow::Cow;
use tree_sitter::Node;

/// A single selection range.
///
Expand Down Expand Up @@ -73,6 +74,12 @@ impl Range {
Self::new(head, head)
}

pub fn from_node(node: Node, text: RopeSlice, direction: Direction) -> Self {
let from = text.byte_to_char(node.start_byte());
let to = text.byte_to_char(node.end_byte());
Range::new(from, to).with_direction(direction)
}

/// Start of the range.
#[inline]
#[must_use]
Expand Down Expand Up @@ -376,6 +383,12 @@ impl Range {
let second = graphemes.next();
first.is_some() && second.is_none()
}

/// Converts this char range into an in order byte range, discarding
/// direction.
pub fn into_byte_range(&self, text: RopeSlice) -> (usize, usize) {
(text.char_to_byte(self.from()), text.char_to_byte(self.to()))
}
}

impl From<(usize, usize)> for Range {
Expand Down Expand Up @@ -783,7 +796,9 @@ pub fn split_on_newline(text: RopeSlice, selection: &Selection) -> Selection {
let mut start = sel_start;

for line in sel.slice(text).lines() {
let Some(line_ending) = get_line_ending(&line) else { break };
let Some(line_ending) = get_line_ending(&line) else {
break;
};
let line_end = start + line.len_chars();
// TODO: retain range direction
result.push(Range::new(start, line_end - line_ending.len_chars()));
Expand Down
118 changes: 111 additions & 7 deletions helix-core/src/syntax/tree_cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,19 @@ impl<'a> TreeCursor<'a> {
true
}

pub fn goto_parent_with<P>(&mut self, predicate: P) -> bool
where
P: Fn(&Node) -> bool,
{
while self.goto_parent() {
if predicate(&self.node()) {
return true;
}
}

false
}

/// Finds the injection layer that has exactly the same range as the given `range`.
fn layer_id_of_byte_range(&self, search_range: Range<usize>) -> Option<LayerId> {
let start_idx = self
Expand All @@ -102,7 +115,7 @@ impl<'a> TreeCursor<'a> {
.find_map(|range| (range.start == search_range.start).then_some(range.layer_id))
}

pub fn goto_first_child(&mut self) -> bool {
fn goto_first_child_impl(&mut self, named: bool) -> bool {
// Check if the current node's range is an exact injection layer range.
if let Some(layer_id) = self
.layer_id_of_byte_range(self.node().byte_range())
Expand All @@ -111,8 +124,16 @@ impl<'a> TreeCursor<'a> {
// Switch to the child layer.
self.current = layer_id;
self.cursor = self.layers[self.current].tree().root_node();
true
} else if let Some(child) = self.cursor.child(0) {
return true;
}

let child = if named {
self.cursor.named_child(0)
} else {
self.cursor.child(0)
};

if let Some(child) = child {
// Otherwise descend in the current tree.
self.cursor = child;
true
Expand All @@ -121,24 +142,60 @@ impl<'a> TreeCursor<'a> {
}
}

pub fn goto_next_sibling(&mut self) -> bool {
if let Some(sibling) = self.cursor.next_sibling() {
pub fn goto_first_child(&mut self) -> bool {
self.goto_first_child_impl(false)
}

pub fn goto_first_named_child(&mut self) -> bool {
self.goto_first_child_impl(true)
}

fn goto_next_sibling_impl(&mut self, named: bool) -> bool {
let sibling = if named {
self.cursor.next_named_sibling()
} else {
self.cursor.next_sibling()
};

if let Some(sibling) = sibling {
self.cursor = sibling;
true
} else {
false
}
}

pub fn goto_prev_sibling(&mut self) -> bool {
if let Some(sibling) = self.cursor.prev_sibling() {
pub fn goto_next_sibling(&mut self) -> bool {
self.goto_next_sibling_impl(false)
}

pub fn goto_next_named_sibling(&mut self) -> bool {
self.goto_next_sibling_impl(true)
}

fn goto_prev_sibling_impl(&mut self, named: bool) -> bool {
let sibling = if named {
self.cursor.prev_named_sibling()
} else {
self.cursor.prev_sibling()
};

if let Some(sibling) = sibling {
self.cursor = sibling;
true
} else {
false
}
}

pub fn goto_prev_sibling(&mut self) -> bool {
self.goto_prev_sibling_impl(false)
}

pub fn goto_prev_named_sibling(&mut self) -> bool {
self.goto_prev_sibling_impl(true)
}

/// Finds the injection layer that contains the given start-end range.
fn layer_id_containing_byte_range(&self, start: usize, end: usize) -> LayerId {
let start_idx = self
Expand All @@ -157,4 +214,51 @@ impl<'a> TreeCursor<'a> {
let root = self.layers[self.current].tree().root_node();
self.cursor = root.descendant_for_byte_range(start, end).unwrap_or(root);
}

/// Returns an iterator over the children of the node the TreeCursor is on
/// at the time this is called.
pub fn children(&'a mut self) -> ChildIter {
let parent = self.node();

ChildIter {
cursor: self,
parent,
named: false,
}
}

/// Returns an iterator over the named children of the node the TreeCursor is on
/// at the time this is called.
pub fn named_children(&'a mut self) -> ChildIter {
let parent = self.node();

ChildIter {
cursor: self,
parent,
named: true,
}
}
}

pub struct ChildIter<'n> {
cursor: &'n mut TreeCursor<'n>,
parent: Node<'n>,
named: bool,
}

impl<'n> Iterator for ChildIter<'n> {
type Item = Node<'n>;

fn next(&mut self) -> Option<Self::Item> {
// first iteration, just visit the first child
if self.cursor.node() == self.parent {
self.cursor
.goto_first_child_impl(self.named)
.then(|| self.cursor.node())
} else {
self.cursor
.goto_next_sibling_impl(self.named)
.then(|| self.cursor.node())
}
}
}
52 changes: 45 additions & 7 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use helix_core::{
textobject,
unicode::width::UnicodeWidthChar,
visual_offset_from_block, Deletion, LineEnding, Position, Range, Rope, RopeGraphemes,
RopeReader, RopeSlice, Selection, SmallVec, Tendril, Transaction,
RopeReader, RopeSlice, Selection, SmallVec, Syntax, Tendril, Transaction,
};
use helix_view::{
document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
Expand Down Expand Up @@ -438,8 +438,10 @@ impl MappableCommand {
reverse_selection_contents, "Reverse selections contents",
expand_selection, "Expand selection to parent syntax node",
shrink_selection, "Shrink selection to previously expanded syntax node",
select_next_sibling, "Select next sibling in syntax tree",
select_prev_sibling, "Select previous sibling in syntax tree",
select_next_sibling, "Select next sibling in the syntax tree",
select_prev_sibling, "Select previous sibling the in syntax tree",
select_all_siblings, "Select all siblings of the current node",
select_all_children, "Select all children of the current node",
jump_forward, "Jump forward on jumplist",
jump_backward, "Jump backward on jumplist",
save_selection, "Save current selection to jumplist",
Expand Down Expand Up @@ -4974,6 +4976,36 @@ pub fn extend_parent_node_start(cx: &mut Context) {
move_node_bound_impl(cx, Direction::Backward, Movement::Extend)
}

fn select_all_impl<F>(editor: &mut Editor, select_fn: F)
where
F: Fn(&Syntax, RopeSlice, Selection) -> Selection,
{
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 = select_fn(syntax, text, current_selection.clone());
doc.set_selection(view.id, selection);
}
}

fn select_all_siblings(cx: &mut Context) {
let motion = |editor: &mut Editor| {
select_all_impl(editor, object::select_all_siblings);
};

cx.editor.apply_motion(motion);
}

fn select_all_children(cx: &mut Context) {
let motion = |editor: &mut Editor| {
select_all_impl(editor, object::select_all_children);
};

cx.editor.apply_motion(motion);
}

fn match_brackets(cx: &mut Context) {
let (view, doc) = current!(cx.editor);
let is_select = cx.editor.mode == Mode::Select;
Expand Down Expand Up @@ -6005,7 +6037,10 @@ fn jump_to_label(cx: &mut Context, labels: Vec<Range>, behaviour: Movement) {
let doc = doc.id();
cx.on_next_key(move |cx, event| {
let alphabet = &cx.editor.config().jump_label_alphabet;
let Some(i ) = event.char().and_then(|ch| alphabet.iter().position(|&it| it == ch)) else {
let Some(i) = event
.char()
.and_then(|ch| alphabet.iter().position(|&it| it == ch))
else {
doc_mut!(cx.editor, &doc).remove_jump_labels(view);
return;
};
Expand All @@ -6018,7 +6053,10 @@ fn jump_to_label(cx: &mut Context, labels: Vec<Range>, behaviour: Movement) {
cx.on_next_key(move |cx, event| {
doc_mut!(cx.editor, &doc).remove_jump_labels(view);
let alphabet = &cx.editor.config().jump_label_alphabet;
let Some(inner ) = event.char().and_then(|ch| alphabet.iter().position(|&it| it == ch)) else {
let Some(inner) = event
.char()
.and_then(|ch| alphabet.iter().position(|&it| it == ch))
else {
return;
};
if let Some(mut range) = labels.get(outer + inner).copied() {
Expand All @@ -6038,8 +6076,8 @@ fn jump_to_label(cx: &mut Context, labels: Vec<Range>, behaviour: Movement) {
to
}
};
Range::new(anchor, range.head)
}else{
Range::new(anchor, range.head)
} else {
range.with_direction(Direction::Forward)
};
doc_mut!(cx.editor, &doc).set_selection(view, range.into());
Expand Down
2 changes: 2 additions & 0 deletions helix-term/src/keymap/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,12 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
"A-;" => flip_selections,
"A-o" | "A-up" => expand_selection,
"A-i" | "A-down" => shrink_selection,
"A-I" | "A-S-down" => select_all_children,
"A-p" | "A-left" => select_prev_sibling,
"A-n" | "A-right" => select_next_sibling,
"A-e" => move_parent_node_end,
"A-b" => move_parent_node_start,
"A-a" => select_all_siblings,

"%" => select_all,
"x" => extend_line_below,
Expand Down
Loading
Loading