Skip to content

Commit 777e2a2

Browse files
darkswormclaude
andcommitted
feat: add vim-style quit key bindings
Add comprehensive vim-style exit options: - :wq and :wq! commands (write and quit) - ZZ key combination (save and quit, like :wq) - ZQ key combination (quit without saving, like :q!) Uses same double-key timing mechanism as existing gg navigation. All commands exit the application consistently with existing :quit family. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 1ed4966 commit 777e2a2

File tree

3 files changed

+22
-1
lines changed

3 files changed

+22
-1
lines changed

cmd/app/input_handlers.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,23 @@ func (m *Model) handleKeyMsg(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
945945
return m, nil
946946
case "G":
947947
return m.handleGoToBottom()
948+
case "Z":
949+
now := time.Now().UnixMilli()
950+
if m.state.Navigation.LastZPressed > 0 && now-m.state.Navigation.LastZPressed < 500 {
951+
// ZZ: save and quit (like vim)
952+
return m, func() tea.Msg { return model.QuitMsg{} }
953+
}
954+
m.state.Navigation.LastZPressed = now
955+
return m, nil
956+
case "Q":
957+
// Check if this is ZQ (quit without saving)
958+
now := time.Now().UnixMilli()
959+
if m.state.Navigation.LastZPressed > 0 && now-m.state.Navigation.LastZPressed < 500 {
960+
// ZQ: quit without saving (like vim)
961+
m.state.Navigation.LastZPressed = 0 // Reset Z state
962+
return m, func() tea.Msg { return model.QuitMsg{} }
963+
}
964+
return m, nil
948965
}
949966
return m, nil
950967
}

pkg/autocomplete/autocomplete.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func NewAutocompleteEngine() *AutocompleteEngine {
107107
},
108108
{
109109
Command: "quit",
110-
Aliases: []string{"quit", "q", "q!", "exit"},
110+
Aliases: []string{"quit", "q", "q!", "wq", "wq!", "exit"},
111111
Description: "Exit the application",
112112
TakesArg: false,
113113
ArgType: "",

pkg/model/state.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type NavigationState struct {
1212
SelectedIdx int `json:"selectedIdx"`
1313
LastGPressed int64 `json:"lastGPressed"`
1414
LastEscPressed int64 `json:"lastEscPressed"`
15+
LastZPressed int64 `json:"lastZPressed"`
1516
}
1617

1718
// SelectionState holds selection-related state using map[string]bool for sets
@@ -172,6 +173,7 @@ func (s *AppState) SaveNavigationState() {
172173
SelectedIdx: s.Navigation.SelectedIdx,
173174
LastGPressed: s.Navigation.LastGPressed,
174175
LastEscPressed: s.Navigation.LastEscPressed,
176+
LastZPressed: s.Navigation.LastZPressed,
175177
}
176178
s.SavedSelections = &SelectionState{
177179
ScopeClusters: copyStringSet(s.Selections.ScopeClusters),
@@ -188,6 +190,7 @@ func (s *AppState) RestoreNavigationState() {
188190
s.Navigation.SelectedIdx = s.SavedNavigation.SelectedIdx
189191
s.Navigation.LastGPressed = s.SavedNavigation.LastGPressed
190192
s.Navigation.LastEscPressed = s.SavedNavigation.LastEscPressed
193+
s.Navigation.LastZPressed = s.SavedNavigation.LastZPressed
191194
// Clear the saved state after restoration
192195
s.SavedNavigation = nil
193196
}
@@ -224,6 +227,7 @@ func NewAppState() *AppState {
224227
SelectedIdx: 0,
225228
LastGPressed: 0,
226229
LastEscPressed: 0,
230+
LastZPressed: 0,
227231
},
228232
Selections: *NewSelectionState(),
229233
UI: UIState{

0 commit comments

Comments
 (0)