aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCossonLeo2021-10-24 13:47:10 +0000
committerGitHub2021-10-24 13:47:10 +0000
commit2ed01f2d9c8c98a7e59fef1bb19af56ec897084b (patch)
tree24745466e00640e7f3ecf2eddba6cac2fee39c97
parentcee7ad781e5f6de249d728425a6283a26bb62dc3 (diff)
find motion and textobj motion repeat (#891)
-rw-r--r--helix-term/src/commands.rs210
-rw-r--r--helix-term/src/keymap.rs1
-rw-r--r--helix-view/src/editor.rs14
3 files changed, 122 insertions, 103 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 07485f9f..c698c641 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -12,8 +12,13 @@ use helix_core::{
};
use helix_view::{
- clipboard::ClipboardType, document::Mode, editor::Action, input::KeyEvent, keyboard::KeyCode,
- view::View, Document, DocumentId, Editor, ViewId,
+ clipboard::ClipboardType,
+ document::Mode,
+ editor::{Action, Motion},
+ input::KeyEvent,
+ keyboard::KeyCode,
+ view::View,
+ Document, DocumentId, Editor, ViewId,
};
use anyhow::{anyhow, bail, Context as _};
@@ -198,6 +203,7 @@ impl Command {
find_prev_char, "Move to previous occurance of char",
extend_till_prev_char, "Extend till 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",
switch_case, "Switch (toggle) case",
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)
}
-#[inline]
-fn find_char_impl<F>(cx: &mut Context, search_fn: F, inclusive: bool, extend: bool)
+fn will_find_char<F>(cx: &mut Context, search_fn: F, inclusive: bool, extend: bool)
where
F: Fn(RopeSlice, char, usize, usize, bool) -> Option<usize> + 'static,
{
@@ -705,29 +710,48 @@ where
_ => return,
};
- let (view, doc) = current!(cx.editor);
- let text = doc.text().slice(..);
+ find_char_impl(cx.editor, &search_fn, inclusive, extend, ch, count);
+ 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| {
- 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);
- })
+#[inline]
+fn find_char_impl<F>(
+ editor: &mut Editor,
+ search_fn: &F,
+ inclusive: bool,
+ extend: bool,
+ ch: char,
+ count: usize,
+) 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(
@@ -741,6 +765,10 @@ fn find_next_char_impl(
if inclusive {
search::find_nth_next(text, ch, pos, n)
} 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))
}
}
@@ -755,80 +783,52 @@ fn find_prev_char_impl(
if inclusive {
search::find_nth_prev(text, ch, pos, n)
} 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()))
}
}
fn find_till_char(cx: &mut Context) {
- find_char_impl(
- cx,
- find_next_char_impl,
- false, /* inclusive */
- false, /* extend */
- )
+ will_find_char(cx, find_next_char_impl, false, false)
}
fn find_next_char(cx: &mut Context) {
- find_char_impl(
- cx,
- find_next_char_impl,
- true, /* inclusive */
- false, /* extend */
- )
+ will_find_char(cx, find_next_char_impl, true, false)
}
fn extend_till_char(cx: &mut Context) {
- find_char_impl(
- cx,
- find_next_char_impl,
- false, /* inclusive */
- true, /* extend */
- )
+ will_find_char(cx, find_next_char_impl, false, true)
}
fn extend_next_char(cx: &mut Context) {
- find_char_impl(
- cx,
- find_next_char_impl,
- true, /* inclusive */
- true, /* extend */
- )
+ will_find_char(cx, find_next_char_impl, true, true)
}
fn till_prev_char(cx: &mut Context) {
- find_char_impl(
- cx,
- find_prev_char_impl,
- false, /* inclusive */
- false, /* extend */
- )
+ will_find_char(cx, find_prev_char_impl, false, false)
}
fn find_prev_char(cx: &mut Context) {
- find_char_impl(
- cx,
- find_prev_char_impl,
- true, /* inclusive */
- false, /* extend */
- )
+ will_find_char(cx, find_prev_char_impl, true, false)
}
fn extend_till_prev_char(cx: &mut Context) {
- find_char_impl(
- cx,
- find_prev_char_impl,
- false, /* inclusive */
- true, /* extend */
- )
+ will_find_char(cx, find_prev_char_impl, false, true)
}
fn extend_prev_char(cx: &mut Context) {
- find_char_impl(
- cx,
- find_prev_char_impl,
- true, /* inclusive */
- true, /* extend */
- )
+ will_find_char(cx, find_prev_char_impl, true, true)
+}
+
+fn repeat_last_motion(cx: &mut Context) {
+ 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) {
@@ -4495,39 +4495,43 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
let count = cx.count();
cx.on_next_key(move |cx, event| {
if let Some(ch) = event.char() {
- let (view, doc) = current!(cx.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()) {
- Some(t) => t,
- None => return range,
+ let textobject = move |editor: &mut Editor| {
+ 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()) {
+ 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| {
- match ch {
- 'w' => textobject::textobject_word(text, range, objtype, count),
- 'c' => textobject_treesitter("class", range),
- 'f' => textobject_treesitter("function", range),
- 'p' => textobject_treesitter("parameter", range),
- // TODO: cancel new ranges if inconsistent surround matches across lines
- ch if !ch.is_ascii_alphanumeric() => {
- textobject::textobject_surround(text, range, objtype, ch, count)
+ let selection = doc.selection(view.id).clone().transform(|range| {
+ match ch {
+ 'w' => textobject::textobject_word(text, range, objtype, count),
+ 'c' => textobject_treesitter("class", range),
+ 'f' => textobject_treesitter("function", range),
+ 'p' => textobject_treesitter("parameter", range),
+ // TODO: cancel new ranges if inconsistent surround matches across lines
+ ch if !ch.is_ascii_alphanumeric() => {
+ 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)));
}
})
}
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs
index f877387c..495fe892 100644
--- a/helix-term/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -395,6 +395,7 @@ impl Default for Keymaps {
"F" => find_prev_char,
"r" => replace,
"R" => replace_with_yanked,
+ "A-." => repeat_last_motion,
"~" => switch_case,
"`" => switch_to_lowercase,
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 51fe8a42..09fc3334 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -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)]
pub struct Editor {
pub tree: Tree,
@@ -112,6 +124,7 @@ pub struct Editor {
pub config: Config,
pub idle_timer: Pin<Box<Sleep>>,
+ pub last_motion: Option<Motion>,
}
#[derive(Debug, Copy, Clone)]
@@ -147,6 +160,7 @@ impl Editor {
clipboard_provider: get_clipboard_provider(),
status_msg: None,
idle_timer: Box::pin(sleep(config.idle_timeout)),
+ last_motion: None,
config,
}
}