mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-25 08:31:03 +00:00
better way of rendering to the main view
This commit is contained in:
parent
5ffbeae3e8
commit
52040d1d99
17 changed files with 266 additions and 167 deletions
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
100
pkg/gui/tasks_adapter.go
Normal 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)
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue