mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-21 23:01:03 +00:00
improve subprocess approach
This commit is contained in:
parent
c6607686eb
commit
fb7b28243b
12 changed files with 65 additions and 106 deletions
|
|
@ -56,8 +56,7 @@ func NewApp(config *config.AppConfig) (*App, error) {
|
|||
}
|
||||
|
||||
func (app *App) Run() error {
|
||||
err := app.Gui.RunWithSubprocesses()
|
||||
return err
|
||||
return app.Gui.Run()
|
||||
}
|
||||
|
||||
func (app *App) Close() error {
|
||||
|
|
|
|||
|
|
@ -492,8 +492,7 @@ func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error {
|
|||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
gui.SubProcess = c
|
||||
return gui.Errors.ErrSubProcess
|
||||
return gui.runSubprocess(c)
|
||||
}
|
||||
|
||||
func (gui *Gui) handlePruneContainers() error {
|
||||
|
|
@ -537,8 +536,7 @@ func (gui *Gui) containerExecShell(container *commands.Container) error {
|
|||
resolvedCommand := utils.ApplyTemplate("docker exec -it {{ .Container.ID }} /bin/sh -c 'eval $(grep ^$(id -un): /etc/passwd | cut -d : -f 7-)'", commandObject)
|
||||
// attach and return the subprocess error
|
||||
cmd := gui.OSCommand.ExecutableFromString(resolvedCommand)
|
||||
gui.SubProcess = cmd
|
||||
return gui.Errors.ErrSubProcess
|
||||
return gui.runSubprocess(cmd)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleContainersCustomCommand(g *gocui.Gui, v *gocui.View) error {
|
||||
|
|
|
|||
|
|
@ -53,8 +53,7 @@ func (gui *Gui) createCommandMenu(customCommands []config.CustomCommand, command
|
|||
// if we have a command for attaching, we attach and return the subprocess error
|
||||
if option.customCommand.Attach {
|
||||
cmd := gui.OSCommand.ExecutableFromString(option.command)
|
||||
gui.SubProcess = cmd
|
||||
return gui.Errors.ErrSubProcess
|
||||
return gui.runSubprocess(cmd)
|
||||
}
|
||||
|
||||
return gui.WithWaitingStatus(waitingStatus, func() error {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -224,6 +224,5 @@ func (gui *Gui) handleViewAllLogs(g *gocui.Gui, v *gocui.View) error {
|
|||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
gui.SubProcess = c
|
||||
return gui.Errors.ErrSubProcess
|
||||
return gui.runSubprocess(c)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -293,8 +293,7 @@ func (gui *Gui) handleServiceAttach(g *gocui.Gui, v *gocui.View) error {
|
|||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
gui.SubProcess = c
|
||||
return gui.Errors.ErrSubProcess
|
||||
return gui.runSubprocess(c)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleServiceRenderLogsToMain(g *gocui.Gui, v *gocui.View) error {
|
||||
|
|
@ -308,8 +307,7 @@ func (gui *Gui) handleServiceRenderLogsToMain(g *gocui.Gui, v *gocui.View) error
|
|||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
gui.SubProcess = c
|
||||
return gui.Errors.ErrSubProcess
|
||||
return gui.runSubprocess(c)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
|
|
@ -366,8 +364,7 @@ func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error {
|
|||
gui.DockerCommand.NewCommandObject(commands.CommandObject{Service: service}),
|
||||
),
|
||||
f: func() error {
|
||||
gui.SubProcess = gui.OSCommand.RunCustomCommand(rebuildCommand)
|
||||
return gui.Errors.ErrSubProcess
|
||||
return gui.runSubprocess(gui.OSCommand.RunCustomCommand(rebuildCommand))
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,48 +4,43 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazydocker/pkg/utils"
|
||||
)
|
||||
|
||||
// RunWithSubprocesses loops, instantiating a new gocui.Gui with each iteration
|
||||
// if the error returned from a run is a ErrSubProcess, it runs the subprocess
|
||||
// otherwise it handles the error, possibly by quitting the application
|
||||
func (gui *Gui) RunWithSubprocesses() error {
|
||||
for {
|
||||
if err := gui.Run(); err != nil {
|
||||
if err == gocui.ErrQuit {
|
||||
break
|
||||
} else if err == gui.Errors.ErrSubProcess {
|
||||
// preparing the state for when we return
|
||||
gui.pushView(gui.currentViewName())
|
||||
// giving goEvery goroutines time to finish
|
||||
gui.State.SessionIndex++
|
||||
func (gui *Gui) runSubprocess(cmd *exec.Cmd) error {
|
||||
gui.Mutexes.SubprocessMutex.Lock()
|
||||
defer gui.Mutexes.SubprocessMutex.Unlock()
|
||||
|
||||
if err := gui.runCommand(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// pop here so we don't stack up view names
|
||||
gui.popView()
|
||||
// ensuring we render e.g. the logs of the currently selected item upon return
|
||||
gui.State.Panels.Main.ObjectKey = ""
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := gui.g.Suspend(); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
gui.PauseBackgroundThreads = true
|
||||
|
||||
cmdErr := gui.runSubprocess(cmd)
|
||||
|
||||
if err := gui.g.Resume(); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
gui.PauseBackgroundThreads = false
|
||||
|
||||
if cmdErr != nil {
|
||||
return gui.createErrorPanel(gui.g, cmdErr.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) runCommand() error {
|
||||
gui.SubProcess.Stdout = os.Stdout
|
||||
gui.SubProcess.Stderr = os.Stdout
|
||||
gui.SubProcess.Stdin = os.Stdin
|
||||
func (gui *Gui) runCommand(cmd *exec.Cmd) error {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stdout
|
||||
cmd.Stdin = os.Stdin
|
||||
|
||||
stop := make(chan os.Signal, 1)
|
||||
defer signal.Stop(stop)
|
||||
|
|
@ -54,23 +49,22 @@ func (gui *Gui) runCommand() error {
|
|||
signal.Notify(stop, os.Interrupt)
|
||||
<-stop
|
||||
|
||||
if err := gui.OSCommand.Kill(gui.SubProcess); err != nil {
|
||||
if err := gui.OSCommand.Kill(cmd); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
fmt.Fprintf(os.Stdout, "\n%s\n\n", utils.ColoredString("+ "+strings.Join(gui.SubProcess.Args, " "), color.FgBlue))
|
||||
fmt.Fprintf(os.Stdout, "\n%s\n\n", utils.ColoredString("+ "+strings.Join(cmd.Args, " "), color.FgBlue))
|
||||
|
||||
if err := gui.SubProcess.Run(); err != nil {
|
||||
if err := cmd.Run(); err != nil {
|
||||
// not handling the error explicitly because usually we're going to see it
|
||||
// in the output anyway
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
|
||||
gui.SubProcess.Stdin = nil
|
||||
gui.SubProcess.Stdout = ioutil.Discard
|
||||
gui.SubProcess.Stderr = ioutil.Discard
|
||||
gui.SubProcess = nil
|
||||
cmd.Stdin = nil
|
||||
cmd.Stdout = ioutil.Discard
|
||||
cmd.Stderr = ioutil.Discard
|
||||
|
||||
gui.promptToReturn()
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ func dutchSet() TranslationSet {
|
|||
StoppingStatus: "stoppen",
|
||||
RunningCustomCommandStatus: "Aangepast commando draaien",
|
||||
|
||||
RunningSubprocess: "subprocess draaien",
|
||||
NoViewMachingNewLineFocusedSwitchStatement: "No view matching newLineFocused switch statement",
|
||||
|
||||
ErrorOccurred: "Er is iets fout gegaan! Zou je hier een issue aan willen maken: https://github.com/jesseduffield/lazydocker/issues",
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ type TranslationSet struct {
|
|||
Scroll string
|
||||
Close string
|
||||
ErrorTitle string
|
||||
RunningSubprocess string
|
||||
NoViewMachingNewLineFocusedSwitchStatement string
|
||||
OpenConfig string
|
||||
EditConfig string
|
||||
|
|
@ -121,7 +120,6 @@ func englishSet() TranslationSet {
|
|||
RunningCustomCommandStatus: "running custom command",
|
||||
RunningBulkCommandStatus: "running bulk command",
|
||||
|
||||
RunningSubprocess: "running subprocess",
|
||||
NoViewMachingNewLineFocusedSwitchStatement: "No view matching newLineFocused switch statement",
|
||||
|
||||
ErrorOccurred: "An error occurred! Please create an issue at https://github.com/jesseduffield/lazydocker/issues",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ func germanSet() TranslationSet {
|
|||
StoppingStatus: "anhalten",
|
||||
RunningCustomCommandStatus: "führt benutzerdefinierten Befehl aus",
|
||||
|
||||
RunningSubprocess: "Unterprozess ausführen",
|
||||
NoViewMachingNewLineFocusedSwitchStatement: "No view matching newLineFocused switch statement",
|
||||
|
||||
ErrorOccurred: "Es ist ein Fehler aufgetreten! Bitte erstelle ein Issue hier: https://github.com/jesseduffield/lazydocker/issues",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ func polishSet() TranslationSet {
|
|||
StoppingStatus: "zatrzymywanie",
|
||||
RunningCustomCommandStatus: "uruchamianie własnej komendty",
|
||||
|
||||
RunningSubprocess: "uruchamianie podprocesu",
|
||||
NoViewMachingNewLineFocusedSwitchStatement: "Żaden widok nie odpowiada instrukcji przełączenia newLineFocused",
|
||||
|
||||
ErrorOccurred: "Wystąpił błąd! Proszę go zgłosić na https://github.com/jesseduffield/lazydocker/issues",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ func turkishSet() TranslationSet {
|
|||
StoppingStatus: "durduruluyor",
|
||||
RunningCustomCommandStatus: "özel komut çalıştır",
|
||||
|
||||
RunningSubprocess: "İkincil işlem yürütülüyor",
|
||||
NoViewMachingNewLineFocusedSwitchStatement: "NewLineFocused anahtar deyimi ile eşleşen görünüm yok",
|
||||
|
||||
ErrorOccurred: "Bir hata oluştu! Lütfen https://github.com/jesseduffield/lazydocker/issues adresinden bir hataya ilişkin konu oluşturun",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue