From 99a8b1ae8bb93565df8acbe168749278dc768979 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 4 Dec 2018 19:50:11 +1100 Subject: making a start on unidirectional data binding to fix these UI bugs --- pkg/app/app.go | 2 + pkg/commands/git.go | 11 +++++ pkg/gui/branches_panel.go | 42 +++++++++++++------ pkg/gui/commits_panel.go | 77 ++++++++++++++++++---------------- pkg/gui/files_panel.go | 65 ++++++++++++++++------------ pkg/gui/gui.go | 71 +++++++++++++++++++++++++------ pkg/gui/keybindings.go | 36 ++++++++++++---- pkg/gui/staging_panel.go | 62 ++++++++++----------------- pkg/gui/stash_panel.go | 7 ---- pkg/gui/status_panel.go | 9 +--- pkg/gui/view_helpers.go | 105 +++++++++++++++++++++++++++++++++++++++++----- pkg/utils/utils.go | 6 +++ 12 files changed, 333 insertions(+), 160 deletions(-) (limited to 'pkg') diff --git a/pkg/app/app.go b/pkg/app/app.go index 65acd2e35..b43f9568c 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -52,6 +52,8 @@ func newLogger(config config.AppConfigurer) *logrus.Entry { } else { log = newProductionLogger(config) } + 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) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 7d820c599..e185b0fbd 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -130,6 +130,17 @@ func (c *GitCommand) GetStashEntryDiff(index int) (string, error) { // GetStatusFiles git status files func (c *GitCommand) GetStatusFiles() []*File { + + // files := []*File{} + // for i := 0; i < 100; i++ { + // files = append(files, &File{ + // Name: strconv.Itoa(i), + // DisplayString: strconv.Itoa(i), + // Type: "file", + // }) + // } + // return files + statusOutput, _ := c.GitStatus() statusStrings := utils.SplitLines(statusOutput) files := []*File{} diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index dbf4b007a..e1504bd69 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -131,30 +131,30 @@ func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) getSelectedBranch(v *gocui.View) *commands.Branch { - lineNumber := gui.getItemPosition(v) - return gui.State.Branches[lineNumber] -} + selectedLine := gui.State.Panels.Branches.SelectedLine + if selectedLine == -1 { + return nil + } -func (gui *Gui) renderBranchesOptions(g *gocui.Gui) error { - return gui.renderGlobalOptions(g) + 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 { - if err := gui.renderBranchesOptions(g); err != nil { - return err - } // 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(v) + if err := gui.focusPoint(0, gui.State.Panels.Branches.SelectedLine, v); err != nil { + return err + } 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") + 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", diff) + _ = gui.renderString(g, "main", graph) }() return nil } @@ -173,6 +173,8 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error { } gui.State.Branches = builder.Build() + gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches)) + v.Clear() list, err := utils.RenderList(gui.State.Branches) if err != nil { @@ -186,3 +188,17 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error { }) 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) +} diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index ee7f191a7..be291091c 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -22,6 +22,8 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error { return err } + gui.refreshSelectedLine(&gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits)) + v.Clear() list, err := utils.RenderList(gui.State.Commits) @@ -41,9 +43,9 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error { func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error { return gui.createConfirmationPanel(g, commitView, gui.Tr.SLocalize("ResetToCommit"), gui.Tr.SLocalize("SureResetThisCommit"), func(g *gocui.Gui, v *gocui.View) error { - 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()) @@ -59,21 +61,15 @@ func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error }, 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 - } + 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 @@ -85,12 +81,12 @@ func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error { if gui.getItemPosition(v) != 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 +109,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 { @@ -165,18 +161,27 @@ 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 +func (gui *Gui) getSelectedCommit(g *gocui.Gui) *commands.Commit { + selectedLine := gui.State.Panels.Commits.SelectedLine + if selectedLine == -1 { + return nil } - return gui.State.Commits[lineNumber], nil + + return gui.State.Commits[selectedLine] +} + +func (gui *Gui) handleCommitsNextLine(g *gocui.Gui, v *gocui.View) error { + gui.Log.Info(utils.AsJson(gui.State.Panels)) + 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 { + gui.Log.Info(utils.AsJson(gui.State.Panels)) + panelState := gui.State.Panels.Commits + gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Commits), true) + + return gui.handleCommitSelect(gui.g, v) } diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 5eb8f63f1..13ce48d10 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -140,15 +140,12 @@ func (gui *Gui) handleAddPatch(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) getSelectedFile(g *gocui.Gui) (*commands.File, error) { - if len(gui.State.Files) == 0 { + selectedLine := gui.State.Panels.Files.SelectedLine + if selectedLine == -1 { 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 + + return gui.State.Files[selectedLine], nil } func (gui *Gui) handleFileRemove(g *gocui.Gui, v *gocui.View) error { @@ -194,26 +191,23 @@ 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 + 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) } @@ -309,6 +303,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,6 +335,20 @@ func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) { return cat, 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) +} + func (gui *Gui) refreshFiles(g *gocui.Gui) error { filesView, err := g.View("files") if err != nil { @@ -347,17 +356,21 @@ func (gui *Gui) refreshFiles(g *gocui.Gui) error { } gui.refreshStateFiles() - filesView.Clear() - list, err := utils.RenderList(gui.State.Files) - if err != nil { - return err - } - fmt.Fprint(filesView, list) + 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() { + gui.handleFileSelect(g, filesView) + } + return nil + }) - gui.correctCursor(filesView) - if filesView == g.CurrentView() { - gui.handleFileSelect(g, filesView) - } return nil } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 9f25121d5..1af3a5e1f 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -72,11 +72,35 @@ type Gui struct { statusManager *statusManager } -type stagingState struct { - StageableLines []int - HunkStarts []int - CurrentLineIndex int - Diff string +type stagingPanelState struct { + SelectedLine int + StageableLines []int + HunkStarts []int + Diff string +} + +type filePanelState struct { + SelectedLine int +} + +type branchPanelState struct { + SelectedLine int +} + +type commitPanelState struct { + SelectedLine int +} + +type stashPanelState struct { + SelectedLine int +} + +type panelStates struct { + Files *filePanelState + Staging *stagingPanelState + Branches *branchPanelState + Commits *commitPanelState + Stash *stashPanelState } type guiState struct { @@ -92,7 +116,7 @@ type guiState struct { EditHistory *stack.Stack Platform commands.Platform Updating bool - StagingState *stagingState + Panels *panelStates } // NewGui builds a new gui handler @@ -108,6 +132,12 @@ 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}, + }, } gui := &Gui{ @@ -193,9 +223,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") @@ -247,12 +279,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 { @@ -325,11 +358,14 @@ func (gui *Gui) layout(g *gocui.Gui) error { return err } - gui.handleFileSelect(g, filesView) + gui.g.SetCurrentView(filesView.Name()) gui.refreshFiles(g) gui.refreshBranches(g) gui.refreshCommits(g) gui.refreshStashEntries(g) + if err := gui.renderGlobalOptions(g); err != nil { + return err + } if err := gui.switchFocus(g, nil, filesView); err != nil { return err } @@ -341,6 +377,17 @@ 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 + } + } + return gui.resizeCurrentPopupPanel(g) } @@ -411,7 +458,7 @@ func (gui *Gui) Run() error { } gui.goEvery(g, time.Second*60, gui.fetch) - gui.goEvery(g, time.Second*10, gui.refreshFiles) + // gui.goEvery(g, time.Second*2, gui.refreshFiles) // TODO: comment back in gui.goEvery(g, time.Millisecond*50, gui.updateLoader) gui.goEvery(g, time.Millisecond*50, gui.renderAppStatus) diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 1a63d009c..c8c3d642f 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -219,7 +219,12 @@ func (gui *Gui) GetKeybindings() []*Binding { Handler: gui.handleSwitchToStagingPanel, Description: gui.Tr.SLocalize("StageLines"), KeyReadable: "enter", - }, { + }, + {ViewName: "files", Key: 'k', Modifier: gocui.ModNone, Handler: gui.handleFilesPrevLine}, + {ViewName: "files", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.handleFilesPrevLine}, + {ViewName: "files", Key: 'j', Modifier: gocui.ModNone, Handler: gui.handleFilesNextLine}, + {ViewName: "files", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.handleFilesNextLine}, + { ViewName: "main", Key: gocui.KeyEsc, Modifier: gocui.ModNone, @@ -322,7 +327,12 @@ func (gui *Gui) GetKeybindings() []*Binding { Modifier: gocui.ModNone, Handler: gui.handleMerge, Description: gui.Tr.SLocalize("mergeIntoCurrentBranch"), - }, { + }, + {ViewName: "branches", Key: 'k', Modifier: gocui.ModNone, Handler: gui.handleBranchesPrevLine}, + {ViewName: "branches", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.handleBranchesPrevLine}, + {ViewName: "branches", Key: 'j', Modifier: gocui.ModNone, Handler: gui.handleBranchesNextLine}, + {ViewName: "branches", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.handleBranchesNextLine}, + { ViewName: "commits", Key: 's', Modifier: gocui.ModNone, @@ -352,7 +362,12 @@ func (gui *Gui) GetKeybindings() []*Binding { Modifier: gocui.ModNone, Handler: gui.handleCommitFixup, Description: gui.Tr.SLocalize("fixupCommit"), - }, { + }, + {ViewName: "commits", Key: 'k', Modifier: gocui.ModNone, Handler: gui.handleCommitsPrevLine}, + {ViewName: "commits", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.handleCommitsPrevLine}, + {ViewName: "commits", Key: 'j', Modifier: gocui.ModNone, Handler: gui.handleCommitsNextLine}, + {ViewName: "commits", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.handleCommitsNextLine}, + { ViewName: "stash", Key: gocui.KeySpace, Modifier: gocui.ModNone, @@ -455,17 +470,22 @@ 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", "commits", "stash", "menu"} { + bindings = append(bindings, []*Binding{ + {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: 'k', Modifier: gocui.ModNone, Handler: gui.cursorUp}, + {ViewName: viewName, Key: 'j', Modifier: gocui.ModNone, Handler: gui.cursorDown}, + }...) + } + + 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}, }...) } diff --git a/pkg/gui/staging_panel.go b/pkg/gui/staging_panel.go index cba7d7638..1408cfb45 100644 --- a/pkg/gui/staging_panel.go +++ b/pkg/gui/staging_panel.go @@ -40,23 +40,23 @@ 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{ - StageableLines: stageableLines, - HunkStarts: hunkStarts, - CurrentLineIndex: currentLineIndex, - Diff: diff, + gui.State.Panels.Staging = &stagingPanelState{ + StageableLines: stageableLines, + HunkStarts: hunkStarts, + SelectedLine: selectedLine, + Diff: diff, } if len(stageableLines) == 0 { @@ -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) diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index 62b4efda7..196a33a08 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -37,14 +37,7 @@ func (gui *Gui) getSelectedStashEntry(v *gocui.View) *commands.StashEntry { return gui.State.StashEntries[lineNumber] } -func (gui *Gui) renderStashOptions(g *gocui.Gui) error { - return gui.renderGlobalOptions(g) -} - 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 { diff --git a/pkg/gui/status_panel.go b/pkg/gui/status_panel.go index aeae19c50..15f3f27d1 100644 --- a/pkg/gui/status_panel.go +++ b/pkg/gui/status_panel.go @@ -42,10 +42,6 @@ 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")) @@ -63,10 +59,7 @@ func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error { "Buy Jesse a coffee: https://donorbox.org/lazygit", }, "\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 { diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 525b05f97..4c5002efe 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -230,19 +230,46 @@ 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 { + return gui.focusPoint(cx, cy, v) +} + +// if the cursor down past the last item, move it to the last line +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 { - return err - } - if err := v.SetOrigin(ox, ly-newCy); err != nil { - return err + 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, 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 } @@ -334,3 +361,59 @@ func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error { _, err := g.SetView(v.Name(), x0, y0, x1, y1, 0) return err } + +// focusLine focuses and selects the given line +func (gui *Gui) focusLine(lineNumber int, v *gocui.View) error { + _, height := v.Size() + overScroll := lineNumber - 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 +} + +// 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 + } +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index e2a5337e3..390f85f70 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -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) +} -- cgit v1.2.3