diff --git a/pkg/cheatsheet/generate.go b/pkg/cheatsheet/generate.go
index 6d0007653..ec5c6db0e 100644
--- a/pkg/cheatsheet/generate.go
+++ b/pkg/cheatsheet/generate.go
@@ -142,7 +142,7 @@ func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*b
bindingsByHeader,
func(header header, hBindings []*types.Binding) headerWithBindings {
uniqBindings := lo.UniqBy(hBindings, func(binding *types.Binding) string {
- return binding.Description + keybindings.GetKeyDisplay(binding.Key)
+ return binding.Description + keybindings.LabelFromKey(binding.Key)
})
return headerWithBindings{
@@ -202,10 +202,10 @@ func formatBinding(binding *types.Binding) string {
if binding.Alternative != "" {
return fmt.Sprintf(
" %s: %s (%s)\n",
- keybindings.GetKeyDisplay(binding.Key),
+ keybindings.LabelFromKey(binding.Key),
binding.Description,
binding.Alternative,
)
}
- return fmt.Sprintf(" %s: %s\n", keybindings.GetKeyDisplay(binding.Key), binding.Description)
+ return fmt.Sprintf(" %s: %s\n", keybindings.LabelFromKey(binding.Key), binding.Description)
}
diff --git a/pkg/commands/oscommands/cmd_obj_runner_win.go b/pkg/commands/oscommands/cmd_obj_runner_win.go
index 9a64dfa77..e4098ddc9 100644
--- a/pkg/commands/oscommands/cmd_obj_runner_win.go
+++ b/pkg/commands/oscommands/cmd_obj_runner_win.go
@@ -7,7 +7,8 @@ import (
"bytes"
"io"
"os/exec"
- "sync"
+
+ "github.com/sasha-s/go-deadlock"
)
type Buffer struct {
diff --git a/pkg/gui/app_status_manager.go b/pkg/gui/app_status_manager.go
index 2c5a8b1ec..21cf353a4 100644
--- a/pkg/gui/app_status_manager.go
+++ b/pkg/gui/app_status_manager.go
@@ -94,7 +94,7 @@ func (gui *Gui) renderAppStatus() {
defer ticker.Stop()
for range ticker.C {
appStatus := gui.statusManager.getStatusString()
- gui.OnUIThread(func() error {
+ gui.c.OnUIThread(func() error {
return gui.renderString(gui.Views.AppStatus, appStatus)
})
@@ -117,7 +117,7 @@ func (gui *Gui) withWaitingStatus(message string, f func() error) error {
gui.renderAppStatus()
if err := f(); err != nil {
- gui.OnUIThread(func() error {
+ gui.c.OnUIThread(func() error {
return gui.c.Error(err)
})
}
diff --git a/pkg/gui/command_log_panel.go b/pkg/gui/command_log_panel.go
index 33f410043..dbee7febb 100644
--- a/pkg/gui/command_log_panel.go
+++ b/pkg/gui/command_log_panel.go
@@ -7,6 +7,7 @@ import (
"time"
"github.com/jesseduffield/lazygit/pkg/constants"
+ "github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/theme"
)
@@ -53,7 +54,7 @@ func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
func (gui *Gui) printCommandLogHeader() {
introStr := fmt.Sprintf(
gui.c.Tr.CommandLogHeader,
- gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.ExtrasMenu),
+ keybindings.Label(gui.c.UserConfig.Keybinding.Universal.ExtrasMenu),
)
fmt.Fprintln(gui.Views.Extras, style.FgCyan.Sprint(introStr))
@@ -71,7 +72,7 @@ func (gui *Gui) getRandomTip() string {
config := gui.c.UserConfig.Keybinding
formattedKey := func(key string) string {
- return gui.getKeyDisplay(key)
+ return keybindings.Label(key)
}
tips := []string{
diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go
index 35ffc822f..4c8ddae2b 100644
--- a/pkg/gui/commit_message_panel.go
+++ b/pkg/gui/commit_message_panel.go
@@ -5,6 +5,7 @@ import (
"strings"
"github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@@ -12,9 +13,9 @@ func (gui *Gui) handleCommitMessageFocused() error {
message := utils.ResolvePlaceholderString(
gui.c.Tr.CommitMessageConfirm,
map[string]string{
- "keyBindClose": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.Return),
- "keyBindConfirm": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.Confirm),
- "keyBindNewLine": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.AppendNewline),
+ "keyBindClose": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Return),
+ "keyBindConfirm": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Confirm),
+ "keyBindNewLine": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.AppendNewline),
},
)
diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go
index 86ae41812..1ad724ad5 100644
--- a/pkg/gui/confirmation_panel.go
+++ b/pkg/gui/confirmation_panel.go
@@ -5,6 +5,7 @@ import (
"strings"
"github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
@@ -220,22 +221,22 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
bindings := []*types.Binding{
{
ViewName: "confirmation",
- Key: gui.getKey(keybindingConfig.Universal.Confirm),
+ Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
Handler: onConfirm,
},
{
ViewName: "confirmation",
- Key: gui.getKey(keybindingConfig.Universal.ConfirmAlt1),
+ Key: keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1),
Handler: onConfirm,
},
{
ViewName: "confirmation",
- Key: gui.getKey(keybindingConfig.Universal.Return),
+ Key: keybindings.GetKey(keybindingConfig.Universal.Return),
Handler: gui.wrappedConfirmationFunction(opts.HandleClose),
},
{
ViewName: "confirmation",
- Key: gui.getKey(keybindingConfig.Universal.TogglePanel),
+ Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
Handler: func() error {
if len(gui.State.Suggestions) > 0 {
return gui.replaceContext(gui.State.Contexts.Suggestions)
@@ -245,22 +246,22 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
},
{
ViewName: "suggestions",
- Key: gui.getKey(keybindingConfig.Universal.Confirm),
+ Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
Handler: onSuggestionConfirm,
},
{
ViewName: "suggestions",
- Key: gui.getKey(keybindingConfig.Universal.ConfirmAlt1),
+ Key: keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1),
Handler: onSuggestionConfirm,
},
{
ViewName: "suggestions",
- Key: gui.getKey(keybindingConfig.Universal.Return),
+ Key: keybindings.GetKey(keybindingConfig.Universal.Return),
Handler: gui.wrappedConfirmationFunction(opts.HandleClose),
},
{
ViewName: "suggestions",
- Key: gui.getKey(keybindingConfig.Universal.TogglePanel),
+ Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
Handler: func() error { return gui.replaceContext(gui.State.Contexts.Confirmation) },
},
}
@@ -276,12 +277,12 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
func (gui *Gui) clearConfirmationViewKeyBindings() {
keybindingConfig := gui.c.UserConfig.Keybinding
- _ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
- _ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
- _ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Return), gocui.ModNone)
- _ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
- _ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
- _ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.Return), gocui.ModNone)
+ _ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
+ _ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
+ _ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone)
+ _ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
+ _ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
+ _ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone)
}
func (gui *Gui) refreshSuggestions() {
@@ -297,8 +298,8 @@ func (gui *Gui) handleAskFocused() error {
message := utils.ResolvePlaceholderString(
gui.c.Tr.CloseConfirm,
map[string]string{
- "keyBindClose": gui.getKeyDisplay(keybindingConfig.Universal.Return),
- "keyBindConfirm": gui.getKeyDisplay(keybindingConfig.Universal.Confirm),
+ "keyBindClose": keybindings.Label(keybindingConfig.Universal.Return),
+ "keyBindConfirm": keybindings.Label(keybindingConfig.Universal.Confirm),
},
)
diff --git a/pkg/gui/context/menu_context.go b/pkg/gui/context/menu_context.go
index 7754d02ae..780c35660 100644
--- a/pkg/gui/context/menu_context.go
+++ b/pkg/gui/context/menu_context.go
@@ -89,7 +89,7 @@ func (self *MenuViewModel) GetDisplayStrings(_startIdx int, _length int) [][]str
return slices.Map(self.menuItems, func(item *types.MenuItem) []string {
displayStrings := item.LabelColumns
if showKeys {
- displayStrings = slices.Prepend(displayStrings, style.FgCyan.Sprint(keybindings.GetKeyDisplay(item.Key)))
+ displayStrings = slices.Prepend(displayStrings, style.FgCyan.Sprint(keybindings.LabelFromKey(item.Key)))
}
return displayStrings
})
diff --git a/pkg/gui/context/merge_conflicts_context.go b/pkg/gui/context/merge_conflicts_context.go
index 48749373c..b49bf2c65 100644
--- a/pkg/gui/context/merge_conflicts_context.go
+++ b/pkg/gui/context/merge_conflicts_context.go
@@ -1,15 +1,20 @@
package context
import (
+ "math"
+ "sync"
+
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
"github.com/jesseduffield/lazygit/pkg/gui/types"
+ "github.com/sasha-s/go-deadlock"
)
type MergeConflictsContext struct {
types.Context
viewModel *ConflictsViewModel
c *types.HelperCommon
+ mutex *sync.Mutex
}
type ConflictsViewModel struct {
@@ -35,6 +40,7 @@ func NewMergeConflictsContext(
return &MergeConflictsContext{
viewModel: viewModel,
+ mutex: &sync.Mutex{},
Context: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
@@ -50,6 +56,18 @@ func NewMergeConflictsContext(
}
}
+func (self *MergeConflictsContext) GetState() *mergeconflicts.State {
+ return self.viewModel.state
+}
+
+func (self *MergeConflictsContext) SetState(state *mergeconflicts.State) {
+ self.viewModel.state = state
+}
+
+func (self *MergeConflictsContext) GetMutex() *sync.Mutex {
+ return self.mutex
+}
+
func (self *MergeConflictsContext) SetUserScrolling(isScrolling bool) {
self.viewModel.userVerticalScrolling = isScrolling
}
@@ -58,6 +76,43 @@ func (self *MergeConflictsContext) IsUserScrolling() bool {
return self.viewModel.userVerticalScrolling
}
-func (self *MergeConflictsContext) State() *mergeconflicts.State {
- return self.viewModel.state
+func (self *MergeConflictsContext) RenderAndFocus(isFocused bool) error {
+ self.setContent(isFocused)
+ self.focusSelection()
+
+ self.c.Render()
+
+ return nil
+}
+
+func (self *MergeConflictsContext) Render(isFocused bool) error {
+ self.setContent(isFocused)
+
+ self.c.Render()
+
+ return nil
+}
+
+func (self *MergeConflictsContext) GetContentToRender(isFocused bool) string {
+ if self.GetState() == nil {
+ return ""
+ }
+
+ return mergeconflicts.ColoredConflictFile(self.GetState(), isFocused)
+}
+
+func (self *MergeConflictsContext) setContent(isFocused bool) {
+ self.GetView().SetContent(self.GetContentToRender(isFocused))
+}
+
+func (self *MergeConflictsContext) focusSelection() {
+ if !self.IsUserScrolling() {
+ _ = self.GetView().SetOrigin(self.GetView().OriginX(), self.GetOriginY())
+ }
+}
+
+func (self *MergeConflictsContext) GetOriginY() int {
+ view := self.GetView()
+ conflictMiddle := self.GetState().GetConflictMiddle()
+ return int(math.Max(0, float64(conflictMiddle-(view.Height()/2))))
}
diff --git a/pkg/gui/context/patch_explorer_context.go b/pkg/gui/context/patch_explorer_context.go
index 626c0be84..a8ad45bbb 100644
--- a/pkg/gui/context/patch_explorer_context.go
+++ b/pkg/gui/context/patch_explorer_context.go
@@ -67,19 +67,16 @@ func (self *PatchExplorerContext) GetIncludedLineIndices() []int {
}
func (self *PatchExplorerContext) RenderAndFocus(isFocused bool) error {
- self.GetView().SetContent(self.GetContentToRender(isFocused))
-
- if err := self.focusSelection(); err != nil {
- return err
- }
+ self.setContent(isFocused)
+ self.focusSelection()
self.c.Render()
return nil
}
func (self *PatchExplorerContext) Render(isFocused bool) error {
- self.GetView().SetContent(self.GetContentToRender(isFocused))
+ self.setContent(isFocused)
self.c.Render()
@@ -87,16 +84,17 @@ func (self *PatchExplorerContext) Render(isFocused bool) error {
}
func (self *PatchExplorerContext) Focus() error {
- if err := self.focusSelection(); err != nil {
- return err
- }
-
+ self.focusSelection()
self.c.Render()
return nil
}
-func (self *PatchExplorerContext) focusSelection() error {
+func (self *PatchExplorerContext) setContent(isFocused bool) {
+ self.GetView().SetContent(self.GetContentToRender(isFocused))
+}
+
+func (self *PatchExplorerContext) focusSelection() {
view := self.GetView()
state := self.GetState()
_, viewHeight := view.Size()
@@ -107,11 +105,8 @@ func (self *PatchExplorerContext) focusSelection() error {
newOrigin := state.CalculateOrigin(origin, bufferHeight)
- if err := view.SetOriginY(newOrigin); err != nil {
- return err
- }
-
- return view.SetCursor(0, selectedLineIdx-newOrigin)
+ _ = view.SetOriginY(newOrigin)
+ _ = view.SetCursor(0, selectedLineIdx-newOrigin)
}
func (self *PatchExplorerContext) GetContentToRender(isFocused bool) string {
diff --git a/pkg/gui/context_config.go b/pkg/gui/context_config.go
index 79f14c68c..7bd0ca7f8 100644
--- a/pkg/gui/context_config.go
+++ b/pkg/gui/context_config.go
@@ -164,16 +164,21 @@ func (gui *Gui) contextTree() *context.ContextTree {
OnFocus: OnFocusWrapper(func() error {
gui.Views.MergeConflicts.Wrap = false
- return gui.renderConflictsWithLock(true)
+ return gui.refreshMergePanel(true)
}),
- OnFocusLost: func(types.OnFocusLostOpts) error {
+ OnFocusLost: func(opts types.OnFocusLostOpts) error {
+ gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
+ gui.State.Contexts.MergeConflicts.GetState().ResetConflictSelection()
gui.Views.MergeConflicts.Wrap = true
return nil
},
},
gui.c,
- gui.getMergingOptions,
+ func() map[string]string {
+ // wrapping in a function because contexts are initialized before helpers
+ return gui.helpers.MergeConflicts.GetMergingOptions()
+ },
),
Confirmation: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
@@ -217,12 +222,11 @@ func (gui *Gui) contextTree() *context.ContextTree {
),
CommandLog: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
- Kind: types.EXTRAS_CONTEXT,
- View: gui.Views.Extras,
- WindowName: "extras",
- Key: context.COMMAND_LOG_CONTEXT_KEY,
- OnGetOptionsMap: gui.getMergingOptions,
- Focusable: true,
+ Kind: types.EXTRAS_CONTEXT,
+ View: gui.Views.Extras,
+ WindowName: "extras",
+ Key: context.COMMAND_LOG_CONTEXT_KEY,
+ Focusable: true,
}),
context.ContextCallbackOpts{
OnFocusLost: func(opts types.OnFocusLostOpts) error {
diff --git a/pkg/gui/controllers.go b/pkg/gui/controllers.go
index e0c2d5eb6..4efb5e1ff 100644
--- a/pkg/gui/controllers.go
+++ b/pkg/gui/controllers.go
@@ -35,6 +35,7 @@ func (gui *Gui) resetControllers() {
Tags: helpers.NewTagsHelper(helperCommon, gui.git),
GPG: helpers.NewGpgHelper(helperCommon, gui.os, gui.git),
MergeAndRebase: rebaseHelper,
+ MergeConflicts: helpers.NewMergeConflictsHelper(helperCommon, gui.State.Contexts, gui.git),
CherryPick: helpers.NewCherryPickHelper(
helperCommon,
gui.git,
@@ -51,7 +52,6 @@ func (gui *Gui) resetControllers() {
gui.git,
gui.State.Contexts,
gui.helpers,
- gui.getKey,
)
common := controllers.NewControllerCommon(
@@ -112,8 +112,8 @@ func (gui *Gui) resetControllers() {
gui.enterSubmodule,
setCommitMessage,
getSavedCommitMessage,
- gui.switchToMerge,
)
+ mergeConflictsController := controllers.NewMergeConflictsController(common)
remotesController := controllers.NewRemotesController(
common,
func(branches []*models.RemoteBranch) { gui.State.Model.RemoteBranches = branches },
@@ -187,6 +187,10 @@ func (gui *Gui) resetControllers() {
verticalScrollControllerFactory.Create(gui.State.Contexts.CustomPatchBuilder),
)
+ controllers.AttachControllers(gui.State.Contexts.MergeConflicts,
+ mergeConflictsController,
+ )
+
controllers.AttachControllers(gui.State.Contexts.Files,
filesController,
filesRemoveController,
diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go
index ba5e49ad9..20a802169 100644
--- a/pkg/gui/controllers/files_controller.go
+++ b/pkg/gui/controllers/files_controller.go
@@ -22,7 +22,6 @@ type FilesController struct {
enterSubmodule func(submodule *models.SubmoduleConfig) error
setCommitMessage func(message string)
getSavedCommitMessage func() string
- switchToMergeFn func(path string) error
}
var _ types.IController = &FilesController{}
@@ -32,14 +31,12 @@ func NewFilesController(
enterSubmodule func(submodule *models.SubmoduleConfig) error,
setCommitMessage func(message string),
getSavedCommitMessage func() string,
- switchToMergeFn func(path string) error,
) *FilesController {
return &FilesController{
controllerCommon: common,
enterSubmodule: enterSubmodule,
setCommitMessage: setCommitMessage,
getSavedCommitMessage: getSavedCommitMessage,
- switchToMergeFn: switchToMergeFn,
}
}
@@ -268,10 +265,6 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
if node.IsFile() {
file := node.File
- if file.HasInlineMergeConflicts {
- return self.c.PushContext(self.contexts.MergeConflicts)
- }
-
if file.HasUnstagedChanges {
self.c.LogAction(self.c.Tr.Actions.StageFile)
@@ -328,6 +321,10 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
}
func (self *FilesController) press(node *filetree.FileNode) error {
+ if node.IsFile() && node.File.HasInlineMergeConflicts {
+ return self.switchToMerge()
+ }
+
if err := self.pressWithLock(node); err != nil {
return err
}
@@ -750,7 +747,7 @@ func (self *FilesController) switchToMerge() error {
return nil
}
- return self.switchToMergeFn(file.Name)
+ return self.helpers.MergeConflicts.SwitchToMerge(file.Name)
}
func (self *FilesController) createStashMenu() error {
diff --git a/pkg/gui/controllers/helpers/helpers.go b/pkg/gui/controllers/helpers/helpers.go
index 61fdedf46..b8c279fac 100644
--- a/pkg/gui/controllers/helpers/helpers.go
+++ b/pkg/gui/controllers/helpers/helpers.go
@@ -8,6 +8,7 @@ type Helpers struct {
WorkingTree *WorkingTreeHelper
Tags *TagsHelper
MergeAndRebase *MergeAndRebaseHelper
+ MergeConflicts *MergeConflictsHelper
CherryPick *CherryPickHelper
Host *HostHelper
PatchBuilding *PatchBuildingHelper
@@ -24,6 +25,7 @@ func NewStubHelpers() *Helpers {
WorkingTree: &WorkingTreeHelper{},
Tags: &TagsHelper{},
MergeAndRebase: &MergeAndRebaseHelper{},
+ MergeConflicts: &MergeConflictsHelper{},
CherryPick: &CherryPickHelper{},
Host: &HostHelper{},
PatchBuilding: &PatchBuildingHelper{},
diff --git a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go
index 4666f103d..21c02d6f4 100644
--- a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go
+++ b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go
@@ -186,8 +186,6 @@ func (self *MergeAndRebaseHelper) workingTreeStateNoun() string {
// PromptToContinueRebase asks the user if they want to continue the rebase/merge that's in progress
func (self *MergeAndRebaseHelper) PromptToContinueRebase() error {
- self.contexts.MergeConflicts.SetUserScrolling(false)
-
return self.c.Confirm(types.ConfirmOpts{
Title: "continue",
Prompt: self.c.Tr.ConflictsResolved,
diff --git a/pkg/gui/controllers/helpers/merge_conflicts_helper.go b/pkg/gui/controllers/helpers/merge_conflicts_helper.go
new file mode 100644
index 000000000..d7f7aa747
--- /dev/null
+++ b/pkg/gui/controllers/helpers/merge_conflicts_helper.go
@@ -0,0 +1,115 @@
+package helpers
+
+import (
+ "fmt"
+
+ "github.com/jesseduffield/lazygit/pkg/commands"
+ "github.com/jesseduffield/lazygit/pkg/gui/context"
+ "github.com/jesseduffield/lazygit/pkg/gui/keybindings"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+)
+
+type MergeConflictsHelper struct {
+ c *types.HelperCommon
+ contexts *context.ContextTree
+ git *commands.GitCommand
+}
+
+func NewMergeConflictsHelper(
+ c *types.HelperCommon,
+ contexts *context.ContextTree,
+ git *commands.GitCommand,
+) *MergeConflictsHelper {
+ return &MergeConflictsHelper{
+ c: c,
+ contexts: contexts,
+ git: git,
+ }
+}
+
+func (self *MergeConflictsHelper) GetMergingOptions() map[string]string {
+ keybindingConfig := self.c.UserConfig.Keybinding
+
+ return map[string]string{
+ fmt.Sprintf("%s %s", keybindings.Label(keybindingConfig.Universal.PrevItem), keybindings.Label(keybindingConfig.Universal.NextItem)): self.c.Tr.LcSelectHunk,
+ fmt.Sprintf("%s %s", keybindings.Label(keybindingConfig.Universal.PrevBlock), keybindings.Label(keybindingConfig.Universal.NextBlock)): self.c.Tr.LcNavigateConflicts,
+ keybindings.Label(keybindingConfig.Universal.Select): self.c.Tr.LcPickHunk,
+ keybindings.Label(keybindingConfig.Main.PickBothHunks): self.c.Tr.LcPickAllHunks,
+ keybindings.Label(keybindingConfig.Universal.Undo): self.c.Tr.LcUndo,
+ }
+}
+
+func (self *MergeConflictsHelper) SetMergeState(path string) (bool, error) {
+ self.context().GetMutex().Lock()
+ defer self.context().GetMutex().Unlock()
+
+ return self.setMergeStateWithoutLock(path)
+}
+
+func (self *MergeConflictsHelper) setMergeStateWithoutLock(path string) (bool, error) {
+ content, err := self.git.File.Cat(path)
+ if err != nil {
+ return false, err
+ }
+
+ if path != self.context().GetState().GetPath() {
+ self.context().SetUserScrolling(false)
+ }
+
+ self.context().GetState().SetContent(content, path)
+
+ return !self.context().GetState().NoConflicts(), nil
+}
+
+func (self *MergeConflictsHelper) ResetMergeState() {
+ self.context().GetMutex().Lock()
+ defer self.context().GetMutex().Unlock()
+
+ self.resetMergeState()
+}
+
+func (self *MergeConflictsHelper) resetMergeState() {
+ self.context().SetUserScrolling(false)
+ self.context().GetState().Reset()
+}
+
+func (self *MergeConflictsHelper) EscapeMerge() error {
+ self.resetMergeState()
+
+ // doing this in separate UI thread so that we're not still holding the lock by the time refresh the file
+ self.c.OnUIThread(func() error {
+ return self.c.PushContext(self.contexts.Files)
+ })
+ return nil
+}
+
+func (self *MergeConflictsHelper) SetConflictsAndRender(path string, isFocused bool) (bool, error) {
+ hasConflicts, err := self.setMergeStateWithoutLock(path)
+ if err != nil {
+ return false, err
+ }
+
+ if hasConflicts {
+ return true, self.context().Render(isFocused)
+ }
+
+ return false, nil
+}
+
+func (self *MergeConflictsHelper) SwitchToMerge(path string) error {
+ if self.context().GetState().GetPath() != path {
+ hasConflicts, err := self.SetMergeState(path)
+ if err != nil {
+ return err
+ }
+ if !hasConflicts {
+ return nil
+ }
+ }
+
+ return self.c.PushContext(self.contexts.MergeConflicts)
+}
+
+func (self *MergeConflictsHelper) context() *context.MergeConflictsContext {
+ return self.contexts.MergeConflicts
+}
diff --git a/pkg/gui/controllers/merge_conflicts_controller.go b/pkg/gui/controllers/merge_conflicts_controller.go
index 6ec6170f2..86d18a6a8 100644
--- a/pkg/gui/controllers/merge_conflicts_controller.go
+++ b/pkg/gui/controllers/merge_conflicts_controller.go
@@ -1,7 +1,11 @@
package controllers
import (
+ "io/ioutil"
+
+ "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
+ "github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -25,14 +29,125 @@ func (self *MergeConflictsController) GetKeybindings(opts types.KeybindingsOpts)
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Edit),
- Handler: self.EditFile,
+ Handler: self.HandleEditFile,
Description: self.c.Tr.LcEditFile,
},
+ {
+ Key: opts.GetKey(opts.Config.Universal.OpenFile),
+ Handler: self.HandleOpenFile,
+ Description: self.c.Tr.LcOpenFile,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.PrevBlock),
+ Handler: self.withRenderAndFocus(self.PrevConflict),
+ Description: self.c.Tr.PrevConflict,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.NextBlock),
+ Handler: self.withRenderAndFocus(self.NextConflict),
+ Description: self.c.Tr.NextConflict,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.PrevItem),
+ Handler: self.withRenderAndFocus(self.PrevConflictHunk),
+ Description: self.c.Tr.SelectPrevHunk,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.NextItem),
+ Handler: self.withRenderAndFocus(self.NextConflictHunk),
+ Description: self.c.Tr.SelectNextHunk,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt),
+ Handler: self.withRenderAndFocus(self.PrevConflict),
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.NextBlockAlt),
+ Handler: self.withRenderAndFocus(self.NextConflict),
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.PrevItemAlt),
+ Handler: self.withRenderAndFocus(self.PrevConflictHunk),
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.NextItemAlt),
+ Handler: self.withRenderAndFocus(self.NextConflictHunk),
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.ScrollLeft),
+ Handler: self.withRenderAndFocus(self.HandleScrollLeft),
+ Description: self.c.Tr.LcScrollLeft,
+ Tag: "navigation",
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.ScrollRight),
+ Handler: self.withRenderAndFocus(self.HandleScrollRight),
+ Description: self.c.Tr.LcScrollRight,
+ Tag: "navigation",
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.Undo),
+ Handler: self.withRenderAndFocus(self.HandleUndo),
+ Description: self.c.Tr.LcUndo,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
+ Handler: self.helpers.WorkingTree.OpenMergeTool,
+ Description: self.c.Tr.LcOpenMergeTool,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.Select),
+ Handler: self.withRenderAndFocus(self.HandlePickHunk),
+ Description: self.c.Tr.PickHunk,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Main.PickBothHunks),
+ Handler: self.withRenderAndFocus(self.HandlePickAllHunks),
+ Description: self.c.Tr.PickAllHunks,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.Return),
+ Handler: self.Escape,
+ Description: self.c.Tr.ReturnToFilesPanel,
+ },
}
return bindings
}
+func (self *MergeConflictsController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
+ return []*gocui.ViewMouseBinding{
+ {
+ ViewName: self.context().GetViewName(),
+ Key: gocui.MouseWheelUp,
+ Handler: func(gocui.ViewMouseBindingOpts) error {
+ return self.HandleScrollUp()
+ },
+ },
+ {
+ ViewName: self.context().GetViewName(),
+ Key: gocui.MouseWheelDown,
+ Handler: func(gocui.ViewMouseBindingOpts) error {
+ return self.HandleScrollDown()
+ },
+ },
+ }
+}
+
+func (self *MergeConflictsController) HandleScrollUp() error {
+ self.context().SetUserScrolling(true)
+ self.context().GetViewTrait().ScrollUp(self.c.UserConfig.Gui.ScrollHeight)
+
+ return nil
+}
+
+func (self *MergeConflictsController) HandleScrollDown() error {
+ self.context().SetUserScrolling(true)
+ self.context().GetViewTrait().ScrollDown(self.c.UserConfig.Gui.ScrollHeight)
+
+ return nil
+}
+
func (self *MergeConflictsController) Context() types.Context {
return self.context()
}
@@ -41,14 +156,162 @@ func (self *MergeConflictsController) context() *context.MergeConflictsContext {
return self.contexts.MergeConflicts
}
-func (self *MergeConflictsController) EditFile() error {
- lineNumber := self.context().State().GetSelectedLine()
- return self.helpers.Files.EditFileAtLine(self.context().State().GetPath(), lineNumber)
+func (self *MergeConflictsController) Escape() error {
+ return self.c.PushContext(self.contexts.Files)
}
-func (self *MergeConflictsController) withMergeConflictLock(f func() error) error {
- self.context().State().Lock()
- defer self.context().State().Unlock()
-
- return f()
+func (self *MergeConflictsController) HandleEditFile() error {
+ lineNumber := self.context().GetState().GetSelectedLine()
+ return self.helpers.Files.EditFileAtLine(self.context().GetState().GetPath(), lineNumber)
+}
+
+func (self *MergeConflictsController) HandleOpenFile() error {
+ lineNumber := self.context().GetState().GetSelectedLine()
+ return self.helpers.Files.OpenFileAtLine(self.context().GetState().GetPath(), lineNumber)
+}
+
+func (self *MergeConflictsController) HandleScrollLeft() error {
+ self.context().GetViewTrait().ScrollLeft()
+
+ return nil
+}
+
+func (self *MergeConflictsController) HandleScrollRight() error {
+ self.context().GetViewTrait().ScrollRight()
+
+ return nil
+}
+
+func (self *MergeConflictsController) HandleUndo() error {
+ state := self.context().GetState()
+
+ ok := state.Undo()
+ if !ok {
+ return nil
+ }
+
+ self.c.LogAction("Restoring file to previous state")
+ self.c.LogCommand("Undoing last conflict resolution", false)
+ if err := ioutil.WriteFile(state.GetPath(), []byte(state.GetContent()), 0o644); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (self *MergeConflictsController) PrevConflictHunk() error {
+ self.context().SetUserScrolling(false)
+ self.context().GetState().SelectPrevConflictHunk()
+
+ return nil
+}
+
+func (self *MergeConflictsController) NextConflictHunk() error {
+ self.context().SetUserScrolling(false)
+ self.context().GetState().SelectNextConflictHunk()
+
+ return nil
+}
+
+func (self *MergeConflictsController) NextConflict() error {
+ self.context().SetUserScrolling(false)
+ self.context().GetState().SelectNextConflict()
+
+ return nil
+}
+
+func (self *MergeConflictsController) PrevConflict() error {
+ self.context().SetUserScrolling(false)
+ self.context().GetState().SelectPrevConflict()
+
+ return nil
+}
+
+func (self *MergeConflictsController) HandlePickHunk() error {
+ return self.pickSelection(self.context().GetState().Selection())
+}
+
+func (self *MergeConflictsController) HandlePickAllHunks() error {
+ return self.pickSelection(mergeconflicts.ALL)
+}
+
+func (self *MergeConflictsController) pickSelection(selection mergeconflicts.Selection) error {
+ ok, err := self.resolveConflict(selection)
+ if err != nil {
+ return err
+ }
+
+ if !ok {
+ return nil
+ }
+
+ if self.context().GetState().AllConflictsResolved() {
+ return self.onLastConflictResolved()
+ }
+
+ return nil
+}
+
+func (self *MergeConflictsController) resolveConflict(selection mergeconflicts.Selection) (bool, error) {
+ self.context().SetUserScrolling(false)
+
+ state := self.context().GetState()
+
+ ok, content, err := state.ContentAfterConflictResolve(selection)
+ if err != nil {
+ return false, err
+ }
+
+ if !ok {
+ return false, nil
+ }
+
+ var logStr string
+ switch selection {
+ case mergeconflicts.TOP:
+ logStr = "Picking top hunk"
+ case mergeconflicts.MIDDLE:
+ logStr = "Picking middle hunk"
+ case mergeconflicts.BOTTOM:
+ logStr = "Picking bottom hunk"
+ case mergeconflicts.ALL:
+ logStr = "Picking all hunks"
+ }
+ self.c.LogAction("Resolve merge conflict")
+ self.c.LogCommand(logStr, false)
+ state.PushContent(content)
+ return true, ioutil.WriteFile(state.GetPath(), []byte(content), 0o644)
+}
+
+func (self *MergeConflictsController) onLastConflictResolved() error {
+ // as part of refreshing files, we handle the situation where a file has had
+ // its merge conflicts resolved.
+ return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
+}
+
+func (self *MergeConflictsController) isFocused() bool {
+ return self.c.CurrentContext().GetKey() == self.context().GetKey()
+}
+
+func (self *MergeConflictsController) withRenderAndFocus(f func() error) func() error {
+ return self.withLock(func() error {
+ if err := f(); err != nil {
+ return err
+ }
+
+ return self.context().RenderAndFocus(self.isFocused())
+ })
+}
+
+func (self *MergeConflictsController) withLock(f func() error) func() error {
+ return func() error {
+ self.context().GetMutex().Lock()
+ defer self.context().GetMutex().Unlock()
+
+ if self.context().GetState() == nil {
+ return nil
+ }
+
+ return f()
+ }
}
diff --git a/pkg/gui/controllers/scroll_controller.go b/pkg/gui/controllers/vertical_scroll_controller.go
similarity index 100%
rename from pkg/gui/controllers/scroll_controller.go
rename to pkg/gui/controllers/vertical_scroll_controller.go
diff --git a/pkg/gui/editors.go b/pkg/gui/editors.go
index 145a6a62b..6f40c6d58 100644
--- a/pkg/gui/editors.go
+++ b/pkg/gui/editors.go
@@ -4,10 +4,11 @@ import (
"unicode"
"github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/gui/keybindings"
)
func (gui *Gui) handleEditorKeypress(textArea *gocui.TextArea, key gocui.Key, ch rune, mod gocui.Modifier, allowMultiline bool) bool {
- newlineKey, ok := gui.getKey(gui.c.UserConfig.Keybinding.Universal.AppendNewline).(gocui.Key)
+ newlineKey, ok := keybindings.GetKey(gui.c.UserConfig.Keybinding.Universal.AppendNewline).(gocui.Key)
if !ok {
newlineKey = gocui.KeyAltEnter
}
diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go
index 568787089..8530ef382 100644
--- a/pkg/gui/files_panel.go
+++ b/pkg/gui/files_panel.go
@@ -6,8 +6,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
)
-// list panel functions
-
func (gui *Gui) getSelectedFileNode() *filetree.FileNode {
return gui.State.Contexts.Files.GetSelected()
}
@@ -20,15 +18,6 @@ func (gui *Gui) getSelectedFile() *models.File {
return node.File
}
-func (gui *Gui) getSelectedPath() string {
- node := gui.getSelectedFileNode()
- if node == nil {
- return ""
- }
-
- return node.GetPath()
-}
-
func (gui *Gui) filesRenderToMain() error {
node := gui.getSelectedFileNode()
@@ -43,16 +32,17 @@ func (gui *Gui) filesRenderToMain() error {
}
if node.File != nil && node.File.HasInlineMergeConflicts {
- ok, err := gui.setConflictsAndRenderWithLock(node.GetPath(), false)
+ hasConflicts, err := gui.helpers.MergeConflicts.SetMergeState(node.GetPath())
if err != nil {
return err
}
- if ok {
- return nil
+
+ if hasConflicts {
+ return gui.refreshMergePanel(false)
}
}
- gui.resetMergeStateWithLock()
+ gui.helpers.MergeConflicts.ResetMergeState()
pair := gui.normalMainContextPair()
if node.File != nil {
@@ -92,13 +82,6 @@ func (gui *Gui) filesRenderToMain() error {
return gui.refreshMainViews(refreshOpts)
}
-func (gui *Gui) onFocusFile() error {
- gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
- return nil
-}
-
-// test
-
func (gui *Gui) getSetTextareaTextFn(getView func() *gocui.View) func(string) {
return func(text string) {
// using a getView function so that we don't need to worry about when the view is created
@@ -108,5 +91,3 @@ func (gui *Gui) getSetTextareaTextFn(getView func() *gocui.View) func(string) {
view.RenderTextArea()
}
}
-
-// test
diff --git a/pkg/gui/global_handlers.go b/pkg/gui/global_handlers.go
index b5a302a45..326b856bd 100644
--- a/pkg/gui/global_handlers.go
+++ b/pkg/gui/global_handlers.go
@@ -75,10 +75,6 @@ func (gui *Gui) scrollDownView(view *gocui.View) {
}
func (gui *Gui) scrollUpMain() error {
- if gui.renderingConflicts() {
- gui.State.Contexts.MergeConflicts.SetUserScrolling(true)
- }
-
var view *gocui.View
if gui.c.CurrentContext().GetWindowName() == "secondary" {
view = gui.secondaryView()
@@ -86,16 +82,20 @@ func (gui *Gui) scrollUpMain() error {
view = gui.mainView()
}
+ if view.Name() == "mergeConflicts" {
+ // although we have this same logic in the controller, this method can be invoked
+ // via the global scroll up/down keybindings, as opposed to just the mouse wheel keybinding.
+ // It would be nice to have a concept of a global keybinding that runs on the top context in a
+ // window but that might be overkill for this one use case.
+ gui.State.Contexts.MergeConflicts.SetUserScrolling(true)
+ }
+
gui.scrollUpView(view)
return nil
}
func (gui *Gui) scrollDownMain() error {
- if gui.renderingConflicts() {
- gui.State.Contexts.MergeConflicts.SetUserScrolling(true)
- }
-
var view *gocui.View
if gui.c.CurrentContext().GetWindowName() == "secondary" {
view = gui.secondaryView()
@@ -103,6 +103,10 @@ func (gui *Gui) scrollDownMain() error {
view = gui.mainView()
}
+ if view.Name() == "mergeConflicts" {
+ gui.State.Contexts.MergeConflicts.SetUserScrolling(true)
+ }
+
gui.scrollDownView(view)
return nil
@@ -120,27 +124,6 @@ func (gui *Gui) secondaryView() *gocui.View {
return view
}
-func (gui *Gui) scrollLeftMain() error {
- gui.scrollLeft(gui.mainView())
-
- return nil
-}
-
-func (gui *Gui) scrollRightMain() error {
- gui.scrollRight(gui.mainView())
-
- return nil
-}
-
-func (gui *Gui) scrollLeft(view *gocui.View) {
- newOriginX := utils.Max(view.OriginX()-view.InnerWidth()/HORIZONTAL_SCROLL_FACTOR, 0)
- _ = view.SetOriginX(newOriginX)
-}
-
-func (gui *Gui) scrollRight(view *gocui.View) {
- _ = view.SetOriginX(view.OriginX() + view.InnerWidth()/HORIZONTAL_SCROLL_FACTOR)
-}
-
func (gui *Gui) scrollUpSecondary() error {
gui.scrollUpView(gui.secondaryView())
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index fc9ce6e1a..6b5f129af 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -19,6 +19,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
+ "github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
"github.com/jesseduffield/lazygit/pkg/gui/modes/filtering"
@@ -481,6 +482,13 @@ func (gui *Gui) Run(startArgs types.StartArgs) error {
gui.g = g
defer gui.g.Close()
+ // if the deadlock package wants to report a deadlock, we first need to
+ // close the gui so that we can actually read what it prints.
+ deadlock.Opts.LogBuf = utils.NewOnceWriter(os.Stderr, func() {
+ gui.g.Close()
+ })
+ deadlock.Opts.Disable = !gui.Debug
+
if replaying() {
gui.g.RecordingConfig = gocui.RecordingConfig{
Speed: getRecordingSpeed(),
@@ -504,9 +512,9 @@ func (gui *Gui) Run(startArgs types.StartArgs) error {
return nil
}
userConfig := gui.UserConfig
- gui.g.SearchEscapeKey = gui.getKey(userConfig.Keybinding.Universal.Return)
- gui.g.NextSearchMatchKey = gui.getKey(userConfig.Keybinding.Universal.NextMatch)
- gui.g.PrevSearchMatchKey = gui.getKey(userConfig.Keybinding.Universal.PrevMatch)
+ gui.g.SearchEscapeKey = keybindings.GetKey(userConfig.Keybinding.Universal.Return)
+ gui.g.NextSearchMatchKey = keybindings.GetKey(userConfig.Keybinding.Universal.NextMatch)
+ gui.g.PrevSearchMatchKey = keybindings.GetKey(userConfig.Keybinding.Universal.PrevMatch)
gui.g.ShowListFooter = userConfig.Gui.ShowListFooter
@@ -771,7 +779,7 @@ func (gui *Gui) setColorScheme() error {
return nil
}
-func (gui *Gui) OnUIThread(f func() error) {
+func (gui *Gui) onUIThread(f func() error) {
gui.g.Update(func(*gocui.Gui) error {
return f()
})
diff --git a/pkg/gui/gui_common.go b/pkg/gui/gui_common.go
index 587a7fb86..2b7226034 100644
--- a/pkg/gui/gui_common.go
+++ b/pkg/gui/gui_common.go
@@ -87,5 +87,5 @@ func (self *guiCommon) OpenSearch() {
}
func (self *guiCommon) OnUIThread(f func() error) {
- self.gui.OnUIThread(f)
+ self.gui.onUIThread(f)
}
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index 9d02d636e..8982a3056 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -2,37 +2,13 @@ package gui
import (
"log"
- "strings"
- "unicode/utf8"
"github.com/jesseduffield/gocui"
- "github.com/jesseduffield/lazygit/pkg/constants"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
-func (gui *Gui) getKeyDisplay(name string) string {
- key := gui.getKey(name)
- return keybindings.GetKeyDisplay(key)
-}
-
-func (gui *Gui) getKey(key string) types.Key {
- runeCount := utf8.RuneCountInString(key)
- if runeCount > 1 {
- binding := keybindings.Keymap[strings.ToLower(key)]
- if binding == nil {
- log.Fatalf("Unrecognized key %s for keybinding. For permitted values see %s", strings.ToLower(key), constants.Links.Docs.CustomKeybindings)
- } else {
- return binding
- }
- } else if runeCount == 1 {
- return []rune(key)[0]
- }
- log.Fatal("Key empty for keybinding: " + strings.ToLower(key))
- return nil
-}
-
func (gui *Gui) noPopupPanel(f func() error) func() error {
return func() error {
if gui.popupPanelFocused() {
@@ -68,7 +44,7 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
}
opts := types.KeybindingsOpts{
- GetKey: self.getKey,
+ GetKey: keybindings.GetKey,
Config: config,
Guards: guards,
}
@@ -325,110 +301,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
Modifier: gocui.ModNone,
Handler: self.scrollUpSecondary,
},
- {
- ViewName: "mergeConflicts",
- Key: opts.GetKey(opts.Config.Universal.ScrollLeft),
- Handler: self.scrollLeftMain,
- Description: self.c.Tr.LcScrollLeft,
- Tag: "navigation",
- },
- {
- ViewName: "mergeConflicts",
- Key: opts.GetKey(opts.Config.Universal.ScrollRight),
- Handler: self.scrollRightMain,
- Description: self.c.Tr.LcScrollRight,
- Tag: "navigation",
- },
- {
- ViewName: "mergeConflicts",
- Key: opts.GetKey(opts.Config.Universal.Return),
- Handler: self.handleEscapeMerge,
- Description: self.c.Tr.ReturnToFilesPanel,
- },
- {
- ViewName: "mergeConflicts",
- Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
- Handler: self.helpers.WorkingTree.OpenMergeTool,
- Description: self.c.Tr.LcOpenMergeTool,
- },
- {
- ViewName: "mergeConflicts",
- Key: opts.GetKey(opts.Config.Universal.Select),
- Handler: self.handlePickHunk,
- Description: self.c.Tr.PickHunk,
- },
- {
- ViewName: "mergeConflicts",
- Key: opts.GetKey(opts.Config.Main.PickBothHunks),
- Handler: self.handlePickAllHunks,
- Description: self.c.Tr.PickAllHunks,
- },
- {
- ViewName: "mergeConflicts",
- Key: opts.GetKey(opts.Config.Universal.PrevBlock),
- Handler: self.handleSelectPrevConflict,
- Description: self.c.Tr.PrevConflict,
- },
- {
- ViewName: "mergeConflicts",
- Key: opts.GetKey(opts.Config.Universal.NextBlock),
- Handler: self.handleSelectNextConflict,
- Description: self.c.Tr.NextConflict,
- },
- {
- ViewName: "mergeConflicts",
- Key: opts.GetKey(opts.Config.Universal.PrevItem),
- Handler: self.handleSelectPrevConflictHunk,
- Description: self.c.Tr.SelectPrevHunk,
- },
- {
- ViewName: "mergeConflicts",
- Key: opts.GetKey(opts.Config.Universal.NextItem),
- Handler: self.handleSelectNextConflictHunk,
- Description: self.c.Tr.SelectNextHunk,
- },
- {
- ViewName: "mergeConflicts",
- Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt),
- Modifier: gocui.ModNone,
- Handler: self.handleSelectPrevConflict,
- },
- {
- ViewName: "mergeConflicts",
- Key: opts.GetKey(opts.Config.Universal.NextBlockAlt),
- Modifier: gocui.ModNone,
- Handler: self.handleSelectNextConflict,
- },
- {
- ViewName: "mergeConflicts",
- Key: opts.GetKey(opts.Config.Universal.PrevItemAlt),
- Modifier: gocui.ModNone,
- Handler: self.handleSelectPrevConflictHunk,
- },
- {
- ViewName: "mergeConflicts",
- Key: opts.GetKey(opts.Config.Universal.NextItemAlt),
- Modifier: gocui.ModNone,
- Handler: self.handleSelectNextConflictHunk,
- },
- {
- ViewName: "mergeConflicts",
- Key: opts.GetKey(opts.Config.Universal.Edit),
- Handler: self.handleMergeConflictEditFileAtLine,
- Description: self.c.Tr.LcEditFile,
- },
- {
- ViewName: "mergeConflicts",
- Key: opts.GetKey(opts.Config.Universal.OpenFile),
- Handler: self.handleMergeConflictOpenFileAtLine,
- Description: self.c.Tr.LcOpenFile,
- },
- {
- ViewName: "mergeConflicts",
- Key: opts.GetKey(opts.Config.Universal.Undo),
- Handler: self.handleMergeConflictUndo,
- Description: self.c.Tr.LcUndo,
- },
{
ViewName: "status",
Key: gocui.MouseLeft,
diff --git a/pkg/gui/keybindings/keybindings.go b/pkg/gui/keybindings/keybindings.go
index fba2528a4..a59180b56 100644
--- a/pkg/gui/keybindings/keybindings.go
+++ b/pkg/gui/keybindings/keybindings.go
@@ -2,12 +2,16 @@ package keybindings
import (
"fmt"
+ "log"
+ "strings"
+ "unicode/utf8"
"github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/constants"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
-var KeyMapReversed = map[gocui.Key]string{
+var keyMapReversed = map[gocui.Key]string{
gocui.KeyF1: "f1",
gocui.KeyF2: "f2",
gocui.KeyF3: "f3",
@@ -66,11 +70,11 @@ var KeyMapReversed = map[gocui.Key]string{
gocui.KeyCtrl5: "ctrl+5", // ctrl+]
gocui.KeyCtrl6: "ctrl+6",
gocui.KeyCtrl8: "ctrl+8",
- gocui.MouseWheelUp: "mouse wheel up",
- gocui.MouseWheelDown: "mouse wheel down",
+ gocui.MouseWheelUp: "mouse wheel ▲",
+ gocui.MouseWheelDown: "mouse wheel ▼",
}
-var Keymap = map[string]types.Key{
+var keyMap = map[string]types.Key{
"": gocui.KeyCtrlA,
"": gocui.KeyCtrlB,
"": gocui.KeyCtrlC,
@@ -142,14 +146,18 @@ var Keymap = map[string]types.Key{
"": gocui.KeyArrowRight,
}
-func GetKeyDisplay(key types.Key) string {
+func Label(name string) string {
+ return LabelFromKey(GetKey(name))
+}
+
+func LabelFromKey(key types.Key) string {
keyInt := 0
switch key := key.(type) {
case rune:
keyInt = int(key)
case gocui.Key:
- value, ok := KeyMapReversed[key]
+ value, ok := keyMapReversed[key]
if ok {
return value
}
@@ -158,3 +166,19 @@ func GetKeyDisplay(key types.Key) string {
return fmt.Sprintf("%c", keyInt)
}
+
+func GetKey(key string) types.Key {
+ runeCount := utf8.RuneCountInString(key)
+ if runeCount > 1 {
+ binding := keyMap[strings.ToLower(key)]
+ if binding == nil {
+ log.Fatalf("Unrecognized key %s for keybinding. For permitted values see %s", strings.ToLower(key), constants.Links.Docs.CustomKeybindings)
+ } else {
+ return binding
+ }
+ } else if runeCount == 1 {
+ return []rune(key)[0]
+ }
+ log.Fatal("Key empty for keybinding: " + strings.ToLower(key))
+ return nil
+}
diff --git a/pkg/gui/list_context_config.go b/pkg/gui/list_context_config.go
index e604cfe7b..2b72714c5 100644
--- a/pkg/gui/list_context_config.go
+++ b/pkg/gui/list_context_config.go
@@ -33,7 +33,7 @@ func (gui *Gui) filesListContext() *context.WorkingTreeContext {
return []string{line}
})
},
- OnFocusWrapper(gui.onFocusFile),
+ nil,
gui.withDiffModeCheck(gui.filesRenderToMain),
nil,
gui.c,
diff --git a/pkg/gui/main_panels.go b/pkg/gui/main_panels.go
index f26ba42fd..a2449e9f8 100644
--- a/pkg/gui/main_panels.go
+++ b/pkg/gui/main_panels.go
@@ -43,6 +43,18 @@ func NewRenderStringWithoutScrollTask(str string) *renderStringWithoutScrollTask
return &renderStringWithoutScrollTask{str: str}
}
+type renderStringWithScrollTask struct {
+ str string
+ originX int
+ originY int
+}
+
+func (t *renderStringWithScrollTask) IsUpdateTask() {}
+
+func NewRenderStringWithScrollTask(str string, originX int, originY int) *renderStringWithScrollTask {
+ return &renderStringWithScrollTask{str: str, originX: originX, originY: originY}
+}
+
type runCommandTask struct {
cmd *exec.Cmd
prefix string
@@ -69,11 +81,6 @@ func NewRunPtyTask(cmd *exec.Cmd) *runPtyTask {
return &runPtyTask{cmd: cmd}
}
-// currently unused
-// func (gui *Gui) createRunPtyTaskWithPrefix(cmd *exec.Cmd, prefix string) *runPtyTask {
-// return &runPtyTask{cmd: cmd, prefix: prefix}
-// }
-
func (gui *Gui) runTaskForView(view *gocui.View, task updateTask) error {
switch v := task.(type) {
case *renderStringTask:
@@ -82,6 +89,9 @@ func (gui *Gui) runTaskForView(view *gocui.View, task updateTask) error {
case *renderStringWithoutScrollTask:
return gui.newStringTaskWithoutScroll(view, v.str)
+ case *renderStringWithScrollTask:
+ return gui.newStringTaskWithScroll(view, v.str, v.originX, v.originY)
+
case *runCommandTask:
return gui.newCmdTask(view, v.cmd, v.prefix)
diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go
index 34dd1c615..bc3d087c3 100644
--- a/pkg/gui/menu_panel.go
+++ b/pkg/gui/menu_panel.go
@@ -3,6 +3,7 @@ package gui
import (
"fmt"
+ "github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
@@ -13,9 +14,9 @@ func (gui *Gui) getMenuOptions() map[string]string {
keybindingConfig := gui.c.UserConfig.Keybinding
return map[string]string{
- gui.getKeyDisplay(keybindingConfig.Universal.Return): gui.c.Tr.LcClose,
- fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcNavigate,
- gui.getKeyDisplay(keybindingConfig.Universal.Select): gui.c.Tr.LcExecute,
+ keybindings.Label(keybindingConfig.Universal.Return): gui.c.Tr.LcClose,
+ fmt.Sprintf("%s %s", keybindings.Label(keybindingConfig.Universal.PrevItem), keybindings.Label(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcNavigate,
+ keybindings.Label(keybindingConfig.Universal.Select): gui.c.Tr.LcExecute,
}
}
diff --git a/pkg/gui/merge_panel.go b/pkg/gui/merge_panel.go
deleted file mode 100644
index a5733e43f..000000000
--- a/pkg/gui/merge_panel.go
+++ /dev/null
@@ -1,315 +0,0 @@
-// 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"
- "io/ioutil"
- "math"
-
- "github.com/jesseduffield/gocui"
- "github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
- "github.com/jesseduffield/lazygit/pkg/gui/types"
-)
-
-func (gui *Gui) handleSelectPrevConflictHunk() error {
- return gui.withMergeConflictLock(func() error {
- gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
- gui.State.Contexts.MergeConflicts.State().SelectPrevConflictHunk()
- return gui.renderConflictsWithFocus()
- })
-}
-
-func (gui *Gui) handleSelectNextConflictHunk() error {
- return gui.withMergeConflictLock(func() error {
- gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
- gui.State.Contexts.MergeConflicts.State().SelectNextConflictHunk()
- return gui.renderConflictsWithFocus()
- })
-}
-
-func (gui *Gui) handleSelectNextConflict() error {
- return gui.withMergeConflictLock(func() error {
- gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
- gui.State.Contexts.MergeConflicts.State().SelectNextConflict()
- return gui.renderConflictsWithFocus()
- })
-}
-
-func (gui *Gui) handleSelectPrevConflict() error {
- return gui.withMergeConflictLock(func() error {
- gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
- gui.State.Contexts.MergeConflicts.State().SelectPrevConflict()
- return gui.renderConflictsWithFocus()
- })
-}
-
-func (gui *Gui) handleMergeConflictUndo() error {
- state := gui.State.Contexts.MergeConflicts.State()
-
- ok := state.Undo()
- if !ok {
- return nil
- }
-
- gui.c.LogAction("Restoring file to previous state")
- gui.LogCommand("Undoing last conflict resolution", false)
- if err := ioutil.WriteFile(state.GetPath(), []byte(state.GetContent()), 0o644); err != nil {
- return err
- }
-
- return gui.renderConflictsWithFocus()
-}
-
-func (gui *Gui) handlePickHunk() error {
- return gui.withMergeConflictLock(func() error {
- ok, err := gui.resolveConflict(gui.State.Contexts.MergeConflicts.State().Selection())
- if err != nil {
- return err
- }
-
- if !ok {
- return nil
- }
-
- if gui.State.Contexts.MergeConflicts.State().AllConflictsResolved() {
- return gui.onLastConflictResolved()
- }
-
- return gui.renderConflictsWithFocus()
- })
-}
-
-func (gui *Gui) handlePickAllHunks() error {
- return gui.withMergeConflictLock(func() error {
- ok, err := gui.resolveConflict(mergeconflicts.ALL)
- if err != nil {
- return err
- }
-
- if !ok {
- return nil
- }
-
- if gui.State.Contexts.MergeConflicts.State().AllConflictsResolved() {
- return gui.onLastConflictResolved()
- }
-
- return gui.renderConflictsWithFocus()
- })
-}
-
-func (gui *Gui) resolveConflict(selection mergeconflicts.Selection) (bool, error) {
- gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
-
- state := gui.State.Contexts.MergeConflicts.State()
-
- ok, content, err := state.ContentAfterConflictResolve(selection)
- if err != nil {
- return false, err
- }
-
- if !ok {
- return false, nil
- }
-
- var logStr string
- switch selection {
- case mergeconflicts.TOP:
- logStr = "Picking top hunk"
- case mergeconflicts.MIDDLE:
- logStr = "Picking middle hunk"
- case mergeconflicts.BOTTOM:
- logStr = "Picking bottom hunk"
- case mergeconflicts.ALL:
- logStr = "Picking all hunks"
- }
- gui.c.LogAction("Resolve merge conflict")
- gui.LogCommand(logStr, false)
- state.PushContent(content)
- return true, ioutil.WriteFile(state.GetPath(), []byte(content), 0o644)
-}
-
-// precondition: we actually have conflicts to render
-func (gui *Gui) renderConflicts(hasFocus bool) error {
- state := gui.State.Contexts.MergeConflicts.State()
- content := mergeconflicts.ColoredConflictFile(state, hasFocus)
-
- if !gui.State.Contexts.MergeConflicts.IsUserScrolling() {
- // 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.c.OnUIThread(func() error {
- gui.State.Contexts.MergeConflicts.State().Lock()
- defer gui.State.Contexts.MergeConflicts.State().Unlock()
-
- if !state.Active() {
- return nil
- }
-
- gui.centerYPos(gui.Views.MergeConflicts, state.GetConflictMiddle())
- return nil
- })
- }
-
- return gui.refreshMainViews(refreshMainOpts{
- pair: gui.mergingMainContextPair(),
- main: &viewUpdateOpts{
- task: NewRenderStringWithoutScrollTask(content),
- },
- })
-}
-
-func (gui *Gui) renderConflictsWithFocus() error {
- return gui.renderConflicts(true)
-}
-
-func (gui *Gui) renderConflictsWithLock(hasFocus bool) error {
- return gui.withMergeConflictLock(func() error {
- return gui.renderConflicts(hasFocus)
- })
-}
-
-func (gui *Gui) centerYPos(view *gocui.View, y int) {
- ox, _ := view.Origin()
- _, height := view.Size()
- newOriginY := int(math.Max(0, float64(y-(height/2))))
- _ = view.SetOrigin(ox, newOriginY)
-}
-
-func (gui *Gui) getMergingOptions() map[string]string {
- keybindingConfig := gui.c.UserConfig.Keybinding
-
- 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,
- }
-}
-
-func (gui *Gui) handleEscapeMerge() error {
- if err := gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
- return err
- }
-
- return gui.escapeMerge()
-}
-
-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}})
-}
-
-func (gui *Gui) resetMergeState() {
- gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
- gui.State.Contexts.MergeConflicts.State().Reset()
-}
-
-func (gui *Gui) setMergeState(path string) (bool, error) {
- content, err := gui.git.File.Cat(path)
- if err != nil {
- return false, err
- }
-
- gui.State.Contexts.MergeConflicts.State().SetContent(content, path)
-
- return !gui.State.Contexts.MergeConflicts.State().NoConflicts(), nil
-}
-
-func (gui *Gui) setMergeStateWithLock(path string) (bool, error) {
- gui.State.Contexts.MergeConflicts.State().Lock()
- defer gui.State.Contexts.MergeConflicts.State().Unlock()
-
- return gui.setMergeState(path)
-}
-
-func (gui *Gui) resetMergeStateWithLock() {
- gui.State.Contexts.MergeConflicts.State().Lock()
- defer gui.State.Contexts.MergeConflicts.State().Unlock()
-
- gui.resetMergeState()
-}
-
-func (gui *Gui) escapeMerge() error {
- gui.resetMergeState()
-
- // 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 {
- return gui.c.PushContext(gui.State.Contexts.Files)
- })
- return nil
-}
-
-func (gui *Gui) renderingConflicts() bool {
- currentView := gui.g.CurrentView()
- if currentView != gui.Views.MergeConflicts && currentView != gui.Views.Files {
- return false
- }
-
- return gui.State.Contexts.MergeConflicts.State().Active()
-}
-
-func (gui *Gui) withMergeConflictLock(f func() error) error {
- gui.State.Contexts.MergeConflicts.State().Lock()
- defer gui.State.Contexts.MergeConflicts.State().Unlock()
-
- return f()
-}
-
-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.Contexts.MergeConflicts.State().Lock()
- defer gui.State.Contexts.MergeConflicts.State().Unlock()
-
- return gui.setConflictsAndRender(path, hasFocus)
-}
-
-func (gui *Gui) switchToMerge(path string) error {
- gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
-
- if gui.State.Contexts.MergeConflicts.State().GetPath() != path {
- hasConflicts, err := gui.setMergeStateWithLock(path)
- if err != nil {
- return err
- }
- if !hasConflicts {
- return nil
- }
- }
-
- return gui.c.PushContext(gui.State.Contexts.MergeConflicts)
-}
-
-func (gui *Gui) handleMergeConflictEditFileAtLine() error {
- file := gui.getSelectedFile()
- if file == nil {
- return nil
- }
-
- lineNumber := gui.State.Contexts.MergeConflicts.State().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.Contexts.MergeConflicts.State().GetSelectedLine()
- return gui.helpers.Files.OpenFileAtLine(file.GetPath(), lineNumber)
-}
diff --git a/pkg/gui/mergeconflicts/state.go b/pkg/gui/mergeconflicts/state.go
index 83969a8eb..384fb735f 100644
--- a/pkg/gui/mergeconflicts/state.go
+++ b/pkg/gui/mergeconflicts/state.go
@@ -1,15 +1,11 @@
package mergeconflicts
import (
- "sync"
-
"github.com/jesseduffield/lazygit/pkg/utils"
)
// State represents the selection state of the merge conflict context.
type State struct {
- sync.Mutex
-
// path of the file with the conflicts
path string
@@ -28,7 +24,6 @@ type State struct {
func NewState() *State {
return &State{
- Mutex: sync.Mutex{},
conflictIndex: 0,
selectionIndex: 0,
conflicts: []*mergeConflict{},
@@ -151,6 +146,12 @@ func (s *State) Reset() {
s.path = ""
}
+// we're not resetting selectedIndex here because the user typically would want
+// to pick either all top hunks or all bottom hunks so we retain that selection
+func (s *State) ResetConflictSelection() {
+ s.conflictIndex = 0
+}
+
func (s *State) Active() bool {
return s.path != ""
}
diff --git a/pkg/gui/options_menu_panel.go b/pkg/gui/options_menu_panel.go
index e21c36291..909c97fc8 100644
--- a/pkg/gui/options_menu_panel.go
+++ b/pkg/gui/options_menu_panel.go
@@ -20,7 +20,7 @@ func (gui *Gui) getBindings(context types.Context) []*types.Binding {
bindings = append(customBindings, bindings...)
for _, binding := range bindings {
- if keybindings.GetKeyDisplay(binding.Key) != "" && binding.Description != "" {
+ if keybindings.LabelFromKey(binding.Key) != "" && binding.Description != "" {
if binding.ViewName == "" {
bindingsGlobal = append(bindingsGlobal, binding)
} else if binding.Tag == "navigation" {
diff --git a/pkg/gui/refresh.go b/pkg/gui/refresh.go
index edcf88c9a..f7f1b2743 100644
--- a/pkg/gui/refresh.go
+++ b/pkg/gui/refresh.go
@@ -22,17 +22,18 @@ import (
func getScopeNames(scopes []types.RefreshableView) []string {
scopeNameMap := map[types.RefreshableView]string{
- types.COMMITS: "commits",
- types.BRANCHES: "branches",
- types.FILES: "files",
- types.SUBMODULES: "submodules",
- types.STASH: "stash",
- types.REFLOG: "reflog",
- types.TAGS: "tags",
- types.REMOTES: "remotes",
- types.STATUS: "status",
- types.BISECT_INFO: "bisect",
- types.STAGING: "staging",
+ types.COMMITS: "commits",
+ types.BRANCHES: "branches",
+ types.FILES: "files",
+ types.SUBMODULES: "submodules",
+ types.STASH: "stash",
+ types.REFLOG: "reflog",
+ types.TAGS: "tags",
+ types.REMOTES: "remotes",
+ types.STATUS: "status",
+ types.BISECT_INFO: "bisect",
+ types.STAGING: "staging",
+ types.MERGE_CONFLICTS: "mergeConflicts",
}
return slices.Map(scopes, func(scope types.RefreshableView) string {
@@ -138,6 +139,10 @@ func (gui *Gui) Refresh(options types.RefreshOptions) error {
refresh(func() { _ = gui.refreshPatchBuildingPanel(types.OnFocusOpts{}) })
}
+ if scopeSet.Includes(types.MERGE_CONFLICTS) || scopeSet.Includes(types.FILES) {
+ refresh(func() { _ = gui.refreshMergeState() })
+ }
+
wg.Wait()
gui.refreshStatus()
@@ -148,7 +153,7 @@ func (gui *Gui) Refresh(options types.RefreshOptions) error {
}
if options.Mode == types.BLOCK_UI {
- gui.OnUIThread(func() error {
+ gui.c.OnUIThread(func() error {
f()
return nil
})
@@ -323,21 +328,15 @@ func (gui *Gui) refreshFilesAndSubmodules() error {
gui.Mutexes.RefreshingFilesMutex.Unlock()
}()
- prevSelectedPath := gui.getSelectedPath()
-
if err := gui.refreshStateSubmoduleConfigs(); err != nil {
return err
}
- if err := gui.refreshMergeState(); err != nil {
- return err
- }
-
if err := gui.refreshStateFiles(); err != nil {
return err
}
- gui.OnUIThread(func() error {
+ gui.c.OnUIThread(func() error {
if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Submodules); err != nil {
gui.c.Log.Error(err)
}
@@ -346,14 +345,6 @@ func (gui *Gui) refreshFilesAndSubmodules() error {
gui.c.Log.Error(err)
}
- if gui.currentContext().GetKey() == context.FILES_CONTEXT_KEY {
- currentSelectedPath := gui.getSelectedPath()
- alreadySelected := prevSelectedPath != "" && currentSelectedPath == prevSelectedPath
- if !alreadySelected {
- gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
- }
- }
-
return nil
})
@@ -361,20 +352,20 @@ func (gui *Gui) refreshFilesAndSubmodules() error {
}
func (gui *Gui) refreshMergeState() error {
- gui.State.Contexts.MergeConflicts.State().Lock()
- defer gui.State.Contexts.MergeConflicts.State().Unlock()
+ gui.State.Contexts.MergeConflicts.GetMutex().Lock()
+ defer gui.State.Contexts.MergeConflicts.GetMutex().Unlock()
if gui.currentContext().GetKey() != context.MERGE_CONFLICTS_CONTEXT_KEY {
return nil
}
- hasConflicts, err := gui.setConflictsAndRender(gui.State.Contexts.MergeConflicts.State().GetPath(), true)
+ hasConflicts, err := gui.helpers.MergeConflicts.SetConflictsAndRender(gui.State.Contexts.MergeConflicts.GetState().GetPath(), true)
if err != nil {
return gui.c.Error(err)
}
if !hasConflicts {
- return gui.escapeMerge()
+ return gui.helpers.MergeConflicts.EscapeMerge()
}
return nil
@@ -426,7 +417,7 @@ func (gui *Gui) refreshStateFiles() error {
}
if gui.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE && conflictFileCount == 0 && prevConflictFileCount > 0 {
- gui.OnUIThread(func() error { return gui.helpers.MergeAndRebase.PromptToContinueRebase() })
+ gui.c.OnUIThread(func() error { return gui.helpers.MergeAndRebase.PromptToContinueRebase() })
}
fileTreeViewModel.RWMutex.Lock()
@@ -701,3 +692,22 @@ func (gui *Gui) refreshPatchBuildingPanel(opts types.OnFocusOpts) error {
},
})
}
+
+func (gui *Gui) refreshMergePanel(isFocused bool) error {
+ content := gui.State.Contexts.MergeConflicts.GetContentToRender(isFocused)
+
+ var task updateTask
+ if gui.State.Contexts.MergeConflicts.IsUserScrolling() {
+ task = NewRenderStringWithoutScrollTask(content)
+ } else {
+ originY := gui.State.Contexts.MergeConflicts.GetOriginY()
+ task = NewRenderStringWithScrollTask(content, 0, originY)
+ }
+
+ return gui.refreshMainViews(refreshMainOpts{
+ pair: gui.mergingMainContextPair(),
+ main: &viewUpdateOpts{
+ task: task,
+ },
+ })
+}
diff --git a/pkg/gui/searching.go b/pkg/gui/searching.go
index c38c77e0a..a8580655c 100644
--- a/pkg/gui/searching.go
+++ b/pkg/gui/searching.go
@@ -3,6 +3,7 @@ package gui
import (
"fmt"
+ "github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/theme"
)
@@ -52,7 +53,7 @@ func (gui *Gui) onSelectItemWrapper(innerFunc func(int) error) func(int, int, in
fmt.Sprintf(
"no matches for '%s' %s",
gui.State.Searching.searchString,
- theme.OptionsFgColor.Sprintf("%s: exit search mode", gui.getKeyDisplay(keybindingConfig.Universal.Return)),
+ theme.OptionsFgColor.Sprintf("%s: exit search mode", keybindings.Label(keybindingConfig.Universal.Return)),
),
)
}
@@ -65,9 +66,9 @@ func (gui *Gui) onSelectItemWrapper(innerFunc func(int) error) func(int, int, in
total,
theme.OptionsFgColor.Sprintf(
"%s: next match, %s: previous match, %s: exit search mode",
- gui.getKeyDisplay(keybindingConfig.Universal.NextMatch),
- gui.getKeyDisplay(keybindingConfig.Universal.PrevMatch),
- gui.getKeyDisplay(keybindingConfig.Universal.Return),
+ keybindings.Label(keybindingConfig.Universal.NextMatch),
+ keybindings.Label(keybindingConfig.Universal.PrevMatch),
+ keybindings.Label(keybindingConfig.Universal.Return),
),
),
)
diff --git a/pkg/gui/services/custom_commands/client.go b/pkg/gui/services/custom_commands/client.go
index a3452067c..aeaae084e 100644
--- a/pkg/gui/services/custom_commands/client.go
+++ b/pkg/gui/services/custom_commands/client.go
@@ -23,11 +23,10 @@ func NewClient(
git *commands.GitCommand,
contexts *context.ContextTree,
helpers *helpers.Helpers,
- getKey func(string) types.Key,
) *Client {
sessionStateLoader := NewSessionStateLoader(contexts, helpers)
handlerCreator := NewHandlerCreator(c, os, git, sessionStateLoader)
- keybindingCreator := NewKeybindingCreator(contexts, getKey)
+ keybindingCreator := NewKeybindingCreator(contexts)
customCommands := c.UserConfig.CustomCommands
return &Client{
diff --git a/pkg/gui/services/custom_commands/keybinding_creator.go b/pkg/gui/services/custom_commands/keybinding_creator.go
index 7df044c28..7251225fe 100644
--- a/pkg/gui/services/custom_commands/keybinding_creator.go
+++ b/pkg/gui/services/custom_commands/keybinding_creator.go
@@ -8,19 +8,18 @@ import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/context"
+ "github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
// KeybindingCreator takes a custom command along with its handler and returns a corresponding keybinding
type KeybindingCreator struct {
contexts *context.ContextTree
- getKey func(string) types.Key
}
-func NewKeybindingCreator(contexts *context.ContextTree, getKey func(string) types.Key) *KeybindingCreator {
+func NewKeybindingCreator(contexts *context.ContextTree) *KeybindingCreator {
return &KeybindingCreator{
contexts: contexts,
- getKey: getKey,
}
}
@@ -41,7 +40,7 @@ func (self *KeybindingCreator) call(customCommand config.CustomCommand, handler
return &types.Binding{
ViewName: viewName,
- Key: self.getKey(customCommand.Key),
+ Key: keybindings.GetKey(customCommand.Key),
Modifier: gocui.ModNone,
Handler: handler,
Description: description,
diff --git a/pkg/gui/tasks_adapter.go b/pkg/gui/tasks_adapter.go
index 59a0670ed..33aa09eb9 100644
--- a/pkg/gui/tasks_adapter.go
+++ b/pkg/gui/tasks_adapter.go
@@ -64,6 +64,22 @@ func (gui *Gui) newStringTaskWithoutScroll(view *gocui.View, str string) error {
return nil
}
+func (gui *Gui) newStringTaskWithScroll(view *gocui.View, str string, originX int, originY int) error {
+ manager := gui.getManager(view)
+
+ f := func(stop chan struct{}) error {
+ gui.setViewContent(view, str)
+ _ = view.SetOrigin(originX, originY)
+ return nil
+ }
+
+ if err := manager.NewTask(f, ""); err != nil {
+ return err
+ }
+
+ return nil
+}
+
func (gui *Gui) newStringTaskWithKey(view *gocui.View, str string, key string) error {
manager := gui.getManager(view)
diff --git a/pkg/gui/types/refresh.go b/pkg/gui/types/refresh.go
index 34e3a65d3..475b90942 100644
--- a/pkg/gui/types/refresh.go
+++ b/pkg/gui/types/refresh.go
@@ -16,6 +16,7 @@ const (
SUBMODULES
STAGING
PATCH_BUILDING
+ MERGE_CONFLICTS
COMMIT_FILES
// not actually a view. Will refactor this later
BISECT_INFO
diff --git a/pkg/gui/updates.go b/pkg/gui/updates.go
index 7f492bbe7..93231e4f0 100644
--- a/pkg/gui/updates.go
+++ b/pkg/gui/updates.go
@@ -58,7 +58,7 @@ func (gui *Gui) startUpdating(newVersion string) {
func (gui *Gui) onUpdateFinish(statusId int, err error) error {
gui.State.Updating = false
gui.statusManager.removeStatus(statusId)
- gui.OnUIThread(func() error {
+ gui.c.OnUIThread(func() error {
_ = gui.renderString(gui.Views.AppStatus, "")
if err != nil {
errMessage := utils.ResolvePlaceholderString(
diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go
index 775a024df..40bc2fa02 100644
--- a/pkg/gui/view_helpers.go
+++ b/pkg/gui/view_helpers.go
@@ -4,6 +4,7 @@ import (
"fmt"
"github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/spkg/bom"
@@ -99,13 +100,13 @@ func (gui *Gui) globalOptionsMap() map[string]string {
keybindingConfig := gui.c.UserConfig.Keybinding
return map[string]string{
- fmt.Sprintf("%s/%s", gui.getKeyDisplay(keybindingConfig.Universal.ScrollUpMain), gui.getKeyDisplay(keybindingConfig.Universal.ScrollDownMain)): gui.c.Tr.LcScroll,
- fmt.Sprintf("%s %s %s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevBlock), gui.getKeyDisplay(keybindingConfig.Universal.NextBlock), gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcNavigate,
- gui.getKeyDisplay(keybindingConfig.Universal.Return): gui.c.Tr.LcCancel,
- gui.getKeyDisplay(keybindingConfig.Universal.Quit): gui.c.Tr.LcQuit,
- gui.getKeyDisplay(keybindingConfig.Universal.OptionMenu): gui.c.Tr.LcMenu,
- fmt.Sprintf("%s-%s", gui.getKeyDisplay(keybindingConfig.Universal.JumpToBlock[0]), gui.getKeyDisplay(keybindingConfig.Universal.JumpToBlock[len(keybindingConfig.Universal.JumpToBlock)-1])): gui.c.Tr.LcJump,
- fmt.Sprintf("%s/%s", gui.getKeyDisplay(keybindingConfig.Universal.ScrollLeft), gui.getKeyDisplay(keybindingConfig.Universal.ScrollRight)): gui.c.Tr.LcScrollLeftRight,
+ fmt.Sprintf("%s/%s", keybindings.Label(keybindingConfig.Universal.ScrollUpMain), keybindings.Label(keybindingConfig.Universal.ScrollDownMain)): gui.c.Tr.LcScroll,
+ fmt.Sprintf("%s %s %s %s", keybindings.Label(keybindingConfig.Universal.PrevBlock), keybindings.Label(keybindingConfig.Universal.NextBlock), keybindings.Label(keybindingConfig.Universal.PrevItem), keybindings.Label(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcNavigate,
+ keybindings.Label(keybindingConfig.Universal.Return): gui.c.Tr.LcCancel,
+ keybindings.Label(keybindingConfig.Universal.Quit): gui.c.Tr.LcQuit,
+ keybindings.Label(keybindingConfig.Universal.OptionMenu): gui.c.Tr.LcMenu,
+ fmt.Sprintf("%s-%s", keybindings.Label(keybindingConfig.Universal.JumpToBlock[0]), keybindings.Label(keybindingConfig.Universal.JumpToBlock[len(keybindingConfig.Universal.JumpToBlock)-1])): gui.c.Tr.LcJump,
+ fmt.Sprintf("%s/%s", keybindings.Label(keybindingConfig.Universal.ScrollLeft), keybindings.Label(keybindingConfig.Universal.ScrollRight)): gui.c.Tr.LcScrollLeftRight,
}
}
@@ -192,5 +193,5 @@ func getTabbedView(gui *Gui) *gocui.View {
}
func (gui *Gui) render() {
- gui.OnUIThread(func() error { return nil })
+ gui.c.OnUIThread(func() error { return nil })
}
diff --git a/pkg/integration/integration.go b/pkg/integration/integration.go
index c56134a4a..ab176d121 100644
--- a/pkg/integration/integration.go
+++ b/pkg/integration/integration.go
@@ -202,7 +202,7 @@ func RunTests(
// validates that the actual and expected dirs have the same repo names (doesn't actually check the contents of the repos)
func validateSameRepos(expectedDir string, actualDir string) error {
- // iterate through each repo in the expected dir and comparet to the corresponding repo in the actual dir
+ // iterate through each repo in the expected dir and compare to the corresponding repo in the actual dir
expectedFiles, err := ioutil.ReadDir(expectedDir)
if err != nil {
return err