summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2018-08-11 15:09:37 +1000
committerJesse Duffield <jessedduffield@gmail.com>2018-08-11 15:09:37 +1000
commit7e141283f64c87a526aec20599e1bd76e8829778 (patch)
tree30f5ddfdf27ec9af75dc001154de322e08f72a7c
parente46f3b33935c666066c5f0716efc2439698a7101 (diff)
merge branch master
-rw-r--r--Gopkg.lock1
-rw-r--r--README.md5
-rw-r--r--branch.go35
-rw-r--r--branch_list_builder.go123
-rw-r--r--branches_panel.go4
-rw-r--r--commit_message_panel.go43
-rw-r--r--confirmation_panel.go18
-rw-r--r--files_panel.go18
-rw-r--r--gitcommands.go156
-rw-r--r--gui.go21
-rw-r--r--keybindings.go111
-rw-r--r--main.go4
-rw-r--r--merge_panel.go2
-rwxr-xr-xscripts/push_new_patch.go (renamed from bin/push_new_patch.go)0
-rw-r--r--status_panel.go10
-rw-r--r--utils.go54
-rw-r--r--view_helpers.go34
17 files changed, 388 insertions, 251 deletions
diff --git a/Gopkg.lock b/Gopkg.lock
index f15d99700..4323983be 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -288,6 +288,7 @@
"github.com/jesseduffield/gocui",
"github.com/tcnksm/go-gitconfig",
"gopkg.in/src-d/go-git.v4",
+ "gopkg.in/src-d/go-git.v4/plumbing",
"gopkg.in/src-d/go-git.v4/plumbing/object",
]
solver-name = "gps-cdcl"
diff --git a/README.md b/README.md
index 0bd9b310f..9449977cd 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,8 @@ Are YOU tired of typing every git command directly into the terminal, but you're
![Gif](https://image.ibb.co/mmeXho/optimisedgif.gif)
+[Twitch Stream](https://www.twitch.tv/jesseduffield)
+
## Installation
### Homebrew
@@ -74,3 +76,6 @@ We love your input! Please check out the [contributing guide](CONTRIBUTING.md).
## Work in progress
This is still a work in progress so there's still bugs to iron out and as this is my first project in Go the code could no doubt use an increase in quality, but I'll be improving on it whenever I find the time. If you have any feedback feel free to [raise an issue](https://github.com/jesseduffield/lazygit/issues)/[submit a PR](https://github.com/jesseduffield/lazygit/pulls).
+
+## Social
+If you want to see what I (Jesse) am up to in terms of development, follow me on [twitter](https://twitter.com/DuffieldJesse) or watch me program on [twitch](https://www.twitch.tv/jesseduffield)
diff --git a/branch.go b/branch.go
new file mode 100644
index 000000000..78c2e55aa
--- /dev/null
+++ b/branch.go
@@ -0,0 +1,35 @@
+package main
+
+import (
+ "strings"
+
+ "github.com/fatih/color"
+)
+
+// Branch : A git branch
+type Branch struct {
+ Name string
+ Recency string
+}
+
+func (b *Branch) getDisplayString() string {
+ return withPadding(b.Recency, 4) + coloredString(b.Name, b.getColor())
+}
+
+func (b *Branch) getColor() color.Attribute {
+ switch b.getType() {
+ case "feature":
+ return color.FgGreen
+ case "bugfix":
+ return color.FgYellow
+ case "hotfix":
+ return color.FgRed
+ default:
+ return color.FgWhite
+ }
+}
+
+// expected to return feature/bugfix/hotfix or blank string
+func (b *Branch) getType() string {
+ return strings.Split(b.Name, "/")[0]
+}
diff --git a/branch_list_builder.go b/branch_list_builder.go
new file mode 100644
index 000000000..113a5a44a
--- /dev/null
+++ b/branch_list_builder.go
@@ -0,0 +1,123 @@
+package main
+
+import (
+ "regexp"
+ "strings"
+
+ "gopkg.in/src-d/go-git.v4/plumbing"
+)
+
+// context:
+// we want to only show 'safe' branches (ones that haven't e.g. been deleted)
+// which `git branch -a` gives us, but we also want the recency data that
+// git reflog gives us.
+// So we get the HEAD, then append get the reflog branches that intersect with
+// our safe branches, then add the remaining safe branches, ensuring uniqueness
+// along the way
+
+type branchListBuilder struct{}
+
+func newBranchListBuilder() *branchListBuilder {
+ return &branchListBuilder{}
+}
+
+func (b *branchListBuilder) obtainCurrentBranch() Branch {
+ // I used go-git for this, but that breaks if you've just done a git init,
+ // even though you're on 'master'
+ branchName, _ := runDirectCommand("git symbolic-ref --short HEAD")
+ return Branch{Name: strings.TrimSpace(branchName), Recency: " *"}
+}
+
+func (*branchListBuilder) obtainReflogBranches() []Branch {
+ branches := make([]Branch, 0)
+ rawString, err := runDirectCommand("git reflog -n100 --pretty='%cr|%gs' --grep-reflog='checkout: moving' HEAD")
+ if err != nil {
+ return branches
+ }
+
+ branchLines := splitLines(rawString)
+ for _, line := range branchLines {
+ timeNumber, timeUnit, branchName := branchInfoFromLine(line)
+ timeUnit = abbreviatedTimeUnit(timeUnit)
+ branch := Branch{Name: branchName, Recency: timeNumber + timeUnit}
+ branches = append(branches, branch)
+ }
+ return branches
+}
+
+func (b *branchListBuilder) obtainSafeBranches() []Branch {
+ branches := make([]Branch, 0)
+
+ bIter, err := r.Branches()
+ if err != nil {
+ panic(err)
+ }
+ err = bIter.ForEach(func(b *plumbing.Reference) error {
+ name := b.Name().Short()
+ branches = append(branches, Branch{Name: name})
+ return nil
+ })
+
+ return branches
+}
+
+func (b *branchListBuilder) appendNewBranches(finalBranches, newBranches, existingBranches []Branch, included bool) []Branch {
+ for _, newBranch := range newBranches {
+ if included == branchIncluded(newBranch.Name, existingBranches) {
+ finalBranches = append(finalBranches, newBranch)
+ }
+
+ }
+ return finalBranches
+}
+
+func (b *branchListBuilder) build() []Branch {
+ branches := make([]Branch, 0)
+ head := b.obtainCurrentBranch()
+ safeBranches := b.obtainSafeBranches()
+ if len(safeBranches) == 0 {
+ return append(branches, head)
+ }
+ reflogBranches := b.obtainReflogBranches()
+ reflogBranches = uniqueByName(append([]Branch{head}, reflogBranches...))
+
+ branches = b.appendNewBranches(branches, reflogBranches, safeBranches, true)
+ branches = b.appendNewBranches(branches, safeBranches, branches, false)
+
+ return branches
+}
+
+func uniqueByName(branches []Branch) []Branch {
+ finalBranches := make([]Branch, 0)
+ for _, branch := range branches {
+ if branchIncluded(branch.Name, finalBranches) {
+ continue
+ }
+ finalBranches = append(finalBranches, branch)
+ }
+ return finalBranches
+}
+
+// A line will have the form '10 days ago master' so we need to strip out the
+// useful information from that into timeNumber, timeUnit, and branchName
+func branchInfoFromLine(line string) (string, string, string) {
+ r := regexp.MustCompile("\\|.*\\s")
+ line = r.ReplaceAllString(line, " ")
+ words := strings.Split(line, " ")
+ return words[0], words[1], words[3]
+}
+
+func abbreviatedTimeUnit(timeUnit string) string {
+ r := regexp.MustCompile("s$")
+ timeUnit = r.ReplaceAllString(timeUnit, "")
+ timeUnitMap := map[string]string{
+ "hour": "h",
+ "minute": "m",
+ "second": "s",
+ "week": "w",
+ "year": "y",
+ "day": "d",
+ "month": "m",
+ }
+ return timeUnitMap[timeUnit]
+}
diff --git a/branches_panel.go b/branches_panel.go
index 0b8508a3d..3102f0c28 100644
--- a/branches_panel.go
+++ b/branches_panel.go
@@ -91,7 +91,7 @@ func handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
}
go func() {
branch := getSelectedBranch(v)
- diff, err := getBranchGraph(branch.Name, branch.BaseBranch)
+ diff, err := getBranchGraph(branch.Name)
if err != nil && strings.HasPrefix(diff, "fatal: ambiguous argument") {
diff = "There is no tracking for this branch"
}
@@ -111,7 +111,7 @@ func refreshBranches(g *gocui.Gui) error {
state.Branches = getGitBranches()
v.Clear()
for _, branch := range state.Branches {
- fmt.Fprintln(v, branch.DisplayString)
+ fmt.Fprintln(v, branch.getDisplayString())
}
resetOrigin(v)
return refreshStatus(g)
diff --git a/commit_message_panel.go b/commit_message_panel.go
new file mode 100644
index 000000000..49551c1eb
--- /dev/null
+++ b/commit_message_panel.go
@@ -0,0 +1,43 @@
+package main
+
+import "github.com/jesseduffield/gocui"
+
+func handleCommitConfirm(g *gocui.Gui, v *gocui.View) error {
+ message := trimmedContent(v)
+ if message == "" {
+ return createErrorPanel(g, "You cannot commit without a commit message")
+ }
+ if output, err := gitCommit(g, message); err != nil {
+ if err == errNoUsername {
+ return createErrorPanel(g, err.Error())
+ }
+ return createErrorPanel(g, output)
+ }
+ refreshFiles(g)
+ g.SetViewOnBottom("commitMessage")
+ switchFocus(g, v, getFilesView(g))
+ return refreshCommits(g)
+}
+
+func handleCommitClose(g *gocui.Gui, v *gocui.View) error {
+ g.SetViewOnBottom("commitMessage")
+ return switchFocus(g, v, getFilesView(g))
+}
+
+func handleNewlineCommitMessage(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("commitMessage", x0, y0, x1, y1+1, 0); err != nil {
+ if err != gocui.ErrUnknownView {
+ return err
+ }
+ }
+
+ v.EditNewLine()
+ return nil
+}
+
+func handleCommitFocused(g *gocui.Gui, v *gocui.View) error {
+ return renderString(g, "options", "esc: close, enter: confirm")
+}
diff --git a/confirmation_panel.go b/confirmation_panel.go
index 362b75c49..563db72d8 100644
--- a/confirmation_panel.go
+++ b/confirmation_panel.go
@@ -56,6 +56,7 @@ func getConfirmationPanelDimensions(g *gocui.Gui, prompt string) (int, int, int,
}
func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, handleConfirm func(*gocui.Gui, *gocui.View) error) error {
+ g.SetViewOnBottom("commitMessage")
// 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 {
@@ -63,11 +64,8 @@ func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, hand
return err
}
- g.Cursor = true
-
confirmationView.Editable = true
confirmationView.Title = title
- confirmationView.FgColor = gocui.ColorWhite
switchFocus(g, currentView, confirmationView)
return setKeyBindings(g, handleConfirm, nil)
}
@@ -75,6 +73,7 @@ func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, hand
}
func createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
+ g.SetViewOnBottom("commitMessage")
g.Update(func(g *gocui.Gui) error {
// delete the existing confirmation panel if it exists
if view, _ := g.View("confirmation"); view != nil {
@@ -141,15 +140,20 @@ func trimTrailingNewline(str string) string {
return str
}
-func resizeConfirmationPanel(g *gocui.Gui) error {
+func resizeConfirmationPanel(g *gocui.Gui, viewName string) error {
// If the confirmation panel is already displayed, just resize the width,
// otherwise continue
- if v, err := g.View("confirmation"); err == nil {
+ g.Update(func(g *gocui.Gui) error {
+ v, err := g.View(viewName)
+ if err != nil {
+ return nil
+ }
content := trimTrailingNewline(v.Buffer())
x0, y0, x1, y1 := getConfirmationPanelDimensions(g, content)
- if _, err = g.SetView("confirmation", x0, y0, x1, y1, 0); err != nil {
+ if _, err := g.SetView(viewName, x0, y0, x1, y1, 0); err != nil {
return err
}
- }
+ return nil
+ })
return nil
}
diff --git a/files_panel.go b/files_panel.go
index e645c6cb6..2d7406c45 100644
--- a/files_panel.go
+++ b/files_panel.go
@@ -177,19 +177,11 @@ func handleCommitPress(g *gocui.Gui, filesView *gocui.View) error {
if len(stagedFiles(state.GitFiles)) == 0 && !state.HasMergeConflicts {
return createErrorPanel(g, "There are no staged files to commit")
}
- createPromptPanel(g, filesView, "Commit message", func(g *gocui.Gui, v *gocui.View) error {
- message := trimmedContent(v)
- if message == "" {
- return createErrorPanel(g, "You cannot commit without a commit message")
- }
- if output, err := gitCommit(g, message); err != nil {
- if err == errNoUsername {
- return createErrorPanel(g, err.Error())
- }
- return createErrorPanel(g, output)
- }
- refreshFiles(g)
- return refreshCommits(g)
+ commitMessageView := getCommitMessageView(g)
+ g.Update(func(g *gocui.Gui) error {
+ g.SetViewOnTop("commitMessage")
+ switchFocus(g, filesView, commitMessageView)
+ return nil
})
return nil
}
diff --git a/gitcommands.go b/gitcommands.go
index 3db1df1ee..f157fa79c 100644
--- a/gitcommands.go
+++ b/gitcommands.go
@@ -7,11 +7,9 @@ import (
"fmt"
"os"
"os/exec"
- "regexp"
"strings"
"time"
- "github.com/fatih/color"
"github.com/jesseduffield/gocui"
gitconfig "github.com/tcnksm/go-gitconfig"
git "gopkg.in/src-d/go-git.v4"
@@ -19,9 +17,6 @@ import (
)
var (
- // ErrNoCheckedOutBranch : When we have no checked out branch
- ErrNoCheckedOutBranch = errors.New("No currently checked out branch")
-
// ErrNoOpenCommand : When we don't know which command to use to open a file
ErrNoOpenCommand = errors.New("Unsure what command to use to open this file")
)
@@ -38,14 +33,6 @@ type GitFile struct {
DisplayString string
}
-// Branch : A git branch
-type Branch struct {
- Name string
- Type string
- BaseBranch string
- DisplayString string
-}
-
// Commit : A git commit
type Commit struct {
Sha string
@@ -144,29 +131,6 @@ func branchStringParts(branchString string) (string, string) {
return splitBranchName[0], splitBranchName[1]
}
-// branchPropertiesFromName : returns branch type, base, and color
-func branchPropertiesFromName(name string) (string, string, color.Attribute) {
- if strings.Contains(name, "feature/") {
- return "feature", "develop", color.FgGreen
- } else if strings.Contains(name, "bugfix/") {
- return "bugfix", "develop", color.FgYellow
- } else if strings.Contains(name, "hotfix/") {
- return "hotfix", "master", color.FgRed
- }
- return "other", name, color.FgWhite
-}
-
-func coloredString(str string, colour *color.Color) string {
- return colour.SprintFunc()(fmt.Sprint(str))
-}
-
-func withPadding(str string, padding int) string {
- if padding-len(str) < 0 {
- return str
- }
- return str + strings.Repeat(" ", padding-len(str))
-}
-
// TODO: DRY up this function and getGitBranches
func getGitStashEntries() []StashEntry {
stashEntries := make([]StashEntry, 0)
@@ -325,12 +289,8 @@ func runSubProcess(g *gocui.Gui, cmdName string, commandArgs ...string) {
})
}
-func getBranchGraph(branch string, baseBranch string) (string, error) {
+func getBranchGraph(branch string) (string, error) {
return runCommand("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 " + branch)
-
- // Leaving this guy commented out in case there's backlash from the design
- // change and I want to make this configurable
- // return runCommand("git log -p -30 --color --no-merges " + branch)
}
func verifyInGitRepo() {
@@ -476,11 +436,7 @@ func gitPull() (string, error) {
}
func gitPush() (string, error) {
- branchName := gitCurrentBranchName()
- if branchName == "" {
- return "", ErrNoCheckedOutBranch
- }
- return runDirectCommand("git push -u origin " + branchName)
+ return runDirectCommand("git push -u origin " + state.Branches[0].Name)
}
func gitSquashPreviousTwoCommits(message string) (string, error) {
@@ -562,120 +518,20 @@ func gitCommitsToPush() []string {
return splitLines(pushables)
}
-func gitCurrentBranchName() string {
- branchName, err := runDirectCommand("git symbolic-ref --short HEAD")
- // if there is an error, assume there are no branches yet
- if err != nil {
- return ""
- }
- return strings.TrimSpace(branchName)
-}
-
-// A line will have the form '10 days ago master' so we need to strip out the
-// useful information from that into timeNumber, timeUnit, and branchName
-func branchInfoFromLine(line string) (string, string, string) {
- r := regexp.MustCompile("\\|.*\\s")
- line = r.ReplaceAllString(line, " ")
- words := strings.Split(line, " ")
- return words[0], words[1], words[3]
-}
-
-func abbreviatedTimeUnit(timeUnit string) string {
- r := regexp.MustCompile("s$")
- timeUnit = r.ReplaceAllString(timeUnit, "")
- timeUnitMap := map[string]string{
- "hour": "h",
- "minute": "m",
- "second": "s",
- "week": "w",
- "year": "y",
- "day": "d",
- "month": "m",
- }
- return timeUnitMap[timeUnit]
-}
-
-func getBranches() []Branch {
- branches := make([]Branch, 0)
- rawString, err := runDirectCommand("git reflog -n100 --pretty='%cr|%gs' --grep-reflog='checkout: moving' HEAD")
- if err != nil {
- return branches
- }
-
- branchLines := splitLines(rawString)
- for i, line := range branchLines {
- timeNumber, timeUnit, branchName := branchInfoFromLine(line)
- timeUnit = abbreviatedTimeUnit(timeUnit)
-
- if branchAlreadyStored(branchName, branches) {
- continue
- }
-
- branch := constructBranch(timeNumber+timeUnit, branchName, i)
- branches = append(branches, branch)
- }
- return branches
-}
-
-func constructBranch(prefix, name string, index int) Branch {
- branchType, branchBase, colourAttr := branchPropertiesFromName(name)
- if index == 0 {
- prefix = " *"
- }
- colour := color.New(colourAttr)
- displayString := withPadding(prefix, 4) + coloredString(name, colour)
- return Branch{
- Name: name,
- Type: branchType,
- BaseBranch: branchBase,
- DisplayString: displayString,
- }
-}
-
func getGitBranches() []Branch {
- // check if there are any branches
- branchCheck, _ := runCommand("git branch")
- if branchCheck == "" {
- return []Branch{constructBranch("", gitCurrentBranchName(), 0)}
- }
- branches := getBranches()
- if len(branches) == 0 {
- branches = append(branches, constructBranch("", gitCurrentBranchName(), 0))
- }
- branches = getAndMergeFetchedBranches(branches)
- return branches
+ builder := newBranchListBuilder()
+ return builder.build()
}
-func branchAlreadyStored(branchName string, branches []Branch) bool {
+func branchIncluded(branchName string, branches []Branch) bool {
for _, existingBranch := range branches {
- if existingBranch.Name == branchName {
+ if strings.ToLower(existingBranch.Name) == strings.ToLower(branchName) {
return true
}
}
return false
}
-// here branches contains all the branches that we've checked out, along with
-// the recency. In this function we append the branches that are in our heads
-// directory i.e. things we've fetched but haven't necessarily checked out.
-// Worth mentioning this has nothing to do with the 'git merge' operation
-func getAndMergeFetchedBranches(branches []Branch) []Branch {
- rawString, err := runDirectCommand("git branch --sort=-committerdate --no-color")
- if err != nil {
- return branches
- }
- branchLines := splitLines(rawString)
- for _, line := range branchLines {
- line = strings.Replace(line, "* ", "", -1)
- line = strings.TrimSpace(line)
- if branchAlreadyStored(line, branches) {
- continue
- }
- branches = append(branches, constructBranch("", line, len(branches)))
- }
- return branches
-}
-
func gitResetHard() error {
return w.Reset(&git.ResetOptions{Mode: git.HardReset})
}
diff --git a/gui.go b/gui.go
index 4d5d0b679..033dbe06a 100644
--- a/gui.go
+++ b/gui.go
@@ -199,12 +199,27 @@ func layout(g *gocui.Gui) error {
if err != gocui.ErrUnknownView {
return err
}
- v.BgColor = gocui.ColorDefault
v.FgColor = gocui.ColorBlue
v.Frame = false
}
- if err = resizeConfirmationPanel(g); err != nil {
+ if getCommitMessageView(g) == nil {
+ // doesn't matter where this view starts because it will be hidden
+ if commitMessageView, err := g.SetView("commitMessage", 0, 0, width, height, 0); err != nil {
+ if err != gocui.ErrUnknownView {
+ return err
+ }
+ g.SetViewOnBottom("commitMessage")
+ commitMessageView.Title = "Commit message"
+ commitMessageView.FgColor = gocui.ColorWhite
+ commitMessageView.Editable = true
+ }
+ }
+
+ if err = resizeConfirmationPanel(g, "commitMessage"); err != nil {
+ return err
+ }
+ if err = resizeConfirmationPanel(g, "confirmation"); err != nil {
return err
}
@@ -261,6 +276,8 @@ func run() (err error) {
}
defer g.Close()
+ g.FgColor = gocui.ColorDefault
+
goEvery(g, time.Second*60, fetch)
goEvery(g, time.Second*10, refreshFiles)
goEvery(g, time.Millisecond*10, updateLoader)
diff --git a/keybindings.go b/keybindings.go
index 292f63007..401a5a477 100644
--- a/keybindings.go
+++ b/keybindings.go
@@ -14,66 +14,69 @@ type Binding struct {
func keybindings(g *gocui.Gui) error {
bindings := []Binding{
- Binding{ViewName: "", Key: 'q', Modifier: gocui.ModNone, Handler: quit},
- Binding{ViewName: "", Key: gocui.KeyCtrlC, Modifier: gocui.ModNone, Handler: quit},
- 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},
- Binding{ViewName: "", Key: 'p', Modifier: gocui.ModNone, Handler: pullFiles},
- Binding{ViewName: "", Key: 'R', Modifier: gocui.ModNone, Handler: handleRefresh},
- Binding{ViewName: "files", Key: 'c', Modifier: gocui.ModNone, Handler: handleCommitPress},
- Binding{ViewName: "files", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleFilePress},
- Binding{ViewName: "files", Key: 'd', Modifier: gocui.ModNone, Handler: handleFileRemove},
- Binding{ViewName: "files", Key: 'm', Modifier: gocui.ModNone, Handler: handleSwitchToMerge},
- Binding{ViewName: "files", Key: 'e', Modifier: gocui.ModNone, Handler: handleFileEdit},
- Binding{ViewName: "files", Key: 'o', Modifier: gocui.ModNone, Handler: handleFileOpen},
- Binding{ViewName: "files", Key: 's', Modifier: gocui.ModNone, Handler: handleSublimeFileOpen},
- Binding{ViewName: "files", Key: 'v', Modifier: gocui.ModNone, Handler: handleVsCodeFileOpen},
- Binding{ViewName: "files", Key: 'i', Modifier: gocui.ModNone, Handler: handleIgnoreFile},
- Binding{ViewName: "files", Key: 'r', Modifier: gocui.ModNone, Handler: handleRefreshFiles},
- Binding{ViewName: "files", Key: 'S', Modifier: gocui.ModNone, Handler: handleStashSave},
- 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.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},
- Binding{ViewName: "branches", Key: 'F', Modifier: gocui.ModNone, Handler: handleForceCheckout},
- Binding{ViewName: "branches", Key: 'n', Modifier: gocui.ModNone, Handler: handleNewBranch},
- Binding{ViewName: "branches", Key: 'm', Modifier: gocui.ModNone, Handler: handleMerge},
- Binding{ViewName: "commits", Key: 's', Modifier: gocui.ModNone, Handler: handleCommitSquashDown},
- Binding{ViewName: "commits", Key: 'r', Modifier: gocui.ModNone, Handler: handleRenameCommit},
- Binding{ViewName: "commits", Key: 'g', Modifier: gocui.ModNone, Handler: handleResetToCommit},
- Binding{ViewName: "commits", Key: 'f', Modifier: gocui.ModNone, Handler: handleCommitFixup},
- Binding{ViewName: "stash", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleStashApply},
- Binding{ViewName: "stash", Key: 'g', Modifier: gocui.ModNone, Handler: handleStashPop},
- Binding{ViewName: "stash", Key: 'd', Modifier: gocui.ModNone, Handler: handleStashDrop},
+ {ViewName: "", Key: 'q', Modifier: gocui.ModNone, Handler: quit},
+ {ViewName: "", Key: gocui.KeyCtrlC, Modifier: gocui.ModNone, Handler: quit},
+ {ViewName: "", Key: gocui.KeyPgup, Modifier: gocui.ModNone, Handler: scrollUpMain},
+ {ViewName: "", Key: gocui.KeyPgdn, Modifier: gocui.ModNone, Handler: scrollDownMain},
+ {ViewName: "", Key: 'P', Modifier: gocui.ModNone, Handler: pushFiles},
+ {ViewName: "", Key: 'p', Modifier: gocui.ModNone, Handler: pullFiles},
+ {ViewName: "", Key: 'R', Modifier: gocui.ModNone, Handler: handleRefresh},
+ {ViewName: "files", Key: 'c', Modifier: gocui.ModNone, Handler: handleCommitPress},
+ {ViewName: "files", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleFilePress},
+ {ViewName: "files", Key: 'd', Modifier: gocui.ModNone, Handler: handleFileRemove},
+ {ViewName: "files", Key: 'm', Modifier: gocui.ModNone, Handler: handleSwitchToMerge},
+ {ViewName: "files", Key: 'e', Modifier: gocui.ModNone, Handler: handleFileEdit},
+ {ViewName: "files", Key: 'o', Modifier: gocui.ModNone, Handler: handleFileOpen},
+ {ViewName: "files", Key: 's', Modifier: gocui.ModNone, Handler: handleSublimeFileOpen},
+ {ViewName: "files", Key: 'v', Modifier: gocui.ModNone, Handler: handleVsCodeFileOpen},
+ {ViewName: "files", Key: 'i', Modifier: gocui.ModNone, Handler: handleIgnoreFile},
+ {ViewName: "files", Key: 'r', Modifier: gocui.ModNone, Handler: handleRefreshFiles},
+ {ViewName: "files", Key: 'S', Modifier: gocui.ModNone, Handler: handleStashSave},
+ {ViewName: "files", Key: 'a', Modifier: gocui.ModNone, Handler: handleAbortMerge},
+ {ViewName: "files", Key: 't', Modifier: gocui.ModNone, Handler: handleAddPatch},
+ {ViewName: "files", Key: 'D', Modifier: gocui.ModNone, Handler: handleResetHard},
+ {ViewName: "main", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: handleEscapeMerge},
+ {ViewName: "main", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handlePickHunk},
+ {ViewName: "main", Key: 'b', Modifier: gocui.ModNone, Handler: handlePickBothHunks},
+ {ViewName: "main", Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: handleSelectPrevConflict},
+ {ViewName: "main", Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: handleSelectNextConflict},
+ {ViewName: "main", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: handleSelectTop},
+ {ViewName: "main", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: handleSelectBottom},
+ {ViewName: "main", Key: 'h', Modifier: gocui.ModNone, Handler: handleSelectPrevConflict},
+ {ViewName: "main", Key: 'l', Modifier: gocui.ModNone, Handler: handleSelectNextConflict},
+ {ViewName: "main", Key: 'k', Modifier: gocui.ModNone, Handler: handleSelectTop},
+ {ViewName: "main", Key: 'j', Modifier: gocui.ModNone, Handler: handleSelectBottom},
+ {ViewName: "main", Key: 'z', Modifier: gocui.ModNone, Handler: handlePopFileSnapshot},
+ {ViewName: "branches", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleBranchPress},
+ {ViewName: "branches", Key: 'c', Modifier: gocui.ModNone, Handler: handleCheckoutByName},
+ {ViewName: "branches", Key: 'F', Modifier: gocui.ModNone, Handler: handleForceCheckout},
+ {ViewName: "branches", Key: 'n', Modifier: gocui.ModNone, Handler: handleNewBranch},
+ {ViewName: "branches", Key: 'm', Modifier: gocui.ModNone, Handler: handleMerge},
+ {ViewName: "commits", Key: 's', Modifier: gocui.ModNone, Handler: handleCommitSquashDown},
+ {ViewName: "commits", Key: 'r', Modifier: gocui.ModNone, Handler: handleRenameCommit},
+ {ViewName: "commits", Key: 'g', Modifier: gocui.ModNone, Handler: handleResetToCommit},
+ {ViewName: "commits", Key: 'f', Modifier: gocui.ModNone, Handler: handleCommitFixup},
+ {ViewName: "stash", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleStashApply},
+ {ViewName: "stash", Key: 'g', Modifier: gocui.ModNone, Handler: handleStashPop},
+ {ViewName: "stash", Key: 'd', Modifier: gocui.ModNone, Handler: handleStashDrop},
+ {ViewName: "commitMessage", Key: gocui.KeyEnter, Modifier: gocui.ModNone, Handler: handleCommitConfirm},
+ {ViewName: "commitMessage", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: handleCommitClose},
+ {ViewName: "commitMessage", Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: handleNewlineCommitMessage},
}
// 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},
+ {ViewName: viewName, Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: nextView},
+ {ViewName: viewName, Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: previousView},
+ {ViewName: viewName, Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: nextView},
+ {ViewName: viewName, Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: cursorUp},
+ {ViewName: viewName, Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: cursorDown},
+ {ViewName: viewName, Key: 'h', Modifier: gocu