find motion and textobj motion repeat (#891)

pull/902/head
CossonLeo 3 years ago committed by GitHub
parent cee7ad781e
commit 2ed01f2d9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -12,8 +12,13 @@ use helix_core::{
}; };
use helix_view::{ use helix_view::{
clipboard::ClipboardType, document::Mode, editor::Action, input::KeyEvent, keyboard::KeyCode, clipboard::ClipboardType,
view::View, Document, DocumentId, Editor, ViewId, document::Mode,
editor::{Action, Motion},
input::KeyEvent,
keyboard::KeyCode,
view::View,
Document, DocumentId, Editor, ViewId,
}; };
use anyhow::{anyhow, bail, Context as _}; use anyhow::{anyhow, bail, Context as _};
@ -198,6 +203,7 @@ impl Command {
find_prev_char, "Move to previous occurance of char", find_prev_char, "Move to previous occurance of char",
extend_till_prev_char, "Extend till previous occurance of char", extend_till_prev_char, "Extend till previous occurance of char",
extend_prev_char, "Extend to previous occurance of char", extend_prev_char, "Extend to previous occurance of char",
repeat_last_motion, "repeat last motion(extend_next_char, extend_till_char, find_next_char, find_till_char...)",
replace, "Replace with new char", replace, "Replace with new char",
switch_case, "Switch (toggle) case", switch_case, "Switch (toggle) case",
switch_to_uppercase, "Switch to uppercase", switch_to_uppercase, "Switch to uppercase",
@ -666,8 +672,7 @@ fn extend_next_long_word_end(cx: &mut Context) {
extend_word_impl(cx, movement::move_next_long_word_end) extend_word_impl(cx, movement::move_next_long_word_end)
} }
#[inline] fn will_find_char<F>(cx: &mut Context, search_fn: F, inclusive: bool, extend: bool)
fn find_char_impl<F>(cx: &mut Context, search_fn: F, inclusive: bool, extend: bool)
where where
F: Fn(RopeSlice, char, usize, usize, bool) -> Option<usize> + 'static, F: Fn(RopeSlice, char, usize, usize, bool) -> Option<usize> + 'static,
{ {
@ -705,29 +710,48 @@ where
_ => return, _ => return,
}; };
let (view, doc) = current!(cx.editor); find_char_impl(cx.editor, &search_fn, inclusive, extend, ch, count);
let text = doc.text().slice(..); cx.editor.last_motion = Some(Motion(Box::new(move |editor: &mut Editor| {
find_char_impl(editor, &search_fn, inclusive, true, ch, 1);
})));
})
}
let selection = doc.selection(view.id).clone().transform(|range| { //
// TODO: use `Range::cursor()` here instead. However, that works in terms of
// graphemes, whereas this function doesn't yet. So we're doing the same logic
// here, but just in terms of chars instead.
let search_start_pos = if range.anchor < range.head {
range.head - 1
} else {
range.head
};
search_fn(text, ch, search_start_pos, count, inclusive).map_or(range, |pos| { #[inline]
if extend { fn find_char_impl<F>(
range.put_cursor(text, pos, true) editor: &mut Editor,
} else { search_fn: &F,
Range::point(range.cursor(text)).put_cursor(text, pos, true) inclusive: bool,
} extend: bool,
}) ch: char,
}); count: usize,
doc.set_selection(view.id, selection); ) where
}) F: Fn(RopeSlice, char, usize, usize, bool) -> Option<usize> + 'static,
{
let (view, doc) = current!(editor);
let text = doc.text().slice(..);
let selection = doc.selection(view.id).clone().transform(|range| {
// TODO: use `Range::cursor()` here instead. However, that works in terms of
// graphemes, whereas this function doesn't yet. So we're doing the same logic
// here, but just in terms of chars instead.
let search_start_pos = if range.anchor < range.head {
range.head - 1
} else {
range.head
};
search_fn(text, ch, search_start_pos, count, inclusive).map_or(range, |pos| {
if extend {
range.put_cursor(text, pos, true)
} else {
Range::point(range.cursor(text)).put_cursor(text, pos, true)
}
})
});
doc.set_selection(view.id, selection);
} }
fn find_next_char_impl( fn find_next_char_impl(
@ -741,6 +765,10 @@ fn find_next_char_impl(
if inclusive { if inclusive {
search::find_nth_next(text, ch, pos, n) search::find_nth_next(text, ch, pos, n)
} else { } else {
let n = match text.get_char(pos) {
Some(next_ch) if next_ch == ch => n + 1,
_ => n,
};
search::find_nth_next(text, ch, pos, n).map(|n| n.saturating_sub(1)) search::find_nth_next(text, ch, pos, n).map(|n| n.saturating_sub(1))
} }
} }
@ -755,80 +783,52 @@ fn find_prev_char_impl(
if inclusive { if inclusive {
search::find_nth_prev(text, ch, pos, n) search::find_nth_prev(text, ch, pos, n)
} else { } else {
let n = match text.get_char(pos.saturating_sub(1)) {
Some(next_ch) if next_ch == ch => n + 1,
_ => n,
};
search::find_nth_prev(text, ch, pos, n).map(|n| (n + 1).min(text.len_chars())) search::find_nth_prev(text, ch, pos, n).map(|n| (n + 1).min(text.len_chars()))
} }
} }
fn find_till_char(cx: &mut Context) { fn find_till_char(cx: &mut Context) {
find_char_impl( will_find_char(cx, find_next_char_impl, false, false)
cx,
find_next_char_impl,
false, /* inclusive */
false, /* extend */
)
} }
fn find_next_char(cx: &mut Context) { fn find_next_char(cx: &mut Context) {
find_char_impl( will_find_char(cx, find_next_char_impl, true, false)
cx,
find_next_char_impl,
true, /* inclusive */
false, /* extend */
)
} }
fn extend_till_char(cx: &mut Context) { fn extend_till_char(cx: &mut Context) {
find_char_impl( will_find_char(cx, find_next_char_impl, false, true)
cx,
find_next_char_impl,
false, /* inclusive */
true, /* extend */
)
} }
fn extend_next_char(cx: &mut Context) { fn extend_next_char(cx: &mut Context) {
find_char_impl( will_find_char(cx, find_next_char_impl, true, true)
cx,
find_next_char_impl,
true, /* inclusive */
true, /* extend */
)
} }
fn till_prev_char(cx: &mut Context) { fn till_prev_char(cx: &mut Context) {
find_char_impl( will_find_char(cx, find_prev_char_impl, false, false)
cx,
find_prev_char_impl,
false, /* inclusive */
false, /* extend */
)
} }
fn find_prev_char(cx: &mut Context) { fn find_prev_char(cx: &mut Context) {
find_char_impl( will_find_char(cx, find_prev_char_impl, true, false)
cx,
find_prev_char_impl,
true, /* inclusive */
false, /* extend */
)
} }
fn extend_till_prev_char(cx: &mut Context) { fn extend_till_prev_char(cx: &mut Context) {
find_char_impl( will_find_char(cx, find_prev_char_impl, false, true)
cx,
find_prev_char_impl,
false, /* inclusive */
true, /* extend */
)
} }
fn extend_prev_char(cx: &mut Context) { fn extend_prev_char(cx: &mut Context) {
find_char_impl( will_find_char(cx, find_prev_char_impl, true, true)
cx, }
find_prev_char_impl,
true, /* inclusive */ fn repeat_last_motion(cx: &mut Context) {
true, /* extend */ let last_motion = cx.editor.last_motion.take();
) if let Some(m) = &last_motion {
m.run(cx.editor);
cx.editor.last_motion = last_motion;
}
} }
fn replace(cx: &mut Context) { fn replace(cx: &mut Context) {
@ -4495,39 +4495,43 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
let count = cx.count(); let count = cx.count();
cx.on_next_key(move |cx, event| { cx.on_next_key(move |cx, event| {
if let Some(ch) = event.char() { if let Some(ch) = event.char() {
let (view, doc) = current!(cx.editor); let textobject = move |editor: &mut Editor| {
let text = doc.text().slice(..); let (view, doc) = current!(editor);
let text = doc.text().slice(..);
let textobject_treesitter = |obj_name: &str, range: Range| -> Range {
let (lang_config, syntax) = match doc.language_config().zip(doc.syntax()) { let textobject_treesitter = |obj_name: &str, range: Range| -> Range {
Some(t) => t, let (lang_config, syntax) = match doc.language_config().zip(doc.syntax()) {
None => return range, Some(t) => t,
None => return range,
};
textobject::textobject_treesitter(
text,
range,
objtype,
obj_name,
syntax.tree().root_node(),
lang_config,
count,
)
}; };
textobject::textobject_treesitter(
text,
range,
objtype,
obj_name,
syntax.tree().root_node(),
lang_config,
count,
)
};
let selection = doc.selection(view.id).clone().transform(|range| { let selection = doc.selection(view.id).clone().transform(|range| {
match ch { match ch {
'w' => textobject::textobject_word(text, range, objtype, count), 'w' => textobject::textobject_word(text, range, objtype, count),
'c' => textobject_treesitter("class", range), 'c' => textobject_treesitter("class", range),
'f' => textobject_treesitter("function", range), 'f' => textobject_treesitter("function", range),
'p' => textobject_treesitter("parameter", range), 'p' => textobject_treesitter("parameter", range),
// TODO: cancel new ranges if inconsistent surround matches across lines // TODO: cancel new ranges if inconsistent surround matches across lines
ch if !ch.is_ascii_alphanumeric() => { ch if !ch.is_ascii_alphanumeric() => {
textobject::textobject_surround(text, range, objtype, ch, count) textobject::textobject_surround(text, range, objtype, ch, count)
}
_ => range,
} }
_ => range, });
} doc.set_selection(view.id, selection);
}); };
doc.set_selection(view.id, selection); textobject(&mut cx.editor);
cx.editor.last_motion = Some(Motion(Box::new(textobject)));
} }
}) })
} }

