diff --git a/pkg/config/keynames.go b/pkg/config/keynames.go index 419a003f4..bb9756b43 100644 --- a/pkg/config/keynames.go +++ b/pkg/config/keynames.go @@ -1,6 +1,9 @@ package config import ( + "strings" + "unicode/utf8" + "github.com/jesseduffield/gocui" "github.com/samber/lo" ) @@ -74,3 +77,17 @@ var LabelByKey = map[gocui.Key]string{ } var KeyByLabel = lo.Invert(LabelByKey) + +func isValidKeybindingKey(key string) bool { + runeCount := utf8.RuneCountInString(key) + if key == "" { + return true + } + + if runeCount > 1 { + _, ok := KeyByLabel[strings.ToLower(key)] + return ok + } + + return true +} diff --git a/pkg/config/user_config_validation.go b/pkg/config/user_config_validation.go index 403119ada..41e63ab86 100644 --- a/pkg/config/user_config_validation.go +++ b/pkg/config/user_config_validation.go @@ -2,8 +2,12 @@ package config import ( "fmt" + "log" + "reflect" "slices" "strings" + + "github.com/jesseduffield/lazygit/pkg/constants" ) func (config *UserConfig) Validate() error { @@ -15,6 +19,9 @@ func (config *UserConfig) Validate() error { []string{"none", "onlyArrow", "arrowAndNumber"}); err != nil { return err } + if err := validateKeybindings(config.Keybinding); err != nil { + return err + } return nil } @@ -25,3 +32,41 @@ func validateEnum(name string, value string, allowedValues []string) error { allowedValuesStr := strings.Join(allowedValues, ", ") return fmt.Errorf("Unexpected value '%s' for '%s'. Allowed values: %s", value, name, allowedValuesStr) } + +func validateKeybindingsRecurse(path string, node any) error { + value := reflect.ValueOf(node) + if value.Kind() == reflect.Struct { + for _, field := range reflect.VisibleFields(reflect.TypeOf(node)) { + var newPath string + if len(path) == 0 { + newPath = field.Name + } else { + newPath = fmt.Sprintf("%s.%s", path, field.Name) + } + if err := validateKeybindingsRecurse(newPath, + value.FieldByName(field.Name).Interface()); err != nil { + return err + } + } + } else if value.Kind() == reflect.Slice { + for i := 0; i < value.Len(); i++ { + if err := validateKeybindingsRecurse( + fmt.Sprintf("%s[%d]", path, i), value.Index(i).Interface()); err != nil { + return err + } + } + } else if value.Kind() == reflect.String { + key := node.(string) + if !isValidKeybindingKey(key) { + return fmt.Errorf("Unrecognized key '%s' for keybinding '%s'. For permitted values see %s", + key, path, constants.Links.Docs.CustomKeybindings) + } + } else { + log.Fatalf("Unexpected type for property '%s': %s", path, value.Kind()) + } + return nil +} + +func validateKeybindings(keybindingConfig KeybindingConfig) error { + return validateKeybindingsRecurse("", keybindingConfig) +} diff --git a/pkg/config/user_config_validation_test.go b/pkg/config/user_config_validation_test.go index 9f7b4d74c..f49b3479b 100644 --- a/pkg/config/user_config_validation_test.go +++ b/pkg/config/user_config_validation_test.go @@ -29,6 +29,19 @@ func TestUserConfigValidate_enums(t *testing.T) { {value: "invalid_value", valid: false}, }, }, + { + name: "Keybindings", + setup: func(config *UserConfig, value string) { + config.Keybinding.Universal.Quit = value + }, + testCases: []testCase{ + {value: "", valid: true}, + {value: "", valid: true}, + {value: "q", valid: true}, + {value: "", valid: true}, + {value: "invalid_value", valid: false}, + }, + }, } for _, s := range scenarios {