summaryrefslogtreecommitdiffstats
path: root/pkg/gui/staging_panel.go
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2018-12-05 19:33:46 +1100
committerJesse Duffield <jessedduffield@gmail.com>2018-12-05 19:33:46 +1100
commitc0f9795910dd840fb83e6992f7f59c77ec4c13fc (patch)
tree05fe245b822008f458025a5dd75cae384bfda845 /pkg/gui/staging_panel.go
parent658e5a9faf8409c62f11f3ad6d636d0255e450f4 (diff)
staging lines and hunks
Diffstat (limited to 'pkg/gui/staging_panel.go')
-rw-r--r--pkg/gui/staging_panel.go166
1 files changed, 119 insertions, 47 deletions
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
}