Skip to content

Commit 1890665

Browse files
authored
chore: use go-keybind (#1397)
## What? Uses [`go-keybind`](https://github.com/floatpane/go-keybind). ## Why? Easier to maintain/expand Signed-off-by: drew <me@andrinoff.com>
1 parent dad48a0 commit 1890665

3 files changed

Lines changed: 58 additions & 92 deletions

File tree

config/keybinds.go

Lines changed: 55 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ package config
33
import (
44
_ "embed"
55
"encoding/json"
6-
"fmt"
7-
"os"
8-
"path/filepath"
6+
7+
keybind "github.com/floatpane/go-keybind"
98
)
109

11-
const keyDelete = "delete"
10+
const keyDelete = "delete" // used in ValidateKeybinds action map keys
1211

1312
//go:embed default_keybinds.json
1413
var defaultKeybindsJSON []byte
@@ -94,27 +93,9 @@ func defaultKeybinds() KeybindsConfig {
9493
// LoadKeybindsFromDir reads keybinds.json from cfgDir, writing defaults if
9594
// the file does not exist, then updates the package-level Keybinds var.
9695
func LoadKeybindsFromDir(cfgDir string) error {
97-
path := filepath.Join(cfgDir, "keybinds.json")
98-
99-
data, err := os.ReadFile(path)
96+
kb, err := keybind.Load(cfgDir, "keybinds.json", defaultKeybinds())
10097
if err != nil {
101-
if !os.IsNotExist(err) {
102-
return fmt.Errorf("keybinds: read %s: %w", path, err)
103-
}
104-
// File missing — write defaults.
105-
if err := os.MkdirAll(cfgDir, 0700); err != nil {
106-
return fmt.Errorf("keybinds: mkdir %s: %w", cfgDir, err)
107-
}
108-
if err := os.WriteFile(path, defaultKeybindsJSON, 0600); err != nil {
109-
return fmt.Errorf("keybinds: write defaults to %s: %w", path, err)
110-
}
111-
Keybinds = defaultKeybinds()
112-
return nil
113-
}
114-
115-
kb := defaultKeybinds()
116-
if err := json.Unmarshal(data, &kb); err != nil {
117-
return fmt.Errorf("keybinds: parse %s: %w", path, err)
98+
return err
11899
}
119100
Keybinds = kb
120101
return nil
@@ -124,73 +105,55 @@ func LoadKeybindsFromDir(cfgDir string) error {
124105
// actions within the same area are mapped to the same key. Cross-area
125106
// duplicates are intentional (e.g. "d" = delete in both inbox and email view).
126107
func ValidateKeybinds(kb KeybindsConfig) []string {
127-
var conflicts []string
128-
129-
check := func(area string, bindings map[string]string) {
130-
seen := make(map[string]string) // key → action name
131-
for action, key := range bindings {
132-
if key == "" {
133-
continue
134-
}
135-
if prev, ok := seen[key]; ok {
136-
conflicts = append(conflicts,
137-
fmt.Sprintf("conflict in %s: key %q used for both %q and %q", area, key, prev, action))
138-
} else {
139-
seen[key] = action
140-
}
141-
}
142-
}
143-
144-
check("global", map[string]string{
145-
"quit": kb.Global.Quit,
146-
"cancel": kb.Global.Cancel,
147-
"nav_up": kb.Global.NavUp,
148-
"nav_down": kb.Global.NavDown,
108+
return keybind.Validate(map[string]map[string]string{
109+
"global": {
110+
"quit": kb.Global.Quit,
111+
"cancel": kb.Global.Cancel,
112+
"nav_up": kb.Global.NavUp,
113+
"nav_down": kb.Global.NavDown,
114+
},
115+
"inbox": {
116+
"visual_mode": kb.Inbox.VisualMode,
117+
"toggle_threaded": kb.Inbox.ToggleThreaded,
118+
keyDelete: kb.Inbox.Delete,
119+
"archive": kb.Inbox.Archive,
120+
"refresh": kb.Inbox.Refresh,
121+
"search": kb.Inbox.Search,
122+
"filter": kb.Inbox.Filter,
123+
"open": kb.Inbox.Open,
124+
"next_tab": kb.Inbox.NextTab,
125+
"prev_tab": kb.Inbox.PrevTab,
126+
},
127+
"email": {
128+
"reply": kb.Email.Reply,
129+
"forward": kb.Email.Forward,
130+
keyDelete: kb.Email.Delete,
131+
"archive": kb.Email.Archive,
132+
"toggle_images": kb.Email.ToggleImages,
133+
"rsvp_accept": kb.Email.RsvpAccept,
134+
"rsvp_decline": kb.Email.RsvpDecline,
135+
"rsvp_tentative": kb.Email.RsvpTentative,
136+
"focus_attachments": kb.Email.FocusAttachments,
137+
},
138+
"composer": {
139+
"external_editor": kb.Composer.ExternalEditor,
140+
"next_field": kb.Composer.NextField,
141+
"prev_field": kb.Composer.PrevField,
142+
keyDelete: kb.Composer.Delete,
143+
// spell_* bindings intentionally excluded — spell_accept reusing
144+
// "tab" with next_field and spell_dismiss reusing "esc" with cancel
145+
// are deliberate: the spellcheck popup intercepts before those handlers.
146+
},
147+
"folder": {
148+
"next_folder": kb.Folder.NextFolder,
149+
"prev_folder": kb.Folder.PrevFolder,
150+
"move": kb.Folder.Move,
151+
"focus_preview": kb.Folder.FocusPreview,
152+
"focus_inbox": kb.Folder.FocusInbox,
153+
},
154+
"drafts": {
155+
"open": kb.Drafts.Open,
156+
keyDelete: kb.Drafts.Delete,
157+
},
149158
})
150-
check("inbox", map[string]string{
151-
"visual_mode": kb.Inbox.VisualMode,
152-
"toggle_threaded": kb.Inbox.ToggleThreaded,
153-
keyDelete: kb.Inbox.Delete,
154-
"archive": kb.Inbox.Archive,
155-
"refresh": kb.Inbox.Refresh,
156-
"search": kb.Inbox.Search,
157-
"filter": kb.Inbox.Filter,
158-
"open": kb.Inbox.Open,
159-
"next_tab": kb.Inbox.NextTab,
160-
"prev_tab": kb.Inbox.PrevTab,
161-
})
162-
check("email", map[string]string{
163-
"reply": kb.Email.Reply,
164-
"forward": kb.Email.Forward,
165-
keyDelete: kb.Email.Delete,
166-
"archive": kb.Email.Archive,
167-
"toggle_images": kb.Email.ToggleImages,
168-
"rsvp_accept": kb.Email.RsvpAccept,
169-
"rsvp_decline": kb.Email.RsvpDecline,
170-
"rsvp_tentative": kb.Email.RsvpTentative,
171-
"focus_attachments": kb.Email.FocusAttachments,
172-
})
173-
check("composer", map[string]string{
174-
"external_editor": kb.Composer.ExternalEditor,
175-
"next_field": kb.Composer.NextField,
176-
"prev_field": kb.Composer.PrevField,
177-
keyDelete: kb.Composer.Delete,
178-
// spell_* bindings intentionally excluded from this conflict
179-
// check — spell_accept reusing "tab" with next_field, and
180-
// spell_dismiss reusing "esc" with cancel, are deliberate: the
181-
// spellcheck popup intercepts before those handlers fire.
182-
})
183-
check("folder", map[string]string{
184-
"next_folder": kb.Folder.NextFolder,
185-
"prev_folder": kb.Folder.PrevFolder,
186-
"move": kb.Folder.Move,
187-
"focus_preview": kb.Folder.FocusPreview,
188-
"focus_inbox": kb.Folder.FocusInbox,
189-
})
190-
check("drafts", map[string]string{
191-
"open": kb.Drafts.Open,
192-
keyDelete: kb.Drafts.Delete,
193-
})
194-
195-
return conflicts
196159
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ require (
1717
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6
1818
github.com/floatpane/bubble-overlay v0.0.1
1919
github.com/floatpane/go-icalendar v0.0.1
20+
github.com/floatpane/go-keybind v0.0.1
2021
github.com/floatpane/go-openpgp-card-hl v0.0.1
2122
github.com/floatpane/go-secretbox v0.1.0
2223
github.com/floatpane/go-uds-jsonrpc v0.0.1

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ github.com/floatpane/bubble-overlay v0.0.1 h1:5xU8cNigDPYegvgGMfOG23fIDXhrqXPvLT
7272
github.com/floatpane/bubble-overlay v0.0.1/go.mod h1:Csi1byxb9L8EAb8X13XdWF5aX5YiBD5C9WEWACyGa8A=
7373
github.com/floatpane/go-icalendar v0.0.1 h1:lF9NhEI4TobX8valDakAFfCnBhM2GDITWMVymhXzD8c=
7474
github.com/floatpane/go-icalendar v0.0.1/go.mod h1:LSy9G+LwUZtfNIAjLlEVRXkuc2A+hq6+pVCIFOiEAyE=
75+
github.com/floatpane/go-keybind v0.0.1 h1:UrzPQ4ldR9sKQt/efpUfcs6gH8Mwy2NO5vwFTVf0dy0=
76+
github.com/floatpane/go-keybind v0.0.1/go.mod h1:B8l43ypYOcjknyaHgU0EXUAbrUvfnV2HOzYSzyqcPJI=
7577
github.com/floatpane/go-openpgp-card-hl v0.0.1 h1:1DYmzwGDb8eneZxbc/xtwjXeFY8DFL3eYnUooMT0L0w=
7678
github.com/floatpane/go-openpgp-card-hl v0.0.1/go.mod h1:Mrx+ukCnpEpMAxyB0p8Ch2gu78Q3Ir40BxBybb2jirw=
7779
github.com/floatpane/go-secretbox v0.1.0 h1:xNryazmCP0oR/yVxIkHRc5bcV56YrbisY+bMl8BBfwU=

0 commit comments

Comments
 (0)