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

Use an interface for tasks instead of a concrete struct

By using an interface for tasks we can use a fake implementation in tests with extra methods
This commit is contained in:
Jesse Duffield
2023-07-09 21:09:52 +10:00
parent 8964cedf27
commit 6b9390409e
45 changed files with 333 additions and 222 deletions

View File

@ -177,87 +177,6 @@ type Gui struct {
taskManager *TaskManager
}
type TaskManager struct {
// Tracks whether the program is busy (i.e. either something is happening on
// the main goroutine or a worker goroutine). Used by integration tests
// to wait until the program is idle before progressing.
idleListeners []chan struct{}
tasks map[int]*Task
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.
func NewGui(mode OutputMode, supportOverlaps bool, playRecording bool, headless bool, runeReplacements map[rune]string) (*Gui, error) {
g := &Gui{}
@ -314,7 +233,7 @@ func NewGui(mode OutputMode, supportOverlaps bool, playRecording bool, headless
return g, nil
}
func (g *Gui) NewTask() *Task {
func (g *Gui) NewTask() *TaskImpl {
return g.taskManager.NewTask()
}
@ -322,7 +241,7 @@ func (g *Gui) NewTask() *Task {
// integration tests which can wait for the program to be idle before taking
// the next step in the test.
func (g *Gui) AddIdleListener(c chan struct{}) {
g.taskManager.AddIdleListener(c)
g.taskManager.addIdleListener(c)
}
// Close finalizes the library. It should be called after a successful
@ -689,7 +608,7 @@ func getKey(key interface{}) (Key, rune, error) {
// userEvent represents an event triggered by the user.
type userEvent struct {
f func(*Gui) error
task *Task
task Task
}
// Update executes the passed function. This method can be called safely from a
@ -712,7 +631,7 @@ func (g *Gui) UpdateAsync(f func(*Gui) error) {
g.updateAsyncAux(f, task)
}
func (g *Gui) updateAsyncAux(f func(*Gui) error, task *Task) {
func (g *Gui) updateAsyncAux(f func(*Gui) error, task Task) {
g.userEvents <- userEvent{f: f, task: task}
}
@ -722,7 +641,7 @@ func (g *Gui) updateAsyncAux(f func(*Gui) error, task *Task) {
// 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
// (i.e. when you wouldn't want a loader to be shown to the user)
func (g *Gui) OnWorker(f func(*Task)) {
func (g *Gui) OnWorker(f func(Task)) {
task := g.NewTask()
go func() {
g.onWorkerAux(f, task)
@ -730,7 +649,7 @@ func (g *Gui) OnWorker(f func(*Task)) {
}()
}
func (g *Gui) onWorkerAux(f func(*Task), task *Task) {
func (g *Gui) onWorkerAux(f func(Task), task Task) {
panicking := true
defer func() {
if panicking && Screen != nil {

94
vendor/github.com/jesseduffield/gocui/task.go generated vendored Normal file
View File

@ -0,0 +1,94 @@
package gocui
// A task represents the fact that the program is busy doing something, which
// is useful for integration tests which only want to proceed when the program
// is idle.
type Task interface {
Done()
Pause()
Continue()
// not exporting because we don't need to
isBusy() bool
}
type TaskImpl struct {
id int
busy bool
onDone func()
withMutex func(func())
}
func (self *TaskImpl) Done() {
self.onDone()
}
func (self *TaskImpl) Pause() {
self.withMutex(func() {
self.busy = false
})
}
func (self *TaskImpl) Continue() {
self.withMutex(func() {
self.busy = true
})
}
func (self *TaskImpl) isBusy() bool {
return self.busy
}
type TaskStatus int
const (
TaskStatusBusy TaskStatus = iota
TaskStatusPaused
TaskStatusDone
)
type FakeTask struct {
status TaskStatus
}
func NewFakeTask() *FakeTask {
return &FakeTask{
status: TaskStatusBusy,
}
}
func (self *FakeTask) Done() {
self.status = TaskStatusDone
}
func (self *FakeTask) Pause() {
self.status = TaskStatusPaused
}
func (self *FakeTask) Continue() {
self.status = TaskStatusBusy
}
func (self *FakeTask) isBusy() bool {
return self.status == TaskStatusBusy
}
func (self *FakeTask) Status() TaskStatus {
return self.status
}
func (self *FakeTask) FormatStatus() string {
return formatTaskStatus(self.status)
}
func formatTaskStatus(status TaskStatus) string {
switch status {
case TaskStatusBusy:
return "busy"
case TaskStatusPaused:
return "paused"
case TaskStatusDone:
return "done"
}
return "unknown"
}

67
vendor/github.com/jesseduffield/gocui/task_manager.go generated vendored Normal file
View File

@ -0,0 +1,67 @@
package gocui
import "sync"
// Tracks whether the program is busy (i.e. either something is happening on
// the main goroutine or a worker goroutine). Used by integration tests
// to wait until the program is idle before progressing.
type TaskManager struct {
// each of these listeners will be notified when the program goes from busy to idle
idleListeners []chan struct{}
tasks map[int]Task
// auto-incrementing id for new tasks
nextId int
mutex sync.Mutex
}
func newTaskManager() *TaskManager {
return &TaskManager{
tasks: make(map[int]Task),
idleListeners: []chan struct{}{},
}
}
func (self *TaskManager) NewTask() *TaskImpl {
self.mutex.Lock()
defer self.mutex.Unlock()
self.nextId++
taskId := self.nextId
onDone := func() { self.delete(taskId) }
task := &TaskImpl{id: taskId, busy: true, onDone: onDone, withMutex: self.withMutex}
self.tasks[taskId] = task
return task
}
func (self *TaskManager) addIdleListener(c chan struct{}) {
self.idleListeners = append(self.idleListeners, c)
}
func (self *TaskManager) withMutex(f func()) {
self.mutex.Lock()
defer self.mutex.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{}{}
}
}
func (self *TaskManager) delete(taskId int) {
self.withMutex(func() {
delete(self.tasks, taskId)
})
}