diff options
author | Mark Kopenga <mkopenga@gmail.com> | 2018-08-14 11:05:26 +0200 |
---|---|---|
committer | Mark Kopenga <mkopenga@gmail.com> | 2018-08-14 11:05:26 +0200 |
commit | dfafb988713a79664139d15ad471736a5a4b1b90 (patch) | |
tree | ebde4bd5c64ce8e1fe5e8670657c708984b0e8ff /pkg/gui/merge_panel.go | |
parent | f2dfcb6e12d78f3e7b890d5bd43be7b032e1df88 (diff) |
tried to update to latest master
Diffstat (limited to 'pkg/gui/merge_panel.go')
-rw-r--r-- | pkg/gui/merge_panel.go | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/pkg/gui/merge_panel.go b/pkg/gui/merge_panel.go new file mode 100644 index 000000000..81e37f593 --- /dev/null +++ b/pkg/gui/merge_panel.go @@ -0,0 +1,260 @@ +// though this panel is called the merge panel, it's really going to use the main panel. This may change in the future + +package gui + +import ( + "bufio" + "bytes" + "io/ioutil" + "math" + "os" + "strings" + + "github.com/fatih/color" + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +func (gui *Gui) findConflicts(content string) ([]commands.Conflict, error) { + conflicts := make([]commands.Conflict, 0) + var newConflict commands.Conflict + for i, line := range utils.SplitLines(content) { + if line == "<<<<<<< HEAD" || line == "<<<<<<< MERGE_HEAD" || line == "<<<<<<< Updated upstream" { + newConflict = commands.Conflict{Start: i} + } else if line == "=======" { + newConflict.Middle = i + } else if strings.HasPrefix(line, ">>>>>>> ") { + newConflict.End = i + conflicts = append(conflicts, newConflict) + } + } + return conflicts, nil +} + +func (gui *Gui) shiftConflict(conflicts []commands.Conflict) (commands.Conflict, []commands.Conflict) { + return conflicts[0], conflicts[1:] +} + +func (gui *Gui) shouldHighlightLine(index int, conflict commands.Conflict, top bool) bool { + return (index >= conflict.Start && index <= conflict.Middle && top) || (index >= conflict.Middle && index <= conflict.End && !top) +} + +func (gui *Gui) coloredConflictFile(content string, conflicts []commands.Conflict, conflictIndex int, conflictTop, hasFocus bool) (string, error) { + if len(conflicts) == 0 { + return content, nil + } + conflict, remainingConflicts := gui.shiftConflict(conflicts) + var outputBuffer bytes.Buffer + for i, line := range utils.SplitLines(content) { + colourAttr := color.FgWhite + if i == conflict.Start || i == conflict.Middle || i == conflict.End { + colourAttr = color.FgRed + } + colour := color.New(colourAttr) + if hasFocus && conflictIndex < len(conflicts) && conflicts[conflictIndex] == conflict && gui.shouldHighlightLine(i, conflict, conflictTop) { + colour.Add(color.Bold) + } + if i == conflict.End && len(remainingConflicts) > 0 { + conflict, remainingConflicts = gui.shiftConflict(remainingConflicts) + } + outputBuffer.WriteString(utils.ColoredStringDirect(line, colour) + "\n") + } + return outputBuffer.String(), nil +} + +func (gui *Gui) handleSelectTop(g *gocui.Gui, v *gocui.View) error { + gui.State.ConflictTop = true + return gui.refreshMergePanel(g) +} + +func (gui *Gui) handleSelectBottom(g *gocui.Gui, v *gocui.View) error { + gui.State.ConflictTop = false + return gui.refreshMergePanel(g) +} + +func (gui *Gui) handleSelectNextConflict(g *gocui.Gui, v *gocui.View) error { + if gui.State.ConflictIndex >= len(gui.State.Conflicts)-1 { + return nil + } + gui.State.ConflictIndex++ + return gui.refreshMergePanel(g) +} + +func (gui *Gui) handleSelectPrevConflict(g *gocui.Gui, v *gocui.View) error { + if gui.State.ConflictIndex <= 0 { + return nil + } + gui.State.ConflictIndex-- + return gui.refreshMergePanel(g) +} + +func (gui *Gui) isIndexToDelete(i int, conflict commands.Conflict, pick string) bool { + return i == conflict.Middle || + i == conflict.Start || + i == conflict.End || + pick != "both" && + (pick == "bottom" && i > conflict.Start && i < conflict.Middle) || + (pick == "top" && i > conflict.Middle && i < conflict.End) +} + +func (gui *Gui) resolveConflict(g *gocui.Gui, conflict commands.Conflict, pick string) error { + gitFile, err := gui.getSelectedFile(g) + if err != nil { + return err + } + file, err := os.Open(gitFile.Name) + if err != nil { + return err + } + defer file.Close() + + reader := bufio.NewReader(file) + output := "" + for i := 0; true; i++ { + line, err := reader.ReadString('\n') + if err != nil { + break + } + if !gui.isIndexToDelete(i, conflict, pick) { + output += line + } + } + gui.Log.Info(output) + return ioutil.WriteFile(gitFile.Name, []byte(output), 0644) +} + +func (gui *Gui) pushFileSnapshot(g *gocui.Gui) error { + gitFile, err := gui.getSelectedFile(g) + if err != nil { + return err + } + content, err := gui.GitCommand.CatFile(gitFile.Name) + if err != nil { + return err + } + gui.State.EditHistory.Push(content) + return nil +} + +func (gui *Gui) handlePopFileSnapshot(g *gocui.Gui, v *gocui.View) error { + if gui.State.EditHistory.Len() == 0 { + return nil + } + prevContent := gui.State.EditHistory.Pop().(string) + gitFile, err := gui.getSelectedFile(g) + if err != nil { + return err + } + ioutil.WriteFile(gitFile.Name, []byte(prevContent), 0644) + return gui.refreshMergePanel(g) +} + +func (gui *Gui) handlePickHunk(g *gocui.Gui, v *gocui.View) error { + conflict := gui.State.Conflicts[gui.State.ConflictIndex] + gui.pushFileSnapshot(g) + pick := "bottom" + if gui.State.ConflictTop { + pick = "top" + } + err := gui.resolveConflict(g, conflict, pick) + if err != nil { + panic(err) + } + gui.refreshMergePanel(g) + return nil +} + +func (gui *Gui) handlePickBothHunks(g *gocui.Gui, v *gocui.View) error { + conflict := gui.State.Conflicts[gui.State.ConflictIndex] + gui.pushFileSnapshot(g) + err := gui.resolveConflict(g, conflict, "both") + if err != nil { + panic(err) + } + return gui.refreshMergePanel(g) +} + +func (gui *Gui) refreshMergePanel(g *gocui.Gui) error { + cat, err := gui.catSelectedFile(g) + if err != nil { + return err + } + gui.State.Conflicts, err = gui.findConflicts(cat) + if err != nil { + return err + } + + if len(gui.State.Conflicts) == 0 { + return gui.handleCompleteMerge(g) + } else if gui.State.ConflictIndex > len(gui.State.Conflicts)-1 { + gui.State.ConflictIndex = len(gui.State.Conflicts) - 1 + } + hasFocus := gui.currentViewName(g) == "main" + if hasFocus { + gui.renderMergeOptions(g) + } + content, err := gui.coloredConflictFile(cat, gui.State.Conflicts, gui.State.ConflictIndex, gui.State.ConflictTop, hasFocus) + if err != nil { + return err + } + if err := gui.scrollToConflict(g); err != nil { + return err + } + return gui.renderString(g, "main", content) +} + +func (gui *Gui) scrollToConflict(g *gocui.Gui) error { + mainView, err := g.View("main") + if err != nil { + return err + } + if len(gui.State.Conflicts) == 0 { + return nil + } + conflict := gui.State.Conflicts[gui.State.ConflictIndex] + ox, _ := mainView.Origin() + _, height := mainView.Size() + conflictMiddle := (conflict.End + conflict.Start) / 2 + newOriginY := int(math.Max(0, float64(conflictMiddle-(height/2)))) + return mainView.SetOrigin(ox, newOriginY) +} + +func (gui *Gui) switchToMerging(g *gocui.Gui) error { + gui.State.ConflictIndex = 0 + gui.State.ConflictTop = true + _, err := g.SetCurrentView("main") + if err != nil { + return err + } + return gui.refreshMergePanel(g) +} + +func (gui *Gui) renderMergeOptions(g *gocui.Gui) error { + return gui.renderOptionsMap(g, map[string]string{ + "↑ ↓": "select hunk", + "← →": "navigate conflicts", + "space": "pick hunk", + "b": "pick both hunks", + "z": "undo", + }) +} + +func (gui *Gui) handleEscapeMerge(g *gocui.Gui, v *gocui.View) error { + filesView, err := g.View("files") + if err != nil { + return err + } + gui.refreshFiles(g) + return gui.switchFocus(g, v, filesView) +} + +func (gui *Gui) handleCompleteMerge(g *gocui.Gui) error { + filesView, err := g.View("files") + if err != nil { + return err + } + gui.stageSelectedFile(g) + gui.refreshFiles(g) + return gui.switchFocus(g, nil, filesView) +} |