vim: Convert from visual mode to normal mode with a single click (#6985)

WindSoilder and Conrad Irwin created

Release Notes:

- Fixed #4319

Here is a demo after the fix:

https://github.com/zed-industries/zed/assets/22256154/a690f146-73c9-4b0e-8527-e4faf690cca2

Actually I'm not really sure should I submit my idea to discussion,
since it's not a large change, and it's something like a bug fix. So I
directly create a pr here.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>

Change summary

crates/vim/src/vim.rs | 26 +++++++++++++++++++-------
1 file changed, 19 insertions(+), 7 deletions(-)

Detailed changes

crates/vim/src/vim.rs 🔗

@@ -204,7 +204,8 @@ impl Vim {
                 let editor = editor.read(cx);
                 if editor.leader_peer_id().is_none() {
                     let newest = editor.selections.newest::<usize>(cx);
-                    local_selections_changed(newest, cx);
+                    let is_multicursor = editor.selections.count() > 1;
+                    local_selections_changed(newest, is_multicursor, cx);
                 }
             }
             EditorEvent::InputIgnored { text } => {
@@ -626,13 +627,24 @@ impl Settings for VimModeSetting {
     }
 }
 
-fn local_selections_changed(newest: Selection<usize>, cx: &mut WindowContext) {
+fn local_selections_changed(
+    newest: Selection<usize>,
+    is_multicursor: bool,
+    cx: &mut WindowContext,
+) {
     Vim::update(cx, |vim, cx| {
-        if vim.enabled && vim.state().mode == Mode::Normal && !newest.is_empty() {
-            if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
-                vim.switch_mode(Mode::VisualBlock, false, cx);
-            } else {
-                vim.switch_mode(Mode::Visual, false, cx)
+        if vim.enabled {
+            if vim.state().mode == Mode::Normal && !newest.is_empty() {
+                if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
+                    vim.switch_mode(Mode::VisualBlock, false, cx);
+                } else {
+                    vim.switch_mode(Mode::Visual, false, cx)
+                }
+            } else if newest.is_empty()
+                && !is_multicursor
+                && [Mode::Visual, Mode::VisualLine, Mode::VisualBlock].contains(&vim.state().mode)
+            {
+                vim.switch_mode(Mode::Normal, true, cx)
             }
         }
     })