diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2018-12-02 19:57:01 +1100 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2018-12-04 22:11:48 +1100 |
commit | 658e5a9faf8409c62f11f3ad6d636d0255e450f4 (patch) | |
tree | 5914a060bfdaaf28064326e66f9a207e2dd8dd0c /pkg/gui | |
parent | 99824c8a7b840cc2fa8c06f248acfaf01a3f964b (diff) |
initial support for staging individual lines
Diffstat (limited to 'pkg/gui')
-rw-r--r-- | pkg/gui/files_panel.go | 22 | ||||
-rw-r--r-- | pkg/gui/gui.go | 22 | ||||
-rw-r--r-- | pkg/gui/keybindings.go | 27 | ||||
-rw-r--r-- | pkg/gui/staging_panel.go | 163 | ||||
-rw-r--r-- | pkg/gui/view_helpers.go | 12 |
5 files changed, 244 insertions, 2 deletions
diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index a00bd2843..3404903ef 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -45,6 +45,25 @@ func (gui *Gui) stageSelectedFile(g *gocui.Gui) error { return gui.GitCommand.StageFile(file.Name) } +func (gui *Gui) handleSwitchToStagingPanel(g *gocui.Gui, v *gocui.View) error { + stagingView, err := g.View("staging") + if err != nil { + return err + } + file, err := gui.getSelectedFile(g) + if err != nil { + if err != gui.Errors.ErrNoFiles { + return err + } + return nil + } + if !file.Tracked || !file.HasUnstagedChanges { + return gui.createErrorPanel(g, gui.Tr.SLocalize("FileStagingRequirements")) + } + gui.switchFocus(g, v, stagingView) + return gui.refreshStagingPanel() +} + func (gui *Gui) handleFilePress(g *gocui.Gui, v *gocui.View) error { file, err := gui.getSelectedFile(g) if err != nil { @@ -188,12 +207,11 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error { if err := gui.renderfilesOptions(g, file); err != nil { return err } - var content string if file.HasMergeConflicts { return gui.refreshMergePanel(g) } - content = gui.GitCommand.Diff(file) + content := gui.GitCommand.Diff(file, false) return gui.renderString(g, "main", content) } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index ee8fb1165..67415c5ce 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -72,6 +72,13 @@ type Gui struct { statusManager *statusManager } +type stagingState struct { + StageableLines []int + HunkStarts []int + CurrentLineIndex int + Diff string +} + type guiState struct { Files []*commands.File Branches []*commands.Branch @@ -85,6 +92,7 @@ type guiState struct { EditHistory *stack.Stack Platform commands.Platform Updating bool + StagingState *stagingState } // NewGui builds a new gui handler @@ -208,6 +216,20 @@ func (gui *Gui) layout(g *gocui.Gui) error { v.FgColor = gocui.ColorWhite } + v, err = g.SetView("staging", leftSideWidth+panelSpacing, 0, width-1, optionsTop, gocui.LEFT) + if err != nil { + if err != gocui.ErrUnknownView { + 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 { + return err + } + } + if v, err := g.SetView("status", 0, 0, leftSideWidth, statusFilesBoundary, gocui.BOTTOM|gocui.RIGHT); err != nil { if err != gocui.ErrUnknownView { return err diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 1b656659d..2c47ab4f3 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -213,6 +213,13 @@ func (gui *Gui) GetKeybindings() []*Binding { Handler: gui.handleResetHard, Description: gui.Tr.SLocalize("resetHard"), }, { + ViewName: "files", + Key: gocui.KeyEnter, + Modifier: gocui.ModNone, + Handler: gui.handleSwitchToStagingPanel, + Description: gui.Tr.SLocalize("StageLines"), + KeyReadable: "enter", + }, { ViewName: "main", Key: gocui.KeyEsc, Modifier: gocui.ModNone, @@ -384,6 +391,26 @@ func (gui *Gui) GetKeybindings() []*Binding { Key: 'q', Modifier: gocui.ModNone, Handler: gui.handleMenuClose, + }, { + ViewName: "staging", + Key: gocui.KeyEsc, + Modifier: gocui.ModNone, + Handler: gui.handleStagingEscape, + }, { + ViewName: "staging", + Key: gocui.KeyArrowUp, + Modifier: gocui.ModNone, + Handler: gui.handleStagingKeyUp, + }, { + ViewName: "staging", + Key: gocui.KeyArrowDown, + Modifier: gocui.ModNone, + Handler: gui.handleStagingKeyDown, + }, { + ViewName: "staging", + Key: gocui.KeySpace, + Modifier: gocui.ModNone, + Handler: gui.handleStageLine, }, } diff --git a/pkg/gui/staging_panel.go b/pkg/gui/staging_panel.go new file mode 100644 index 000000000..be207c2fb --- /dev/null +++ b/pkg/gui/staging_panel.go @@ -0,0 +1,163 @@ +package gui + +import ( + "errors" + "io/ioutil" + + "github.com/davecgh/go-spew/spew" + + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/git" +) + +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 { + return err + } + return gui.handleStagingEscape(gui.g, nil) + } + + if !file.HasUnstagedChanges { + return gui.handleStagingEscape(gui.g, nil) + } + + // note for custom diffs, we'll need to send a flag here saying not to use the custom diff + 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) + } + + // parse the diff and store the line numbers of hunks and stageable lines + // TODO: maybe instantiate this at application start + p, err := git.NewPatchParser(gui.Log) + if err != nil { + return nil + } + hunkStarts, stageableLines, err := p.ParsePatch(diff) + if err != nil { + return nil + } + + var currentLineIndex int + if gui.State.StagingState != nil { + end := len(stageableLines) - 1 + if end < gui.State.StagingState.CurrentLineIndex { + currentLineIndex = end + } else { + currentLineIndex = gui.State.StagingState.CurrentLineIndex + } + } else { + currentLineIndex = 0 + } + + gui.State.StagingState = &stagingState{ + StageableLines: stageableLines, + HunkStarts: hunkStarts, + CurrentLineIndex: currentLineIndex, + Diff: diff, + } + + if len(stageableLines) == 0 { + return errors.New("No lines to stage") + } + + stagingView := gui.getStagingView(gui.g) + stagingView.SetCursor(0, stageableLines[currentLineIndex]) + stagingView.SetOrigin(0, 0) + return gui.renderString(gui.g, "staging", colorDiff) +} + +func (gui *Gui) handleStagingEscape(g *gocui.Gui, v *gocui.View) error { + if _, err := gui.g.SetViewOnBottom("staging"); err != nil { + return err + } + + 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 +} + +// 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) handleStagingKeyUp(g *gocui.Gui, v *gocui.View) error { + return gui.handleCycleLine(true) +} + +func (gui *Gui) handleStagingKeyDown(g *gocui.Gui, v *gocui.View) error { + return gui.handleCycleLine(false) +} + +func (gui *Gui) handleCycleLine(up bool) error { + state := gui.State.StagingState + lineNumbers := state.StageableLines + currentLine := lineNumbers[state.CurrentLineIndex] + var newIndex int + if up { + newIndex = prevIndex(lineNumbers, currentLine) + } else { + newIndex = nextIndex(lineNumbers, currentLine) + } + + state.CurrentLineIndex = newIndex + stagingView := gui.getStagingView(gui.g) + stagingView.SetCursor(0, lineNumbers[newIndex]) + stagingView.SetOrigin(0, 0) + return nil +} + +func (gui *Gui) handleStageLine(g *gocui.Gui, v *gocui.View) error { + state := gui.State.StagingState + p, err := git.NewPatchModifier(gui.Log) + if err != nil { + return err + } + + currentLine := state.StageableLines[state.CurrentLineIndex] + patch, err := p.ModifyPatch(state.Diff, currentLine) + if err != nil { + return err + } + + // for logging purposes + 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) + } + + gui.refreshStagingPanel() + gui.refreshFiles(gui.g) + return nil +} diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 6c3e5505c..e6970d92f 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -103,6 +103,9 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error { return gui.handleCommitSelect(g, v) case "stash": return gui.handleStashEntrySelect(g, v) + case "staging": + return nil + // return gui.handleStagingSelect(g, v) default: panic(gui.Tr.SLocalize("NoViewMachingNewLineFocusedSwitchStatement")) } @@ -153,6 +156,10 @@ func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error { if _, err := g.SetCurrentView(newView.Name()); err != nil { return err } + if _, err := g.SetViewOnTop(newView.Name()); err != nil { + return err + } + g.Cursor = newView.Editable return gui.newLineFocused(g, newView) @@ -293,6 +300,11 @@ func (gui *Gui) getBranchesView(g *gocui.Gui) *gocui.View { return v } +func (gui *Gui) getStagingView(g *gocui.Gui) *gocui.View { + v, _ := g.View("staging") + return v +} + func (gui *Gui) trimmedContent(v *gocui.View) string { return strings.TrimSpace(v.Buffer()) } |