mirror of https://github.com/helix-editor/helix
Add whole word selection commands
parent
ebf96bd469
commit
c312f0baff
|
@ -171,6 +171,15 @@ impl Range {
|
||||||
self.from() <= pos && pos < self.to()
|
self.from() <= pos && pos < self.to()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns equal if anchor and head are the same, and disregards old_visual_position. When
|
||||||
|
/// a single character is selected, the orientation (i.e. which is the head and which is the
|
||||||
|
/// anchor) is indistinguishable and should be disregarded.
|
||||||
|
pub fn visual_eq(&self, other: Range) -> bool {
|
||||||
|
self.anchor == other.anchor && self.head == other.head
|
||||||
|
// TODO: this does not work for graphemes like \r\n
|
||||||
|
|| self.len() == 1 && self.from() == other.from() && self.to() == other.to()
|
||||||
|
}
|
||||||
|
|
||||||
/// Map a range through a set of changes. Returns a new range representing
|
/// Map a range through a set of changes. Returns a new range representing
|
||||||
/// the same position after the changes are applied. Note that this
|
/// the same position after the changes are applied. Note that this
|
||||||
/// function runs in O(N) (N is number of changes) and can therefore
|
/// function runs in O(N) (N is number of changes) and can therefore
|
||||||
|
@ -700,6 +709,14 @@ impl Selection {
|
||||||
pub fn contains(&self, other: &Selection) -> bool {
|
pub fn contains(&self, other: &Selection) -> bool {
|
||||||
is_subset::<true>(self.range_bounds(), other.range_bounds())
|
is_subset::<true>(self.range_bounds(), other.range_bounds())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if two selections are identical as perceived by the user
|
||||||
|
pub fn visual_eq(&self, other: Selection) -> bool {
|
||||||
|
self.ranges()
|
||||||
|
.iter()
|
||||||
|
.zip(other.ranges().iter())
|
||||||
|
.all(|(&r1, &r2)| r1.visual_eq(r2))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IntoIterator for &'a Selection {
|
impl<'a> IntoIterator for &'a Selection {
|
||||||
|
|
|
@ -555,6 +555,10 @@ impl MappableCommand {
|
||||||
surround_delete, "Surround delete",
|
surround_delete, "Surround delete",
|
||||||
select_textobject_around, "Select around object",
|
select_textobject_around, "Select around object",
|
||||||
select_textobject_inner, "Select inside object",
|
select_textobject_inner, "Select inside object",
|
||||||
|
select_next_word, "Select next whole word",
|
||||||
|
select_next_long_word, "Select next whole long word",
|
||||||
|
select_prev_word, "Select previous whole word",
|
||||||
|
select_prev_long_word, "Select previous whole long word",
|
||||||
goto_next_function, "Goto next function",
|
goto_next_function, "Goto next function",
|
||||||
goto_prev_function, "Goto previous function",
|
goto_prev_function, "Goto previous function",
|
||||||
goto_next_class, "Goto next type definition",
|
goto_next_class, "Goto next type definition",
|
||||||
|
@ -5882,6 +5886,39 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
|
||||||
cx.on_next_key(move |cx, event| {
|
cx.on_next_key(move |cx, event| {
|
||||||
cx.editor.autoinfo = None;
|
cx.editor.autoinfo = None;
|
||||||
if let Some(ch) = event.char() {
|
if let Some(ch) = event.char() {
|
||||||
|
select_textobject_for_char(cx, ch, objtype, count);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let title = match objtype {
|
||||||
|
textobject::TextObject::Inside => "Match inside",
|
||||||
|
textobject::TextObject::Around => "Match around",
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
let help_text = [
|
||||||
|
("w", "Word"),
|
||||||
|
("W", "WORD"),
|
||||||
|
("p", "Paragraph"),
|
||||||
|
("t", "Type definition (tree-sitter)"),
|
||||||
|
("f", "Function (tree-sitter)"),
|
||||||
|
("a", "Argument/parameter (tree-sitter)"),
|
||||||
|
("c", "Comment (tree-sitter)"),
|
||||||
|
("T", "Test (tree-sitter)"),
|
||||||
|
("e", "Data structure entry (tree-sitter)"),
|
||||||
|
("m", "Closest surrounding pair (tree-sitter)"),
|
||||||
|
("g", "Change"),
|
||||||
|
(" ", "... or any character acting as a pair"),
|
||||||
|
];
|
||||||
|
|
||||||
|
cx.editor.autoinfo = Some(Info::new(title, &help_text));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_for_char(
|
||||||
|
cx: &mut Context,
|
||||||
|
ch: char,
|
||||||
|
objtype: textobject::TextObject,
|
||||||
|
count: usize,
|
||||||
|
) {
|
||||||
let textobject = move |editor: &mut Editor| {
|
let textobject = move |editor: &mut Editor| {
|
||||||
let (view, doc) = current!(editor);
|
let (view, doc) = current!(editor);
|
||||||
let loader = editor.syn_loader.load();
|
let loader = editor.syn_loader.load();
|
||||||
|
@ -5951,30 +5988,55 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
|
||||||
doc.set_selection(view.id, selection);
|
doc.set_selection(view.id, selection);
|
||||||
};
|
};
|
||||||
cx.editor.apply_motion(textobject);
|
cx.editor.apply_motion(textobject);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_next_word(cx: &mut Context) {
|
||||||
|
let current_selection = get_current_selection(cx);
|
||||||
|
select_textobject_for_char(cx, 'w', textobject::TextObject::Inside, cx.count());
|
||||||
|
let new_selection = get_current_selection(cx);
|
||||||
|
if current_selection.visual_eq(new_selection) {
|
||||||
|
move_next_word_end(cx);
|
||||||
|
select_textobject_for_char(cx, 'w', textobject::TextObject::Inside, cx.count());
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
let title = match objtype {
|
fn select_next_long_word(cx: &mut Context) {
|
||||||
textobject::TextObject::Inside => "Match inside",
|
let current_selection = get_current_selection(cx);
|
||||||
textobject::TextObject::Around => "Match around",
|
select_textobject_for_char(cx, 'W', textobject::TextObject::Inside, cx.count());
|
||||||
_ => return,
|
let new_selection = get_current_selection(cx);
|
||||||
};
|
if current_selection.visual_eq(new_selection) {
|
||||||
let help_text = [
|
move_next_long_word_end(cx);
|
||||||
("w", "Word"),
|
select_textobject_for_char(cx, 'W', textobject::TextObject::Inside, cx.count());
|
||||||
("W", "WORD"),
|
}
|
||||||
("p", "Paragraph"),
|
}
|
||||||
("t", "Type definition (tree-sitter)"),
|
|
||||||
("f", "Function (tree-sitter)"),
|
|
||||||
("a", "Argument/parameter (tree-sitter)"),
|
|
||||||
("c", "Comment (tree-sitter)"),
|
|
||||||
("T", "Test (tree-sitter)"),
|
|
||||||
("e", "Data structure entry (tree-sitter)"),
|
|
||||||
("m", "Closest surrounding pair (tree-sitter)"),
|
|
||||||
("g", "Change"),
|
|
||||||
(" ", "... or any character acting as a pair"),
|
|
||||||
];
|
|
||||||
|
|
||||||
cx.editor.autoinfo = Some(Info::new(title, &help_text));
|
fn select_prev_word(cx: &mut Context) {
|
||||||
|
let current_selection = get_current_selection(cx);
|
||||||
|
select_textobject_for_char(cx, 'w', textobject::TextObject::Inside, cx.count());
|
||||||
|
flip_selections(cx);
|
||||||
|
let new_selection = get_current_selection(cx);
|
||||||
|
if current_selection.visual_eq(new_selection) {
|
||||||
|
move_prev_word_start(cx);
|
||||||
|
select_textobject_for_char(cx, 'w', textobject::TextObject::Inside, cx.count());
|
||||||
|
flip_selections(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_prev_long_word(cx: &mut Context) {
|
||||||
|
let current_selection = get_current_selection(cx);
|
||||||
|
select_textobject_for_char(cx, 'W', textobject::TextObject::Inside, cx.count());
|
||||||
|
flip_selections(cx);
|
||||||
|
let new_selection = get_current_selection(cx);
|
||||||
|
if current_selection.visual_eq(new_selection) {
|
||||||
|
move_prev_long_word_start(cx);
|
||||||
|
select_textobject_for_char(cx, 'W', textobject::TextObject::Inside, cx.count());
|
||||||
|
flip_selections(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_current_selection(cx: &mut Context) -> Selection {
|
||||||
|
let (view, doc) = current!(cx.editor);
|
||||||
|
doc.selection(view.id).clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
static SURROUND_HELP_TEXT: [(&str, &str); 6] = [
|
static SURROUND_HELP_TEXT: [(&str, &str); 6] = [
|
||||||
|
|
Loading…
Reference in New Issue