diff --git a/confirmation_panel.go b/confirmation_panel.go index 20d0ff821..362b75c49 100644 --- a/confirmation_panel.go +++ b/confirmation_panel.go @@ -55,7 +55,7 @@ func getConfirmationPanelDimensions(g *gocui.Gui, prompt string) (int, int, int, height/2 + panelHeight/2 } -func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, handleYes func(*gocui.Gui, *gocui.View) error) error { +func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, handleConfirm func(*gocui.Gui, *gocui.View) error) error { // only need to fit one line x0, y0, x1, y1 := getConfirmationPanelDimensions(g, "") if confirmationView, err := g.SetView("confirmation", x0, y0, x1, y1, 0); err != nil { @@ -69,12 +69,12 @@ func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, hand confirmationView.Title = title confirmationView.FgColor = gocui.ColorWhite switchFocus(g, currentView, confirmationView) - return setKeyBindings(g, handleYes, nil) + return setKeyBindings(g, handleConfirm, nil) } return nil } -func createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, handleYes, handleNo func(*gocui.Gui, *gocui.View) error) error { +func createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error { g.Update(func(g *gocui.Gui) error { // delete the existing confirmation panel if it exists if view, _ := g.View("confirmation"); view != nil { @@ -91,20 +91,36 @@ func createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, promp confirmationView.FgColor = gocui.ColorWhite renderString(g, "confirmation", prompt) switchFocus(g, currentView, confirmationView) - return setKeyBindings(g, handleYes, handleNo) + return setKeyBindings(g, handleConfirm, handleClose) } return nil }) return nil } -func setKeyBindings(g *gocui.Gui, handleYes, handleNo func(*gocui.Gui, *gocui.View) error) error { - renderString(g, "options", "esc: close, enter: confirm") - if err := g.SetKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone, wrappedConfirmationFunction(handleYes)); err != nil { - return err +func handleNewline(g *gocui.Gui, v *gocui.View) error { + // resising ahead of time so that the top line doesn't get hidden to make + // room for the cursor on the second line + x0, y0, x1, y1 := getConfirmationPanelDimensions(g, v.Buffer()) + if _, err := g.SetView("confirmation", x0, y0, x1, y1+1, 0); err != nil { + if err != gocui.ErrUnknownView { + return err + } } - return g.SetKeybinding("confirmation", gocui.KeyEsc, gocui.ModNone, wrappedConfirmationFunction(handleNo)) + v.EditNewLine() + return nil +} + +func setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error { + renderString(g, "options", "esc: close, enter: confirm") + if err := g.SetKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone, wrappedConfirmationFunction(handleConfirm)); err != nil { + return err + } + if err := g.SetKeybinding("confirmation", gocui.KeyTab, gocui.ModNone, handleNewline); err != nil { + return err + } + return g.SetKeybinding("confirmation", gocui.KeyEsc, gocui.ModNone, wrappedConfirmationFunction(handleClose)) } func createMessagePanel(g *gocui.Gui, currentView *gocui.View, title, prompt string) error { @@ -117,3 +133,23 @@ func createErrorPanel(g *gocui.Gui, message string) error { coloredMessage := colorFunction(strings.TrimSpace(message)) return createConfirmationPanel(g, currentView, "Error", coloredMessage, nil, nil) } + +func trimTrailingNewline(str string) string { + if strings.HasSuffix(str, "\n") { + return str[:len(str)-1] + } + return str +} + +func resizeConfirmationPanel(g *gocui.Gui) error { + // If the confirmation panel is already displayed, just resize the width, + // otherwise continue + if v, err := g.View("confirmation"); err == nil { + content := trimTrailingNewline(v.Buffer()) + x0, y0, x1, y1 := getConfirmationPanelDimensions(g, content) + if _, err = g.SetView("confirmation", x0, y0, x1, y1, 0); err != nil { + return err + } + } + return nil +} diff --git a/docs/Keybindings.md b/docs/Keybindings.md index e15bce3b9..cefafeba5 100644 --- a/docs/Keybindings.md +++ b/docs/Keybindings.md @@ -2,11 +2,11 @@ ## Global: - ← → ↑ ↓: navigate - PgUp/PgDn: scroll diff panel (use fn+up/down on osx) - q: quit - p: pull - shift+P: push + ← → ↑ ↓/h j k l: navigate + PgUp/PgDn: scroll diff panel (use fn+up/down on osx) + q: quit + p: pull + shift+P: push ## Files Panel: @@ -39,7 +39,7 @@ ## Stash Panel: space: apply - k: pop + g: pop d: drop ## Popup Panel: @@ -49,8 +49,8 @@ ## Resolving Merge Conflicts (Diff Panel): - ← →: navigate conflicts - ↑ ↓: select hunk - space: pick hunk - b: pick both hunks - z: undo (only available while still inside diff panel) + ← →/h l: navigate conflicts + ↑ ↓/ k j: select hunk + space: pick hunk + b: pick both hunks + z: undo (only available while still inside diff panel) diff --git a/gui.go b/gui.go index 4765423f1..4d5d0b679 100644 --- a/gui.go +++ b/gui.go @@ -204,12 +204,8 @@ func layout(g *gocui.Gui) error { v.Frame = false } - // If the confirmation panel is already displayed, just resize the width, - // otherwise continue - if v, err := g.View("confirmation"); err == nil { - _, y := v.Size() - x0, y0, x1, _ := getConfirmationPanelDimensions(g, "") - g.SetView("confirmation", x0, y0, x1, y0+y+1, 0) + if err = resizeConfirmationPanel(g); err != nil { + return err } if v, err := g.SetView("version", width-len(version)-1, optionsTop, width, optionsTop+2, 0); err != nil { diff --git a/keybindings.go b/keybindings.go index 5ab572d81..0fb800d6e 100644 --- a/keybindings.go +++ b/keybindings.go @@ -14,13 +14,8 @@ type Binding struct { func keybindings(g *gocui.Gui) error { bindings := []Binding{ - Binding{ViewName: "", Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: previousView}, - Binding{ViewName: "", Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: nextView}, - Binding{ViewName: "", Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: nextView}, Binding{ViewName: "", Key: 'q', Modifier: gocui.ModNone, Handler: quit}, Binding{ViewName: "", Key: gocui.KeyCtrlC, Modifier: gocui.ModNone, Handler: quit}, - Binding{ViewName: "", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: cursorDown}, - Binding{ViewName: "", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: cursorUp}, Binding{ViewName: "", Key: gocui.KeyPgup, Modifier: gocui.ModNone, Handler: scrollUpMain}, Binding{ViewName: "", Key: gocui.KeyPgdn, Modifier: gocui.ModNone, Handler: scrollDownMain}, Binding{ViewName: "", Key: 'P', Modifier: gocui.ModNone, Handler: pushFiles}, @@ -40,13 +35,17 @@ func keybindings(g *gocui.Gui) error { Binding{ViewName: "files", Key: 'a', Modifier: gocui.ModNone, Handler: handleAbortMerge}, Binding{ViewName: "files", Key: 't', Modifier: gocui.ModNone, Handler: handleAddPatch}, Binding{ViewName: "files", Key: 'D', Modifier: gocui.ModNone, Handler: handleResetHard}, - Binding{ViewName: "main", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: handleSelectTop}, - Binding{ViewName: "main", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: handleSelectBottom}, Binding{ViewName: "main", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: handleEscapeMerge}, Binding{ViewName: "main", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handlePickHunk}, Binding{ViewName: "main", Key: 'b', Modifier: gocui.ModNone, Handler: handlePickBothHunks}, Binding{ViewName: "main", Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: handleSelectPrevConflict}, Binding{ViewName: "main", Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: handleSelectNextConflict}, + Binding{ViewName: "main", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: handleSelectTop}, + Binding{ViewName: "main", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: handleSelectBottom}, + Binding{ViewName: "main", Key: 'h', Modifier: gocui.ModNone, Handler: handleSelectPrevConflict}, + Binding{ViewName: "main", Key: 'l', Modifier: gocui.ModNone, Handler: handleSelectNextConflict}, + Binding{ViewName: "main", Key: 'k', Modifier: gocui.ModNone, Handler: handleSelectTop}, + Binding{ViewName: "main", Key: 'j', Modifier: gocui.ModNone, Handler: handleSelectBottom}, Binding{ViewName: "main", Key: 'z', Modifier: gocui.ModNone, Handler: handlePopFileSnapshot}, Binding{ViewName: "branches", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleBranchPress}, Binding{ViewName: "branches", Key: 'c', Modifier: gocui.ModNone, Handler: handleCheckoutByName}, @@ -57,9 +56,26 @@ func keybindings(g *gocui.Gui) error { Binding{ViewName: "commits", Key: 'r', Modifier: gocui.ModNone, Handler: handleRenameCommit}, Binding{ViewName: "commits", Key: 'g', Modifier: gocui.ModNone, Handler: handleResetToCommit}, Binding{ViewName: "stash", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleStashApply}, - Binding{ViewName: "stash", Key: 'k', Modifier: gocui.ModNone, Handler: handleStashPop}, + Binding{ViewName: "stash", Key: 'g', Modifier: gocui.ModNone, Handler: handleStashPop}, Binding{ViewName: "stash", Key: 'd', Modifier: gocui.ModNone, Handler: handleStashDrop}, } + + // Would make these keybindings global but that interferes with editing + // input in the confirmation panel + for _, viewName := range []string{"files", "branches", "commits", "stash"} { + bindings = append(bindings, []Binding{ + Binding{ViewName: viewName, Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: nextView}, + Binding{ViewName: viewName, Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: previousView}, + Binding{ViewName: viewName, Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: nextView}, + Binding{ViewName: viewName, Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: cursorUp}, + Binding{ViewName: viewName, Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: cursorDown}, + Binding{ViewName: viewName, Key: 'h', Modifier: gocui.ModNone, Handler: previousView}, + Binding{ViewName: viewName, Key: 'l', Modifier: gocui.ModNone, Handler: nextView}, + Binding{ViewName: viewName, Key: 'k', Modifier: gocui.ModNone, Handler: cursorUp}, + Binding{ViewName: viewName, Key: 'j', Modifier: gocui.ModNone, Handler: cursorDown}, + }...) + } + for _, binding := range bindings { if err := g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, binding.Handler); err != nil { return err diff --git a/stash_panel.go b/stash_panel.go index 9fe386ac7..33c7e297b 100644 --- a/stash_panel.go +++ b/stash_panel.go @@ -33,7 +33,7 @@ func getSelectedStashEntry(v *gocui.View) *StashEntry { func renderStashOptions(g *gocui.Gui) error { return renderOptionsMap(g, map[string]string{ "space": "apply", - "k": "pop", + "g": "pop", "d": "drop", "← → ↑ ↓": "navigate", })