mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-08-24 02:24:25 -05:00
Fix clicking in commits panel of unfocused VS Code window (#5859)
When lazygit is running in a VS Code window that doesn't have the focus (e.g. a split tab) and you click in the commits panel to focus it and select a commit at the same time, it would briefly select the commit you clicked but then flash back to the commit that was previously selected. This PR fixes that so that the clicked commit stays selected. This was only a problem with VS Code; in Zed's builtin terminal it worked, apparently because it first dispatches the click and then the focus-in event. Ghostty and iTerm2 were not affected because they don't dispatch clicks in inactive windows or tabs at all. Labelling as ignore-for-release because it fixes a regression that was introduced since the last release.
This commit is contained in:
@@ -322,7 +322,7 @@ func (self *RefreshHelper) performRefresh(options types.RefreshOptions, calledFr
|
||||
var capturedReflog capturedReflogState
|
||||
var capturedBranches capturedBranchState
|
||||
self.captureOnUIThread(calledFromWorker, env.background, func() {
|
||||
capturedCommits = self.captureCommitsState(options.CommitSelection)
|
||||
capturedCommits = self.captureCommitsState()
|
||||
capturedReflog = self.captureReflogState()
|
||||
capturedBranches = self.captureBranchState()
|
||||
})
|
||||
@@ -704,7 +704,6 @@ func (self *RefreshHelper) refreshReflogAndBranches(capturedReflog capturedReflo
|
||||
// worker computes from an immutable snapshot rather than reading state the UI
|
||||
// thread concurrently mutates.
|
||||
type capturedCommitState struct {
|
||||
selectionRange *localCommitSelectionRange
|
||||
limitCommits bool
|
||||
showWholeGitGraph bool
|
||||
filterPath string
|
||||
@@ -716,17 +715,12 @@ type capturedCommitState struct {
|
||||
|
||||
// captureCommitsState reads the commits refresh's model/context/mode inputs
|
||||
// into an immutable snapshot. It must run on the UI thread.
|
||||
func (self *RefreshHelper) captureCommitsState(commitSelection types.CommitSelectionBehavior) capturedCommitState {
|
||||
var selectionRange *localCommitSelectionRange
|
||||
if commitSelection == types.KeepCommitSelectionByHash {
|
||||
selectedIdx, rangeStartIdx, rangeSelectMode := self.c.Contexts().LocalCommits.GetSelectionRangeAndMode()
|
||||
selectionRange = captureLocalCommitSelectionRange(self.c.Model().Commits, selectedIdx, rangeStartIdx, rangeSelectMode)
|
||||
}
|
||||
|
||||
// The selection is captured later, when applying the refresh, so user input
|
||||
// received while the git work is in flight is not overwritten.
|
||||
func (self *RefreshHelper) captureCommitsState() capturedCommitState {
|
||||
parentCtx := self.c.Contexts().CommitFiles.GetParentContext()
|
||||
|
||||
return capturedCommitState{
|
||||
selectionRange: selectionRange,
|
||||
limitCommits: self.c.Contexts().LocalCommits.GetLimitCommits(),
|
||||
showWholeGitGraph: self.c.Contexts().LocalCommits.GetShowWholeGitGraph(),
|
||||
filterPath: self.c.Modes().Filtering.GetPath(),
|
||||
@@ -815,6 +809,12 @@ func (self *RefreshHelper) refreshCommitsWithLimit(captured capturedCommitState,
|
||||
workingTreeState := env.git.Status.WorkingTreeState()
|
||||
|
||||
self.onUIThreadUnlessRepoChanged(env, func() {
|
||||
var selectionRange *localCommitSelectionRange
|
||||
if commitSelection == types.KeepCommitSelectionByHash {
|
||||
selectedIdx, rangeStartIdx, rangeSelectMode := self.c.Contexts().LocalCommits.GetSelectionRangeAndMode()
|
||||
selectionRange = captureLocalCommitSelectionRange(self.c.Model().Commits, selectedIdx, rangeStartIdx, rangeSelectMode)
|
||||
}
|
||||
|
||||
self.c.Model().BisectInfo = bisectInfo
|
||||
self.c.Model().Commits = commits
|
||||
self.RefreshAuthors(commits)
|
||||
@@ -833,10 +833,10 @@ func (self *RefreshHelper) refreshCommitsWithLimit(captured capturedCommitState,
|
||||
scrollSelectionIntoView = true
|
||||
}
|
||||
case types.KeepCommitSelectionByHash:
|
||||
if captured.selectionRange != nil {
|
||||
selectedIdx, rangeStartIdx, didMove, found := findLocalCommitSelectionRange(commits, captured.selectionRange)
|
||||
if selectionRange != nil {
|
||||
selectedIdx, rangeStartIdx, didMove, found := findLocalCommitSelectionRange(commits, selectionRange)
|
||||
if found {
|
||||
self.c.Contexts().LocalCommits.SetSelectionRangeAndMode(selectedIdx, rangeStartIdx, captured.selectionRange.mode)
|
||||
self.c.Contexts().LocalCommits.SetSelectionRangeAndMode(selectedIdx, rangeStartIdx, selectionRange.mode)
|
||||
scrollSelectionIntoView = didMove
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,25 @@ func (self *GuiDriver) FocusIn() {
|
||||
self.waitTillIdle()
|
||||
}
|
||||
|
||||
func (self *GuiDriver) FocusInAndClick(x, y int) {
|
||||
self.CheckAllToastsAcknowledged()
|
||||
|
||||
self.gui.g.ReplayFocusEvent(gocui.NewTcellFocusEventWrapper(
|
||||
tcell.NewEventFocus(true),
|
||||
0,
|
||||
))
|
||||
self.gui.g.ReplayMouseEvent(gocui.NewTcellMouseEventWrapper(
|
||||
tcell.NewEventMouse(x, y, tcell.ButtonPrimary, 0),
|
||||
0,
|
||||
))
|
||||
self.waitTillIdle()
|
||||
self.gui.g.ReplayMouseEvent(gocui.NewTcellMouseEventWrapper(
|
||||
tcell.NewEventMouse(x, y, tcell.ButtonNone, 0),
|
||||
0,
|
||||
))
|
||||
self.waitTillIdle()
|
||||
}
|
||||
|
||||
func (self *GuiDriver) PretendMergeOrRebaseStartedInLazygit() {
|
||||
self.gui.onUIThread(func() error {
|
||||
self.gui.State.SetMergeOrRebaseStartedInLazygit(true)
|
||||
|
||||
@@ -73,6 +73,12 @@ func (self *TestDriver) FocusIn() {
|
||||
self.Wait(self.inputDelay)
|
||||
}
|
||||
|
||||
func (self *TestDriver) focusInAndClick(x, y int) {
|
||||
self.SetCaption(fmt.Sprintf("Focusing window and clicking %d, %d", x, y))
|
||||
self.gui.FocusInAndClick(x, y)
|
||||
self.Wait(self.inputDelay)
|
||||
}
|
||||
|
||||
func (self *TestDriver) typeContent(content string) {
|
||||
for _, char := range content {
|
||||
self.pressFast(string(char))
|
||||
|
||||
@@ -41,6 +41,10 @@ func (self *fakeGuiDriver) Click(x, y int) {
|
||||
func (self *fakeGuiDriver) FocusIn() {
|
||||
}
|
||||
|
||||
func (self *fakeGuiDriver) FocusInAndClick(x, y int) {
|
||||
self.clickedCoordinates = append(self.clickedCoordinates, coordinate{x: x, y: y})
|
||||
}
|
||||
|
||||
func (self *fakeGuiDriver) Keys() config.KeybindingConfig {
|
||||
return config.KeybindingConfig{}
|
||||
}
|
||||
|
||||
@@ -475,6 +475,14 @@ func (self *ViewDriver) Click(x, y int) *ViewDriver {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *ViewDriver) FocusInAndClick(x, y int) *ViewDriver {
|
||||
offsetX, offsetY, _, _ := self.getView().Dimensions()
|
||||
|
||||
self.t.focusInAndClick(offsetX+1+x, offsetY+1+y)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// i.e. pressing down arrow
|
||||
func (self *ViewDriver) SelectNextItem() *ViewDriver {
|
||||
return self.PressFast(self.t.keys.Universal.NextItem)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package commit
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var KeepClickedCommitSelectedAfterFocusIn = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Keep a clicked commit selected when focus-in immediately precedes the click",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateNCommits(2)
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("commit-02").IsSelected(),
|
||||
Contains("commit-01"),
|
||||
).
|
||||
FocusInAndClick(1, 1).
|
||||
SelectedLine(Contains("commit-01"))
|
||||
},
|
||||
})
|
||||
@@ -140,6 +140,7 @@ var tests = []*components.IntegrationTest{
|
||||
commit.Highlight,
|
||||
commit.History,
|
||||
commit.HistoryComplex,
|
||||
commit.KeepClickedCommitSelectedAfterFocusIn,
|
||||
commit.KeepSelectedCommitAfterExternalCommit,
|
||||
commit.NewBranch,
|
||||
commit.PasteCommitMessage,
|
||||
|
||||
@@ -31,6 +31,9 @@ type GuiDriver interface {
|
||||
// Simulate the terminal window regaining focus (which triggers a reload of
|
||||
// changed config files)
|
||||
FocusIn()
|
||||
// Simulate a terminal dispatching focus-in immediately followed by a click,
|
||||
// without waiting for the focus refresh to finish in between.
|
||||
FocusInAndClick(int, int)
|
||||
Keys() config.KeybindingConfig
|
||||
CurrentContext() types.Context
|
||||
ContextForView(viewName string) types.Context
|
||||
|
||||
Reference in New Issue
Block a user