1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-07-28 16:02:01 +03:00

Use first class task objects instead of global counter

The global counter approach is easy to understand but it's brittle and depends on implicit behaviour that is not very discoverable.

With a global counter, if any goroutine accidentally decrements the counter twice, we'll think lazygit is idle when it's actually busy.
Likewise if a goroutine accidentally increments the counter twice we'll think lazygit is busy when it's actually idle.
With the new approach we have a map of tasks where each task can either be busy or not. We create a new task and add it to the map
when we spawn a worker goroutine (among other things) and we remove it once the task is done.

The task can also be paused and continued for situations where we switch back and forth between running a program and asking for user
input.

In order for this to work with `git push` (and other commands that require credentials) we need to obtain the task from gocui when
we create the worker goroutine, and then pass it along to the commands package to pause/continue the task as required. This is
MUCH more discoverable than the old approach which just decremented and incremented the global counter from within the commands package,
but it's at the cost of expanding some function signatures (arguably a good thing).

Likewise, whenever you want to call WithWaitingStatus or WithLoaderPanel the callback will now have access to the task for pausing/
continuing. We only need to actually make use of this functionality in a couple of places so it's a high price to pay, but I don't
know if I want to introduce a WithWaitingStatusTask and WithLoaderPanelTask function (open to suggestions).
This commit is contained in:
Jesse Duffield
2023-07-09 11:32:27 +10:00
parent 9e79ee5fe3
commit 14ecc15e71
43 changed files with 320 additions and 247 deletions

View File

