mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-08-24 02:24:25 -05:00
Ever since scrolling the selection into view became opt-in, we have been fixing the same class of regression by hand, five times so far: a controller moves the selection somewhere new, doesn't say that it wants the view to follow, and the selection ends up off screen. The decision needs facts from two places — whether the selection went somewhere new is known to the list, whether the scroll position is the caller's to manage is known to the caller — and asking every caller for both is what keeps going wrong. The callers that get it wrong are usually not even the ones that moved the selection: they are pass-throughs like postRefreshUpdate, which can't know what a refresh did to the selection. So default to scrolling, and let the two callers that maintain the scroll position themselves say so. The one case where scrolling is always wrong is a refresh that no user action is behind: a background poll, or a reload of state on window focus, after a subprocess, or after a repo switch. Those must leave the viewport wherever the user last scrolled it to — that is what made the scrolling opt-in in the first place. Both are already marked in RefreshOptions, so the refresh can decide it once, centrally, instead of each caller judging it. A user action that ends in a foreground refresh does now yank the view back to the selection if the user had scrolled away from it. That's a behaviour change, and there may be actions where it turns out to be unwelcome; those we can fix individually, and it beats the ones that don't scroll today. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
232 lines
6.3 KiB
Go
232 lines
6.3 KiB
Go
package gui
|
|
|
|
import (
|
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
|
"github.com/jesseduffield/lazygit/pkg/config"
|
|
"github.com/jesseduffield/lazygit/pkg/gocui"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
|
"github.com/jesseduffield/lazygit/pkg/tasks"
|
|
)
|
|
|
|
// hacking this by including the gui struct for now until we split more things out
|
|
type guiCommon struct {
|
|
gui *Gui
|
|
types.IPopupHandler
|
|
}
|
|
|
|
var _ types.IGuiCommon = &guiCommon{}
|
|
|
|
func (self *guiCommon) LogAction(msg string) {
|
|
self.gui.LogAction(msg)
|
|
}
|
|
|
|
func (self *guiCommon) LogCommand(cmdStr string, isCommandLine bool) {
|
|
self.gui.LogCommand(cmdStr, isCommandLine)
|
|
}
|
|
|
|
func (self *guiCommon) Refresh(opts types.RefreshOptions) {
|
|
self.gui.helpers.Refresh.Refresh(opts)
|
|
}
|
|
|
|
func (self *guiCommon) RefreshBlockingInput(opts types.RefreshOptions) {
|
|
self.gui.helpers.Refresh.RefreshBlockingInput(opts)
|
|
}
|
|
|
|
func (self *guiCommon) RefreshFromWorker(opts types.RefreshOptions) {
|
|
self.gui.helpers.Refresh.RefreshFromWorker(opts)
|
|
}
|
|
|
|
func (self *guiCommon) PostRefreshUpdate(context types.Context) {
|
|
self.gui.postRefreshUpdate(context, false)
|
|
}
|
|
|
|
func (self *guiCommon) PostRefreshUpdateKeepingScrollPosition(context types.Context) {
|
|
self.gui.postRefreshUpdate(context, true)
|
|
}
|
|
|
|
func (self *guiCommon) RunSubprocessAndRefresh(cmdObj *oscommands.CmdObj) error {
|
|
return self.gui.runSubprocessWithSuspenseAndRefresh(cmdObj)
|
|
}
|
|
|
|
func (self *guiCommon) RunSubprocess(cmdObj *oscommands.CmdObj) (bool, error) {
|
|
return self.gui.runSubprocessWithSuspense(cmdObj)
|
|
}
|
|
|
|
func (self *guiCommon) Suspend() error {
|
|
return self.gui.suspend()
|
|
}
|
|
|
|
func (self *guiCommon) Resume() error {
|
|
return self.gui.resume()
|
|
}
|
|
|
|
func (self *guiCommon) PauseBackgroundRefreshes(pause bool) {
|
|
self.gui.BackgroundRoutineMgr.PauseBackgroundRefreshes(pause)
|
|
}
|
|
|
|
// assertOnUIThread panics (in debug builds) if called from a worker goroutine.
|
|
// Use it to guard accessors for state that only the UI thread may touch, so
|
|
// that a stray worker access fails deterministically -- and points at itself --
|
|
// rather than surfacing later as a probabilistic data race.
|
|
func (self *guiCommon) assertOnUIThread(accessor string) {
|
|
if self.GetConfig().GetDebug() && !self.GocuiGui().IsUIThread() {
|
|
panic(accessor + " accessed from a worker")
|
|
}
|
|
}
|
|
|
|
func (self *guiCommon) Context() types.IContextMgr {
|
|
self.assertOnUIThread("Context()")
|
|
return self.gui.State.ContextMgr
|
|
}
|
|
|
|
func (self *guiCommon) ContextForKey(key types.ContextKey) types.Context {
|
|
return self.gui.State.ContextMgr.ContextForKey(key)
|
|
}
|
|
|
|
func (self *guiCommon) GetAppState() *config.AppState {
|
|
return self.gui.Config.GetAppState()
|
|
}
|
|
|
|
func (self *guiCommon) SaveAppState() error {
|
|
return self.gui.Config.SaveAppState()
|
|
}
|
|
|
|
func (self *guiCommon) SaveAppStateAndLogError() {
|
|
if err := self.gui.Config.SaveAppState(); err != nil {
|
|
self.gui.Log.Errorf("error when saving app state: %v", err)
|
|
}
|
|
}
|
|
|
|
func (self *guiCommon) GetConfig() config.AppConfigurer {
|
|
return self.gui.Config
|
|
}
|
|
|
|
func (self *guiCommon) ResetViewOrigin(view *gocui.View) {
|
|
self.gui.resetViewOrigin(view)
|
|
}
|
|
|
|
func (self *guiCommon) SetViewContent(view *gocui.View, content string) {
|
|
self.gui.setViewContent(view, content)
|
|
}
|
|
|
|
func (self *guiCommon) Render() {
|
|
self.gui.render()
|
|
}
|
|
|
|
func (self *guiCommon) Views() types.Views {
|
|
return self.gui.Views
|
|
}
|
|
|
|
func (self *guiCommon) Git() *commands.GitCommand {
|
|
return self.gui.git
|
|
}
|
|
|
|
func (self *guiCommon) OS() *oscommands.OSCommand {
|
|
return self.gui.os
|
|
}
|
|
|
|
func (self *guiCommon) Modes() *types.Modes {
|
|
return self.gui.State.Modes
|
|
}
|
|
|
|
func (self *guiCommon) Model() *types.Model {
|
|
self.assertOnUIThread("Model()")
|
|
return self.gui.State.Model
|
|
}
|
|
|
|
func (self *guiCommon) Mutexes() *types.Mutexes {
|
|
return &self.gui.Mutexes
|
|
}
|
|
|
|
func (self *guiCommon) GocuiGui() *gocui.Gui {
|
|
return self.gui.g
|
|
}
|
|
|
|
func (self *guiCommon) OnUIThread(f func() error) {
|
|
self.gui.onUIThread(f)
|
|
}
|
|
|
|
func (self *guiCommon) OnUIThreadBackground(f func() error) {
|
|
self.gui.onUIThreadBackground(f)
|
|
}
|
|
|
|
func (self *guiCommon) OnUIThreadContentOnly(f func() error) {
|
|
self.gui.onUIThreadContentOnly(f)
|
|
}
|
|
|
|
func (self *guiCommon) OnUIThreadContentOnlyBackground(f func() error) {
|
|
self.gui.onUIThreadContentOnlyBackground(f)
|
|
}
|
|
|
|
func (self *guiCommon) OnWorker(f func(gocui.Task) error) {
|
|
self.gui.onWorker(f)
|
|
}
|
|
|
|
func (self *guiCommon) OnWorkerBackground(f func(gocui.Task) error) {
|
|
self.gui.onWorkerBackground(f)
|
|
}
|
|
|
|
func (self *guiCommon) RenderToMainViews(opts types.RefreshMainOpts) {
|
|
self.gui.refreshMainViews(opts)
|
|
}
|
|
|
|
func (self *guiCommon) MainViewPairs() types.MainViewPairs {
|
|
return types.MainViewPairs{
|
|
Normal: self.gui.normalMainContextPair(),
|
|
Staging: self.gui.stagingMainContextPair(),
|
|
PatchBuilding: self.gui.patchBuildingMainContextPair(),
|
|
MergeConflicts: self.gui.mergingMainContextPair(),
|
|
}
|
|
}
|
|
|
|
func (self *guiCommon) GetViewBufferManagerForView(view *gocui.View) *tasks.ViewBufferManager {
|
|
return self.gui.getViewBufferManagerForView(view)
|
|
}
|
|
|
|
func (self *guiCommon) ReadLinesToFillView(view *gocui.View) {
|
|
self.gui.readLinesToFillView(view)
|
|
}
|
|
|
|
func (self *guiCommon) State() types.IStateAccessor {
|
|
return self.gui.stateAccessor
|
|
}
|
|
|
|
func (self *guiCommon) KeybindingsOpts() types.KeybindingsOpts {
|
|
return self.gui.keybindingOpts()
|
|
}
|
|
|
|
func (self *guiCommon) CallKeybindingHandler(binding *types.Binding) error {
|
|
return self.gui.callKeybindingHandler(binding)
|
|
}
|
|
|
|
func (self *guiCommon) ResetKeybindings() error {
|
|
return self.gui.resetKeybindings()
|
|
}
|
|
|
|
func (self *guiCommon) IsAnyModeActive() bool {
|
|
return self.gui.helpers.Mode.IsAnyModeActive()
|
|
}
|
|
|
|
func (self *guiCommon) GetInitialKeybindingsWithCustomCommands() ([]*types.Binding, []*gocui.ViewMouseBinding) {
|
|
return self.gui.GetInitialKeybindingsWithCustomCommands()
|
|
}
|
|
|
|
func (self *guiCommon) AfterLayout(f func() error) {
|
|
self.gui.afterLayout(f)
|
|
}
|
|
|
|
func (self *guiCommon) RunningIntegrationTest() bool {
|
|
return self.gui.integrationTest != nil
|
|
}
|
|
|
|
func (self *guiCommon) InDemo() bool {
|
|
return self.gui.integrationTest != nil && self.gui.integrationTest.IsDemo()
|
|
}
|
|
|
|
func (self *guiCommon) WithInlineStatus(item types.HasUrn, operation types.ItemOperation, contextKey types.ContextKey, f func(gocui.Task) error) error {
|
|
self.gui.helpers.InlineStatus.WithInlineStatus(helpers.InlineStatusOpts{Item: item, Operation: operation, ContextKey: contextKey}, f)
|
|
return nil
|
|
}
|