From 5f70e7afe2a9150fe84d716e5848b37351183e0e Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sat, 3 Oct 2026 02:13:23 +0200 Subject: [PATCH] vim: Close popovers and expanded hunks on escape in Helix normal mode In Helix normal mode escape runs `vim::SwitchToHelixNormalMode`, which only switches the mode, so the diagnostic shown by `] d`, a hunk expanded with `g o`, a hover popover and the like stayed open. Vim mode closes them because escape there is `editor::Cancel`. Binding Helix to `Cancel` isn't an option, since once nothing is open it collapses the selections. Pull the part of `Editor::cancel` that runs before it touches the selections into `Editor::dismiss_overlays`, and call it when escape is pressed in Helix normal mode. Selections are kept as before. Closes #55197 --- crates/editor/src/editor.rs | 39 ++++++++++++++++++++--------------- crates/vim/src/helix.rs | 41 ++++++++++++++++++++++++++++++++++++- crates/vim/src/vim.rs | 5 +++++ 3 files changed, 68 insertions(+), 17 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bff66cb..0f4b576 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3519,22 +3519,7 @@ impl Editor { self.selection_mark_mode = false; self.selection_drag_state = SelectionDragState::None; - if self.dismiss_menus_and_popups(true, window, cx) { - cx.notify(); - return; - } - if self.clear_expanded_diff_hunks(cx) { - cx.notify(); - return; - } - if self.show_git_blame_gutter - && !self - .blame - .as_ref() - .is_some_and(|blame| blame.read(cx).is_static()) - { - self.show_git_blame_gutter = false; - cx.notify(); + if self.dismiss_overlays(window, cx) { return; } @@ -3553,6 +3538,28 @@ impl Editor { cx.propagate(); } + /// Closes what [`Cancel`] closes before it gets to the selections: menus and popovers, + /// expanded diff hunks and the git blame gutter. Returns whether anything was closed. + pub fn dismiss_overlays(&mut self, window: &mut Window, cx: &mut Context) -> bool { + let mut dismissed = + self.dismiss_menus_and_popups(true, window, cx) || self.clear_expanded_diff_hunks(cx); + if !dismissed + && self.show_git_blame_gutter + && !self + .blame + .as_ref() + .is_some_and(|blame| blame.read(cx).is_static()) + { + self.show_git_blame_gutter = false; + dismissed = true; + } + + if dismissed { + cx.notify(); + } + dismissed + } + pub fn dismiss_menus_and_popups( &mut self, is_user_requested: bool, diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index 9b2ecf8..67d4b7e 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -1879,7 +1879,7 @@ mod test { use futures::StreamExt; use std::{fmt::Write, time::Duration}; - use editor::{HighlightKey, MultiBufferOffset}; + use editor::{Anchor, HighlightKey, MultiBufferOffset}; use gpui::{ Bounds, DispatchEventResult, ElementInputHandler, KeyBinding, KeyDownEvent, Keystroke, PlatformInput, PlatformInputHandler, UpdateGlobal, VisualTestContext, @@ -4687,6 +4687,45 @@ mod test { assert_eq!(cx.active_operator(), None); } + #[gpui::test] + async fn test_helix_escape_collapses_expanded_hunk(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + cx.enable_helix(); + + let selected = indoc! {" + one + «twoˇ» + three"}; + cx.set_state(selected, Mode::HelixNormal); + cx.set_head_text(indoc! {" + one + CHANGED + three"}); + cx.run_until_parked(); + + let has_expanded_hunks = |cx: &mut VimTestContext| { + cx.update_editor(|editor, _, cx| { + editor + .buffer() + .read(cx) + .has_expanded_diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], cx) + }) + }; + + cx.simulate_keystrokes("g o"); + assert!(has_expanded_hunks(&mut cx)); + + cx.simulate_keystrokes("escape"); + assert!( + !has_expanded_hunks(&mut cx), + "escape should collapse the expanded hunk" + ); + cx.assert_state(selected, Mode::HelixNormal); + + cx.simulate_keystrokes("escape"); + cx.assert_state(selected, Mode::HelixNormal); + } + #[gpui::test] async fn test_helix_rename_uses_visible_cursor_position(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new_typescript(cx).await; diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index c016bd6..8c6c8c3 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -714,6 +714,11 @@ impl Vim { editor, cx, |vim, _: &SwitchToHelixNormalMode, window, cx| { + // Escape in helix normal mode lands here. Close popovers and overlays + // like `editor::Cancel` does in vim mode, but keep the selections. + if vim.mode == Mode::HelixNormal { + vim.update_editor(cx, |_, editor, cx| editor.dismiss_overlays(window, cx)); + } vim.switch_mode(Mode::HelixNormal, true, window, cx) }, );