Fix visual glitches when moving a rebase todo

This fixes two problems:
1. the list selection updated before the list content did, which caused
a bit of a wobble effect in the list
2. the main view would first update to a different commit's diff and
then back to the one that is being moved, resulting in a very ugly
flicker especially when moving the todo multiple times with auto repeat
This commit is contained in:
Stefan Haller 2026-08-29 12:59:33 +02:00 committed by GitHub
parent d37f901ac1
commit 3914755c98
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 30 additions and 6 deletions

View file

@ -41,7 +41,7 @@ func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) {
fn(opts)
}
if self.onRenderToMainFn != nil {
if self.onRenderToMainFn != nil && !opts.SkipMainViewUpdate {
self.onRenderToMainFn()
}
}

View file

@ -119,6 +119,9 @@ type refreshEnv struct {
// reload state (see RefreshOptions.DontBlockRepoSwitch).
keepScrollPosition bool
// Whether refreshing a side context should leave the main view unchanged.
skipMainViewUpdate bool
// the repo generation captured when the refresh started
generation int
@ -231,6 +234,7 @@ func (self *RefreshHelper) performRefresh(options types.RefreshOptions, calledFr
background: options.Background || options.DontBlockRepoSwitch,
backgroundRoutine: options.Background,
keepScrollPosition: options.Background || options.DontBlockRepoSwitch,
skipMainViewUpdate: options.SkipMainViewUpdate,
}
if !self.captureOnUIThread(calledFromWorker, env.background, func() {
env.generation = self.c.State().GetRepoGeneration()
@ -1672,6 +1676,7 @@ func (self *RefreshHelper) refreshView(context types.Context, env refreshEnv) {
self.c.PostRefreshUpdateWithOptions(context, types.OnFocusOpts{
KeepScrollPosition: env.keepScrollPosition,
SkipMainViewUpdate: env.skipMainViewUpdate,
})
self.c.AfterLayout(func() error {

View file

@ -345,7 +345,9 @@ func (self *LocalCommitsController) startMovingCommitsIndicator(insertionIndex i
func (self *LocalCommitsController) stopMovingCommitsIndicator() {
self.stopMovingCommitsIndicatorTicker()
self.context().ClearDropInsertionIndex()
self.c.PostRefreshUpdate(self.context())
self.c.PostRefreshUpdateWithOptions(
self.context(), types.OnFocusOpts{SkipMainViewUpdate: true},
)
}
func (self *LocalCommitsController) stopMovingCommitsIndicatorTicker() {
@ -1171,15 +1173,21 @@ func (self *LocalCommitsController) move(
if err := self.c.Git().Rebase.MoveTodos(selectedCommits, offset); err != nil {
return err
}
self.context().MoveSelection(offset)
self.context().HandleFocus(types.OnFocusOpts{})
// Block input until the refresh has landed: a quick second press must
// read the moved todo from the refreshed model, not grab whatever the
// advanced selection index points at in the stale one.
self.c.RefreshBlockingInput(types.RefreshOptions{
Scope: []types.RefreshableView{types.REBASE_COMMITS},
Then: onComplete,
Scope: []types.RefreshableView{types.REBASE_COMMITS},
SkipMainViewUpdate: true,
Then: func() error {
self.context().MoveSelection(offset)
self.context().FocusLine(true)
if onComplete != nil {
return onComplete()
}
return nil
},
})
return nil
}

View file

@ -234,6 +234,10 @@ type OnFocusOpts struct {
// the view's scroll position alone instead; only for callers that maintain
// it themselves, e.g. by keeping the selection at the edge of the viewport.
KeepScrollPosition bool
// Set this when the focused item hasn't changed and the main view's current
// content is still valid.
SkipMainViewUpdate bool
}
type OnFocusLostOpts struct {

View file

@ -72,6 +72,10 @@ type RefreshOptions struct {
// letting each scope update the UI as soon as it's done.
BatchUIUpdates bool
// Set this when the refresh doesn't invalidate the main view's current
// content, so refreshing the side context needn't render it again.
SkipMainViewUpdate bool
// Controls which local branch is selected after the refresh. Defaults to
// KeepBranchSelectionByName.
BranchSelection BranchSelectionBehavior

View file

@ -149,6 +149,9 @@ func (gui *Gui) postRefreshUpdate(c types.Context, opts types.OnFocusOpts) {
// correctly, and that integration tests see the up to date selection
// state.
c.FocusLine(!opts.KeepScrollPosition)
if opts.SkipMainViewUpdate {
return
}
currentCtx := gui.State.ContextMgr.Current()
if currentCtx.GetKey() == context.NORMAL_MAIN_CONTEXT_KEY || currentCtx.GetKey() == context.NORMAL_SECONDARY_CONTEXT_KEY {