1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-06-17 00:18:05 +02:00

Merge branch 'master' into https-ask-for-username-password

This commit is contained in:
Mark Kopenga
2018-12-08 16:41:39 +01:00
committed by GitHub
22 changed files with 688 additions and 407 deletions

View File

@ -52,6 +52,11 @@ func newLogger(config config.AppConfigurer) *logrus.Entry {
} else { } else {
log = newProductionLogger(config) log = newProductionLogger(config)
} }
// highly recommended: tail -f development.log | humanlog
// https://github.com/aybabtme/humanlog
log.Formatter = &logrus.JSONFormatter{}
if config.GetUserConfig().GetString("reporting") == "on" { if config.GetUserConfig().GetString("reporting") == "on" {
// this isn't really a secret token: it only has permission to push new rollbar items // this isn't really a secret token: it only has permission to push new rollbar items
hook := rollrus.NewHook("23432119147a4367abf7c0de2aa99a2d", environment) hook := rollrus.NewHook("23432119147a4367abf7c0de2aa99a2d", environment)

View File

@ -1,6 +1,7 @@
package commands package commands
import ( import (
"fmt"
"strings" "strings"
"github.com/fatih/color" "github.com/fatih/color"
@ -12,11 +13,19 @@ import (
type Branch struct { type Branch struct {
Name string Name string
Recency string Recency string
Pushables string
Pullables string
Selected bool
} }
// GetDisplayStrings returns the dispaly string of branch // GetDisplayStrings returns the dispaly string of branch
func (b *Branch) GetDisplayStrings() []string { func (b *Branch) GetDisplayStrings() []string {
return []string{b.Recency, utils.ColoredString(b.Name, b.GetColor())} displayName := utils.ColoredString(b.Name, b.GetColor())
if b.Selected && b.Pushables != "" && b.Pullables != "" {
displayName = fmt.Sprintf("↑%s↓%s %s", b.Pushables, b.Pullables, displayName)
}
return []string{b.Recency, displayName}
} }
// GetColor branch color // GetColor branch color

View File

@ -154,7 +154,6 @@ func (c *GitCommand) GetStatusFiles() []*File {
} }
files = append(files, file) files = append(files, file)
} }
c.Log.Info(files) // TODO: use a dumper-esque log here
return files return files
} }
@ -208,19 +207,33 @@ func includesInt(list []int, a int) bool {
return false return false
} }
// ResetHard does the equivalent of `git reset --hard HEAD` // ResetAndClean removes all unstaged changes and removes all untracked files
func (c *GitCommand) ResetHard() error { func (c *GitCommand) ResetAndClean() error {
return c.Worktree.Reset(&gogit.ResetOptions{Mode: gogit.HardReset}) if err := c.OSCommand.RunCommand("git reset --hard HEAD"); err != nil {
return err
}
return c.OSCommand.RunCommand("git clean -fd")
} }
// UpstreamDifferenceCount checks how many pushables/pullables there are for the func (c *GitCommand) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
return c.GetCommitDifferences("HEAD", "@{u}")
}
func (c *GitCommand) GetBranchUpstreamDifferenceCount(branchName string) (string, string) {
upstream := "origin" // hardcoded for now
return c.GetCommitDifferences(branchName, fmt.Sprintf("%s/%s", upstream, branchName))
}
// GetCommitDifferences checks how many pushables/pullables there are for the
// current branch // current branch
func (c *GitCommand) UpstreamDifferenceCount() (string, string) { func (c *GitCommand) GetCommitDifferences(from, to string) (string, string) {
pushableCount, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..HEAD --count") command := "git rev-list %s..%s --count"
pushableCount, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf(command, to, from))
if err != nil { if err != nil {
return "?", "?" return "?", "?"
} }
pullableCount, err := c.OSCommand.RunCommandWithOutput("git rev-list HEAD..@{u} --count") pullableCount, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf(command, from, to))
if err != nil { if err != nil {
return "?", "?" return "?", "?"
} }
@ -609,3 +622,8 @@ func (c *GitCommand) ApplyPatch(patch string) (string, error) {
return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git apply --cached %s", filename)) return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git apply --cached %s", filename))
} }
func (c *GitCommand) FastForward(branchName string) error {
upstream := "origin" // hardcoding for now
return c.OSCommand.RunCommand(fmt.Sprintf("git fetch %s %s:%s", upstream, branchName, branchName))
}

View File

@ -557,8 +557,8 @@ func TestGitCommandMergeStatusFiles(t *testing.T) {
} }
} }
// TestGitCommandUpstreamDifferentCount is a function. // TestGitCommandGetCommitDifferences is a function.
func TestGitCommandUpstreamDifferentCount(t *testing.T) { func TestGitCommandGetCommitDifferences(t *testing.T) {
type scenario struct { type scenario struct {
testName string testName string
command func(string, ...string) *exec.Cmd command func(string, ...string) *exec.Cmd
@ -610,7 +610,7 @@ func TestGitCommandUpstreamDifferentCount(t *testing.T) {
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
gitCmd := newDummyGitCommand() gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = s.command gitCmd.OSCommand.command = s.command
s.test(gitCmd.UpstreamDifferenceCount()) s.test(gitCmd.GetCommitDifferences("HEAD", "@{u}"))
}) })
} }
} }

View File

