issue-9: finishing keybinding service and implemented controller

Have now got rebinding keys working with the "rebind" command.
Still need to make sure key names are correct and implement rebinding
as part of an RC file and add bindings for the table.
This commit is contained in:
Leon Mika 2022-08-25 22:14:36 +10:00
parent 2f89610c51
commit 7c5bfd27a3
6 changed files with 159 additions and 17 deletions

View file

@ -98,15 +98,17 @@ func main() {
},
}
commandController := commandctrl.NewCommandController()
keyBindingService := keybindings.NewService(defaultKeyBindings)
_ = keyBindingService
keyBindingController := controllers.NewKeyBindingController(keyBindingService)
commandController := commandctrl.NewCommandController()
model := ui.NewModel(
tableReadController,
tableWriteController,
itemRendererService,
commandController,
keyBindingController,
defaultKeyBindings,
)

View file

@ -0,0 +1,38 @@
package controllers
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/audax/internal/common/ui/events"
"github.com/lmika/audax/internal/dynamo-browse/services/keybindings"
"github.com/pkg/errors"
)
type KeyBindingController struct {
service *keybindings.Service
}
func NewKeyBindingController(service *keybindings.Service) *KeyBindingController {
return &KeyBindingController{service: service}
}
func (kb *KeyBindingController) Rebind(newKey string, bindingName string) tea.Msg {
err := kb.service.Rebind(newKey, bindingName, false)
if err == nil {
return events.SetStatus(fmt.Sprintf("Key '%v' now bound to '%v'", newKey, bindingName))
}
var keyAlreadyBoundErr keybindings.KeyAlreadyBoundError
if errors.As(err, &keyAlreadyBoundErr) {
promptMsg := fmt.Sprintf("Key '%v' already bound to '%v'. Continue? ", keyAlreadyBoundErr.Key, keyAlreadyBoundErr.ExistingBindingName)
return events.Confirm(promptMsg, func() tea.Msg {
err := kb.service.Rebind(newKey, bindingName, true)
if err != nil {
return events.Error(err)
}
return events.SetStatus(fmt.Sprintf("Key '%v' now bound to '%v'", newKey, bindingName))
})
}
return events.Error(err)
}

View file

@ -0,0 +1,14 @@
package keybindings
import (
"fmt"
)
type KeyAlreadyBoundError struct {
Key string
ExistingBindingName string
}
func (e KeyAlreadyBoundError) Error() string {
return fmt.Sprintf("key '%v' already bound to '%v'", e.Key, e.ExistingBindingName)
}

View file

@ -1,6 +1,11 @@
package keybindings
import "reflect"
import (
"github.com/charmbracelet/bubbles/key"
"github.com/pkg/errors"
"reflect"
"strings"
)
type Service struct {
keyBindingValue reflect.Value
@ -17,16 +22,95 @@ func NewService(keyBinding any) *Service {
}
}
func (s *Service) Rebind(name string, key string) error {
func (s *Service) Rebind(newKey string, name string, force bool) error {
// Check if there already exists a binding
if !force {
var foundBinding = ""
s.walkBindingFields(func(bindingName string, binding key.Binding) bool {
for _, key := range binding.Keys() {
if key == newKey {
foundBinding = bindingName
return false
}
}
return true
})
if foundBinding != "" {
return KeyAlreadyBoundError{Key: newKey, ExistingBindingName: foundBinding}
}
}
func (s *Service) findFieldForBinding(name string) reflect.Value {
// Rebind
binding := s.findFieldForBinding(name)
if binding == nil {
return errors.Errorf("invalid binding: %v", name)
}
func (s *Service) findFieldForBindingInGroup(group reflect.Value, name string) reflect.Value {
*binding = key.NewBinding(key.WithKeys(newKey))
return nil
}
func (s *Service) findFieldForBinding(name string) *key.Binding {
return s.findFieldForBindingInGroup(s.keyBindingValue, name)
}
func (s *Service) findFieldForBindingInGroup(group reflect.Value, name string) *key.Binding {
bindingName, bindingSuffix, _ := strings.Cut(name, ".")
groupType := group.Type()
for i := 0; i < group.NumField(); i++ {
group.Field(i).Type().
fieldType := groupType.Field(i)
keymapTag := fieldType.Tag.Get("keymap")
if keymapTag != bindingName {
continue
}
if fieldType.Type.Kind() == reflect.Pointer && fieldType.Type.Elem().Kind() == reflect.Struct {
return s.findFieldForBindingInGroup(group.Field(i).Elem(), bindingSuffix)
}
binding, isBinding := group.Field(i).Addr().Interface().(*key.Binding)
if !isBinding {
return nil
}
return binding
}
return nil
}
func (s *Service) walkBindingFields(fn func(name string, binding key.Binding) bool) {
s.walkBindingFieldsInGroup(s.keyBindingValue, "", fn)
}
func (s *Service) walkBindingFieldsInGroup(group reflect.Value, prefix string, fn func(name string, binding key.Binding) bool) bool {
groupType := group.Type()
for i := 0; i < group.NumField(); i++ {
fieldType := groupType.Field(i)
keymapTag := fieldType.Tag.Get("keymap")
var fullName string
if prefix != "" {
fullName = prefix + "." + keymapTag
} else {
fullName = keymapTag
}
if fieldType.Type.Kind() == reflect.Pointer && fieldType.Type.Elem().Kind() == reflect.Struct {
if !s.walkBindingFieldsInGroup(group.Field(i).Elem(), fullName, fn) {
return false
}
}
binding, isBinding := group.Field(i).Addr().Interface().(*key.Binding)
if !isBinding {
continue
}
if !fn(fullName, *binding) {
return false
}
}
return true
}

View file

@ -3,7 +3,7 @@ package ui
import "github.com/charmbracelet/bubbles/key"
type KeyBindings struct {
View *ViewKeyBindings `keymap:"view,group"`
View *ViewKeyBindings `keymap:"view"`
}
type ViewKeyBindings struct {

View file

@ -54,6 +54,7 @@ func NewModel(
wc *controllers.TableWriteController,
itemRendererService *itemrenderer.Service,
cc *commandctrl.CommandController,
keyBindingController *controllers.KeyBindingController,
defaultKeyMap *KeyBindings,
) Model {
uiStyles := styles.DefaultStyles
@ -129,9 +130,12 @@ func NewModel(
return wc.NoisyTouchItem(dtv.SelectedItemIndex())
},
//"rebind": func(args []string) tea.Msg {
//
//},
"rebind": func(args []string) tea.Msg {
if len(args) != 2 {
return events.Error(errors.New("expected: name newKey"))
}
return keyBindingController.Rebind(args[0], args[1])
},
// Aliases
"sa": cc.Alias("set-attr"),