: nply/wmenu

commit 87343a5fc63c320e6fdffd01f6c84b9e9cf46f8b
parent baae6b1c0e2820a3e39f0dc2c1bfad4b9381dfa1
Author: Bharatvaj Hemanth <bharatvaj@yahoo.com>
Date: Tue, 13 Oct 2026 23:08:21 +0530

Return to vi normal mode on Escape from insert mode
4 files changed, 19 insertions(+), 5 deletions(-)
M
HACKING
|
5
+++--
M
README
|
2
+-
M
test_stream.c
|
9
+++++++++
M
wmenu.c
|
8
++++++--
diff --git a/HACKING b/HACKING
@@ -61,7 +61,7 @@ Default mode is emacs-ish, but pragmatic:
 - Ctrl+D / Ctrl+U: page down/up list
 - Ctrl+W: cut selection
 - Ctrl+Alt+V: toggle vi mode
-- Esc: exit
+- Esc: exit, except in vi insert mode where it returns to normal mode
 
 Use -default-controls for normal Windows edit behavior.
 

@@ -80,7 +80,8 @@ Release checklist
 3. Check a slow pipe shows the bottom progress bar animating; search a loaded
    large file and check the same bar shows search progress.
 4. Check Alt-Tab back focuses the input.
-5. Check Esc exits from input and list focus.
+5. Check Esc exits from input and list focus; in vi insert mode it returns to
+   normal mode instead.
 6. Update the version string in wmenu.c.
 7. Do not commit build outputs (*.obj, *.exe) unless intentionally packaging.
 
diff --git a/README b/README
@@ -46,7 +46,7 @@ Default keys:
 
 ```
 Enter       print selection and exit
-Esc         exit
+Esc         exit (vi insert mode: return to normal mode)
 Up/Down     move list selection
 Ctrl+N/P    next/previous item
 Ctrl+D/U    page down/up
diff --git a/test_stream.c b/test_stream.c
@@ -77,6 +77,15 @@ int main(int argc, char **argv)
     UpdateWindow(list_hwnd);
     assert(SendMessageW(list_hwnd, LB_GETCOUNT, 0, 0) == 1);
     assert(draw_calls > 0); /* First streamed row must actually paint. */
+    vi_mode = true;
+    normal_mode = false;
+    assert(InputProc(input_hwnd, WM_KEYDOWN, VK_ESCAPE, 0) == 0 && normal_mode);
+    MSG quit;
+    assert(!PeekMessageW(&quit, NULL, WM_QUIT, WM_QUIT, PM_NOREMOVE));
+    normal_mode = false;
+    assert(ListProc(list_hwnd, WM_KEYDOWN, VK_ESCAPE, 0) == 0 && normal_mode);
+    assert(!PeekMessageW(&quit, NULL, WM_QUIT, WM_QUIT, PM_NOREMOVE));
+    vi_mode = normal_mode = false;
     for (int i = 1; i < 60000; ++i) {
         char buf[2700];
         sprintf(buf, "item-%d", i);
diff --git a/wmenu.c b/wmenu.c
@@ -574,7 +574,11 @@ static bool handle_global_keys(WORD key, struct key_state k)
     if (k.ctrl && key == 'I') { visit_location(1); return true; }
     if (k.ctrl && key == VK_RETURN && multi_select) { add_selection(); return true; }
     if (key == VK_RETURN) { finish(); return true; }
-    if (key == VK_ESCAPE) { PostQuitMessage(0); return true; }
+    if (key == VK_ESCAPE) {
+        if (vi_mode && !normal_mode) normal_mode = true;
+        else PostQuitMessage(0);
+        return true;
+    }
     if (key == VK_DOWN) { list_move(1); return true; }
     if (key == VK_UP) { list_move(-1); return true; }
     return false;

@@ -645,7 +649,7 @@ static LRESULT CALLBACK InputProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
 static LRESULT CALLBACK ListProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
 {
     if (msg == WM_KEYDOWN) {
-        if (wp == VK_ESCAPE) { PostQuitMessage(0); return 0; }
+        if (wp == VK_ESCAPE) { handle_global_keys(VK_ESCAPE, key_state()); return 0; }
         if (wp == VK_RETURN) { finish(); return 0; }
     }
     LRESULT r = CallWindowProcW(old_list_proc, hwnd, msg, wp, lp);