@ -7,25 +7,116 @@ import (
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/git" "github.com/jesseduffield/lazygit/pkg/git"
"github.com/jesseduffield/lazygit/pkg/utils"
) )
// list panel functions
func (gui *Gui) getSelectedBranch() *commands.Branch {
selectedLine := gui.State.Panels.Branches.SelectedLine
if selectedLine == -1 {
return nil
}
return gui.State.Branches[selectedLine]
}
// may want to standardise how these select methods work
func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
// This really shouldn't happen: there should always be a master branch
if len(gui.State.Branches) == 0 {
return gui.renderString(g, "main", gui.Tr.SLocalize("NoBranchesThisRepo"))
}
branch := gui.getSelectedBranch()
if err := gui.focusPoint(0, gui.State.Panels.Branches.SelectedLine, v); err != nil {
return err
}
go func() {
_ = gui.RenderSelectedBranchUpstreamDifferences()
}()
go func() {
graph, err := gui.GitCommand.GetBranchGraph(branch.Name)
if err != nil && strings.HasPrefix(graph, "fatal: ambiguous argument") {
graph = gui.Tr.SLocalize("NoTrackingThisBranch")
}
_ = gui.renderString(g, "main", graph)
}()
return nil
}
func (gui *Gui) RenderSelectedBranchUpstreamDifferences() error {
// here we tell the selected branch that it is selected.
// this is necessary for showing stats on a branch that is selected, because
// the displaystring function doesn't have access to gui state to tell if it's selected
for i, branch := range gui.State.Branches {
branch.Selected = i == gui.State.Panels.Branches.SelectedLine
}
branch := gui.getSelectedBranch()
branch.Pushables, branch.Pullables = gui.GitCommand.GetBranchUpstreamDifferenceCount(branch.Name)
return gui.renderListPanel(gui.getBranchesView(gui.g), gui.State.Branches)
}
// gui.refreshStatus is called at the end of this because that's when we can
// be sure there is a state.Branches array to pick the current branch from
func (gui *Gui) refreshBranches(g *gocui.Gui) error {
g.Update(func(g *gocui.Gui) error {
builder, err := git.NewBranchListBuilder(gui.Log, gui.GitCommand)
if err != nil {
return err
}
gui.State.Branches = builder.Build()
gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches))
if err := gui.resetOrigin(gui.getBranchesView(gui.g)); err != nil {
return err
}
if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil {
return err
}
return gui.refreshStatus(g)
})
return nil
}
func (gui *Gui) handleBranchesNextLine(g *gocui.Gui, v *gocui.View) error {
panelState := gui.State.Panels.Branches
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Branches), false)
return gui.handleBranchSelect(gui.g, v)
}
func (gui *Gui) handleBranchesPrevLine(g *gocui.Gui, v *gocui.View) error {
panelState := gui.State.Panels.Branches
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Branches), true)
return gui.handleBranchSelect(gui.g, v)
}
// specific functions
func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error {
index := gui.getItemPosition(gui.getBranchesView(g)) if gui.State.Panels.Branches.SelectedLine == -1 {
if index == 0 { return nil
}
if gui.State.Panels.Branches.SelectedLine == 0 {
return gui.createErrorPanel(g, gui.Tr.SLocalize("AlreadyCheckedOutBranch")) return gui.createErrorPanel(g, gui.Tr.SLocalize("AlreadyCheckedOutBranch"))
} }
branch := gui.getSelectedBranch(gui.getBranchesView(g)) branch := gui.getSelectedBranch()
if err := gui.GitCommand.Checkout(branch.Name, false); err != nil { if err := gui.GitCommand.Checkout(branch.Name, false); err != nil {
gui.createErrorPanel(g, err.Error()) if err := gui.createErrorPanel(g, err.Error()); err != nil {
return err
} }
} else {
gui.State.Panels.Branches.SelectedLine = 0
}
return gui.refreshSidePanels(g) return gui.refreshSidePanels(g)
} }
func (gui *Gui) handleCreatePullRequestPress(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleCreatePullRequestPress(g *gocui.Gui, v *gocui.View) error {
branch := gui.getSelectedBranch(gui.getBranchesView(g))
pullRequest := commands.NewPullRequest(gui.GitCommand) pullRequest := commands.NewPullRequest(gui.GitCommand)
branch := gui.getSelectedBranch()
if err := pullRequest.Create(branch); err != nil { if err := pullRequest.Create(branch); err != nil {
return gui.createErrorPanel(g, err.Error()) return gui.createErrorPanel(g, err.Error())
} }
@ -60,7 +151,7 @@ func (gui *Gui) handleGitFetch(g *gocui.Gui, v *gocui.View) error {
} }
func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error {
branch := gui.getSelectedBranch(v) branch := gui.getSelectedBranch()
message := gui.Tr.SLocalize("SureForceCheckout") message := gui.Tr.SLocalize("SureForceCheckout")
title := gui.Tr.SLocalize("ForceCheckoutBranch") title := gui.Tr.SLocalize("ForceCheckoutBranch")
return gui.createConfirmationPanel(g, v, title, message, func(g *gocui.Gui, v *gocui.View) error { return gui.createConfirmationPanel(g, v, title, message, func(g *gocui.Gui, v *gocui.View) error {
@ -108,8 +199,11 @@ func (gui *Gui) handleForceDeleteBranch(g *gocui.Gui, v *gocui.View) error {
} }
func (gui *Gui) deleteBranch(g *gocui.Gui, v *gocui.View, force bool) error { func (gui *Gui) deleteBranch(g *gocui.Gui, v *gocui.View, force bool) error {
selectedBranch := gui.getSelectedBranch()
if selectedBranch == nil {
return nil
}
checkedOutBranch := gui.State.Branches[0] checkedOutBranch := gui.State.Branches[0]
selectedBranch := gui.getSelectedBranch(v)
if checkedOutBranch.Name == selectedBranch.Name { if checkedOutBranch.Name == selectedBranch.Name {
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantDeleteCheckOutBranch")) return gui.createErrorPanel(g, gui.Tr.SLocalize("CantDeleteCheckOutBranch"))
} }
@ -144,7 +238,7 @@ func (gui *Gui) deleteNamedBranch(g *gocui.Gui, v *gocui.View, selectedBranch *c
func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error {
checkedOutBranch := gui.State.Branches[0] checkedOutBranch := gui.State.Branches[0]
selectedBranch := gui.getSelectedBranch(v) selectedBranch := gui.getSelectedBranch()
defer gui.refreshSidePanels(g) defer gui.refreshSidePanels(g)
if checkedOutBranch.Name == selectedBranch.Name { if checkedOutBranch.Name == selectedBranch.Name {
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantMergeBranchIntoItself")) return gui.createErrorPanel(g, gui.Tr.SLocalize("CantMergeBranchIntoItself"))
@ -155,59 +249,36 @@ func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error {
return nil return nil
} }
func (gui *Gui) getSelectedBranch(v *gocui.View) *commands.Branch { func (gui *Gui) handleFastForward(g *gocui.Gui, v *gocui.View) error {
lineNumber := gui.getItemPosition(v) branch := gui.getSelectedBranch()
return gui.State.Branches[lineNumber] if branch == nil {
} return nil
func (gui *Gui) renderBranchesOptions(g *gocui.Gui) error {
return gui.renderGlobalOptions(g)
}
// may want to standardise how these select methods work
func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
if err := gui.renderBranchesOptions(g); err != nil {
return err
} }
// This really shouldn't happen: there should always be a master branch if branch.Pushables == "" {
if len(gui.State.Branches) == 0 { return nil
return gui.renderString(g, "main", gui.Tr.SLocalize("NoBranchesThisRepo"))
} }
if branch.Pushables == "?" {
return gui.createErrorPanel(gui.g, "Cannot fast-forward a branch with no upstream")
}
if branch.Pushables != "0" {
return gui.createErrorPanel(gui.g, "Cannot fast-forward a branch with commits to push")
}
upstream := "origin" // hardcoding for now
message := gui.Tr.TemplateLocalize(
"Fetching",
Teml{
"from": fmt.Sprintf("%s/%s", upstream, branch.Name),
"to": branch.Name,
},
)
go func() { go func() {
branch := gui.getSelectedBranch(v) _ = gui.createMessagePanel(gui.g, v, "", message)
diff, err := gui.GitCommand.GetBranchGraph(branch.Name) if err := gui.GitCommand.FastForward(branch.Name); err != nil {
if err != nil && strings.HasPrefix(diff, "fatal: ambiguous argument") { _ = gui.createErrorPanel(gui.g, err.Error())
diff = gui.Tr.SLocalize("NoTrackingThisBranch") } else {
_ = gui.closeConfirmationPrompt(gui.g)
_ = gui.RenderSelectedBranchUpstreamDifferences()
} }
gui.renderString(g, "main", diff)
}() }()
return nil return nil
} }
// gui.refreshStatus is called at the end of this because that's when we can
// be sure there is a state.Branches array to pick the current branch from
func (gui *Gui) refreshBranches(g *gocui.Gui) error {
g.Update(func(g *gocui.Gui) error {
v, err := g.View("branches")
if err != nil {
panic(err)
}
builder, err := git.NewBranchListBuilder(gui.Log, gui.GitCommand)
if err != nil {
return err
}
gui.State.Branches = builder.Build()
v.Clear()
list, err := utils.RenderList(gui.State.Branches)
if err != nil {
return err
}
fmt.Fprint(v, list)
gui.resetOrigin(v)
return gui.refreshStatus(g)
})
return nil
}

View File

@ -23,12 +23,12 @@ func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error {
gui.SubProcess = sub gui.SubProcess = sub
return gui.Errors.ErrSubProcess return gui.Errors.ErrSubProcess
} }
gui.refreshFiles(g)
v.Clear() v.Clear()
v.SetCursor(0, 0) _ = v.SetCursor(0, 0)
g.SetViewOnBottom("commitMessage") _ = v.SetOrigin(0, 0)
gui.switchFocus(g, v, gui.getFilesView(g)) _, _ = g.SetViewOnBottom("commitMessage")
return gui.refreshCommits(g) _ = gui.switchFocus(g, v, gui.getFilesView(g))
return gui.refreshSidePanels(g)
} }
func (gui *Gui) handleCommitClose(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleCommitClose(g *gocui.Gui, v *gocui.View) error {

View File

@ -9,29 +9,54 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
// list panel functions
func (gui *Gui) getSelectedCommit(g *gocui.Gui) *commands.Commit {
selectedLine := gui.State.Panels.Commits.SelectedLine
if selectedLine == -1 {
return nil
}
return gui.State.Commits[selectedLine]
}
func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
commit := gui.getSelectedCommit(g)
if commit == nil {
return gui.renderString(g, "main", gui.Tr.SLocalize("NoCommitsThisBranch"))
}
if err := gui.focusPoint(0, gui.State.Panels.Commits.SelectedLine, v); err != nil {
return err
}
commitText, err := gui.GitCommand.Show(commit.Sha)
if err != nil {
return err
}
return gui.renderString(g, "main", commitText)
}
func (gui *Gui) refreshCommits(g *gocui.Gui) error { func (gui *Gui) refreshCommits(g *gocui.Gui) error {
g.Update(func(*gocui.Gui) error { g.Update(func(*gocui.Gui) error {
commits, err := gui.GitCommand.GetCommits() commits, err := gui.GitCommand.GetCommits()
if err != nil { if err != nil {
return err return err
} }
gui.State.Commits = commits gui.State.Commits = commits
v, err := g.View("commits")
if err != nil {
return err
}
v.Clear() gui.refreshSelectedLine(&gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits))
list, err := utils.RenderList(gui.State.Commits) list, err := utils.RenderList(gui.State.Commits)
if err != nil { if err != nil {
return err return err
} }
v := gui.getCommitsView(gui.g)
v.Clear()
fmt.Fprint(v, list) fmt.Fprint(v, list)
gui.refreshStatus(g) gui.refreshStatus(g)
if g.CurrentView().Name() == "commits" { if v == g.CurrentView() {
gui.handleCommitSelect(g, v) gui.handleCommitSelect(g, v)
} }
return nil return nil
@ -39,11 +64,27 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error {
return nil return nil
} }
func (gui *Gui) handleCommitsNextLine(g *gocui.Gui, v *gocui.View) error {
panelState := gui.State.Panels.Commits
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Commits), false)
return gui.handleCommitSelect(gui.g, v)
}
func (gui *Gui) handleCommitsPrevLine(g *gocui.Gui, v *gocui.View) error {
panelState := gui.State.Panels.Commits
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Commits), true)
return gui.handleCommitSelect(gui.g, v)
}
// specific functions
func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error { func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error {
return gui.createConfirmationPanel(g, commitView, gui.Tr.SLocalize("ResetToCommit"), gui.Tr.SLocalize("SureResetThisCommit"), func(g *gocui.Gui, v *gocui.View) error { return gui.createConfirmationPanel(g, commitView, gui.Tr.SLocalize("ResetToCommit"), gui.Tr.SLocalize("SureResetThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
commit, err := gui.getSelectedCommit(g) commit := gui.getSelectedCommit(g)
if err != nil { if commit == nil {
panic(err) panic(errors.New(gui.Tr.SLocalize("NoCommitsThisBranch")))
} }
if err := gui.GitCommand.ResetToCommit(commit.Sha); err != nil { if err := gui.GitCommand.ResetToCommit(commit.Sha); err != nil {
return gui.createErrorPanel(g, err.Error()) return gui.createErrorPanel(g, err.Error())
@ -55,42 +96,21 @@ func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error
panic(err) panic(err)
} }
gui.resetOrigin(commitView) gui.resetOrigin(commitView)
return gui.handleCommitSelect(g, nil) gui.State.Panels.Commits.SelectedLine = 0
return gui.handleCommitSelect(g, commitView)
}, nil) }, nil)
} }
func (gui *Gui) renderCommitsOptions(g *gocui.Gui) error {
return gui.renderGlobalOptions(g)
}
func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
if err := gui.renderCommitsOptions(g); err != nil {
return err
}
commit, err := gui.getSelectedCommit(g)
if err != nil {
if err.Error() != gui.Tr.SLocalize("NoCommitsThisBranch") {
return err
}
return gui.renderString(g, "main", gui.Tr.SLocalize("NoCommitsThisBranch"))
}
commitText, err := gui.GitCommand.Show(commit.Sha)
if err != nil {
return err
}
return gui.renderString(g, "main", commitText)
}
func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error {
if gui.getItemPosition(v) != 0 { if gui.State.Panels.Commits.SelectedLine != 0 {
return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlySquashTopmostCommit")) return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlySquashTopmostCommit"))
} }
if len(gui.State.Commits) == 1 { if len(gui.State.Commits) <= 1 {
return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash")) return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash"))
} }
commit, err := gui.getSelectedCommit(g) commit := gui.getSelectedCommit(g)
if err != nil { if commit == nil {
return err return errors.New(gui.Tr.SLocalize("NoCommitsThisBranch"))
} }
if err := gui.GitCommand.SquashPreviousTwoCommits(commit.Name); err != nil { if err := gui.GitCommand.SquashPreviousTwoCommits(commit.Name); err != nil {
return gui.createErrorPanel(g, err.Error()) return gui.createErrorPanel(g, err.Error())
@ -113,16 +133,16 @@ func (gui *Gui) anyUnStagedChanges(files []*commands.File) bool {
} }
func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error {
if len(gui.State.Commits) == 1 { if len(gui.State.Commits) <= 1 {
return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash")) return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash"))
} }
if gui.anyUnStagedChanges(gui.State.Files) { if gui.anyUnStagedChanges(gui.State.Files) {
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantFixupWhileUnstagedChanges")) return gui.createErrorPanel(g, gui.Tr.SLocalize("CantFixupWhileUnstagedChanges"))
} }
branch := gui.State.Branches[0] branch := gui.State.Branches[0]
commit, err := gui.getSelectedCommit(g) commit := gui.getSelectedCommit(g)
if err != nil { if commit == nil {
return err return gui.createErrorPanel(g, gui.Tr.SLocalize("NoCommitsThisBranch"))
} }
message := gui.Tr.SLocalize("SureFixupThisCommit") message := gui.Tr.SLocalize("SureFixupThisCommit")
gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("Fixup"), message, func(g *gocui.Gui, v *gocui.View) error { gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("Fixup"), message, func(g *gocui.Gui, v *gocui.View) error {
@ -138,7 +158,7 @@ func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error {
} }
func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
if gui.getItemPosition(gui.getCommitsView(g)) != 0 { if gui.State.Panels.Commits.SelectedLine != 0 {
return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlyRenameTopCommit")) return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlyRenameTopCommit"))
} }
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("renameCommit"), func(g *gocui.Gui, v *gocui.View) error { return gui.createPromptPanel(g, v, gui.Tr.SLocalize("renameCommit"), func(g *gocui.Gui, v *gocui.View) error {
@ -153,7 +173,7 @@ func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
} }
func (gui *Gui) handleRenameCommitEditor(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleRenameCommitEditor(g *gocui.Gui, v *gocui.View) error {
if gui.getItemPosition(gui.getCommitsView(g)) != 0 { if gui.State.Panels.Commits.SelectedLine != 0 {
return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlyRenameTopCommit")) return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlyRenameTopCommit"))
} }
@ -164,19 +184,3 @@ func (gui *Gui) handleRenameCommitEditor(g *gocui.Gui, v *gocui.View) error {
return nil return nil
} }
func (gui *Gui) getSelectedCommit(g *gocui.Gui) (*commands.Commit, error) {
v, err := g.View("commits")
if err != nil {
panic(err)
}
if len(gui.State.Commits) == 0 {
return &commands.Commit{}, errors.New(gui.Tr.SLocalize("NoCommitsThisBranch"))
}
lineNumber := gui.getItemPosition(v)
if lineNumber > len(gui.State.Commits)-1 {
gui.Log.Info(gui.Tr.SLocalize("PotentialErrInGetselectedCommit"), gui.State.Commits, lineNumber)
return gui.State.Commits[len(gui.State.Commits)-1], nil
}
return gui.State.Commits[lineNumber], nil
}

View File

@ -28,7 +28,7 @@ func (gui *Gui) wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.Vie
func (gui *Gui) closeConfirmationPrompt(g *gocui.Gui) error { func (gui *Gui) closeConfirmationPrompt(g *gocui.Gui) error {
view, err := g.View("confirmation") view, err := g.View("confirmation")
if err != nil { if err != nil {
panic(err) return nil // if it's already been closed we can just return
} }
if err := gui.returnFocus(g, view); err != nil { if err := gui.returnFocus(g, view); err != nil {
panic(err) panic(err)
@ -77,11 +77,10 @@ func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt
confirmationView.Wrap = true confirmationView.Wrap = true
confirmationView.FgColor = gocui.ColorWhite confirmationView.FgColor = gocui.ColorWhite
} }
gui.g.Update(func(g *gocui.Gui) error {
confirmationView.Clear() confirmationView.Clear()
return gui.switchFocus(gui.g, currentView, confirmationView)
if err := gui.switchFocus(gui.g, currentView, confirmationView); err != nil { })
return nil, err
}
return confirmationView, nil return confirmationView, nil
} }

View File

@ -15,6 +15,79 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
// list panel functions
func (gui *Gui) getSelectedFile(g *gocui.Gui) (*commands.File, error) {
selectedLine := gui.State.Panels.Files.SelectedLine
if selectedLine == -1 {
return &commands.File{}, gui.Errors.ErrNoFiles
}
return gui.State.Files[selectedLine], nil
}
func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error {
file, err := gui.getSelectedFile(g)
if err != nil {
if err != gui.Errors.ErrNoFiles {
return err
}
return gui.renderString(g, "main", gui.Tr.SLocalize("NoChangedFiles"))
}
if file.HasMergeConflicts {
return gui.refreshMergePanel(g)
}
if err := gui.focusPoint(0, gui.State.Panels.Files.SelectedLine, v); err != nil {
return err
}
content := gui.GitCommand.Diff(file, false)
return gui.renderString(g, "main", content)
}
func (gui *Gui) refreshFiles(g *gocui.Gui) error {
filesView, err := g.View("files")
if err != nil {
return err
}
gui.refreshStateFiles()
gui.g.Update(func(g *gocui.Gui) error {
filesView.Clear()
list, err := utils.RenderList(gui.State.Files)
if err != nil {
return err
}
fmt.Fprint(filesView, list)
if filesView == g.CurrentView() {
return gui.handleFileSelect(g, filesView)
}
return nil
})
return nil
}
func (gui *Gui) handleFilesNextLine(g *gocui.Gui, v *gocui.View) error {
panelState := gui.State.Panels.Files
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Files), false)
return gui.handleFileSelect(gui.g, v)
}
func (gui *Gui) handleFilesPrevLine(g *gocui.Gui, v *gocui.View) error {
panelState := gui.State.Panels.Files
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Files), true)
return gui.handleFileSelect(gui.g, v)
}
// specific functions
func (gui *Gui) stagedFiles() []*commands.File { func (gui *Gui) stagedFiles() []*commands.File {
files := gui.State.Files files := gui.State.Files
result := make([]*commands.File, 0) result := make([]*commands.File, 0)
@ -139,18 +212,6 @@ func (gui *Gui) handleAddPatch(g *gocui.Gui, v *gocui.View) error {
return gui.Errors.ErrSubProcess return gui.Errors.ErrSubProcess
} }
func (gui *Gui) getSelectedFile(g *gocui.Gui) (*commands.File, error) {
if len(gui.State.Files) == 0 {
return &commands.File{}, gui.Errors.ErrNoFiles
}
filesView, err := g.View("files")
if err != nil {
panic(err)
}
lineNumber := gui.getItemPosition(filesView)
return gui.State.Files[lineNumber], nil
}
func (gui *Gui) handleFileRemove(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleFileRemove(g *gocui.Gui, v *gocui.View) error {
file, err := gui.getSelectedFile(g) file, err := gui.getSelectedFile(g)
if err != nil { if err != nil {
@ -194,30 +255,6 @@ func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error {
return gui.refreshFiles(g) return gui.refreshFiles(g)
} }
func (gui *Gui) renderfilesOptions(g *gocui.Gui, file *commands.File) error {
return gui.renderGlobalOptions(g)
}
func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error {
file, err := gui.getSelectedFile(g)
if err != nil {
if err != gui.Errors.ErrNoFiles {
return err
}
gui.renderString(g, "main", gui.Tr.SLocalize("NoChangedFiles"))
return gui.renderfilesOptions(g, nil)
}
if err := gui.renderfilesOptions(g, file); err != nil {
return err
}
if file.HasMergeConflicts {
return gui.refreshMergePanel(g)
}
content := gui.GitCommand.Diff(file, false)
return gui.renderString(g, "main", content)
}
func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error {
if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts { if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts {
return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit")) return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit"))
@ -309,6 +346,7 @@ func (gui *Gui) refreshStateFiles() {
// get files to stage // get files to stage
files := gui.GitCommand.GetStatusFiles() files := gui.GitCommand.GetStatusFiles()
gui.State.Files = gui.GitCommand.MergeStatusFiles(gui.State.Files, files) gui.State.Files = gui.GitCommand.MergeStatusFiles(gui.State.Files, files)
gui.refreshSelectedLine(&gui.State.Panels.Files.SelectedLine, len(gui.State.Files))
gui.updateHasMergeConflictStatus() gui.updateHasMergeConflictStatus()
} }
@ -340,27 +378,6 @@ func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) {
return cat, nil return cat, nil
} }
func (gui *Gui) refreshFiles(g *gocui.Gui) error {
filesView, err := g.View("files")
if err != nil {
return err
}
gui.refreshStateFiles()
filesView.Clear()
list, err := utils.RenderList(gui.State.Files)
if err != nil {
return err
}
fmt.Fprint(filesView, list)
gui.correctCursor(filesView)
if filesView == g.CurrentView() {
gui.handleFileSelect(g, filesView)
}
return nil
}
func (gui *Gui) pullFiles(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) pullFiles(g *gocui.Gui, v *gocui.View) error {
if err := gui.createMessagePanel(gui.g, v, "", gui.Tr.SLocalize("PullWait")); err != nil { if err := gui.createMessagePanel(gui.g, v, "", gui.Tr.SLocalize("PullWait")); err != nil {
return err return err
@ -424,7 +441,7 @@ func (gui *Gui) pushWithForceFlag(g *gocui.Gui, v *gocui.View, force bool) error
func (gui *Gui) pushFiles(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) pushFiles(g *gocui.Gui, v *gocui.View) error {
// if we have pullables we'll ask if the user wants to force push // if we have pullables we'll ask if the user wants to force push
_, pullables := gui.GitCommand.UpstreamDifferenceCount() _, pullables := gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
if pullables == "?" || pullables == "0" { if pullables == "?" || pullables == "0" {
return gui.pushWithForceFlag(g, v, false) return gui.pushWithForceFlag(g, v, false)
} }
@ -462,9 +479,9 @@ func (gui *Gui) handleAbortMerge(g *gocui.Gui, v *gocui.View) error {
return gui.refreshFiles(g) return gui.refreshFiles(g)
} }
func (gui *Gui) handleResetHard(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleResetAndClean(g *gocui.Gui, v *gocui.View) error {
return gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("ClearFilePanel"), gui.Tr.SLocalize("SureResetHardHead"), func(g *gocui.Gui, v *gocui.View) error { return gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("ClearFilePanel"), gui.Tr.SLocalize("SureResetHardHead"), func(g *gocui.Gui, v *gocui.View) error {
if err := gui.GitCommand.ResetHard(); err != nil { if err := gui.GitCommand.ResetAndClean(); err != nil {
gui.createErrorPanel(g, err.Error()) gui.createErrorPanel(g, err.Error())
} }
return gui.refreshFiles(g) return gui.refreshFiles(g)

View File

@ -76,13 +76,45 @@ type Gui struct {
introAgree sync.WaitGroup introAgree sync.WaitGroup
} }
type stagingState struct { // for now the staging panel state, unlike the other panel states, is going to be
// non-mutative, so that we don't accidentally end up
// with mismatches of data. We might change this in the future
type stagingPanelState struct {
SelectedLine int
StageableLines []int StageableLines []int
HunkStarts []int HunkStarts []int
CurrentLineIndex int
Diff string Diff string
} }
type filePanelState struct {
SelectedLine int
}
type branchPanelState struct {
SelectedLine int
}
type commitPanelState struct {
SelectedLine int
}
type stashPanelState struct {
SelectedLine int
}
type menuPanelState struct {
SelectedLine int
}
type panelStates struct {
Files *filePanelState
Staging *stagingPanelState
Branches *branchPanelState
Commits *commitPanelState
Stash *stashPanelState
Menu *menuPanelState
}
type guiState struct { type guiState struct {
Files []*commands.File Files []*commands.File
Branches []*commands.Branch Branches []*commands.Branch
@ -96,7 +128,7 @@ type guiState struct {
EditHistory *stack.Stack EditHistory *stack.Stack
Platform commands.Platform Platform commands.Platform
Updating bool Updating bool
StagingState *stagingState Panels *panelStates
} }
// NewGui builds a new gui handler // NewGui builds a new gui handler
@ -112,6 +144,13 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma
Conflicts: make([]commands.Conflict, 0), Conflicts: make([]commands.Conflict, 0),
EditHistory: stack.New(), EditHistory: stack.New(),
Platform: *oSCommand.Platform, Platform: *oSCommand.Platform,
Panels: &panelStates{
Files: &filePanelState{SelectedLine: -1},
Branches: &branchPanelState{SelectedLine: 0},
Commits: &commitPanelState{SelectedLine: -1},
Stash: &stashPanelState{SelectedLine: -1},
Menu: &menuPanelState{SelectedLine: 0},
},
} }
gui := &Gui{ gui := &Gui{
@ -197,9 +236,11 @@ func (gui *Gui) layout(g *gocui.Gui) error {
} }
v.Title = gui.Tr.SLocalize("NotEnoughSpace") v.Title = gui.Tr.SLocalize("NotEnoughSpace")
v.Wrap = true v.Wrap = true
g.SetCurrentView(v.Name()) g.SetViewOnTop("limit")
} }
return nil return nil
} else {
_, _ = g.SetViewOnBottom("limit")
} }
g.DeleteView("limit") g.DeleteView("limit")
@ -251,12 +292,13 @@ func (gui *Gui) layout(g *gocui.Gui) error {
v.FgColor = gocui.ColorWhite v.FgColor = gocui.ColorWhite
} }
if v, err := g.SetView("branches", 0, filesBranchesBoundary+panelSpacing, leftSideWidth, commitsBranchesBoundary, gocui.TOP|gocui.BOTTOM); err != nil { branchesView, err := g.SetView("branches", 0, filesBranchesBoundary+panelSpacing, leftSideWidth, commitsBranchesBoundary, gocui.TOP|gocui.BOTTOM)
if err != nil {
if err != gocui.ErrUnknownView { if err != gocui.ErrUnknownView {
return err return err
} }
v.Title = gui.Tr.SLocalize("BranchesTitle") branchesView.Title = gui.Tr.SLocalize("BranchesTitle")
v.FgColor = gocui.ColorWhite branchesView.FgColor = gocui.ColorWhite
} }
if v, err := g.SetView("commits", 0, commitsBranchesBoundary+panelSpacing, leftSideWidth, commitsStashBoundary, gocui.TOP|gocui.BOTTOM); err != nil { if v, err := g.SetView("commits", 0, commitsBranchesBoundary+panelSpacing, leftSideWidth, commitsStashBoundary, gocui.TOP|gocui.BOTTOM); err != nil {
@ -346,11 +388,14 @@ func (gui *Gui) layout(g *gocui.Gui) error {
return err return err
} }
gui.handleFileSelect(g, filesView) if _, err := gui.g.SetCurrentView(filesView.Name()); err != nil {
gui.refreshFiles(g) return err
gui.refreshBranches(g) }
gui.refreshCommits(g)
gui.refreshStashEntries(g) if err := gui.refreshSidePanels(gui.g); err != nil {
return err
}
if err := gui.switchFocus(g, nil, filesView); err != nil { if err := gui.switchFocus(g, nil, filesView); err != nil {
return err return err
} }
@ -362,6 +407,22 @@ func (gui *Gui) layout(g *gocui.Gui) error {
} }
} }
listViews := map[*gocui.View]int{
filesView: gui.State.Panels.Files.SelectedLine,
branchesView: gui.State.Panels.Branches.SelectedLine,
}
for view, selectedLine := range listViews {
// check if the selected line is now out of view and if so refocus it
if err := gui.focusPoint(0, selectedLine, view); err != nil {
return err
}
}
// here is a good place log some stuff
// if you download humanlog and do tail -f development.log | humanlog
// this will let you see these branches as prettified json
// gui.Log.Info(utils.AsJson(gui.State.Branches[0:4]))
return gui.resizeCurrentPopupPanel(g) return gui.resizeCurrentPopupPanel(g)
} }
@ -416,8 +477,8 @@ func (gui *Gui) renderAppStatus(g *gocui.Gui) error {
return nil return nil
} }
func (gui *Gui) renderGlobalOptions(g *gocui.Gui) error { func (gui *Gui) renderGlobalOptions() error {
return gui.renderOptionsMap(g, map[string]string{ return gui.renderOptionsMap(map[string]string{
"PgUp/PgDn": gui.Tr.SLocalize("scroll"), "PgUp/PgDn": gui.Tr.SLocalize("scroll"),
"← → ↑ ↓": gui.Tr.SLocalize("navigate"), "← → ↑ ↓": gui.Tr.SLocalize("navigate"),
"esc/q": gui.Tr.SLocalize("close"), "esc/q": gui.Tr.SLocalize("close"),

View File

@ -210,7 +210,7 @@ func (gui *Gui) GetKeybindings() []*Binding {
ViewName: "files", ViewName: "files",
Key: 'D', Key: 'D',
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleResetHard, Handler: gui.handleResetAndClean,
Description: gui.Tr.SLocalize("resetHard"), Description: gui.Tr.SLocalize("resetHard"),
}, { }, {
ViewName: "files", ViewName: "files",
@ -328,6 +328,12 @@ func (gui *Gui) GetKeybindings() []*Binding {
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleMerge, Handler: gui.handleMerge,
Description: gui.Tr.SLocalize("mergeIntoCurrentBranch"), Description: gui.Tr.SLocalize("mergeIntoCurrentBranch"),
}, {
ViewName: "branches",
Key: 'f',
Modifier: gocui.ModNone,
Handler: gui.handleFastForward,
Description: gui.Tr.SLocalize("FastForward"),
}, { }, {
ViewName: "commits", ViewName: "commits",
Key: 's', Key: 's',
@ -469,19 +475,33 @@ func (gui *Gui) GetKeybindings() []*Binding {
}, },
} }
// Would make these keybindings global but that interferes with editing for _, viewName := range []string{"status", "branches", "files", "commits", "stash", "menu"} {
// input in the confirmation panel
for _, viewName := range []string{"status", "files", "branches", "commits", "stash", "menu"} {
bindings = append(bindings, []*Binding{ bindings = append(bindings, []*Binding{
{ViewName: viewName, Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: gui.nextView}, {ViewName: viewName, Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: gui.nextView},
{ViewName: viewName, Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: gui.previousView}, {ViewName: viewName, Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: gui.previousView},
{ViewName: viewName, Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: gui.nextView}, {ViewName: viewName, Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: gui.nextView},
{ViewName: viewName, Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.cursorUp},
{ViewName: viewName, Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.cursorDown},
{ViewName: viewName, Key: 'h', Modifier: gocui.ModNone, Handler: gui.previousView}, {ViewName: viewName, Key: 'h', Modifier: gocui.ModNone, Handler: gui.previousView},
{ViewName: viewName, Key: 'l', Modifier: gocui.ModNone, Handler: gui.nextView}, {ViewName: viewName, Key: 'l', Modifier: gocui.ModNone, Handler: gui.nextView},
{ViewName: viewName, Key: 'k', Modifier: gocui.ModNone, Handler: gui.cursorUp}, }...)
{ViewName: viewName, Key: 'j', Modifier: gocui.ModNone, Handler: gui.cursorDown}, }
listPanelMap := map[string]struct {
prevLine func(*gocui.Gui, *gocui.View) error
nextLine func(*gocui.Gui, *gocui.View) error
}{
"menu": {prevLine: gui.handleMenuPrevLine, nextLine: gui.handleMenuNextLine},
"files": {prevLine: gui.handleFilesPrevLine, nextLine: gui.handleFilesNextLine},
"branches": {prevLine: gui.handleBranchesPrevLine, nextLine: gui.handleBranchesNextLine},
"commits": {prevLine: gui.handleCommitsPrevLine, nextLine: gui.handleCommitsNextLine},
"stash": {prevLine: gui.handleStashPrevLine, nextLine: gui.handleStashNextLine},
}
for viewName, functions := range listPanelMap {
bindings = append(bindings, []*Binding{
{ViewName: viewName, Key: 'k', Modifier: gocui.ModNone, Handler: functions.prevLine},
{ViewName: viewName, Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: functions.prevLine},
{ViewName: viewName, Key: 'j', Modifier: gocui.ModNone, Handler: functions.nextLine},
{ViewName: viewName, Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: functions.nextLine},
}...) }...)
} }

View File

@ -8,19 +8,35 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
// list panel functions
func (gui *Gui) handleMenuSelect(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleMenuSelect(g *gocui.Gui, v *gocui.View) error {
// doing nothing for now return gui.focusPoint(0, gui.State.Panels.Menu.SelectedLine, v)
// but it is needed for switch in newLineFocused
return nil
} }
func (gui *Gui) renderMenuOptions(g *gocui.Gui) error { func (gui *Gui) handleMenuNextLine(g *gocui.Gui, v *gocui.View) error {
panelState := gui.State.Panels.Menu
gui.changeSelectedLine(&panelState.SelectedLine, v.LinesHeight(), false)
return gui.handleMenuSelect(g, v)
}
func (gui *Gui) handleMenuPrevLine(g *gocui.Gui, v *gocui.View) error {
panelState := gui.State.Panels.Menu
gui.changeSelectedLine(&panelState.SelectedLine, v.LinesHeight(), true)
return gui.handleMenuSelect(g, v)
}
// specific functions
func (gui *Gui) renderMenuOptions() error {
optionsMap := map[string]string{ optionsMap := map[string]string{
"esc/q": gui.Tr.SLocalize("close"), "esc/q": gui.Tr.SLocalize("close"),
"↑ ↓": gui.Tr.SLocalize("navigate"), "↑ ↓": gui.Tr.SLocalize("navigate"),
"space": gui.Tr.SLocalize("execute"), "space": gui.Tr.SLocalize("execute"),
} }
return gui.renderOptionsMap(g, optionsMap) return gui.renderOptionsMap(optionsMap)
} }
func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error {
@ -46,14 +62,11 @@ func (gui *Gui) createMenu(items interface{}, handlePress func(int) error) error
menuView.FgColor = gocui.ColorWhite menuView.FgColor = gocui.ColorWhite
menuView.Clear() menuView.Clear()
fmt.Fprint(menuView, list) fmt.Fprint(menuView, list)
gui.State.Panels.Menu.SelectedLine = 0
if err := gui.renderMenuOptions(gui.g); err != nil {
return err
}
wrappedHandlePress := func(g *gocui.Gui, v *gocui.View) error { wrappedHandlePress := func(g *gocui.Gui, v *gocui.View) error {
lineNumber := gui.getItemPosition(v) selectedLine := gui.State.Panels.Menu.SelectedLine
return handlePress(lineNumber) return handlePress(selectedLine)
} }
if err := gui.g.SetKeybinding("menu", gocui.KeySpace, gocui.ModNone, wrappedHandlePress); err != nil { if err := gui.g.SetKeybinding("menu", gocui.KeySpace, gocui.ModNone, wrappedHandlePress); err != nil {

View File

@ -194,9 +194,6 @@ func (gui *Gui) refreshMergePanel(g *gocui.Gui) error {
gui.State.ConflictIndex = len(gui.State.Conflicts) - 1 gui.State.ConflictIndex = len(gui.State.Conflicts) - 1
} }
hasFocus := gui.currentViewName(g) == "main" hasFocus := gui.currentViewName(g) == "main"
if hasFocus {
gui.renderMergeOptions(g)
}
content, err := gui.coloredConflictFile(cat, gui.State.Conflicts, gui.State.ConflictIndex, gui.State.ConflictTop, hasFocus) content, err := gui.coloredConflictFile(cat, gui.State.Conflicts, gui.State.ConflictIndex, gui.State.ConflictTop, hasFocus)
if err != nil { if err != nil {
return err return err
@ -233,8 +230,8 @@ func (gui *Gui) switchToMerging(g *gocui.Gui) error {
return gui.refreshMergePanel(g) return gui.refreshMergePanel(g)
} }
func (gui *Gui) renderMergeOptions(g *gocui.Gui) error { func (gui *Gui) renderMergeOptions() error {
return gui.renderOptionsMap(g, map[string]string{ return gui.renderOptionsMap(map[string]string{
"↑ ↓": gui.Tr.SLocalize("selectHunk"), "↑ ↓": gui.Tr.SLocalize("selectHunk"),
"← →": gui.Tr.SLocalize("navigateConflicts"), "← →": gui.Tr.SLocalize("navigateConflicts"),
"space": gui.Tr.SLocalize("pickHunk"), "space": gui.Tr.SLocalize("pickHunk"),

View File

@ -40,22 +40,22 @@ func (gui *Gui) refreshStagingPanel() error {
return nil return nil
} }
var currentLineIndex int var selectedLine int
if gui.State.StagingState != nil { if gui.State.Panels.Staging != nil {
end := len(stageableLines) - 1 end := len(stageableLines) - 1
if end < gui.State.StagingState.CurrentLineIndex { if end < gui.State.Panels.Staging.SelectedLine {
currentLineIndex = end selectedLine = end
} else { } else {
currentLineIndex = gui.State.StagingState.CurrentLineIndex selectedLine = gui.State.Panels.Staging.SelectedLine
} }
} else { } else {
currentLineIndex = 0 selectedLine = 0
} }
gui.State.StagingState = &stagingState{ gui.State.Panels.Staging = &stagingPanelState{
StageableLines: stageableLines, StageableLines: stageableLines,
HunkStarts: hunkStarts, HunkStarts: hunkStarts,
CurrentLineIndex: currentLineIndex, SelectedLine: selectedLine,
Diff: diff, Diff: diff,
} }
@ -74,7 +74,7 @@ func (gui *Gui) handleStagingEscape(g *gocui.Gui, v *gocui.View) error {
return err return err
} }
gui.State.StagingState = nil gui.State.Panels.Staging = nil
return gui.switchFocus(gui.g, nil, gui.getFilesView(gui.g)) return gui.switchFocus(gui.g, nil, gui.getFilesView(gui.g))
} }
@ -96,9 +96,9 @@ func (gui *Gui) handleStagingNextHunk(g *gocui.Gui, v *gocui.View) error {
} }
func (gui *Gui) handleCycleHunk(prev bool) error { func (gui *Gui) handleCycleHunk(prev bool) error {
state := gui.State.StagingState state := gui.State.Panels.Staging
lineNumbers := state.StageableLines lineNumbers := state.StageableLines
currentLine := lineNumbers[state.CurrentLineIndex] currentLine := lineNumbers[state.SelectedLine]
currentHunkIndex := utils.PrevIndex(state.HunkStarts, currentLine) currentHunkIndex := utils.PrevIndex(state.HunkStarts, currentLine)
var newHunkIndex int var newHunkIndex int
if prev { if prev {
@ -115,22 +115,22 @@ func (gui *Gui) handleCycleHunk(prev bool) error {
} }
} }
state.CurrentLineIndex = utils.NextIndex(lineNumbers, state.HunkStarts[newHunkIndex]) state.SelectedLine = utils.NextIndex(lineNumbers, state.HunkStarts[newHunkIndex])
return gui.focusLineAndHunk() return gui.focusLineAndHunk()
} }
func (gui *Gui) handleCycleLine(prev bool) error { func (gui *Gui) handleCycleLine(prev bool) error {
state := gui.State.StagingState state := gui.State.Panels.Staging
lineNumbers := state.StageableLines lineNumbers := state.StageableLines
currentLine := lineNumbers[state.CurrentLineIndex] currentLine := lineNumbers[state.SelectedLine]
var newIndex int var newIndex int
if prev { if prev {
newIndex = utils.PrevIndex(lineNumbers, currentLine) newIndex = utils.PrevIndex(lineNumbers, currentLine)
} else { } else {
newIndex = utils.NextIndex(lineNumbers, currentLine) newIndex = utils.NextIndex(lineNumbers, currentLine)
} }
state.CurrentLineIndex = newIndex state.SelectedLine = newIndex
return gui.focusLineAndHunk() return gui.focusLineAndHunk()
} }
@ -139,9 +139,9 @@ func (gui *Gui) handleCycleLine(prev bool) error {
// selected line and size of the hunk // selected line and size of the hunk
func (gui *Gui) focusLineAndHunk() error { func (gui *Gui) focusLineAndHunk() error {
stagingView := gui.getStagingView(gui.g) stagingView := gui.getStagingView(gui.g)
state := gui.State.StagingState state := gui.State.Panels.Staging
lineNumber := state.StageableLines[state.CurrentLineIndex] lineNumber := state.StageableLines[state.SelectedLine]
// we want the bottom line of the view buffer to ideally be the bottom line // we want the bottom line of the view buffer to ideally be the bottom line
// of the hunk, but if the hunk is too big we'll just go three lines beyond // of the hunk, but if the hunk is too big we'll just go three lines beyond
@ -170,23 +170,7 @@ func (gui *Gui) focusLineAndHunk() error {
bottomLine = lineNumber + 3 bottomLine = lineNumber + 3
} }
return gui.focusLine(lineNumber, bottomLine, stagingView) return gui.generalFocusLine(lineNumber, bottomLine, stagingView)
}
// focusLine takes a lineNumber to focus, and a bottomLine to ensure we can see
func (gui *Gui) focusLine(lineNumber int, bottomLine int, v *gocui.View) error {
_, height := v.Size()
overScroll := bottomLine - height + 1
if overScroll < 0 {
overScroll = 0
}
if err := v.SetOrigin(0, overScroll); err != nil {
return err
}
if err := v.SetCursor(0, lineNumber-overScroll); err != nil {
return err
}
return nil
} }
func (gui *Gui) handleStageHunk(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleStageHunk(g *gocui.Gui, v *gocui.View) error {
@ -198,13 +182,13 @@ func (gui *Gui) handleStageLine(g *gocui.Gui, v *gocui.View) error {
} }
func (gui *Gui) handleStageLineOrHunk(hunk bool) error { func (gui *Gui) handleStageLineOrHunk(hunk bool) error {
state := gui.State.StagingState state := gui.State.Panels.Staging
p, err := git.NewPatchModifier(gui.Log) p, err := git.NewPatchModifier(gui.Log)
if err != nil { if err != nil {
return err return err
} }
currentLine := state.StageableLines[state.CurrentLineIndex] currentLine := state.StageableLines[state.SelectedLine]
var patch string var patch string
if hunk { if hunk {
patch, err = p.ModifyPatchForHunk(state.Diff, state.HunkStarts, currentLine) patch, err = p.ModifyPatchForHunk(state.Diff, state.HunkStarts, currentLine)

View File

@ -8,19 +8,46 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
// list panel functions
func (gui *Gui) getSelectedStashEntry(v *gocui.View) *commands.StashEntry {
selectedLine := gui.State.Panels.Stash.SelectedLine
if selectedLine == -1 {
return nil
}
return gui.State.StashEntries[selectedLine]
}
func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error {
stashEntry := gui.getSelectedStashEntry(v)
if stashEntry == nil {
return gui.renderString(g, "main", gui.Tr.SLocalize("NoStashEntries"))
}
if err := gui.focusPoint(0, gui.State.Panels.Stash.SelectedLine, v); err != nil {
return err
}
go func() {
// doing this asynchronously cos it can take time
diff, _ := gui.GitCommand.GetStashEntryDiff(stashEntry.Index)
_ = gui.renderString(g, "main", diff)
}()
return nil
}
func (gui *Gui) refreshStashEntries(g *gocui.Gui) error { func (gui *Gui) refreshStashEntries(g *gocui.Gui) error {
g.Update(func(g *gocui.Gui) error { g.Update(func(g *gocui.Gui) error {
v, err := g.View("stash")
if err != nil {
panic(err)
}
gui.State.StashEntries = gui.GitCommand.GetStashEntries() gui.State.StashEntries = gui.GitCommand.GetStashEntries()
v.Clear() gui.refreshSelectedLine(&gui.State.Panels.Stash.SelectedLine, len(gui.State.StashEntries))
list, err := utils.RenderList(gui.State.StashEntries) list, err := utils.RenderList(gui.State.StashEntries)
if err != nil { if err != nil {
return err return err
} }
v := gui.getStashView(gui.g)
v.Clear()
fmt.Fprint(v, list) fmt.Fprint(v, list)
return gui.resetOrigin(v) return gui.resetOrigin(v)
@ -28,34 +55,21 @@ func (gui *Gui) refreshStashEntries(g *gocui.Gui) error {
return nil return nil
} }
func (gui *Gui) getSelectedStashEntry(v *gocui.View) *commands.StashEntry { func (gui *Gui) handleStashNextLine(g *gocui.Gui, v *gocui.View) error {
if len(gui.State.StashEntries) == 0 { panelState := gui.State.Panels.Stash
return nil gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.StashEntries), false)
}
stashView, _ := gui.g.View("stash") return gui.handleStashEntrySelect(gui.g, v)
lineNumber := gui.getItemPosition(stashView)
return gui.State.StashEntries[lineNumber]
} }
func (gui *Gui) renderStashOptions(g *gocui.Gui) error { func (gui *Gui) handleStashPrevLine(g *gocui.Gui, v *gocui.View) error {
return gui.renderGlobalOptions(g) panelState := gui.State.Panels.Stash
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.StashEntries), true)
return gui.handleStashEntrySelect(gui.g, v)
} }
func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error { // specific functions
if err := gui.renderStashOptions(g); err != nil {
return err
}
go func() {
stashEntry := gui.getSelectedStashEntry(v)
if stashEntry == nil {
gui.renderString(g, "main", gui.Tr.SLocalize("NoStashEntries"))
return
}
diff, _ := gui.GitCommand.GetStashEntryDiff(stashEntry.Index)
gui.renderString(g, "main", diff)
}()
return nil
}
func (gui *Gui) handleStashApply(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleStashApply(g *gocui.Gui, v *gocui.View) error {
return gui.stashDo(g, v, "apply") return gui.stashDo(g, v, "apply")

View File

@ -19,7 +19,7 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error {
// contents end up cleared // contents end up cleared
g.Update(func(*gocui.Gui) error { g.Update(func(*gocui.Gui) error {
v.Clear() v.Clear()
pushables, pullables := gui.GitCommand.UpstreamDifferenceCount() pushables, pullables := gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
fmt.Fprint(v, "↑"+pushables+"↓"+pullables) fmt.Fprint(v, "↑"+pushables+"↓"+pullables)
branches := gui.State.Branches branches := gui.State.Branches
if err := gui.updateHasMergeConflictStatus(); err != nil { if err := gui.updateHasMergeConflictStatus(); err != nil {
@ -42,16 +42,14 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error {
return nil return nil
} }
func (gui *Gui) renderStatusOptions(g *gocui.Gui) error {
return gui.renderGlobalOptions(g)
}
func (gui *Gui) handleCheckForUpdate(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleCheckForUpdate(g *gocui.Gui, v *gocui.View) error {
gui.Updater.CheckForNewUpdate(gui.onUserUpdateCheckFinish, true) gui.Updater.CheckForNewUpdate(gui.onUserUpdateCheckFinish, true)
return gui.createMessagePanel(gui.g, v, "", gui.Tr.SLocalize("CheckingForUpdates")) return gui.createMessagePanel(gui.g, v, "", gui.Tr.SLocalize("CheckingForUpdates"))
} }
func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error {
blue := color.New(color.FgBlue)
dashboardString := strings.Join( dashboardString := strings.Join(
[]string{ []string{
lazygitTitle(), lazygitTitle(),
@ -60,13 +58,10 @@ func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error {
"Config Options: https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md", "Config Options: https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md",
"Tutorial: https://youtu.be/VDXvbHZYeKY", "Tutorial: https://youtu.be/VDXvbHZYeKY",
"Raise an Issue: https://github.com/jesseduffield/lazygit/issues", "Raise an Issue: https://github.com/jesseduffield/lazygit/issues",
"Buy Jesse a coffee: https://donorbox.org/lazygit", blue.Sprint("Buy Jesse a coffee: https://donorbox.org/lazygit"), // caffeine ain't free
}, "\n\n") }, "\n\n")
if err := gui.renderString(g, "main", dashboardString); err != nil { return gui.renderString(g, "main", dashboardString)
return err
}
return gui.renderStatusOptions(g)
} }
func (gui *Gui) handleOpenConfig(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleOpenConfig(g *gocui.Gui, v *gocui.View) error {

View File

@ -13,10 +13,16 @@ import (
var cyclableViews = []string{"status", "files", "branches", "commits", "stash"} var cyclableViews = []string{"status", "files", "branches", "commits", "stash"}
func (gui *Gui) refreshSidePanels(g *gocui.Gui) error { func (gui *Gui) refreshSidePanels(g *gocui.Gui) error {
gui.refreshBranches(g) if err := gui.refreshBranches(g); err != nil {
gui.refreshFiles(g) return err
gui.refreshCommits(g) }
return nil if err := gui.refreshFiles(g); err != nil {
return err
}
if err := gui.refreshCommits(g); err != nil {
return err
}
return gui.refreshStashEntries(g)
} }
func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error {
@ -78,9 +84,6 @@ func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error {
} }
func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
mainView, _ := g.View("main")
mainView.SetOrigin(0, 0)
switch v.Name() { switch v.Name() {
case "menu": case "menu":
return gui.handleMenuSelect(g, v) return gui.handleMenuSelect(g, v)
@ -90,6 +93,10 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
return gui.handleFileSelect(g, v) return gui.handleFileSelect(g, v)
case "branches": case "branches":
return gui.handleBranchSelect(g, v) return gui.handleBranchSelect(g, v)
case "commits":
return gui.handleCommitSelect(g, v)
case "stash":
return gui.handleStashEntrySelect(g, v)
case "confirmation": case "confirmation":
return nil return nil
case "commitMessage": case "commitMessage":
@ -101,10 +108,6 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
gui.refreshMergePanel(g) gui.refreshMergePanel(g)
v.Highlight = false v.Highlight = false
return nil return nil
case "commits":
return gui.handleCommitSelect(g, v)
case "stash":
return gui.handleStashEntrySelect(g, v)
case "staging": case "staging":
return nil return nil
// return gui.handleStagingSelect(g, v) // return gui.handleStagingSelect(g, v)
@ -164,64 +167,13 @@ func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error {
g.Cursor = newView.Editable g.Cursor = newView.Editable
if err := gui.renderPanelOptions(); err != nil {
return err
}
return gui.newLineFocused(g, newView) return gui.newLineFocused(g, newView)
} }
func (gui *Gui) getItemPosition(v *gocui.View) int {
gui.correctCursor(v)
_, cy := v.Cursor()
_, oy := v.Origin()
return oy + cy
}
func (gui *Gui) cursorUp(g *gocui.Gui, v *gocui.View) error {
// swallowing cursor movements in main
if v == nil || v.Name() == "main" {
return nil
}
ox, oy := v.Origin()
cx, cy := v.Cursor()
if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
if err := v.SetOrigin(ox, oy-1); err != nil {
return err
}
}
gui.newLineFocused(g, v)
return nil
}
func (gui *Gui) cursorDown(g *gocui.Gui, v *gocui.View) error {
// swallowing cursor movements in main
if v == nil || v.Name() == "main" {
return nil
}
cx, cy := v.Cursor()
ox, oy := v.Origin()
ly := v.LinesHeight() - 1
_, height := v.Size()
maxY := height - 1
// if we are at the end we just return
if cy+oy == ly {
return nil
}
var err error
if cy < maxY {
err = v.SetCursor(cx, cy+1)
} else {
err = v.SetOrigin(ox, oy+1)
}
if err != nil {
return err
}
gui.newLineFocused(g, v)
return nil
}
func (gui *Gui) resetOrigin(v *gocui.View) error { func (gui *Gui) resetOrigin(v *gocui.View) error {
if err := v.SetCursor(0, 0); err != nil { if err := v.SetCursor(0, 0); err != nil {
return err return err
@ -230,22 +182,43 @@ func (gui *Gui) resetOrigin(v *gocui.View) error {
} }
// if the cursor down past the last item, move it to the last line // if the cursor down past the last item, move it to the last line
func (gui *Gui) correctCursor(v *gocui.View) error { func (gui *Gui) focusPoint(cx int, cy int, v *gocui.View) error {
cx, cy := v.Cursor() if cy < 0 {
ox, oy := v.Origin()
_, height := v.Size()
maxY := height - 1
ly := v.LinesHeight() - 1
if oy+cy <= ly {
return nil return nil
} }
newCy := utils.Min(ly, maxY) ox, oy := v.Origin()
if err := v.SetCursor(cx, newCy); err != nil { _, height := v.Size()
ly := height - 1
// if line is above origin, move origin and set cursor to zero
// if line is below origin + height, move origin and set cursor to max
// otherwise set cursor to value - origin
if ly > v.LinesHeight() {
if err := v.SetCursor(cx, cy); err != nil {
return err return err
} }
if err := v.SetOrigin(ox, ly-newCy); err != nil { if err := v.SetOrigin(ox, 0); err != nil {
return err return err
} }
} else if cy < oy {
if err := v.SetCursor(cx, 0); err != nil {
return err
}
if err := v.SetOrigin(ox, cy); err != nil {
return err
}
} else if cy > oy+ly {
if err := v.SetCursor(cx, ly); err != nil {
return err
}
if err := v.SetOrigin(ox, cy-ly); err != nil {
return err
}
} else {
if err := v.SetCursor(cx, cy-oy); err != nil {
return err
}
}
return nil return nil
} }
@ -258,6 +231,9 @@ func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error {
return nil return nil
} }
v.Clear() v.Clear()
if err := v.SetOrigin(0, 0); err != nil {
return err
}
output := string(bom.Clean([]byte(s))) output := string(bom.Clean([]byte(s)))
output = utils.NormalizeLinefeeds(output) output = utils.NormalizeLinefeeds(output)
fmt.Fprint(v, output) fmt.Fprint(v, output)
@ -275,8 +251,8 @@ func (gui *Gui) optionsMapToString(optionsMap map[string]string) string {
return strings.Join(optionsArray, ", ") return strings.Join(optionsArray, ", ")
} }
func (gui *Gui) renderOptionsMap(g *gocui.Gui, optionsMap map[string]string) error { func (gui *Gui) renderOptionsMap(optionsMap map[string]string) error {
return gui.renderString(g, "options", gui.optionsMapToString(optionsMap)) return gui.renderString(gui.g, "options", gui.optionsMapToString(optionsMap))
} }
// TODO: refactor properly // TODO: refactor properly
@ -306,6 +282,16 @@ func (gui *Gui) getStagingView(g *gocui.Gui) *gocui.View {
return v return v
} }
func (gui *Gui) getMainView(g *gocui.Gui) *gocui.View {
v, _ := g.View("main")
return v
}
func (gui *Gui) getStashView(g *gocui.Gui) *gocui.View {
v, _ := g.View("stash")
return v
}
func (gui *Gui) trimmedContent(v *gocui.View) string { func (gui *Gui) trimmedContent(v *gocui.View) string {
return strings.TrimSpace(v.Buffer()) return strings.TrimSpace(v.Buffer())
} }
@ -336,3 +322,68 @@ func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error {
_, err := g.SetView(v.Name(), x0, y0, x1, y1, 0) _, err := g.SetView(v.Name(), x0, y0, x1, y1, 0)
return err return err
} }
// generalFocusLine takes a lineNumber to focus, and a bottomLine to ensure we can see
func (gui *Gui) generalFocusLine(lineNumber int, bottomLine int, v *gocui.View) error {
_, height := v.Size()
overScroll := bottomLine - height + 1
if overScroll < 0 {
overScroll = 0
}
if err := v.SetOrigin(0, overScroll); err != nil {
return err
}
if err := v.SetCursor(0, lineNumber-overScroll); err != nil {
return err
}
return nil
}
func (gui *Gui) changeSelectedLine(line *int, total int, up bool) {
if up {
if *line == -1 || *line == 0 {
return
}
*line -= 1
} else {
if *line == -1 || *line == total-1 {
return
}
*line += 1
}
}
func (gui *Gui) refreshSelectedLine(line *int, total int) {
if *line == -1 && total > 0 {
*line = 0
} else if total-1 < *line {
*line = total - 1
}
}
func (gui *Gui) renderListPanel(v *gocui.View, items interface{}) error {
gui.g.Update(func(g *gocui.Gui) error {
list, err := utils.RenderList(items)
if err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
v.Clear()
fmt.Fprint(v, list)
return nil
})
return nil
}
func (gui *Gui) renderPanelOptions() error {
currentView := gui.g.CurrentView()
switch currentView.Name() {
case "menu":
return gui.renderMenuOptions()
case "main":
return gui.renderMergeOptions()
default:
return gui.renderGlobalOptions()
}
}

View File

@ -150,7 +150,7 @@ func addDutch(i18nObject *i18n.Bundle) error {
Other: "Dit bestand heeft geen merge conflicten", Other: "Dit bestand heeft geen merge conflicten",
}, &i18n.Message{ }, &i18n.Message{
ID: "SureResetHardHead", ID: "SureResetHardHead",
Other: "Weet je het zeker dat je `reset --hard HEAD` wil uitvoeren? Het kan dat je hierdoor bestanden verliest", Other: "Weet je het zeker dat je `reset --hard HEAD` en `clean -fd` wil uitvoeren? Het kan dat je hierdoor bestanden verliest",
}, &i18n.Message{ }, &i18n.Message{
ID: "SureTo", ID: "SureTo",
Other: "Weet je het zeker dat je {{.fileName}} wilt {{.deleteVerb}} (je veranderingen zullen worden verwijderd)", Other: "Weet je het zeker dat je {{.fileName}} wilt {{.deleteVerb}} (je veranderingen zullen worden verwijderd)",
@ -396,7 +396,7 @@ func addDutch(i18nObject *i18n.Bundle) error {
Other: `refresh bestanden`, Other: `refresh bestanden`,
}, &i18n.Message{ }, &i18n.Message{
ID: "resetHard", ID: "resetHard",
Other: `harde reset`, Other: `harde reset and verwijderen ongevolgde bestanden`,
}, &i18n.Message{ }, &i18n.Message{
ID: "mergeIntoCurrentBranch", ID: "mergeIntoCurrentBranch",
Other: `merge in met huidige checked out branch`, Other: `merge in met huidige checked out branch`,

View File

@ -158,7 +158,7 @@ func addEnglish(i18nObject *i18n.Bundle) error {
Other: "This file has no merge conflicts", Other: "This file has no merge conflicts",
}, &i18n.Message{ }, &i18n.Message{
ID: "SureResetHardHead", ID: "SureResetHardHead",
Other: "Are you sure you want `reset --hard HEAD`? You may lose changes", Other: "Are you sure you want `reset --hard HEAD` and `clean -fd`? You may lose changes",
}, &i18n.Message{ }, &i18n.Message{
ID: "SureTo", ID: "SureTo",
Other: "Are you sure you want to {{.deleteVerb}} {{.fileName}} (you will lose your changes)?", Other: "Are you sure you want to {{.deleteVerb}} {{.fileName}} (you will lose your changes)?",
@ -404,7 +404,7 @@ func addEnglish(i18nObject *i18n.Bundle) error {
Other: `refresh files`, Other: `refresh files`,
}, &i18n.Message{ }, &i18n.Message{
ID: "resetHard", ID: "resetHard",
Other: `reset hard`, Other: `reset hard and remove untracked files`,
}, &i18n.Message{ }, &i18n.Message{
ID: "mergeIntoCurrentBranch", ID: "mergeIntoCurrentBranch",
Other: `merge into currently checked out branch`, Other: `merge into currently checked out branch`,
@ -456,6 +456,12 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{ }, &i18n.Message{
ID: "CantFindHunk", ID: "CantFindHunk",
Other: `Could not find hunk`, Other: `Could not find hunk`,
}, &i18n.Message{
ID: "FastForward",
Other: `fast-forward this branch from its upstream`,
}, &i18n.Message{
ID: "Fetching",
Other: "fetching and fast-forwarding {{.from}} -> {{.to}} ...",
}, },
) )
} }

View File

@ -139,7 +139,7 @@ func addPolish(i18nObject *i18n.Bundle) error {
Other: "Ten plik nie powoduje konfliktów scalania", Other: "Ten plik nie powoduje konfliktów scalania",
}, &i18n.Message{ }, &i18n.Message{
ID: "SureResetHardHead", ID: "SureResetHardHead",
Other: "Jesteś pewny, że chcesz wykonać `reset --hard HEAD`? Możesz stracić wprowadzone zmiany", Other: "Jesteś pewny, że chcesz wykonać `reset --hard HEAD` i `clean -fd`? Możesz stracić wprowadzone zmiany",
}, &i18n.Message{ }, &i18n.Message{
ID: "SureTo", ID: "SureTo",
Other: "Jesteś pewny, że chcesz {{.deleteVerb}} {{.fileName}} (stracisz swoje wprowadzone zmiany)?", Other: "Jesteś pewny, że chcesz {{.deleteVerb}} {{.fileName}} (stracisz swoje wprowadzone zmiany)?",
@ -382,7 +382,7 @@ func addPolish(i18nObject *i18n.Bundle) error {
Other: `odśwież pliki`, Other: `odśwież pliki`,
}, &i18n.Message{ }, &i18n.Message{
ID: "resetHard", ID: "resetHard",
Other: `zresetuj twardo`, Other: `zresetuj twardo i usuń niepotwierdzone pliki`,
}, &i18n.Message{ }, &i18n.Message{
ID: "mergeIntoCurrentBranch", ID: "mergeIntoCurrentBranch",
Other: `scal do obecnej gałęzi`, Other: `scal do obecnej gałęzi`,

View File

@ -1,6 +1,7 @@
package utils package utils
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"log" "log"
@ -235,3 +236,8 @@ func PrevIndex(numbers []int, currentNumber int) int {
} }
return end return end
} }
func AsJson(i interface{}) string {
bytes, _ := json.MarshalIndent(i, "", " ")
return string(bytes)
}

View File

@ -517,3 +517,14 @@ func TestPrevIndex(t *testing.T) {
}) })
} }
} }
func TestAsJson(t *testing.T) {
type myStruct struct {
a string
}
output := AsJson(&myStruct{a: "foo"})
// no idea why this is returning empty hashes but it's works in the app ¯\_(ツ)_/¯
assert.EqualValues(t, "{}", output)
}