summaryrefslogtreecommitdiffstats
path: root/pkg/gui/line_by_line_panel.go
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2019-11-05 14:21:19 +1100
committerJesse Duffield <jessedduffield@gmail.com>2019-11-05 19:22:01 +1100
commit6d5d054c30050bc66c9c3e8697982c6c801a2a62 (patch)
tree58e08dbfadbfb0af55799e810b1f684e8e1d5527 /pkg/gui/line_by_line_panel.go
parent234415537943c090a3f9c3fa3ad7889b6998f9d2 (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.go254
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)
+}