|
|
|
@ -2,20 +2,14 @@ package gui
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"os/exec"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/docker/docker/api/types"
|
|
|
|
|
|
|
|
|
|
// "io"
|
|
|
|
|
// "io/ioutil"
|
|
|
|
|
|
|
|
|
|
"github.com/go-errors/errors"
|
|
|
|
|
|
|
|
|
|
// "strings"
|
|
|
|
|
|
|
|
|
|
throttle "github.com/boz/go-throttle"
|
|
|
|
|
"github.com/jesseduffield/gocui"
|
|
|
|
|
"github.com/jesseduffield/lazydocker/pkg/commands"
|
|
|
|
@ -31,7 +25,6 @@ var OverlappingEdges = false
|
|
|
|
|
// SentinelErrors are the errors that have special meaning and need to be checked
|
|
|
|
|
// by calling functions. The less of these, the better
|
|
|
|
|
type SentinelErrors struct {
|
|
|
|
|
ErrSubProcess error
|
|
|
|
|
ErrNoContainers error
|
|
|
|
|
ErrNoImages error
|
|
|
|
|
ErrNoVolumes error
|
|
|
|
@ -49,7 +42,6 @@ type SentinelErrors struct {
|
|
|
|
|
// localising things in the code.
|
|
|
|
|
func (gui *Gui) GenerateSentinelErrors() {
|
|
|
|
|
gui.Errors = SentinelErrors{
|
|
|
|
|
ErrSubProcess: errors.New(gui.Tr.RunningSubprocess),
|
|
|
|
|
ErrNoContainers: errors.New(gui.Tr.NoContainers),
|
|
|
|
|
ErrNoImages: errors.New(gui.Tr.NoImages),
|
|
|
|
|
ErrNoVolumes: errors.New(gui.Tr.NoVolumes),
|
|
|
|
@ -62,7 +54,6 @@ type Gui struct {
|
|
|
|
|
Log *logrus.Entry
|
|
|
|
|
DockerCommand *commands.DockerCommand
|
|
|
|
|
OSCommand *commands.OSCommand
|
|
|
|
|
SubProcess *exec.Cmd
|
|
|
|
|
State guiState
|
|
|
|
|
Config *config.AppConfig
|
|
|
|
|
Tr *i18n.TranslationSet
|
|
|
|
@ -73,8 +64,20 @@ type Gui struct {
|
|
|
|
|
ErrorChan chan error
|
|
|
|
|
CyclableViews []string
|
|
|
|
|
Views Views
|
|
|
|
|
|
|
|
|
|
// returns true if our views have been created and assigned to gui.Views.
|
|
|
|
|
// Views are setup only once, upon application start.
|
|
|
|
|
ViewsSetup bool
|
|
|
|
|
|
|
|
|
|
// if we've suspended the gui (e.g. because we've switched to a subprocess)
|
|
|
|
|
// we typically want to pause some things that are running like background
|
|
|
|
|
// file refreshes
|
|
|
|
|
PauseBackgroundThreads bool
|
|
|
|
|
|
|
|
|
|
Mutexes
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Mutexes struct {
|
|
|
|
|
SubprocessMutex sync.Mutex
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type servicePanelState struct {
|
|
|
|
@ -130,11 +133,6 @@ type guiState struct {
|
|
|
|
|
SubProcessOutput string
|
|
|
|
|
Stats map[string]commands.ContainerStats
|
|
|
|
|
|
|
|
|
|
// SessionIndex tells us how many times we've come back from a subprocess.
|
|
|
|
|
// We increment it each time we switch to a new subprocess
|
|
|
|
|
// Every time we go to a subprocess we need to close a few goroutines so this index is used for that purpose
|
|
|
|
|
SessionIndex int
|
|
|
|
|
|
|
|
|
|
ScreenMode WindowMaximisation
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -165,8 +163,7 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand
|
|
|
|
|
},
|
|
|
|
|
Project: &projectState{ContextIndex: 0},
|
|
|
|
|
},
|
|
|
|
|
SessionIndex: 0,
|
|
|
|
|
ViewStack: []string{},
|
|
|
|
|
ViewStack: []string{},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cyclableViews := []string{"project", "containers", "images", "volumes"}
|
|
|
|
@ -193,13 +190,6 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand
|
|
|
|
|
return gui, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func max(a, b int) int {
|
|
|
|
|
if a > b {
|
|
|
|
|
return a
|
|
|
|
|
}
|
|
|
|
|
return b
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (gui *Gui) renderGlobalOptions() error {
|
|
|
|
|
return gui.renderOptionsMap(map[string]string{
|
|
|
|
|
"PgUp/PgDn": gui.Tr.Scroll,
|
|
|
|
@ -211,15 +201,15 @@ func (gui *Gui) renderGlobalOptions() error {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (gui *Gui) goEvery(interval time.Duration, function func() error) {
|
|
|
|
|
currentSessionIndex := gui.State.SessionIndex
|
|
|
|
|
_ = function() // time.Tick doesn't run immediately so we'll do that here // TODO: maybe change
|
|
|
|
|
go func() {
|
|
|
|
|
ticker := time.NewTicker(interval)
|
|
|
|
|
defer ticker.Stop()
|
|
|
|
|
for range ticker.C {
|
|
|
|
|
if gui.State.SessionIndex > currentSessionIndex {
|
|
|
|
|
if gui.PauseBackgroundThreads {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_ = function()
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
@ -289,6 +279,9 @@ func (gui *Gui) Run() error {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = g.MainLoop()
|
|
|
|
|
if err == gocui.ErrQuit {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -411,8 +404,12 @@ func (gui *Gui) handleDonate(g *gocui.Gui, v *gocui.View) error {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (gui *Gui) editFile(filename string) error {
|
|
|
|
|
_, err := gui.runSyncOrAsyncCommand(gui.OSCommand.EditFile(filename))
|
|
|
|
|
return err
|
|
|
|
|
cmd, err := gui.OSCommand.EditFile(filename)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return gui.createErrorPanel(gui.g, err.Error())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return gui.runSubprocess(cmd)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (gui *Gui) openFile(filename string) error {
|
|
|
|
@ -422,28 +419,10 @@ func (gui *Gui) openFile(filename string) error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// runSyncOrAsyncCommand takes the output of a command that may have returned
|
|
|
|
|
// either no error, an error, or a subprocess to execute, and if a subprocess
|
|
|
|
|
// needs to be set on the gui object, it does so, and then returns the error
|
|
|
|
|
// the bool returned tells us whether the calling code should continue
|
|
|
|
|
func (gui *Gui) runSyncOrAsyncCommand(sub *exec.Cmd, err error) (bool, error) {
|
|
|
|
|
if err != nil {
|
|
|
|
|
if err != gui.Errors.ErrSubProcess {
|
|
|
|
|
return false, gui.createErrorPanel(gui.g, err.Error())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if sub != nil {
|
|
|
|
|
gui.SubProcess = sub
|
|
|
|
|
return false, gui.Errors.ErrSubProcess
|
|
|
|
|
}
|
|
|
|
|
return true, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (gui *Gui) handleCustomCommand(g *gocui.Gui, v *gocui.View) error {
|
|
|
|
|
return gui.createPromptPanel(g, v, gui.Tr.CustomCommandTitle, func(g *gocui.Gui, v *gocui.View) error {
|
|
|
|
|
command := gui.trimmedContent(v)
|
|
|
|
|
gui.SubProcess = gui.OSCommand.RunCustomCommand(command)
|
|
|
|
|
return gui.Errors.ErrSubProcess
|
|
|
|
|
return gui.runSubprocess(gui.OSCommand.RunCustomCommand(command))
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|