From 5e3ccb8c53ca90d3d648fcc534ee21416608cc6b Mon Sep 17 00:00:00 2001 From: 3nprob <3nprob@3nprob> Date: Tue, 7 Dec 2021 22:18:59 +0900 Subject: [PATCH] Configurable keybindings --- config/config.go | 89 ++++++++++++++++++++++++++++++++++++++++ ui/fuzzy-search-modal.go | 16 +++++--- ui/help-modal.go | 8 +++- ui/room-view.go | 37 +++++++++-------- ui/verification-modal.go | 10 ++++- ui/view-main.go | 55 +++++++++++++------------ 6 files changed, 163 insertions(+), 52 deletions(-) diff --git a/config/config.go b/config/config.go index 617968f..ce8f9b0 100644 --- a/config/config.go +++ b/config/config.go @@ -29,6 +29,9 @@ import ( "maunium.net/go/mautrix/id" "maunium.net/go/mautrix/pushrules" + "github.com/3nprob/cbind" + "maunium.net/go/tcell" + "maunium.net/go/gomuks/debug" "maunium.net/go/gomuks/matrix/rooms" ) @@ -56,6 +59,26 @@ type UserPreferences struct { AltEnterToSend bool `yaml:"alt_enter_to_send"` } +type Keybind struct { + Mod tcell.ModMask + Key tcell.Key + Ch rune +} + +type Keybindings struct { + Main map[Keybind]string `yaml:"main,omitempty"` + Room map[Keybind]string `yaml:"room,omitempty"` + Modal map[Keybind]string `yaml:"modal,omitempty"` + Visual map[Keybind]string `yaml:"visual,omitempty"` +} + +type KeybindingsConfig struct { + Main map[string]string `yaml:"main,omitempty"` + Room map[string]string `yaml:"room,omitempty"` + Modal map[string]string `yaml:"modal,omitempty"` + Visual map[string]string `yaml:"visual,omitempty"` +} + // Config contains the main config of gomuks. type Config struct { UserID id.UserID `yaml:"mxid"` @@ -85,6 +108,7 @@ type Config struct { AuthCache AuthCache `yaml:"-"` Rooms *rooms.RoomCache `yaml:"-"` PushRules *pushrules.PushRuleset `yaml:"-"` + Keybindings Keybindings `yaml:"-"` nosave bool } @@ -152,6 +176,7 @@ func (config *Config) LoadAll() { config.LoadAuthCache() config.LoadPushRules() config.LoadPreferences() + config.LoadKeybindings() err := config.Rooms.LoadList() if err != nil { panic(err) @@ -189,6 +214,70 @@ func (config *Config) SavePreferences() { config.save("user preferences", config.CacheDir, "preferences.yaml", &config.Preferences) } +func (config *Config) LoadKeybindings() { + cfg := KeybindingsConfig{} + config.load("keybindings", config.Dir, "keybindings.yaml", &cfg) + config.Keybindings.Main = make(map[Keybind]string) + config.Keybindings.Room = make(map[Keybind]string) + config.Keybindings.Modal = make(map[Keybind]string) + config.Keybindings.Visual = make(map[Keybind]string) + + for k, v := range cfg.Main { + mod, key, ch, err := cbind.Decode(k) + if err != nil { + // todo + } + kb := Keybind{ + Mod: mod, + Key: key, + Ch: ch, + } + config.Keybindings.Main[kb] = v + } + for k, v := range cfg.Room { + mod, key, ch, err := cbind.Decode(k) + if err != nil { + // todo + } + kb := Keybind{ + Mod: mod, + Key: key, + Ch: ch, + } + config.Keybindings.Room[kb] = v + } + + for k, v := range cfg.Modal { + mod, key, ch, err := cbind.Decode(k) + if err != nil { + // todo + } + kb := Keybind{ + Mod: mod, + Key: key, + Ch: ch, + } + config.Keybindings.Modal[kb] = v + } + + for k, v := range cfg.Visual { + mod, key, ch, err := cbind.Decode(k) + if err != nil { + // todo + } + kb := Keybind{ + Mod: mod, + Key: key, + Ch: ch, + } + config.Keybindings.Visual[kb] = v + } +} + +func (config *Config) SaveKeybindings() { + config.save("keybindings", config.Dir, "keybindings.yaml", &config.Keybindings) +} + func (config *Config) LoadAuthCache() { config.load("auth cache", config.CacheDir, "auth-cache.yaml", &config.AuthCache) } diff --git a/ui/fuzzy-search-modal.go b/ui/fuzzy-search-modal.go index b96f758..d22826c 100644 --- a/ui/fuzzy-search-modal.go +++ b/ui/fuzzy-search-modal.go @@ -27,6 +27,7 @@ import ( "maunium.net/go/mauview" "maunium.net/go/tcell" + "maunium.net/go/gomuks/config" "maunium.net/go/gomuks/debug" "maunium.net/go/gomuks/matrix/rooms" ) @@ -120,12 +121,17 @@ func (fs *FuzzySearchModal) changeHandler(str string) { func (fs *FuzzySearchModal) OnKeyEvent(event mauview.KeyEvent) bool { highlights := fs.results.GetHighlights() - switch event.Key() { - case tcell.KeyEsc: + kb := config.Keybind{ + Key: event.Key(), + Ch: event.Rune(), + Mod: event.Modifiers(), + } + switch fs.parent.config.Keybindings.Modal[kb] { + case "cancel": // Close room finder fs.parent.HideModal() return true - case tcell.KeyTab: + case "select_next": // Cycle highlighted area to next match if len(highlights) > 0 { fs.selected = (fs.selected + 1) % len(fs.matches) @@ -133,7 +139,7 @@ func (fs *FuzzySearchModal) OnKeyEvent(event mauview.KeyEvent) bool { fs.results.ScrollToHighlight() } return true - case tcell.KeyBacktab: + case "select_prev": if len(highlights) > 0 { fs.selected = (fs.selected - 1) % len(fs.matches) if fs.selected < 0 { @@ -143,7 +149,7 @@ func (fs *FuzzySearchModal) OnKeyEvent(event mauview.KeyEvent) bool { fs.results.ScrollToHighlight() } return true - case tcell.KeyEnter: + case "confirm": // Switch room to currently selected room if len(highlights) > 0 { debug.Print("Fuzzy Selected Room:", fs.roomList[fs.matches[fs.selected].OriginalIndex].GetTitle()) diff --git a/ui/help-modal.go b/ui/help-modal.go index 36b57c6..b3bf657 100644 --- a/ui/help-modal.go +++ b/ui/help-modal.go @@ -1,6 +1,7 @@ package ui import ( + "maunium.net/go/gomuks/config" "maunium.net/go/tcell" "maunium.net/go/mauview" @@ -94,7 +95,12 @@ func NewHelpModal(parent *MainView) *HelpModal { } func (hm *HelpModal) OnKeyEvent(event mauview.KeyEvent) bool { - if event.Key() == tcell.KeyEscape || event.Rune() == 'q' { + kb := config.Keybind{ + Key: event.Key(), + Ch: event.Rune(), + Mod: event.Modifiers(), + } + if hm.parent.config.Keybindings.Room[kb] == "cancel" { hm.parent.HideModal() return true } diff --git a/ui/room-view.go b/ui/room-view.go index 116448c..dd0e25b 100644 --- a/ui/room-view.go +++ b/ui/room-view.go @@ -37,7 +37,7 @@ import ( "maunium.net/go/gomuks/config" "maunium.net/go/gomuks/debug" - "maunium.net/go/gomuks/interface" + ifc "maunium.net/go/gomuks/interface" "maunium.net/go/gomuks/lib/open" "maunium.net/go/gomuks/lib/util" "maunium.net/go/gomuks/matrix/muksevt" @@ -339,41 +339,44 @@ func (view *RoomView) ClearAllContext() { func (view *RoomView) OnKeyEvent(event mauview.KeyEvent) bool { msgView := view.MessageView() + kb := config.Keybind{ + Key: event.Key(), + Ch: event.Rune(), + Mod: event.Modifiers(), + } + if view.selecting { - k := event.Key() - c := event.Rune() - switch { - case k == tcell.KeyEscape || c == 'h': + switch view.config.Keybindings.Visual[kb] { + case "clear": view.ClearAllContext() - case k == tcell.KeyUp || c == 'k': + case "select_prev": view.SelectPrevious() - case k == tcell.KeyDown || c == 'j': + case "select_next": view.SelectNext() - case k == tcell.KeyEnter || c == 'l': + case "confirm": view.OnSelect(msgView.selected) default: return false } return true } - switch event.Key() { - case tcell.KeyEscape: + + switch view.config.Keybindings.Room[kb] { + case "clear": view.ClearAllContext() return true - case tcell.KeyPgUp: + case "scroll_up": if msgView.IsAtTop() { go view.parent.LoadHistory(view.Room.ID) } msgView.AddScrollOffset(+msgView.Height() / 2) return true - case tcell.KeyPgDn: + case "scroll_down": msgView.AddScrollOffset(-msgView.Height() / 2) return true - case tcell.KeyEnter: - if (event.Modifiers()&tcell.ModShift == 0 && event.Modifiers()&tcell.ModCtrl == 0) != (view.config.Preferences.AltEnterToSend) { - view.InputSubmit(view.input.GetText()) - return true - } + case "send": + view.InputSubmit(view.input.GetText()) + return true } return view.input.OnKeyEvent(event) } diff --git a/ui/verification-modal.go b/ui/verification-modal.go index 8c7714f..86ea90a 100644 --- a/ui/verification-modal.go +++ b/ui/verification-modal.go @@ -27,6 +27,7 @@ import ( "maunium.net/go/mauview" "maunium.net/go/tcell" + "maunium.net/go/gomuks/config" "maunium.net/go/gomuks/debug" "maunium.net/go/mautrix/crypto" "maunium.net/go/mautrix/event" @@ -207,8 +208,13 @@ func (vm *VerificationModal) OnSuccess() { } func (vm *VerificationModal) OnKeyEvent(event mauview.KeyEvent) bool { + kb := config.Keybind{ + Key: event.Key(), + Ch: event.Rune(), + Mod: event.Modifiers(), + } if vm.done { - if event.Key() == tcell.KeyEnter || event.Key() == tcell.KeyEsc { + if vm.parent.config.Keybindings.Modal[kb] == "cancel" || vm.parent.config.Keybindings.Modal[kb] == "confirm" { vm.parent.HideModal() return true } @@ -217,7 +223,7 @@ func (vm *VerificationModal) OnKeyEvent(event mauview.KeyEvent) bool { debug.Print("Ignoring pre-emoji key event") return false } - if event.Key() == tcell.KeyEnter { + if vm.parent.config.Keybindings.Modal[kb] == "confirm" { text := strings.ToLower(strings.TrimSpace(vm.inputBar.GetText())) if text == "yes" { debug.Print("Confirming verification") diff --git a/ui/view-main.go b/ui/view-main.go index fc1b9c9..df28742 100644 --- a/ui/view-main.go +++ b/ui/view-main.go @@ -33,7 +33,7 @@ import ( "maunium.net/go/gomuks/config" "maunium.net/go/gomuks/debug" - "maunium.net/go/gomuks/interface" + ifc "maunium.net/go/gomuks/interface" "maunium.net/go/gomuks/lib/notification" "maunium.net/go/gomuks/matrix/rooms" "maunium.net/go/gomuks/ui/widget" @@ -170,33 +170,34 @@ func (view *MainView) OnKeyEvent(event mauview.KeyEvent) bool { return view.modal.OnKeyEvent(event) } - k := event.Key() - c := event.Rune() - if event.Modifiers() == tcell.ModCtrl || event.Modifiers() == tcell.ModAlt { - switch { - case k == tcell.KeyDown: - view.SwitchRoom(view.roomList.Next()) - case k == tcell.KeyUp: - view.SwitchRoom(view.roomList.Previous()) - case c == 'k' || k == tcell.KeyCtrlK: - view.ShowModal(NewFuzzySearchModal(view, 42, 12)) - case k == tcell.KeyHome: - msgView := view.currentRoom.MessageView() - msgView.AddScrollOffset(msgView.TotalHeight()) - case k == tcell.KeyEnd: - msgView := view.currentRoom.MessageView() - msgView.AddScrollOffset(-msgView.TotalHeight()) - case k == tcell.KeyEnter: - return view.flex.OnKeyEvent(tcell.NewEventKey(tcell.KeyEnter, '\n', event.Modifiers()|tcell.ModShift, "")) - case c == 'a': - view.SwitchRoom(view.roomList.NextWithActivity()) - case c == 'l' || k == tcell.KeyCtrlL: - view.ShowBare(view.currentRoom) - default: - goto defaultHandler - } - return true + kb := config.Keybind{ + Key: event.Key(), + Ch: event.Rune(), + Mod: event.Modifiers(), } + switch view.config.Keybindings.Main[kb] { + case "next_room": + view.SwitchRoom(view.roomList.Next()) + case "prev_room": + view.SwitchRoom(view.roomList.Previous()) + case "search_rooms": + view.ShowModal(NewFuzzySearchModal(view, 42, 12)) + case "scroll_up": + msgView := view.currentRoom.MessageView() + msgView.AddScrollOffset(msgView.TotalHeight()) + case "scroll_down": + msgView := view.currentRoom.MessageView() + msgView.AddScrollOffset(-msgView.TotalHeight()) + case "add_newline": + return view.flex.OnKeyEvent(tcell.NewEventKey(tcell.KeyEnter, '\n', event.Modifiers()|tcell.ModShift, "")) + case "next_active_room": + view.SwitchRoom(view.roomList.NextWithActivity()) + case "show_bare": + view.ShowBare(view.currentRoom) + default: + goto defaultHandler + } + return true defaultHandler: if view.config.Preferences.HideRoomList { return view.roomView.OnKeyEvent(event)