mirror of
				https://github.com/jesseduffield/lazygit.git
				synced 2025-10-30 23:57:43 +02:00 
			
		
		
		
	move merge conflicts code into controller
This commit is contained in:
		| @@ -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( | ||||
| 			"  <kbd>%s</kbd>: %s (%s)\n", | ||||
| 			keybindings.GetKeyDisplay(binding.Key), | ||||
| 			keybindings.LabelFromKey(binding.Key), | ||||
| 			binding.Description, | ||||
| 			binding.Alternative, | ||||
| 		) | ||||
| 	} | ||||
| 	return fmt.Sprintf("  <kbd>%s</kbd>: %s\n", keybindings.GetKeyDisplay(binding.Key), binding.Description) | ||||
| 	return fmt.Sprintf("  <kbd>%s</kbd>: %s\n", keybindings.LabelFromKey(binding.Key), binding.Description) | ||||
| } | ||||
|   | ||||
| @@ -7,7 +7,8 @@ import ( | ||||
| 	"bytes" | ||||
| 	"io" | ||||
| 	"os/exec" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/sasha-s/go-deadlock" | ||||
| ) | ||||
|  | ||||
| type Buffer struct { | ||||
|   | ||||
| @@ -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) | ||||
| 			}) | ||||
| 		} | ||||
|   | ||||
| @@ -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{ | ||||
|   | ||||
| @@ -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), | ||||
| 		}, | ||||
| 	) | ||||
|  | ||||
|   | ||||
| @@ -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), | ||||
| 		}, | ||||
| 	) | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| 	}) | ||||
|   | ||||
| @@ -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)))) | ||||
| } | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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{}, | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
							
								
								
									
										115
									
								
								pkg/gui/controllers/helpers/merge_conflicts_helper.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								pkg/gui/controllers/helpers/merge_conflicts_helper.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
| @@ -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() | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
| 	} | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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()) | ||||
|  | ||||
|   | ||||
| @@ -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() | ||||
| 	}) | ||||
|   | ||||
| @@ -87,5 +87,5 @@ func (self *guiCommon) OpenSearch() { | ||||
| } | ||||
|  | ||||
| func (self *guiCommon) OnUIThread(f func() error) { | ||||
| 	self.gui.OnUIThread(f) | ||||
| 	self.gui.onUIThread(f) | ||||
| } | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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{ | ||||
| 	"<c-a>":       gocui.KeyCtrlA, | ||||
| 	"<c-b>":       gocui.KeyCtrlB, | ||||
| 	"<c-c>":       gocui.KeyCtrlC, | ||||
| @@ -142,14 +146,18 @@ var Keymap = map[string]types.Key{ | ||||
| 	"<right>":     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 | ||||
| } | ||||
|   | ||||
| @@ -33,7 +33,7 @@ func (gui *Gui) filesListContext() *context.WorkingTreeContext { | ||||
| 				return []string{line} | ||||
| 			}) | ||||
| 		}, | ||||
| 		OnFocusWrapper(gui.onFocusFile), | ||||
| 		nil, | ||||
| 		gui.withDiffModeCheck(gui.filesRenderToMain), | ||||
| 		nil, | ||||
| 		gui.c, | ||||
|   | ||||
| @@ -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) | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
| @@ -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 != "" | ||||
| } | ||||
|   | ||||
| @@ -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" { | ||||
|   | ||||
| @@ -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, | ||||
| 		}, | ||||
| 	}) | ||||
| } | ||||
|   | ||||
| @@ -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), | ||||
| 				), | ||||
| 			), | ||||
| 		) | ||||
|   | ||||
| @@ -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{ | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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) | ||||
|  | ||||
|   | ||||
| @@ -16,6 +16,7 @@ const ( | ||||
| 	SUBMODULES | ||||
| 	STAGING | ||||
| 	PATCH_BUILDING | ||||
| 	MERGE_CONFLICTS | ||||
| 	COMMIT_FILES | ||||
| 	// not actually a view. Will refactor this later | ||||
| 	BISECT_INFO | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
| @@ -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 }) | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user