From 7410acd1aaa97f678295a328264360802346b33a Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 6 Aug 2022 18:50:52 +1000 Subject: [PATCH] move merge conflicts code into controller --- pkg/cheatsheet/generate.go | 6 +- pkg/commands/oscommands/cmd_obj_runner_win.go | 3 +- pkg/gui/app_status_manager.go | 4 +- pkg/gui/command_log_panel.go | 5 +- pkg/gui/commit_message_panel.go | 7 +- pkg/gui/confirmation_panel.go | 33 +- pkg/gui/context/menu_context.go | 2 +- pkg/gui/context/merge_conflicts_context.go | 59 +++- pkg/gui/context/patch_explorer_context.go | 27 +- pkg/gui/context_config.go | 22 +- pkg/gui/controllers.go | 8 +- pkg/gui/controllers/files_controller.go | 13 +- pkg/gui/controllers/helpers/helpers.go | 2 + .../helpers/merge_and_rebase_helper.go | 2 - .../helpers/merge_conflicts_helper.go | 115 +++++++ .../controllers/merge_conflicts_controller.go | 281 +++++++++++++++- ...oller.go => vertical_scroll_controller.go} | 0 pkg/gui/editors.go | 3 +- pkg/gui/files_panel.go | 29 +- pkg/gui/global_handlers.go | 41 +-- pkg/gui/gui.go | 16 +- pkg/gui/gui_common.go | 2 +- pkg/gui/keybindings.go | 130 +------- pkg/gui/keybindings/keybindings.go | 36 +- pkg/gui/list_context_config.go | 2 +- pkg/gui/main_panels.go | 20 +- pkg/gui/menu_panel.go | 7 +- pkg/gui/merge_panel.go | 315 ------------------ pkg/gui/mergeconflicts/state.go | 11 +- pkg/gui/options_menu_panel.go | 2 +- pkg/gui/refresh.go | 74 ++-- pkg/gui/searching.go | 9 +- pkg/gui/services/custom_commands/client.go | 3 +- .../custom_commands/keybinding_creator.go | 7 +- pkg/gui/tasks_adapter.go | 16 + pkg/gui/types/refresh.go | 1 + pkg/gui/updates.go | 2 +- pkg/gui/view_helpers.go | 17 +- pkg/integration/integration.go | 2 +- 39 files changed, 682 insertions(+), 652 deletions(-) create mode 100644 pkg/gui/controllers/helpers/merge_conflicts_helper.go rename pkg/gui/controllers/{scroll_controller.go => vertical_scroll_controller.go} (100%) delete mode 100644 pkg/gui/merge_panel.go 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