diff --git a/pkg/gui/container_logs.go b/pkg/gui/container_logs.go index b74fba9..d3362c9 100644 --- a/pkg/gui/container_logs.go +++ b/pkg/gui/container_logs.go @@ -18,25 +18,23 @@ import ( func (gui *Gui) renderContainerLogsToMain(container *commands.Container) tasks.TaskFunc { return gui.NewTickerTask(TickerTaskOpts{ - Func: func(stop, notifyStopped chan struct{}) { - gui.renderContainerLogsToMainAux(container, stop, notifyStopped) + Func: func(ctx context.Context, notifyStopped chan struct{}) { + gui.renderContainerLogsToMainAux(container, ctx, notifyStopped) }, Duration: time.Millisecond * 200, // TODO: see why this isn't working (when switching from Top tab to Logs tab in the services panel, the tops tab's content isn't removed) - Before: func(stop chan struct{}) { gui.clearMainView() }, + Before: func(ctx context.Context) { gui.clearMainView() }, Wrap: gui.Config.UserConfig.Gui.WrapMainPanel, Autoscroll: true, }) } -func (gui *Gui) renderContainerLogsToMainAux(container *commands.Container, stop, notifyStopped chan struct{}) { +func (gui *Gui) renderContainerLogsToMainAux(container *commands.Container, ctx context.Context, notifyStopped chan struct{}) { gui.clearMainView() defer func() { notifyStopped <- struct{}{} }() - ctx := stopIntoCtx(stop) - mainView := gui.Views.Main if err := gui.writeContainerLogs(container, ctx, mainView); err != nil { @@ -49,7 +47,7 @@ func (gui *Gui) renderContainerLogsToMainAux(container *commands.Container, stop defer ticker.Stop() for { select { - case <-stop: + case <-ctx.Done(): return case <-ticker.C: result, err := container.Inspect() diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go index 8c316ed..862fc4b 100644 --- a/pkg/gui/containers_panel.go +++ b/pkg/gui/containers_panel.go @@ -212,7 +212,7 @@ func (gui *Gui) containerConfigStr(container *commands.Container) string { func (gui *Gui) renderContainerStats(container *commands.Container) tasks.TaskFunc { return gui.NewTickerTask(TickerTaskOpts{ - Func: func(stop, notifyStopped chan struct{}) { + Func: func(ctx context.Context, notifyStopped chan struct{}) { contents, err := presentation.RenderStats(gui.Config.UserConfig, container, gui.Views.Main.Width()) if err != nil { _ = gui.createErrorPanel(err.Error()) @@ -221,26 +221,15 @@ func (gui *Gui) renderContainerStats(container *commands.Container) tasks.TaskFu gui.reRenderStringMain(contents) }, Duration: time.Second, - Before: func(stop chan struct{}) { gui.clearMainView() }, + Before: func(ctx context.Context) { gui.clearMainView() }, Wrap: false, // wrapping looks bad here so we're overriding the config value Autoscroll: false, }) } -// TODO: remove this and just use a context -func stopIntoCtx(stop chan struct{}) context.Context { - ctx, ctxCancel := context.WithCancel(context.Background()) - go func() { - <-stop - ctxCancel() - }() - return ctx -} - func (gui *Gui) renderContainerTop(container *commands.Container) tasks.TaskFunc { return gui.NewTickerTask(TickerTaskOpts{ - Func: func(stop, notifyStopped chan struct{}) { - ctx := stopIntoCtx(stop) + Func: func(ctx context.Context, notifyStopped chan struct{}) { contents, err := container.RenderTop(ctx) if err != nil { gui.RenderStringMain(err.Error()) @@ -249,7 +238,7 @@ func (gui *Gui) renderContainerTop(container *commands.Container) tasks.TaskFunc gui.reRenderStringMain(contents) }, Duration: time.Second, - Before: func(stop chan struct{}) { gui.clearMainView() }, + Before: func(ctx context.Context) { gui.clearMainView() }, Wrap: gui.Config.UserConfig.Gui.WrapMainPanel, Autoscroll: false, }) diff --git a/pkg/gui/main_panel.go b/pkg/gui/main_panel.go index a653068..004147b 100644 --- a/pkg/gui/main_panel.go +++ b/pkg/gui/main_panel.go @@ -76,13 +76,6 @@ func (gui *Gui) jumpToTopMain(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) onMainTabClick(tabIndex int) error { gui.Log.Warn(tabIndex) - viewName := gui.currentViewName() - - mainView := gui.Views.Main - if viewName == "main" && mainView.ParentView != nil { - viewName = mainView.ParentView.Name() - } - currentSidePanel, ok := gui.currentSidePanel() if !ok { diff --git a/pkg/gui/options_menu_panel.go b/pkg/gui/options_menu_panel.go index a7dca6d..38ba71a 100644 --- a/pkg/gui/options_menu_panel.go +++ b/pkg/gui/options_menu_panel.go @@ -41,10 +41,6 @@ func (gui *Gui) getBindings(v *gocui.View) []*Binding { } } - // } else if v.ParentView != nil && binding.ViewName == v.ParentView.Name() { - // // only add this if we don't have our own matching binding - // bindingsPanel = append(bindingsPanel, binding) - // append dummy element to have a separator between // panel and global keybindings bindingsPanel = append(bindingsPanel, &Binding{}) diff --git a/pkg/gui/panels/side_list_panel.go b/pkg/gui/panels/side_list_panel.go index 36b6fd3..e8fe80f 100644 --- a/pkg/gui/panels/side_list_panel.go +++ b/pkg/gui/panels/side_list_panel.go @@ -1,6 +1,7 @@ package panels import ( + "context" "fmt" "strings" @@ -74,7 +75,7 @@ type IGui interface { IgnoreStrings() []string Update(func() error) - QueueTask(f func(stop chan struct{})) error + QueueTask(f func(ctx context.Context)) error } func (self *SideListPanel[T]) HandleClick() error { diff --git a/pkg/gui/project_panel.go b/pkg/gui/project_panel.go index 8a24a82..bfc829f 100644 --- a/pkg/gui/project_panel.go +++ b/pkg/gui/project_panel.go @@ -2,6 +2,7 @@ package gui import ( "bytes" + "context" "path" "strings" @@ -115,7 +116,7 @@ func (gui *Gui) renderAllLogs(_project *commands.Project) tasks.TaskFunc { return gui.NewTask(TaskOpts{ Autoscroll: true, Wrap: gui.Config.UserConfig.Gui.WrapMainPanel, - Func: func(stop chan struct{}) { + Func: func(ctx context.Context) { gui.clearMainView() cmd := gui.OSCommand.RunCustomCommand( @@ -132,7 +133,7 @@ func (gui *Gui) renderAllLogs(_project *commands.Project) tasks.TaskFunc { _ = cmd.Start() go func() { - <-stop + <-ctx.Done() if err := gui.OSCommand.Kill(cmd); err != nil { gui.Log.Error(err) } diff --git a/pkg/gui/services_panel.go b/pkg/gui/services_panel.go index d7bf541..2c1caae 100644 --- a/pkg/gui/services_panel.go +++ b/pkg/gui/services_panel.go @@ -1,6 +1,7 @@ package gui import ( + "context" "fmt" "time" @@ -106,8 +107,7 @@ func (gui *Gui) renderServiceStats(service *commands.Service) tasks.TaskFunc { func (gui *Gui) renderServiceTop(service *commands.Service) tasks.TaskFunc { return gui.NewTickerTask(TickerTaskOpts{ - Func: func(stop, notifyStopped chan struct{}) { - ctx := stopIntoCtx(stop) + Func: func(ctx context.Context, notifyStopped chan struct{}) { contents, err := service.RenderTop(ctx) if err != nil { gui.RenderStringMain(err.Error()) @@ -116,7 +116,7 @@ func (gui *Gui) renderServiceTop(service *commands.Service) tasks.TaskFunc { gui.reRenderStringMain(contents) }, Duration: time.Second, - Before: func(stop chan struct{}) { gui.clearMainView() }, + Before: func(ctx context.Context) { gui.clearMainView() }, Wrap: gui.Config.UserConfig.Gui.WrapMainPanel, Autoscroll: false, }) diff --git a/pkg/gui/tasks_adapter.go b/pkg/gui/tasks_adapter.go index b282502..f85e472 100644 --- a/pkg/gui/tasks_adapter.go +++ b/pkg/gui/tasks_adapter.go @@ -1,12 +1,13 @@ package gui import ( + "context" "time" "github.com/jesseduffield/lazydocker/pkg/tasks" ) -func (gui *Gui) QueueTask(f func(stop chan struct{})) error { +func (gui *Gui) QueueTask(f func(ctx context.Context)) error { return gui.taskManager.NewTask(f) } @@ -19,13 +20,13 @@ type RenderStringTaskOpts struct { type TaskOpts struct { Autoscroll bool Wrap bool - Func func(stop chan struct{}) + Func func(ctx context.Context) } type TickerTaskOpts struct { Duration time.Duration - Before func(stop chan struct{}) - Func func(stop, notifyStopped chan struct{}) + Before func(ctx context.Context) + Func func(ctx context.Context, notifyStopped chan struct{}) Autoscroll bool Wrap bool } @@ -34,10 +35,7 @@ func (gui *Gui) NewRenderStringTask(opts RenderStringTaskOpts) tasks.TaskFunc { taskOpts := TaskOpts{ Autoscroll: opts.Autoscroll, Wrap: opts.Wrap, - Func: func(stop chan struct{}) { - // GetStrContent may be a slow function, so we clear the main view first - // so that we're not seeing the previous tab's content appear. - gui.clearMainView() + Func: func(ctx context.Context) { gui.RenderStringMain(opts.GetStrContent()) }, } @@ -55,12 +53,12 @@ func (gui *Gui) NewSimpleRenderStringTask(getContent func() string) tasks.TaskFu } func (gui *Gui) NewTask(opts TaskOpts) tasks.TaskFunc { - return func(stop chan struct{}) { + return func(ctx context.Context) { mainView := gui.Views.Main mainView.Autoscroll = opts.Autoscroll mainView.Wrap = opts.Wrap - opts.Func(stop) + opts.Func(ctx) } } @@ -70,25 +68,25 @@ func (gui *Gui) NewTask(opts TaskOpts) tasks.TaskFunc { func (gui *Gui) NewTickerTask(opts TickerTaskOpts) tasks.TaskFunc { notifyStopped := make(chan struct{}, 10) - task := func(stop chan struct{}) { + task := func(ctx context.Context) { if opts.Before != nil { - opts.Before(stop) + opts.Before(ctx) } tickChan := time.NewTicker(opts.Duration) defer tickChan.Stop() // calling f first so that we're not waiting for the first tick - opts.Func(stop, notifyStopped) + opts.Func(ctx, notifyStopped) for { select { case <-notifyStopped: gui.Log.Info("exiting ticker task due to notifyStopped channel") return - case <-stop: + case <-ctx.Done(): gui.Log.Info("exiting ticker task due to stopped channel") return case <-tickChan.C: gui.Log.Info("running ticker task again") - opts.Func(stop, notifyStopped) + opts.Func(ctx, notifyStopped) } } } diff --git a/pkg/tasks/tasks.go b/pkg/tasks/tasks.go index 01bd689..e6b9d4a 100644 --- a/pkg/tasks/tasks.go +++ b/pkg/tasks/tasks.go @@ -1,6 +1,7 @@ package tasks import ( + "context" "fmt" "time" @@ -19,15 +20,16 @@ type TaskManager struct { } type Task struct { - stop chan struct{} + ctx context.Context + cancel context.CancelFunc stopped bool stopMutex deadlock.Mutex notifyStopped chan struct{} Log *logrus.Entry - f func(chan struct{}) + f func(ctx context.Context) } -type TaskFunc func(stop chan struct{}) +type TaskFunc func(ctx context.Context) func NewTaskManager(log *logrus.Entry, translationSet *i18n.TranslationSet) *TaskManager { return &TaskManager{Log: log, Tr: translationSet} @@ -54,7 +56,7 @@ func (t *TaskManager) Close() { } } -func (t *TaskManager) NewTask(f func(stop chan struct{})) error { +func (t *TaskManager) NewTask(f func(ctx context.Context)) error { go func() { t.taskIDMutex.Lock() t.newTaskId++ @@ -67,7 +69,7 @@ func (t *TaskManager) NewTask(f func(stop chan struct{})) error { return } - stop := make(chan struct{}, 1) // we don't want to block on this in case the task already returned + ctx, cancel := context.WithCancel(context.Background()) notifyStopped := make(chan struct{}) if t.currentTask != nil { @@ -77,14 +79,15 @@ func (t *TaskManager) NewTask(f func(stop chan struct{})) error { } t.currentTask = &Task{ - stop: stop, + ctx: ctx, + cancel: cancel, notifyStopped: notifyStopped, Log: t.Log, f: f, } go func() { - f(stop) + f(ctx) t.Log.Info("returned from function, closing notifyStopped") close(notifyStopped) }() @@ -99,7 +102,8 @@ func (t *Task) Stop() { if t.stopped { return } - close(t.stop) + + t.cancel() t.Log.Info("closed stop channel, waiting for notifyStopped message") <-t.notifyStopped t.Log.Info("received notifystopped message") @@ -109,28 +113,28 @@ func (t *Task) Stop() { // NewTickerTask is a convenience function for making a new task that repeats some action once per e.g. second // the before function gets called after the lock is obtained, but before the ticker starts. // if you handle a message on the stop channel in f() you need to send a message on the notifyStopped channel because returning is not sufficient. Here, unlike in a regular task, simply returning means we're now going to wait till the next tick to run again. -func (t *TaskManager) NewTickerTask(duration time.Duration, before func(stop chan struct{}), f func(stop, notifyStopped chan struct{})) error { +func (t *TaskManager) NewTickerTask(duration time.Duration, before func(ctx context.Context), f func(ctx context.Context, notifyStopped chan struct{})) error { notifyStopped := make(chan struct{}, 10) - return t.NewTask(func(stop chan struct{}) { + return t.NewTask(func(ctx context.Context) { if before != nil { - before(stop) + before(ctx) } tickChan := time.NewTicker(duration) defer tickChan.Stop() // calling f first so that we're not waiting for the first tick - f(stop, notifyStopped) + f(ctx, notifyStopped) for { select { case <-notifyStopped: t.Log.Info("exiting ticker task due to notifyStopped channel") return - case <-stop: + case <-ctx.Done(): t.Log.Info("exiting ticker task due to stopped cahnnel") return case <-tickChan.C: t.Log.Info("running ticker task again") - f(stop, notifyStopped) + f(ctx, notifyStopped) } } })