summaryrefslogtreecommitdiffstats
path: root/pkg/gui/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/gui/controllers')
-rw-r--r--pkg/gui/controllers/bisect_controller.go2
-rw-r--r--pkg/gui/controllers/commits_files_controller.go10
-rw-r--r--pkg/gui/controllers/context_lines_controller.go116
-rw-r--r--pkg/gui/controllers/files_controller.go32
-rw-r--r--pkg/gui/controllers/helpers/patch_building_helper.go37
-rw-r--r--pkg/gui/controllers/list_controller.go37
-rw-r--r--pkg/gui/controllers/patch_building_controller.go138
-rw-r--r--pkg/gui/controllers/patch_explorer_controller.go289
-rw-r--r--pkg/gui/controllers/scroll_controller.go70
-rw-r--r--pkg/gui/controllers/staging_controller.go242
10 files changed, 937 insertions, 36 deletions
diff --git a/pkg/gui/controllers/bisect_controller.go b/pkg/gui/controllers/bisect_controller.go
index 805911030..083b6dce8 100644
--- a/pkg/gui/controllers/bisect_controller.go
+++ b/pkg/gui/controllers/bisect_controller.go
@@ -222,7 +222,7 @@ func (self *BisectController) selectCurrentBisectCommit() {
for i, commit := range self.model.Commits {
if commit.Sha == info.GetCurrentSha() {
self.context().SetSelectedLineIdx(i)
- _ = self.context().HandleFocus()
+ _ = self.context().HandleFocus(types.OnFocusOpts{})
break
}
}
diff --git a/pkg/gui/controllers/commits_files_controller.go b/pkg/gui/controllers/commits_files_controller.go
index 5beecba02..f11005cd4 100644
--- a/pkg/gui/controllers/commits_files_controller.go
+++ b/pkg/gui/controllers/commits_files_controller.go
@@ -75,10 +75,10 @@ func (self *CommitFilesController) GetKeybindings(opts types.KeybindingsOpts) []
func (self *CommitFilesController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{
{
- ViewName: "main",
+ ViewName: "patchBuilding",
Key: gocui.MouseLeft,
Handler: self.onClickMain,
- FromContext: string(self.context().GetKey()),
+ FocusedView: self.context().GetViewName(),
},
}
}
@@ -107,7 +107,7 @@ func (self *CommitFilesController) onClickMain(opts gocui.ViewMouseBindingOpts)
if node == nil {
return nil
}
- return self.enterCommitFile(node, types.OnFocusOpts{ClickedViewName: "main", ClickedViewLineIdx: opts.Y})
+ return self.enterCommitFile(node, types.OnFocusOpts{ClickedWindowName: "main", ClickedViewLineIdx: opts.Y})
}
func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error {
@@ -220,7 +220,7 @@ func (self *CommitFilesController) startPatchManager() error {
}
func (self *CommitFilesController) enter(node *filetree.CommitFileNode) error {
- return self.enterCommitFile(node, types.OnFocusOpts{ClickedViewName: "", ClickedViewLineIdx: -1})
+ return self.enterCommitFile(node, types.OnFocusOpts{ClickedWindowName: "", ClickedViewLineIdx: -1})
}
func (self *CommitFilesController) enterCommitFile(node *filetree.CommitFileNode, opts types.OnFocusOpts) error {
@@ -235,7 +235,7 @@ func (self *CommitFilesController) enterCommitFile(node *filetree.CommitFileNode
}
}
- return self.c.PushContext(self.contexts.PatchBuilding, opts)
+ return self.c.PushContext(self.contexts.CustomPatchBuilder, opts)
}
if self.git.Patch.PatchManager.Active() && self.git.Patch.PatchManager.To != self.context().GetRef().RefName() {
diff --git a/pkg/gui/controllers/context_lines_controller.go b/pkg/gui/controllers/context_lines_controller.go
new file mode 100644
index 000000000..c90bd9c9f
--- /dev/null
+++ b/pkg/gui/controllers/context_lines_controller.go
@@ -0,0 +1,116 @@
+package controllers
+
+import (
+ "errors"
+
+ "github.com/jesseduffield/lazygit/pkg/gui/context"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+ "github.com/samber/lo"
+)
+
+// This controller lets you change the context size for diffs. The 'context' in 'context size' refers to the conventional meaning of the word 'context' in a diff, as opposed to lazygit's own idea of a 'context'.
+
+var CONTEXT_KEYS_SHOWING_DIFFS = []types.ContextKey{
+ context.FILES_CONTEXT_KEY,
+ context.COMMIT_FILES_CONTEXT_KEY,
+ context.STASH_CONTEXT_KEY,
+ context.LOCAL_COMMITS_CONTEXT_KEY,
+ context.SUB_COMMITS_CONTEXT_KEY,
+ context.STAGING_MAIN_CONTEXT_KEY,
+ context.STAGING_SECONDARY_CONTEXT_KEY,
+ context.PATCH_BUILDING_MAIN_CONTEXT_KEY,
+ context.PATCH_BUILDING_SECONDARY_CONTEXT_KEY,
+}
+
+type ContextLinesController struct {
+ baseController
+ *controllerCommon
+}
+
+var _ types.IController = &ContextLinesController{}
+
+func NewContextLinesController(
+ common *controllerCommon,
+) *ContextLinesController {
+ return &ContextLinesController{
+ baseController: baseController{},
+ controllerCommon: common,
+ }
+}
+
+func (self *ContextLinesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
+ bindings := []*types.Binding{
+ {
+ Key: opts.GetKey(opts.Config.Universal.IncreaseContextInDiffView),
+ Handler: self.Increase,
+ Description: self.c.Tr.IncreaseContextInDiffView,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.DecreaseContextInDiffView),
+ Handler: self.Decrease,
+ Description: self.c.Tr.DecreaseContextInDiffView,
+ },
+ }
+
+ return bindings
+}
+
+func (self *ContextLinesController) Context() types.Context {
+ return nil
+}
+
+func (self *ContextLinesController) Increase() error {
+ if self.isShowingDiff() {
+ if err := self.checkCanChangeContext(); err != nil {
+ return self.c.Error(err)
+ }
+
+ self.c.UserConfig.Git.DiffContextSize = self.c.UserConfig.Git.DiffContextSize + 1
+ return self.applyChange()
+ }
+
+ return nil
+}
+
+func (self *ContextLinesController) Decrease() error {
+ old_size := self.c.UserConfig.Git.DiffContextSize
+
+ if self.isShowingDiff() && old_size > 1 {
+ if err := self.checkCanChangeContext(); err != nil {
+ return self.c.Error(err)
+ }
+
+ self.c.UserConfig.Git.DiffContextSize = old_size - 1
+ return self.applyChange()
+ }
+
+ return nil
+}
+
+func (self *ContextLinesController) applyChange() error {
+ currentContext := self.c.CurrentStaticContext()
+ switch currentContext.GetKey() {
+ // we make an exception for our staging and patch building contexts because they actually need to refresh their state afterwards.
+ case context.PATCH_BUILDING_MAIN_CONTEXT_KEY:
+ return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.PATCH_BUILDING}})
+ case context.STAGING_MAIN_CONTEXT_KEY, context.STAGING_SECONDARY_CONTEXT_KEY:
+ return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STAGING}})
+ default:
+ return currentContext.HandleRenderToMain()
+ }
+}
+
+func (self *ContextLinesController) checkCanChangeContext() error {
+ if self.git.Patch.PatchManager.Active() {
+ return errors.New(self.c.Tr.CantChangeContextSizeError)
+ }
+
+ return nil
+}
+
+func (self *ContextLinesController) isShowingDiff() bool {
+ return lo.Contains(
+ CONTEXT_KEYS_SHOWING_DIFFS,
+ self.c.CurrentStaticContext().GetKey(),
+ )
+}
diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go
index 5304d0d81..0d2a10b35 100644
--- a/pkg/gui/controllers/files_controller.go
+++ b/pkg/gui/controllers/files_controller.go
@@ -152,13 +152,31 @@ func (self *FilesController) GetMouseKeybindings(opts types.KeybindingsOpts) []*
ViewName: "main",
Key: gocui.MouseLeft,
Handler: self.onClickMain,
- FromContext: string(self.context().GetKey()),
+ FocusedView: self.context().GetViewName(),
+ },
+ {
+ ViewName: "patchBuilding",
+ Key: gocui.MouseLeft,
+ Handler: self.onClickMain,
+ FocusedView: self.context().GetViewName(),
+ },
+ {
+ ViewName: "merging",
+ Key: gocui.MouseLeft,
+ Handler: self.onClickMain,
+ FocusedView: self.context().GetViewName(),
},
{
ViewName: "secondary",
Key: gocui.MouseLeft,
Handler: self.onClickSecondary,
- FromContext: string(self.context().GetKey()),
+ FocusedView: self.context().GetViewName(),
+ },
+ {
+ ViewName: "patchBuildingSecondary",
+ Key: gocui.MouseLeft,
+ Handler: self.onClickSecondary,
+ FocusedView: self.context().GetViewName(),
},
}
}
@@ -318,7 +336,7 @@ func (self *FilesController) press(node *filetree.FileNode) error {
return err
}
- return self.context().HandleFocus()
+ return self.context().HandleFocus(types.OnFocusOpts{})
}
func (self *FilesController) checkSelectedFileNode(callback func(*filetree.FileNode) error) func() error {
@@ -349,7 +367,7 @@ func (self *FilesController) getSelectedFile() *models.File {
}
func (self *FilesController) enter() error {
- return self.EnterFile(types.OnFocusOpts{ClickedViewName: "", ClickedViewLineIdx: -1})
+ return self.EnterFile(types.OnFocusOpts{ClickedWindowName: "", ClickedViewLineIdx: -1})
}
func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
@@ -389,7 +407,7 @@ func (self *FilesController) toggleStagedAll() error {
return err
}
- return self.context().HandleFocus()
+ return self.context().HandleFocus(types.OnFocusOpts{})
}
func (self *FilesController) toggleStagedAllWithLock() error {
@@ -828,11 +846,11 @@ func (self *FilesController) handleStashSave(stashFunc func(message string) erro
}
func (self *FilesController) onClickMain(opts gocui.ViewMouseBindingOpts) error {
- return self.EnterFile(types.OnFocusOpts{ClickedViewName: "main", ClickedViewLineIdx: opts.Y})
+ return self.EnterFile(types.OnFocusOpts{ClickedWindowName: "main", ClickedViewLineIdx: opts.Y})
}
func (self *FilesController) onClickSecondary(opts gocui.ViewMouseBindingOpts) error {
- return self.EnterFile(types.OnFocusOpts{ClickedViewName: "secondary", ClickedViewLineIdx: opts.Y})
+ return self.EnterFile(types.OnFocusOpts{ClickedWindowName: "secondary", ClickedViewLineIdx: opts.Y})
}
func (self *FilesController) fetch() error {
diff --git a/pkg/gui/controllers/helpers/patch_building_helper.go b/pkg/gui/controllers/helpers/patch_building_helper.go
index efb8ec671..25ac63a08 100644
--- a/pkg/gui/controllers/helpers/patch_building_helper.go
+++ b/pkg/gui/controllers/helpers/patch_building_helper.go
@@ -3,6 +3,7 @@ package helpers
import (
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
+ "github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -11,17 +12,20 @@ type IPatchBuildingHelper interface {
}
type PatchBuildingHelper struct {
- c *types.HelperCommon
- git *commands.GitCommand
+ c *types.HelperCommon
+ git *commands.GitCommand
+ contexts *context.ContextTree
}
func NewPatchBuildingHelper(
c *types.HelperCommon,
git *commands.GitCommand,
+ contexts *context.ContextTree,
) *PatchBuildingHelper {
return &PatchBuildingHelper{
- c: c,
- git: git,
+ c: c,
+ git: git,
+ contexts: contexts,
}
}
@@ -31,3 +35,28 @@ func (self *PatchBuildingHelper) ValidateNormalWorkingTreeState() (bool, error)
}
return true, nil
}
+
+// takes us from the patch building panel back to the commit files panel
+func (self *PatchBuildingHelper) Escape() error {
+ return self.c.PushContext(self.contexts.CommitFiles)
+}
+
+// kills the custom patch and returns us back to the commit files panel if needed
+func (self *PatchBuildingHelper) Reset() error {
+ self.git.Patch.PatchManager.Reset()
+
+ if self.c.CurrentStaticContext().GetKind() != types.SIDE_CONTEXT {
+ if err := self.Escape(); err != nil {
+ return err
+ }
+ }
+
+ if err := self.c.Refresh(types.RefreshOptions{
+ Scope: []types.RefreshableView{types.COMMIT_FILES},
+ }); err != nil {
+ return err
+ }
+
+ // refreshing the current context so that the secondary panel is hidden if necessary.
+ return self.c.PostRefreshUpdate(self.c.CurrentContext())
+}
diff --git a/pkg/gui/controllers/list_controller.go b/pkg/gui/controllers/list_controller.go
index 7da05710e..c74d87244 100644
--- a/pkg/gui/controllers/list_controller.go
+++ b/pkg/gui/controllers/list_controller.go
@@ -51,22 +51,24 @@ func (self *ListController) HandleScrollRight() error {
}
func (self *ListController) HandleScrollUp() error {
- self.context.GetViewTrait().ScrollUp()
+ scrollHeight := self.c.UserConfig.Gui.ScrollHeight
+ self.context.GetViewTrait().ScrollUp(scrollHeight)
// we only need to do a line change if our line has been pushed out of the viewport, because
// at the moment much logic depends on the selected line always being visible
if !self.isSelectedLineInViewPort() {
- return self.handleLineChange(-1)
+ return self.handleLineChange(-scrollHeight)
}
return nil
}
func (self *ListController) HandleScrollDown() error {
- self.context.GetViewTrait().ScrollDown()
+ scrollHeight := self.c.UserConfig.Gui.ScrollHeight
+ self.context.GetViewTrait().ScrollDown(scrollHeight)
if !self.isSelectedLineInViewPort() {
- return self.handleLineChange(1)
+ return self.handleLineChange(scrollHeight)
}
return nil
@@ -81,7 +83,7 @@ func (self *ListController) isSelectedLineInViewPort() bool {
func (self *ListController) scrollHorizontal(scrollFunc func()) error {
scrollFunc()
- return self.context.HandleFocus()
+ return self.context.HandleFocus(types.OnFocusOpts{})
}
func (self *ListController) handleLineChange(change int) error {
@@ -96,7 +98,7 @@ func (self *ListController) handleLineChange(change int) error {
// doing this check so that if we're holding the up key at the start of the list
// we're not constantly re-rendering the main view.
if before != after {
- return self.context.HandleFocus()
+ return self.context.HandleFocus(types.OnFocusOpts{})
}
return nil
@@ -136,7 +138,7 @@ func (self *ListController) HandleClick(opts gocui.ViewMouseBindingOpts) error {
if prevSelectedLineIdx == newSelectedLineIdx && alreadyFocused && self.context.GetOnClick() != nil {
return self.context.GetOnClick()()
}
- return self.context.HandleFocus()
+ return self.context.HandleFocus(types.OnFocusOpts{})
}
func (self *ListController) pushContextIfNotFocused() error {
@@ -182,22 +184,19 @@ func (self *ListController) GetKeybindings(opts types.KeybindingsOpts) []*types.
func (self *ListController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{
{
- ViewName: self.context.GetViewName(),
- ToContext: string(self.context.GetKey()),
- Key: gocui.MouseWheelUp,
- Handler: func(gocui.ViewMouseBindingOpts) error { return self.HandleScrollUp() },
+ ViewName: self.context.GetViewName(),
+ Key: gocui.MouseWheelUp,
+ Handler: func(gocui.ViewMouseBindingOpts) error { return self.HandleScrollUp() },
},
{
- ViewName: self.context.GetViewName(),
- ToContext: string(self.context.GetKey()),
- Key: gocui.MouseLeft,
- Handler: func(opts gocui.ViewMouseBindingOpts) error { return self.HandleClick(opts) },
+ ViewName: self.context.GetViewName(),
+ Key: gocui.MouseLeft,
+ Handler: func(opts gocui.ViewMouseBindingOpts) error { return self.HandleClick(opts) },
},
{
- ViewName: self.context.GetViewName(),
- ToContext: string(self.context.GetKey()),
- Key: gocui.MouseWheelDown,
- Handler: func(gocui.ViewMouseBindingOpts) error { return self.HandleScrollDown() },
+ ViewName: self.context.GetViewName(),
+ Key: gocui.MouseWheelDown,
+ Handler: func(gocui.ViewMouseBindingOpts) error { return self.HandleScrollDown() },
},
}
}
diff --git a/pkg/gui/controllers/patch_building_controller.go b/pkg/gui/controllers/patch_building_controller.go
new file mode 100644
index 000000000..798472f7f
--- /dev/null
+++ b/pkg/gui/controllers/patch_building_controller.go
@@ -0,0 +1,138 @@
+package controllers
+
+import (
+ "github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+ "github.com/samber/lo"
+)
+
+type PatchBuildingController struct {
+ baseController
+ *controllerCommon
+}
+
+var _ types.IController = &PatchBuildingController{}
+
+func NewPatchBuildingController(
+ common *controllerCommon,
+) *PatchBuildingController {
+ return &PatchBuildingController{
+ baseController: baseController{},
+ controllerCommon: common,
+ }
+}
+
+func (self *PatchBuildingController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
+ return []*types.Binding{
+ {
+ Key: opts.GetKey(opts.Config.Universal.OpenFile),
+ Handler: self.OpenFile,
+ Description: self.c.Tr.LcOpenFile,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.Edit),
+ Handler: self.EditFile,
+ Description: self.c.Tr.LcEditFile,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.Select),
+ Handler: self.ToggleSelectionAndRefresh,
+ Description: self.c.Tr.ToggleSelectionForPatch,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.Return),
+ Handler: self.Escape,
+ Description: self.c.Tr.ExitCustomPatchBuilder,
+ },
+ }
+}
+
+func (self *PatchBuildingController) Context() types.Context {
+ return self.contexts.CustomPatchBuilder
+}
+
+func (self *PatchBuildingController) context() types.IPatchExplorerContext {
+ return self.contexts.CustomPatchBuilder
+}
+
+func (self *PatchBuildingController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
+ return []*gocui.ViewMouseBinding{}
+}
+
+func (self *PatchBuildingController) OpenFile() error {
+ self.context().GetMutex().Lock()
+ defer self.context().GetMutex().Unlock()
+
+ path := self.contexts.CommitFiles.GetSelectedPath()
+
+ if path == "" {
+ return nil
+ }
+
+ lineNumber := self.context().GetState().CurrentLineNumber()
+ return self.helpers.Files.OpenFileAtLine(path, lineNumber)
+}
+
+func (self *PatchBuildingController) EditFile() error {
+ self.context().GetMutex().Lock()
+ defer self.context().GetMutex().Unlock()
+
+ path := self.contexts.CommitFiles.GetSelectedPath()
+
+ if path == "" {
+ return nil
+ }
+
+ lineNumber := self.context().GetState().CurrentLineNumber()
+ return self.helpers.Files.EditFileAtLine(path, lineNumber)
+}
+
+func (self *PatchBuildingController) ToggleSelectionAndRefresh() error {
+ if err := self.toggleSelection(); err != nil {
+ return err
+ }
+
+ return self.c.Refresh(types.RefreshOptions{
+ Scope: []types.RefreshableView{types.PATCH_BUILDING, types.COMMIT_FILES},
+ })
+}
+
+func (self *PatchBuildingController) toggleSelection() error {
+ self.context().GetMutex().Lock()
+ defer self.context().GetMutex().Unlock()
+
+ toggleFunc := self.git.Patch.PatchManager.AddFileLineRange
+ filename := self.contexts.CommitFiles.GetSelectedPath()
+ if filename == "" {
+ return nil
+ }
+
+ state := self.context().GetState()
+
+ includedLineIndices, err := self.git.Patch.PatchManager.GetFileIncLineIndices(filename)
+ if err != nil {
+ return err
+ }
+ currentLineIsStaged := lo.Contains(includedLineIndices, state.GetSelectedLineIdx())
+ if currentLineIsStaged {
+ toggleFunc = self.git.Patch.PatchManager.RemoveFileLineRange
+ }
+
+ // add range of lines to those set for the file
+ firstLineIdx, lastLineIdx := state.SelectedRange()
+
+ if err := toggleFunc(filename, firstLineIdx, lastLineIdx); err != nil {
+ // might actually want to return an error here
+ self.c.Log.Error(err)
+ }
+
+ if state.SelectingRange() {
+ state.SetLineSelectMode()
+ }
+
+ return nil
+}
+
+func (self *PatchBuildingController) Escape() error {
+ return self.helpers.PatchBuilding.Escape()
+}
diff --git a/pkg/gui/controllers/patch_explorer_controller.go b/pkg/gui/controllers/patch_explorer_controller.go
new file mode 100644
index 000000000..dac63a7b1
--- /dev/null
+++ b/pkg/gui/controllers/patch_explorer_controller.go
@@ -0,0 +1,289 @@
+package controllers
+
+import (
+ "github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+)
+
+type PatchExplorerControllerFactory struct {
+ *controllerCommon
+}
+
+func NewPatchExplorerControllerFactory(c *controllerCommon) *PatchExplorerControllerFactory {
+ return &PatchExplorerControllerFactory{
+ controllerCommon: c,
+ }
+}
+
+func (self *PatchExplorerControllerFactory) Create(context types.IPatchExplorerContext) *PatchExplorerController {
+ return &PatchExplorerController{
+ baseController: baseController{},
+ controllerCommon: self.controllerCommon,
+ context: context,
+ }
+}
+
+type PatchExplorerController struct {
+ baseController
+ *controllerCommon
+
+ context types.IPatchExplorerContext
+}
+
+func (self *PatchExplorerController) Context() types.Context {
+ return self.context
+}
+
+func (self *PatchExplorerController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
+ return []*types.Binding{
+ {
+ Tag: "navigation",
+ Key: opts.GetKey(opts.Config.Universal.PrevItemAlt),
+ Handler: self.withRenderAndFocus(self.HandlePrevLine),
+ },
+ {
+ Tag: "navigation",
+ Key: opts.GetKey(opts.Config.Universal.PrevItem),
+ Handler: self.withRenderAndFocus(self.HandlePrevLine),
+ },
+ {
+ Tag: "navigation",
+ Key: opts.GetKey(opts.Config.Universal.NextItemAlt),
+ Handler: self.withRenderAndFocus(self.HandleNextLine),
+ },
+ {
+ Tag: "navigation",
+ Key: opts.GetKey(opts.Config.Universal.NextItem),
+ Handler: self.withRenderAndFocus(self.HandleNextLine),
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.PrevBlock),
+ Handler: self.withRenderAndFocus(self.HandlePrevHunk),
+ Description: self.c.Tr.PrevHunk,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt),
+ Handler: self.withRenderAndFocus(self.HandlePrevHunk),
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.NextBlock),
+ Handler: self.withRenderAndFocus(self.HandleNextHunk),
+ Description: self.c.Tr.NextHunk,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.NextBlockAlt),
+ Handler: self.withRenderAndFocus(self.HandleNextHunk),
+ },
+ {
+ Key: opts.GetKey(opts.Config.Main.ToggleDragSelect),
+ Handler: self.withRenderAndFocus(self.HandleToggleSelectRange),
+ Description: self.c.Tr.ToggleDragSelect,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Main.ToggleDragSelectAlt),
+ Handler: self.withRenderAndFocus(self.HandleToggleSelectRange),
+ Description: self.c.Tr.ToggleDragSelect,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Main.ToggleSelectHunk),
+ Handler: self.withRenderAndFocus(self.HandleToggleSelectHunk),
+ Description: self.c.Tr.ToggleSelectHunk,
+ },
+ {
+ Tag: "navigation",
+ Key: opts.GetKey(opts.Config.Universal.PrevPage),
+ Handler: self.withRenderAndFocus(self.HandlePrevPage),
+ Description: self.c.Tr.LcPrevPage,
+ },
+ {
+ Tag: "navigation",
+ Key: opts.GetKey(opts.Config.Universal.NextPage),
+ Handler: self.withRenderAndFocus(self.HandleNextPage),
+ Description: self.c.Tr.LcNextPage,
+ },
+ {
+ Tag: "navigation",
+ Key: opts.GetKey(opts.Config.Universal.GotoTop),
+ Handler: self.withRenderAndFocus(self.HandleGotoTop),
+ Description: self.c.Tr.LcGotoTop,
+ },
+ {
+ Tag: "navigation",
+ Key: opts.GetKey(opts.Config.Universal.GotoBottom),
+ Description: self.c.Tr.LcGotoBottom,
+ Handler: self.withRenderAndFocus(self.HandleGotoBottom),
+ },
+ {
+ Tag: "navigation",
+ Key: opts.GetKey(opts.Config.Universal.ScrollLeft),
+ Handler: self.withRenderAndFocus(self.HandleScrollLeft),
+ },
+ {
+ Tag: "navigation",
+ Key: opts.GetKey(opts.Config.Universal.ScrollRight),
+ Handler: self.withRenderAndFocus(self.HandleScrollRight),
+ },
+ {
+ Tag: "navigation",
+ Key: opts.GetKey(opts.Config.Universal.StartSearch),
+ Handler: func() error { self.c.OpenSearch(); return nil },
+ Description: self.c.Tr.LcStartSearch,
+ },
+ {
+ Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
+ Handler: self.withLock(self.CopySelectedToClipboard),
+ Description: self.c.Tr.LcCopySelectedTexToClipboard,
+ },
+ }
+}
+
+func (self *PatchExplorerController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
+ return []*gocui.ViewMouseBinding{
+ {
+ ViewName: self.context.GetViewName(),
+ Key: gocui.MouseLeft,
+ Handler: func(opts gocui.ViewMouseBindingOpts) error {
+ if self.isFocused() {
+ return self.withRenderAndFocus(self.HandleMouseDown)()
+ }
+
+ return self.c.PushContext(self.context, types.OnFocusOpts{
+ ClickedWindowName: self.context.GetWindowName(),
+ ClickedViewLineIdx: opts.Y,
+ })
+ },
+ },
+ {
+ ViewName: self.context.GetViewName(),
+ Key: gocui.MouseLeft,
+ Modifier: gocui.ModMotion,
+ Handler: func(gocui.ViewMouseBindingOpts) error {
+ return self.withRenderAndFocus(self.HandleMouseDrag)()
+ },
+ },
+ }
+}
+
+func (self *PatchExplorerController) HandlePrevLine() error {
+ self.context.GetState().CycleSelection(false)
+
+ return nil
+}
+
+func (self *PatchExplorerController) HandleNextLine() error {
+ self.context.GetState().CycleSelection(true)
+
+ return nil
+}
+
+func (self *PatchExplorerController) HandlePrevHunk() error {
+ self.context.GetState().CycleHunk(false)
+
+ return nil
+}
+
+func (self *PatchExplorerController) HandleNextHunk() error {
+ self.context.GetState().CycleHunk(true)
+
+ return nil
+}
+
+func (self *PatchExplorerController) HandleToggleSelectRange() error {
+ self.context.GetState().ToggleSelectRange()
+
+ return nil
+}
+
+func (self *PatchExplorerController) HandleToggleSelectHunk() error {
+ self.context.GetState().ToggleSelectHunk()
+
+ return nil
+}
+
+func (self *PatchExplorerController) HandleScrollLeft() error {
+ self.context.GetViewTrait().ScrollLeft()
+
+ return nil
+}
+
+func (self *PatchExplorerController) HandleScrollRight() error {
+ self.context.GetViewTrait().ScrollRight()
+
+ return nil
+}
+
+func (self *PatchExplorerController) HandlePrevPage() error {
+ self.context.GetState().SetLineSelectMode()
+ self.context.GetState().AdjustSelectedLineIdx(-self.context.GetViewTrait().PageDelta())
+
+ return nil
+}
+
+func (self *PatchExplorerController) HandleNextPage() error {
+ self.context.GetState().SetLineSelectMode()
+ self.context.GetState().AdjustSelectedLineIdx(self.context.GetViewTrait().PageDelta())
+
+ return nil
+}
+
+func (self *PatchExplorerController) HandleGotoTop() error {
+ self.context.GetState().SelectTop()
+
+ return nil
+}
+
+func (self *PatchExplorerController) HandleGotoBottom() error {
+ self.context.GetState().SelectBottom()
+
+ return nil
+}
+
+func (self *PatchExplorerController) HandleMouseDown() error {
+ self.context.GetState().SelectNewLineForRange(self.context.GetViewTrait().SelectedLineIdx())
+
+ return nil
+}
+
+func (self *PatchExplorerController) HandleMouseDrag() error {
+ self.context.GetState().SelectLine(self.context.GetViewTrait().SelectedLineIdx())
+
+ return nil
+}
+
+func (self *PatchExplorerController) CopySelectedToClipboard() error {
+ selected := self.context.GetState().PlainRenderSelected()
+
+ self.c.LogAction(self.c.Tr.Actions.CopySelectedTextToClipboard)
+ if err := self.os.CopyToClipboard(selected); err != nil {
+ return self.c.Error(err)
+ }
+
+ return nil
+}
+
+func (self *PatchExplorerController) isFocused() bool {
+ return self.c.CurrentContext().GetKey() == self.context.GetKey()
+}
+
+func (self *PatchExplorerController) 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 *PatchExplorerController) 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/scroll_controller.go
new file mode 100644
index 000000000..3f3e9d177
--- /dev/null
+++ b/pkg/gui/controllers/scroll_controller.go
@@ -0,0 +1,70 @@
+package controllers
+
+import (
+ "github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+)
+
+// given we have no fields here, arguably we shouldn't even need this factory
+// struct, but we're maintaining consistency with the other files.
+type VerticalScrollControllerFactory struct {
+ controllerCommon *controllerCommon
+}
+
+func NewVerticalScrollControllerFactory(c *controllerCommon) *VerticalScrollControllerFactory {
+ return &VerticalScrollControllerFactory{controllerCommon: c}
+}
+
+func (self *VerticalScrollControllerFactory) Create(context types.Context) types.IController {
+ return &VerticalScrollController{
+ baseController: baseController{},
+ controllerCommon: self.controllerCommon,
+ context: context,
+ }
+}
+
+type VerticalScrollController struct {
+ baseController
+ *controllerCommon
+
+ context types.Context
+}
+
+func (self *VerticalScrollController) Context() types.Context {
+ return self.context
+}
+
+func (self *VerticalScrollController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
+ return []*types.Binding{}
+}
+
+func (self *VerticalScrollController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
+ return []*gocui.ViewMouseBinding{
+ {
+ ViewName: se