Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,551 changes: 1,436 additions & 1,115 deletions coverage.txt

Large diffs are not rendered by default.

24 changes: 23 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,31 @@ func main() {
flaggy.Parse()

if configFlag {
// Read-only operation: find config dir without creating it
configDir := config.FindConfigDir("lazydocker")

var userConfig *config.UserConfig
var err error

if configDir != "" {
// Config directory exists, try to load it
userConfig, err = config.LoadUserConfigWithDefaults(configDir)
if err != nil {
// On error loading config, fall back to defaults with warning
log.Printf("Warning: Could not load config from %s: %v\n", configDir, err)
log.Println("Showing defaults instead...")
defaultConfig := config.GetDefaultConfig()
userConfig = &defaultConfig
}
} else {
// No config directory exists, show pure defaults
defaultConfig := config.GetDefaultConfig()
userConfig = &defaultConfig
}

var buf bytes.Buffer
encoder := yaml.NewEncoder(&buf)
err := encoder.Encode(config.GetDefaultConfig())
err = encoder.Encode(userConfig)
if err != nil {
log.Fatal(err.Error())
}
Expand Down
8 changes: 7 additions & 1 deletion pkg/cheatsheet/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,13 @@ func formatBinding(binding *gui.Binding) string {
func getBindingSections(mApp *app.App) []*bindingSection {
bindingSections := []*bindingSection{}

for _, binding := range mApp.Gui.GetInitialKeybindings() {
opts := mApp.Gui.KeybindingOpts()
bindings, err := mApp.Gui.GetInitialKeybindings(opts)
if err != nil {
log.Fatalf("Error getting initial keybindings: %v", err)
}

for _, binding := range bindings {
if binding.Description == "" {
continue
}
Expand Down
47 changes: 37 additions & 10 deletions pkg/config/app_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ type UserConfig struct {
// will be filtered out and not displayed.
// Not documented because it's subject to change
Ignore []string `yaml:"ignore,omitempty"`

// Keybinding configuration allows users to customize all keybindings
Keybinding KeybindingConfig `yaml:"keybinding,omitempty"`
}

// ThemeConfig is for setting the colors of panels and some text.
Expand Down Expand Up @@ -474,6 +477,7 @@ func GetDefaultConfig() UserConfig {
Replacements: Replacements{
ImageNamePrefixes: map[string]string{},
},
Keybinding: GetDefaultKeybindings(),
}
}

Expand All @@ -492,12 +496,12 @@ type AppConfig struct {

// NewAppConfig makes a new app config
func NewAppConfig(name, version, commit, date string, buildSource string, debuggingFlag bool, composeFiles []string, projectDir string) (*AppConfig, error) {
configDir, err := findOrCreateConfigDir(name)
configDir, err := FindOrCreateConfigDir(name)
if err != nil {
return nil, err
}

userConfig, err := loadUserConfigWithDefaults(configDir)
userConfig, err := LoadUserConfigWithDefaults(configDir)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -540,7 +544,8 @@ func configDir(projectName string) string {
return configDirectory
}

func findOrCreateConfigDir(projectName string) (string, error) {
// FindOrCreateConfigDir finds or creates the configuration directory for the given project
func FindOrCreateConfigDir(projectName string) (string, error) {
folder := configDir(projectName)

err := os.MkdirAll(folder, 0o755)
Expand All @@ -551,22 +556,44 @@ func findOrCreateConfigDir(projectName string) (string, error) {
return folder, nil
}

func loadUserConfigWithDefaults(configDir string) (*UserConfig, error) {
// FindConfigDir finds the config directory without creating it.
// Returns empty string if directory doesn't exist.
func FindConfigDir(projectName string) string {
folder := configDir(projectName)

// Check if directory exists
if _, err := os.Stat(folder); os.IsNotExist(err) {
return "" // Not found, don't create
}

return folder
}

// LoadUserConfigWithDefaults loads the user config and merges it with defaults
func LoadUserConfigWithDefaults(configDir string) (*UserConfig, error) {
config := GetDefaultConfig()

return loadUserConfig(configDir, &config)
userConfig, err := loadUserConfig(configDir, &config)
if err != nil {
return nil, err
}


if err := userConfig.Validate(); err != nil {
return nil, err
}

return userConfig, nil
}

func loadUserConfig(configDir string, base *UserConfig) (*UserConfig, error) {
fileName := filepath.Join(configDir, "config.yml")

if _, err := os.Stat(fileName); err != nil {
if os.IsNotExist(err) {
file, err := os.Create(fileName)
if err != nil {
return nil, err
}
file.Close()
// File doesn't exist - return defaults without creating file
// File will be created later when user actually saves config
return base, nil
} else {
return nil, err
}
Expand Down
105 changes: 105 additions & 0 deletions pkg/config/keynames.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package config

import (
"strings"
"unicode/utf8"

"github.com/jesseduffield/gocui"
"github.com/samber/lo"
)

// NOTE: if you make changes to this table, be sure to update
// documentation as well

// LabelByKey maps gocui key constants to their string labels
var LabelByKey = map[gocui.Key]string{
gocui.KeyF1: "<f1>",
gocui.KeyF2: "<f2>",
gocui.KeyF3: "<f3>",
gocui.KeyF4: "<f4>",
gocui.KeyF5: "<f5>",
gocui.KeyF6: "<f6>",
gocui.KeyF7: "<f7>",
gocui.KeyF8: "<f8>",
gocui.KeyF9: "<f9>",
gocui.KeyF10: "<f10>",
gocui.KeyF11: "<f11>",
gocui.KeyF12: "<f12>",
gocui.KeyInsert: "<insert>",
gocui.KeyDelete: "<delete>",
gocui.KeyHome: "<home>",
gocui.KeyEnd: "<end>",
gocui.KeyPgup: "<pgup>",
gocui.KeyPgdn: "<pgdown>",
gocui.KeyArrowUp: "<up>",
gocui.KeyShiftArrowUp: "<s-up>",
gocui.KeyArrowDown: "<down>",
gocui.KeyShiftArrowDown: "<s-down>",
gocui.KeyArrowLeft: "<left>",
gocui.KeyArrowRight: "<right>",
gocui.KeyTab: "<tab>", // <c-i>
gocui.KeyBacktab: "<backtab>",
gocui.KeyEnter: "<enter>", // <c-m>
gocui.KeyAltEnter: "<a-enter>",
gocui.KeyEsc: "<esc>", // <c-[>, <c-3>
gocui.KeyBackspace: "<backspace>", // <c-h>
gocui.KeyCtrlSpace: "<c-space>", // <c-~>, <c-2>
gocui.KeyCtrlSlash: "<c-/>", // <c-_>
gocui.KeySpace: "<space>",
gocui.KeyCtrlA: "<c-a>",
gocui.KeyCtrlB: "<c-b>",
gocui.KeyCtrlC: "<c-c>",
gocui.KeyCtrlD: "<c-d>",
gocui.KeyCtrlE: "<c-e>",
gocui.KeyCtrlF: "<c-f>",
gocui.KeyCtrlG: "<c-g>",
gocui.KeyCtrlJ: "<c-j>",
gocui.KeyCtrlK: "<c-k>",
gocui.KeyCtrlL: "<c-l>",
gocui.KeyCtrlN: "<c-n>",
gocui.KeyCtrlO: "<c-o>",
gocui.KeyCtrlP: "<c-p>",
gocui.KeyCtrlQ: "<c-q>",
gocui.KeyCtrlR: "<c-r>",
gocui.KeyCtrlS: "<c-s>",
gocui.KeyCtrlT: "<c-t>",
gocui.KeyCtrlU: "<c-u>",
gocui.KeyCtrlV: "<c-v>",
gocui.KeyCtrlW: "<c-w>",
gocui.KeyCtrlX: "<c-x>",
gocui.KeyCtrlY: "<c-y>",
gocui.KeyCtrlZ: "<c-z>",
gocui.KeyCtrl4: "<c-4>", // <c-\>
gocui.KeyCtrl5: "<c-5>", // <c-]>
gocui.KeyCtrl6: "<c-6>",
gocui.KeyCtrl8: "<c-8>",
gocui.MouseWheelUp: "mouse wheel up",
gocui.MouseWheelDown: "mouse wheel down",
}

// KeyByLabel is the reverse map: string labels to gocui key constants
var KeyByLabel = lo.Invert(LabelByKey)

// IsValidKeybindingKey checks if a keybinding key string is valid
func IsValidKeybindingKey(key string) bool {
// Reject empty strings explicitly
if key == "" {
return false
}

// Special tokens
if key == "<disabled>" {
return true
}

runeCount := utf8.RuneCountInString(key)

// Multi-character keys must be in KeyByLabel map
if runeCount > 1 {
_, ok := KeyByLabel[strings.ToLower(key)]
return ok
}

// Single character keys are valid
return runeCount == 1
}
Loading