Add surround keybinds

pull/368/head
Gokul Soumya 3 years ago committed by Blaž Hrastnik
parent 2f321b9335
commit 13648d28b9

@ -16,6 +16,7 @@ pub mod register;
pub mod search; pub mod search;
pub mod selection; pub mod selection;
mod state; mod state;
pub mod surround;
pub mod syntax; pub mod syntax;
mod transaction; mod transaction;

@ -0,0 +1,58 @@
use crate::{search, Selection};
use ropey::RopeSlice;
pub const PAIRS: &[(char, char)] = &[('(', ')'), ('[', ']'), ('{', '}'), ('<', '>')];
/// Given any char in [PAIRS], return the open and closing chars. If not found in
/// [PAIRS] return (ch, ch).
pub fn get_pair(ch: char) -> (char, char) {
PAIRS
.iter()
.find(|(open, close)| *open == ch || *close == ch)
.copied()
.unwrap_or((ch, ch))
}
/// Find the position of surround pairs of `ch` which can be either a closing
/// or opening pair. `n` will skip n - 1 pairs (eg. n=2 will discard (only)
/// the first pair found and keep looking)
pub fn find_nth_pairs_pos(
text: RopeSlice,
ch: char,
pos: usize,
n: usize,
) -> Option<(usize, usize)> {
let (open, close) = get_pair(ch);
let open_pos = search::find_nth_prev(text, open, pos, n, true)?;
let close_pos = search::find_nth_next(text, close, pos, n, true)?;
Some((open_pos, close_pos))
}
/// Find position of surround characters around every cursor. Returns None
/// if any positions overlap. Note that the positions are in a flat Vec.
/// Use get_surround_pos().chunks(2) to get matching pairs of surround positions.
/// `ch` can be either closing or opening pair.
pub fn get_surround_pos(
text: RopeSlice,
selection: &Selection,
ch: char,
skip: usize,
) -> Option<Vec<usize>> {
let mut change_pos = Vec::new();
for range in selection {
let head = range.head;
match find_nth_pairs_pos(text, ch, head, skip) {
Some((open_pos, close_pos)) => {
if change_pos.contains(&open_pos) || change_pos.contains(&close_pos) {
return None;
}
change_pos.extend_from_slice(&[open_pos, close_pos]);
}
None => return None,
}
}
Some(change_pos)
}

@ -255,7 +255,8 @@ impl Command {
space_mode, space_mode,
view_mode, view_mode,
left_bracket_mode, left_bracket_mode,
right_bracket_mode right_bracket_mode,
surround
); );
} }
@ -1906,6 +1907,7 @@ fn goto_mode(cx: &mut Context) {
match (doc.mode, ch) { match (doc.mode, ch) {
(_, 'g') => move_file_start(cx), (_, 'g') => move_file_start(cx),
(_, 'e') => move_file_end(cx), (_, 'e') => move_file_end(cx),
(_, 'm') => match_brackets(cx),
(_, 'a') => switch_to_last_accessed_file(cx), (_, 'a') => switch_to_last_accessed_file(cx),
(Mode::Normal, 'h') => move_line_start(cx), (Mode::Normal, 'h') => move_line_start(cx),
(Mode::Normal, 'l') => move_line_end(cx), (Mode::Normal, 'l') => move_line_end(cx),
@ -3311,6 +3313,122 @@ fn right_bracket_mode(cx: &mut Context) {
}) })
} }
fn surround(cx: &mut Context) {
let count = cx.count;
cx.on_next_key(move |cx, event| {
if let KeyEvent {
code: KeyCode::Char(ch),
..
} = event
{
// FIXME: count gets reset because of cx.on_next_key()
cx.count = count;
match ch {
'a' => surround_add(cx),
'r' => surround_replace(cx),
'd' => {
surround_delete(cx);
let (view, doc) = current!(cx.editor);
}
_ => (),
}
}
})
}
use helix_core::surround;
fn surround_add(cx: &mut Context) {
cx.on_next_key(move |cx, event| {
if let KeyEvent {
code: KeyCode::Char(ch),
..
} = event
{
let (view, doc) = current!(cx.editor);
let text = doc.text().slice(..);
let selection = doc.selection(view.id);
let (open, close) = surround::get_pair(ch);
let mut changes = Vec::new();
for (i, range) in selection.iter().enumerate() {
let (from, to) = (range.from(), range.to() + 1);
changes.push((from, from, Some(Tendril::from_char(open))));
changes.push((to, to, Some(Tendril::from_char(close))));
}
let transaction = Transaction::change(doc.text(), changes.into_iter());
doc.apply(&transaction, view.id);
doc.append_changes_to_history(view.id);
}
})
}
fn surround_replace(cx: &mut Context) {
let count = cx.count();
cx.on_next_key(move |cx, event| {
if let KeyEvent {
code: KeyCode::Char(from),
..
} = event
{
cx.on_next_key(move |cx, event| {
if let KeyEvent {
code: KeyCode::Char(to),
..
} = event
{
let (view, doc) = current!(cx.editor);
let text = doc.text().slice(..);
let selection = doc.selection(view.id);
let change_pos = match surround::get_surround_pos(text, selection, from, count)
{
Some(c) => c,
None => return,
};
let (open, close) = surround::get_pair(to);
let transaction = Transaction::change(
doc.text(),
change_pos.iter().enumerate().map(|(i, &pos)| {
let ch = if i % 2 == 0 { open } else { close };
(pos, pos + 1, Some(Tendril::from_char(ch)))
}),
);
doc.apply(&transaction, view.id);
doc.append_changes_to_history(view.id);
}
});
}
})
}
fn surround_delete(cx: &mut Context) {
let count = cx.count();
cx.on_next_key(move |cx, event| {
if let KeyEvent {
code: KeyCode::Char(ch),
..
} = event
{
let (view, doc) = current!(cx.editor);
let text = doc.text().slice(..);
let selection = doc.selection(view.id);
let change_pos = match surround::get_surround_pos(text, selection, ch, count) {
Some(c) => c,
None => return,
};
let transaction =
Transaction::change(doc.text(), change_pos.into_iter().map(|p| (p, p + 1, None)));
doc.apply(&transaction, view.id);
doc.append_changes_to_history(view.id);
}
})
}
impl fmt::Display for Command { impl fmt::Display for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Command(name, _) = self; let Command(name, _) = self;

@ -200,7 +200,7 @@ impl Default for Keymaps {
// extend_to_whole_line, crop_to_whole_line // extend_to_whole_line, crop_to_whole_line
key!('m') => Command::match_brackets, key!('m') => Command::surround,
// TODO: refactor into // TODO: refactor into
// key!('m') => commands::select_to_matching, // key!('m') => commands::select_to_matching,
// key!('M') => commands::back_select_to_matching, // key!('M') => commands::back_select_to_matching,

@ -649,6 +649,7 @@ impl Document {
} }
} }
/// Commit pending changes to history
pub fn append_changes_to_history(&mut self, view_id: ViewId) { pub fn append_changes_to_history(&mut self, view_id: ViewId) {
if self.changes.is_empty() { if self.changes.is_empty() {
return; return;

Loading…
Cancel
Save