@ -25,27 +25,27 @@ First, it's important to distinguish three different types of goroutines:
The point of distinguishing worker goroutines from background goroutines is that when any worker goroutine is running, we consider Lazygit to be 'busy', whereas this is not the case with background goroutines. It would be pointless to have background goroutines be considered 'busy' because then Lazygit would be considered busy for the entire duration of the program! The point of distinguishing worker goroutines from background goroutines is that when any worker goroutine is running, we consider Lazygit to be 'busy', whereas this is not the case with background goroutines. It would be pointless to have background goroutines be considered 'busy' because then Lazygit would be considered busy for the entire duration of the program!
Lazygit is considered to be 'busy' so long as the counter remains greater than zero, and as soon as it hits zero, Lazygit is considered 'idle' and the integration test is notified. So it's important that we play by the rules below to ensure that after the user does anything, all the processing that follows happens in a contiguous block of busy-ness with no gaps. In gocui, the underlying package we use for managing the UI and events, we keep track of how many busy goroutines there are using the `Task` type. A task represents some work being done by lazygit. The gocui Gui struct holds a map of tasks and allows creating a new task (which adds it to the map), pausing/continuing a task, and marking a task as done (which removes it from the map). Lazygit is considered to be busy so long as there is at least one busy task in the map; otherwise it's considered idle. When Lazygit goes from busy to idle, it notifies the integration test.
In gocui, the underlying package we use for managing the UI and events, we keep track of how many busy goroutines there are with a `busyCount` counter. It's important that we play by the rules below to ensure that after the user does anything, all the processing that follows happens in a contiguous block of busy-ness with no gaps.
### Spawning a worker goroutine ### Spawning a worker goroutine
Here's the basic implementation of `OnWorker`: Here's the basic implementation of `OnWorker` (using the same flow as `WaitGroup`s):
```go ```go
func (g *Gui) OnWorker(f func()) { func (g *Gui) OnWorker(f func(*Task)) {
g.IncrementBusyCount() task := g.NewTask()
go func() { go func() {
f() f(task)
g.DecrementBusyCount() task.Done()
}() }()
} }
``` ```
The crucial thing here is that we increment the busy count _before_ spawning the goroutine, because it means that our counter never goes to zero while there's still work being done. If we incremented the busy count within the goroutine, the current function could exit and decrement the counter to zero before the goroutine starts. The crucial thing here is that we create the task _before_ spawning the goroutine, because it means that we'll have at least one busy task in the map until the completion of the goroutine. If we created the task within the goroutine, the current function could exit and Lazygit would be considered idle before the goroutine starts, leading to our integration test prematurely progressing.
You typically invoke this with `self.c.OnWorker(f)` You typically invoke this with `self.c.OnWorker(f)`. Note that the callback function receives the task. This allows the callback to pause/continue the task (see below).
### Spawning a background goroutine ### Spawning a background goroutine
@ -59,30 +59,20 @@ Where `utils.Safe` is a helper function that ensures we clean up the gui if the
### Programmatically enqueing a UI event ### Programmatically enqueing a UI event
This is invoked with `self.c.OnUIThread(f)`. Internally, it increments the counter before enqueuing the function as an event and once that event is processed by the event queue (and any other pending events are processed) the counter is decremented again. This is invoked with `self.c.OnUIThread(f)`. Internally, it creates a task before enqueuing the function as an event (including the task in the event struct) and once that event is processed by the event queue (and any other pending events are processed) the task is removed from the map by calling `task.Done()`.
### Pressing a key ### Pressing a key
If the user presses a key, an event will be enqueued automatically and the counter will be incremented before (and decremented after) the event is processed. If the user presses a key, an event will be enqueued automatically and a task will be created before (and `Done`'d after) the event is processed.
## Special cases ## Special cases
There are a couple of special cases where we manually increment/decrement the counter in the code. These are subject to change but for the sake of completeness: There are a couple of special cases where we manually pause/continue the task directly in the client code. These are subject to change but for the sake of completeness:
### Writing to the main view(s) ### Writing to the main view(s)
If the user focuses a file in the files panel, we run a `git diff` command for that file and write the output to the main view. But we only read enough of the command's output to fill the view's viewport: further loading only happens if the user scrolls. Given that we have a background goroutine for running the command and writing more output upon scrolling, we manually increment the busy count within that goroutine and then decrement it once the viewport is filled. If the user focuses a file in the files panel, we run a `git diff` command for that file and write the output to the main view. But we only read enough of the command's output to fill the view's viewport: further loading only happens if the user scrolls. Given that we have a background goroutine for running the command and writing more output upon scrolling, we create our own task and call `Done` on it as soon as the viewport is filled.
### Requesting credentials from a git command ### Requesting credentials from a git command
Some git commands (e.g. git push) may request credentials. This is the same deal as above; we use a background goroutine and manually increment/decrement the counter as we go from waiting on the git command to waiting on user input. Some git commands (e.g. git push) may request credentials. This is the same deal as above; we use a worker goroutine and manually pause continue its task as we go from waiting on the git command to waiting on user input. This requires passing the task through to the `Push` method so that it can be paused/continued.
## Future improvements
### Better API
The current approach is fairly simple in terms of the API which, except for the special cases above, encapsulates the incrementing/decrementing of the busy counter. But the counter is a form of global state and in future we may switch to an API where we have objects representing a task in progress, and those objects have `Start()`, `Finish()`, and `Pause()` methods. This would better defend against bugs caused by a random goroutine accidentally decrementing twice, for example.
### More applications
We could use the concept of idle/busy to show a loader whenever Lazygit is busy. But our current situation is pretty good: we have the `WithWaitingStatus()` method for running a function on a worker goroutine along with a message to show within the loader e.g. 'Refreshing branches'. If we find a situation where we're a function is taking a while and a loader isn't appearing, that's because we're running the code on the UI goroutine and we should just wrap the code in `WithWaitingStatus()`.

2
go.mod
View File

@ -18,7 +18,7 @@ require (
github.com/integrii/flaggy v1.4.0 github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
github.com/jesseduffield/gocui v0.3.1-0.20230708122437-f7e1c7c16828 github.com/jesseduffield/gocui v0.3.1-0.20230709105400-44d9f78b4b52
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e

4
go.sum
View File

@ -72,8 +72,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk= github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
github.com/jesseduffield/gocui v0.3.1-0.20230708122437-f7e1c7c16828 h1:1Eos/Z+6/JhXZ9qsniKpKqLsf/z7dSoP2EBfK7T2Mic= github.com/jesseduffield/gocui v0.3.1-0.20230709105400-44d9f78b4b52 h1:rrKgkOAVJD5rgC6aoX3zWTSiSiHkuQBA2JW/r+v1eKE=
github.com/jesseduffield/gocui v0.3.1-0.20230708122437-f7e1c7c16828/go.mod h1:dJ/BEUt3OWtaRg/PmuJWendRqREhre9JQ1SLvqrVJ8s= github.com/jesseduffield/gocui v0.3.1-0.20230709105400-44d9f78b4b52/go.mod h1:dJ/BEUt3OWtaRg/PmuJWendRqREhre9JQ1SLvqrVJ8s=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0= github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo= github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY= github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=

View File

@ -2,6 +2,8 @@ package git_commands
import ( import (
"fmt" "fmt"
"github.com/jesseduffield/gocui"
) )
type RemoteCommands struct { type RemoteCommands struct {
@ -46,12 +48,12 @@ func (self *RemoteCommands) UpdateRemoteUrl(remoteName string, updatedUrl string
return self.cmd.New(cmdArgs).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *RemoteCommands) DeleteRemoteBranch(remoteName string, branchName string) error { func (self *RemoteCommands) DeleteRemoteBranch(task *gocui.Task, remoteName string, branchName string) error {
cmdArgs := NewGitCmd("push"). cmdArgs := NewGitCmd("push").
Arg(remoteName, "--delete", branchName). Arg(remoteName, "--delete", branchName).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
} }
// CheckRemoteBranchExists Returns remote branch // CheckRemoteBranchExists Returns remote branch

View File

@ -2,6 +2,7 @@ package git_commands
import ( import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
) )
@ -23,7 +24,7 @@ type PushOpts struct {
SetUpstream bool SetUpstream bool
} }
func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error) { func (self *SyncCommands) PushCmdObj(task *gocui.Task, opts PushOpts) (oscommands.ICmdObj, error) {
if opts.UpstreamBranch != "" && opts.UpstreamRemote == "" { if opts.UpstreamBranch != "" && opts.UpstreamRemote == "" {
return nil, errors.New(self.Tr.MustSpecifyOriginError) return nil, errors.New(self.Tr.MustSpecifyOriginError)
} }
@ -35,12 +36,12 @@ func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error)
ArgIf(opts.UpstreamBranch != "", opts.UpstreamBranch). ArgIf(opts.UpstreamBranch != "", opts.UpstreamBranch).
ToArgv() ToArgv()
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex) cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex)
return cmdObj, nil return cmdObj, nil
} }
func (self *SyncCommands) Push(opts PushOpts) error { func (self *SyncCommands) Push(task *gocui.Task, opts PushOpts) error {
cmdObj, err := self.PushCmdObj(opts) cmdObj, err := self.PushCmdObj(task, opts)
if err != nil { if err != nil {
return err return err
} }
@ -48,28 +49,24 @@ func (self *SyncCommands) Push(opts PushOpts) error {
return cmdObj.Run() return cmdObj.Run()
} }
type FetchOptions struct { func (self *SyncCommands) Fetch(task *gocui.Task) error {
Background bool
}
// Fetch fetch git repo
func (self *SyncCommands) FetchCmdObj(opts FetchOptions) oscommands.ICmdObj {
cmdArgs := NewGitCmd("fetch"). cmdArgs := NewGitCmd("fetch").
ArgIf(self.UserConfig.Git.FetchAll, "--all"). ArgIf(self.UserConfig.Git.FetchAll, "--all").
ToArgv() ToArgv()
cmdObj := self.cmd.New(cmdArgs) cmdObj := self.cmd.New(cmdArgs)
if opts.Background { cmdObj.PromptOnCredentialRequest(task)
cmdObj.DontLog().FailOnCredentialRequest() return cmdObj.WithMutex(self.syncMutex).Run()
} else {
cmdObj.PromptOnCredentialRequest()
}
return cmdObj.WithMutex(self.syncMutex)
} }
func (self *SyncCommands) Fetch(opts FetchOptions) error { func (self *SyncCommands) FetchBackground() error {
cmdObj := self.FetchCmdObj(opts) cmdArgs := NewGitCmd("fetch").
return cmdObj.Run() ArgIf(self.UserConfig.Git.FetchAll, "--all").
ToArgv()
cmdObj := self.cmd.New(cmdArgs)
cmdObj.DontLog().FailOnCredentialRequest()
return cmdObj.WithMutex(self.syncMutex).Run()
} }
type PullOptions struct { type PullOptions struct {
@ -78,7 +75,7 @@ type PullOptions struct {
FastForwardOnly bool FastForwardOnly bool
} }
func (self *SyncCommands) Pull(opts PullOptions) error { func (self *SyncCommands) Pull(task *gocui.Task, opts PullOptions) error {
cmdArgs := NewGitCmd("pull"). cmdArgs := NewGitCmd("pull").
Arg("--no-edit"). Arg("--no-edit").
ArgIf(opts.FastForwardOnly, "--ff-only"). ArgIf(opts.FastForwardOnly, "--ff-only").
@ -88,22 +85,22 @@ func (self *SyncCommands) Pull(opts PullOptions) error {
// setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user // setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
// has 'pull.rebase = interactive' configured. // has 'pull.rebase = interactive' configured.
return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
} }
func (self *SyncCommands) FastForward(branchName string, remoteName string, remoteBranchName string) error { func (self *SyncCommands) FastForward(task *gocui.Task, branchName string, remoteName string, remoteBranchName string) error {
cmdArgs := NewGitCmd("fetch"). cmdArgs := NewGitCmd("fetch").
Arg(remoteName). Arg(remoteName).
Arg(remoteBranchName + ":" + branchName). Arg(remoteBranchName + ":" + branchName).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
} }
func (self *SyncCommands) FetchRemote(remoteName string) error { func (self *SyncCommands) FetchRemote(task *gocui.Task, remoteName string) error {
cmdArgs := NewGitCmd("fetch"). cmdArgs := NewGitCmd("fetch").
Arg(remoteName). Arg(remoteName).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
} }

View File

@ -1,5 +1,7 @@
package git_commands package git_commands
import "github.com/jesseduffield/gocui"
type TagCommands struct { type TagCommands struct {
*GitCommon *GitCommon
} }
@ -34,9 +36,9 @@ func (self *TagCommands) Delete(tagName string) error {
return self.cmd.New(cmdArgs).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *TagCommands) Push(remoteName string, tagName string) error { func (self *TagCommands) Push(task *gocui.Task, remoteName string, tagName string) error {
cmdArgs := NewGitCmd("push").Arg(remoteName, "tag", tagName). cmdArgs := NewGitCmd("push").Arg(remoteName, "tag", tagName).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
} }

View File

@ -4,6 +4,7 @@ import (
"os/exec" "os/exec"
"strings" "strings"
"github.com/jesseduffield/gocui"
"github.com/samber/lo" "github.com/samber/lo"
"github.com/sasha-s/go-deadlock" "github.com/sasha-s/go-deadlock"
) )
@ -56,13 +57,14 @@ type ICmdObj interface {
// returns true if IgnoreEmptyError() was called // returns true if IgnoreEmptyError() was called
ShouldIgnoreEmptyError() bool ShouldIgnoreEmptyError() bool
PromptOnCredentialRequest() ICmdObj PromptOnCredentialRequest(task *gocui.Task) ICmdObj
FailOnCredentialRequest() ICmdObj FailOnCredentialRequest() ICmdObj
WithMutex(mutex *deadlock.Mutex) ICmdObj WithMutex(mutex *deadlock.Mutex) ICmdObj
Mutex() *deadlock.Mutex Mutex() *deadlock.Mutex
GetCredentialStrategy() CredentialStrategy GetCredentialStrategy() CredentialStrategy
GetTask() *gocui.Task
} }
type CmdObj struct { type CmdObj struct {
@ -85,6 +87,7 @@ type CmdObj struct {
// if set to true, it means we might be asked to enter a username/password by this command. // if set to true, it means we might be asked to enter a username/password by this command.
credentialStrategy CredentialStrategy credentialStrategy CredentialStrategy
task *gocui.Task
// can be set so that we don't run certain commands simultaneously // can be set so that we don't run certain commands simultaneously
mutex *deadlock.Mutex mutex *deadlock.Mutex
@ -192,8 +195,9 @@ func (self *CmdObj) RunAndProcessLines(onLine func(line string) (bool, error)) e
return self.runner.RunAndProcessLines(self, onLine) return self.runner.RunAndProcessLines(self, onLine)
} }
func (self *CmdObj) PromptOnCredentialRequest() ICmdObj { func (self *CmdObj) PromptOnCredentialRequest(task *gocui.Task) ICmdObj {
self.credentialStrategy = PROMPT self.credentialStrategy = PROMPT
self.task = task
return self return self
} }
@ -207,3 +211,7 @@ func (self *CmdObj) FailOnCredentialRequest() ICmdObj {
func (self *CmdObj) GetCredentialStrategy() CredentialStrategy { func (self *CmdObj) GetCredentialStrategy() CredentialStrategy {
return self.credentialStrategy return self.credentialStrategy
} }
func (self *CmdObj) GetTask() *gocui.Task {
return self.task
}

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -308,7 +309,7 @@ func (self *cmdObjRunner) runAndDetectCredentialRequest(
tr := io.TeeReader(handler.stdoutPipe, cmdWriter) tr := io.TeeReader(handler.stdoutPipe, cmdWriter)
go utils.Safe(func() { go utils.Safe(func() {
self.processOutput(tr, handler.stdinPipe, promptUserForCredential) self.processOutput(tr, handler.stdinPipe, promptUserForCredential, cmdObj.GetTask())
}) })
}) })
} }
@ -317,6 +318,7 @@ func (self *cmdObjRunner) processOutput(
reader io.Reader, reader io.Reader,
writer io.Writer, writer io.Writer,
promptUserForCredential func(CredentialType) <-chan string, promptUserForCredential func(CredentialType) <-chan string,
task *gocui.Task,
) { ) {
checkForCredentialRequest := self.getCheckForCredentialRequestFunc() checkForCredentialRequest := self.getCheckForCredentialRequestFunc()
@ -327,13 +329,9 @@ func (self *cmdObjRunner) processOutput(
askFor, ok := checkForCredentialRequest(newBytes) askFor, ok := checkForCredentialRequest(newBytes)
if ok { if ok {
responseChan := promptUserForCredential(askFor) responseChan := promptUserForCredential(askFor)
// We assume that the busy count is greater than zero here because we're task.Pause()
// in the middle of a command. We decrement it so that The user can be prompted
// without lazygit thinking it's still doing its own processing. This helps
// integration tests know how long to wait before typing in a response.
self.guiIO.DecrementBusyCount()
toInput := <-responseChan toInput := <-responseChan
self.guiIO.IncrementBusyCount() task.Continue()
// If the return data is empty we don't write anything to stdin // If the return data is empty we don't write anything to stdin
if toInput != "" { if toInput != "" {
_, _ = writer.Write([]byte(toInput)) _, _ = writer.Write([]byte(toInput))

View File

@ -27,9 +27,6 @@ type guiIO struct {
// that a command requests it. // that a command requests it.
// the 'credential' arg is something like 'username' or 'password' // the 'credential' arg is something like 'username' or 'password'
promptForCredentialFn func(credential CredentialType) <-chan string promptForCredentialFn func(credential CredentialType) <-chan string
IncrementBusyCount func()
DecrementBusyCount func()
} }
func NewGuiIO( func NewGuiIO(
@ -37,16 +34,12 @@ func NewGuiIO(
logCommandFn func(string, bool), logCommandFn func(string, bool),
newCmdWriterFn func() io.Writer, newCmdWriterFn func() io.Writer,
promptForCredentialFn func(CredentialType) <-chan string, promptForCredentialFn func(CredentialType) <-chan string,
IncrementBusyCount func(),
DecrementBusyCount func(),
) *guiIO { ) *guiIO {
return &guiIO{ return &guiIO{
log: log, log: log,
logCommandFn: logCommandFn, logCommandFn: logCommandFn,
newCmdWriterFn: newCmdWriterFn, newCmdWriterFn: newCmdWriterFn,
promptForCredentialFn: promptForCredentialFn, promptForCredentialFn: promptForCredentialFn,
IncrementBusyCount: IncrementBusyCount,
DecrementBusyCount: DecrementBusyCount,
} }
} }
@ -58,7 +51,5 @@ func NewNullGuiIO(log *logrus.Entry) *guiIO {
logCommandFn: func(string, bool) {}, logCommandFn: func(string, bool) {},
newCmdWriterFn: func() io.Writer { return io.Discard }, newCmdWriterFn: func() io.Writer { return io.Discard },
promptForCredentialFn: failPromptFn, promptForCredentialFn: failPromptFn,
IncrementBusyCount: func() {},
DecrementBusyCount: func() {},
} }
} }

View File

@ -4,7 +4,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
@ -86,7 +86,7 @@ func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan stru
if self.pauseBackgroundRefreshes { if self.pauseBackgroundRefreshes {
continue continue
} }
self.gui.c.OnWorker(func() { _ = function() }) self.gui.c.OnWorker(func(*gocui.Task) { _ = function() })
case <-stop: case <-stop:
return return
} }
@ -95,7 +95,7 @@ func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan stru
} }
func (self *BackgroundRoutineMgr) backgroundFetch() (err error) { func (self *BackgroundRoutineMgr) backgroundFetch() (err error) {
err = self.gui.git.Sync.Fetch(git_commands.FetchOptions{Background: true}) err = self.gui.git.Sync.FetchBackground()
_ = self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC}) _ = self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/context"
@ -363,11 +364,12 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
}, },
) )
return self.c.WithLoaderPanel(message, func() error { return self.c.WithLoaderPanel(message, func(task *gocui.Task) error {
if branch == self.c.Helpers().Refs.GetCheckedOutRef() { if branch == self.c.Helpers().Refs.GetCheckedOutRef() {
self.c.LogAction(action) self.c.LogAction(action)
err := self.c.Git().Sync.Pull( err := self.c.Git().Sync.Pull(
task,
git_commands.PullOptions{ git_commands.PullOptions{
RemoteName: branch.UpstreamRemote, RemoteName: branch.UpstreamRemote,
BranchName: branch.UpstreamBranch, BranchName: branch.UpstreamBranch,
@ -381,7 +383,7 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
} else { } else {
self.c.LogAction(action) self.c.LogAction(action)
err := self.c.Git().Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch) err := self.c.Git().Sync.FastForward(task, branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
if err != nil { if err != nil {
_ = self.c.Error(err) _ = self.c.Error(err)
} }

View File

@ -177,7 +177,7 @@ func (self *CommitFilesController) discard(node *filetree.CommitFileNode) error
Title: self.c.Tr.DiscardFileChangesTitle, Title: self.c.Tr.DiscardFileChangesTitle,
Prompt: prompt, Prompt: prompt,
HandleConfirm: func() error { HandleConfirm: func() error {
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.DiscardOldFileChange) self.c.LogAction(self.c.Tr.Actions.DiscardOldFileChange)
if err := self.c.Git().Rebase.DiscardOldFileChanges(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), node.GetPath()); err != nil { if err := self.c.Git().Rebase.DiscardOldFileChanges(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), node.GetPath()); err != nil {
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil { if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil {
@ -205,7 +205,7 @@ func (self *CommitFilesController) edit(node *filetree.CommitFileNode) error {
func (self *CommitFilesController) toggleForPatch(node *filetree.CommitFileNode) error { func (self *CommitFilesController) toggleForPatch(node *filetree.CommitFileNode) error {
toggle := func() error { toggle := func() error {
return self.c.WithWaitingStatus(self.c.Tr.UpdatingPatch, func() error { return self.c.WithWaitingStatus(self.c.Tr.UpdatingPatch, func(*gocui.Task) error {
if !self.c.Git().Patch.PatchBuilder.Active() { if !self.c.Git().Patch.PatchBuilder.Active() {
if err := self.startPatchBuilder(); err != nil { if err := self.startPatchBuilder(); err != nil {
return err return err

View File

@ -3,6 +3,7 @@ package controllers
import ( import (
"fmt" "fmt"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums" "github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
) )
@ -116,7 +117,7 @@ func (self *CustomPatchOptionsMenuAction) handleDeletePatchFromCommit() error {
return err return err
} }
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(*gocui.Task) error {
commitIndex := self.getPatchCommitIndex() commitIndex := self.getPatchCommitIndex()
self.c.LogAction(self.c.Tr.Actions.RemovePatchFromCommit) self.c.LogAction(self.c.Tr.Actions.RemovePatchFromCommit)
err := self.c.Git().Patch.DeletePatchesFromCommit(self.c.Model().Commits, commitIndex) err := self.c.Git().Patch.DeletePatchesFromCommit(self.c.Model().Commits, commitIndex)
@ -133,7 +134,7 @@ func (self *CustomPatchOptionsMenuAction) handleMovePatchToSelectedCommit() erro
return err return err
} }
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(*gocui.Task) error {
commitIndex := self.getPatchCommitIndex() commitIndex := self.getPatchCommitIndex()
self.c.LogAction(self.c.Tr.Actions.MovePatchToSelectedCommit) self.c.LogAction(self.c.Tr.Actions.MovePatchToSelectedCommit)
err := self.c.Git().Patch.MovePatchToSelectedCommit(self.c.Model().Commits, commitIndex, self.c.Contexts().LocalCommits.GetSelectedLineIdx()) err := self.c.Git().Patch.MovePatchToSelectedCommit(self.c.Model().Commits, commitIndex, self.c.Contexts().LocalCommits.GetSelectedLineIdx())
@ -151,7 +152,7 @@ func (self *CustomPatchOptionsMenuAction) handleMovePatchIntoWorkingTree() error
} }
pull := func(stash bool) error { pull := func(stash bool) error {
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(*gocui.Task) error {
commitIndex := self.getPatchCommitIndex() commitIndex := self.getPatchCommitIndex()
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoIndex) self.c.LogAction(self.c.Tr.Actions.MovePatchIntoIndex)
err := self.c.Git().Patch.MovePatchIntoIndex(self.c.Model().Commits, commitIndex, stash) err := self.c.Git().Patch.MovePatchIntoIndex(self.c.Model().Commits, commitIndex, stash)
@ -181,7 +182,7 @@ func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error {
return err return err
} }
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(*gocui.Task) error {
commitIndex := self.getPatchCommitIndex() commitIndex := self.getPatchCommitIndex()
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit) self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit)
err := self.c.Git().Patch.PullPatchIntoNewCommit(self.c.Model().Commits, commitIndex) err := self.c.Git().Patch.PullPatchIntoNewCommit(self.c.Model().Commits, commitIndex)

View File

@ -4,7 +4,6 @@ import (
"strings" "strings"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/filetree" "github.com/jesseduffield/lazygit/pkg/gui/filetree"
@ -801,17 +800,17 @@ func (self *FilesController) onClickSecondary(opts gocui.ViewMouseBindingOpts) e
} }
func (self *FilesController) fetch() error { func (self *FilesController) fetch() error {
return self.c.WithLoaderPanel(self.c.Tr.FetchWait, func() error { return self.c.WithLoaderPanel(self.c.Tr.FetchWait, func(task *gocui.Task) error {
if err := self.fetchAux(); err != nil { if err := self.fetchAux(task); err != nil {
_ = self.c.Error(err) _ = self.c.Error(err)
} }
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
}) })
} }
func (self *FilesController) fetchAux() (err error) { func (self *FilesController) fetchAux(task *gocui.Task) (err error) {
self.c.LogAction("Fetch") self.c.LogAction("Fetch")
err = self.c.Git().Sync.Fetch(git_commands.FetchOptions{}) err = self.c.Git().Sync.Fetch(task)
if err != nil && strings.Contains(err.Error(), "exit status 128") { if err != nil && strings.Contains(err.Error(), "exit status 128") {
_ = self.c.ErrorMsg(self.c.Tr.PassUnameWrong) _ = self.c.ErrorMsg(self.c.Tr.PassUnameWrong)

View File

@ -1,6 +1,7 @@
package controllers package controllers
import ( import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/filetree" "github.com/jesseduffield/lazygit/pkg/gui/filetree"
@ -145,7 +146,7 @@ func (self *FilesRemoveController) remove(node *filetree.FileNode) error {
} }
func (self *FilesRemoveController) ResetSubmodule(submodule *models.SubmoduleConfig) error { func (self *FilesRemoveController) ResetSubmodule(submodule *models.SubmoduleConfig) error {
return self.c.WithWaitingStatus(self.c.Tr.ResettingSubmoduleStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.ResettingSubmoduleStatus, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.ResetSubmodule) self.c.LogAction(self.c.Tr.Actions.ResetSubmodule)
file := self.c.Helpers().WorkingTree.FileForSubmodule(submodule) file := self.c.Helpers().WorkingTree.FileForSubmodule(submodule)

View File

@ -3,6 +3,7 @@ package helpers
import ( import (
"time" "time"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/status" "github.com/jesseduffield/lazygit/pkg/gui/status"
) )
@ -26,12 +27,12 @@ func (self *AppStatusHelper) Toast(message string) {
} }
// withWaitingStatus wraps a function and shows a waiting status while the function is still executing // withWaitingStatus wraps a function and shows a waiting status while the function is still executing
func (self *AppStatusHelper) WithWaitingStatus(message string, f func() error) { func (self *AppStatusHelper) WithWaitingStatus(message string, f func(*gocui.Task) error) {
self.c.OnWorker(func() { self.c.OnWorker(func(task *gocui.Task) {
self.statusMgr().WithWaitingStatus(message, func() { self.statusMgr().WithWaitingStatus(message, func() {
self.renderAppStatus() self.renderAppStatus()
if err := f(); err != nil { if err := f(task); err != nil {
self.c.OnUIThread(func() error { self.c.OnUIThread(func() error {
return self.c.Error(err) return self.c.Error(err)
}) })
@ -49,7 +50,7 @@ func (self *AppStatusHelper) GetStatusString() string {
} }
func (self *AppStatusHelper) renderAppStatus() { func (self *AppStatusHelper) renderAppStatus() {
self.c.OnWorker(func() { self.c.OnWorker(func(_ *gocui.Task) {
ticker := time.NewTicker(time.Millisecond * 50) ticker := time.NewTicker(time.Millisecond * 50)
defer ticker.Stop() defer ticker.Stop()
for range ticker.C { for range ticker.C {

View File

@ -1,6 +1,7 @@
package helpers package helpers
import ( import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking" "github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
@ -75,7 +76,7 @@ func (self *CherryPickHelper) Paste() error {
Title: self.c.Tr.CherryPick, Title: self.c.Tr.CherryPick,
Prompt: self.c.Tr.SureCherryPick, Prompt: self.c.Tr.SureCherryPick,
HandleConfirm: func() error { HandleConfirm: func() error {
return self.c.WithWaitingStatus(self.c.Tr.CherryPickingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.CherryPickingStatus, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.CherryPick) self.c.LogAction(self.c.Tr.Actions.CherryPick)
err := self.c.Git().Rebase.CherryPickCommits(self.getData().CherryPickedCommits) err := self.c.Git().Rebase.CherryPickCommits(self.getData().CherryPickedCommits)
return self.rebaseHelper.CheckMergeOrRebase(err) return self.rebaseHelper.CheckMergeOrRebase(err)

View File

@ -3,6 +3,7 @@ package helpers
import ( import (
"fmt" "fmt"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
) )
@ -41,7 +42,7 @@ func (self *GpgHelper) WithGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus
} }
func (self *GpgHelper) runAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error { func (self *GpgHelper) runAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error {
return self.c.WithWaitingStatus(waitingStatus, func() error { return self.c.WithWaitingStatus(waitingStatus, func(*gocui.Task) error {
if err := cmdObj.StreamOutput().Run(); err != nil { if err := cmdObj.StreamOutput().Run(); err != nil {
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) _ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return self.c.Error( return self.c.Error(

View File

@ -7,6 +7,7 @@ import (
"github.com/jesseduffield/generics/set" "github.com/jesseduffield/generics/set"
"github.com/jesseduffield/generics/slices" "github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums" "github.com/jesseduffield/lazygit/pkg/commands/types/enums"
@ -86,7 +87,9 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
refresh := func(f func()) { refresh := func(f func()) {
if options.Mode == types.ASYNC { if options.Mode == types.ASYNC {
self.c.OnWorker(f) self.c.OnWorker(func(t *gocui.Task) {
f()
})
} else { } else {
f() f()
} }
@ -198,7 +201,7 @@ func getModeName(mode types.RefreshMode) string {
func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() { func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
switch self.c.State().GetRepoState().GetStartupStage() { switch self.c.State().GetRepoState().GetStartupStage() {
case types.INITIAL: case types.INITIAL:
self.c.OnWorker(func() { self.c.OnWorker(func(_ *gocui.Task) {
_ = self.refreshReflogCommits() _ = self.refreshReflogCommits()
self.refreshBranches() self.refreshBranches()
self.c.State().GetRepoState().SetStartupStage(types.COMPLETE) self.c.State().GetRepoState().SetStartupStage(types.COMPLETE)

View File

@ -5,6 +5,7 @@ import (
"strings" "strings"
"github.com/jesseduffield/generics/slices" "github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/style"
@ -50,7 +51,7 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
self.c.Contexts().LocalCommits.SetLimitCommits(true) self.c.Contexts().LocalCommits.SetLimitCommits(true)
} }
return self.c.WithWaitingStatus(waitingStatus, func() error { return self.c.WithWaitingStatus(waitingStatus, func(*gocui.Task) error {
if err := self.c.Git().Branch.Checkout(ref, cmdOptions); err != nil { if err := self.c.Git().Branch.Checkout(ref, cmdOptions); err != nil {
// note, this will only work for english-language git commands. If we force git to use english, and the error isn't this one, then the user will receive an english command they may not understand. I'm not sure what the best solution to this is. Running the command once in english and a second time in the native language is one option // note, this will only work for english-language git commands. If we force git to use english, and the error isn't this one, then the user will receive an english command they may not understand. I'm not sure what the best solution to this is. Running the command once in english and a second time in the native language is one option

View File

@ -5,6 +5,7 @@ import (
"os" "os"
"github.com/jesseduffield/generics/slices" "github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation" "github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
@ -100,7 +101,7 @@ func (self *SuggestionsHelper) GetBranchNameSuggestionsFunc() func(string) []*ty
// Notably, unlike other suggestion functions we're not showing all the options // Notably, unlike other suggestion functions we're not showing all the options
// if nothing has been typed because there'll be too much to display efficiently // if nothing has been typed because there'll be too much to display efficiently
func (self *SuggestionsHelper) GetFilePathSuggestionsFunc() func(string) []*types.Suggestion { func (self *SuggestionsHelper) GetFilePathSuggestionsFunc() func(string) []*types.Suggestion {
_ = self.c.WithWaitingStatus(self.c.Tr.LoadingFileSuggestions, func() error { _ = self.c.WithWaitingStatus(self.c.Tr.LoadingFileSuggestions, func(*gocui.Task) error {
trie := patricia.NewTrie() trie := patricia.NewTrie()
// load every non-gitignored file in the repo // load every non-gitignored file in the repo
ignore, err := gitignore.FromGit() ignore, err := gitignore.FromGit()

View File

@ -1,6 +1,7 @@
package helpers package helpers
import ( import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/updates" "github.com/jesseduffield/lazygit/pkg/updates"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
@ -37,7 +38,7 @@ func (self *UpdateHelper) CheckForUpdateInBackground() {
} }
func (self *UpdateHelper) CheckForUpdateInForeground() error { func (self *UpdateHelper) CheckForUpdateInForeground() error {
return self.c.WithWaitingStatus(self.c.Tr.CheckingForUpdates, func() error { return self.c.WithWaitingStatus(self.c.Tr.CheckingForUpdates, func(*gocui.Task) error {
self.updater.CheckForNewUpdate(func(newVersion string, err error) error { self.updater.CheckForNewUpdate(func(newVersion string, err error) error {
if err != nil { if err != nil {
return self.c.Error(err) return self.c.Error(err)
@ -53,7 +54,7 @@ func (self *UpdateHelper) CheckForUpdateInForeground() error {
} }
func (self *UpdateHelper) startUpdating(newVersion string) { func (self *UpdateHelper) startUpdating(newVersion string) {
_ = self.c.WithWaitingStatus(self.c.Tr.UpdateInProgressWaitingStatus, func() error { _ = self.c.WithWaitingStatus(self.c.Tr.UpdateInProgressWaitingStatus, func(*gocui.Task) error {
self.c.State().SetUpdating(true) self.c.State().SetUpdating(true)
err := self.updater.Update(newVersion) err := self.updater.Update(newVersion)
return self.onUpdateFinish(err) return self.onUpdateFinish(err)

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/fsmiamoto/git-todo-parser/todo" "github.com/fsmiamoto/git-todo-parser/todo"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums" "github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/context"
@ -217,7 +218,7 @@ func (self *LocalCommitsController) squashDown(commit *models.Commit) error {
Title: self.c.Tr.Squash, Title: self.c.Tr.Squash,
Prompt: self.c.Tr.SureSquashThisCommit, Prompt: self.c.Tr.SureSquashThisCommit,
HandleConfirm: func() error { HandleConfirm: func() error {
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.SquashCommitDown) self.c.LogAction(self.c.Tr.Actions.SquashCommitDown)
return self.interactiveRebase(todo.Squash) return self.interactiveRebase(todo.Squash)
}) })
@ -242,7 +243,7 @@ func (self *LocalCommitsController) fixup(commit *models.Commit) error {
Title: self.c.Tr.Fixup, Title: self.c.Tr.Fixup,
Prompt: self.c.Tr.SureFixupThisCommit, Prompt: self.c.Tr.SureFixupThisCommit,
HandleConfirm: func() error { HandleConfirm: func() error {
return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.FixupCommit) self.c.LogAction(self.c.Tr.Actions.FixupCommit)
return self.interactiveRebase(todo.Fixup) return self.interactiveRebase(todo.Fixup)
}) })
@ -338,7 +339,7 @@ func (self *LocalCommitsController) drop(commit *models.Commit) error {
Title: self.c.Tr.DeleteCommitTitle, Title: self.c.Tr.DeleteCommitTitle,
Prompt: self.c.Tr.DeleteCommitPrompt, Prompt: self.c.Tr.DeleteCommitPrompt,
HandleConfirm: func() error { HandleConfirm: func() error {
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.DropCommit) self.c.LogAction(self.c.Tr.Actions.DropCommit)
return self.interactiveRebase(todo.Drop) return self.interactiveRebase(todo.Drop)
}) })
@ -355,7 +356,7 @@ func (self *LocalCommitsController) edit(commit *models.Commit) error {
return nil return nil
} }
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.EditCommit) self.c.LogAction(self.c.Tr.Actions.EditCommit)
err := self.c.Git().Rebase.EditRebase(commit.Sha) err := self.c.Git().Rebase.EditRebase(commit.Sha)
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
@ -460,7 +461,7 @@ func (self *LocalCommitsController) moveDown(commit *models.Commit) error {
return self.c.ErrorMsg(self.c.Tr.AlreadyRebasing) return self.c.ErrorMsg(self.c.Tr.AlreadyRebasing)
} }
return self.c.WithWaitingStatus(self.c.Tr.MovingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.MovingStatus, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.MoveCommitDown) self.c.LogAction(self.c.Tr.Actions.MoveCommitDown)
err := self.c.Git().Rebase.MoveCommitDown(self.c.Model().Commits, index) err := self.c.Git().Rebase.MoveCommitDown(self.c.Model().Commits, index)
if err == nil { if err == nil {
@ -498,7 +499,7 @@ func (self *LocalCommitsController) moveUp(commit *models.Commit) error {
return self.c.ErrorMsg(self.c.Tr.AlreadyRebasing) return self.c.ErrorMsg(self.c.Tr.AlreadyRebasing)
} }
return self.c.WithWaitingStatus(self.c.Tr.MovingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.MovingStatus, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.MoveCommitUp) self.c.LogAction(self.c.Tr.Actions.MoveCommitUp)
err := self.c.Git().Rebase.MoveCommitUp(self.c.Model().Commits, index) err := self.c.Git().Rebase.MoveCommitUp(self.c.Model().Commits, index)
if err == nil { if err == nil {
@ -524,7 +525,7 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
Title: self.c.Tr.AmendCommitTitle, Title: self.c.Tr.AmendCommitTitle,
Prompt: self.c.Tr.AmendCommitPrompt, Prompt: self.c.Tr.AmendCommitPrompt,
HandleConfirm: func() error { HandleConfirm: func() error {
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.AmendCommit) self.c.LogAction(self.c.Tr.Actions.AmendCommit)
err := self.c.Git().Rebase.AmendTo(self.c.Model().Commits, self.context().GetView().SelectedLineIdx()) err := self.c.Git().Rebase.AmendTo(self.c.Model().Commits, self.context().GetView().SelectedLineIdx())
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
@ -558,7 +559,7 @@ func (self *LocalCommitsController) amendAttribute(commit *models.Commit) error
} }
func (self *LocalCommitsController) resetAuthor() error { func (self *LocalCommitsController) resetAuthor() error {
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.ResetCommitAuthor) self.c.LogAction(self.c.Tr.Actions.ResetCommitAuthor)
if err := self.c.Git().Rebase.ResetCommitAuthor(self.c.Model().Commits, self.context().GetSelectedLineIdx()); err != nil { if err := self.c.Git().Rebase.ResetCommitAuthor(self.c.Model().Commits, self.context().GetSelectedLineIdx()); err != nil {
return self.c.Error(err) return self.c.Error(err)
@ -573,7 +574,7 @@ func (self *LocalCommitsController) setAuthor() error {
Title: self.c.Tr.SetAuthorPromptTitle, Title: self.c.Tr.SetAuthorPromptTitle,
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(), FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(),
HandleConfirm: func(value string) error { HandleConfirm: func(value string) error {
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.SetCommitAuthor) self.c.LogAction(self.c.Tr.Actions.SetCommitAuthor)
if err := self.c.Git().Rebase.SetCommitAuthor(self.c.Model().Commits, self.context().GetSelectedLineIdx(), value); err != nil { if err := self.c.Git().Rebase.SetCommitAuthor(self.c.Model().Commits, self.context().GetSelectedLineIdx(), value); err != nil {
return self.c.Error(err) return self.c.Error(err)
@ -671,7 +672,7 @@ func (self *LocalCommitsController) squashAllAboveFixupCommits(commit *models.Co
Title: self.c.Tr.SquashAboveCommits, Title: self.c.Tr.SquashAboveCommits,
Prompt: prompt, Prompt: prompt,
HandleConfirm: func() error { HandleConfirm: func() error {
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits) self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits)
err := self.c.Git().Rebase.SquashAllAboveFixupCommits(commit) err := self.c.Git().Rebase.SquashAllAboveFixupCommits(commit)
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
@ -723,7 +724,7 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
self.context().SetLimitCommits(false) self.context().SetLimitCommits(false)
} }
return self.c.WithWaitingStatus(self.c.Tr.LoadingCommits, func() error { return self.c.WithWaitingStatus(self.c.Tr.LoadingCommits, func(*gocui.Task) error {
return self.c.Refresh( return self.c.Refresh(
types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS}}, types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS}},
) )
@ -766,7 +767,7 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
onPress := func(value string) func() error { onPress := func(value string) func() error {
return func() error { return func() error {
self.c.UserConfig.Git.Log.Order = value self.c.UserConfig.Git.Log.Order = value
return self.c.WithWaitingStatus(self.c.Tr.LoadingCommits, func() error { return self.c.WithWaitingStatus(self.c.Tr.LoadingCommits, func(*gocui.Task) error {
return self.c.Refresh( return self.c.Refresh(
types.RefreshOptions{ types.RefreshOptions{
Mode: types.SYNC, Mode: types.SYNC,
@ -816,7 +817,7 @@ func (self *LocalCommitsController) GetOnFocus() func(types.OnFocusOpts) error {
context := self.context() context := self.context()
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() { if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
context.SetLimitCommits(false) context.SetLimitCommits(false)
self.c.OnWorker(func() { self.c.OnWorker(func(_ *gocui.Task) {
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}}); err != nil { if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}}); err != nil {
_ = self.c.Error(err) _ = self.c.Error(err)
} }

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
@ -117,9 +118,9 @@ func (self *RemoteBranchesController) delete(selectedBranch *models.RemoteBranch
Title: self.c.Tr.DeleteRemoteBranch, Title: self.c.Tr.DeleteRemoteBranch,
Prompt: message, Prompt: message,
HandleConfirm: func() error { HandleConfirm: func() error {
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(task *gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.DeleteRemoteBranch) self.c.LogAction(self.c.Tr.Actions.DeleteRemoteBranch)
err := self.c.Git().Remote.DeleteRemoteBranch(selectedBranch.RemoteName, selectedBranch.Name) err := self.c.Git().Remote.DeleteRemoteBranch(task, selectedBranch.RemoteName, selectedBranch.Name)
if err != nil { if err != nil {
_ = self.c.Error(err) _ = self.c.Error(err)
} }

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/style"
@ -197,8 +198,8 @@ func (self *RemotesController) edit(remote *models.Remote) error {
} }
func (self *RemotesController) fetch(remote *models.Remote) error { func (self *RemotesController) fetch(remote *models.Remote) error {
return self.c.WithWaitingStatus(self.c.Tr.FetchingRemoteStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.FetchingRemoteStatus, func(task *gocui.Task) error {
err := self.c.Git().Sync.FetchRemote(remote.Name) err := self.c.Git().Sync.FetchRemote(task, remote.Name)
if err != nil { if err != nil {
_ = self.c.Error(err) _ = self.c.Error(err)
} }

View File

@ -1,6 +1,7 @@
package controllers package controllers
import ( import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
) )
@ -59,7 +60,7 @@ func (self *SubCommitsController) GetOnFocus() func(types.OnFocusOpts) error {
context := self.context() context := self.context()
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() { if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
context.SetLimitCommits(false) context.SetLimitCommits(false)
self.c.OnWorker(func() { self.c.OnWorker(func(_ *gocui.Task) {
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUB_COMMITS}}); err != nil { if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUB_COMMITS}}); err != nil {
_ = self.c.Error(err) _ = self.c.Error(err)
} }

View File

@ -5,6 +5,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/style"
@ -130,7 +131,7 @@ func (self *SubmodulesController) add() error {
Title: self.c.Tr.NewSubmodulePath, Title: self.c.Tr.NewSubmodulePath,
InitialContent: submoduleName, InitialContent: submoduleName,
HandleConfirm: func(submodulePath string) error { HandleConfirm: func(submodulePath string) error {
return self.c.WithWaitingStatus(self.c.Tr.AddingSubmoduleStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.AddingSubmoduleStatus, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.AddSubmodule) self.c.LogAction(self.c.Tr.Actions.AddSubmodule)
err := self.c.Git().Submodule.Add(submoduleName, submodulePath, submoduleUrl) err := self.c.Git().Submodule.Add(submoduleName, submodulePath, submoduleUrl)
if err != nil { if err != nil {
@ -152,7 +153,7 @@ func (self *SubmodulesController) editURL(submodule *models.SubmoduleConfig) err
Title: fmt.Sprintf(self.c.Tr.UpdateSubmoduleUrl, submodule.Name), Title: fmt.Sprintf(self.c.Tr.UpdateSubmoduleUrl, submodule.Name),
InitialContent: submodule.Url, InitialContent: submodule.Url,
HandleConfirm: func(newUrl string) error { HandleConfirm: func(newUrl string) error {
return self.c.WithWaitingStatus(self.c.Tr.UpdatingSubmoduleUrlStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.UpdatingSubmoduleUrlStatus, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.UpdateSubmoduleUrl) self.c.LogAction(self.c.Tr.Actions.UpdateSubmoduleUrl)
err := self.c.Git().Submodule.UpdateUrl(submodule.Name, submodule.Path, newUrl) err := self.c.Git().Submodule.UpdateUrl(submodule.Name, submodule.Path, newUrl)
if err != nil { if err != nil {
@ -166,7 +167,7 @@ func (self *SubmodulesController) editURL(submodule *models.SubmoduleConfig) err
} }
func (self *SubmodulesController) init(submodule *models.SubmoduleConfig) error { func (self *SubmodulesController) init(submodule *models.SubmoduleConfig) error {
return self.c.WithWaitingStatus(self.c.Tr.InitializingSubmoduleStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.InitializingSubmoduleStatus, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.InitialiseSubmodule) self.c.LogAction(self.c.Tr.Actions.InitialiseSubmodule)
err := self.c.Git().Submodule.Init(submodule.Path) err := self.c.Git().Submodule.Init(submodule.Path)
if err != nil { if err != nil {
@ -184,7 +185,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
{ {
LabelColumns: []string{self.c.Tr.BulkInitSubmodules, style.FgGreen.Sprint(self.c.Git().Submodule.BulkInitCmdObj().ToString())}, LabelColumns: []string{self.c.Tr.BulkInitSubmodules, style.FgGreen.Sprint(self.c.Git().Submodule.BulkInitCmdObj().ToString())},
OnPress: func() error { OnPress: func() error {
return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func() error { return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.BulkInitialiseSubmodules) self.c.LogAction(self.c.Tr.Actions.BulkInitialiseSubmodules)
err := self.c.Git().Submodule.BulkInitCmdObj().Run() err := self.c.Git().Submodule.BulkInitCmdObj().Run()
if err != nil { if err != nil {
@ -199,7 +200,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
{ {
LabelColumns: []string{self.c.Tr.BulkUpdateSubmodules, style.FgYellow.Sprint(self.c.Git().Submodule.BulkUpdateCmdObj().ToString())}, LabelColumns: []string{self.c.Tr.BulkUpdateSubmodules, style.FgYellow.Sprint(self.c.Git().Submodule.BulkUpdateCmdObj().ToString())},
OnPress: func() error { OnPress: func() error {
return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func() error { return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.BulkUpdateSubmodules) self.c.LogAction(self.c.Tr.Actions.BulkUpdateSubmodules)
if err := self.c.Git().Submodule.BulkUpdateCmdObj().Run(); err != nil { if err := self.c.Git().Submodule.BulkUpdateCmdObj().Run(); err != nil {
return self.c.Error(err) return self.c.Error(err)
@ -213,7 +214,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
{ {
LabelColumns: []string{self.c.Tr.BulkDeinitSubmodules, style.FgRed.Sprint(self.c.Git().Submodule.BulkDeinitCmdObj().ToString())}, LabelColumns: []string{self.c.Tr.BulkDeinitSubmodules, style.FgRed.Sprint(self.c.Git().Submodule.BulkDeinitCmdObj().ToString())},
OnPress: func() error { OnPress: func() error {
return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func() error { return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.BulkDeinitialiseSubmodules) self.c.LogAction(self.c.Tr.Actions.BulkDeinitialiseSubmodules)
if err := self.c.Git().Submodule.BulkDeinitCmdObj().Run(); err != nil { if err := self.c.Git().Submodule.BulkDeinitCmdObj().Run(); err != nil {
return self.c.Error(err) return self.c.Error(err)
@ -229,7 +230,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
} }
func (self *SubmodulesController) update(submodule *models.SubmoduleConfig) error { func (self *SubmodulesController) update(submodule *models.SubmoduleConfig) error {
return self.c.WithWaitingStatus(self.c.Tr.UpdatingSubmoduleStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.UpdatingSubmoduleStatus, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.UpdateSubmodule) self.c.LogAction(self.c.Tr.Actions.UpdateSubmodule)
err := self.c.Git().Submodule.Update(submodule.Path) err := self.c.Git().Submodule.Update(submodule.Path)
if err != nil { if err != nil {

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
@ -138,15 +139,16 @@ type PullFilesOptions struct {
} }
func (self *SyncController) PullAux(opts PullFilesOptions) error { func (self *SyncController) PullAux(opts PullFilesOptions) error {
return self.c.WithLoaderPanel(self.c.Tr.PullWait, func() error { return self.c.WithLoaderPanel(self.c.Tr.PullWait, func(task *gocui.Task) error {
return self.pullWithLock(opts) return self.pullWithLock(task, opts)
}) })
} }
func (self *SyncController) pullWithLock(opts PullFilesOptions) error { func (self *SyncController) pullWithLock(task *gocui.Task, opts PullFilesOptions) error {
self.c.LogAction(opts.Action) self.c.LogAction(opts.Action)
err := self.c.Git().Sync.Pull( err := self.c.Git().Sync.Pull(
task,
git_commands.PullOptions{ git_commands.PullOptions{
RemoteName: opts.UpstreamRemote, RemoteName: opts.UpstreamRemote,
BranchName: opts.UpstreamBranch, BranchName: opts.UpstreamBranch,
@ -165,14 +167,16 @@ type pushOpts struct {
} }
func (self *SyncController) pushAux(opts pushOpts) error { func (self *SyncController) pushAux(opts pushOpts) error {
return self.c.WithLoaderPanel(self.c.Tr.PushWait, func() error { return self.c.WithLoaderPanel(self.c.Tr.PushWait, func(task *gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.Push) self.c.LogAction(self.c.Tr.Actions.Push)
err := self.c.Git().Sync.Push(git_commands.PushOpts{ err := self.c.Git().Sync.Push(
Force: opts.force, task,
UpstreamRemote: opts.upstreamRemote, git_commands.PushOpts{
UpstreamBranch: opts.upstreamBranch, Force: opts.force,
SetUpstream: opts.setUpstream, UpstreamRemote: opts.upstreamRemote,
}) UpstreamBranch: opts.upstreamBranch,
SetUpstream: opts.setUpstream,
})
if err != nil { if err != nil {
if !opts.force && strings.Contains(err.Error(), "Updates were rejected") { if !opts.force && strings.Contains(err.Error(), "Updates were rejected") {
forcePushDisabled := self.c.UserConfig.Git.DisableForcePushing forcePushDisabled := self.c.UserConfig.Git.DisableForcePushing

View File

@ -1,6 +1,7 @@
package controllers package controllers
import ( import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
@ -121,9 +122,9 @@ func (self *TagsController) push(tag *models.Tag) error {
InitialContent: "origin", InitialContent: "origin",
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteSuggestionsFunc(), FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteSuggestionsFunc(),
HandleConfirm: func(response string) error { HandleConfirm: func(response string) error {
return self.c.WithWaitingStatus(self.c.Tr.PushingTagStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.PushingTagStatus, func(task *gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.PushTag) self.c.LogAction(self.c.Tr.Actions.PushTag)
err := self.c.Git().Tag.Push(response, tag.Name) err := self.c.Git().Tag.Push(task, response, tag.Name)
if err != nil { if err != nil {
_ = self.c.Error(err) _ = self.c.Error(err)
} }

View File

@ -3,6 +3,7 @@ package controllers
import ( import (
"fmt" "fmt"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums" "github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
@ -247,7 +248,7 @@ func (self *UndoController) hardResetWithAutoStash(commitSha string, options har
Title: self.c.Tr.AutoStashTitle, Title: self.c.Tr.AutoStashTitle,
Prompt: self.c.Tr.AutoStashPrompt, Prompt: self.c.Tr.AutoStashPrompt,
HandleConfirm: func() error { HandleConfirm: func() error {
return self.c.WithWaitingStatus(options.WaitingStatus, func() error { return self.c.WithWaitingStatus(options.WaitingStatus, func(*gocui.Task) error {
if err := self.c.Git().Stash.Save(self.c.Tr.StashPrefix + commitSha); err != nil { if err := self.c.Git().Stash.Save(self.c.Tr.StashPrefix + commitSha); err != nil {
return self.c.Error(err) return self.c.Error(err)
} }
@ -268,7 +269,7 @@ func (self *UndoController) hardResetWithAutoStash(commitSha string, options har
}) })
} }
return self.c.WithWaitingStatus(options.WaitingStatus, func() error { return self.c.WithWaitingStatus(options.WaitingStatus, func(*gocui.Task) error {
return reset() return reset()
}) })
} }

View File

@ -472,10 +472,10 @@ func NewGui(
func() error { return gui.State.ContextMgr.Pop() }, func() error { return gui.State.ContextMgr.Pop() },
func() types.Context { return gui.State.ContextMgr.Current() }, func() types.Context { return gui.State.ContextMgr.Current() },
gui.createMenu, gui.createMenu,
func(message string, f func() error) { gui.helpers.AppStatus.WithWaitingStatus(message, f) }, func(message string, f func(*gocui.Task) error) { gui.helpers.AppStatus.WithWaitingStatus(message, f) },
func(message string) { gui.helpers.AppStatus.Toast(message) }, func(message string) { gui.helpers.AppStatus.Toast(message) },
func() string { return gui.Views.Confirmation.TextArea.GetContent() }, func() string { return gui.Views.Confirmation.TextArea.GetContent() },
func(f func()) { gui.c.OnWorker(f) }, func(f func(*gocui.Task)) { gui.c.OnWorker(f) },
) )
guiCommon := &guiCommon{gui: gui, IPopupHandler: gui.PopupHandler} guiCommon := &guiCommon{gui: gui, IPopupHandler: gui.PopupHandler}
@ -488,8 +488,6 @@ func NewGui(
gui.LogCommand, gui.LogCommand,
gui.getCmdWriter, gui.getCmdWriter,
credentialsHelper.PromptUserForCredential, credentialsHelper.PromptUserForCredential,
func() { gui.g.IncrementBusyCount() },
func() { gui.g.DecrementBusyCount() },
) )
osCommand := oscommands.NewOSCommand(cmn, config, oscommands.GetPlatform(), guiIO) osCommand := oscommands.NewOSCommand(cmn, config, oscommands.GetPlatform(), guiIO)
@ -786,15 +784,15 @@ func (gui *Gui) showInitialPopups(tasks []func(chan struct{}) error) {
gui.waitForIntro.Add(len(tasks)) gui.waitForIntro.Add(len(tasks))
done := make(chan struct{}) done := make(chan struct{})
gui.c.OnWorker(func() { gui.c.OnWorker(func(gocuiTask *gocui.Task) {
for _, task := range tasks { for _, task := range tasks {
if err := task(done); err != nil { if err := task(done); err != nil {
_ = gui.c.Error(err) _ = gui.c.Error(err)
} }
gui.g.DecrementBusyCount() gocuiTask.Pause()
<-done <-done
gui.g.IncrementBusyCount() gocuiTask.Continue()
gui.waitForIntro.Done() gui.waitForIntro.Done()
} }
}) })
@ -835,7 +833,7 @@ func (gui *Gui) onUIThread(f func() error) {
}) })
} }
func (gui *Gui) onWorker(f func()) { func (gui *Gui) onWorker(f func(*gocui.Task)) {
gui.g.OnWorker(f) gui.g.OnWorker(f)
} }

View File

@ -136,7 +136,7 @@ func (self *guiCommon) OnUIThread(f func() error) {
self.gui.onUIThread(f) self.gui.onUIThread(f)
} }
func (self *guiCommon) OnWorker(f func()) { func (self *guiCommon) OnWorker(f func(*gocui.Task)) {
self.gui.onWorker(f) self.gui.onWorker(f)
} }

View File

@ -1,6 +1,9 @@
package popup package popup
import "github.com/jesseduffield/lazygit/pkg/gui/types" import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type FakePopupHandler struct { type FakePopupHandler struct {
OnErrorMsg func(message string) error OnErrorMsg func(message string) error
@ -30,12 +33,12 @@ func (self *FakePopupHandler) Prompt(opts types.PromptOpts) error {
return self.OnPrompt(opts) return self.OnPrompt(opts)
} }
func (self *FakePopupHandler) WithLoaderPanel(message string, f func() error) error { func (self *FakePopupHandler) WithLoaderPanel(message string, f func(*gocui.Task) error) error {
return f() return f(&gocui.Task{})
} }
func (self *FakePopupHandler) WithWaitingStatus(message string, f func() error) error { func (self *FakePopupHandler) WithWaitingStatus(message string, f func(*gocui.Task) error) error {
return f() return f(&gocui.Task{})
} }
func (self *FakePopupHandler) Menu(opts types.CreateMenuOptions) error { func (self *FakePopupHandler) Menu(opts types.CreateMenuOptions) error {

View File

@ -21,10 +21,10 @@ type PopupHandler struct {
popContextFn func() error popContextFn func() error
currentContextFn func() types.Context currentContextFn func() types.Context
createMenuFn func(types.CreateMenuOptions) error createMenuFn func(types.CreateMenuOptions) error
withWaitingStatusFn func(message string, f func() error) withWaitingStatusFn func(message string, f func(*gocui.Task) error)
toastFn func(message string) toastFn func(message string)
getPromptInputFn func() string getPromptInputFn func() string
onWorker func(func()) onWorker func(func(*gocui.Task))
} }
var _ types.IPopupHandler = &PopupHandler{} var _ types.IPopupHandler = &PopupHandler{}
@ -36,10 +36,10 @@ func NewPopupHandler(
popContextFn func() error, popContextFn func() error,
currentContextFn func() types.Context, currentContextFn func() types.Context,
createMenuFn func(types.CreateMenuOptions) error, createMenuFn func(types.CreateMenuOptions) error,
withWaitingStatusFn func(message string, f func() error), withWaitingStatusFn func(message string, f func(*gocui.Task) error),
toastFn func(message string), toastFn func(message string),
getPromptInputFn func() string, getPromptInputFn func() string,
onWorker func(func()), onWorker func(func(*gocui.Task)),
) *PopupHandler { ) *PopupHandler {
return &PopupHandler{ return &PopupHandler{
Common: common, Common: common,
@ -64,7 +64,7 @@ func (self *PopupHandler) Toast(message string) {
self.toastFn(message) self.toastFn(message)
} }
func (self *PopupHandler) WithWaitingStatus(message string, f func() error) error { func (self *PopupHandler) WithWaitingStatus(message string, f func(*gocui.Task) error) error {
self.withWaitingStatusFn(message, f) self.withWaitingStatusFn(message, f)
return nil return nil
} }
@ -124,7 +124,7 @@ func (self *PopupHandler) Prompt(opts types.PromptOpts) error {
}) })
} }
func (self *PopupHandler) WithLoaderPanel(message string, f func() error) error { func (self *PopupHandler) WithLoaderPanel(message string, f func(*gocui.Task) error) error {
index := 0 index := 0
self.Lock() self.Lock()
self.index++ self.index++
@ -143,8 +143,8 @@ func (self *PopupHandler) WithLoaderPanel(message string, f func() error) error
return nil return nil
} }
self.onWorker(func() { self.onWorker(func(task *gocui.Task) {
if err := f(); err != nil { if err := f(task); err != nil {
self.Log.Error(err) self.Log.Error(err)
} }

View File

@ -6,6 +6,7 @@ import (
"text/template" "text/template"
"github.com/jesseduffield/generics/slices" "github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/style"
@ -264,7 +265,7 @@ func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, ses
loadingText = self.c.Tr.RunningCustomCommandStatus loadingText = self.c.Tr.RunningCustomCommandStatus
} }
return self.c.WithWaitingStatus(loadingText, func() error { return self.c.WithWaitingStatus(loadingText, func(*gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.CustomCommand) self.c.LogAction(self.c.Tr.Actions.CustomCommand)
if customCommand.Stream { if customCommand.Stream {

View File

@ -130,8 +130,9 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager {
func() { func() {
_ = view.SetOrigin(0, 0) _ = view.SetOrigin(0, 0)
}, },
gui.c.GocuiGui().IncrementBusyCount, func() *gocui.Task {
gui.c.GocuiGui().DecrementBusyCount, return gui.c.GocuiGui().NewTask()
},
) )
gui.viewBufferManagerMap[view.Name()] = manager gui.viewBufferManagerMap[view.Name()] = manager
} }

View File

@ -79,7 +79,7 @@ type IGuiCommon interface {
OnUIThread(f func() error) OnUIThread(f func() error)
// Runs a function in a goroutine. Use this whenever you want to run a goroutine and keep track of the fact // Runs a function in a goroutine. Use this whenever you want to run a goroutine and keep track of the fact
// that lazygit is still busy. See docs/dev/Busy.md // that lazygit is still busy. See docs/dev/Busy.md
OnWorker(f func()) OnWorker(f func(*gocui.Task))
// returns the gocui Gui struct. There is a good chance you don't actually want to use // returns the gocui Gui struct. There is a good chance you don't actually want to use
// this struct and instead want to use another method above // this struct and instead want to use another method above
@ -121,8 +121,8 @@ type IPopupHandler interface {
Confirm(opts ConfirmOpts) error Confirm(opts ConfirmOpts) error
// Shows a popup prompting the user for input. // Shows a popup prompting the user for input.
Prompt(opts PromptOpts) error Prompt(opts PromptOpts) error
WithLoaderPanel(message string, f func() error) error WithLoaderPanel(message string, f func(*gocui.Task) error) error
WithWaitingStatus(message string, f func() error) error WithWaitingStatus(message string, f func(*gocui.Task) error) error
Menu(opts CreateMenuOptions) error Menu(opts CreateMenuOptions) error
Toast(message string) Toast(message string)
GetPromptInput() string GetPromptInput() string

View File

@ -1,6 +1,7 @@
package tasks package tasks
import ( import (
"github.com/jesseduffield/gocui"
"github.com/sasha-s/go-deadlock" "github.com/sasha-s/go-deadlock"
) )
@ -17,10 +18,10 @@ type AsyncHandler struct {
lastId int lastId int
mutex deadlock.Mutex mutex deadlock.Mutex
onReject func() onReject func()
onWorker func(func()) onWorker func(func(*gocui.Task))
} }
func NewAsyncHandler(onWorker func(func())) *AsyncHandler { func NewAsyncHandler(onWorker func(func(*gocui.Task))) *AsyncHandler {
return &AsyncHandler{ return &AsyncHandler{
mutex: deadlock.Mutex{}, mutex: deadlock.Mutex{},
onWorker: onWorker, onWorker: onWorker,
@ -33,7 +34,7 @@ func (self *AsyncHandler) Do(f func() func()) {
id := self.currentId id := self.currentId
self.mutex.Unlock() self.mutex.Unlock()
self.onWorker(func() { self.onWorker(func(*gocui.Task) {
after := f() after := f()
self.handle(after, id) self.handle(after, id)
}) })

View File

@ -5,6 +5,7 @@ import (
"sync" "sync"
"testing" "testing"
"github.com/jesseduffield/gocui"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -12,8 +13,8 @@ func TestAsyncHandler(t *testing.T) {
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(2) wg.Add(2)
onWorker := func(f func()) { onWorker := func(f func(*gocui.Task)) {
go f() go f(&gocui.Task{})
} }
handler := NewAsyncHandler(onWorker) handler := NewAsyncHandler(onWorker)
handler.onReject = func() { handler.onReject = func() {

View File

@ -9,6 +9,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sasha-s/go-deadlock" "github.com/sasha-s/go-deadlock"
@ -49,8 +50,7 @@ type ViewBufferManager struct {
onEndOfInput func() onEndOfInput func()
// see docs/dev/Busy.md // see docs/dev/Busy.md
incrementBusyCount func() newTask func() *gocui.Task
decrementBusyCount func()
// if the user flicks through a heap of items, with each one // if the user flicks through a heap of items, with each one
// spawning a process to render something to the main view, // spawning a process to render something to the main view,
@ -80,19 +80,17 @@ func NewViewBufferManager(
refreshView func(), refreshView func(),
onEndOfInput func(), onEndOfInput func(),
onNewKey func(), onNewKey func(),
incrementBusyCount func(), newTask func() *gocui.Task,
decrementBusyCount func(),
) *ViewBufferManager { ) *ViewBufferManager {
return &ViewBufferManager{ return &ViewBufferManager{
Log: log, Log: log,
writer: writer, writer: writer,
beforeStart: beforeStart, beforeStart: beforeStart,
refreshView: refreshView, refreshView: refreshView,
onEndOfInput: onEndOfInput, onEndOfInput: onEndOfInput,
readLines: make(chan LinesToRead, 1024), readLines: make(chan LinesToRead, 1024),
onNewKey: onNewKey, onNewKey: onNewKey,
incrementBusyCount: incrementBusyCount, newTask: newTask,
decrementBusyCount: decrementBusyCount,
} }
} }
@ -298,18 +296,18 @@ type TaskOpts struct {
} }
func (self *ViewBufferManager) NewTask(f func(TaskOpts) error, key string) error { func (self *ViewBufferManager) NewTask(f func(TaskOpts) error, key string) error {
self.incrementBusyCount() task := self.newTask()
var decrementCounterOnce sync.Once var completeTaskOnce sync.Once
decrementCounter := func() { completeTask := func() {
decrementCounterOnce.Do(func() { completeTaskOnce.Do(func() {
self.decrementBusyCount() task.Done()
}) })
} }
go utils.Safe(func() { go utils.Safe(func() {
defer decrementCounter() defer completeTask()
self.taskIDMutex.Lock() self.taskIDMutex.Lock()
self.newTaskID++ self.newTaskID++
@ -349,7 +347,7 @@ func (self *ViewBufferManager) NewTask(f func(TaskOpts) error, key string) error
self.waitingMutex.Unlock() self.waitingMutex.Unlock()
if err := f(TaskOpts{Stop: stop, InitialContentLoaded: decrementCounter}); err != nil { if err := f(TaskOpts{Stop: stop, InitialContentLoaded: completeTask}); err != nil {
self.Log.Error(err) // might need an onError callback self.Log.Error(err) // might need an onError callback
} }

View File

@ -10,6 +10,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/secureexec" "github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
@ -31,7 +32,10 @@ func TestNewCmdTaskInstantStop(t *testing.T) {
onEndOfInput, getOnEndOfInputCallCount := getCounter() onEndOfInput, getOnEndOfInputCallCount := getCounter()
onNewKey, getOnNewKeyCallCount := getCounter() onNewKey, getOnNewKeyCallCount := getCounter()
onDone, getOnDoneCallCount := getCounter() onDone, getOnDoneCallCount := getCounter()
incBusyCount, decBusyCount, getBusyCount := getIncDecCounter(1) task := &gocui.Task{}
newTask := func() *gocui.Task {
return task
}
manager := NewViewBufferManager( manager := NewViewBufferManager(
utils.NewDummyLog(), utils.NewDummyLog(),
@ -40,8 +44,7 @@ func TestNewCmdTaskInstantStop(t *testing.T) {
refreshView, refreshView,
onEndOfInput, onEndOfInput,
onNewKey, onNewKey,
incBusyCount, newTask,
decBusyCount,
) )
stop := make(chan struct{}) stop := make(chan struct{})
@ -57,7 +60,7 @@ func TestNewCmdTaskInstantStop(t *testing.T) {
fn := manager.NewCmdTask(start, "prefix\n", LinesToRead{20, -1}, onDone) fn := manager.NewCmdTask(start, "prefix\n", LinesToRead{20, -1}, onDone)
_ = fn(TaskOpts{Stop: stop, InitialContentLoaded: decBusyCount}) _ = fn(TaskOpts{Stop: stop, InitialContentLoaded: func() { task.Done() }})
callCountExpectations := []struct { callCountExpectations := []struct {
expected int expected int

View File

@ -174,12 +174,88 @@ type Gui struct {
suspendedMutex sync.Mutex suspendedMutex sync.Mutex
suspended bool suspended bool
taskManager *TaskManager
}
type TaskManager struct {
// Tracks whether the program is busy (i.e. either something is happening on // Tracks whether the program is busy (i.e. either something is happening on
// the main goroutine or a worker goroutine). Used by integration tests // the main goroutine or a worker goroutine). Used by integration tests
// to wait until the program is idle before progressing. // to wait until the program is idle before progressing.
busyCount int idleListeners []chan struct{}
busyCountMutex sync.Mutex tasks map[int]*Task
idleListeners []chan struct{} newTaskId int
tasksMutex sync.Mutex
}
func newTaskManager() *TaskManager {
return &TaskManager{
tasks: make(map[int]*Task),
idleListeners: []chan struct{}{},
}
}
func (self *TaskManager) NewTask() *Task {
self.tasksMutex.Lock()
defer self.tasksMutex.Unlock()
self.newTaskId++
taskId := self.newTaskId
withMutex := func(f func()) {
self.tasksMutex.Lock()
defer self.tasksMutex.Unlock()
f()
// Check if all tasks are done
for _, task := range self.tasks {
if task.isBusy {
return
}
}
// If we get here, all tasks are done, so
// notify listeners that the program is idle
for _, listener := range self.idleListeners {
listener <- struct{}{}
}
}
onDone := func() {
withMutex(func() {
delete(self.tasks, taskId)
})
}
task := &Task{id: taskId, isBusy: true, onDone: onDone, withMutex: withMutex}
self.tasks[taskId] = task
return task
}
func (self *TaskManager) AddIdleListener(c chan struct{}) {
self.idleListeners = append(self.idleListeners, c)
}
type Task struct {
id int
isBusy bool
onDone func()
withMutex func(func())
}
func (self *Task) Done() {
self.onDone()
}
func (self *Task) Pause() {
self.withMutex(func() {
self.isBusy = false
})
}
func (self *Task) Continue() {
self.withMutex(func() {
self.isBusy = true
})
} }
// NewGui returns a new Gui object with a given output mode. // NewGui returns a new Gui object with a given output mode.
@ -212,6 +288,7 @@ func NewGui(mode OutputMode, supportOverlaps bool, playRecording bool, headless
g.gEvents = make(chan GocuiEvent, 20) g.gEvents = make(chan GocuiEvent, 20)
g.userEvents = make(chan userEvent, 20) g.userEvents = make(chan userEvent, 20)
g.taskManager = newTaskManager()
if playRecording { if playRecording {
g.ReplayedEvents = replayedEvents{ g.ReplayedEvents = replayedEvents{
@ -237,11 +314,15 @@ func NewGui(mode OutputMode, supportOverlaps bool, playRecording bool, headless
return g, nil return g, nil
} }
func (g *Gui) NewTask() *Task {
return g.taskManager.NewTask()
}
// An idle listener listens for when the program is idle. This is useful for // An idle listener listens for when the program is idle. This is useful for
// integration tests which can wait for the program to be idle before taking // integration tests which can wait for the program to be idle before taking
// the next step in the test. // the next step in the test.
func (g *Gui) AddIdleListener(c chan struct{}) { func (g *Gui) AddIdleListener(c chan struct{}) {
g.idleListeners = append(g.idleListeners, c) g.taskManager.AddIdleListener(c)
} }
// Close finalizes the library. It should be called after a successful // Close finalizes the library. It should be called after a successful
@ -607,7 +688,8 @@ func getKey(key interface{}) (Key, rune, error) {
// userEvent represents an event triggered by the user. // userEvent represents an event triggered by the user.
type userEvent struct { type userEvent struct {
f func(*Gui) error f func(*Gui) error
task *Task
} }
// Update executes the passed function. This method can be called safely from a // Update executes the passed function. This method can be called safely from a
@ -616,22 +698,22 @@ type userEvent struct {
// the user events queue. Given that Update spawns a goroutine, the order in // the user events queue. Given that Update spawns a goroutine, the order in
// which the user events will be handled is not guaranteed. // which the user events will be handled is not guaranteed.
func (g *Gui) Update(f func(*Gui) error) { func (g *Gui) Update(f func(*Gui) error) {
g.IncrementBusyCount() task := g.NewTask()
go g.updateAsyncAux(f) go g.updateAsyncAux(f, task)
} }
// UpdateAsync is a version of Update that does not spawn a go routine, it can // UpdateAsync is a version of Update that does not spawn a go routine, it can
// be a bit more efficient in cases where Update is called many times like when // be a bit more efficient in cases where Update is called many times like when
// tailing a file. In general you should use Update() // tailing a file. In general you should use Update()
func (g *Gui) UpdateAsync(f func(*Gui) error) { func (g *Gui) UpdateAsync(f func(*Gui) error) {
g.IncrementBusyCount() task := g.NewTask()
g.updateAsyncAux(f) g.updateAsyncAux(f, task)
} }
func (g *Gui) updateAsyncAux(f func(*Gui) error) { func (g *Gui) updateAsyncAux(f func(*Gui) error, task *Task) {
g.userEvents <- userEvent{f: f} g.userEvents <- userEvent{f: f, task: task}
} }
// Calls a function in a goroutine. Handles panics gracefully and tracks // Calls a function in a goroutine. Handles panics gracefully and tracks
@ -640,15 +722,15 @@ func (g *Gui) updateAsyncAux(f func(*Gui) error) {
// consider itself 'busy` as it runs the code. Don't use for long-running // consider itself 'busy` as it runs the code. Don't use for long-running
// background goroutines where you wouldn't want lazygit to be considered busy // background goroutines where you wouldn't want lazygit to be considered busy
// (i.e. when you wouldn't want a loader to be shown to the user) // (i.e. when you wouldn't want a loader to be shown to the user)
func (g *Gui) OnWorker(f func()) { func (g *Gui) OnWorker(f func(*Task)) {
g.IncrementBusyCount() task := g.NewTask()
go func() { go func() {
g.onWorkerAux(f) g.onWorkerAux(f, task)
g.DecrementBusyCount() task.Done()
}() }()
} }
func (g *Gui) onWorkerAux(f func()) { func (g *Gui) onWorkerAux(f func(*Task), task *Task) {
panicking := true panicking := true
defer func() { defer func() {
if panicking && Screen != nil { if panicking && Screen != nil {
@ -656,7 +738,7 @@ func (g *Gui) onWorkerAux(f func()) {
} }
}() }()
f() f(task)
panicking = false panicking = false
} }
@ -722,43 +804,17 @@ func (g *Gui) MainLoop() error {
} }
} }
func (g *Gui) IncrementBusyCount() {
g.busyCountMutex.Lock()
defer g.busyCountMutex.Unlock()
g.busyCount++
}
func (g *Gui) DecrementBusyCount() {
g.busyCountMutex.Lock()
defer g.busyCountMutex.Unlock()
if g.busyCount == 0 {
panic("busyCount is already 0")
}
if g.busyCount == 1 {
// notify listeners that the program is idle
for _, listener := range g.idleListeners {
listener <- struct{}{}
}
}
g.busyCount--
}
func (g *Gui) processEvent() error { func (g *Gui) processEvent() error {
select { select {
case ev := <-g.gEvents: case ev := <-g.gEvents:
g.IncrementBusyCount() task := g.NewTask()
defer func() { g.DecrementBusyCount() }() defer func() { task.Done() }()
if err := g.handleEvent(&ev); err != nil { if err := g.handleEvent(&ev); err != nil {
return err return err
} }
case ev := <-g.userEvents: case ev := <-g.userEvents:
// user events increment busyCount ahead of time defer func() { ev.task.Done() }()
defer func() { g.DecrementBusyCount() }()
if err := ev.f(g); err != nil { if err := ev.f(g); err != nil {
return err return err
@ -785,7 +841,7 @@ func (g *Gui) processRemainingEvents() error {
} }
case ev := <-g.userEvents: case ev := <-g.userEvents:
err := ev.f(g) err := ev.f(g)
g.DecrementBusyCount() ev.task.Done()
if err != nil { if err != nil {
return err return err
} }

2
vendor/modules.txt vendored
View File

@ -172,7 +172,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem
github.com/jesseduffield/go-git/v5/utils/merkletrie/index github.com/jesseduffield/go-git/v5/utils/merkletrie/index
github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame 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/merkletrie/noder
# github.com/jesseduffield/gocui v0.3.1-0.20230708122437-f7e1c7c16828 # github.com/jesseduffield/gocui v0.3.1-0.20230709105400-44d9f78b4b52
## explicit; go 1.12 ## explicit; go 1.12
github.com/jesseduffield/gocui github.com/jesseduffield/gocui
# github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 # github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10