From 7c752dff4f6ec20f91ddeef8abf6879471612f4d Mon Sep 17 00:00:00 2001 From: etienne-k <2804556+etienne-k@users.noreply.github.com> Date: Fri, 31 Mar 2023 19:17:20 +0200 Subject: [PATCH] feat(commands): avoid selections in the normal/insert mode --- helix-term/src/commands.rs | 45 ++++++++++++++--------- helix-term/src/commands/evil.rs | 64 +++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 16 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 6a2d6a78b..153f8d34d 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4,8 +4,8 @@ pub(crate) mod lsp; pub(crate) mod typed; pub use dap::*; -use helix_vcs::Hunk; pub use evil::*; +use helix_vcs::Hunk; pub use lsp::*; use tokio::sync::oneshot; use tui::widgets::Row; @@ -2449,6 +2449,11 @@ fn ensure_selections_forward(cx: &mut Context) { } pub fn enter_insert_mode(cx: &mut Context) { + if EvilCommands::is_enabled() { + // In evil mode, selections are possible in the selection/visual mode only. + EvilCommands::collapse_selections(cx, CollapseMode::Backward); + } + cx.editor.mode = Mode::Insert; } @@ -2501,6 +2506,12 @@ fn append_mode(cx: &mut Context) { ) }); doc.set_selection(view.id, selection); + + // We already collapsed selections in `enter_insert_mode()`, but this function creates selections again, + // and we want to leave the cursor(s) at the end of the range(s). + if EvilCommands::is_enabled() { + EvilCommands::collapse_selections(cx, CollapseMode::Forward); + } } fn file_picker(cx: &mut Context) { @@ -3110,6 +3121,11 @@ pub fn select_mode(cx: &mut Context) { } pub fn exit_select_mode(cx: &mut Context) { + if EvilCommands::is_enabled() { + // In evil mode, selections are possible in the selection/visual mode only. + EvilCommands::collapse_selections(cx, CollapseMode::ToHead); + } + if cx.editor.mode == Mode::Select { cx.editor.mode = Mode::Normal; } @@ -5605,22 +5621,19 @@ where let (view, doc) = current!(cx.editor); let text = doc.text().slice(..); - let selection = doc - .selection(view.id) - .clone() - .transform(|range| { - let old_anchor = range.anchor; - let mut new_range = move_fn(text, range, count); - - new_range.anchor = match cx.editor.mode { - // In select mode, use a sticky anchor and move the head only - Mode::Select => old_anchor, - // When not in select mode, just move the cursor and do not select - _ => new_range.head - }; + let selection = doc.selection(view.id).clone().transform(|range| { + let old_anchor = range.anchor; + let mut new_range = move_fn(text, range, count); + + new_range.anchor = match cx.editor.mode { + // In select mode, use a sticky anchor and move the head only + Mode::Select => old_anchor, + // When not in select mode, just move the cursor and do not select + _ => new_range.head, + }; - return new_range; - }); + return new_range; + }); doc.set_selection(view.id, selection); } diff --git a/helix-term/src/commands/evil.rs b/helix-term/src/commands/evil.rs index f199c4282..d899c0c86 100644 --- a/helix-term/src/commands/evil.rs +++ b/helix-term/src/commands/evil.rs @@ -51,6 +51,14 @@ enum Motion { LineEnd, } +#[derive(Debug)] +pub enum CollapseMode { + Forward, + Backward, + ToAnchor, + ToHead, +} + impl TryFrom for Motion { type Error = (); @@ -104,6 +112,62 @@ impl EvilCommands { true } + /// Collapse selections such that the selections cover one character per cursor only. + pub fn collapse_selections(cx: &mut Context, collapse_mode: CollapseMode) { + let (view, doc) = current!(cx.editor); + + doc.set_selection( + view.id, + doc.selection(view.id).clone().transform(|mut range| { + log::warn!( + "Adjusting range (mode: {:?}): {} -> {}", + collapse_mode, + range.anchor, + range.head + ); + + // TODO: when exiting insert mode after appending, we end up on the character _after_ the curson, + // while vim returns to the character _before_ the cursor. + + match collapse_mode { + CollapseMode::Forward => { + let end = range.anchor.max(range.head); + range.anchor = 0.max(end - 1); + range.head = end; + } + CollapseMode::Backward => { + let start = range.anchor.min(range.head); + range.anchor = start; + range.head = start + 1; + } + CollapseMode::ToAnchor => { + if range.head > range.anchor { + range.head = range.anchor + 1; + } else { + range.head = 0.max(range.anchor - 1); + } + } + CollapseMode::ToHead => { + if range.head > range.anchor { + range.anchor = 0.max(range.head - 1); + } else { + range.anchor = range.head + 1; + } + } + } + + log::warn!( + "- Adjusted range (mode: {:?}): {} -> {}", + collapse_mode, + range.anchor, + range.head + ); + + return range; + }), + ); + } + fn context() -> RwLockReadGuard<'static, EvilContext> { return CONTEXT.read().unwrap(); }