From c0f9795910dd840fb83e6992f7f59c77ec4c13fc Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 5 Dec 2018 19:33:46 +1100 Subject: staging lines and hunks --- pkg/gui/staging_panel.go | 166 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 119 insertions(+), 47 deletions(-) (limited to 'pkg/gui/staging_panel.go') diff --git a/pkg/gui/staging_panel.go b/pkg/gui/staging_panel.go index be207c2fb..cba7d7638 100644 --- a/pkg/gui/staging_panel.go +++ b/pkg/gui/staging_panel.go @@ -2,19 +2,13 @@ package gui import ( "errors" - "io/ioutil" - - "github.com/davecgh/go-spew/spew" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/git" + "github.com/jesseduffield/lazygit/pkg/utils" ) func (gui *Gui) refreshStagingPanel() error { - // get the currently selected file. Get the diff of that file directly, not - // using any custom diff tools. - // parse the file to find out where the chunks and unstaged changes are - file, err := gui.getSelectedFile(gui.g) if err != nil { if err != gui.Errors.ErrNoFiles { @@ -31,10 +25,6 @@ func (gui *Gui) refreshStagingPanel() error { diff := gui.GitCommand.Diff(file, true) colorDiff := gui.GitCommand.Diff(file, false) - gui.Log.WithField("staging", "staging").Info("DIFF IS:") - gui.Log.WithField("staging", "staging").Info(spew.Sdump(diff)) - gui.Log.WithField("staging", "staging").Info("hello") - if len(diff) < 2 { return gui.handleStagingEscape(gui.g, nil) } @@ -73,9 +63,9 @@ func (gui *Gui) refreshStagingPanel() error { return errors.New("No lines to stage") } - stagingView := gui.getStagingView(gui.g) - stagingView.SetCursor(0, stageableLines[currentLineIndex]) - stagingView.SetOrigin(0, 0) + if err := gui.focusLineAndHunk(); err != nil { + return err + } return gui.renderString(gui.g, "staging", colorDiff) } @@ -84,57 +74,130 @@ func (gui *Gui) handleStagingEscape(g *gocui.Gui, v *gocui.View) error { return err } + gui.State.StagingState = nil + return gui.switchFocus(gui.g, nil, gui.getFilesView(gui.g)) } -// nextNumber returns the next index, cycling if we reach the end -func nextIndex(numbers []int, currentNumber int) int { - for index, number := range numbers { - if number > currentNumber { - return index - } - } - return 0 +func (gui *Gui) handleStagingPrevLine(g *gocui.Gui, v *gocui.View) error { + return gui.handleCycleLine(true) } -// prevNumber returns the next number, cycling if we reach the end -func prevIndex(numbers []int, currentNumber int) int { - end := len(numbers) - 1 - for i := end; i >= 0; i -= 1 { - if numbers[i] < currentNumber { - return i - } - } - return end +func (gui *Gui) handleStagingNextLine(g *gocui.Gui, v *gocui.View) error { + return gui.handleCycleLine(false) } -func (gui *Gui) handleStagingKeyUp(g *gocui.Gui, v *gocui.View) error { - return gui.handleCycleLine(true) +func (gui *Gui) handleStagingPrevHunk(g *gocui.Gui, v *gocui.View) error { + return gui.handleCycleHunk(true) } -func (gui *Gui) handleStagingKeyDown(g *gocui.Gui, v *gocui.View) error { - return gui.handleCycleLine(false) +func (gui *Gui) handleStagingNextHunk(g *gocui.Gui, v *gocui.View) error { + return gui.handleCycleHunk(false) } -func (gui *Gui) handleCycleLine(up bool) error { +func (gui *Gui) handleCycleHunk(prev bool) error { state := gui.State.StagingState lineNumbers := state.StageableLines currentLine := lineNumbers[state.CurrentLineIndex] - var newIndex int - if up { - newIndex = prevIndex(lineNumbers, currentLine) + currentHunkIndex := utils.PrevIndex(state.HunkStarts, currentLine) + var newHunkIndex int + if prev { + if currentHunkIndex == 0 { + newHunkIndex = len(state.HunkStarts) - 1 + } else { + newHunkIndex = currentHunkIndex - 1 + } } else { - newIndex = nextIndex(lineNumbers, currentLine) + if currentHunkIndex == len(state.HunkStarts)-1 { + newHunkIndex = 0 + } else { + newHunkIndex = currentHunkIndex + 1 + } } + state.CurrentLineIndex = utils.NextIndex(lineNumbers, state.HunkStarts[newHunkIndex]) + + return gui.focusLineAndHunk() +} + +func (gui *Gui) handleCycleLine(prev bool) error { + state := gui.State.StagingState + lineNumbers := state.StageableLines + currentLine := lineNumbers[state.CurrentLineIndex] + var newIndex int + if prev { + newIndex = utils.PrevIndex(lineNumbers, currentLine) + } else { + newIndex = utils.NextIndex(lineNumbers, currentLine) + } state.CurrentLineIndex = newIndex + + return gui.focusLineAndHunk() +} + +// 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.SetCursor(0, lineNumbers[newIndex]) - stagingView.SetOrigin(0, 0) + state := gui.State.StagingState + + lineNumber := state.StageableLines[state.CurrentLineIndex] + + // 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 + // the currently selected line so that the user can see the context + var bottomLine int + nextHunkStartIndex := utils.NextIndex(state.HunkStarts, lineNumber) + if nextHunkStartIndex == 0 { + // for now linesHeight is an efficient means of getting the number of lines + // in the patch. However if we introduce word wrap we'll need to update this + bottomLine = stagingView.LinesHeight() - 1 + } else { + bottomLine = state.HunkStarts[nextHunkStartIndex] - 1 + } + + hunkStartIndex := utils.PrevIndex(state.HunkStarts, lineNumber) + hunkStart := state.HunkStarts[hunkStartIndex] + // if it's the first hunk we'll also show the diff header + if hunkStartIndex == 0 { + hunkStart = 0 + } + + _, height := stagingView.Size() + // if this hunk is too big, we will just ensure that the user can at least + // see three lines of context below the cursor + if bottomLine-hunkStart > height { + 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 } +func (gui *Gui) handleStageHunk(g *gocui.Gui, v *gocui.View) error { + return gui.handleStageLineOrHunk(true) +} + func (gui *Gui) handleStageLine(g *gocui.Gui, v *gocui.View) error { + return gui.handleStageLineOrHunk(false) +} + +func (gui *Gui) handleStageLineOrHunk(hunk bool) error { state := gui.State.StagingState p, err := git.NewPatchModifier(gui.Log) if err != nil { @@ -142,22 +205,31 @@ func (gui *Gui) handleStageLine(g *gocui.Gui, v *gocui.View) error { } currentLine := state.StageableLines[state.CurrentLineIndex] - patch, err := p.ModifyPatch(state.Diff, currentLine) + var patch string + if hunk { + patch, err = p.ModifyPatchForHunk(state.Diff, state.HunkStarts, currentLine) + } else { + patch, err = p.ModifyPatchForLine(state.Diff, currentLine) + } if err != nil { return err } // for logging purposes - ioutil.WriteFile("patch.diff", []byte(patch), 0600) + // ioutil.WriteFile("patch.diff", []byte(patch), 0600) // apply the patch then refresh this panel // create a new temp file with the patch, then call git apply with that patch _, err = gui.GitCommand.ApplyPatch(patch) if err != nil { - panic(err) + return err } - gui.refreshStagingPanel() - gui.refreshFiles(gui.g) + if err := gui.refreshFiles(gui.g); err != nil { + return err + } + if err := gui.refreshStagingPanel(); err != nil { + return err + } return nil } -- cgit v1.2.3