diff --git a/go.mod b/go.mod index de2f7f7c..3cc56535 100644 --- a/go.mod +++ b/go.mod @@ -23,9 +23,7 @@ require ( github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a github.com/mattn/go-colorable v0.0.9 // indirect github.com/mattn/go-isatty v0.0.4 // indirect - github.com/mattn/go-runewidth v0.0.3 // indirect github.com/mitchellh/go-homedir v1.0.0 - github.com/nsf/termbox-go v0.0.0-20181027232701-60ab7e3d12ed // indirect github.com/opencontainers/go-digest v1.0.0-rc1 // indirect github.com/opencontainers/image-spec v1.0.1 // indirect github.com/phayes/permbits v0.0.0-20180830030258-59f2482cd460 @@ -33,6 +31,7 @@ require ( github.com/sirupsen/logrus v1.2.0 github.com/spf13/cobra v0.0.3 github.com/spf13/viper v1.2.1 + github.com/wagoodman/keybinding v0.0.0-20181213133715-6a824da6df05 golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 // indirect golang.org/x/net v0.0.0-20181114220301-adae6a3d119a golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect diff --git a/go.sum b/go.sum index c99b782c..c89fcbea 100644 --- a/go.sum +++ b/go.sum @@ -102,6 +102,8 @@ github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaN github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/wagoodman/keybinding v0.0.0-20181213133715-6a824da6df05 h1:YMcRwVDe8DLDZ/vrhuImCfqjjG/+gZs6SF61DDQkL/8= +github.com/wagoodman/keybinding v0.0.0-20181213133715-6a824da6df05/go.mod h1:gXFkc2sM2o06uzn5Lgo6Ql76uweGdxNfeAlFyKiHAdk= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 h1:kkXA53yGe04D0adEYJwEVQjeBppL01Exg+fnMjfUraU= golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= diff --git a/ui/filetreeview.go b/ui/filetreeview.go index db03bad7..8f42cd4e 100644 --- a/ui/filetreeview.go +++ b/ui/filetreeview.go @@ -5,6 +5,8 @@ import ( "github.com/sirupsen/logrus" "github.com/spf13/viper" "github.com/wagoodman/dive/utils" + "github.com/wagoodman/keybinding" + "log" "regexp" "strings" @@ -37,13 +39,13 @@ type FileTreeView struct { bufferIndexUpperBound uint bufferIndexLowerBound uint - keybindingToggleCollapse []Key - keybindingToggleAdded []Key - keybindingToggleRemoved []Key - keybindingToggleModified []Key - keybindingToggleUnchanged []Key - keybindingPageDown []Key - keybindingPageUp []Key + keybindingToggleCollapse []keybinding.Key + keybindingToggleAdded []keybinding.Key + keybindingToggleRemoved []keybinding.Key + keybindingToggleModified []keybinding.Key + keybindingToggleUnchanged []keybinding.Key + keybindingPageDown []keybinding.Key + keybindingPageUp []keybinding.Key } // NewFileTreeView creates a new view object attached the the global [gocui] screen object. @@ -74,13 +76,41 @@ func NewFileTreeView(name string, gui *gocui.Gui, tree *filetree.FileTree, refTr } } - 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")) + var err error + treeView.keybindingToggleCollapse, err = keybinding.ParseAll(viper.GetString("keybinding.toggle-collapse-dir")) + if err != nil { + log.Panicln(err) + } + + treeView.keybindingToggleAdded, err = keybinding.ParseAll(viper.GetString("keybinding.toggle-added-files")) + if err != nil { + log.Panicln(err) + } + + treeView.keybindingToggleRemoved, err = keybinding.ParseAll(viper.GetString("keybinding.toggle-removed-files")) + if err != nil { + log.Panicln(err) + } + + treeView.keybindingToggleModified, err = keybinding.ParseAll(viper.GetString("keybinding.toggle-modified-files")) + if err != nil { + log.Panicln(err) + } + + treeView.keybindingToggleUnchanged, err = keybinding.ParseAll(viper.GetString("keybinding.toggle-unchanged-files")) + if err != nil { + log.Panicln(err) + } + + treeView.keybindingPageUp, err = keybinding.ParseAll(viper.GetString("keybinding.page-up")) + if err != nil { + log.Panicln(err) + } + + treeView.keybindingPageDown, err = keybinding.ParseAll(viper.GetString("keybinding.page-down")) + if err != nil { + log.Panicln(err) + } return treeView } @@ -114,37 +144,37 @@ func (view *FileTreeView) Setup(v *gocui.View, header *gocui.View) error { } for _, key := range view.keybindingPageUp { - if err := view.gui.SetKeybinding(view.Name, key.value, key.modifier, func(*gocui.Gui, *gocui.View) error { return view.PageUp() }); err != nil { + if err := view.gui.SetKeybinding(view.Name, key.Value, key.Modifier, func(*gocui.Gui, *gocui.View) error { return view.PageUp() }); err != nil { return err } } for _, key := range view.keybindingPageDown { - if err := view.gui.SetKeybinding(view.Name, key.value, key.modifier, func(*gocui.Gui, *gocui.View) error { return view.PageDown() }); err != nil { + if err := view.gui.SetKeybinding(view.Name, key.Value, key.Modifier, func(*gocui.Gui, *gocui.View) error { return view.PageDown() }); err != nil { return err } } for _, key := range view.keybindingToggleCollapse { - if err := view.gui.SetKeybinding(view.Name, key.value, key.modifier, func(*gocui.Gui, *gocui.View) error { return view.toggleCollapse() }); err != nil { + if err := view.gui.SetKeybinding(view.Name, key.Value, key.Modifier, func(*gocui.Gui, *gocui.View) error { return view.toggleCollapse() }); err != nil { return err } } for _, key := range view.keybindingToggleAdded { - if err := view.gui.SetKeybinding(view.Name, key.value, key.modifier, func(*gocui.Gui, *gocui.View) error { return view.toggleShowDiffType(filetree.Added) }); err != nil { + if err := view.gui.SetKeybinding(view.Name, key.Value, key.Modifier, func(*gocui.Gui, *gocui.View) error { return view.toggleShowDiffType(filetree.Added) }); err != nil { return err } } for _, key := range view.keybindingToggleRemoved { - if err := view.gui.SetKeybinding(view.Name, key.value, key.modifier, func(*gocui.Gui, *gocui.View) error { return view.toggleShowDiffType(filetree.Removed) }); err != nil { + if err := view.gui.SetKeybinding(view.Name, key.Value, key.Modifier, func(*gocui.Gui, *gocui.View) error { return view.toggleShowDiffType(filetree.Removed) }); err != nil { return err } } for _, key := range view.keybindingToggleModified { - if err := view.gui.SetKeybinding(view.Name, key.value, key.modifier, func(*gocui.Gui, *gocui.View) error { return view.toggleShowDiffType(filetree.Changed) }); err != nil { + if err := view.gui.SetKeybinding(view.Name, key.Value, key.Modifier, func(*gocui.Gui, *gocui.View) error { return view.toggleShowDiffType(filetree.Changed) }); err != nil { return err } } for _, key := range view.keybindingToggleUnchanged { - if err := view.gui.SetKeybinding(view.Name, key.value, key.modifier, func(*gocui.Gui, *gocui.View) error { return view.toggleShowDiffType(filetree.Unchanged) }); err != nil { + if err := view.gui.SetKeybinding(view.Name, key.Value, key.Modifier, func(*gocui.Gui, *gocui.View) error { return view.toggleShowDiffType(filetree.Unchanged) }); err != nil { return err } } diff --git a/ui/keybinding.go b/ui/keybinding.go deleted file mode 100644 index 06675821..00000000 --- a/ui/keybinding.go +++ /dev/null @@ -1,186 +0,0 @@ -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 deleted file mode 100644 index be4954d1..00000000 --- a/ui/keybinding_test.go +++ /dev/null @@ -1,55 +0,0 @@ -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 b29b8742..98dce2e1 100644 --- a/ui/layerview.go +++ b/ui/layerview.go @@ -4,6 +4,8 @@ import ( "fmt" "github.com/spf13/viper" "github.com/wagoodman/dive/utils" + "github.com/wagoodman/keybinding" + "log" "github.com/jroimartin/gocui" "github.com/lunixbochs/vtclean" @@ -24,8 +26,8 @@ type LayerView struct { CompareStartIndex int ImageSize uint64 - keybindingCompareAll []Key - keybindingCompareLayer []Key + keybindingCompareAll []keybinding.Key + keybindingCompareLayer []keybinding.Key } // NewDetailsView creates a new view object attached the the global [gocui] screen object. @@ -46,8 +48,16 @@ func NewLayerView(name string, gui *gocui.Gui, layers []image.Layer) (layerView utils.PrintAndExit(fmt.Sprintf("unknown layer.show-aggregated-changes value: %v", mode)) } - layerView.keybindingCompareAll = getKeybindings(viper.GetString("keybinding.compare-all")) - layerView.keybindingCompareLayer = getKeybindings(viper.GetString("keybinding.compare-layer")) + var err error + layerView.keybindingCompareAll, err = keybinding.ParseAll(viper.GetString("keybinding.compare-all")) + if err != nil { + log.Panicln(err) + } + + layerView.keybindingCompareLayer, err = keybinding.ParseAll(viper.GetString("keybinding.compare-layer")) + if err != nil { + log.Panicln(err) + } return layerView } @@ -75,13 +85,13 @@ func (view *LayerView) Setup(v *gocui.View, header *gocui.View) error { } for _, key := range view.keybindingCompareLayer { - if err := view.gui.SetKeybinding(view.Name, key.value, key.modifier, func(*gocui.Gui, *gocui.View) error { return view.setCompareMode(CompareLayer) }); err != nil { + if err := view.gui.SetKeybinding(view.Name, key.Value, key.Modifier, func(*gocui.Gui, *gocui.View) error { return view.setCompareMode(CompareLayer) }); err != nil { return err } } for _, key := range view.keybindingCompareAll { - if err := view.gui.SetKeybinding(view.Name, key.value, key.modifier, func(*gocui.Gui, *gocui.View) error { return view.setCompareMode(CompareAll) }); err != nil { + if err := view.gui.SetKeybinding(view.Name, key.Value, key.Modifier, func(*gocui.Gui, *gocui.View) error { return view.setCompareMode(CompareAll) }); err != nil { return err } } diff --git a/ui/ui.go b/ui/ui.go index dae772c7..ccdcd3ff 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -10,6 +10,7 @@ import ( "github.com/wagoodman/dive/filetree" "github.com/wagoodman/dive/image" "github.com/wagoodman/dive/utils" + "github.com/wagoodman/keybinding" "log" ) @@ -54,9 +55,9 @@ var Views struct { } var GlobalKeybindings struct { - quit []Key - toggleView []Key - filterView []Key + quit []keybinding.Key + toggleView []keybinding.Key + filterView []keybinding.Key } // View defines the a renderable terminal screen pane. @@ -152,19 +153,19 @@ 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 { for _, key := range GlobalKeybindings.quit { - if err := g.SetKeybinding("", key.value, key.modifier, quit); err != nil { + if err := g.SetKeybinding("", key.Value, key.Modifier, quit); err != nil { return err } } for _, key := range GlobalKeybindings.toggleView { - if err := g.SetKeybinding("", key.value, key.modifier, toggleView); err != nil { + if err := g.SetKeybinding("", key.Value, key.Modifier, toggleView); err != nil { return err } } for _, key := range GlobalKeybindings.filterView { - if err := g.SetKeybinding("", key.value, key.modifier, toggleFilterView); err != nil { + if err := g.SetKeybinding("", key.Value, key.Modifier, toggleFilterView); err != nil { return err } } @@ -314,9 +315,19 @@ func Run(analysis *image.AnalysisResult, cache filetree.TreeCache) { 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")) + var err error + GlobalKeybindings.quit, err = keybinding.ParseAll(viper.GetString("keybinding.quit")) + if err != nil { + log.Panicln(err) + } + GlobalKeybindings.toggleView, err = keybinding.ParseAll(viper.GetString("keybinding.toggle-view")) + if err != nil { + log.Panicln(err) + } + GlobalKeybindings.filterView, err = keybinding.ParseAll(viper.GetString("keybinding.filter-files")) + if err != nil { + log.Panicln(err) + } g, err := gocui.NewGui(gocui.OutputNormal) if err != nil {