diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2018-12-05 19:33:46 +1100 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2018-12-05 19:33:46 +1100 |
commit | c0f9795910dd840fb83e6992f7f59c77ec4c13fc (patch) | |
tree | 05fe245b822008f458025a5dd75cae384bfda845 /pkg/gui | |
parent | 658e5a9faf8409c62f11f3ad6d636d0255e450f4 (diff) |
staging lines and hunks
Diffstat (limited to 'pkg/gui')
-rw-r--r-- | pkg/gui/confirmation_panel.go | 12 | ||||
-rw-r--r-- | pkg/gui/files_panel.go | 7 | ||||
-rw-r--r-- | pkg/gui/gui.go | 1 | ||||
-rw-r--r-- | pkg/gui/keybindings.go | 55 | ||||
-rw-r--r-- | pkg/gui/staging_panel.go | 166 | ||||
-rw-r--r-- | pkg/gui/view_helpers.go | 1 |
6 files changed, 182 insertions, 60 deletions
diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index 58577e430..afe53ca2c 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -8,6 +8,7 @@ package gui import ( "strings" + "time" "github.com/fatih/color" "github.com/jesseduffield/gocui" @@ -73,6 +74,7 @@ func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt return nil, err } confirmationView.Title = title + confirmationView.Wrap = true confirmationView.FgColor = gocui.ColorWhite } confirmationView.Clear() @@ -137,7 +139,15 @@ func (gui *Gui) createMessagePanel(g *gocui.Gui, currentView *gocui.View, title, } func (gui *Gui) createErrorPanel(g *gocui.Gui, message string) error { - gui.Log.Error(message) + go func() { + // when reporting is switched on this log call sometimes introduces + // a delay on the error panel popping up. Here I'm adding a second wait + // so that the error is logged while the user is reading the error message + time.Sleep(time.Second) + gui.Log.Error(message) + }() + + // gui.Log.WithField("staging", "staging").Info("creating confirmation panel") currentView := g.CurrentView() colorFunction := color.New(color.FgRed).SprintFunc() coloredMessage := colorFunction(strings.TrimSpace(message)) diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 3404903ef..a16ef4909 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -57,10 +57,13 @@ func (gui *Gui) handleSwitchToStagingPanel(g *gocui.Gui, v *gocui.View) error { } return nil } - if !file.Tracked || !file.HasUnstagedChanges { + if !file.HasUnstagedChanges { + gui.Log.WithField("staging", "staging").Info("making error panel") return gui.createErrorPanel(g, gui.Tr.SLocalize("FileStagingRequirements")) } - gui.switchFocus(g, v, stagingView) + if err := gui.switchFocus(g, v, stagingView); err != nil { + return err + } return gui.refreshStagingPanel() } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 67415c5ce..9f25121d5 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -222,7 +222,6 @@ func (gui *Gui) layout(g *gocui.Gui) error { return err } v.Title = gui.Tr.SLocalize("StagingTitle") - v.Wrap = true v.Highlight = true v.FgColor = gocui.ColorWhite if _, err := g.SetViewOnBottom("staging"); err != nil { diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 2c47ab4f3..4158bedb7 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -392,25 +392,64 @@ func (gui *Gui) GetKeybindings() []*Binding { Modifier: gocui.ModNone, Handler: gui.handleMenuClose, }, { - ViewName: "staging", - Key: gocui.KeyEsc, - Modifier: gocui.ModNone, - Handler: gui.handleStagingEscape, + ViewName: "staging", + Key: gocui.KeyEsc, + Modifier: gocui.ModNone, + Handler: gui.handleStagingEscape, + KeyReadable: "esc", + Description: gui.Tr.SLocalize("EscapeStaging"), }, { ViewName: "staging", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, - Handler: gui.handleStagingKeyUp, + Handler: gui.handleStagingPrevLine, }, { ViewName: "staging", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, - Handler: gui.handleStagingKeyDown, + Handler: gui.handleStagingNextLine, }, { ViewName: "staging", - Key: gocui.KeySpace, + Key: 'k', + Modifier: gocui.ModNone, + Handler: gui.handleStagingPrevLine, + }, { + ViewName: "staging", + Key: 'j', + Modifier: gocui.ModNone, + Handler: gui.handleStagingNextLine, + }, { + ViewName: "staging", + Key: gocui.KeyArrowLeft, + Modifier: gocui.ModNone, + Handler: gui.handleStagingPrevHunk, + }, { + ViewName: "staging", + Key: gocui.KeyArrowRight, + Modifier: gocui.ModNone, + Handler: gui.handleStagingNextHunk, + }, { + ViewName: "staging", + Key: 'h', + Modifier: gocui.ModNone, + Handler: gui.handleStagingPrevHunk, + }, { + ViewName: "staging", + Key: 'l', Modifier: gocui.ModNone, - Handler: gui.handleStageLine, + Handler: gui.handleStagingNextHunk, + }, { + ViewName: "staging", + Key: gocui.KeySpace, + Modifier: gocui.ModNone, + Handler: gui.handleStageLine, + Description: gui.Tr.SLocalize("StageLine"), + }, { + ViewName: "staging", + Key: 'a', + Modifier: gocui.ModNone, + Handler: gui.handleStageHunk, + Description: gui.Tr.SLocalize("StageHunk"), }, } 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 } diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index e6970d92f..525b05f97 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -259,7 +259,6 @@ func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error { output := string(bom.Clean([]byte(s))) output = utils.NormalizeLinefeeds(output) fmt.Fprint(v, output) - v.Wrap = true return nil }) return nil |