From 235a7f98ea2cac3f9c546955dc0b70097c9e442f Mon Sep 17 00:00:00 2001 From: VERSE Date: Fri, 31 Jul 2026 18:38:31 +0700 Subject: [PATCH] fix(config): resolve enter key submission and keybinding issues (#89) closes #88 closes #85 --- internal/config/config_test.go | 26 ++++++++++++++++++++++++++ internal/config/keys.go | 9 +++++++++ root/wrapper.go | 28 +++++++++++++++++++--------- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index b45cb26..8e53c9a 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -272,3 +272,29 @@ func TestMigration(t *testing.T) { t.Errorf("expected backup file update_state.json.bak to exist") } } + +func TestMatchKey(t *testing.T) { + tests := []struct { + input []byte + expected string + matched bool + consumed int + }{ + {[]byte{0x19}, "ctrl+y", true, 1}, + {[]byte{0x0b}, "ctrl+k", true, 1}, + {[]byte{0x0a}, "ctrl+j", true, 1}, + {[]byte{0x12}, "ctrl+r", true, 1}, + {[]byte{'j'}, "j", true, 1}, + {[]byte{'k'}, "k", true, 1}, + {[]byte{0x09}, "tab", true, 1}, + {[]byte{0x0d}, "enter", true, 1}, + {[]byte{0x0d}, "ctrl+r", false, 0}, + } + + for _, tt := range tests { + m, c := MatchKey(tt.input, tt.expected) + if m != tt.matched || c != tt.consumed { + t.Errorf("MatchKey(%v, %q) = (%v, %d); want (%v, %d)", tt.input, tt.expected, m, c, tt.matched, tt.consumed) + } + } +} diff --git a/internal/config/keys.go b/internal/config/keys.go index 7e1e966..41a8852 100644 --- a/internal/config/keys.go +++ b/internal/config/keys.go @@ -15,6 +15,15 @@ func MatchKey(input []byte, expected string) (matched bool, consumed int) { expected = strings.ToLower(strings.TrimSpace(expected)) expected = strings.TrimPrefix(expected, "<") expected = strings.TrimSuffix(expected, ">") + expected = strings.ReplaceAll(expected, "-", "+") + expected = strings.ReplaceAll(expected, "ctrk", "ctrl") + expected = strings.ReplaceAll(expected, "ctl", "ctrl") + + if len(expected) == 1 { + if input[0] == expected[0] { + return true, 1 + } + } if strings.HasPrefix(expected, "ctrl+") && len(expected) == 6 { char := expected[5] diff --git a/root/wrapper.go b/root/wrapper.go index 5c8c500..e85af89 100644 --- a/root/wrapper.go +++ b/root/wrapper.go @@ -747,18 +747,24 @@ func runWrapper() { i += navConsumed - 1 continue - } else if naiveBuffer == "" { - // up/down arrow on empty prompt + } else { + // up/down arrow or navigation key when overlay is closed intercepted = true activeModeMu.Lock() - activeMode = "history" - saveMode(activeMode) + if activeMode == "" { + activeMode = loadMode() + } activeModeMu.Unlock() activeModeMu.RLock() currentMode := activeMode activeModeMu.RUnlock() - results := MergeResults("", currentMode) + + bufferMu.Lock() + bufQuery := naiveBuffer + bufferMu.Unlock() + + results := MergeResults(bufQuery, currentMode) if len(results) > 0 { limit := min(len(results), 100) var historyList []spec.Suggestion @@ -791,8 +797,8 @@ func runWrapper() { } if matched, consumed := config.MatchKey(inputSlice[i:], config.Get().Keybindings.SelectSuggestion); matched && config.Get().Keybindings.SelectSuggestion != "" { - intercepted = true if overlay.IsVisible() { + intercepted = true selected := overlay.GetCurrentCmd() if selected != "" { activeModeMu.RLock() @@ -816,18 +822,22 @@ func runWrapper() { userNavigated.Store(false) writeStdout([]byte(overlay.Render())) } + i += consumed - 1 + continue } - i += consumed - 1 - continue } if b == 0x0d || b == 0x0a { // enter intercepted = true logger.Debugf("Intercepted Enter key") + + if b == 0x0d && i+1 < n && inputSlice[i+1] == 0x0a { + i++ // consume trailing \n in \r\n to prevent matching ctrl+j + } var selectedCmd string var shouldAutoExecute bool - if config.Get().Core.AutoExecute && overlay.IsVisible() { + if overlay.IsVisible() && (config.Get().Core.AutoExecute || userNavigated.Load()) { selectedCmd = overlay.GetCurrentCmd() if selectedCmd != "" { shouldAutoExecute = true