evil-helix icon indicating copy to clipboard operation
evil-helix copied to clipboard

Hitting `v` in select mode does not collapse selections

Open thomie opened this issue 8 months ago • 5 comments

We have the following functions:

Mapped to keys.select.esc:

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;
    }
}

Mapped to keys.select.v and keys.insert.esc (and keys.normal.esc, but that's a no-op):

fn normal_mode(cx: &mut Context) {
    cx.editor.enter_normal_mode();
}

enter_normal_mode does 3 things:

  • set editor.mode = Mode::Normal
  • call try_restore_indent
  • call restore_cursor

try_restore_indent and restore_cursor are only relevant when exiting insert mode (perhaps a better name for enter_normal_mode would be exit_insert_mode).

Conclusion

We should map keys.select.v to exit_select_mode instead of normal_mode.

thomie avatar Apr 15 '25 13:04 thomie

Hmm, that's not the whole story.

enter_normal_mode is also called:

  • when switching windows
  • when switching buffers

So there is another way to reproduce the evil-helix bug: when first making a selection and then switching windows/buffers, if you later return to the window/buffer you will be in normal mode but the selection is still there.

So we need to modify enter_normal_mode. There's no avoiding it.

thomie avatar Apr 15 '25 18:04 thomie

That's an important issue you're mentioning here. I've been bothered since the beginning how I avoid selections in normal mode at local places in the code instead of at one or few central places.

I wanted to avoid the slight overhead of the collapsing in enter_normal_mode() and also didn't want to modify upstream code. But I'm afraid you're right: collapsing selections in enter_normal_mode() might be unavoidable indeed (if is_evil of course).

usagi-flow avatar Apr 16 '25 19:04 usagi-flow

Or keep the selections but don't render them in normal mode?

Not sure, just thinking out loud.

thomie avatar Apr 16 '25 20:04 thomie

It's a good idea, but I fear that not showing selections while they're there might end up causing more problems than solutions. Helix doesn't exactly have this concept of hidden/secondary selections, as far as I'm aware.

I had a brief look at the function, and especially when it's called:

The good news is that enter_normal_mode() called in situations like switching buffers/windows. The bad news is that I didn't manage to collapse (with a very simplistic/dirty implementation, however).

diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index e0165bb6..3b569bc5 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -2250,6 +2250,16 @@ pub fn enter_normal_mode(&mut self) {
             doc.set_selection(view.id, selection);
             doc.restore_cursor = false;
         }
+
+        log::warn!("enter_normal_mode() called; evil: {}", self.evil);
+        // if self.evil {
+        let text = doc.text().slice(..);
+        let selection = doc.selection(view.id).clone().transform(|range| {
+            let pos = range.cursor(text);
+            Range::new(pos, pos)
+        });
+        doc.set_selection(view.id, selection);
+        // }
     }

     pub fn current_stack_frame(&self) -> Option<&StackFrame> {

I hope the function doesn't get called too late - i.e. after switching to the target buffer/window.

PS: For some reason, self.evil also evaluated to false... I should check what's up with that field; it's probably a little detail I oversaw.

usagi-flow avatar Apr 16 '25 20:04 usagi-flow

PS: For some reason, self.evil also evaluated to false... I should check what's up with that field; it's probably a little detail I oversaw.

Yeah that field is unused and can be deleted.

You should be able to use self.config().evil.

thomie avatar Apr 17 '25 07:04 thomie