1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-06-15 00:15:32 +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 {
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" {
// this isn't really a secret token: it only has permission to push new rollbar items
hook := rollrus.NewHook("23432119147a4367abf7c0de2aa99a2d", environment)

View File

@ -1,6 +1,7 @@
package commands
import (
"fmt"
"strings"
"github.com/fatih/color"
@ -12,11 +13,19 @@ import (
type Branch struct {
Name string
Recency string
Pushables string
Pullables string
Selected bool
}
// GetDisplayStrings returns the dispaly string of branch
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

View File

@ -154,7 +154,6 @@ func (c *GitCommand) GetStatusFiles() []*File {
}
files = append(files, file)
}
c.Log.Info(files) // TODO: use a dumper-esque log here
return files
}
@ -208,19 +207,33 @@ func includesInt(list []int, a int) bool {
return false
}
// ResetHard does the equivalent of `git reset --hard HEAD`
func (c *GitCommand) ResetHard() error {
return c.Worktree.Reset(&gogit.ResetOptions{Mode: gogit.HardReset})
// ResetAndClean removes all unstaged changes and removes all untracked files
func (c *GitCommand) ResetAndClean() error {
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
func (c *GitCommand) UpstreamDifferenceCount() (string, string) {
pushableCount, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..HEAD --count")
func (c *GitCommand) GetCommitDifferences(from, to string) (string, string) {
command := "git rev-list %s..%s --count"
pushableCount, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf(command, to, from))
if err != nil {
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 {
return "?", "?"
}
@ -609,3 +622,8 @@ func (c *GitCommand) ApplyPatch(patch string) (string, error) {
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.
func TestGitCommandUpstreamDifferentCount(t *testing.T) {
// TestGitCommandGetCommitDifferences is a function.
func TestGitCommandGetCommitDifferences(t *testing.T) {
type scenario struct {
testName string
command func(string, ...string) *exec.Cmd
@ -610,7 +610,7 @@ func TestGitCommandUpstreamDifferentCount(t *testing.T) {
t.Run(s.testName, func(t *testing.T) {
gitCmd := newDummyGitCommand()
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/lazygit/pkg/commands"
"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 {
index := gui.getItemPosition(gui.getBranchesView(g))
if index == 0 {
if gui.State.Panels.Branches.SelectedLine == -1 {
return nil
}
if gui.State.Panels.Branches.SelectedLine == 0 {
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 {
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)
}
func (gui *Gui) handleCreatePullRequestPress(g *gocui.Gui, v *gocui.View) error {
branch := gui.getSelectedBranch(gui.getBranchesView(g))
pullRequest := commands.NewPullRequest(gui.GitCommand)
branch := gui.getSelectedBranch()
if err := pullRequest.Create(branch); err != nil {
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 {
branch := gui.getSelectedBranch(v)
branch := gui.getSelectedBranch()
message := gui.Tr.SLocalize("SureForceCheckout")
title := gui.Tr.SLocalize("ForceCheckoutBranch")
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 {
selectedBranch := gui.getSelectedBranch()
if selectedBranch == nil {
return nil
}
checkedOutBranch := gui.State.Branches[0]
selectedBranch := gui.getSelectedBranch(v)
if checkedOutBranch.Name == selectedBranch.Name {
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 {
checkedOutBranch := gui.State.Branches[0]
selectedBranch := gui.getSelectedBranch(v)
selectedBranch := gui.getSelectedBranch()
defer gui.refreshSidePanels(g)
if checkedOutBranch.Name == selectedBranch.Name {
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
}
func (gui *Gui) getSelectedBranch(v *gocui.View) *commands.Branch {
lineNumber := gui.getItemPosition(v)
return gui.State.Branches[lineNumber]
}
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
func (gui *Gui) handleFastForward(g *gocui.Gui, v *gocui.View) error {
branch := gui.getSelectedBranch()
if branch == nil {
return nil
}
// 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"))
if branch.Pushables == "" {
return nil
}
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() {
branch := gui.getSelectedBranch(v)
diff, err := gui.GitCommand.GetBranchGraph(branch.Name)
if err != nil && strings.HasPrefix(diff, "fatal: ambiguous argument") {
diff = gui.Tr.SLocalize("NoTrackingThisBranch")
_ = gui.createMessagePanel(gui.g, v, "", message)
if err := gui.GitCommand.FastForward(branch.Name); err != nil {
_ = gui.createErrorPanel(gui.g, err.Error())
} else {
_ = gui.closeConfirmationPrompt(gui.g)
_ = gui.RenderSelectedBranchUpstreamDifferences()
}
gui.renderString(g, "main", diff)
}()
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
return gui.Errors.ErrSubProcess
}
gui.refreshFiles(g)
v.Clear()
v.SetCursor(0, 0)
g.SetViewOnBottom("commitMessage")
gui.switchFocus(g, v, gui.getFilesView(g))
return gui.refreshCommits(g)
_ = v.SetCursor(0, 0)
_ = v.SetOrigin(0, 0)
_, _ = g.SetViewOnBottom("commitMessage")
_ = gui.switchFocus(g, v, gui.getFilesView(g))
return gui.refreshSidePanels(g)
}
func (gui *Gui) handleCommitClose(g *gocui.Gui, v *gocui.View) error {

View File

@ -9,29 +9,54 @@ import (
"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 {
g.Update(func(*gocui.Gui) error {
commits, err := gui.GitCommand.GetCommits()
if err != nil {
return err
}
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)
if err != nil {
return err
}
v := gui.getCommitsView(gui.g)
v.Clear()
fmt.Fprint(v, list)
gui.refreshStatus(g)
if g.CurrentView().Name() == "commits" {
if v == g.CurrentView() {
gui.handleCommitSelect(g, v)
}
return nil
@ -39,11 +64,27 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error {
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 {
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)
if err != nil {
panic(err)
commit := gui.getSelectedCommit(g)
if commit == nil {
panic(errors.New(gui.Tr.SLocalize("NoCommitsThisBranch")))
}
if err := gui.GitCommand.ResetToCommit(commit.Sha); err != nil {
return gui.createErrorPanel(g, err.Error())
@ -55,42 +96,21 @@ func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error
panic(err)
}
gui.resetOrigin(commitView)
return gui.handleCommitSelect(g, nil)
gui.State.Panels.Commits.SelectedLine = 0
return gui.handleCommitSelect(g, commitView)
}, 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 {
if gui.getItemPosition(v) != 0 {
if gui.State.Panels.Commits.SelectedLine != 0 {
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"))
}
commit, err := gui.getSelectedCommit(g)
if err != nil {
return err
commit := gui.getSelectedCommit(g)
if commit == nil {
return errors.New(gui.Tr.SLocalize("NoCommitsThisBranch"))
}
if err := gui.GitCommand.SquashPreviousTwoCommits(commit.Name); err != nil {
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 {
if len(gui.State.Commits) == 1 {
if len(gui.State.Commits) <= 1 {
return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash"))
}
if gui.anyUnStagedChanges(gui.State.Files) {
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantFixupWhileUnstagedChanges"))
}
branch := gui.State.Branches[0]
commit, err := gui.getSelectedCommit(g)
if err != nil {
return err
commit := gui.getSelectedCommit(g)
if commit == nil {
return gui.createErrorPanel(g, gui.Tr.SLocalize("NoCommitsThisBranch"))
}
message := gui.Tr.SLocalize("SureFixupThisCommit")
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 {
if gui.getItemPosition(gui.getCommitsView(g)) != 0 {
if gui.State.Panels.Commits.SelectedLine != 0 {
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 {
@ -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 {
if gui.getItemPosition(gui.getCommitsView(g)) != 0 {
if gui.State.Panels.Commits.SelectedLine != 0 {
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
}
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 {
view, err := g.View("confirmation")
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 {
panic(err)
@ -77,11 +77,10 @@ func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt
confirmationView.Wrap = true
confirmationView.FgColor = gocui.ColorWhite
}
gui.g.Update(func(g *gocui.Gui) error {
confirmationView.Clear()
if err := gui.switchFocus(gui.g, currentView, confirmationView); err != nil {
return nil, err
}
return gui.switchFocus(gui.g, currentView, confirmationView)
})
return confirmationView, nil
}

View File

@ -15,6 +15,79 @@ import (
"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 {
files := gui.State.Files
result := make([]*commands.File, 0)
@ -139,18 +212,6 @@ func (gui *Gui) handleAddPatch(g *gocui.Gui, v *gocui.View) error {
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 {
file, err := gui.getSelectedFile(g)
if err != nil {
@ -194,30 +255,6 @@ func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error {
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 {
if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts {
return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit"))
@ -309,6 +346,7 @@ func (gui *Gui) refreshStateFiles() {
// get files to stage
files := gui.GitCommand.GetStatusFiles()
gui.State.Files = gui.GitCommand.MergeStatusFiles(gui.State.Files, files)
gui.refreshSelectedLine(&gui.State.Panels.Files.SelectedLine, len(gui.State.Files))
gui.updateHasMergeConflictStatus()
}
@ -340,27 +378,6 @@ func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) {
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 {
if err := gui.createMessagePanel(gui.g, v, "", gui.Tr.SLocalize("PullWait")); err != nil {
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 {
// 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" {
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)
}
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 {
if err := gui.GitCommand.ResetHard(); err != nil {
if err := gui.GitCommand.ResetAndClean(); err != nil {
gui.createErrorPanel(g, err.Error())
}
return gui.refreshFiles(g)

View File

@ -76,13 +76,45 @@ type Gui struct {
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
HunkStarts []int
CurrentLineIndex int
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 {
Files []*commands.File
Branches []*commands.Branch
@ -96,7 +128,7 @@ type guiState struct {
EditHistory *stack.Stack
Platform commands.Platform
Updating bool
StagingState *stagingState
Panels *panelStates
}
// 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),
EditHistory: stack.New(),
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{
@ -197,9 +236,11 @@ func (gui *Gui) layout(g *gocui.Gui) error {
}
v.Title = gui.Tr.SLocalize("NotEnoughSpace")
v.Wrap = true
g.SetCurrentView(v.Name())
g.SetViewOnTop("limit")
}
return nil
} else {
_, _ = g.SetViewOnBottom("limit")
}
g.DeleteView("limit")
@ -251,12 +292,13 @@ func (gui *Gui) layout(g *gocui.Gui) error {
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 {
return err
}
v.Title = gui.Tr.SLocalize("BranchesTitle")
v.FgColor = gocui.ColorWhite
branchesView.Title = gui.Tr.SLocalize("BranchesTitle")
branchesView.FgColor = gocui.ColorWhite
}
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
}
gui.handleFileSelect(g, filesView)
gui.refreshFiles(g)
gui.refreshBranches(g)
gui.refreshCommits(g)
gui.refreshStashEntries(g)
if _, err := gui.g.SetCurrentView(filesView.Name()); err != nil {
return err
}
if err := gui.refreshSidePanels(gui.g); err != nil {
return err
}
if err := gui.switchFocus(g, nil, filesView); err != nil {
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)
}
@ -416,8 +477,8 @@ func (gui *Gui) renderAppStatus(g *gocui.Gui) error {
return nil
}
func (gui *Gui) renderGlobalOptions(g *gocui.Gui) error {
return gui.renderOptionsMap(g, map[string]string{
func (gui *Gui) renderGlobalOptions() error {
return gui.renderOptionsMap(map[string]string{
"PgUp/PgDn": gui.Tr.SLocalize("scroll"),
"← → ↑ ↓": gui.Tr.SLocalize("navigate"),
"esc/q": gui.Tr.SLocalize("close"),

View File

@ -210,7 +210,7 @@ func (gui *Gui) GetKeybindings() []*Binding {
ViewName: "files",
Key: 'D',
Modifier: gocui.ModNone,
Handler: gui.handleResetHard,
Handler: gui.handleResetAndClean,
Description: gui.Tr.SLocalize("resetHard"),
}, {
ViewName: "files",
@ -328,6 +328,12 @@ func (gui *Gui) GetKeybindings() []*Binding {
Modifier: gocui.ModNone,
Handler: gui.handleMerge,
Description: gui.Tr.SLocalize("mergeIntoCurrentBranch"),
}, {
ViewName: "branches",
Key: 'f',
Modifier: gocui.ModNone,
Handler: gui.handleFastForward,
Description: gui.Tr.SLocalize("FastForward"),
}, {
ViewName: "commits",
Key: 's',
@ -469,19 +475,33 @@ func (gui *Gui) GetKeybindings() []*Binding {
},
}
// Would make these keybindings global but that interferes with editing
// input in the confirmation panel
for _, viewName := range []string{"status", "files", "branches", "commits", "stash", "menu"} {
for _, viewName := range []string{"status", "branches", "files", "commits", "stash", "menu"} {
bindings = append(bindings, []*Binding{
{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.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: '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"
)
// list panel functions
func (gui *Gui) handleMenuSelect(g *gocui.Gui, v *gocui.View) error {
// doing nothing for now
// but it is needed for switch in newLineFocused
return nil
return gui.focusPoint(0, gui.State.Panels.Menu.SelectedLine, v)
}
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{
"esc/q": gui.Tr.SLocalize("close"),
"↑ ↓": gui.Tr.SLocalize("navigate"),
"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 {
@ -46,14 +62,11 @@ func (gui *Gui) createMenu(items interface{}, handlePress func(int) error) error
menuView.FgColor = gocui.ColorWhite
menuView.Clear()
fmt.Fprint(menuView, list)
if err := gui.renderMenuOptions(gui.g); err != nil {
return err
}
gui.State.Panels.Menu.SelectedLine = 0
wrappedHandlePress := func(g *gocui.Gui, v *gocui.View) error {
lineNumber := gui.getItemPosition(v)
return handlePress(lineNumber)
selectedLine := gui.State.Panels.Menu.SelectedLine
return handlePress(selectedLine)
}
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
}
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)
if err != nil {
return err
@ -233,8 +230,8 @@ func (gui *Gui) switchToMerging(g *gocui.Gui) error {
return gui.refreshMergePanel(g)
}
func (gui *Gui) renderMergeOptions(g *gocui.Gui) error {
return gui.renderOptionsMap(g, map[string]string{
func (gui *Gui) renderMergeOptions() error {
return gui.renderOptionsMap(map[string]string{
"↑ ↓": gui.Tr.SLocalize("selectHunk"),
"← →": gui.Tr.SLocalize("navigateConflicts"),
"space": gui.Tr.SLocalize("pickHunk"),

View File

@ -40,22 +40,22 @@ func (gui *Gui) refreshStagingPanel() error {
return nil
}
var currentLineIndex int
if gui.State.StagingState != nil {
var selectedLine int
if gui.State.Panels.Staging != nil {
end := len(stageableLines) - 1
if end < gui.State.StagingState.CurrentLineIndex {
currentLineIndex = end
if end < gui.State.Panels.Staging.SelectedLine {
selectedLine = end
} else {
currentLineIndex = gui.State.StagingState.CurrentLineIndex
selectedLine = gui.State.Panels.Staging.SelectedLine
}
} else {
currentLineIndex = 0
selectedLine = 0
}
gui.State.StagingState = &stagingState{
gui.State.Panels.Staging = &stagingPanelState{
StageableLines: stageableLines,
HunkStarts: hunkStarts,
CurrentLineIndex: currentLineIndex,
SelectedLine: selectedLine,
Diff: diff,
}
@ -74,7 +74,7 @@ func (gui *Gui) handleStagingEscape(g *gocui.Gui, v *gocui.View) error {
return err
}
gui.State.StagingState = nil
gui.State.Panels.Staging = nil
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 {
state := gui.State.StagingState
state := gui.State.Panels.Staging
lineNumbers := state.StageableLines
currentLine := lineNumbers[state.CurrentLineIndex]
currentLine := lineNumbers[state.SelectedLine]
currentHunkIndex := utils.PrevIndex(state.HunkStarts, currentLine)
var newHunkIndex int
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()
}
func (gui *Gui) handleCycleLine(prev bool) error {
state := gui.State.StagingState
state := gui.State.Panels.Staging
lineNumbers := state.StageableLines
currentLine := lineNumbers[state.CurrentLineIndex]
currentLine := lineNumbers[state.SelectedLine]
var newIndex int
if prev {
newIndex = utils.PrevIndex(lineNumbers, currentLine)
} else {
newIndex = utils.NextIndex(lineNumbers, currentLine)
}
state.CurrentLineIndex = newIndex
state.SelectedLine = newIndex
return gui.focusLineAndHunk()
}
@ -139,9 +139,9 @@ func (gui *Gui) handleCycleLine(prev bool) error {
// selected line and size of the hunk
func (gui *Gui) focusLineAndHunk() error {
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
// 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
}
return gui.focusLine(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
return gui.generalFocusLine(lineNumber, bottomLine, stagingView)
}
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 {
state := gui.State.StagingState
state := gui.State.Panels.Staging
p, err := git.NewPatchModifier(gui.Log)
if err != nil {
return err
}
currentLine := state.StageableLines[state.CurrentLineIndex]
currentLine := state.StageableLines[state.SelectedLine]
var patch string
if hunk {
patch, err = p.ModifyPatchForHunk(state.Diff, state.HunkStarts, currentLine)

View File

@ -8,19 +8,46 @@ import (
"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 {
g.Update(func(g *gocui.Gui) error {
v, err := g.View("stash")
if err != nil {
panic(err)
}
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)
if err != nil {
return err
}
v := gui.getStashView(gui.g)
v.Clear()
fmt.Fprint(v, list)
return gui.resetOrigin(v)
@ -28,34 +55,21 @@ func (gui *Gui) refreshStashEntries(g *gocui.Gui) error {
return nil
}
func (gui *Gui) getSelectedStashEntry(v *gocui.View) *commands.StashEntry {
if len(gui.State.StashEntries) == 0 {
return nil
}
stashView, _ := gui.g.View("stash")
lineNumber := gui.getItemPosition(stashView)
return gui.State.StashEntries[lineNumber]
func (gui *Gui) handleStashNextLine(g *gocui.Gui, v *gocui.View) error {
panelState := gui.State.Panels.Stash
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.StashEntries), false)
return gui.handleStashEntrySelect(gui.g, v)
}
func (gui *Gui) renderStashOptions(g *gocui.Gui) error {
return gui.renderGlobalOptions(g)
func (gui *Gui) handleStashPrevLine(g *gocui.Gui, v *gocui.View) error {
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 {
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
}
// specific functions
func (gui *Gui) handleStashApply(g *gocui.Gui, v *gocui.View) error {
return gui.stashDo(g, v, "apply")

View File

@ -19,7 +19,7 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error {
// contents end up cleared
g.Update(func(*gocui.Gui) error {
v.Clear()
pushables, pullables := gui.GitCommand.UpstreamDifferenceCount()
pushables, pullables := gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
fmt.Fprint(v, "↑"+pushables+"↓"+pullables)
branches := gui.State.Branches
if err := gui.updateHasMergeConflictStatus(); err != nil {
@ -42,16 +42,14 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error {
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 {
gui.Updater.CheckForNewUpdate(gui.onUserUpdateCheckFinish, true)
return gui.createMessagePanel(gui.g, v, "", gui.Tr.SLocalize("CheckingForUpdates"))
}
func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error {
blue := color.New(color.FgBlue)
dashboardString := strings.Join(
[]string{
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",
"Tutorial: https://youtu.be/VDXvbHZYeKY",
"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")
if err := gui.renderString(g, "main", dashboardString); err != nil {
return err
}
return gui.renderStatusOptions(g)
return gui.renderString(g, "main", dashboardString)
}
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"}
func (gui *Gui) refreshSidePanels(g *gocui.Gui) error {
gui.refreshBranches(g)
gui.refreshFiles(g)
gui.refreshCommits(g)
return nil
if err := gui.refreshBranches(g); err != nil {
return err
}
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 {
@ -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 {
mainView, _ := g.View("main")
mainView.SetOrigin(0, 0)
switch v.Name() {
case "menu":
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)
case "branches":
return gui.handleBranchSelect(g, v)
case "commits":
return gui.handleCommitSelect(g, v)
case "stash":
return gui.handleStashEntrySelect(g, v)
case "confirmation":
return nil
case "commitMessage":
@ -101,10 +108,6 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
gui.refreshMergePanel(g)
v.Highlight = false
return nil
case "commits":
return gui.handleCommitSelect(g, v)
case "stash":
return gui.handleStashEntrySelect(g, v)
case "staging":
return nil
// 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
if err := gui.renderPanelOptions(); err != nil {
return err
}
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 {
if err := v.SetCursor(0, 0); err != nil {
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
func (gui *Gui) correctCursor(v *gocui.View) error {
cx, cy := v.Cursor()
ox, oy := v.Origin()
_, height := v.Size()
maxY := height - 1
ly := v.LinesHeight() - 1
if oy+cy <= ly {
func (gui *Gui) focusPoint(cx int, cy int, v *gocui.View) error {
if cy < 0 {
return nil
}
newCy := utils.Min(ly, maxY)
if err := v.SetCursor(cx, newCy); err != nil {
ox, oy := v.Origin()
_, 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
}
if err := v.SetOrigin(ox, ly-newCy); err != nil {
if err := v.SetOrigin(ox, 0); err != nil {
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
}
@ -258,6 +231,9 @@ func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error {
return nil
}
v.Clear()
if err := v.SetOrigin(0, 0); err != nil {
return err
}
output := string(bom.Clean([]byte(s)))
output = utils.NormalizeLinefeeds(output)
fmt.Fprint(v, output)
@ -275,8 +251,8 @@ func (gui *Gui) optionsMapToString(optionsMap map[string]string) string {
return strings.Join(optionsArray, ", ")
}
func (gui *Gui) renderOptionsMap(g *gocui.Gui, optionsMap map[string]string) error {
return gui.renderString(g, "options", gui.optionsMapToString(optionsMap))
func (gui *Gui) renderOptionsMap(optionsMap map[string]string) error {
return gui.renderString(gui.g, "options", gui.optionsMapToString(optionsMap))
}
// TODO: refactor properly
@ -306,6 +282,16 @@ func (gui *Gui) getStagingView(g *gocui.Gui) *gocui.View {
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 {
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)
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",
}, &i18n.Message{
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{
ID: "SureTo",
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`,
}, &i18n.Message{
ID: "resetHard",
Other: `harde reset`,
Other: `harde reset and verwijderen ongevolgde bestanden`,
}, &i18n.Message{
ID: "mergeIntoCurrentBranch",
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",
}, &i18n.Message{
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{
ID: "SureTo",
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`,
}, &i18n.Message{
ID: "resetHard",
Other: `reset hard`,
Other: `reset hard and remove untracked files`,
}, &i18n.Message{
ID: "mergeIntoCurrentBranch",
Other: `merge into currently checked out branch`,
@ -456,6 +456,12 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CantFindHunk",
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",
}, &i18n.Message{
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{
ID: "SureTo",
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`,
}, &i18n.Message{
ID: "resetHard",
Other: `zresetuj twardo`,
Other: `zresetuj twardo i usuń niepotwierdzone pliki`,
}, &i18n.Message{
ID: "mergeIntoCurrentBranch",
Other: `scal do obecnej gałęzi`,

View File

@ -1,6 +1,7 @@
package utils
import (
"encoding/json"
"errors"
"fmt"
"log"
@ -235,3 +236,8 @@ func PrevIndex(numbers []int, currentNumber int) int {
}
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)
}