@ -395,6 +395,7 @@ impl Default for Keymaps {
"F" => find_prev_char, "F" => find_prev_char,
"r" => replace, "r" => replace,
"R" => replace_with_yanked, "R" => replace_with_yanked,
"A-." => repeat_last_motion,
"~" => switch_case, "~" => switch_case,
"`" => switch_to_lowercase, "`" => switch_to_lowercase,

@ -93,6 +93,18 @@ impl Default for Config {
} }
} }
pub struct Motion(pub Box<dyn Fn(&mut Editor)>);
impl Motion {
pub fn run(&self, e: &mut Editor) {
(self.0)(e)
}
}
impl std::fmt::Debug for Motion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("motion")
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct Editor { pub struct Editor {
pub tree: Tree, pub tree: Tree,
@ -112,6 +124,7 @@ pub struct Editor {
pub config: Config, pub config: Config,
pub idle_timer: Pin<Box<Sleep>>, pub idle_timer: Pin<Box<Sleep>>,
pub last_motion: Option<Motion>,
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@ -147,6 +160,7 @@ impl Editor {
clipboard_provider: get_clipboard_provider(), clipboard_provider: get_clipboard_provider(),
status_msg: None, status_msg: None,
idle_timer: Box::pin(sleep(config.idle_timeout)), idle_timer: Box::pin(sleep(config.idle_timeout)),
last_motion: None,
config, config,
} }
} }

Loading…
Cancel
Save