From d3ea878fc4b967789fa8c796d1971595947112b2 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 1 Jun 2019 17:08:21 +1000 Subject: [PATCH] more progress --- pkg/gui/containers_panel.go | 94 ++++++++++--------------------------- pkg/gui/gui.go | 60 ++++++++++++----------- 2 files changed, 58 insertions(+), 96 deletions(-) diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go index 92f65ba9..15fc96b7 100644 --- a/pkg/gui/containers_panel.go +++ b/pkg/gui/containers_panel.go @@ -5,11 +5,7 @@ import ( "context" "encoding/json" "fmt" - "io" - "os" "os/exec" - "strings" - "time" "github.com/docker/docker/api/types" "github.com/fatih/color" @@ -17,8 +13,6 @@ import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/utils" - "github.com/jesseduffield/pty" - "golang.org/x/crypto/ssh/terminal" "golang.org/x/xerrors" ) @@ -74,12 +68,6 @@ func (gui *Gui) handleContainerSelect(g *gocui.Gui, v *gocui.View) error { return gui.renderString(g, "main", gui.Tr.SLocalize("NoChangedContainers")) } - // if we are in an attached state, we won't do anything here - // TODO: generalise - if strings.HasPrefix(gui.State.Panels.Main.ObjectKey, "attached-") { - return nil - } - key := container.ID + "-" + gui.getContainerContexts()[gui.State.Panels.Containers.ContextIndex] if gui.State.Panels.Main.ObjectKey == key { return nil @@ -189,17 +177,32 @@ func (gui *Gui) renderContainerLogs(mainView *gocui.View, container *commands.Co var cmd *exec.Cmd cmd = gui.OSCommand.RunCustomCommand("docker logs --since=60m --timestamps --follow " + container.ID) - cmd.Stdout = mainView - cmd.Start() + r, err := cmd.StdoutPipe() + if err != nil { + return err + } go func() { - for { - time.Sleep(time.Second / 100) - if gui.State.Panels.Main.WriterID != writerID { - cmd.Process.Kill() - return + gui.State.MainProcessChan <- struct{}{} + gui.State.MainProcessMutex.Lock() + cmd.Start() + + go func() { + <-gui.State.MainProcessChan + cmd.Process.Kill() + }() + + go func() { + for { + s := bufio.NewScanner(r) + s.Split(bufio.ScanLines) + for s.Scan() { + mainView.Write(append(s.Bytes(), '\n')) + } } - } + }() + cmd.Wait() + gui.State.MainProcessMutex.Unlock() }() return nil @@ -404,55 +407,10 @@ func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error { return nil } - gui.State.Panels.Main.WriterID++ + c := container.Attach() - // c := container.Attach() - - // // Create arbitrary command. - c := exec.Command("bash") - - // Start the command with a pty. - ptmx, err := pty.Start(c) - if err != nil { - return err - } - - go func() { - // Set stdin in raw mode. - oldState, err := terminal.MakeRaw(int(os.Stdin.Fd())) - if err != nil { - panic(err) - } - defer func() { _ = terminal.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort. - defer func() { _ = ptmx.Close() }() // Best effort. - - go func() { _, _ = io.Copy(ptmx, os.Stdin) }() - - mainView := gui.getMainView() - mainView.Clear() - mainView.Autoscroll = true - gui.State.Panels.Main.ObjectKey = "attached-" + container.ID - - scanner := bufio.NewScanner(ptmx) - scanner.Split(bufio.ScanBytes) - - content := "" - for scanner.Scan() { - content += scanner.Text() - gui.Log.Warn("content") - gui.Log.Warn(content) - - gui.renderString(gui.g, "main", content) - } - - // reset object key - gui.State.Panels.Main.ObjectKey = "" - gui.State.Panels.Main.WriterID++ - - gui.handleContainerSelect(gui.g, v) - }() - - return nil + gui.SubProcess = c + return gui.Errors.ErrSubProcess } func (gui *Gui) handleServiceRestart(g *gocui.Gui, v *gocui.View) error { diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 9effc52c..cae31cc0 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -1,9 +1,9 @@ package gui import ( - "bytes" - "io" + "fmt" "math" + "strings" "sync" // "io" @@ -14,6 +14,7 @@ import ( "os/exec" "time" + "github.com/fatih/color" "github.com/go-errors/errors" // "strings" @@ -110,6 +111,8 @@ type guiState struct { Updating bool Panels *panelStates SubProcessOutput string + MainProcessMutex sync.Mutex + MainProcessChan chan struct{} } // NewGui builds a new gui handler @@ -128,8 +131,17 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand ObjectKey: "", }, }, + MainProcessChan: make(chan struct{}), } + go func() { + // setting up a goroutine for listening to the first stop signal on this channel + // because whenever something wants to lock the mutex, it tells the existing process to stop + // but on startup we don't have a process so we just mock it + // this is because we're using an unbuffered channel + <-initialState.MainProcessChan + }() + gui := &Gui{ Log: log, DockerCommand: dockerCommand, @@ -392,16 +404,6 @@ func (gui *Gui) layout(g *gocui.Gui) error { } } - if gui.State.SubProcessOutput != "" { - output := gui.State.SubProcessOutput - gui.State.SubProcessOutput = "" - x, y := gui.g.Size() - // if we just came back from vim, we don't want vim's output to show up in our popup - if float64(len(output))*1.5 < float64(x*y) { - return gui.createMessagePanel(gui.g, nil, "Output", output) - } - } - type listViewState struct { selectedLine int lineCount int @@ -541,16 +543,9 @@ func (gui *Gui) RunWithSubprocesses() error { if err == gocui.ErrQuit { break } else if err == gui.Errors.ErrSubProcess { - gui.SubProcess.Stdin = os.Stdin - output, err := gui.runCommand(gui.SubProcess) - if err != nil { + if err := gui.runCommand(); err != nil { return err } - gui.State.SubProcessOutput = output - gui.SubProcess.Stdout = ioutil.Discard - gui.SubProcess.Stderr = ioutil.Discard - gui.SubProcess.Stdin = nil - gui.SubProcess = nil } else { return err } @@ -559,20 +554,29 @@ func (gui *Gui) RunWithSubprocesses() error { return nil } -// adapted from https://blog.kowalczyk.info/article/wOYk/advanced-command-execution-in-go-with-osexec.html -func (gui *Gui) runCommand(cmd *exec.Cmd) (string, error) { - var stdoutBuf bytes.Buffer - cmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf) - cmd.Stderr = io.MultiWriter(os.Stderr, &stdoutBuf) +func (gui *Gui) runCommand() error { + gui.SubProcess.Stdout = os.Stdout + gui.SubProcess.Stderr = os.Stdout + gui.SubProcess.Stdin = os.Stdin - if err := cmd.Run(); err != nil { + fmt.Fprintf(os.Stdout, "\n%s\n\n", utils.ColoredString("+ "+strings.Join(gui.SubProcess.Args, " "), color.FgBlue)) + + if err := gui.SubProcess.Run(); err != nil { // not handling the error explicitly because usually we're going to see it // in the output anyway gui.Log.Error(err) } - outStr := stdoutBuf.String() - return outStr, nil + gui.SubProcess.Stdout = ioutil.Discard + gui.SubProcess.Stderr = ioutil.Discard + gui.SubProcess.Stdin = nil + gui.SubProcess = nil + + // fmt.Fprintf(os.Stdout, "\n%s", utils.ColoredString(gui.Tr.SLocalize("pressEnterToReturn"), color.FgGreen)) + + // fmt.Scanln() // wait for enter press + + return nil } func (gui *Gui) quit(g *gocui.Gui, v *gocui.View) error {