From 67bb7f62c5fbd6faf80b93db293f7f35030bd62e Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 16 Feb 2025 14:36:16 +0100 Subject: [PATCH 1/5] Move labelByKey to config package --- pkg/config/keynames.go | 73 ++++++++++++++++++++++++++++++ pkg/gui/keybindings/keybindings.go | 73 ++---------------------------- 2 files changed, 76 insertions(+), 70 deletions(-) create mode 100644 pkg/config/keynames.go diff --git a/pkg/config/keynames.go b/pkg/config/keynames.go new file mode 100644 index 00000000000..69574affa77 --- /dev/null +++ b/pkg/config/keynames.go @@ -0,0 +1,73 @@ +package config + +import ( + "github.com/jesseduffield/gocui" + "github.com/samber/lo" +) + +var LabelByKey = map[gocui.Key]string{ + gocui.KeyF1: "", + gocui.KeyF2: "", + gocui.KeyF3: "", + gocui.KeyF4: "", + gocui.KeyF5: "", + gocui.KeyF6: "", + gocui.KeyF7: "", + gocui.KeyF8: "", + gocui.KeyF9: "", + gocui.KeyF10: "", + gocui.KeyF11: "", + gocui.KeyF12: "", + gocui.KeyInsert: "", + gocui.KeyDelete: "", + gocui.KeyHome: "", + gocui.KeyEnd: "", + gocui.KeyPgup: "", + gocui.KeyPgdn: "", + gocui.KeyArrowUp: "", + gocui.KeyShiftArrowUp: "", + gocui.KeyArrowDown: "", + gocui.KeyShiftArrowDown: "", + gocui.KeyArrowLeft: "", + gocui.KeyArrowRight: "", + gocui.KeyTab: "", // + gocui.KeyBacktab: "", + gocui.KeyEnter: "", // + gocui.KeyAltEnter: "", + gocui.KeyEsc: "", // , + gocui.KeyBackspace: "", // + gocui.KeyCtrlSpace: "", // , + gocui.KeyCtrlSlash: "", // + gocui.KeySpace: "", + gocui.KeyCtrlA: "", + gocui.KeyCtrlB: "", + gocui.KeyCtrlC: "", + gocui.KeyCtrlD: "", + gocui.KeyCtrlE: "", + gocui.KeyCtrlF: "", + gocui.KeyCtrlG: "", + gocui.KeyCtrlJ: "", + gocui.KeyCtrlK: "", + gocui.KeyCtrlL: "", + gocui.KeyCtrlN: "", + gocui.KeyCtrlO: "", + gocui.KeyCtrlP: "", + gocui.KeyCtrlQ: "", + gocui.KeyCtrlR: "", + gocui.KeyCtrlS: "", + gocui.KeyCtrlT: "", + gocui.KeyCtrlU: "", + gocui.KeyCtrlV: "", + gocui.KeyCtrlW: "", + gocui.KeyCtrlX: "", + gocui.KeyCtrlY: "", + gocui.KeyCtrlZ: "", + gocui.KeyCtrl4: "", // + gocui.KeyCtrl5: "", // + gocui.KeyCtrl6: "", + gocui.KeyCtrl8: "", + gocui.MouseWheelUp: "mouse wheel up", + gocui.MouseWheelDown: "mouse wheel down", +} + +var KeyByLabel = lo.Invert(LabelByKey) diff --git a/pkg/gui/keybindings/keybindings.go b/pkg/gui/keybindings/keybindings.go index da917b82e0e..923a30c3b69 100644 --- a/pkg/gui/keybindings/keybindings.go +++ b/pkg/gui/keybindings/keybindings.go @@ -7,78 +7,11 @@ import ( "unicode/utf8" "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/constants" "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/samber/lo" ) -var labelByKey = map[gocui.Key]string{ - gocui.KeyF1: "", - gocui.KeyF2: "", - gocui.KeyF3: "", - gocui.KeyF4: "", - gocui.KeyF5: "", - gocui.KeyF6: "", - gocui.KeyF7: "", - gocui.KeyF8: "", - gocui.KeyF9: "", - gocui.KeyF10: "", - gocui.KeyF11: "", - gocui.KeyF12: "", - gocui.KeyInsert: "", - gocui.KeyDelete: "", - gocui.KeyHome: "", - gocui.KeyEnd: "", - gocui.KeyPgup: "", - gocui.KeyPgdn: "", - gocui.KeyArrowUp: "", - gocui.KeyShiftArrowUp: "", - gocui.KeyArrowDown: "", - gocui.KeyShiftArrowDown: "", - gocui.KeyArrowLeft: "", - gocui.KeyArrowRight: "", - gocui.KeyTab: "", // - gocui.KeyBacktab: "", - gocui.KeyEnter: "", // - gocui.KeyAltEnter: "", - gocui.KeyEsc: "", // , - gocui.KeyBackspace: "", // - gocui.KeyCtrlSpace: "", // , - gocui.KeyCtrlSlash: "", // - gocui.KeySpace: "", - gocui.KeyCtrlA: "", - gocui.KeyCtrlB: "", - gocui.KeyCtrlC: "", - gocui.KeyCtrlD: "", - gocui.KeyCtrlE: "", - gocui.KeyCtrlF: "", - gocui.KeyCtrlG: "", - gocui.KeyCtrlJ: "", - gocui.KeyCtrlK: "", - gocui.KeyCtrlL: "", - gocui.KeyCtrlN: "", - gocui.KeyCtrlO: "", - gocui.KeyCtrlP: "", - gocui.KeyCtrlQ: "", - gocui.KeyCtrlR: "", - gocui.KeyCtrlS: "", - gocui.KeyCtrlT: "", - gocui.KeyCtrlU: "", - gocui.KeyCtrlV: "", - gocui.KeyCtrlW: "", - gocui.KeyCtrlX: "", - gocui.KeyCtrlY: "", - gocui.KeyCtrlZ: "", - gocui.KeyCtrl4: "", // - gocui.KeyCtrl5: "", // - gocui.KeyCtrl6: "", - gocui.KeyCtrl8: "", - gocui.MouseWheelUp: "mouse wheel up", - gocui.MouseWheelDown: "mouse wheel down", -} - -var keyByLabel = lo.Invert(labelByKey) - func Label(name string) string { return LabelFromKey(GetKey(name)) } @@ -90,7 +23,7 @@ func LabelFromKey(key types.Key) string { case rune: keyInt = int(key) case gocui.Key: - value, ok := labelByKey[key] + value, ok := config.LabelByKey[key] if ok { return value } @@ -105,7 +38,7 @@ func GetKey(key string) types.Key { if key == "" { return nil } else if runeCount > 1 { - binding, ok := keyByLabel[strings.ToLower(key)] + binding, ok := config.KeyByLabel[strings.ToLower(key)] if !ok { log.Fatalf("Unrecognized key %s for keybinding. For permitted values see %s", strings.ToLower(key), constants.Links.Docs.CustomKeybindings) } else { From 130801dbf62970b5ec1c4e8fc92ba63929c7423e Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 16 Feb 2025 17:07:18 +0100 Subject: [PATCH 2/5] Add a few missing keybindings to docs/keybindings/Custom_Keybindings.md --- docs/keybindings/Custom_Keybindings.md | 4 ++++ pkg/config/keynames.go | 3 +++ 2 files changed, 7 insertions(+) diff --git a/docs/keybindings/Custom_Keybindings.md b/docs/keybindings/Custom_Keybindings.md index 698b87e65d0..a2537f0696c 100644 --- a/docs/keybindings/Custom_Keybindings.md +++ b/docs/keybindings/Custom_Keybindings.md @@ -20,11 +20,15 @@ | `` | Pgup | | `` | Pgdn | | `` | ArrowUp | +| `` | ShiftArrowUp | | `` | ArrowDown | +| `` | ShiftArrowDown | | `` | ArrowLeft | | `` | ArrowRight | | `` | Tab | +| `` | Backtab | | `` | Enter | +| `` | AltEnter | | `` | Esc | | `` | Backspace | | `` | CtrlSpace | diff --git a/pkg/config/keynames.go b/pkg/config/keynames.go index 69574affa77..419a003f4be 100644 --- a/pkg/config/keynames.go +++ b/pkg/config/keynames.go @@ -5,6 +5,9 @@ import ( "github.com/samber/lo" ) +// NOTE: if you make changes to this table, be sure to update +// docs/keybindings/Custom_Keybindings.md as well + var LabelByKey = map[gocui.Key]string{ gocui.KeyF1: "", gocui.KeyF2: "", From f3791e6ab65c6103b26ed75ad79ac10ecdde078d Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 16 Feb 2025 16:18:57 +0100 Subject: [PATCH 3/5] Validate keybindings --- pkg/config/keynames.go | 17 +++++++++ pkg/config/user_config_validation.go | 45 +++++++++++++++++++++++ pkg/config/user_config_validation_test.go | 13 +++++++ 3 files changed, 75 insertions(+) diff --git a/pkg/config/keynames.go b/pkg/config/keynames.go index 419a003f4be..bb9756b4387 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 403119adadb..41e63ab8609 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 9f7b4d74c32..f49b3479b29 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 { From a5f78d32229096b08a3ba28d28e64c5bd54ea947 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 16 Feb 2025 16:45:28 +0100 Subject: [PATCH 4/5] Validate that Universal.JumpToBlock array has 5 elements The code that uses this panics if it has fewer. --- pkg/config/user_config_validation.go | 11 ++++++++++- pkg/config/user_config_validation_test.go | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/pkg/config/user_config_validation.go b/pkg/config/user_config_validation.go index 41e63ab8609..83b2a7c5ab0 100644 --- a/pkg/config/user_config_validation.go +++ b/pkg/config/user_config_validation.go @@ -68,5 +68,14 @@ func validateKeybindingsRecurse(path string, node any) error { } func validateKeybindings(keybindingConfig KeybindingConfig) error { - return validateKeybindingsRecurse("", keybindingConfig) + if err := validateKeybindingsRecurse("", keybindingConfig); err != nil { + return err + } + + if len(keybindingConfig.Universal.JumpToBlock) != 5 { + return fmt.Errorf("keybinding.universal.jumpToBlock must have 5 elements; found %d.", + len(keybindingConfig.Universal.JumpToBlock)) + } + + return nil } diff --git a/pkg/config/user_config_validation_test.go b/pkg/config/user_config_validation_test.go index f49b3479b29..8069b13be52 100644 --- a/pkg/config/user_config_validation_test.go +++ b/pkg/config/user_config_validation_test.go @@ -1,6 +1,7 @@ package config import ( + "strings" "testing" "github.com/stretchr/testify/assert" @@ -42,6 +43,19 @@ func TestUserConfigValidate_enums(t *testing.T) { {value: "invalid_value", valid: false}, }, }, + { + name: "JumpToBlock keybinding", + setup: func(config *UserConfig, value string) { + config.Keybinding.Universal.JumpToBlock = strings.Split(value, ",") + }, + testCases: []testCase{ + {value: "", valid: false}, + {value: "1,2,3", valid: false}, + {value: "1,2,3,4,5", valid: true}, + {value: "1,2,3,4,invalid", valid: false}, + {value: "1,2,3,4,5,6", valid: false}, + }, + }, } for _, s := range scenarios { From 5979b40546456b89b745f59edbec8f371364dea7 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 16 Feb 2025 16:19:15 +0100 Subject: [PATCH 5/5] Validate keys of custom commands --- pkg/config/user_config_validation.go | 20 ++++++++++++++++++++ pkg/config/user_config_validation_test.go | 18 ++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/pkg/config/user_config_validation.go b/pkg/config/user_config_validation.go index 83b2a7c5ab0..735c5aad2c2 100644 --- a/pkg/config/user_config_validation.go +++ b/pkg/config/user_config_validation.go @@ -22,6 +22,9 @@ func (config *UserConfig) Validate() error { if err := validateKeybindings(config.Keybinding); err != nil { return err } + if err := validateCustomCommands(config.CustomCommands); err != nil { + return err + } return nil } @@ -79,3 +82,20 @@ func validateKeybindings(keybindingConfig KeybindingConfig) error { return nil } + +func validateCustomCommandKey(key string) error { + if !isValidKeybindingKey(key) { + return fmt.Errorf("Unrecognized key '%s' for custom command. For permitted values see %s", + key, constants.Links.Docs.CustomKeybindings) + } + return nil +} + +func validateCustomCommands(customCommands []CustomCommand) error { + for _, customCommand := range customCommands { + if err := validateCustomCommandKey(customCommand.Key); err != nil { + return err + } + } + return nil +} diff --git a/pkg/config/user_config_validation_test.go b/pkg/config/user_config_validation_test.go index 8069b13be52..1d842801966 100644 --- a/pkg/config/user_config_validation_test.go +++ b/pkg/config/user_config_validation_test.go @@ -56,6 +56,24 @@ func TestUserConfigValidate_enums(t *testing.T) { {value: "1,2,3,4,5,6", valid: false}, }, }, + { + name: "Custom command keybinding", + setup: func(config *UserConfig, value string) { + config.CustomCommands = []CustomCommand{ + { + Key: value, + Command: "echo 'hello'", + }, + } + }, + testCases: []testCase{ + {value: "", valid: true}, + {value: "", valid: true}, + {value: "q", valid: true}, + {value: "", valid: true}, + {value: "invalid_value", valid: false}, + }, + }, } for _, s := range scenarios {