diff --git a/pkg/app/app.go b/pkg/app/app.go index b6318b745..011375737 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -2,9 +2,9 @@ package app import ( "io" + "os" "github.com/Sirupsen/logrus" - "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/gui" @@ -18,7 +18,21 @@ type App struct { Log *logrus.Logger OSCommand *commands.OSCommand GitCommand *commands.GitCommand - Gui *gocui.Gui + Gui *gui.Gui +} + +func newLogger(config config.AppConfigurer) *logrus.Logger { + log := logrus.New() + if !config.GetDebug() { + log.Out = nil + return log + } + file, err := os.OpenFile("development.log", os.O_CREATE|os.O_WRONLY, 0666) + if err != nil { + panic("unable to log to file") // TODO: don't panic (also, remove this call to the `panic` function) + } + log.Out = file + return log } // NewApp retruns a new applications @@ -28,7 +42,7 @@ func NewApp(config config.AppConfigurer) (*App, error) { Config: config, } var err error - app.Log = logrus.New() + app.Log = newLogger(config) app.OSCommand, err = commands.NewOSCommand(app.Log) if err != nil { return nil, err @@ -37,7 +51,7 @@ func NewApp(config config.AppConfigurer) (*App, error) { if err != nil { return nil, err } - app.Gui, err = gui.NewGui(app.Log, app.GitCommand, config.GetVersion()) + app.Gui, err = gui.NewGui(app.Log, app.GitCommand, app.OSCommand, config.GetVersion()) if err != nil { return nil, err } diff --git a/pkg/commands/git_structs.go b/pkg/commands/git_structs.go index 2f7255be1..6b10b18bb 100644 --- a/pkg/commands/git_structs.go +++ b/pkg/commands/git_structs.go @@ -30,7 +30,7 @@ type StashEntry struct { // Conflict : A git conflict with a start middle and end corresponding to line // numbers in the file where the conflict bars appear type Conflict struct { - start int - middle int - end int + Start int + Middle int + End int } diff --git a/pkg/commands/os.go b/pkg/commands/os.go index 2313b5550..3be1e1288 100644 --- a/pkg/commands/os.go +++ b/pkg/commands/os.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/Sirupsen/logrus" - "github.com/jesseduffield/gocui" gitconfig "github.com/tcnksm/go-gitconfig" ) @@ -105,28 +104,34 @@ func (c *OSCommand) GetOpenCommand() (string, string, error) { // VsCodeOpenFile opens the file in code, with the -r flag to open in the // current window -func (c *OSCommand) VsCodeOpenFile(g *gocui.Gui, filename string) (string, error) { - return c.RunCommand("code -r " + filename) +// each of these open files needs to have the same function signature because +// they're being passed as arguments into another function, +// but only editFile actually returns a *exec.Cmd +func (c *OSCommand) VsCodeOpenFile(filename string) (*exec.Cmd, error) { + _, err := c.RunCommand("code -r " + filename) + return nil, err } // SublimeOpenFile opens the filein sublime // may be deprecated in the future -func (c *OSCommand) SublimeOpenFile(g *gocui.Gui, filename string) (string, error) { - return c.RunCommand("subl " + filename) +func (c *OSCommand) SublimeOpenFile(filename string) (*exec.Cmd, error) { + _, err := c.RunCommand("subl " + filename) + return nil, err } // OpenFile opens a file with the given -func (c *OSCommand) OpenFile(g *gocui.Gui, filename string) (string, error) { +func (c *OSCommand) OpenFile(filename string) (*exec.Cmd, error) { cmdName, cmdTrail, err := c.GetOpenCommand() if err != nil { - return "", err + return nil, err } - return c.RunCommand(cmdName + " " + filename + cmdTrail) + _, err = c.RunCommand(cmdName + " " + filename + cmdTrail) + return nil, err } // EditFile opens a file in a subprocess using whatever editor is available, // falling back to core.editor, VISUAL, EDITOR, then vi -func (c *OSCommand) editFile(g *gocui.Gui, filename string) (string, error) { +func (c *OSCommand) EditFile(filename string) (*exec.Cmd, error) { editor, _ := gitconfig.Global("core.editor") if editor == "" { editor = os.Getenv("VISUAL") @@ -140,10 +145,9 @@ func (c *OSCommand) editFile(g *gocui.Gui, filename string) (string, error) { } } if editor == "" { - return "", ErrNoEditorDefined + return nil, ErrNoEditorDefined } - c.PrepareSubProcess(editor, filename) - return "", nil + return c.PrepareSubProcess(editor, filename) } // PrepareSubProcess iniPrepareSubProcessrocess then tells the Gui to switch to it diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index 53b8465cb..2820b7b9e 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -117,7 +117,7 @@ func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error { return nil } -// refreshStatus is called at the end of this because that's when we can +// 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 { @@ -135,7 +135,7 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error { fmt.Fprintln(v, branch.GetDisplayString()) } gui.resetOrigin(v) - return refreshStatus(g) + return gui.refreshStatus(g) }) return nil } diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index 45134e44a..faff6ad32 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -34,7 +34,7 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error { shaColor.Fprint(v, commit.Sha+" ") white.Fprintln(v, commit.Name) } - refreshStatus(g) + gui.refreshStatus(g) if g.CurrentView().Name() == "commits" { gui.handleCommitSelect(g, v) } @@ -105,7 +105,7 @@ func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error { if err := gui.refreshCommits(g); err != nil { panic(err) } - refreshStatus(g) + gui.refreshStatus(g) return gui.handleCommitSelect(g, v) } @@ -138,7 +138,7 @@ func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error { if err := gui.refreshCommits(g); err != nil { panic(err) } - return refreshStatus(g) + return gui.refreshStatus(g) }, nil) return nil } diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 9ffcafa44..8c60fcbf4 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -8,6 +8,7 @@ import ( // "strings" "errors" + "os/exec" "strings" "github.com/fatih/color" @@ -171,7 +172,7 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error { gui.renderfilesOptions(g, &file) var content string if file.HasMergeConflicts { - return refreshMergePanel(g) + return gui.refreshMergePanel(g) } content = gui.GitCommand.Diff(file) @@ -191,9 +192,9 @@ func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { return nil } -// HandleCommitEditorPress - handle when the user wants to commit changes via +// handleCommitEditorPress - handle when the user wants to commit changes via // their editor rather than via the popup panel -func (gui *Gui) HandleCommitEditorPress(g *gocui.Gui, filesView *gocui.View) error { +func (gui *Gui) handleCommitEditorPress(g *gocui.Gui, filesView *gocui.View) error { if len(gui.stagedFiles(gui.State.Files)) == 0 && !gui.State.HasMergeConflicts { return gui.createErrorPanel(g, "There are no staged files to commit") } @@ -214,7 +215,7 @@ func (gui *Gui) PrepareSubProcess(g *gocui.Gui, commands ...string) error { return nil } -func (gui *Gui) genericFileOpen(g *gocui.Gui, v *gocui.View, open func(*gocui.Gui, string) (string, error)) error { +func (gui *Gui) genericFileOpen(g *gocui.Gui, v *gocui.View, open func(string) (*exec.Cmd, error)) error { file, err := gui.getSelectedFile(g) if err != nil { if err != errNoFiles { @@ -222,26 +223,31 @@ func (gui *Gui) genericFileOpen(g *gocui.Gui, v *gocui.View, open func(*gocui.Gu } return nil } - if _, err := open(g, file.Name); err != nil { + sub, err := open(file.Name) + if err != nil { return gui.createErrorPanel(g, err.Error()) } + if sub != nil { + gui.SubProcess = sub + return ErrSubProcess + } return nil } func (gui *Gui) handleFileEdit(g *gocui.Gui, v *gocui.View) error { - return gui.genericFileOpen(g, v, gui.editFile) + return gui.genericFileOpen(g, v, gui.OSCommand.EditFile) } func (gui *Gui) handleFileOpen(g *gocui.Gui, v *gocui.View) error { - return gui.genericFileOpen(g, v, gui.openFile) + return gui.genericFileOpen(g, v, gui.OSCommand.OpenFile) } func (gui *Gui) handleSublimeFileOpen(g *gocui.Gui, v *gocui.View) error { - return gui.genericFileOpen(g, v, gui.sublimeOpenFile) + return gui.genericFileOpen(g, v, gui.OSCommand.SublimeOpenFile) } func (gui *Gui) handleVsCodeFileOpen(g *gocui.Gui, v *gocui.View) error { - return gui.genericFileOpen(g, v, gui.vsCodeOpenFile) + return gui.genericFileOpen(g, v, gui.OSCommand.VsCodeOpenFile) } func (gui *Gui) handleRefreshFiles(g *gocui.Gui, v *gocui.View) error { @@ -250,13 +256,13 @@ func (gui *Gui) handleRefreshFiles(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) refreshStateFiles() { // get files to stage - files := getGitStatusFiles() - gui.State.Files = mergeGitStatusFiles(gui.State.Files, files) - updateHasMergeConflictStatus() + files := gui.GitCommand.GetStatusFiles() + gui.State.Files = gui.GitCommand.MergeStatusFiles(gui.State.Files, files) + gui.updateHasMergeConflictStatus() } func (gui *Gui) updateHasMergeConflictStatus() error { - merging, err := isInMergeState() + merging, err := gui.GitCommand.IsInMergeState() if err != nil { return err } @@ -290,7 +296,7 @@ func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) { } return "", gui.renderString(g, "main", "No file to display") } - cat, err := catFile(item.Name) + cat, err := gui.GitCommand.CatFile(item.Name) if err != nil { panic(err) } @@ -302,12 +308,12 @@ func (gui *Gui) refreshFiles(g *gocui.Gui) error { if err != nil { return err } - refreshStateFiles() + gui.refreshStateFiles() filesView.Clear() for _, file := range gui.State.Files { - renderFile(file, filesView) + gui.renderFile(file, filesView) } - correctCursor(filesView) + gui.correctCursor(filesView) if filesView == g.CurrentView() { gui.handleFileSelect(g, filesView) } @@ -315,14 +321,14 @@ func (gui *Gui) refreshFiles(g *gocui.Gui) error { } func (gui *Gui) pullFiles(g *gocui.Gui, v *gocui.View) error { - createMessagePanel(g, v, "", "Pulling...") + gui.createMessagePanel(g, v, "", "Pulling...") go func() { - if output, err := gitPull(); err != nil { + if output, err := gui.GitCommand.Pull(); err != nil { gui.createErrorPanel(g, output) } else { gui.closeConfirmationPrompt(g) - refreshCommits(g) - refreshStatus(g) + gui.refreshCommits(g) + gui.refreshStatus(g) } gui.refreshFiles(g) }() @@ -330,15 +336,15 @@ func (gui *Gui) pullFiles(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) pushFiles(g *gocui.Gui, v *gocui.View) error { - createMessagePanel(g, v, "", "Pushing...") + gui.createMessagePanel(g, v, "", "Pushing...") go func() { - branchName = gui.State.Branches[0].Name - if output, err := commands.Push(branchName); err != nil { + branchName := gui.State.Branches[0].Name + if output, err := gui.GitCommand.Push(branchName); err != nil { gui.createErrorPanel(g, output) } else { gui.closeConfirmationPrompt(g) - refreshCommits(g) - refreshStatus(g) + gui.refreshCommits(g) + gui.refreshStatus(g) } }() return nil @@ -360,22 +366,22 @@ func (gui *Gui) handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error { return gui.createErrorPanel(g, "This file has no merge conflicts") } gui.switchFocus(g, v, mergeView) - return refreshMergePanel(g) + return gui.refreshMergePanel(g) } func (gui *Gui) handleAbortMerge(g *gocui.Gui, v *gocui.View) error { - output, err := gitAbortMerge() + output, err := gui.GitCommand.AbortMerge() if err != nil { return gui.createErrorPanel(g, output) } - createMessagePanel(g, v, "", "Merge aborted") - refreshStatus(g) + gui.createMessagePanel(g, v, "", "Merge aborted") + gui.refreshStatus(g) return gui.refreshFiles(g) } func (gui *Gui) handleResetHard(g *gocui.Gui, v *gocui.View) error { return gui.createConfirmationPanel(g, v, "Clear file panel", "Are you sure you want `reset --hard HEAD`? You may lose changes", func(g *gocui.Gui, v *gocui.View) error { - if err := commands.ResetHard(); err != nil { + if err := gui.GitCommand.ResetHard(); err != nil { gui.createErrorPanel(g, err.Error()) } return gui.refreshFiles(g) diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 402b4ff32..aa524ce0d 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -37,12 +37,27 @@ type Gui struct { OSCommand *commands.OSCommand Version string SubProcess *exec.Cmd - State StateType + State guiState +} + +type guiState struct { + Files []commands.File + Branches []commands.Branch + Commits []commands.Commit + StashEntries []commands.StashEntry + PreviousView string + HasMergeConflicts bool + ConflictIndex int + ConflictTop bool + Conflicts []commands.Conflict + EditHistory *stack.Stack + Platform platform + Version string } // NewGui builds a new gui handler func NewGui(log *logrus.Logger, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, version string) (*Gui, error) { - initialState := StateType{ + initialState := guiState{ Files: make([]commands.File, 0), PreviousView: "files", Commits: make([]commands.Commit, 0), @@ -64,21 +79,6 @@ func NewGui(log *logrus.Logger, gitCommand *commands.GitCommand, oSCommand *comm }, nil } -type StateType struct { - Files []commands.File - Branches []commands.Branch - Commits []commands.Commit - StashEntries []commands.StashEntry - PreviousView string - HasMergeConflicts bool - ConflictIndex int - ConflictTop bool - Conflicts []commands.Conflict - EditHistory *stack.Stack - Platform platform - Version string -} - type platform struct { os string shell string @@ -105,7 +105,7 @@ func getPlatform() platform { } } -func scrollUpMain(g *gocui.Gui, v *gocui.View) error { +func (gui *Gui) scrollUpMain(g *gocui.Gui, v *gocui.View) error { mainView, _ := g.View("main") ox, oy := mainView.Origin() if oy >= 1 { @@ -114,7 +114,7 @@ func scrollUpMain(g *gocui.Gui, v *gocui.View) error { return nil } -func scrollDownMain(g *gocui.Gui, v *gocui.View) error { +func (gui *Gui) scrollDownMain(g *gocui.Gui, v *gocui.View) error { mainView, _ := g.View("main") ox, oy := mainView.Origin() if oy < len(mainView.BufferLines()) { @@ -123,8 +123,8 @@ func scrollDownMain(g *gocui.Gui, v *gocui.View) error { return nil } -func handleRefresh(g *gocui.Gui, v *gocui.View) error { - return refreshSidePanels(g) +func (gui *Gui) handleRefresh(g *gocui.Gui, v *gocui.View) error { + return gui.refreshSidePanels(g) } func max(a, b int) int { @@ -257,35 +257,35 @@ func (gui *Gui) layout(g *gocui.Gui) error { // these are only called once gui.handleFileSelect(g, filesView) gui.refreshFiles(g) - refreshBranches(g) - refreshCommits(g) - refreshStashEntries(g) - nextView(g, nil) + gui.refreshBranches(g) + gui.refreshCommits(g) + gui.refreshStashEntries(g) + gui.nextView(g, nil) } - resizePopupPanels(g) + gui.resizePopupPanels(g) return nil } -func fetch(g *gocui.Gui) error { - gitFetch() - refreshStatus(g) +func (gui *Gui) fetch(g *gocui.Gui) error { + gui.GitCommand.Fetch() + gui.refreshStatus(g) return nil } -func updateLoader(g *gocui.Gui) error { +func (gui *Gui) updateLoader(g *gocui.Gui) error { if confirmationView, _ := g.View("confirmation"); confirmationView != nil { content := gui.trimmedContent(confirmationView) if strings.Contains(content, "...") { staticContent := strings.Split(content, "...")[0] + "..." - gui.renderString(g, "confirmation", staticContent+" "+loader()) + gui.renderString(g, "confirmation", staticContent+" "+gui.loader()) } } return nil } -func goEvery(g *gocui.Gui, interval time.Duration, function func(*gocui.Gui) error) { +func (gui *Gui) goEvery(g *gocui.Gui, interval time.Duration, function func(*gocui.Gui) error) { go func() { for range time.Tick(interval) { function(g) @@ -293,36 +293,36 @@ func goEvery(g *gocui.Gui, interval time.Duration, function func(*gocui.Gui) err }() } -func resizePopupPanels(g *gocui.Gui) error { +func (gui *Gui) resizePopupPanels(g *gocui.Gui) error { v := g.CurrentView() if v.Name() == "commitMessage" || v.Name() == "confirmation" { - return resizePopupPanel(g, v) + return gui.resizePopupPanel(g, v) } return nil } // Run setup the gui with keybindings and start the mainloop -func (gui *Gui) Run() (*exec.Cmd, error) { +func (gui *Gui) Run() error { g, err := gocui.NewGui(gocui.OutputNormal, OverlappingEdges) if err != nil { - return nil, err + return err } defer g.Close() g.FgColor = gocui.ColorDefault - goEvery(g, time.Second*60, fetch) - goEvery(g, time.Second*10, gui.refreshFiles) - goEvery(g, time.Millisecond*10, updateLoader) + gui.goEvery(g, time.Second*60, gui.fetch) + gui.goEvery(g, time.Second*10, gui.refreshFiles) + gui.goEvery(g, time.Millisecond*10, gui.updateLoader) g.SetManagerFunc(gui.layout) if err = gui.keybindings(g); err != nil { - return nil, err + return err } err = g.MainLoop() - return nil, err + return err } // RunWithSubprocesses loops, instantiating a new gocui.Gui with each iteration @@ -342,6 +342,6 @@ func (gui *Gui) RunWithSubprocesses() { } } -func quit(g *gocui.Gui, v *gocui.View) error { +func (gui *Gui) quit(g *gocui.Gui, v *gocui.View) error { return gocui.ErrQuit } diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 194746d28..6a8a645f5 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -70,15 +70,15 @@ func (gui *Gui) keybindings(g *gocui.Gui) error { // input in the confirmation panel for _, viewName := range []string{"files", "branches", "commits", "stash"} { bindings = append(bindings, []Binding{ - {ViewName: viewName, Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: nextView}, - {ViewName: viewName, Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: previousView}, - {ViewName: viewName, Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: nextView}, - {ViewName: viewName, Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: cursorUp}, - {ViewName: viewName, Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: cursorDown}, - {ViewName: viewName, Key: 'h', Modifier: gocui.ModNone, Handler: previousView}, - {ViewName: viewName, Key: 'l', Modifier: gocui.ModNone, Handler: nextView}, - {ViewName: viewName, Key: 'k', Modifier: gocui.ModNone, Handler: cursorUp}, - {ViewName: viewName, Key: 'j', Modifier: gocui.ModNone, Handler: cursorDown}, + {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}, }...) } diff --git a/pkg/gui/merge_panel.go b/pkg/gui/merge_panel.go index 7e0a3a873..81e37f593 100644 --- a/pkg/gui/merge_panel.go +++ b/pkg/gui/merge_panel.go @@ -16,89 +16,89 @@ import ( "github.com/jesseduffield/lazygit/pkg/utils" ) -func findConflicts(content string) ([]commands.Conflict, error) { +func (gui *Gui) findConflicts(content string) ([]commands.Conflict, error) { conflicts := make([]commands.Conflict, 0) - var newConflict conflict + var newConflict commands.Conflict for i, line := range utils.SplitLines(content) { if line == "<<<<<<< HEAD" || line == "<<<<<<< MERGE_HEAD" || line == "<<<<<<< Updated upstream" { - newConflict = conflict{start: i} + newConflict = commands.Conflict{Start: i} } else if line == "=======" { - newConflict.middle = i + newConflict.Middle = i } else if strings.HasPrefix(line, ">>>>>>> ") { - newConflict.end = i + newConflict.End = i conflicts = append(conflicts, newConflict) } } return conflicts, nil } -func shiftConflict(conflicts []commands.Conflict) (commands.Conflict, []commands.Conflict) { +func (gui *Gui) shiftConflict(conflicts []commands.Conflict) (commands.Conflict, []commands.Conflict) { return conflicts[0], conflicts[1:] } -func shouldHighlightLine(index int, conflict commands.Conflict, top bool) bool { - return (index >= conflict.start && index <= conflict.middle && top) || (index >= conflict.middle && index <= conflict.end && !top) +func (gui *Gui) shouldHighlightLine(index int, conflict commands.Conflict, top bool) bool { + return (index >= conflict.Start && index <= conflict.Middle && top) || (index >= conflict.Middle && index <= conflict.End && !top) } -func coloredConflictFile(content string, conflicts []commands.Conflict, conflictIndex int, conflictTop, hasFocus bool) (string, error) { +func (gui *Gui) coloredConflictFile(content string, conflicts []commands.Conflict, conflictIndex int, conflictTop, hasFocus bool) (string, error) { if len(conflicts) == 0 { return content, nil } - conflict, remainingConflicts := shiftConflict(conflicts) + conflict, remainingConflicts := gui.shiftConflict(conflicts) var outputBuffer bytes.Buffer - for i, line := range splitLines(content) { + for i, line := range utils.SplitLines(content) { colourAttr := color.FgWhite - if i == conflict.start || i == conflict.middle || i == conflict.end { + if i == conflict.Start || i == conflict.Middle || i == conflict.End { colourAttr = color.FgRed } colour := color.New(colourAttr) - if hasFocus && conflictIndex < len(conflicts) && conflicts[conflictIndex] == conflict && shouldHighlightLine(i, conflict, conflictTop) { + if hasFocus && conflictIndex < len(conflicts) && conflicts[conflictIndex] == conflict && gui.shouldHighlightLine(i, conflict, conflictTop) { colour.Add(color.Bold) } - if i == conflict.end && len(remainingConflicts) > 0 { - conflict, remainingConflicts = shiftConflict(remainingConflicts) + if i == conflict.End && len(remainingConflicts) > 0 { + conflict, remainingConflicts = gui.shiftConflict(remainingConflicts) } - outputBuffer.WriteString(coloredStringDirect(line, colour) + "\n") + outputBuffer.WriteString(utils.ColoredStringDirect(line, colour) + "\n") } return outputBuffer.String(), nil } -func handleSelectTop(g *gocui.Gui, v *gocui.View) error { - state.ConflictTop = true - return refreshMergePanel(g) +func (gui *Gui) handleSelectTop(g *gocui.Gui, v *gocui.View) error { + gui.State.ConflictTop = true + return gui.refreshMergePanel(g) } -func handleSelectBottom(g *gocui.Gui, v *gocui.View) error { - state.ConflictTop = false - return refreshMergePanel(g) +func (gui *Gui) handleSelectBottom(g *gocui.Gui, v *gocui.View) error { + gui.State.ConflictTop = false + return gui.refreshMergePanel(g) } -func handleSelectNextConflict(g *gocui.Gui, v *gocui.View) error { - if state.ConflictIndex >= len(state.Conflicts)-1 { +func (gui *Gui) handleSelectNextConflict(g *gocui.Gui, v *gocui.View) error { + if gui.State.ConflictIndex >= len(gui.State.Conflicts)-1 { return nil } - state.ConflictIndex++ - return refreshMergePanel(g) + gui.State.ConflictIndex++ + return gui.refreshMergePanel(g) } -func handleSelectPrevConflict(g *gocui.Gui, v *gocui.View) error { - if state.ConflictIndex <= 0 { +func (gui *Gui) handleSelectPrevConflict(g *gocui.Gui, v *gocui.View) error { + if gui.State.ConflictIndex <= 0 { return nil } - state.ConflictIndex-- - return refreshMergePanel(g) + gui.State.ConflictIndex-- + return gui.refreshMergePanel(g) } -func isIndexToDelete(i int, conflict commands.Conflict, pick string) bool { - return i == conflict.middle || - i == conflict.start || - i == conflict.end || +func (gui *Gui) isIndexToDelete(i int, conflict commands.Conflict, pick string) bool { + return i == conflict.Middle || + i == conflict.Start || + i == conflict.End || pick != "both" && - (pick == "bottom" && i > conflict.start && i < conflict.middle) || - (pick == "top" && i > conflict.middle && i < conflict.end) + (pick == "bottom" && i > conflict.Start && i < conflict.Middle) || + (pick == "top" && i > conflict.Middle && i < conflict.End) } -func resolveConflict(g *gocui.Gui, conflict commands.Conflict, pick string) error { +func (gui *Gui) resolveConflict(g *gocui.Gui, conflict commands.Conflict, pick string) error { gitFile, err := gui.getSelectedFile(g) if err != nil { return err @@ -116,126 +116,121 @@ func resolveConflict(g *gocui.Gui, conflict commands.Conflict, pick string) erro if err != nil { break } - if !isIndexToDelete(i, conflict, pick) { + if !gui.isIndexToDelete(i, conflict, pick) { output += line } } - devLog(output) + gui.Log.Info(output) return ioutil.WriteFile(gitFile.Name, []byte(output), 0644) } -func pushFileSnapshot(g *gocui.Gui) error { +func (gui *Gui) pushFileSnapshot(g *gocui.Gui) error { gitFile, err := gui.getSelectedFile(g) if err != nil { return err } - content, err := catFile(gitFile.Name) + content, err := gui.GitCommand.CatFile(gitFile.Name) if err != nil { return err } - state.EditHistory.Push(content) + gui.State.EditHistory.Push(content) return nil } -func handlePopFileSnapshot(g *gocui.Gui, v *gocui.View) error { - if state.EditHistory.Len() == 0 { +func (gui *Gui) handlePopFileSnapshot(g *gocui.Gui, v *gocui.View) error { + if gui.State.EditHistory.Len() == 0 { return nil } - prevContent := state.EditHistory.Pop().(string) + prevContent := gui.State.EditHistory.Pop().(string) gitFile, err := gui.getSelectedFile(g) if err != nil { return err } ioutil.WriteFile(gitFile.Name, []byte(prevContent), 0644) - return refreshMergePanel(g) + return gui.refreshMergePanel(g) } -func handlePickHunk(g *gocui.Gui, v *gocui.View) error { - conflict := state.Conflicts[state.ConflictIndex] - pushFileSnapshot(g) +func (gui *Gui) handlePickHunk(g *gocui.Gui, v *gocui.View) error { + conflict := gui.State.Conflicts[gui.State.ConflictIndex] + gui.pushFileSnapshot(g) pick := "bottom" - if state.ConflictTop { + if gui.State.ConflictTop { pick = "top" } - err := resolveConflict(g, conflict, pick) + err := gui.resolveConflict(g, conflict, pick) if err != nil { panic(err) } - refreshMergePanel(g) + gui.refreshMergePanel(g) return nil } -func handlePickBothHunks(g *gocui.Gui, v *gocui.View) error { - conflict := state.Conflicts[state.ConflictIndex] - pushFileSnapshot(g) - err := resolveConflict(g, conflict, "both") +func (gui *Gui) handlePickBothHunks(g *gocui.Gui, v *gocui.View) error { + conflict := gui.State.Conflicts[gui.State.ConflictIndex] + gui.pushFileSnapshot(g) + err := gui.resolveConflict(g, conflict, "both") if err != nil { panic(err) } - return refreshMergePanel(g) + return gui.refreshMergePanel(g) } -func currentViewName(g *gocui.Gui) string { - currentView := g.CurrentView() - return currentView.Name() -} - -func refreshMergePanel(g *gocui.Gui) error { - cat, err := catSelectedFile(g) +func (gui *Gui) refreshMergePanel(g *gocui.Gui) error { + cat, err := gui.catSelectedFile(g) if err != nil { return err } - state.Conflicts, err = findConflicts(cat) + gui.State.Conflicts, err = gui.findConflicts(cat) if err != nil { return err } - if len(state.Conflicts) == 0 { - return handleCompleteMerge(g) - } else if state.ConflictIndex > len(state.Conflicts)-1 { - state.ConflictIndex = len(state.Conflicts) - 1 + 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 } - hasFocus := currentViewName(g) == "main" + hasFocus := gui.currentViewName(g) == "main" if hasFocus { - renderMergeOptions(g) + gui.renderMergeOptions(g) } - content, err := coloredConflictFile(cat, state.Conflicts, state.ConflictIndex, state.ConflictTop, hasFocus) + content, err := gui.coloredConflictFile(cat, gui.State.Conflicts, gui.State.ConflictIndex, gui.State.ConflictTop, hasFocus) if err != nil { return err } - if err := scrollToConflict(g); err != nil { + if err := gui.scrollToConflict(g); err != nil { return err } return gui.renderString(g, "main", content) } -func scrollToConflict(g *gocui.Gui) error { +func (gui *Gui) scrollToConflict(g *gocui.Gui) error { mainView, err := g.View("main") if err != nil { return err } - if len(state.Conflicts) == 0 { + if len(gui.State.Conflicts) == 0 { return nil } - conflict := state.Conflicts[state.ConflictIndex] + conflict := gui.State.Conflicts[gui.State.ConflictIndex] ox, _ := mainView.Origin() _, height := mainView.Size() - conflictMiddle := (conflict.end + conflict.start) / 2 + conflictMiddle := (conflict.End + conflict.Start) / 2 newOriginY := int(math.Max(0, float64(conflictMiddle-(height/2)))) return mainView.SetOrigin(ox, newOriginY) } -func switchToMerging(g *gocui.Gui) error { - state.ConflictIndex = 0 - state.ConflictTop = true +func (gui *Gui) switchToMerging(g *gocui.Gui) error { + gui.State.ConflictIndex = 0 + gui.State.ConflictTop = true _, err := g.SetCurrentView("main") if err != nil { return err } - return refreshMergePanel(g) + return gui.refreshMergePanel(g) } -func renderMergeOptions(g *gocui.Gui) error { +func (gui *Gui) renderMergeOptions(g *gocui.Gui) error { return gui.renderOptionsMap(g, map[string]string{ "↑ ↓": "select hunk", "← →": "navigate conflicts", @@ -245,7 +240,7 @@ func renderMergeOptions(g *gocui.Gui) error { }) } -func handleEscapeMerge(g *gocui.Gui, v *gocui.View) error { +func (gui *Gui) handleEscapeMerge(g *gocui.Gui, v *gocui.View) error { filesView, err := g.View("files") if err != nil { return err @@ -254,12 +249,12 @@ func handleEscapeMerge(g *gocui.Gui, v *gocui.View) error { return gui.switchFocus(g, v, filesView) } -func handleCompleteMerge(g *gocui.Gui) error { +func (gui *Gui) handleCompleteMerge(g *gocui.Gui) error { filesView, err := g.View("files") if err != nil { return err } - stageSelectedFile(g) + gui.stageSelectedFile(g) gui.refreshFiles(g) return gui.switchFocus(g, nil, filesView) } diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index 1af6bcc29..b1a209bb4 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -4,17 +4,18 @@ import ( "fmt" "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands" ) -func refreshStashEntries(g *gocui.Gui) error { +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) } - state.StashEntries = getGitStashEntries() + gui.State.StashEntries = gui.GitCommand.GetStashEntries() v.Clear() - for _, stashEntry := range state.StashEntries { + for _, stashEntry := range gui.State.StashEntries { fmt.Fprintln(v, stashEntry.DisplayString) } return gui.resetOrigin(v) @@ -22,15 +23,15 @@ func refreshStashEntries(g *gocui.Gui) error { return nil } -func getSelectedStashEntry(v *gocui.View) *StashEntry { - if len(state.StashEntries) == 0 { +func (gui *Gui) getSelectedStashEntry(v *gocui.View) *commands.StashEntry { + if len(gui.State.StashEntries) == 0 { return nil } lineNumber := gui.getItemPosition(v) - return &state.StashEntries[lineNumber] + return &gui.State.StashEntries[lineNumber] } -func renderStashOptions(g *gocui.Gui) error { +func (gui *Gui) renderStashOptions(g *gocui.Gui) error { return gui.renderOptionsMap(g, map[string]string{ "space": "apply", "g": "pop", @@ -39,54 +40,54 @@ func renderStashOptions(g *gocui.Gui) error { }) } -func handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error { - if err := renderStashOptions(g); err != nil { +func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error { + if err := gui.renderStashOptions(g); err != nil { return err } go func() { - stashEntry := getSelectedStashEntry(v) + stashEntry := gui.getSelectedStashEntry(v) if stashEntry == nil { gui.renderString(g, "main", "No stash entries") return } - diff, _ := getStashEntryDiff(stashEntry.Index) + diff, _ := gui.GitCommand.GetStashEntryDiff(stashEntry.Index) gui.renderString(g, "main", diff) }() return nil } -func handleStashApply(g *gocui.Gui, v *gocui.View) error { - return stashDo(g, v, "apply") +func (gui *Gui) handleStashApply(g *gocui.Gui, v *gocui.View) error { + return gui.stashDo(g, v, "apply") } -func handleStashPop(g *gocui.Gui, v *gocui.View) error { - return stashDo(g, v, "pop") +func (gui *Gui) handleStashPop(g *gocui.Gui, v *gocui.View) error { + return gui.stashDo(g, v, "pop") } -func handleStashDrop(g *gocui.Gui, v *gocui.View) error { +func (gui *Gui) handleStashDrop(g *gocui.Gui, v *gocui.View) error { return gui.createConfirmationPanel(g, v, "Stash drop", "Are you sure you want to drop this stash entry?", func(g *gocui.Gui, v *gocui.View) error { - return stashDo(g, v, "drop") + return gui.stashDo(g, v, "drop") }, nil) } -func stashDo(g *gocui.Gui, v *gocui.View, method string) error { - stashEntry := getSelectedStashEntry(v) +func (gui *Gui) stashDo(g *gocui.Gui, v *gocui.View, method string) error { + stashEntry := gui.getSelectedStashEntry(v) if stashEntry == nil { return gui.createErrorPanel(g, "No stash to "+method) } - if output, err := gitStashDo(stashEntry.Index, method); err != nil { + if output, err := gui.GitCommand.StashDo(stashEntry.Index, method); err != nil { gui.createErrorPanel(g, output) } - refreshStashEntries(g) + gui.refreshStashEntries(g) return gui.refreshFiles(g) } -func handleStashSave(g *gocui.Gui, filesView *gocui.View) error { +func (gui *Gui) handleStashSave(g *gocui.Gui, filesView *gocui.View) error { gui.createPromptPanel(g, filesView, "Stash changes", func(g *gocui.Gui, v *gocui.View) error { - if output, err := gitStashSave(gui.trimmedContent(v)); err != nil { + if output, err := gui.GitCommand.StashSave(gui.trimmedContent(v)); err != nil { gui.createErrorPanel(g, output) } - refreshStashEntries(g) + gui.refreshStashEntries(g) return gui.refreshFiles(g) }) return nil diff --git a/pkg/gui/status_panel.go b/pkg/gui/status_panel.go index 822be2846..67f133738 100644 --- a/pkg/gui/status_panel.go +++ b/pkg/gui/status_panel.go @@ -5,9 +5,10 @@ import ( "github.com/fatih/color" "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/utils" ) -func refreshStatus(g *gocui.Gui) error { +func (gui *Gui) refreshStatus(g *gocui.Gui) error { v, err := g.View("status") if err != nil { panic(err) @@ -17,22 +18,22 @@ func refreshStatus(g *gocui.Gui) error { // contents end up cleared g.Update(func(*gocui.Gui) error { v.Clear() - pushables, pullables := git.UpstreamDifferenceCount() + pushables, pullables := gui.GitCommand.UpstreamDifferenceCount() fmt.Fprint(v, "↑"+pushables+"↓"+pullables) - branches := state.Branches - if err := updateHasMergeConflictStatus(); err != nil { + branches := gui.State.Branches + if err := gui.updateHasMergeConflictStatus(); err != nil { return err } - if state.HasMergeConflicts { - fmt.Fprint(v, coloredString(" (merging)", color.FgYellow)) + if gui.State.HasMergeConflicts { + fmt.Fprint(v, utils.ColoredString(" (merging)", color.FgYellow)) } if len(branches) == 0 { return nil } branch := branches[0] - name := coloredString(branch.Name, branch.getColor()) - repo := getCurrentProject() + name := utils.ColoredString(branch.Name, branch.GetColor()) + repo := utils.GetCurrentRepoName() fmt.Fprint(v, " "+repo+" → "+name) return nil }) diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index a9564de5e..a3e2877c3 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -13,7 +13,7 @@ var cyclableViews = []string{"files", "branches", "commits", "stash"} func (gui *Gui) refreshSidePanels(g *gocui.Gui) error { gui.refreshBranches(g) - gui.gui.refreshFiles(g) + gui.refreshFiles(g) gui.refreshCommits(g) return nil } @@ -38,7 +38,7 @@ func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error { if err != nil { panic(err) } - return gui.gui.switchFocus(g, v, focusedView) + return gui.switchFocus(g, v, focusedView) } func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error { @@ -52,7 +52,7 @@ func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error { break } if i == len(cyclableViews)-1 { - devLog(v.Name() + " is not in the list of views") + gui.Log.Info(v.Name() + " is not in the list of views") return nil } } @@ -72,27 +72,27 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error { case "files": return gui.handleFileSelect(g, v) case "branches": - return handleBranchSelect(g, v) + return gui.handleBranchSelect(g, v) case "confirmation": return nil case "commitMessage": - return handleCommitFocused(g, v) + return gui.handleCommitFocused(g, v) case "main": // TODO: pull this out into a 'view focused' function - refreshMergePanel(g) + gui.refreshMergePanel(g) v.Highlight = false return nil case "commits": - return handleCommitSelect(g, v) + return gui.handleCommitSelect(g, v) case "stash": - return handleStashEntrySelect(g, v) + return gui.handleStashEntrySelect(g, v) default: panic("No view matching newLineFocused switch statement") } } func (gui *Gui) returnFocus(g *gocui.Gui, v *gocui.View) error { - previousView, err := g.View(state.PreviousView) + previousView, err := g.View(gui.State.PreviousView) if err != nil { panic(err) } @@ -105,16 +105,16 @@ func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error { // we should never stack confirmation panels if oldView != nil && oldView.Name() != "confirmation" { oldView.Highlight = false - devLog("setting previous view to:", oldView.Name()) - state.PreviousView = oldView.Name() + gui.Log.Info("setting previous view to:", oldView.Name()) + gui.State.PreviousView = oldView.Name() } newView.Highlight = true - devLog("new focused view is " + newView.Name()) + gui.Log.Info("new focused view is " + newView.Name()) if _, err := g.SetCurrentView(newView.Name()); err != nil { return err } g.Cursor = newView.Editable - return newLineFocused(g, newView) + return gui.newLineFocused(g, newView) } func (gui *Gui) getItemPosition(v *gocui.View) int { @@ -138,7 +138,7 @@ func (gui *Gui) cursorUp(g *gocui.Gui, v *gocui.View) error { } } - newLineFocused(g, v) + gui.newLineFocused(g, v) return nil } @@ -159,7 +159,7 @@ func (gui *Gui) cursorDown(g *gocui.Gui, v *gocui.View) error { } } - newLineFocused(g, v) + gui.newLineFocused(g, v) return nil } @@ -207,7 +207,7 @@ func (gui *Gui) optionsMapToString(optionsMap map[string]string) string { } func (gui *Gui) renderOptionsMap(g *gocui.Gui, optionsMap map[string]string) error { - return gui.renderString(g, "options", optionsMapToString(optionsMap)) + return gui.renderString(g, "options", gui.optionsMapToString(optionsMap)) } func (gui *Gui) loader() string { @@ -237,3 +237,8 @@ func (gui *Gui) getCommitMessageView(g *gocui.Gui) *gocui.View { func (gui *Gui) trimmedContent(v *gocui.View) string { return strings.TrimSpace(v.Buffer()) } + +func (gui *Gui) currentViewName(g *gocui.Gui) string { + currentView := g.CurrentView() + return currentView.Name() +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index b3efcc2db..68438246a 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -46,8 +46,8 @@ func ColoredStringDirect(str string, colour *color.Color) string { return colour.SprintFunc()(fmt.Sprint(str)) } -// GetCurrentProject gets the repo's base name -func GetCurrentProject() string { +// GetCurrentRepoName gets the repo's base name +func GetCurrentRepoName() string { pwd, err := os.Getwd() if err != nil { log.Fatalln(err.Error())