diff --git a/README.md b/README.md index 2e151743..ed564c02 100644 --- a/README.md +++ b/README.md @@ -96,3 +96,47 @@ For docker in windows (does not support pulling images yet): docker run --rm -it -v //var/run/docker.sock:/var/run/docker.sock wagoodman/dive:latest ``` +## KeyBindings + +Key Binding | Description +-------------------------------------------|--------------------------------------------------------- +Ctrl + C | Exit +Tab or Ctrl + Space | Switch between the layer and filetree views +Ctrl + F | Filter files +Ctrl + A | Layer view: see aggregated image modifications +Ctrl + L | Layer view: see current layer modifications +Space | Filetree view: collapse/uncollapse a directory +Ctrl + A | Filetree view: show/hide added files +Ctrl + R | Filetree view: show/hide removed files +Ctrl + M | Filetree view: show/hide modified files +Ctrl + U | Filetree view: show/hide unmodified files +PageUp | Filetree view: scroll up a page +PageDown | Filetree view: scroll down a page + +## Configuration + +No configuration is necessary, however, you can create a `~/.dive.yaml` file and override values: +```yaml +log: + enabled: true + path: ./dive.log + level: info + +# note: you can specify multiple bindings by separating values with a comma. UI hinting shows the first binding. +keybinding: + # global bindings + quit: ctrl+c + toggle-view: tab, ctrl+space + filter-files: ctrl+f, ctrl+slash + # layer view specific bindings + compare-all: ctrl+a + compare-layer: ctrl+l + # file view specific bindings + toggle-collapse-dir: space + toggle-added-files: ctrl+a + toggle-removed-files: ctrl+r + toggle-modified-files: ctrl+m + toggle-unmodified-files: ctrl+u + page-up: pgup + page-down: pgdn +``` diff --git a/cmd/root.go b/cmd/root.go index 6a7ac178..0b2fe038 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" "github.com/wagoodman/dive/utils" + "io/ioutil" "os" "github.com/k0kubun/go-ansi" @@ -38,8 +39,7 @@ func init() { cobra.OnInitialize(initConfig) cobra.OnInitialize(initLogging) - // TODO: add config options - // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.dive.yaml)") + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.dive.yaml)") rootCmd.PersistentFlags().BoolP("version", "v", false, "display version number") } @@ -62,6 +62,25 @@ func initConfig() { viper.SetConfigName(".dive") } + viper.SetDefault("log.level", log.InfoLevel.String()) + viper.SetDefault("log.path", "./dive.log") + viper.SetDefault("log.enabled", true) + // status view / global + viper.SetDefault("keybinding.quit", "ctrl+c") + viper.SetDefault("keybinding.toggle-view", "tab, ctrl+space") + viper.SetDefault("keybinding.filter-files", "ctrl+f, ctrl+slash") + // layer view + viper.SetDefault("keybinding.compare-all", "ctrl+a") + viper.SetDefault("keybinding.compare-layer", "ctrl+l") + // filetree view + viper.SetDefault("keybinding.toggle-collapse-dir", "space") + viper.SetDefault("keybinding.toggle-added-files", "ctrl+a") + viper.SetDefault("keybinding.toggle-removed-files", "ctrl+r") + viper.SetDefault("keybinding.toggle-modified-files", "ctrl+m") + viper.SetDefault("keybinding.toggle-unchanged-files", "ctrl+u") + viper.SetDefault("keybinding.page-up", "pgup") + viper.SetDefault("keybinding.page-down", "pgdn") + viper.AutomaticEnv() // read in environment variables that match // If a config file is found, read it in. @@ -70,21 +89,33 @@ func initConfig() { } } -// initLogging sets up the loggin object with a formatter and location +// initLogging sets up the logging object with a formatter and location func initLogging() { - // TODO: clean this up and make more configurable - var filename string = "dive.log" - // create the log file if doesn't exist. And append to it if it already exists. - f, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + + if viper.GetBool("log.enabled") == false { + log.SetOutput(ioutil.Discard) + } + + logFileObj, err := os.OpenFile(viper.GetString("log.path"), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + if err != nil { + fmt.Fprintln(os.Stderr, err) + } + Formatter := new(log.TextFormatter) Formatter.DisableTimestamp = true log.SetFormatter(Formatter) - log.SetLevel(log.DebugLevel) + + level, err := log.ParseLevel(viper.GetString("log.level")) if err != nil { - // cannot open log file. Logging to stderr - fmt.Println(err) + fmt.Fprintln(os.Stderr, err) + } + log.SetLevel(level) + + if err != nil { + fmt.Fprintln(os.Stderr, err) } else { - log.SetOutput(f) + log.SetOutput(logFileObj) } + log.Debug("Starting Dive...") } diff --git a/ui/filetreeview.go b/ui/filetreeview.go index e35d782d..185dbe01 100644 --- a/ui/filetreeview.go +++ b/ui/filetreeview.go @@ -3,6 +3,7 @@ package ui import ( "fmt" "github.com/sirupsen/logrus" + "github.com/spf13/viper" "regexp" "strings" @@ -33,6 +34,14 @@ type FileTreeView struct { bufferIndex uint bufferIndexUpperBound uint bufferIndexLowerBound uint + + keybindingToggleCollapse []Key + keybindingToggleAdded []Key + keybindingToggleRemoved []Key + keybindingToggleModified []Key + keybindingToggleUnchanged []Key + keybindingPageDown []Key + keybindingPageUp []Key } // NewFileTreeView creates a new view object attached the the global [gocui] screen object. @@ -46,6 +55,14 @@ func NewFileTreeView(name string, gui *gocui.Gui, tree *filetree.FileTree, refTr treeView.RefTrees = refTrees treeView.HiddenDiffTypes = make([]bool, 4) + treeView.keybindingToggleCollapse = getKeybindings(viper.GetString("keybinding.toggle-collapse-dir")) + treeView.keybindingToggleAdded = getKeybindings(viper.GetString("keybinding.toggle-added-files")) + treeView.keybindingToggleRemoved = getKeybindings(viper.GetString("keybinding.toggle-removed-files")) + treeView.keybindingToggleModified = getKeybindings(viper.GetString("keybinding.toggle-modified-files")) + treeView.keybindingToggleUnchanged = getKeybindings(viper.GetString("keybinding.toggle-unchanged-files")) + treeView.keybindingPageUp = getKeybindings(viper.GetString("keybinding.page-up")) + treeView.keybindingPageDown = getKeybindings(viper.GetString("keybinding.page-down")) + return treeView } @@ -76,26 +93,41 @@ func (view *FileTreeView) Setup(v *gocui.View, header *gocui.View) error { if err := view.gui.SetKeybinding(view.Name, gocui.KeyArrowRight, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorRight() }); err != nil { return err } - if err := view.gui.SetKeybinding(view.Name, gocui.KeyPgdn, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.PageDown() }); err != nil { - return err + + for _, key := range view.keybindingPageUp { + if err := view.gui.SetKeybinding("", key.value, key.modifier, func(*gocui.Gui, *gocui.View) error { return view.PageUp() }); err != nil { + return err + } } - if err := view.gui.SetKeybinding(view.Name, gocui.KeyPgup, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.PageUp() }); err != nil { - return err + for _, key := range view.keybindingPageDown { + if err := view.gui.SetKeybinding("", key.value, key.modifier, func(*gocui.Gui, *gocui.View) error { return view.PageDown() }); err != nil { + return err + } } - if err := view.gui.SetKeybinding(view.Name, gocui.KeySpace, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.toggleCollapse() }); err != nil { - return err + for _, key := range view.keybindingToggleCollapse { + if err := view.gui.SetKeybinding("", key.value, key.modifier, func(*gocui.Gui, *gocui.View) error { return view.toggleCollapse() }); err != nil { + return err + } } - if err := view.gui.SetKeybinding(view.Name, gocui.KeyCtrlA, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.toggleShowDiffType(filetree.Added) }); err != nil { - return err + for _, key := range view.keybindingToggleAdded { + if err := view.gui.SetKeybinding("", key.value, key.modifier, func(*gocui.Gui, *gocui.View) error { return view.toggleShowDiffType(filetree.Added) }); err != nil { + return err + } } - if err := view.gui.SetKeybinding(view.Name, gocui.KeyCtrlR, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.toggleShowDiffType(filetree.Removed) }); err != nil { - return err + for _, key := range view.keybindingToggleRemoved { + if err := view.gui.SetKeybinding("", key.value, key.modifier, func(*gocui.Gui, *gocui.View) error { return view.toggleShowDiffType(filetree.Removed) }); err != nil { + return err + } } - if err := view.gui.SetKeybinding(view.Name, gocui.KeyCtrlM, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.toggleShowDiffType(filetree.Changed) }); err != nil { - return err + for _, key := range view.keybindingToggleModified { + if err := view.gui.SetKeybinding("", key.value, key.modifier, func(*gocui.Gui, *gocui.View) error { return view.toggleShowDiffType(filetree.Changed) }); err != nil { + return err + } } - if err := view.gui.SetKeybinding(view.Name, gocui.KeyCtrlU, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.toggleShowDiffType(filetree.Unchanged) }); err != nil { - return err + for _, key := range view.keybindingToggleUnchanged { + if err := view.gui.SetKeybinding("", key.value, key.modifier, func(*gocui.Gui, *gocui.View) error { return view.toggleShowDiffType(filetree.Unchanged) }); err != nil { + return err + } } view.bufferIndexLowerBound = 0 @@ -494,9 +526,9 @@ func (view *FileTreeView) Render() error { // KeyHelp indicates all the possible actions a user can take while the current pane is selected. func (view *FileTreeView) KeyHelp() string { - return renderStatusOption("Space", "Collapse dir", false) + - renderStatusOption("^A", "Added files", !view.HiddenDiffTypes[filetree.Added]) + - renderStatusOption("^R", "Removed files", !view.HiddenDiffTypes[filetree.Removed]) + - renderStatusOption("^M", "Modified files", !view.HiddenDiffTypes[filetree.Changed]) + - renderStatusOption("^U", "Unmodified files", !view.HiddenDiffTypes[filetree.Unchanged]) + return renderStatusOption(view.keybindingToggleCollapse[0].String(), "Collapse dir", false) + + renderStatusOption(view.keybindingToggleAdded[0].String(), "Added files", !view.HiddenDiffTypes[filetree.Added]) + + renderStatusOption(view.keybindingToggleRemoved[0].String(), "Removed files", !view.HiddenDiffTypes[filetree.Removed]) + + renderStatusOption(view.keybindingToggleModified[0].String(), "Modified files", !view.HiddenDiffTypes[filetree.Changed]) + + renderStatusOption(view.keybindingToggleUnchanged[0].String(), "Unmodified files", !view.HiddenDiffTypes[filetree.Unchanged]) } diff --git a/ui/keybinding.go b/ui/keybinding.go new file mode 100644 index 00000000..06675821 --- /dev/null +++ b/ui/keybinding.go @@ -0,0 +1,186 @@ +package ui + +import ( + "fmt" + "github.com/jroimartin/gocui" + "strings" + "unicode" +) + +var translate = map[string]string{ + "/": "Slash", + "\\": "Backslash", + "[": "LsqBracket", + "]": "RsqBracket", + "_": "Underscore", + "escape": "Esc", + "~": "Tilde", + "pageup": "Pgup", + "pagedown": "Pgdn", + "pgup": "Pgup", + "pgdown": "Pgdn", + // "up": "ArrowUp", + // "down": "ArrowDown", + // "right": "ArrowRight", + // "left": "ArrowLeft", + "ctl": "Ctrl", +} + +var display = map[string]string{ + "Slash": "/", + "Backslash": "\\", + "LsqBracket": "[", + "RsqBracket": "]", + "Underscore": "_", + "Tilde": "~", + "Ctrl": "^", +} + +var supportedKeybindings = map[string]gocui.Key{ + "KeyF1": gocui.KeyF1, + "KeyF2": gocui.KeyF2, + "KeyF3": gocui.KeyF3, + "KeyF4": gocui.KeyF4, + "KeyF5": gocui.KeyF5, + "KeyF6": gocui.KeyF6, + "KeyF7": gocui.KeyF7, + "KeyF8": gocui.KeyF8, + "KeyF9": gocui.KeyF9, + "KeyF10": gocui.KeyF10, + "KeyF11": gocui.KeyF11, + "KeyF12": gocui.KeyF12, + "KeyInsert": gocui.KeyInsert, + "KeyDelete": gocui.KeyDelete, + "KeyHome": gocui.KeyHome, + "KeyEnd": gocui.KeyEnd, + "KeyPgup": gocui.KeyPgup, + "KeyPgdn": gocui.KeyPgdn, + // "KeyArrowUp": gocui.KeyArrowUp, + // "KeyArrowDown": gocui.KeyArrowDown, + // "KeyArrowLeft": gocui.KeyArrowLeft, + // "KeyArrowRight": gocui.KeyArrowRight, + "KeyCtrlTilde": gocui.KeyCtrlTilde, + "KeyCtrl2": gocui.KeyCtrl2, + "KeyCtrlSpace": gocui.KeyCtrlSpace, + "KeyCtrlA": gocui.KeyCtrlA, + "KeyCtrlB": gocui.KeyCtrlB, + "KeyCtrlC": gocui.KeyCtrlC, + "KeyCtrlD": gocui.KeyCtrlD, + "KeyCtrlE": gocui.KeyCtrlE, + "KeyCtrlF": gocui.KeyCtrlF, + "KeyCtrlG": gocui.KeyCtrlG, + "KeyBackspace": gocui.KeyBackspace, + "KeyCtrlH": gocui.KeyCtrlH, + "KeyTab": gocui.KeyTab, + "KeyCtrlI": gocui.KeyCtrlI, + "KeyCtrlJ": gocui.KeyCtrlJ, + "KeyCtrlK": gocui.KeyCtrlK, + "KeyCtrlL": gocui.KeyCtrlL, + "KeyEnter": gocui.KeyEnter, + "KeyCtrlM": gocui.KeyCtrlM, + "KeyCtrlN": gocui.KeyCtrlN, + "KeyCtrlO": gocui.KeyCtrlO, + "KeyCtrlP": gocui.KeyCtrlP, + "KeyCtrlQ": gocui.KeyCtrlQ, + "KeyCtrlR": gocui.KeyCtrlR, + "KeyCtrlS": gocui.KeyCtrlS, + "KeyCtrlT": gocui.KeyCtrlT, + "KeyCtrlU": gocui.KeyCtrlU, + "KeyCtrlV": gocui.KeyCtrlV, + "KeyCtrlW": gocui.KeyCtrlW, + "KeyCtrlX": gocui.KeyCtrlX, + "KeyCtrlY": gocui.KeyCtrlY, + "KeyCtrlZ": gocui.KeyCtrlZ, + "KeyEsc": gocui.KeyEsc, + "KeyCtrlLsqBracket": gocui.KeyCtrlLsqBracket, + "KeyCtrl3": gocui.KeyCtrl3, + "KeyCtrl4": gocui.KeyCtrl4, + "KeyCtrlBackslash": gocui.KeyCtrlBackslash, + "KeyCtrl5": gocui.KeyCtrl5, + "KeyCtrlRsqBracket": gocui.KeyCtrlRsqBracket, + "KeyCtrl6": gocui.KeyCtrl6, + "KeyCtrl7": gocui.KeyCtrl7, + "KeyCtrlSlash": gocui.KeyCtrlSlash, + "KeyCtrlUnderscore": gocui.KeyCtrlUnderscore, + "KeySpace": gocui.KeySpace, + "KeyBackspace2": gocui.KeyBackspace2, + "KeyCtrl8": gocui.KeyCtrl8, +} + +type Key struct { + value gocui.Key + modifier gocui.Modifier + tokens []string + input string +} + +func getKeybinding(input string) (Key, error) { + f := func(c rune) bool { return unicode.IsSpace(c) || c == '+' } + tokens := strings.FieldsFunc(input, f) + var normalizedTokens []string + var modifier = gocui.ModNone + + for _, token := range tokens { + normalized := strings.ToLower(token) + + if value, exists := translate[normalized]; exists { + normalized = value + } else { + normalized = strings.Title(normalized) + } + + if normalized == "Alt" { + modifier = gocui.ModAlt + continue + } + + if len(normalized) == 1 { + normalizedTokens = append(normalizedTokens, strings.ToUpper(normalized)) + continue + } + + normalizedTokens = append(normalizedTokens, normalized) + } + + lookup := "Key" + strings.Join(normalizedTokens, "") + + if key, exists := supportedKeybindings[lookup]; exists { + return Key{key, modifier, normalizedTokens, input}, nil + } + + if modifier != gocui.ModNone { + return Key{0, modifier, normalizedTokens, input}, fmt.Errorf("unsupported keybinding: %s (+%+v)", lookup, modifier) + } + return Key{0, modifier, normalizedTokens, input}, fmt.Errorf("unsupported keybinding: %s", lookup) +} + +func getKeybindings(input string) []Key { + ret := make([]Key, 0) + for _, value := range strings.Split(input, ",") { + key, err := getKeybinding(value) + if err != nil { + panic(fmt.Errorf("could not parse keybinding '%s' from request '%s': %+v", value, input, err)) + } + ret = append(ret, key) + } + if len(ret) == 0 { + panic(fmt.Errorf("must have at least one keybinding")) + } + return ret +} + +func (key Key) String() string { + displayTokens := make([]string, 0) + prefix := "" + for _, token := range key.tokens { + if token == "Ctrl" { + prefix = "^" + continue + } + if value, exists := display[token]; exists { + token = value + } + displayTokens = append(displayTokens, token) + } + return prefix + strings.Join(displayTokens, "+") +} diff --git a/ui/keybinding_test.go b/ui/keybinding_test.go new file mode 100644 index 00000000..be4954d1 --- /dev/null +++ b/ui/keybinding_test.go @@ -0,0 +1,55 @@ +package ui + +import ( + "github.com/jroimartin/gocui" + "testing" +) + +func TestGetKeybinding(t *testing.T) { + var table = []struct { + input string + key gocui.Key + modifier gocui.Modifier + errStr string + }{ + {"ctrl + A", gocui.KeyCtrlA, gocui.ModNone, ""}, + {"Ctrl + a", gocui.KeyCtrlA, gocui.ModNone, ""}, + {"Ctl + a", gocui.KeyCtrlA, gocui.ModNone, ""}, + {"ctl + A", gocui.KeyCtrlA, gocui.ModNone, ""}, + {"f2", gocui.KeyF2, gocui.ModNone, ""}, + {"ctrl + [", gocui.KeyCtrlLsqBracket, gocui.ModNone, ""}, + {" ctrl + ] ", gocui.KeyCtrlRsqBracket, gocui.ModNone, ""}, + {"ctrl + /", gocui.KeyCtrlSlash, gocui.ModNone, ""}, + {"ctrl + \\", gocui.KeyCtrlBackslash, gocui.ModNone, ""}, + // {"left", gocui.KeyArrowLeft, gocui.ModNone, ""}, + {"PageUp", gocui.KeyPgup, gocui.ModNone, ""}, + {"PgUp", gocui.KeyPgup, gocui.ModNone, ""}, + {"pageup", gocui.KeyPgup, gocui.ModNone, ""}, + {"pgup", gocui.KeyPgup, gocui.ModNone, ""}, + {"tab", gocui.KeyTab, gocui.ModNone, ""}, + {"escape", gocui.KeyEsc, gocui.ModNone, ""}, + {"enter", gocui.KeyEnter, gocui.ModNone, ""}, + {"space", gocui.KeySpace, gocui.ModNone, ""}, + {"ctrl + alt + z", gocui.KeyCtrlZ, gocui.ModAlt, ""}, + {"f22", 0, gocui.ModNone, "unsupported keybinding: KeyF22"}, + {"ctrl + alt + !", 0, gocui.ModAlt, "unsupported keybinding: KeyCtrl! (+1)"}, + } + + for idx, trial := range table { + actualKey, actualErr := getKeybinding(trial.input) + + if actualKey.value != trial.key { + t.Errorf("Expected key '%+v' but got '%+v' (trial %d)", trial.key, actualKey, idx) + } + + if actualKey.modifier != trial.modifier { + t.Errorf("Expected modifier '%+v' but got '%+v' (trial %d)", trial.modifier, actualKey, idx) + } + + if actualErr == nil && trial.errStr != "" { + t.Errorf("Expected error message of '%s' but got no message (trial %d)", trial.errStr, idx) + } else if actualErr != nil && actualErr.Error() != trial.errStr { + t.Errorf("Expected error message '%s' but got '%s' (trial %d)", trial.errStr, actualErr.Error(), idx) + } + } +} diff --git a/ui/layerview.go b/ui/layerview.go index 9d13aeed..599c25d9 100644 --- a/ui/layerview.go +++ b/ui/layerview.go @@ -2,6 +2,7 @@ package ui import ( "fmt" + "github.com/spf13/viper" "github.com/dustin/go-humanize" "github.com/jroimartin/gocui" @@ -21,6 +22,9 @@ type LayerView struct { Layers []*image.Layer CompareMode CompareType CompareStartIndex int + + keybindingCompareAll []Key + keybindingCompareLayer []Key } // NewDetailsView creates a new view object attached the the global [gocui] screen object. @@ -33,6 +37,9 @@ func NewLayerView(name string, gui *gocui.Gui, layers []*image.Layer) (layerView layerView.Layers = layers layerView.CompareMode = CompareLayer + layerView.keybindingCompareAll = getKeybindings(viper.GetString("keybinding.compare-all")) + layerView.keybindingCompareLayer = getKeybindings(viper.GetString("keybinding.compare-layer")) + return layerView } @@ -57,11 +64,17 @@ func (view *LayerView) Setup(v *gocui.View, header *gocui.View) error { if err := view.gui.SetKeybinding(view.Name, gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorUp() }); err != nil { return err } - if err := view.gui.SetKeybinding(view.Name, gocui.KeyCtrlL, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.setCompareMode(CompareLayer) }); err != nil { - return err + + for _, key := range view.keybindingCompareLayer { + if err := view.gui.SetKeybinding("", key.value, key.modifier, func(*gocui.Gui, *gocui.View) error { return view.setCompareMode(CompareLayer) }); err != nil { + return err + } } - if err := view.gui.SetKeybinding(view.Name, gocui.KeyCtrlA, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.setCompareMode(CompareAll) }); err != nil { - return err + + for _, key := range view.keybindingCompareAll { + if err := view.gui.SetKeybinding("", key.value, key.modifier, func(*gocui.Gui, *gocui.View) error { return view.setCompareMode(CompareAll) }); err != nil { + return err + } } return view.Render() @@ -212,6 +225,6 @@ func (view *LayerView) Render() error { // KeyHelp indicates all the possible actions a user can take while the current pane is selected. func (view *LayerView) KeyHelp() string { - return renderStatusOption("^L", "Show layer changes", view.CompareMode == CompareLayer) + - renderStatusOption("^A", "Show aggregated changes", view.CompareMode == CompareAll) + return renderStatusOption(view.keybindingCompareLayer[0].String(), "Show layer changes", view.CompareMode == CompareLayer) + + renderStatusOption(view.keybindingCompareAll[0].String(), "Show aggregated changes", view.CompareMode == CompareAll) } diff --git a/ui/statusview.go b/ui/statusview.go index dc211433..9fe9cd0f 100644 --- a/ui/statusview.go +++ b/ui/statusview.go @@ -75,7 +75,7 @@ func (view *StatusView) Render() error { // KeyHelp indicates all the possible global actions a user can take when any pane is selected. func (view *StatusView) KeyHelp() string { - return renderStatusOption("^C", "Quit", false) + - renderStatusOption("TAB", "Switch view", false) + - renderStatusOption("^F", "Filter files", Views.Filter.IsVisible()) + return renderStatusOption(GlobalKeybindings.quit[0].String(), "Quit", false) + + renderStatusOption(GlobalKeybindings.toggleView[0].String(), "Switch view", false) + + renderStatusOption(GlobalKeybindings.filterView[0].String(), "Filter files", Views.Filter.IsVisible()) } diff --git a/ui/ui.go b/ui/ui.go index f1ca7772..a55c197d 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/fatih/color" "github.com/jroimartin/gocui" + "github.com/spf13/viper" "github.com/wagoodman/dive/filetree" "github.com/wagoodman/dive/image" "log" @@ -49,6 +50,12 @@ var Views struct { lookup map[string]View } +var GlobalKeybindings struct { + quit []Key + toggleView []Key + filterView []Key +} + // View defines the a renderable terminal screen pane. type View interface { Setup(*gocui.View, *gocui.View) error @@ -140,23 +147,22 @@ func quit(g *gocui.Gui, v *gocui.View) error { // keyBindings registers global key press actions, valid when in any pane. func keyBindings(g *gocui.Gui) error { - if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { - return err - } - //if err := g.SetKeybinding("main", gocui.MouseLeft, gocui.ModNone, toggleCollapse); err != nil { - // return err - //} - if err := g.SetKeybinding("", gocui.KeyCtrlSpace, gocui.ModNone, toggleView); err != nil { - return err - } - if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, toggleView); err != nil { - return err + for _, key := range GlobalKeybindings.quit { + if err := g.SetKeybinding("", key.value, key.modifier, quit); err != nil { + return err + } } - if err := g.SetKeybinding("", gocui.KeyCtrlSlash, gocui.ModNone, toggleFilterView); err != nil { - return err + + for _, key := range GlobalKeybindings.toggleView { + if err := g.SetKeybinding("", key.value, key.modifier, toggleView); err != nil { + return err + } } - if err := g.SetKeybinding("", gocui.KeyCtrlF, gocui.ModNone, toggleFilterView); err != nil { - return err + + for _, key := range GlobalKeybindings.filterView { + if err := g.SetKeybinding("", key.value, key.modifier, toggleFilterView); err != nil { + return err + } } return nil @@ -299,6 +305,10 @@ func Run(layers []*image.Layer, refTrees []*filetree.FileTree, efficiency float6 Formatting.CompareTop = color.New(color.BgMagenta).SprintFunc() Formatting.CompareBottom = color.New(color.BgGreen).SprintFunc() + GlobalKeybindings.quit = getKeybindings(viper.GetString("keybinding.quit")) + GlobalKeybindings.toggleView = getKeybindings(viper.GetString("keybinding.toggle-view")) + GlobalKeybindings.filterView = getKeybindings(viper.GetString("keybinding.filter-files")) + g, err := gocui.NewGui(gocui.OutputNormal) if err != nil { log.Panicln(err) @@ -330,9 +340,6 @@ func Run(layers []*image.Layer, refTrees []*filetree.FileTree, efficiency float6 Update() Render() - // let the default position of the cursor be the last layer - // Views.Layer.SetCursor(len(Views.Layer.Layers)-1) - if err := keyBindings(g); err != nil { log.Panicln(err) }