diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2019-11-05 14:21:19 +1100 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2019-11-05 19:22:01 +1100 |
commit | 6d5d054c30050bc66c9c3e8697982c6c801a2a62 (patch) | |
tree | 58e08dbfadbfb0af55799e810b1f684e8e1d5527 /pkg/gui/line_by_line_panel.go | |
parent | 234415537943c090a3f9c3fa3ad7889b6998f9d2 (diff) |
support line by line additions in staging and patch building contexts
Diffstat (limited to 'pkg/gui/line_by_line_panel.go')
-rw-r--r-- | pkg/gui/line_by_line_panel.go | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/pkg/gui/line_by_line_panel.go b/pkg/gui/line_by_line_panel.go new file mode 100644 index 000000000..f36441823 --- /dev/null +++ b/pkg/gui/line_by_line_panel.go @@ -0,0 +1,254 @@ +package gui + +import ( + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands" +) + +// Currently there are two 'pseudo-panels' that make use of this 'pseudo-panel'. +// One is the staging panel where we stage files line-by-line, the other is the +// patch building panel where we add lines of an old commit's file to a patch. +// This file contains the logic around selecting lines and displaying the diffs +// staging_panel.go and patch_building_panel.go have functions specific to their +// use cases + +// these represent what select mode we're in +const ( + LINE = iota + RANGE + HUNK +) + +// returns whether the patch is empty so caller can escape if necessary +// both diffs should be non-coloured because we'll parse them and colour them here +func (gui *Gui) refreshLineByLinePanel(diff string, secondaryDiff string, secondaryFocused bool) (bool, error) { + state := gui.State.Panels.LineByLine + + patchParser, err := commands.NewPatchParser(gui.Log, diff) + if err != nil { + return false, nil + } + + if len(patchParser.StageableLines) == 0 { + return true, nil + } + + var selectedLineIdx int + var firstLineIdx int + var lastLineIdx int + selectMode := LINE + if state != nil { + if state.SelectMode == HUNK { + // this is tricky: we need to find out which hunk we just staged based on our old `state.PatchParser` (as opposed to the new `patchParser`) + // we do this by getting the first line index of the original hunk, then + // finding the next stageable line, then getting its containing hunk + // in the new diff + selectMode = HUNK + prevNewHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, 0) + selectedLineIdx = patchParser.GetNextStageableLineIndex(prevNewHunk.FirstLineIdx) + newHunk := patchParser.GetHunkContainingLine(selectedLineIdx, 0) + firstLineIdx, lastLineIdx = newHunk.FirstLineIdx, newHunk.LastLineIdx + } else { + selectedLineIdx = patchParser.GetNextStageableLineIndex(state.SelectedLineIdx) + firstLineIdx, lastLineIdx = selectedLineIdx, selectedLineIdx + } + } else { + selectedLineIdx = patchParser.StageableLines[0] + firstLineIdx, lastLineIdx = selectedLineIdx, selectedLineIdx + } + + gui.State.Panels.LineByLine = &lineByLinePanelState{ + PatchParser: patchParser, + SelectedLineIdx: selectedLineIdx, + SelectMode: selectMode, + FirstLineIdx: firstLineIdx, + LastLineIdx: lastLineIdx, + Diff: diff, + SecondaryFocused: secondaryFocused, + } + + if err := gui.refreshMainView(); err != nil { + return false, err + } + + if err := gui.focusSelection(selectMode == HUNK); err != nil { + return false, err + } + + secondaryView := gui.getSecondaryView() + secondaryView.Highlight = true + secondaryView.Wrap = false + + secondaryPatchParser, err := commands.NewPatchParser(gui.Log, secondaryDiff) + if err != nil { + return false, nil + } + + gui.g.Update(func(*gocui.Gui) error { + return gui.setViewContent(gui.g, gui.getSecondaryView(), secondaryPatchParser.Render(-1, -1, nil)) + }) + + return false, nil +} + +func (gui *Gui) handleSelectPrevLine(g *gocui.Gui, v *gocui.View) error { + return gui.handleCycleLine(-1) +} + +func (gui *Gui) handleSelectNextLine(g *gocui.Gui, v *gocui.View) error { + return gui.handleCycleLine(1) +} + +func (gui *Gui) handleSelectPrevHunk(g *gocui.Gui, v *gocui.View) error { + return gui.handleCycleHunk(-1) +} + +func (gui *Gui) handleSelectNextHunk(g *gocui.Gui, v *gocui.View) error { + return gui.handleCycleHunk(1) +} + +func (gui *Gui) handleCycleHunk(change int) error { + state := gui.State.Panels.LineByLine + newHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, change) + state.SelectedLineIdx = state.PatchParser.GetNextStageableLineIndex(newHunk.FirstLineIdx) + if state.SelectMode == HUNK { + state.FirstLineIdx, state.LastLineIdx = newHunk.FirstLineIdx, newHunk.LastLineIdx + } else { + state.FirstLineIdx, state.LastLineIdx = state.SelectedLineIdx, state.SelectedLineIdx + } + + if err := gui.refreshMainView(); err != nil { + return err + } + + return gui.focusSelection(true) +} + +func (gui *Gui) handleCycleLine(change int) error { + state := gui.State.Panels.LineByLine + + if state.SelectMode == HUNK { + return gui.handleCycleHunk(change) + } + + newSelectedLineIdx := state.SelectedLineIdx + change + if newSelectedLineIdx < 0 { + newSelectedLineIdx = 0 + } else if newSelectedLineIdx > len(state.PatchParser.PatchLines)-1 { + newSelectedLineIdx = len(state.PatchParser.PatchLines) - 1 + } + + state.SelectedLineIdx = newSelectedLineIdx + + if state.SelectMode == RANGE { + if state.SelectedLineIdx < state.FirstLineIdx { + state.FirstLineIdx = state.SelectedLineIdx + } else { + state.LastLineIdx = state.SelectedLineIdx + } + } else { + state.LastLineIdx = state.SelectedLineIdx + state.FirstLineIdx = state.SelectedLineIdx + } + + if err := gui.refreshMainView(); err != nil { + return err + } + + return gui.focusSelection(false) +} + +func (gui *Gui) refreshMainView() error { + state := gui.State.Panels.LineByLine + + var includedLineIndices []int + // I'd prefer not to have knowledge of contexts using this file but I'm not sure + // how to get around this + if gui.State.Contexts["main"] == "patch-building" { + filename := gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLine].Name + includedLineIndices = gui.GitCommand.PatchManager.GetFileIncLineIndices(filename) + } + colorDiff := state.PatchParser.Render(state.FirstLineIdx, state.LastLineIdx, includedLineIndices) + + mainView := gui.getMainView() + mainView.Highlight = true + mainView.Wrap = false + + gui.g.Update(func(*gocui.Gui) error { + return gui.setViewContent(gui.g, gui.getMainView(), colorDiff) + }) + + return nil +} + +// focusSelection works out the best focus for the staging panel given the +// selected line and size of the hunk +func (gui *Gui) focusSelection(includeCurrentHunk bool) error { + stagingView := gui.getMainView() + state := gui.State.Panels.LineByLine + + _, viewHeight := stagingView.Size() + bufferHeight := viewHeight - 1 + _, origin := stagingView.Origin() + + firstLineIdx := state.SelectedLineIdx + lastLineIdx := state.SelectedLineIdx + + if includeCurrentHunk { + hunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, 0) + firstLineIdx = hunk.FirstLineIdx + lastLineIdx = hunk.LastLineIdx + } + + margin := 0 // we may want to have a margin in place to show context but right now I'm thinking we keep this at zero + + var newOrigin int + if firstLineIdx-origin < margin { + newOrigin = firstLineIdx - margin + } else if lastLineIdx-origin > bufferHeight-margin { + newOrigin = lastLineIdx - bufferHeight + margin + } else { + newOrigin = origin + } + + gui.g.Update(func(*gocui.Gui) error { + if err := stagingView.SetOrigin(0, newOrigin); err != nil { + return err + } + + return stagingView.SetCursor(0, state.SelectedLineIdx-newOrigin) + }) + + return nil +} + +func (gui *Gui) handleToggleSelectRange(g *gocui.Gui, v *gocui.View) error { + state := gui.State.Panels.LineByLine + if state.SelectMode == RANGE { + state.SelectMode = LINE + } else { + state.SelectMode = RANGE + } + state.FirstLineIdx, state.LastLineIdx = state.SelectedLineIdx, state.SelectedLineIdx + + return gui.refreshMainView() +} + +func (gui *Gui) handleToggleSelectHunk(g *gocui.Gui, v *gocui.View) error { + state := gui.State.Panels.LineByLine + + if state.SelectMode == HUNK { + state.SelectMode = LINE + state.FirstLineIdx, state.LastLineIdx = state.SelectedLineIdx, state.SelectedLineIdx + } else { + state.SelectMode = HUNK + selectedHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, 0) + state.FirstLineIdx, state.LastLineIdx = selectedHunk.FirstLineIdx, selectedHunk.LastLineIdx + } + + if err := gui.refreshMainView(); err != nil { + return err + } + + return gui.focusSelection(state.SelectMode == HUNK) +} |