diff --git a/go.mod b/go.mod index 4cd96901ad5..278aebf6b58 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/integrii/flaggy v1.4.0 github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd - github.com/jesseduffield/gocui v0.3.1-0.20251214132308-02ab34c1c624 + github.com/jesseduffield/gocui v0.3.1-0.20251223143206-950739ccd44a github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/karimkhaleel/jsonschema v0.0.0-20231001195015-d933f0d94ea3 diff --git a/go.sum b/go.sum index 70fbbbe6ffd..5ef0c6593c0 100644 --- a/go.sum +++ b/go.sum @@ -194,8 +194,8 @@ github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c h1:tC2Paiis github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c/go.mod h1:F2fEBk0ddf6ixrBrJjY7phfQ3hL9rXG0uSjvwYe50bE= github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd h1:ViKj6qth8FgcIWizn9KiACWwPemWSymx62OPN0tHT+Q= github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd/go.mod h1:lRhCiBr6XjQrvcQVa+UYsy/99d3wMXn/a0nSQlhnhlA= -github.com/jesseduffield/gocui v0.3.1-0.20251214132308-02ab34c1c624 h1:30mIX4f52zrO4fWfZQKHJG29t2apcSOtR/sbd3BNsVE= -github.com/jesseduffield/gocui v0.3.1-0.20251214132308-02ab34c1c624/go.mod h1:sLIyZ2J42R6idGdtemZzsiR3xY5EF0KsvYEGh3dQv3s= +github.com/jesseduffield/gocui v0.3.1-0.20251223143206-950739ccd44a h1:XRsyqrSljes4TlaPczQViIAA4xqdnB0fKEEpZdqWWTA= +github.com/jesseduffield/gocui v0.3.1-0.20251223143206-950739ccd44a/go.mod h1:sLIyZ2J42R6idGdtemZzsiR3xY5EF0KsvYEGh3dQv3s= github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY= github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5/go.mod h1:qxN4mHOAyeIDLP7IK7defgPClM/z1Kze8VVQiaEjzsQ= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= diff --git a/pkg/gui/context/list_context_trait.go b/pkg/gui/context/list_context_trait.go index cc1d402c067..2ea58360bfd 100644 --- a/pkg/gui/context/list_context_trait.go +++ b/pkg/gui/context/list_context_trait.go @@ -25,8 +25,8 @@ type ListContextTrait struct { func (self *ListContextTrait) IsListContext() {} -func (self *ListContextTrait) FocusLine() { - self.Context.FocusLine() +func (self *ListContextTrait) FocusLine(scrollIntoView bool) { + self.Context.FocusLine(scrollIntoView) // Doing this at the end of the layout function because we need the view to be // resized before we focus the line, otherwise if we're in accordion mode @@ -36,7 +36,7 @@ func (self *ListContextTrait) FocusLine() { oldOrigin, _ := self.GetViewTrait().ViewPortYBounds() self.GetViewTrait().FocusPoint( - self.ModelIndexToViewIndex(self.list.GetSelectedLineIdx())) + self.ModelIndexToViewIndex(self.list.GetSelectedLineIdx()), scrollIntoView) selectRangeIndex, isSelectingRange := self.list.GetRangeStartIdx() if isSelectingRange { @@ -75,7 +75,7 @@ func formatListFooter(selectedLineIdx int, length int) string { } func (self *ListContextTrait) HandleFocus(opts types.OnFocusOpts) { - self.FocusLine() + self.FocusLine(opts.ScrollSelectionIntoView) self.GetViewTrait().SetHighlight(self.list.Len() > 0) diff --git a/pkg/gui/context/simple_context.go b/pkg/gui/context/simple_context.go index a5b051b9349..f51d3dc5c99 100644 --- a/pkg/gui/context/simple_context.go +++ b/pkg/gui/context/simple_context.go @@ -54,7 +54,7 @@ func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) { } } -func (self *SimpleContext) FocusLine() { +func (self *SimpleContext) FocusLine(scrollIntoView bool) { } func (self *SimpleContext) HandleRender() { diff --git a/pkg/gui/context/view_trait.go b/pkg/gui/context/view_trait.go index ccf7d3e9623..9a30bde6f77 100644 --- a/pkg/gui/context/view_trait.go +++ b/pkg/gui/context/view_trait.go @@ -17,8 +17,8 @@ func NewViewTrait(view *gocui.View) *ViewTrait { return &ViewTrait{view: view} } -func (self *ViewTrait) FocusPoint(yIdx int) { - self.view.FocusPoint(self.view.OriginX(), yIdx) +func (self *ViewTrait) FocusPoint(yIdx int, scrollIntoView bool) { + self.view.FocusPoint(self.view.OriginX(), yIdx, scrollIntoView) } func (self *ViewTrait) SetRangeSelectStart(yIdx int) { diff --git a/pkg/gui/controllers/branches_controller.go b/pkg/gui/controllers/branches_controller.go index f430f7b0ef8..c796e19ab9e 100644 --- a/pkg/gui/controllers/branches_controller.go +++ b/pkg/gui/controllers/branches_controller.go @@ -531,7 +531,7 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er return err } - self.context().SetSelection(0) + self.c.Helpers().Refs.SelectFirstBranchAndFirstCommit() self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, KeepBranchSelectionIndex: true}) return nil } diff --git a/pkg/gui/controllers/helpers/cherry_pick_helper.go b/pkg/gui/controllers/helpers/cherry_pick_helper.go index c4be07ceb28..359d1cbc299 100644 --- a/pkg/gui/controllers/helpers/cherry_pick_helper.go +++ b/pkg/gui/controllers/helpers/cherry_pick_helper.go @@ -101,7 +101,7 @@ func (self *CherryPickHelper) Paste() error { // below the selection. if commit := self.c.Contexts().LocalCommits.GetSelected(); commit != nil && !commit.IsTODO() { self.c.Contexts().LocalCommits.MoveSelection(len(cherryPickedCommits)) - self.c.Contexts().LocalCommits.FocusLine() + self.c.Contexts().LocalCommits.FocusLine(true) } // If we're in the cherry-picking state at this point, it must diff --git a/pkg/gui/controllers/helpers/refs_helper.go b/pkg/gui/controllers/helpers/refs_helper.go index 1976bf3867d..d52d9bbad10 100644 --- a/pkg/gui/controllers/helpers/refs_helper.go +++ b/pkg/gui/controllers/helpers/refs_helper.go @@ -31,6 +31,15 @@ func NewRefsHelper( } } +func (self *RefsHelper) SelectFirstBranchAndFirstCommit() { + self.c.Contexts().Branches.SetSelection(0) + self.c.Contexts().ReflogCommits.SetSelection(0) + self.c.Contexts().LocalCommits.SetSelection(0) + self.c.Contexts().Branches.GetView().SetOriginY(0) + self.c.Contexts().ReflogCommits.GetView().SetOriginY(0) + self.c.Contexts().LocalCommits.GetView().SetOriginY(0) +} + func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions) error { waitingStatus := options.WaitingStatus if waitingStatus == "" { @@ -40,9 +49,8 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions cmdOptions := git_commands.CheckoutOptions{Force: false, EnvVars: options.EnvVars} refresh := func() { - self.c.Contexts().Branches.SetSelection(0) - self.c.Contexts().ReflogCommits.SetSelection(0) - self.c.Contexts().LocalCommits.SetSelection(0) + self.SelectFirstBranchAndFirstCommit() + // loading a heap of commits is slow so we limit them whenever doing a reset self.c.Contexts().LocalCommits.SetLimitCommits(true) @@ -348,8 +356,7 @@ func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggest self.c.Context().Push(self.c.Contexts().Branches, types.OnFocusOpts{}) } - self.c.Contexts().LocalCommits.SetSelection(0) - self.c.Contexts().Branches.SetSelection(0) + self.SelectFirstBranchAndFirstCommit() self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true}) } @@ -504,8 +511,7 @@ func (self *RefsHelper) moveCommitsToNewBranchStackedOnCurrentBranch(newBranchNa } } - self.c.Contexts().LocalCommits.SetSelection(0) - self.c.Contexts().Branches.SetSelection(0) + self.SelectFirstBranchAndFirstCommit() self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true}) return nil @@ -543,8 +549,7 @@ func (self *RefsHelper) moveCommitsToNewBranchOffOfMainBranch(newBranchName stri } } - self.c.Contexts().LocalCommits.SetSelection(0) - self.c.Contexts().Branches.SetSelection(0) + self.SelectFirstBranchAndFirstCommit() self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true}) return nil diff --git a/pkg/gui/controllers/list_controller.go b/pkg/gui/controllers/list_controller.go index f772fb3d8c5..a9107439269 100644 --- a/pkg/gui/controllers/list_controller.go +++ b/pkg/gui/controllers/list_controller.go @@ -116,7 +116,12 @@ func (self *ListController) handleLineChangeAux(f func(int), change int) error { } if cursorMoved || rangeBefore != rangeAfter { - self.context.HandleFocus(types.OnFocusOpts{}) + self.context.HandleFocus(types.OnFocusOpts{ScrollSelectionIntoView: true}) + } else { + // If the selection did not change (because, for example, we are at the top of the list and + // press up), we still want to ensure that the selection is visible. This is useful after + // scrolling the selection out of view with the mouse. + self.context.FocusLine(true) } return nil @@ -173,6 +178,8 @@ func (self *ListController) handlePageChange(delta int) error { } } + // Since we are maintaining the scroll position ourselves above, there's no point in passing + // ScrollSelectionIntoView=true here. self.context.HandleFocus(types.OnFocusOpts{}) return nil diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go index 5b76fd700e7..1091b91782f 100644 --- a/pkg/gui/controllers/local_commits_controller.go +++ b/pkg/gui/controllers/local_commits_controller.go @@ -869,7 +869,7 @@ func (self *LocalCommitsController) revert(commits []*models.Commit, start, end return err } self.context().MoveSelection(len(commits)) - self.context().HandleFocus(types.OnFocusOpts{}) + self.context().HandleFocus(types.OnFocusOpts{ScrollSelectionIntoView: true}) if mustStash { if err := self.c.Git().Stash.Pop(0); err != nil { diff --git a/pkg/gui/controllers/remotes_controller.go b/pkg/gui/controllers/remotes_controller.go index bd88b1d583f..e1f08bc7d0b 100644 --- a/pkg/gui/controllers/remotes_controller.go +++ b/pkg/gui/controllers/remotes_controller.go @@ -368,7 +368,7 @@ func (self *RemotesController) fetchAndCheckout(remote *models.Remote, branchNam err = self.c.Git().Branch.New(branchName, remote.Name+"/"+branchName) if err == nil { self.c.Context().Push(self.c.Contexts().Branches, types.OnFocusOpts{}) - self.c.Contexts().Branches.SetSelection(0) + self.c.Helpers().Refs.SelectFirstBranchAndFirstCommit() refreshOptions.KeepBranchSelectionIndex = true } } diff --git a/pkg/gui/controllers/stash_controller.go b/pkg/gui/controllers/stash_controller.go index 87f84d7ea6c..7027cd5fec1 100644 --- a/pkg/gui/controllers/stash_controller.go +++ b/pkg/gui/controllers/stash_controller.go @@ -205,7 +205,7 @@ func (self *StashController) handleRenameStashEntry(stashEntry *models.StashEntr return err } self.context().SetSelection(0) // Select the renamed stash - self.context().FocusLine() + self.context().FocusLine(true) self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}}) return nil }, diff --git a/pkg/gui/types/context.go b/pkg/gui/types/context.go index 917342776e3..339cf2b492a 100644 --- a/pkg/gui/types/context.go +++ b/pkg/gui/types/context.go @@ -109,7 +109,7 @@ type Context interface { HandleFocus(opts OnFocusOpts) HandleFocusLost(opts OnFocusLostOpts) - FocusLine() + FocusLine(scrollIntoView bool) HandleRender() HandleRenderToMain() } @@ -201,7 +201,7 @@ type IPatchExplorerContext interface { } type IViewTrait interface { - FocusPoint(yIdx int) + FocusPoint(yIdx int, scrollIntoView bool) SetRangeSelectStart(yIdx int) CancelRangeSelect() SetViewPortContent(content string) @@ -221,8 +221,9 @@ type IViewTrait interface { } type OnFocusOpts struct { - ClickedWindowName string - ClickedViewLineIdx int + ClickedWindowName string + ClickedViewLineIdx int + ScrollSelectionIntoView bool } type OnFocusLostOpts struct { diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 3dfea4c399c..b8ea49c4488 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -140,7 +140,7 @@ func (gui *Gui) postRefreshUpdate(c types.Context) { // non-focused views to ensure that an inactive selection is painted // correctly, and that integration tests see the up to date selection // state. - c.FocusLine() + c.FocusLine(false) currentCtx := gui.State.ContextMgr.Current() if currentCtx.GetKey() == context.NORMAL_MAIN_CONTEXT_KEY || currentCtx.GetKey() == context.NORMAL_SECONDARY_CONTEXT_KEY { diff --git a/pkg/integration/clients/tui.go b/pkg/integration/clients/tui.go index 992f107e877..13b03726e4a 100644 --- a/pkg/integration/clients/tui.go +++ b/pkg/integration/clients/tui.go @@ -51,7 +51,7 @@ func RunTUI(raceDetector bool) { if err != nil { return err } - listView.FocusPoint(0, app.itemIdx) + listView.FocusPoint(0, app.itemIdx, true) return nil }); err != nil { log.Panicln(err) @@ -66,7 +66,7 @@ func RunTUI(raceDetector bool) { if err != nil { return err } - listView.FocusPoint(0, app.itemIdx) + listView.FocusPoint(0, app.itemIdx, true) return nil }); err != nil { log.Panicln(err) diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go index 70cbd9735e4..87f61eebd31 100644 --- a/vendor/github.com/jesseduffield/gocui/view.go +++ b/vendor/github.com/jesseduffield/gocui/view.go @@ -262,7 +262,7 @@ func (v *View) SelectSearchResult(index int) error { y := v.searcher.searchPositions[index].Y - v.FocusPoint(v.ox, y) + v.FocusPoint(v.ox, y, true) if v.searcher.onSelectItem != nil { return v.searcher.onSelectItem(y, index, itemCount) } @@ -325,14 +325,17 @@ func (v *View) IsSearching() bool { return v.searcher.searchString != "" } -func (v *View) FocusPoint(cx int, cy int) { +func (v *View) FocusPoint(cx int, cy int, scrollIntoView bool) { lineCount := len(v.lines) if cy < 0 || cy > lineCount { return } - height := v.InnerHeight() - v.oy = calculateNewOrigin(cy, v.oy, lineCount, height) + if scrollIntoView { + height := v.InnerHeight() + v.oy = calculateNewOrigin(cy, v.oy, lineCount, height) + } + v.cx = cx v.cy = cy - v.oy } diff --git a/vendor/modules.txt b/vendor/modules.txt index eaf7b8c7c1c..49ff6fe7ae0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -221,7 +221,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame github.com/jesseduffield/go-git/v5/utils/merkletrie/noder github.com/jesseduffield/go-git/v5/utils/sync github.com/jesseduffield/go-git/v5/utils/trace -# github.com/jesseduffield/gocui v0.3.1-0.20251214132308-02ab34c1c624 +# github.com/jesseduffield/gocui v0.3.1-0.20251223143206-950739ccd44a ## explicit; go 1.12 github.com/jesseduffield/gocui # github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5