From 9489a9447396b30bca86ea3df201cacfdffdb1a9 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 8 Dec 2018 16:54:54 +1100 Subject: [PATCH] Make merge panel its own panel --- .gitignore | 4 +- pkg/app/app.go | 14 +++- pkg/commands/git.go | 43 +++++++++- pkg/gui/branches_panel.go | 32 +++++--- pkg/gui/commit_message_panel.go | 6 +- pkg/gui/commits_panel.go | 10 ++- pkg/gui/confirmation_panel.go | 2 +- pkg/gui/files_panel.go | 90 +++++++++------------ pkg/gui/gui.go | 74 ++++++++++------- pkg/gui/keybindings.go | 58 +++++++------- pkg/gui/menu_panel.go | 13 ++- pkg/gui/merge_panel.go | 137 +++++++++++++++++++------------- pkg/gui/options_menu_panel.go | 3 +- pkg/gui/rebase_options_panel.go | 61 ++++++++++++++ pkg/gui/recent_repos_panel.go | 2 +- pkg/gui/staging_panel.go | 6 +- pkg/gui/stash_panel.go | 17 +++- pkg/gui/status_panel.go | 25 +++++- pkg/gui/view_helpers.go | 58 +++++++++----- pkg/i18n/english.go | 30 +++++++ 20 files changed, 467 insertions(+), 218 deletions(-) create mode 100644 pkg/gui/rebase_options_panel.go diff --git a/.gitignore b/.gitignore index e87f09a89..42ef840fe 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,6 @@ lazygit !.gitignore !.goreleaser.yml !.circleci/ -!.github/ \ No newline at end of file +!.github/ + +test/git_server/data \ No newline at end of file diff --git a/pkg/app/app.go b/pkg/app/app.go index 74152b08b..ad5edca70 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -4,6 +4,7 @@ import ( "io" "io/ioutil" "os" + "path/filepath" "github.com/heroku/rollrus" "github.com/jesseduffield/lazygit/pkg/commands" @@ -11,6 +12,7 @@ import ( "github.com/jesseduffield/lazygit/pkg/gui" "github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/updates" + "github.com/shibukawa/configdir" "github.com/sirupsen/logrus" ) @@ -33,9 +35,15 @@ func newProductionLogger(config config.AppConfigurer) *logrus.Logger { return log } -func newDevelopmentLogger() *logrus.Logger { +func globalConfigDir() string { + configDirs := configdir.New("jesseduffield", "lazygit") + configDir := configDirs.QueryFolders(configdir.Global)[0] + return configDir.Path +} + +func newDevelopmentLogger(config config.AppConfigurer) *logrus.Logger { log := logrus.New() - file, err := os.OpenFile("development.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + file, err := os.OpenFile(filepath.Join(globalConfigDir(), "development.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { panic("unable to log to file") // TODO: don't panic (also, remove this call to the `panic` function) } @@ -48,7 +56,7 @@ func newLogger(config config.AppConfigurer) *logrus.Entry { environment := "production" if config.GetDebug() { environment = "development" - log = newDevelopmentLogger() + log = newDevelopmentLogger(config) } else { log = newProductionLogger(config) } diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 4fed44d2a..7a0932501 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -285,6 +285,14 @@ func (c *GitCommand) AbortRebaseBranch() error { return c.OSCommand.RunCommand("git rebase --abort") } +func (c *GitCommand) ContinueMergeBranch() error { + return c.OSCommand.RunCommand("git merge --continue") +} + +func (c *GitCommand) AbortMergeBranch() error { + return c.OSCommand.RunCommand("git merge --abort") +} + // Fetch fetch git repo func (c *GitCommand) Fetch() error { return c.OSCommand.RunCommand("git fetch") @@ -682,7 +690,40 @@ func (c *GitCommand) Ignore(filename string) error { // Show shows the diff of a commit func (c *GitCommand) Show(sha string) (string, error) { - return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git show --color %s", sha)) + show, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git show --color %s", sha)) + if err != nil { + return "", err + } + + // if this is a merge commit, we need to go a step further and get the diff between the two branches we merged + revList, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git rev-list -1 --merges %s^...%s", sha, sha)) + if err != nil { + // turns out we get an error here when it's the first commit. We'll just return the original show + return show, nil + } + if len(revList) == 0 { + return show, nil + } + + // we want to pull out 1a6a69a and 3b51d7c from this: + // commit ccc771d8b13d5b0d4635db4463556366470fd4f6 + // Merge: 1a6a69a 3b51d7c + lines := utils.SplitLines(show) + if len(lines) < 2 { + return show, nil + } + + secondLineWords := strings.Split(lines[1], " ") + if len(secondLineWords) < 3 { + return show, nil + } + + mergeDiff, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git diff --color %s...%s", secondLineWords[1], secondLineWords[2])) + if err != nil { + return "", err + } + + return show + mergeDiff, nil } // GetRemoteURL returns current repo remote url diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index 165a75a4a..82a850e9d 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -53,7 +53,7 @@ func (gui *Gui) RenderSelectedBranchUpstreamDifferences() error { branch := gui.getSelectedBranch() branch.Pushables, branch.Pullables = gui.GitCommand.GetBranchUpstreamDifferenceCount(branch.Name) - return gui.renderListPanel(gui.getBranchesView(gui.g), gui.State.Branches) + return gui.renderListPanel(gui.getBranchesView(), gui.State.Branches) } // gui.refreshStatus is called at the end of this because that's when we can @@ -67,9 +67,6 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error { 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 } @@ -82,6 +79,10 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error { 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) + + if err := gui.resetOrigin(gui.getMainView()); err != nil { + return err + } return gui.handleBranchSelect(gui.g, v) } @@ -89,6 +90,9 @@ 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) + if err := gui.resetOrigin(gui.getMainView()); err != nil { + return err + } return gui.handleBranchSelect(gui.g, v) } @@ -108,8 +112,7 @@ func (gui *Gui) handleRebase(g *gocui.Gui, v *gocui.View) error { } if err := gui.GitCommand.RebaseBranch(selectedBranch); err != nil { - gui.Log.Errorln(err) - if err := gui.createConfirmationPanel(g, v, "Rebase failed", "Damn, conflicts! To abort press 'esc', otherwise press 'enter'", + return gui.createConfirmationPanel(g, v, "Auto-rebase failed", gui.Tr.SLocalize("FoundConflicts"), func(g *gocui.Gui, v *gocui.View) error { return nil }, func(g *gocui.Gui, v *gocui.View) error { @@ -117,9 +120,8 @@ func (gui *Gui) handleRebase(g *gocui.Gui, v *gocui.View) error { return err } return gui.refreshSidePanels(g) - }); err != nil { - gui.Log.Errorln(err) - } + }, + ) } return gui.refreshSidePanels(g) @@ -251,6 +253,18 @@ func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error { return gui.createErrorPanel(g, gui.Tr.SLocalize("CantMergeBranchIntoItself")) } if err := gui.GitCommand.Merge(selectedBranch.Name); err != nil { + if strings.Contains(err.Error(), "fix conflicts") { + return gui.createConfirmationPanel(g, v, "Auto-merge failed", gui.Tr.SLocalize("FoundConflicts"), + func(g *gocui.Gui, v *gocui.View) error { + return nil + }, func(g *gocui.Gui, v *gocui.View) error { + if err := gui.GitCommand.AbortMergeBranch(); err != nil { + return err + } + return gui.refreshSidePanels(g) + }, + ) + } return gui.createErrorPanel(g, err.Error()) } return nil diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index 33eb5b218..130ec3401 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -27,13 +27,13 @@ func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error { _ = v.SetCursor(0, 0) _ = v.SetOrigin(0, 0) _, _ = g.SetViewOnBottom("commitMessage") - _ = gui.switchFocus(g, v, gui.getFilesView(g)) + _ = gui.switchFocus(g, v, gui.getFilesView()) return gui.refreshSidePanels(g) } func (gui *Gui) handleCommitClose(g *gocui.Gui, v *gocui.View) error { g.SetViewOnBottom("commitMessage") - return gui.switchFocus(g, v, gui.getFilesView(g)) + return gui.switchFocus(g, v, gui.getFilesView()) } func (gui *Gui) handleCommitFocused(g *gocui.Gui, v *gocui.View) error { @@ -87,6 +87,6 @@ func (gui *Gui) RenderCommitLength() { if !gui.Config.GetUserConfig().GetBool("gui.commitLength.show") { return } - v := gui.getCommitMessageView(gui.g) + v := gui.getCommitMessageView() v.Subtitle = gui.getBufferLength(v) } diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index 4ff79960d..53e979ac4 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -51,7 +51,7 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error { return err } - v := gui.getCommitsView(gui.g) + v := gui.getCommitsView() v.Clear() fmt.Fprint(v, list) @@ -68,6 +68,9 @@ 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) + if err := gui.resetOrigin(gui.getMainView()); err != nil { + return err + } return gui.handleCommitSelect(gui.g, v) } @@ -75,6 +78,9 @@ 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) + if err := gui.resetOrigin(gui.getMainView()); err != nil { + return err + } return gui.handleCommitSelect(gui.g, v) } @@ -92,7 +98,7 @@ func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error if err := gui.refreshCommits(g); err != nil { panic(err) } - if err := gui.refreshFiles(g); err != nil { + if err := gui.refreshFiles(); err != nil { panic(err) } gui.resetOrigin(commitView) diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index 30d5e0661..66e859be3 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -78,7 +78,6 @@ func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt confirmationView.FgColor = gocui.ColorWhite } gui.g.Update(func(g *gocui.Gui) error { - confirmationView.Clear() return gui.switchFocus(gui.g, currentView, confirmationView) }) return confirmationView, nil @@ -86,6 +85,7 @@ func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt func (gui *Gui) onNewPopupPanel() { gui.g.SetViewOnBottom("commitMessage") + gui.g.SetViewOnBottom("menu") } func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error { diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 2059ac97f..3c8e6c7e7 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -35,23 +35,24 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error { 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 } + if file.HasMergeConflicts { + return gui.refreshMergePanel(g) + } else { + if _, err := gui.g.SetViewOnBottom("merging"); 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 - } +func (gui *Gui) refreshFiles() error { + filesView := gui.getFilesView() gui.refreshStateFiles() gui.g.Update(func(g *gocui.Gui) error { @@ -63,8 +64,8 @@ func (gui *Gui) refreshFiles(g *gocui.Gui) error { } fmt.Fprint(filesView, list) - if filesView == g.CurrentView() { - return gui.handleFileSelect(g, filesView) + if filesView == gui.g.CurrentView() { + return gui.handleFileSelect(gui.g, filesView) } return nil }) @@ -76,6 +77,9 @@ 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) + if err := gui.resetOrigin(gui.getMainView()); err != nil { + return err + } return gui.handleFileSelect(gui.g, v) } @@ -83,6 +87,9 @@ 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) + if err := gui.resetOrigin(gui.getMainView()); err != nil { + return err + } return gui.handleFileSelect(gui.g, v) } @@ -130,13 +137,9 @@ func (gui *Gui) handleEnterFile(g *gocui.Gui, v *gocui.View) error { return gui.handleSwitchToMerge(g, v) } if !file.HasUnstagedChanges { - gui.Log.WithField("staging", "staging").Info("making error panel") return gui.createErrorPanel(g, gui.Tr.SLocalize("FileStagingRequirements")) } - stagingView, err := g.View("staging") - if err != nil { - return err - } + stagingView := gui.getStagingView() if err := gui.switchFocus(g, v, stagingView); err != nil { return err } @@ -162,7 +165,7 @@ func (gui *Gui) handleFilePress(g *gocui.Gui, v *gocui.View) error { gui.GitCommand.UnStageFile(file.Name, file.Tracked) } - if err := gui.refreshFiles(g); err != nil { + if err := gui.refreshFiles(); err != nil { return err } @@ -189,7 +192,7 @@ func (gui *Gui) handleStageAll(g *gocui.Gui, v *gocui.View) error { _ = gui.createErrorPanel(g, err.Error()) } - if err := gui.refreshFiles(g); err != nil { + if err := gui.refreshFiles(); err != nil { return err } @@ -240,7 +243,7 @@ func (gui *Gui) handleFileRemove(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.RemoveFile(file); err != nil { return err } - return gui.refreshFiles(g) + return gui.refreshFiles() }, nil) } @@ -255,14 +258,14 @@ func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.Ignore(file.Name); err != nil { return gui.createErrorPanel(g, err.Error()) } - return gui.refreshFiles(g) + return gui.refreshFiles() } func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { if len(gui.stagedFiles()) == 0 && gui.State.WorkingTreeState == "normal" { return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit")) } - commitMessageView := gui.getCommitMessageView(g) + commitMessageView := gui.getCommitMessageView() g.Update(func(g *gocui.Gui) error { g.SetViewOnTop("commitMessage") gui.switchFocus(g, filesView, commitMessageView) @@ -342,7 +345,7 @@ func (gui *Gui) handleFileOpen(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) handleRefreshFiles(g *gocui.Gui, v *gocui.View) error { - return gui.refreshFiles(g) + return gui.refreshFiles() } func (gui *Gui) refreshStateFiles() { @@ -353,27 +356,6 @@ func (gui *Gui) refreshStateFiles() { gui.updateWorkTreeState() } -func (gui *Gui) updateWorkTreeState() error { - merging, err := gui.GitCommand.IsInMergeState() - if err != nil { - return err - } - if merging { - gui.State.WorkingTreeState = "merging" - return nil - } - rebasing, err := gui.GitCommand.IsInRebaseState() - if err != nil { - return err - } - if rebasing { - gui.State.WorkingTreeState = "rebasing" - return nil - } - gui.State.WorkingTreeState = "normal" - return nil -} - func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) { item, err := gui.getSelectedFile(g) if err != nil { @@ -403,7 +385,7 @@ func (gui *Gui) pullFiles(g *gocui.Gui, v *gocui.View) error { gui.refreshCommits(g) gui.refreshStatus(g) } - gui.refreshFiles(g) + gui.refreshFiles() }() return nil } @@ -437,10 +419,6 @@ func (gui *Gui) pushFiles(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error { - mergeView, err := g.View("main") - if err != nil { - return err - } file, err := gui.getSelectedFile(g) if err != nil { if err != gui.Errors.ErrNoFiles { @@ -451,8 +429,7 @@ func (gui *Gui) handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error { if !file.HasMergeConflicts { return gui.createErrorPanel(g, gui.Tr.SLocalize("FileNoMergeCons")) } - gui.switchFocus(g, v, mergeView) - return gui.refreshMergePanel(g) + return gui.switchFocus(g, v, gui.getMergingView()) } func (gui *Gui) handleAbortMerge(g *gocui.Gui, v *gocui.View) error { @@ -461,7 +438,7 @@ func (gui *Gui) handleAbortMerge(g *gocui.Gui, v *gocui.View) error { } gui.createMessagePanel(g, v, "", gui.Tr.SLocalize("MergeAborted")) gui.refreshStatus(g) - return gui.refreshFiles(g) + return gui.refreshFiles() } func (gui *Gui) handleResetAndClean(g *gocui.Gui, v *gocui.View) error { @@ -469,7 +446,7 @@ func (gui *Gui) handleResetAndClean(g *gocui.Gui, v *gocui.View) error { if err := gui.GitCommand.ResetAndClean(); err != nil { gui.createErrorPanel(g, err.Error()) } - return gui.refreshFiles(g) + return gui.refreshFiles() }, nil) } @@ -479,3 +456,12 @@ func (gui *Gui) openFile(filename string) error { } return nil } + +func (gui *Gui) anyFilesWithMergeConflicts() bool { + for _, file := range gui.State.Files { + if file.HasMergeConflicts { + return true + } + } + return false +} diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index ef41482c9..2b6eccb5d 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -82,6 +82,13 @@ type stagingPanelState struct { Diff string } +type mergingPanelState struct { + ConflictIndex int + ConflictTop bool + Conflicts []commands.Conflict + EditHistory *stack.Stack +} + type filePanelState struct { SelectedLine int } @@ -104,11 +111,12 @@ type menuPanelState struct { type panelStates struct { Files *filePanelState - Staging *stagingPanelState Branches *branchPanelState Commits *commitPanelState Stash *stashPanelState Menu *menuPanelState + Staging *stagingPanelState + Merging *mergingPanelState } type guiState struct { @@ -118,10 +126,6 @@ type guiState struct { StashEntries []*commands.StashEntry PreviousView string HasMergeConflicts bool - ConflictIndex int - ConflictTop bool - Conflicts []commands.Conflict - EditHistory *stack.Stack Platform commands.Platform Updating bool Panels *panelStates @@ -132,21 +136,23 @@ type guiState struct { func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater) (*Gui, error) { initialState := guiState{ - Files: make([]*commands.File, 0), - PreviousView: "files", - Commits: make([]*commands.Commit, 0), - StashEntries: make([]*commands.StashEntry, 0), - ConflictIndex: 0, - ConflictTop: true, - Conflicts: make([]commands.Conflict, 0), - EditHistory: stack.New(), - Platform: *oSCommand.Platform, + Files: make([]*commands.File, 0), + PreviousView: "files", + Commits: make([]*commands.Commit, 0), + StashEntries: make([]*commands.StashEntry, 0), + 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}, + Merging: &mergingPanelState{ + ConflictIndex: 0, + ConflictTop: true, + Conflicts: []commands.Conflict{}, + EditHistory: stack.New(), + }, }, } @@ -271,6 +277,18 @@ func (gui *Gui) layout(g *gocui.Gui) error { } } + v, err = g.SetView("merging", leftSideWidth+panelSpacing, 0, width-1, optionsTop, gocui.LEFT) + if err != nil { + if err != gocui.ErrUnknownView { + return err + } + v.Title = gui.Tr.SLocalize("MergingTitle") + v.FgColor = gocui.ColorWhite + if _, err := g.SetViewOnBottom("merging"); err != nil { + return err + } + } + if v, err := g.SetView("status", 0, 0, leftSideWidth, statusFilesBoundary, gocui.BOTTOM|gocui.RIGHT); err != nil { if err != gocui.ErrUnknownView { return err @@ -324,7 +342,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { } } - if gui.getCommitMessageView(g) == nil { + if gui.getCommitMessageView() == nil { // doesn't matter where this view starts because it will be hidden if commitMessageView, err := g.SetView("commitMessage", 0, 0, width/2, height/2, 0); err != nil { if err != gocui.ErrUnknownView { @@ -401,7 +419,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { // 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])) + // gui.Log.Info(utils.AsJson(gui.State.Files)) return gui.resizeCurrentPopupPanel(g) } @@ -414,18 +432,18 @@ func (gui *Gui) promptAnonymousReporting() error { }) } -func (gui *Gui) fetch(g *gocui.Gui) error { +func (gui *Gui) fetch() error { gui.GitCommand.Fetch() - gui.refreshStatus(g) + gui.refreshStatus(gui.g) return nil } -func (gui *Gui) updateLoader(g *gocui.Gui) error { - if view, _ := g.View("confirmation"); view != nil { +func (gui *Gui) updateLoader() error { + if view, _ := gui.g.View("confirmation"); view != nil { content := gui.trimmedContent(view) if strings.Contains(content, "...") { staticContent := strings.Split(content, "...")[0] + "..." - if err := gui.renderString(g, "confirmation", staticContent+" "+utils.Loader()); err != nil { + if err := gui.renderString(gui.g, "confirmation", staticContent+" "+utils.Loader()); err != nil { return err } } @@ -433,7 +451,7 @@ func (gui *Gui) updateLoader(g *gocui.Gui) error { return nil } -func (gui *Gui) renderAppStatus(g *gocui.Gui) error { +func (gui *Gui) renderAppStatus() error { appStatus := gui.statusManager.getStatusString() if appStatus != "" { return gui.renderString(gui.g, "appStatus", appStatus) @@ -450,10 +468,10 @@ func (gui *Gui) renderGlobalOptions() error { }) } -func (gui *Gui) goEvery(g *gocui.Gui, interval time.Duration, function func(*gocui.Gui) error) { +func (gui *Gui) goEvery(interval time.Duration, function func() error) { go func() { for range time.Tick(interval) { - function(g) + function() } }() } @@ -472,10 +490,10 @@ func (gui *Gui) Run() error { return err } - gui.goEvery(g, time.Second*60, gui.fetch) - gui.goEvery(g, time.Second*2, gui.refreshFiles) - gui.goEvery(g, time.Millisecond*50, gui.updateLoader) - gui.goEvery(g, time.Millisecond*50, gui.renderAppStatus) + gui.goEvery(time.Second*60, gui.fetch) + gui.goEvery(time.Second*2, gui.refreshFiles) + gui.goEvery(time.Millisecond*50, gui.updateLoader) + gui.goEvery(time.Millisecond*50, gui.renderAppStatus) g.SetManagerFunc(gui.layout) diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 745e2fa7f..410d4545c 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -153,11 +153,11 @@ func (gui *Gui) GetKeybindings() []*Binding { Handler: gui.handleFileRemove, Description: gui.Tr.SLocalize("removeFile"), }, { - ViewName: "files", + ViewName: "files", // TODO: might make this for more views as well Key: 'm', Modifier: gocui.ModNone, - Handler: gui.handleSwitchToMerge, - Description: gui.Tr.SLocalize("resolveMergeConflicts"), + Handler: gui.handleCreateRebaseOptionsMenu, + Description: gui.Tr.SLocalize("ViewMergeRebaseOptions"), }, { ViewName: "files", Key: 'e', @@ -188,12 +188,6 @@ func (gui *Gui) GetKeybindings() []*Binding { Modifier: gocui.ModNone, Handler: gui.handleStashSave, Description: gui.Tr.SLocalize("stashFiles"), - }, { - ViewName: "files", - Key: 'M', - Modifier: gocui.ModNone, - Handler: gui.handleAbortMerge, - Description: gui.Tr.SLocalize("abortMerge"), }, { ViewName: "files", Key: 'a', @@ -220,65 +214,69 @@ func (gui *Gui) GetKeybindings() []*Binding { Description: gui.Tr.SLocalize("StageLines"), KeyReadable: "enter", }, { - ViewName: "main", + ViewName: "merging", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: gui.handleEscapeMerge, }, { - ViewName: "main", - Key: gocui.KeySpace, - Modifier: gocui.ModNone, - Handler: gui.handlePickHunk, + ViewName: "merging", + Key: gocui.KeySpace, + Modifier: gocui.ModNone, + Handler: gui.handlePickHunk, + Description: gui.Tr.SLocalize("PickHunk"), + KeyReadable: "space", }, { - ViewName: "main", - Key: 'b', - Modifier: gocui.ModNone, - Handler: gui.handlePickBothHunks, + ViewName: "merging", + Key: 'b', + Modifier: gocui.ModNone, + Handler: gui.handlePickBothHunks, + Description: gui.Tr.SLocalize("PickBothHunks"), }, { - ViewName: "main", + ViewName: "merging", Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: gui.handleSelectPrevConflict, }, { - ViewName: "main", + ViewName: "merging", Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: gui.handleSelectNextConflict, }, { - ViewName: "main", + ViewName: "merging", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.handleSelectTop, }, { - ViewName: "main", + ViewName: "merging", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.handleSelectBottom, }, { - ViewName: "main", + ViewName: "merging", Key: 'h', Modifier: gocui.ModNone, Handler: gui.handleSelectPrevConflict, }, { - ViewName: "main", + ViewName: "merging", Key: 'l', Modifier: gocui.ModNone, Handler: gui.handleSelectNextConflict, }, { - ViewName: "main", + ViewName: "merging", Key: 'k', Modifier: gocui.ModNone, Handler: gui.handleSelectTop, }, { - ViewName: "main", + ViewName: "merging", Key: 'j', Modifier: gocui.ModNone, Handler: gui.handleSelectBottom, }, { - ViewName: "main", - Key: 'z', - Modifier: gocui.ModNone, - Handler: gui.handlePopFileSnapshot, + ViewName: "merging", + Key: 'z', + Modifier: gocui.ModNone, + Handler: gui.handlePopFileSnapshot, + Description: gui.Tr.SLocalize("Undo"), }, { ViewName: "branches", Key: gocui.KeySpace, diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index 8ce60f6be..6dca1ad35 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -2,7 +2,6 @@ package gui import ( "fmt" - "strings" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/utils" @@ -50,7 +49,7 @@ func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error { return gui.returnFocus(g, v) } -func (gui *Gui) createMenu(items interface{}, handlePress func(int) error) error { +func (gui *Gui) createMenu(title string, items interface{}, handlePress func(int) error) error { list, err := utils.RenderList(items) if err != nil { return err @@ -58,7 +57,7 @@ func (gui *Gui) createMenu(items interface{}, handlePress func(int) error) error x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(gui.g, list) menuView, _ := gui.g.SetView("menu", x0, y0, x1, y1, 0) - menuView.Title = strings.Title(gui.Tr.SLocalize("menu")) + menuView.Title = title menuView.FgColor = gocui.ColorWhite menuView.Clear() fmt.Fprint(menuView, list) @@ -66,7 +65,13 @@ func (gui *Gui) createMenu(items interface{}, handlePress func(int) error) error wrappedHandlePress := func(g *gocui.Gui, v *gocui.View) error { selectedLine := gui.State.Panels.Menu.SelectedLine - return handlePress(selectedLine) + if err := handlePress(selectedLine); err != nil { + return err + } + if _, err := gui.g.SetViewOnBottom("menu"); err != nil { + return err + } + return gui.returnFocus(gui.g, menuView) } if err := gui.g.SetKeybinding("menu", gocui.KeySpace, gocui.ModNone, wrappedHandlePress); err != nil { diff --git a/pkg/gui/merge_panel.go b/pkg/gui/merge_panel.go index 404266d29..07ba55894 100644 --- a/pkg/gui/merge_panel.go +++ b/pkg/gui/merge_panel.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/fatih/color" + "github.com/golang-collections/collections/stack" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/utils" @@ -21,6 +22,7 @@ func (gui *Gui) findConflicts(content string) ([]commands.Conflict, error) { var newConflict commands.Conflict for i, line := range utils.SplitLines(content) { trimmedLine := strings.TrimPrefix(line, "++") + gui.Log.Info(trimmedLine) if trimmedLine == "<<<<<<< HEAD" || trimmedLine == "<<<<<<< MERGE_HEAD" || trimmedLine == "<<<<<<< Updated upstream" { newConflict = commands.Conflict{Start: i} } else if trimmedLine == "=======" { @@ -65,28 +67,28 @@ func (gui *Gui) coloredConflictFile(content string, conflicts []commands.Conflic } func (gui *Gui) handleSelectTop(g *gocui.Gui, v *gocui.View) error { - gui.State.ConflictTop = true + gui.State.Panels.Merging.ConflictTop = true return gui.refreshMergePanel(g) } func (gui *Gui) handleSelectBottom(g *gocui.Gui, v *gocui.View) error { - gui.State.ConflictTop = false + gui.State.Panels.Merging.ConflictTop = false return gui.refreshMergePanel(g) } func (gui *Gui) handleSelectNextConflict(g *gocui.Gui, v *gocui.View) error { - if gui.State.ConflictIndex >= len(gui.State.Conflicts)-1 { + if gui.State.Panels.Merging.ConflictIndex >= len(gui.State.Panels.Merging.Conflicts)-1 { return nil } - gui.State.ConflictIndex++ + gui.State.Panels.Merging.ConflictIndex++ return gui.refreshMergePanel(g) } func (gui *Gui) handleSelectPrevConflict(g *gocui.Gui, v *gocui.View) error { - if gui.State.ConflictIndex <= 0 { + if gui.State.Panels.Merging.ConflictIndex <= 0 { return nil } - gui.State.ConflictIndex-- + gui.State.Panels.Merging.ConflictIndex-- return gui.refreshMergePanel(g) } @@ -134,15 +136,15 @@ func (gui *Gui) pushFileSnapshot(g *gocui.Gui) error { if err != nil { return err } - gui.State.EditHistory.Push(content) + gui.State.Panels.Merging.EditHistory.Push(content) return nil } func (gui *Gui) handlePopFileSnapshot(g *gocui.Gui, v *gocui.View) error { - if gui.State.EditHistory.Len() == 0 { + if gui.State.Panels.Merging.EditHistory.Len() == 0 { return nil } - prevContent := gui.State.EditHistory.Pop().(string) + prevContent := gui.State.Panels.Merging.EditHistory.Pop().(string) gitFile, err := gui.getSelectedFile(g) if err != nil { return err @@ -152,22 +154,29 @@ func (gui *Gui) handlePopFileSnapshot(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) handlePickHunk(g *gocui.Gui, v *gocui.View) error { - conflict := gui.State.Conflicts[gui.State.ConflictIndex] + conflict := gui.State.Panels.Merging.Conflicts[gui.State.Panels.Merging.ConflictIndex] gui.pushFileSnapshot(g) pick := "bottom" - if gui.State.ConflictTop { + if gui.State.Panels.Merging.ConflictTop { pick = "top" } err := gui.resolveConflict(g, conflict, pick) if err != nil { panic(err) } + + // if that was the last conflict, finish the merge for this file + if len(gui.State.Panels.Merging.Conflicts) == 1 { + if err := gui.handleCompleteMerge(); err != nil { + return err + } + } gui.refreshMergePanel(g) return nil } func (gui *Gui) handlePickBothHunks(g *gocui.Gui, v *gocui.View) error { - conflict := gui.State.Conflicts[gui.State.ConflictIndex] + conflict := gui.State.Panels.Merging.Conflicts[gui.State.Panels.Merging.ConflictIndex] gui.pushFileSnapshot(g) err := gui.resolveConflict(g, conflict, "both") if err != nil { @@ -177,6 +186,7 @@ func (gui *Gui) handlePickBothHunks(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) refreshMergePanel(g *gocui.Gui) error { + panelState := gui.State.Panels.Merging cat, err := gui.catSelectedFile(g) if err != nil { return err @@ -184,47 +194,56 @@ func (gui *Gui) refreshMergePanel(g *gocui.Gui) error { if cat == "" { return nil } - gui.State.Conflicts, err = gui.findConflicts(cat) + gui.Log.Info(cat) + panelState.Conflicts, err = gui.findConflicts(cat) if err != nil { return err } - if len(gui.State.Conflicts) == 0 { - return gui.handleCompleteMerge(g) - } else if gui.State.ConflictIndex > len(gui.State.Conflicts)-1 { - gui.State.ConflictIndex = len(gui.State.Conflicts) - 1 + // handle potential fixes that the user made in their editor since we last refreshed + if len(panelState.Conflicts) == 0 { + return gui.handleCompleteMerge() + } else if panelState.ConflictIndex > len(panelState.Conflicts)-1 { + panelState.ConflictIndex = len(panelState.Conflicts) - 1 } - hasFocus := gui.currentViewName(g) == "main" - content, err := gui.coloredConflictFile(cat, gui.State.Conflicts, gui.State.ConflictIndex, gui.State.ConflictTop, hasFocus) + + gui.g.SetViewOnTop("merging") + hasFocus := gui.currentViewName(g) == "merging" + content, err := gui.coloredConflictFile(cat, panelState.Conflicts, panelState.ConflictIndex, panelState.ConflictTop, hasFocus) if err != nil { return err } + if err := gui.renderString(g, "merging", content); err != nil { + return err + } if err := gui.scrollToConflict(g); err != nil { return err } - return gui.renderString(g, "main", content) + return nil } func (gui *Gui) scrollToConflict(g *gocui.Gui) error { - mainView, err := g.View("main") - if err != nil { - return err - } - if len(gui.State.Conflicts) == 0 { + panelState := gui.State.Panels.Merging + if len(panelState.Conflicts) == 0 { return nil } - conflict := gui.State.Conflicts[gui.State.ConflictIndex] - ox, _ := mainView.Origin() - _, height := mainView.Size() + mergingView := gui.getMergingView() + conflict := panelState.Conflicts[panelState.ConflictIndex] + gui.Log.Info(utils.AsJson(conflict)) + ox, _ := mergingView.Origin() + _, height := mergingView.Size() conflictMiddle := (conflict.End + conflict.Start) / 2 newOriginY := int(math.Max(0, float64(conflictMiddle-(height/2)))) - return mainView.SetOrigin(ox, newOriginY) + gui.Log.Info(utils.AsJson("origin Y")) + gui.Log.Info(utils.AsJson(newOriginY)) + gui.g.Update(func(g *gocui.Gui) error { + return mergingView.SetOrigin(ox, newOriginY) + }) + return nil } func (gui *Gui) switchToMerging(g *gocui.Gui) error { - gui.State.ConflictIndex = 0 - gui.State.ConflictTop = true - _, err := g.SetCurrentView("main") + _, err := g.SetCurrentView("merging") if err != nil { return err } @@ -242,34 +261,44 @@ func (gui *Gui) renderMergeOptions() error { } func (gui *Gui) handleEscapeMerge(g *gocui.Gui, v *gocui.View) error { - filesView, err := g.View("files") - if err != nil { - return err + gui.State.Panels.Merging.EditHistory = stack.New() + gui.g.SetViewOnBottom("merging") + gui.refreshFiles() + // it's possible this method won't be called from the merging view so we need to + // ensure we only 'return' focus if we already have it + if gui.g.CurrentView() == gui.getMergingView() { + return gui.switchFocus(g, v, gui.getFilesView()) } - gui.refreshFiles(g) - return gui.switchFocus(g, v, filesView) + return nil } -func (gui *Gui) handleCompleteMerge(g *gocui.Gui) error { - filesView, err := g.View("files") - if err != nil { - return err - } - gui.stageSelectedFile(g) - gui.refreshFiles(g) - if rebase, err := gui.GitCommand.IsInRebaseState(); rebase && err == nil { - if err := gui.GitCommand.ContinueRebaseBranch(); err != nil { - if strings.Contains(err.Error(), "No changes - did you forget to use") { - if err := gui.GitCommand.SkipRebaseBranch(); err != nil { - gui.Log.Errorln(err) +func (gui *Gui) handleCompleteMerge() error { + filesView := gui.getFilesView() + gui.stageSelectedFile(gui.g) + gui.refreshFiles() + // if there are no more files with merge conflicts, we should ask whether the user wants to continue + if !gui.anyFilesWithMergeConflicts() { + // ask if user wants to continue + if err := gui.createConfirmationPanel(gui.g, filesView, "continue", "all merge conflicts, resolved. Continue?", func(g *gocui.Gui, v *gocui.View) error { + if err := gui.genericRebaseCommand("continue"); err != nil { + if err == gui.Errors.ErrSubProcess { + return err + } + if strings.Contains(err.Error(), "No changes - did you forget to use") { + if err := gui.genericRebaseCommand("skip"); err != nil { + if err == gui.Errors.ErrSubProcess { + return err + } + gui.createErrorPanel(gui.g, err.Error()) + } + } else { + gui.createErrorPanel(gui.g, err.Error()) } - } else { - gui.Log.Errorln(err) } - } - if err := gui.refreshSidePanels(g); err != nil { + return gui.refreshSidePanels(gui.g) + }, nil); err != nil { return err } } - return gui.switchFocus(g, nil, filesView) + return gui.handleEscapeMerge(gui.g, gui.getMergingView()) } diff --git a/pkg/gui/options_menu_panel.go b/pkg/gui/options_menu_panel.go index ac01ad03d..f7019ed8e 100644 --- a/pkg/gui/options_menu_panel.go +++ b/pkg/gui/options_menu_panel.go @@ -2,6 +2,7 @@ package gui import ( "errors" + "strings" "github.com/jesseduffield/gocui" ) @@ -47,5 +48,5 @@ func (gui *Gui) handleCreateOptionsMenu(g *gocui.Gui, v *gocui.View) error { return bindings[index].Handler(g, v) } - return gui.createMenu(bindings, handleMenuPress) + return gui.createMenu(strings.Title(gui.Tr.SLocalize("menu")), bindings, handleMenuPress) } diff --git a/pkg/gui/rebase_options_panel.go b/pkg/gui/rebase_options_panel.go new file mode 100644 index 000000000..1752af496 --- /dev/null +++ b/pkg/gui/rebase_options_panel.go @@ -0,0 +1,61 @@ +package gui + +import ( + "fmt" + "strings" + + "github.com/jesseduffield/gocui" +) + +type option struct { + value string +} + +// GetDisplayStrings is a function. +func (r *option) GetDisplayStrings() []string { + return []string{r.value} +} + +func (gui *Gui) handleCreateRebaseOptionsMenu(g *gocui.Gui, v *gocui.View) error { + options := []*option{ + {value: "continue"}, + {value: "abort"}, + } + + if gui.State.WorkingTreeState == "rebasing" { + options = append(options, &option{value: "skip"}) + } + + handleMenuPress := func(index int) error { + command := options[index].value + return gui.genericRebaseCommand(command) + } + + var title string + if gui.State.WorkingTreeState == "merging" { + title = gui.Tr.SLocalize("MergeOptionsTitle") + } else { + title = gui.Tr.SLocalize("RebaseOptionsTitle") + } + + return gui.createMenu(title, options, handleMenuPress) +} + +func (gui *Gui) genericRebaseCommand(command string) error { + status := gui.State.WorkingTreeState + + if status != "merging" && status != "rebasing" { + return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NotMergingOrRebasing")) + } + + commandType := strings.Replace(status, "ing", "e", 1) + // we should end up with a command like 'git merge --continue' + + sub := gui.OSCommand.PrepareSubProcess("git", commandType, fmt.Sprintf("--%s", command)) + if sub != nil { + gui.SubProcess = sub + return gui.Errors.ErrSubProcess + } + + return nil +} diff --git a/pkg/gui/recent_repos_panel.go b/pkg/gui/recent_repos_panel.go index 98da6a9c2..13c26ccd0 100644 --- a/pkg/gui/recent_repos_panel.go +++ b/pkg/gui/recent_repos_panel.go @@ -44,7 +44,7 @@ func (gui *Gui) handleCreateRecentReposMenu(g *gocui.Gui, v *gocui.View) error { return gui.Errors.ErrSwitchRepo } - return gui.createMenu(recentRepos, handleMenuPress) + return gui.createMenu(gui.Tr.SLocalize("RecentRepos"), recentRepos, handleMenuPress) } // updateRecentRepoList registers the fact that we opened lazygit in this repo, diff --git a/pkg/gui/staging_panel.go b/pkg/gui/staging_panel.go index 1408cfb45..777da08c2 100644 --- a/pkg/gui/staging_panel.go +++ b/pkg/gui/staging_panel.go @@ -76,7 +76,7 @@ func (gui *Gui) handleStagingEscape(g *gocui.Gui, v *gocui.View) error { gui.State.Panels.Staging = nil - return gui.switchFocus(gui.g, nil, gui.getFilesView(gui.g)) + return gui.switchFocus(gui.g, nil, gui.getFilesView()) } func (gui *Gui) handleStagingPrevLine(g *gocui.Gui, v *gocui.View) error { @@ -138,7 +138,7 @@ func (gui *Gui) handleCycleLine(prev bool) error { // focusLineAndHunk works out the best focus for the staging panel given the // selected line and size of the hunk func (gui *Gui) focusLineAndHunk() error { - stagingView := gui.getStagingView(gui.g) + stagingView := gui.getStagingView() state := gui.State.Panels.Staging lineNumber := state.StageableLines[state.SelectedLine] @@ -209,7 +209,7 @@ func (gui *Gui) handleStageLineOrHunk(hunk bool) error { return err } - if err := gui.refreshFiles(gui.g); err != nil { + if err := gui.refreshFiles(); err != nil { return err } if err := gui.refreshStagingPanel(); err != nil { diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index 306d61771..4ee19793c 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -46,11 +46,14 @@ func (gui *Gui) refreshStashEntries(g *gocui.Gui) error { return err } - v := gui.getStashView(gui.g) + v := gui.getStashView() v.Clear() fmt.Fprint(v, list) - return gui.resetOrigin(v) + if err := gui.resetOrigin(v); err != nil { + return err + } + return nil }) return nil } @@ -59,6 +62,9 @@ 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) + if err := gui.resetOrigin(gui.getMainView()); err != nil { + return err + } return gui.handleStashEntrySelect(gui.g, v) } @@ -66,6 +72,9 @@ 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) + if err := gui.resetOrigin(gui.getMainView()); err != nil { + return err + } return gui.handleStashEntrySelect(gui.g, v) } @@ -102,7 +111,7 @@ func (gui *Gui) stashDo(g *gocui.Gui, v *gocui.View, method string) error { gui.createErrorPanel(g, err.Error()) } gui.refreshStashEntries(g) - return gui.refreshFiles(g) + return gui.refreshFiles() } func (gui *Gui) handleStashSave(g *gocui.Gui, filesView *gocui.View) error { @@ -114,7 +123,7 @@ func (gui *Gui) handleStashSave(g *gocui.Gui, filesView *gocui.View) error { gui.createErrorPanel(g, err.Error()) } gui.refreshStashEntries(g) - return gui.refreshFiles(g) + return gui.refreshFiles() }) return nil } diff --git a/pkg/gui/status_panel.go b/pkg/gui/status_panel.go index 16017abba..bb0ac773b 100644 --- a/pkg/gui/status_panel.go +++ b/pkg/gui/status_panel.go @@ -48,7 +48,7 @@ func (gui *Gui) handleCheckForUpdate(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error { - blue := color.New(color.FgBlue) + magenta := color.New(color.FgMagenta) dashboardString := strings.Join( []string{ @@ -58,7 +58,7 @@ 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", - blue.Sprint("Buy Jesse a coffee: https://donorbox.org/lazygit"), // caffeine ain't free + magenta.Sprint("Buy Jesse a coffee: https://donorbox.org/lazygit"), // caffeine ain't free }, "\n\n") return gui.renderString(g, "main", dashboardString) @@ -84,3 +84,24 @@ func lazygitTitle() string { __/ | __/ | |___/ |___/ ` } + +func (gui *Gui) updateWorkTreeState() error { + merging, err := gui.GitCommand.IsInMergeState() + if err != nil { + return err + } + if merging { + gui.State.WorkingTreeState = "merging" + return nil + } + rebasing, err := gui.GitCommand.IsInRebaseState() + if err != nil { + return err + } + if rebasing { + gui.State.WorkingTreeState = "rebasing" + return nil + } + gui.State.WorkingTreeState = "normal" + return nil +} diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 1508daa1b..df32e8fb5 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -16,7 +16,7 @@ func (gui *Gui) refreshSidePanels(g *gocui.Gui) error { if err := gui.refreshBranches(g); err != nil { return err } - if err := gui.refreshFiles(g); err != nil { + if err := gui.refreshFiles(); err != nil { return err } if err := gui.refreshCommits(g); err != nil { @@ -101,7 +101,7 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error { return nil case "commitMessage": return gui.handleCommitFocused(g, v) - case "main": + case "merging": // TODO: pull this out into a 'view focused' function gui.refreshMergePanel(g) v.Highlight = false @@ -126,6 +126,20 @@ func (gui *Gui) returnFocus(g *gocui.Gui, v *gocui.View) error { return gui.switchFocus(g, v, previousView) } +// in lieu of a proper window system, we've got three panels that overlap, +// the main panel, the staging panel, and the merging panel. We will call this +// function whenever we might need to hide one of these panels +// this function introduces some unwanted technical debt but is necessary for this rebasing feature +func (gui *Gui) showCorrectMainPanel() error { + // if the files view is not focused or the current file is not in a merging state we hide the merging panel + if gui.g.CurrentView().Name() != "merging" && gui.g.CurrentView().Name() != "confirmation" { + if _, err := gui.g.SetViewOnBottom("merging"); err != nil { + return err + } + } + return nil +} + // pass in oldView = nil if you don't want to be able to return to your old view func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error { // we assume we'll never want to return focus to a confirmation panel i.e. @@ -169,6 +183,10 @@ func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error { return err } + if err := gui.showCorrectMainPanel(); err != nil { + return err + } + return gui.newLineFocused(g, newView) } @@ -229,9 +247,6 @@ 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) @@ -255,38 +270,43 @@ func (gui *Gui) renderOptionsMap(optionsMap map[string]string) error { // TODO: refactor properly // i'm so sorry but had to add this getBranchesView -func (gui *Gui) getFilesView(g *gocui.Gui) *gocui.View { - v, _ := g.View("files") +func (gui *Gui) getFilesView() *gocui.View { + v, _ := gui.g.View("files") return v } -func (gui *Gui) getCommitsView(g *gocui.Gui) *gocui.View { - v, _ := g.View("commits") +func (gui *Gui) getCommitsView() *gocui.View { + v, _ := gui.g.View("commits") return v } -func (gui *Gui) getCommitMessageView(g *gocui.Gui) *gocui.View { - v, _ := g.View("commitMessage") +func (gui *Gui) getCommitMessageView() *gocui.View { + v, _ := gui.g.View("commitMessage") return v } -func (gui *Gui) getBranchesView(g *gocui.Gui) *gocui.View { - v, _ := g.View("branches") +func (gui *Gui) getBranchesView() *gocui.View { + v, _ := gui.g.View("branches") return v } -func (gui *Gui) getStagingView(g *gocui.Gui) *gocui.View { - v, _ := g.View("staging") +func (gui *Gui) getStagingView() *gocui.View { + v, _ := gui.g.View("staging") return v } -func (gui *Gui) getMainView(g *gocui.Gui) *gocui.View { - v, _ := g.View("main") +func (gui *Gui) getMainView() *gocui.View { + v, _ := gui.g.View("main") return v } -func (gui *Gui) getStashView(g *gocui.Gui) *gocui.View { - v, _ := g.View("stash") +func (gui *Gui) getStashView() *gocui.View { + v, _ := gui.g.View("stash") + return v +} + +func (gui *Gui) getMergingView() *gocui.View { + v, _ := gui.g.View("merging") return v } diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 8847cde7e..86a7b6fee 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -447,6 +447,36 @@ func addEnglish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "Fetching", Other: "fetching and fast-forwarding {{.from}} -> {{.to}} ...", + }, &i18n.Message{ + ID: "MergingTitle", + Other: "Resolve merge conflicts", + }, &i18n.Message{ + ID: "FoundConflicts", + Other: "Damn, conflicts! To abort press 'esc', otherwise press 'enter'", + }, &i18n.Message{ + ID: "Undo", + Other: "undo", + }, &i18n.Message{ + ID: "PickHunk", + Other: "pick hunk", + }, &i18n.Message{ + ID: "PickBothHunks", + Other: "pick both hunks", + }, &i18n.Message{ + ID: "ViewMergeRebaseOptions", + Other: "view merge/rebase options", + }, &i18n.Message{ + ID: "NotMergingOrRebasing", + Other: "You are currently neither rebasing nor merging", + }, &i18n.Message{ + ID: "RecentRepos", + Other: "recent repositories", + }, &i18n.Message{ + ID: "MergeOptionsTitle", + Other: "Merge Options", + }, &i18n.Message{ + ID: "RebaseOptionsTitle", + Other: "Rebase Options", }, ) }