1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-12 11:15:00 +02:00
lazygit/pkg/gui/merge_panel.go

322 lines
8.2 KiB
Go
Raw Normal View History

2018-08-14 11:05:26 +02:00
// 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 (
"fmt"
2018-08-14 11:05:26 +02:00
"io/ioutil"
"math"
"github.com/jesseduffield/gocui"
2021-04-01 15:48:13 +02:00
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
2022-01-28 11:44:36 +02:00
"github.com/jesseduffield/lazygit/pkg/gui/types"
2018-08-14 11:05:26 +02:00
)
func (gui *Gui) handleSelectPrevConflictHunk() error {
2021-04-01 15:48:13 +02:00
return gui.withMergeConflictLock(func() error {
gui.takeOverMergeConflictScrolling()
gui.State.Panels.Merging.SelectPrevConflictHunk()
2022-01-25 16:20:19 +02:00
return gui.renderConflictsWithFocus()
2021-04-01 15:48:13 +02:00
})
2018-08-14 11:05:26 +02:00
}
func (gui *Gui) handleSelectNextConflictHunk() error {
2021-04-01 15:48:13 +02:00
return gui.withMergeConflictLock(func() error {
gui.takeOverMergeConflictScrolling()
gui.State.Panels.Merging.SelectNextConflictHunk()
2022-01-25 16:20:19 +02:00
return gui.renderConflictsWithFocus()
2021-04-01 15:48:13 +02:00
})
2018-08-14 11:05:26 +02:00
}
2021-04-01 15:48:13 +02:00
func (gui *Gui) handleSelectNextConflict() error {
return gui.withMergeConflictLock(func() error {
gui.takeOverMergeConflictScrolling()
2021-04-18 10:07:10 +02:00
gui.State.Panels.Merging.SelectNextConflict()
2022-01-25 16:20:19 +02:00
return gui.renderConflictsWithFocus()
2021-04-01 15:48:13 +02:00
})
2018-08-14 11:05:26 +02:00
}
2021-04-01 15:48:13 +02:00
func (gui *Gui) handleSelectPrevConflict() error {
return gui.withMergeConflictLock(func() error {
gui.takeOverMergeConflictScrolling()
2021-04-18 10:07:10 +02:00
gui.State.Panels.Merging.SelectPrevConflict()
2022-01-25 16:20:19 +02:00
return gui.renderConflictsWithFocus()
2021-04-01 15:48:13 +02:00
})
2018-08-14 11:05:26 +02:00
}
2022-01-25 16:20:19 +02:00
func (gui *Gui) handleMergeConflictUndo() error {
state := gui.State.Panels.Merging
2018-08-14 11:05:26 +02:00
2022-01-25 16:20:19 +02:00
ok := state.Undo()
2021-04-18 10:07:10 +02:00
if !ok {
2018-08-14 11:05:26 +02:00
return nil
}
2021-04-18 10:07:10 +02:00
gui.c.LogAction("Restoring file to previous state")
gui.LogCommand("Undoing last conflict resolution", false)
2022-03-19 00:38:49 +02:00
if err := ioutil.WriteFile(state.GetPath(), []byte(state.GetContent()), 0o644); err != nil {
2020-03-09 02:34:10 +02:00
return err
}
2022-01-25 16:20:19 +02:00
return gui.renderConflictsWithFocus()
2018-08-14 11:05:26 +02:00
}
2021-04-01 15:48:13 +02:00
func (gui *Gui) handlePickHunk() error {
return gui.withMergeConflictLock(func() error {
2021-04-18 10:07:10 +02:00
ok, err := gui.resolveConflict(gui.State.Panels.Merging.Selection())
if err != nil {
2021-04-01 15:48:13 +02:00
return err
}
2020-03-09 02:34:10 +02:00
2021-04-18 10:07:10 +02:00
if !ok {
return nil
2021-04-01 15:48:13 +02:00
}
2018-12-08 07:54:54 +02:00
2022-01-25 16:20:19 +02:00
if gui.State.Panels.Merging.AllConflictsResolved() {
return gui.onLastConflictResolved()
2021-04-01 15:48:13 +02:00
}
2022-01-25 16:20:19 +02:00
return gui.renderConflictsWithFocus()
2021-04-01 15:48:13 +02:00
})
}
2021-08-24 14:38:34 +02:00
func (gui *Gui) handlePickAllHunks() error {
2021-04-01 15:48:13 +02:00
return gui.withMergeConflictLock(func() error {
2021-08-24 14:33:19 +02:00
ok, err := gui.resolveConflict(mergeconflicts.ALL)
2021-04-18 10:07:10 +02:00
if err != nil {
2018-12-08 07:54:54 +02:00
return err
}
2021-04-18 10:07:10 +02:00
if !ok {
return nil
2021-04-01 15:48:13 +02:00
}
2021-04-18 10:07:10 +02:00
2022-01-25 16:20:19 +02:00
if gui.State.Panels.Merging.AllConflictsResolved() {
return gui.onLastConflictResolved()
}
return gui.renderConflictsWithFocus()
2021-04-01 15:48:13 +02:00
})
2018-08-14 11:05:26 +02:00
}
2021-04-18 10:07:10 +02:00
func (gui *Gui) resolveConflict(selection mergeconflicts.Selection) (bool, error) {
2022-01-25 16:20:19 +02:00
gui.takeOverMergeConflictScrolling()
2021-04-18 10:07:10 +02:00
2022-01-25 16:20:19 +02:00
state := gui.State.Panels.Merging
ok, content, err := state.ContentAfterConflictResolve(selection)
2018-08-14 11:05:26 +02:00
if err != nil {
2021-04-18 10:07:10 +02:00
return false, err
2018-08-14 11:05:26 +02:00
}
2021-04-01 15:48:13 +02:00
2021-04-18 10:07:10 +02:00
if !ok {
return false, nil
}
2021-04-10 09:31:23 +02:00
var logStr string
switch selection {
case mergeconflicts.TOP:
logStr = "Picking top hunk"
case mergeconflicts.MIDDLE:
logStr = "Picking middle hunk"
2021-04-10 09:31:23 +02:00
case mergeconflicts.BOTTOM:
logStr = "Picking bottom hunk"
2021-08-24 14:33:19 +02:00
case mergeconflicts.ALL:
logStr = "Picking all hunks"
2021-04-10 09:31:23 +02:00
}
gui.c.LogAction("Resolve merge conflict")
gui.LogCommand(logStr, false)
2022-01-25 16:20:19 +02:00
state.PushContent(content)
2022-03-19 00:38:49 +02:00
return true, ioutil.WriteFile(state.GetPath(), []byte(content), 0o644)
2022-01-15 04:29:28 +02:00
}
2022-01-25 16:20:19 +02:00
// precondition: we actually have conflicts to render
func (gui *Gui) renderConflicts(hasFocus bool) error {
state := gui.State.Panels.Merging.State
content := mergeconflicts.ColoredConflictFile(state, hasFocus)
2022-01-15 04:29:28 +02:00
if !gui.State.Panels.Merging.UserVerticalScrolling {
2022-01-16 14:09:01 +02:00
// TODO: find a way to not have to do this OnUIThread thing. Why doesn't it work
// without it given that we're calling the 'no scroll' variant below?
gui.OnUIThread(func() error {
2022-01-27 11:10:25 +02:00
gui.State.Panels.Merging.Lock()
defer gui.State.Panels.Merging.Unlock()
if !state.Active() {
return nil
}
2022-01-16 14:09:01 +02:00
gui.centerYPos(gui.Views.Main, state.GetConflictMiddle())
return nil
})
2020-03-29 02:49:37 +02:00
}
2022-01-25 16:20:19 +02:00
return gui.refreshMainViews(refreshMainOpts{
2020-08-18 14:02:35 +02:00
main: &viewUpdateOpts{
2022-02-05 07:56:36 +02:00
title: gui.c.Tr.MergeConflictsTitle,
task: NewRenderStringWithoutScrollTask(content),
context: gui.State.Contexts.Merging,
noWrap: true,
2020-08-18 14:02:35 +02:00
},
})
2018-08-14 11:05:26 +02:00
}
2022-01-25 16:20:19 +02:00
func (gui *Gui) renderConflictsWithFocus() error {
return gui.renderConflicts(true)
}
2022-01-27 11:10:25 +02:00
func (gui *Gui) renderConflictsWithLock(hasFocus bool) error {
return gui.withMergeConflictLock(func() error {
return gui.renderConflicts(hasFocus)
})
}
2021-04-18 10:07:10 +02:00
func (gui *Gui) centerYPos(view *gocui.View, y int) {
ox, _ := view.Origin()
_, height := view.Size()
newOriginY := int(math.Max(0, float64(y-(height/2))))
2022-01-15 03:04:00 +02:00
_ = view.SetOrigin(ox, newOriginY)
2018-08-14 11:05:26 +02:00
}
func (gui *Gui) getMergingOptions() map[string]string {
keybindingConfig := gui.c.UserConfig.Keybinding
2020-10-03 06:54:55 +02:00
return map[string]string{
fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcSelectHunk,
fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevBlock), gui.getKeyDisplay(keybindingConfig.Universal.NextBlock)): gui.c.Tr.LcNavigateConflicts,
gui.getKeyDisplay(keybindingConfig.Universal.Select): gui.c.Tr.LcPickHunk,
gui.getKeyDisplay(keybindingConfig.Main.PickBothHunks): gui.c.Tr.LcPickAllHunks,
gui.getKeyDisplay(keybindingConfig.Universal.Undo): gui.c.Tr.LcUndo,
}
2018-08-14 11:05:26 +02:00
}
2020-08-15 09:23:16 +02:00
func (gui *Gui) handleEscapeMerge() error {
if err := gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
2019-02-16 12:30:29 +02:00
return err
}
2022-01-15 04:29:28 +02:00
return gui.escapeMerge()
2018-08-14 11:05:26 +02:00
}
2022-01-25 16:20:19 +02:00
func (gui *Gui) onLastConflictResolved() error {
// as part of refreshing files, we handle the situation where a file has had
// its merge conflicts resolved.
return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
2022-01-25 16:20:19 +02:00
}
func (gui *Gui) resetMergeState() {
gui.takeOverMergeConflictScrolling()
gui.State.Panels.Merging.Reset()
}
2022-01-15 04:29:28 +02:00
2022-01-25 16:20:19 +02:00
func (gui *Gui) setMergeState(path string) (bool, error) {
content, err := gui.git.File.Cat(path)
2022-01-25 16:20:19 +02:00
if err != nil {
return false, err
2018-12-02 18:34:15 +02:00
}
2022-01-15 04:29:28 +02:00
2022-01-25 16:20:19 +02:00
gui.State.Panels.Merging.SetContent(content, path)
return !gui.State.Panels.Merging.NoConflicts(), nil
2022-01-15 04:29:28 +02:00
}
2022-01-27 11:10:25 +02:00
func (gui *Gui) setMergeStateWithLock(path string) (bool, error) {
gui.State.Panels.Merging.Lock()
defer gui.State.Panels.Merging.Unlock()
return gui.setMergeState(path)
}
func (gui *Gui) resetMergeStateWithLock() {
gui.State.Panels.Merging.Lock()
defer gui.State.Panels.Merging.Unlock()
2022-01-25 16:20:19 +02:00
gui.resetMergeState()
2022-01-27 11:10:25 +02:00
}
2022-01-15 04:29:28 +02:00
2022-01-27 11:10:25 +02:00
func (gui *Gui) escapeMerge() error {
gui.resetMergeState()
2022-01-25 16:20:19 +02:00
2022-01-27 11:10:25 +02:00
// doing this in separate UI thread so that we're not still holding the lock by the time refresh the file
gui.OnUIThread(func() error {
2022-01-15 04:29:28 +02:00
return gui.pushContext(gui.State.Contexts.Files)
2022-01-27 11:10:25 +02:00
})
2022-01-15 04:29:28 +02:00
return nil
2018-08-14 11:05:26 +02:00
}
2019-03-03 07:11:20 +02:00
2022-01-25 16:20:19 +02:00
func (gui *Gui) renderingConflicts() bool {
2021-04-04 17:10:23 +02:00
currentView := gui.g.CurrentView()
if currentView != gui.Views.Main && currentView != gui.Views.Files {
return false
}
2022-01-25 16:20:19 +02:00
return gui.State.Panels.Merging.Active()
}
2021-04-01 15:48:13 +02:00
func (gui *Gui) withMergeConflictLock(f func() error) error {
2021-04-18 10:07:10 +02:00
gui.State.Panels.Merging.Lock()
defer gui.State.Panels.Merging.Unlock()
2021-04-01 15:48:13 +02:00
return f()
}
func (gui *Gui) takeOverMergeConflictScrolling() {
2021-11-02 11:35:53 +02:00
gui.State.Panels.Merging.UserVerticalScrolling = false
2021-04-01 15:48:13 +02:00
}
2022-01-27 11:10:25 +02:00
func (gui *Gui) setConflictsAndRender(path string, hasFocus bool) (bool, error) {
hasConflicts, err := gui.setMergeState(path)
if err != nil {
return false, err
}
if hasConflicts {
return true, gui.renderConflicts(hasFocus)
}
return false, nil
}
func (gui *Gui) setConflictsAndRenderWithLock(path string, hasFocus bool) (bool, error) {
gui.State.Panels.Merging.Lock()
defer gui.State.Panels.Merging.Unlock()
return gui.setConflictsAndRender(path, hasFocus)
}
func (gui *Gui) switchToMerge(path string) error {
gui.takeOverMergeConflictScrolling()
if gui.State.Panels.Merging.GetPath() != path {
hasConflicts, err := gui.setMergeStateWithLock(path)
if err != nil {
return err
}
if !hasConflicts {
return nil
}
}
return gui.c.PushContext(gui.State.Contexts.Merging)
}
func (gui *Gui) handleMergeConflictEditFileAtLine() error {
file := gui.getSelectedFile()
if file == nil {
return nil
}
lineNumber := gui.State.Panels.Merging.GetSelectedLine()
return gui.helpers.Files.EditFileAtLine(file.GetPath(), lineNumber)
}
func (gui *Gui) handleMergeConflictOpenFileAtLine() error {
file := gui.getSelectedFile()
if file == nil {
return nil
}
lineNumber := gui.State.Panels.Merging.GetSelectedLine()
return gui.helpers.Files.OpenFileAtLine(file.GetPath(), lineNumber)
}