better way of rendering to the main view

This commit is contained in:
Jesse Duffield 2022-11-10 19:14:12 +11:00
parent 5ffbeae3e8
commit 52040d1d99
17 changed files with 266 additions and 167 deletions

View file

@ -105,7 +105,7 @@ func (c *Container) Attach() (*exec.Cmd, error) {
} }
// Top returns process information // Top returns process information
func (c *Container) Top() (container.ContainerTopOKBody, error) { func (c *Container) Top(ctx context.Context) (container.ContainerTopOKBody, error) {
detail, err := c.Inspect() detail, err := c.Inspect()
if err != nil { if err != nil {
return container.ContainerTopOKBody{}, err return container.ContainerTopOKBody{}, err
@ -116,7 +116,7 @@ func (c *Container) Top() (container.ContainerTopOKBody, error) {
return container.ContainerTopOKBody{}, errors.New("container is not running") return container.ContainerTopOKBody{}, errors.New("container is not running")
} }
return c.Client.ContainerTop(context.Background(), c.ID, []string{}) return c.Client.ContainerTop(ctx, c.ID, []string{})
} }
// PruneContainers prunes containers // PruneContainers prunes containers
@ -131,8 +131,8 @@ func (c *Container) Inspect() (dockerTypes.ContainerJSON, error) {
} }
// RenderTop returns details about the container // RenderTop returns details about the container
func (c *Container) RenderTop() (string, error) { func (c *Container) RenderTop(ctx context.Context) (string, error) {
result, err := c.Top() result, err := c.Top(ctx)
if err != nil { if err != nil {
return "", err return "", err
} }

View file

@ -1,6 +1,7 @@
package commands package commands
import ( import (
"context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -65,6 +66,15 @@ func (c *OSCommand) RunCommandWithOutput(command string) (string, error) {
return output, err return output, err
} }
// RunCommandWithOutput wrapper around commands returning their output and error
func (c *OSCommand) RunCommandWithOutputContext(ctx context.Context, command string) (string, error) {
cmd := c.ExecutableFromStringContext(ctx, command)
before := time.Now()
output, err := sanitisedCommandOutput(cmd.Output())
c.Log.Warn(fmt.Sprintf("'%s': %s", command, time.Since(before)))
return output, err
}
// RunExecutableWithOutput runs an executable file and returns its output // RunExecutableWithOutput runs an executable file and returns its output
func (c *OSCommand) RunExecutableWithOutput(cmd *exec.Cmd) (string, error) { func (c *OSCommand) RunExecutableWithOutput(cmd *exec.Cmd) (string, error) {
return sanitisedCommandOutput(cmd.CombinedOutput()) return sanitisedCommandOutput(cmd.CombinedOutput())
@ -79,10 +89,15 @@ func (c *OSCommand) RunExecutable(cmd *exec.Cmd) error {
// ExecutableFromString takes a string like `docker ps -a` and returns an executable command for it // ExecutableFromString takes a string like `docker ps -a` and returns an executable command for it
func (c *OSCommand) ExecutableFromString(commandStr string) *exec.Cmd { func (c *OSCommand) ExecutableFromString(commandStr string) *exec.Cmd {
splitCmd := str.ToArgv(commandStr) splitCmd := str.ToArgv(commandStr)
// c.Log.Info(splitCmd)
return c.command(splitCmd[0], splitCmd[1:]...) return c.command(splitCmd[0], splitCmd[1:]...)
} }
// Same as ExecutableFromString but cancellable via a context
func (c *OSCommand) ExecutableFromStringContext(ctx context.Context, commandStr string) *exec.Cmd {
splitCmd := str.ToArgv(commandStr)
return exec.CommandContext(ctx, splitCmd[0], splitCmd[1:]...)
}
// RunCommand runs a command and just returns the error // RunCommand runs a command and just returns the error
func (c *OSCommand) RunCommand(command string) error { func (c *OSCommand) RunCommand(command string) error {
_, err := c.RunCommandWithOutput(command) _, err := c.RunCommandWithOutput(command)

View file

@ -1,10 +1,9 @@
package commands package commands
import ( import (
"context"
"os/exec" "os/exec"
"github.com/docker/docker/api/types/container"
dockerTypes "github.com/docker/docker/api/types" dockerTypes "github.com/docker/docker/api/types"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -58,11 +57,6 @@ func (s *Service) Attach() (*exec.Cmd, error) {
return s.Container.Attach() return s.Container.Attach()
} }
// Top returns process information
func (s *Service) Top() (container.ContainerTopOKBody, error) {
return s.Container.Top()
}
// ViewLogs attaches to a subprocess viewing the service's logs // ViewLogs attaches to a subprocess viewing the service's logs
func (s *Service) ViewLogs() (*exec.Cmd, error) { func (s *Service) ViewLogs() (*exec.Cmd, error) {
templateString := s.OSCommand.Config.UserConfig.CommandTemplates.ViewServiceLogs templateString := s.OSCommand.Config.UserConfig.CommandTemplates.ViewServiceLogs
@ -78,12 +72,12 @@ func (s *Service) ViewLogs() (*exec.Cmd, error) {
} }
// RenderTop renders the process list of the service // RenderTop renders the process list of the service
func (s *Service) RenderTop() (string, error) { func (s *Service) RenderTop(ctx context.Context) (string, error) {
templateString := s.OSCommand.Config.UserConfig.CommandTemplates.ServiceTop templateString := s.OSCommand.Config.UserConfig.CommandTemplates.ServiceTop
command := utils.ApplyTemplate( command := utils.ApplyTemplate(
templateString, templateString,
s.DockerCommand.NewCommandObject(CommandObject{Service: s}), s.DockerCommand.NewCommandObject(CommandObject{Service: s}),
) )
return s.OSCommand.RunCommandWithOutput(command) return s.OSCommand.RunCommandWithOutputContext(ctx, command)
} }

View file

@ -12,16 +12,19 @@ import (
"github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/stdcopy"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/tasks"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
) )
func (gui *Gui) renderContainerLogsToMain(container *commands.Container) error { func (gui *Gui) renderContainerLogsToMain(container *commands.Container) tasks.TaskFunc {
mainView := gui.Views.Main return gui.NewTickerTask(TickerTaskOpts{
mainView.Autoscroll = true Func: func(stop, notifyStopped chan struct{}) {
mainView.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel gui.renderContainerLogsToMainAux(container, stop, notifyStopped)
},
return gui.T.NewTickerTask(time.Millisecond*200, nil, func(stop, notifyStopped chan struct{}) { Duration: time.Millisecond * 200,
gui.renderContainerLogsToMainAux(container, stop, notifyStopped) Before: nil,
Wrap: gui.Config.UserConfig.Gui.WrapMainPanel,
Autoscroll: true,
}) })
} }

View file

@ -1,6 +1,7 @@
package gui package gui
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings" "strings"
@ -14,6 +15,7 @@ import (
"github.com/jesseduffield/lazydocker/pkg/gui/panels" "github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/gui/presentation" "github.com/jesseduffield/lazydocker/pkg/gui/presentation"
"github.com/jesseduffield/lazydocker/pkg/gui/types" "github.com/jesseduffield/lazydocker/pkg/gui/types"
"github.com/jesseduffield/lazydocker/pkg/tasks"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
) )
@ -119,28 +121,10 @@ func sortContainers(a *commands.Container, b *commands.Container, legacySort boo
return containerStates[a.Container.State] < containerStates[b.Container.State] return containerStates[a.Container.State] < containerStates[b.Container.State]
} }
type MainRenderTask struct { func (gui *Gui) renderContainerEnv(container *commands.Container) tasks.TaskFunc {
Wrap bool return gui.NewSimpleRenderStringTask(func() string { return gui.containerEnv(container) })
Autoscroll bool
StrContent string
// TicketTask
} }
func (gui *Gui) renderContainerEnv(container *commands.Container) error {
mainView := gui.Views.Main
mainView.Autoscroll = false
mainView.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel
return gui.T.NewTask(func(stop chan struct{}) {
_ = gui.RenderStringMain(gui.containerEnv(container))
})
}
// I want a struct that gets returned explaining what needs to happen. So for example,
// autoscroll: true, wrap: true, content: string
func (gui *Gui) containerEnv(container *commands.Container) string { func (gui *Gui) containerEnv(container *commands.Container) string {
if !container.DetailsLoaded() { if !container.DetailsLoaded() {
return gui.Tr.WaitingForContainerInfo return gui.Tr.WaitingForContainerInfo
@ -172,14 +156,8 @@ func (gui *Gui) containerEnv(container *commands.Container) string {
return output return output
} }
func (gui *Gui) renderContainerConfig(container *commands.Container) error { func (gui *Gui) renderContainerConfig(container *commands.Container) tasks.TaskFunc {
mainView := gui.Views.Main return gui.NewSimpleRenderStringTask(func() string { return gui.containerConfigStr(container) })
mainView.Autoscroll = false
mainView.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel
return gui.T.NewTask(func(stop chan struct{}) {
_ = gui.RenderStringMain(gui.containerConfigStr(container))
})
} }
func (gui *Gui) containerConfigStr(container *commands.Container) string { func (gui *Gui) containerConfigStr(container *commands.Container) string {
@ -232,35 +210,48 @@ func (gui *Gui) containerConfigStr(container *commands.Container) string {
return output return output
} }
func (gui *Gui) renderContainerStats(container *commands.Container) error { func (gui *Gui) renderContainerStats(container *commands.Container) tasks.TaskFunc {
mainView := gui.Views.Main return gui.NewTickerTask(TickerTaskOpts{
mainView.Autoscroll = false Func: func(stop, notifyStopped chan struct{}) {
mainView.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel contents, err := presentation.RenderStats(gui.Config.UserConfig, container, gui.Views.Main.Width())
if err != nil {
_ = gui.createErrorPanel(err.Error())
}
return gui.T.NewTickerTask(time.Second, func(stop chan struct{}) { gui.clearMainView() }, func(stop, notifyStopped chan struct{}) { gui.reRenderStringMain(contents)
width, _ := mainView.Size() },
Duration: time.Second,
contents, err := presentation.RenderStats(gui.Config.UserConfig, container, width) Before: func(stop chan struct{}) { gui.clearMainView() },
if err != nil { Wrap: false, // wrapping looks bad here so we're overriding the config value
_ = gui.createErrorPanel(err.Error()) Autoscroll: false,
}
gui.reRenderStringMain(contents)
}) })
} }
func (gui *Gui) renderContainerTop(container *commands.Container) error { // TODO: remove this and just use a context
mainView := gui.Views.Main func stopIntoCtx(stop chan struct{}) context.Context {
mainView.Autoscroll = false ctx, ctxCancel := context.WithCancel(context.Background())
mainView.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel go func() {
<-stop
ctxCancel()
}()
return ctx
}
return gui.T.NewTickerTask(time.Second, func(stop chan struct{}) { gui.clearMainView() }, func(stop, notifyStopped chan struct{}) { func (gui *Gui) renderContainerTop(container *commands.Container) tasks.TaskFunc {
contents, err := container.RenderTop() return gui.NewTickerTask(TickerTaskOpts{
if err != nil { Func: func(stop, notifyStopped chan struct{}) {
gui.reRenderStringMain(err.Error()) ctx := stopIntoCtx(stop)
} contents, err := container.RenderTop(ctx)
if err != nil {
gui.RenderStringMain(err.Error())
}
gui.reRenderStringMain(contents) gui.reRenderStringMain(contents)
},
Duration: time.Second,
Before: func(stop chan struct{}) { gui.clearMainView() },
Wrap: gui.Config.UserConfig.Gui.WrapMainPanel,
Autoscroll: false,
}) })
} }

View file

@ -36,7 +36,7 @@ type Gui struct {
Config *config.AppConfig Config *config.AppConfig
Tr *i18n.TranslationSet Tr *i18n.TranslationSet
statusManager *statusManager statusManager *statusManager
T *tasks.TaskManager taskManager *tasks.TaskManager
ErrorChan chan error ErrorChan chan error
Views Views Views Views
@ -135,7 +135,7 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand
Config: config, Config: config,
Tr: tr, Tr: tr,
statusManager: &statusManager{}, statusManager: &statusManager{},
T: tasks.NewTaskManager(log, tr), taskManager: tasks.NewTaskManager(log, tr),
ErrorChan: errorChan, ErrorChan: errorChan,
} }
@ -173,7 +173,7 @@ func (gui *Gui) goEvery(interval time.Duration, function func() error) {
// Run setup the gui with keybindings and start the mainloop // Run setup the gui with keybindings and start the mainloop
func (gui *Gui) Run() error { func (gui *Gui) Run() error {
// closing our task manager which in turn closes the current task if there is any, so we aren't leaving processes lying around after closing lazydocker // closing our task manager which in turn closes the current task if there is any, so we aren't leaving processes lying around after closing lazydocker
defer gui.T.Close() defer gui.taskManager.Close()
g, err := gocui.NewGui(gocui.OutputTrue, OverlappingEdges, gocui.NORMAL, false, map[rune]string{}) g, err := gocui.NewGui(gocui.OutputTrue, OverlappingEdges, gocui.NORMAL, false, map[rune]string{})
if err != nil { if err != nil {

View file

@ -13,6 +13,7 @@ import (
"github.com/jesseduffield/lazydocker/pkg/gui/panels" "github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/gui/presentation" "github.com/jesseduffield/lazydocker/pkg/gui/presentation"
"github.com/jesseduffield/lazydocker/pkg/gui/types" "github.com/jesseduffield/lazydocker/pkg/gui/types"
"github.com/jesseduffield/lazydocker/pkg/tasks"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
) )
@ -25,11 +26,9 @@ func (gui *Gui) getImagesPanel() *panels.SideListPanel[*commands.Image] {
GetMainTabs: func() []panels.MainTab[*commands.Image] { GetMainTabs: func() []panels.MainTab[*commands.Image] {
return []panels.MainTab[*commands.Image]{ return []panels.MainTab[*commands.Image]{
{ {
Key: "config", Key: "config",
Title: gui.Tr.ConfigTitle, Title: gui.Tr.ConfigTitle,
Render: func(image *commands.Image) error { Render: gui.renderImageConfigTask,
return gui.renderImageConfig(image)
},
}, },
} }
}, },
@ -66,13 +65,11 @@ func (gui *Gui) getImagesPanel() *panels.SideListPanel[*commands.Image] {
} }
} }
func (gui *Gui) renderImageConfig(image *commands.Image) error { func (gui *Gui) renderImageConfigTask(image *commands.Image) tasks.TaskFunc {
return gui.T.NewTask(func(stop chan struct{}) { return gui.NewRenderStringTask(RenderStringTaskOpts{
mainView := gui.Views.Main GetStrContent: func() string { return gui.imageConfigStr(image) },
mainView.Autoscroll = false Autoscroll: false,
mainView.Wrap = false // don't care what your config is this page is ugly without wrapping Wrap: false, // don't care what your config is this page is ugly without wrapping
_ = gui.RenderStringMain(gui.imageConfigStr(image))
}) })
} }

View file

@ -1,6 +1,9 @@
package panels package panels
import "github.com/samber/lo" import (
"github.com/jesseduffield/lazydocker/pkg/tasks"
"github.com/samber/lo"
)
// A 'context' generally corresponds to an item and the tab in the main panel that we're // A 'context' generally corresponds to an item and the tab in the main panel that we're
// displaying. So if we switch to a new item, or change the tab in the panel panel // displaying. So if we switch to a new item, or change the tab in the panel panel
@ -24,7 +27,7 @@ type MainTab[T any] struct {
// title of the tab, rendered in the main view // title of the tab, rendered in the main view
Title string Title string
// function to render the content of the tab // function to render the content of the tab
Render func(item T) error Render func(item T) tasks.TaskFunc
} }
func (self *ContextState[T]) GetMainTabTitles() []string { func (self *ContextState[T]) GetMainTabTitles() []string {

View file

@ -63,7 +63,7 @@ var _ ISideListPanel = &SideListPanel[int]{}
type IGui interface { type IGui interface {
HandleClick(v *gocui.View, itemCount int, selectedLine *int, handleSelect func() error) error HandleClick(v *gocui.View, itemCount int, selectedLine *int, handleSelect func() error) error
RenderStringMain(message string) error RenderStringMain(message string)
FocusY(selectedLine int, itemCount int, view *gocui.View) FocusY(selectedLine int, itemCount int, view *gocui.View)
ShouldRefresh(contextKey string) bool ShouldRefresh(contextKey string) bool
GetMainView() *gocui.View GetMainView() *gocui.View
@ -72,6 +72,8 @@ type IGui interface {
FilterString(view *gocui.View) string FilterString(view *gocui.View) string
IgnoreStrings() []string IgnoreStrings() []string
Update(func() error) Update(func() error)
QueueTask(f func(stop chan struct{})) error
} }
func (self *SideListPanel[T]) HandleClick() error { func (self *SideListPanel[T]) HandleClick() error {
@ -105,7 +107,8 @@ func (self *SideListPanel[T]) HandleSelect() error {
} }
if self.NoItemsMessage != "" { if self.NoItemsMessage != "" {
return self.Gui.RenderStringMain(self.NoItemsMessage) // TODO: use task for this
self.Gui.RenderStringMain(self.NoItemsMessage)
} }
return nil return nil
@ -130,7 +133,9 @@ func (self *SideListPanel[T]) renderContext(item T) error {
mainView.Tabs = self.ContextState.GetMainTabTitles() mainView.Tabs = self.ContextState.GetMainTabTitles()
mainView.TabIndex = self.ContextState.mainTabIdx mainView.TabIndex = self.ContextState.mainTabIdx
return self.ContextState.GetCurrentMainTab().Render(item) task := self.ContextState.GetCurrentMainTab().Render(item)
return self.Gui.QueueTask(task)
} }
func (self *SideListPanel[T]) GetSelectedItem() (T, error) { func (self *SideListPanel[T]) GetSelectedItem() (T, error) {

View file

@ -14,18 +14,20 @@ import (
) )
func GetContainerDisplayStrings(container *commands.Container) []string { func GetContainerDisplayStrings(container *commands.Container) []string {
image := strings.TrimPrefix(container.Container.Image, "sha256:")
return []string{ return []string{
getContainerDisplayStatus(container), getContainerDisplayStatus(container),
getContainerDisplaySubstatus(container), getContainerDisplaySubstatus(container),
container.Name, container.Name,
getDisplayCPUPerc(container), getDisplayCPUPerc(container),
utils.ColoredString(image, color.FgMagenta),
utils.ColoredString(displayPorts(container), color.FgYellow), utils.ColoredString(displayPorts(container), color.FgYellow),
utils.ColoredString(displayContainerImage(container), color.FgMagenta),
} }
} }
func displayContainerImage(container *commands.Container) string {
return strings.TrimPrefix(container.Container.Image, "sha256:")
}
func displayPorts(c *commands.Container) string { func displayPorts(c *commands.Container) string {
portStrings := lo.Map(c.Container.Ports, func(port dockerTypes.Port, _ int) string { portStrings := lo.Map(c.Container.Ports, func(port dockerTypes.Port, _ int) string {
if port.PublicPort == 0 { if port.PublicPort == 0 {

View file

@ -14,6 +14,7 @@ func GetServiceDisplayStrings(service *commands.Service) []string {
service.Name, service.Name,
"", "",
"", "",
"",
} }
} }
@ -24,5 +25,6 @@ func GetServiceDisplayStrings(service *commands.Service) []string {
service.Name, service.Name,
getDisplayCPUPerc(container), getDisplayCPUPerc(container),
utils.ColoredString(displayPorts(container), color.FgYellow), utils.ColoredString(displayPorts(container), color.FgYellow),
utils.ColoredString(displayContainerImage(container), color.FgMagenta),
} }
} }

View file

@ -10,6 +10,7 @@ import (
"github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/gui/panels" "github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/gui/presentation" "github.com/jesseduffield/lazydocker/pkg/gui/presentation"
"github.com/jesseduffield/lazydocker/pkg/tasks"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/jesseduffield/yaml" "github.com/jesseduffield/yaml"
) )
@ -89,14 +90,8 @@ func (gui *Gui) getProjectName() string {
return projectName return projectName
} }
func (gui *Gui) renderCredits(_project *commands.Project) error { func (gui *Gui) renderCredits(_project *commands.Project) tasks.TaskFunc {
return gui.T.NewTask(func(stop chan struct{}) { return gui.NewSimpleRenderStringTask(func() string { return gui.creditsStr() })
mainView := gui.Views.Main
mainView.Autoscroll = false
mainView.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel
_ = gui.RenderStringMain(gui.creditsStr())
})
} }
func (gui *Gui) creditsStr() string { func (gui *Gui) creditsStr() string {
@ -116,47 +111,40 @@ func (gui *Gui) creditsStr() string {
}, "\n\n") }, "\n\n")
} }
func (gui *Gui) renderAllLogs(_project *commands.Project) error { func (gui *Gui) renderAllLogs(_project *commands.Project) tasks.TaskFunc {
return gui.T.NewTask(func(stop chan struct{}) { return gui.NewTask(TaskOpts{
mainView := gui.Views.Main Autoscroll: true,
mainView.Autoscroll = true Wrap: gui.Config.UserConfig.Gui.WrapMainPanel,
mainView.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel Func: func(stop chan struct{}) {
gui.clearMainView()
gui.clearMainView() cmd := gui.OSCommand.RunCustomCommand(
utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.AllLogs,
gui.DockerCommand.NewCommandObject(commands.CommandObject{}),
),
)
cmd := gui.OSCommand.RunCustomCommand( cmd.Stdout = gui.Views.Main
utils.ApplyTemplate( cmd.Stderr = gui.Views.Main
gui.Config.UserConfig.CommandTemplates.AllLogs,
gui.DockerCommand.NewCommandObject(commands.CommandObject{}),
),
)
cmd.Stdout = mainView gui.OSCommand.PrepareForChildren(cmd)
cmd.Stderr = mainView _ = cmd.Start()
gui.OSCommand.PrepareForChildren(cmd) go func() {
_ = cmd.Start() <-stop
if err := gui.OSCommand.Kill(cmd); err != nil {
gui.Log.Error(err)
}
}()
go func() { _ = cmd.Wait()
<-stop },
if err := gui.OSCommand.Kill(cmd); err != nil {
gui.Log.Error(err)
}
}()
_ = cmd.Wait()
}) })
} }
func (gui *Gui) renderDockerComposeConfig(_project *commands.Project) error { func (gui *Gui) renderDockerComposeConfig(_project *commands.Project) tasks.TaskFunc {
return gui.T.NewTask(func(stop chan struct{}) { return gui.NewSimpleRenderStringTask(func() string { return gui.DockerCommand.DockerComposeConfig() })
mainView := gui.Views.Main
mainView.Autoscroll = false
mainView.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel
config := gui.DockerCommand.DockerComposeConfig()
_ = gui.RenderStringMain(config)
})
} }
func (gui *Gui) handleOpenConfig(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleOpenConfig(g *gocui.Gui, v *gocui.View) error {

View file

@ -11,6 +11,7 @@ import (
"github.com/jesseduffield/lazydocker/pkg/gui/panels" "github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/gui/presentation" "github.com/jesseduffield/lazydocker/pkg/gui/presentation"
"github.com/jesseduffield/lazydocker/pkg/gui/types" "github.com/jesseduffield/lazydocker/pkg/gui/types"
"github.com/jesseduffield/lazydocker/pkg/tasks"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
) )
@ -79,50 +80,51 @@ func (gui *Gui) getServicesPanel() *panels.SideListPanel[*commands.Service] {
} }
} }
func (gui *Gui) renderServiceContainerConfig(service *commands.Service) error { func (gui *Gui) renderServiceContainerConfig(service *commands.Service) tasks.TaskFunc {
if service.Container == nil { if service.Container == nil {
return gui.RenderStringMain(gui.Tr.NoContainer) return gui.NewSimpleRenderStringTask(func() string { return gui.Tr.NoContainer })
} }
return gui.renderContainerConfig(service.Container) return gui.renderContainerConfig(service.Container)
} }
func (gui *Gui) renderServiceContainerEnv(service *commands.Service) error { func (gui *Gui) renderServiceContainerEnv(service *commands.Service) tasks.TaskFunc {
if service.Container == nil { if service.Container == nil {
return gui.RenderStringMain(gui.Tr.NoContainer) return gui.NewSimpleRenderStringTask(func() string { return gui.Tr.NoContainer })
} }
return gui.renderContainerEnv(service.Container) return gui.renderContainerEnv(service.Container)
} }
func (gui *Gui) renderServiceStats(service *commands.Service) error { func (gui *Gui) renderServiceStats(service *commands.Service) tasks.TaskFunc {
if service.Container == nil { if service.Container == nil {
return nil return gui.NewSimpleRenderStringTask(func() string { return gui.Tr.NoContainer })
} }
return gui.renderContainerStats(service.Container) return gui.renderContainerStats(service.Container)
} }
func (gui *Gui) renderServiceTop(service *commands.Service) error { func (gui *Gui) renderServiceTop(service *commands.Service) tasks.TaskFunc {
mainView := gui.Views.Main return gui.NewTickerTask(TickerTaskOpts{
mainView.Autoscroll = false Func: func(stop, notifyStopped chan struct{}) {
mainView.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel ctx := stopIntoCtx(stop)
contents, err := service.RenderTop(ctx)
if err != nil {
gui.RenderStringMain(err.Error())
}
return gui.T.NewTickerTask(time.Second, func(stop chan struct{}) { gui.clearMainView() }, func(stop, notifyStopped chan struct{}) { gui.reRenderStringMain(contents)
contents, err := service.RenderTop() },
if err != nil { Duration: time.Second,
gui.reRenderStringMain(err.Error()) Before: func(stop chan struct{}) { gui.clearMainView() },
} Wrap: gui.Config.UserConfig.Gui.WrapMainPanel,
Autoscroll: false,
gui.reRenderStringMain(contents)
}) })
} }
func (gui *Gui) renderServiceLogs(service *commands.Service) error { func (gui *Gui) renderServiceLogs(service *commands.Service) tasks.TaskFunc {
if service.Container == nil { if service.Container == nil {
return gui.T.NewTask(func(stop chan struct{}) { return gui.NewSimpleRenderStringTask(func() string { return gui.Tr.NoContainerForService })
gui.reRenderStringMain(gui.Tr.NoContainerForService)
})
} }
return gui.renderContainerLogsToMain(service.Container) return gui.renderContainerLogsToMain(service.Container)

100
pkg/gui/tasks_adapter.go Normal file
View file

@ -0,0 +1,100 @@
package gui
import (
"time"
"github.com/jesseduffield/lazydocker/pkg/tasks"
)
func (gui *Gui) QueueTask(f func(stop chan struct{})) error {
return gui.taskManager.NewTask(f)
}
type RenderStringTaskOpts struct {
Autoscroll bool
Wrap bool
GetStrContent func() string
}
type TaskOpts struct {
Autoscroll bool
Wrap bool
Func func(stop chan struct{})
}
type TickerTaskOpts struct {
Duration time.Duration
Before func(stop chan struct{})
Func func(stop, notifyStopped chan struct{})
Autoscroll bool
Wrap bool
}
func (gui *Gui) NewRenderStringTask(opts RenderStringTaskOpts) tasks.TaskFunc {
taskOpts := TaskOpts{
Autoscroll: opts.Autoscroll,
Wrap: opts.Wrap,
Func: func(stop chan struct{}) {
gui.RenderStringMain(opts.GetStrContent())
},
}
return gui.NewTask(taskOpts)
}
// assumes it's cheap to obtain the content (otherwise we would pass a function that returns the content)
func (gui *Gui) NewSimpleRenderStringTask(getContent func() string) tasks.TaskFunc {
return gui.NewRenderStringTask(RenderStringTaskOpts{
GetStrContent: getContent,
Autoscroll: false,
Wrap: gui.Config.UserConfig.Gui.WrapMainPanel,
})
}
func (gui *Gui) NewTask(opts TaskOpts) tasks.TaskFunc {
return func(stop chan struct{}) {
mainView := gui.Views.Main
mainView.Autoscroll = opts.Autoscroll
mainView.Wrap = opts.Wrap
opts.Func(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 (gui *Gui) NewTickerTask(opts TickerTaskOpts) tasks.TaskFunc {
notifyStopped := make(chan struct{}, 10)
task := func(stop chan struct{}) {
if opts.Before != nil {
opts.Before(stop)
}
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)
for {
select {
case <-notifyStopped:
gui.Log.Info("exiting ticker task due to notifyStopped channel")
return
case <-stop:
gui.Log.Info("exiting ticker task due to stopped cahnnel")
return
case <-tickChan.C:
gui.Log.Info("running ticker task again")
opts.Func(stop, notifyStopped)
}
}
}
taskOpts := TaskOpts{
Autoscroll: opts.Autoscroll,
Wrap: opts.Wrap,
Func: task,
}
return gui.NewTask(taskOpts)
}

View file

@ -145,8 +145,8 @@ func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error {
return nil return nil
} }
func (gui *Gui) RenderStringMain(s string) error { func (gui *Gui) RenderStringMain(s string) {
return gui.renderString(gui.g, "main", s) _ = gui.renderString(gui.g, "main", s)
} }
// reRenderString sets the main view's content, without changing its origin // reRenderString sets the main view's content, without changing its origin

View file

@ -10,6 +10,7 @@ import (
"github.com/jesseduffield/lazydocker/pkg/gui/panels" "github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/gui/presentation" "github.com/jesseduffield/lazydocker/pkg/gui/presentation"
"github.com/jesseduffield/lazydocker/pkg/gui/types" "github.com/jesseduffield/lazydocker/pkg/gui/types"
"github.com/jesseduffield/lazydocker/pkg/tasks"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
) )
@ -52,14 +53,8 @@ func (gui *Gui) getVolumesPanel() *panels.SideListPanel[*commands.Volume] {
} }
} }
func (gui *Gui) renderVolumeConfig(volume *commands.Volume) error { func (gui *Gui) renderVolumeConfig(volume *commands.Volume) tasks.TaskFunc {
return gui.T.NewTask(func(stop chan struct{}) { return gui.NewSimpleRenderStringTask(func() string { return gui.volumeConfigStr(volume) })
mainView := gui.Views.Main
mainView.Autoscroll = false
mainView.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel
_ = gui.RenderStringMain(gui.volumeConfigStr(volume))
})
} }
func (gui *Gui) volumeConfigStr(volume *commands.Volume) string { func (gui *Gui) volumeConfigStr(volume *commands.Volume) string {

View file

@ -27,6 +27,8 @@ type Task struct {
f func(chan struct{}) f func(chan struct{})
} }
type TaskFunc func(stop chan struct{})
func NewTaskManager(log *logrus.Entry, translationSet *i18n.TranslationSet) *TaskManager { func NewTaskManager(log *logrus.Entry, translationSet *i18n.TranslationSet) *TaskManager {
return &TaskManager{Log: log, Tr: translationSet} return &TaskManager{Log: log, Tr: translationSet}
} }