more progress

This commit is contained in:
Jesse Duffield 2019-06-01 17:08:21 +10:00
parent f0ffdcd81f
commit d3ea878fc4
2 changed files with 58 additions and 96 deletions

View file

@ -5,11 +5,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"os"
"os/exec" "os/exec"
"strings"
"time"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/fatih/color" "github.com/fatih/color"
@ -17,8 +13,6 @@ import (
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/jesseduffield/pty"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/xerrors" "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")) 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] key := container.ID + "-" + gui.getContainerContexts()[gui.State.Panels.Containers.ContextIndex]
if gui.State.Panels.Main.ObjectKey == key { if gui.State.Panels.Main.ObjectKey == key {
return nil return nil
@ -189,17 +177,32 @@ func (gui *Gui) renderContainerLogs(mainView *gocui.View, container *commands.Co
var cmd *exec.Cmd var cmd *exec.Cmd
cmd = gui.OSCommand.RunCustomCommand("docker logs --since=60m --timestamps --follow " + container.ID) cmd = gui.OSCommand.RunCustomCommand("docker logs --since=60m --timestamps --follow " + container.ID)
cmd.Stdout = mainView r, err := cmd.StdoutPipe()
cmd.Start() if err != nil {
return err
}
go func() { go func() {
for { gui.State.MainProcessChan <- struct{}{}
time.Sleep(time.Second / 100) gui.State.MainProcessMutex.Lock()
if gui.State.Panels.Main.WriterID != writerID { cmd.Start()
cmd.Process.Kill()
return 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 return nil
@ -404,55 +407,10 @@ func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error {
return nil return nil
} }
gui.State.Panels.Main.WriterID++ c := container.Attach()
// c := container.Attach() gui.SubProcess = c
return gui.Errors.ErrSubProcess
// // 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
} }
func (gui *Gui) handleServiceRestart(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleServiceRestart(g *gocui.Gui, v *gocui.View) error {

View file

@ -1,9 +1,9 @@
package gui package gui
import ( import (
"bytes" "fmt"
"io"
"math" "math"
"strings"
"sync" "sync"
// "io" // "io"
@ -14,6 +14,7 @@ import (
"os/exec" "os/exec"
"time" "time"
"github.com/fatih/color"
"github.com/go-errors/errors" "github.com/go-errors/errors"
// "strings" // "strings"
@ -110,6 +111,8 @@ type guiState struct {
Updating bool Updating bool
Panels *panelStates Panels *panelStates
SubProcessOutput string SubProcessOutput string
MainProcessMutex sync.Mutex
MainProcessChan chan struct{}
} }
// NewGui builds a new gui handler // NewGui builds a new gui handler
@ -128,8 +131,17 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand
ObjectKey: "", 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{ gui := &Gui{
Log: log, Log: log,
DockerCommand: dockerCommand, 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 { type listViewState struct {
selectedLine int selectedLine int
lineCount int lineCount int
@ -541,16 +543,9 @@ func (gui *Gui) RunWithSubprocesses() error {
if err == gocui.ErrQuit { if err == gocui.ErrQuit {
break break
} else if err == gui.Errors.ErrSubProcess { } else if err == gui.Errors.ErrSubProcess {
gui.SubProcess.Stdin = os.Stdin if err := gui.runCommand(); err != nil {
output, err := gui.runCommand(gui.SubProcess)
if err != nil {
return err return err
} }
gui.State.SubProcessOutput = output
gui.SubProcess.Stdout = ioutil.Discard
gui.SubProcess.Stderr = ioutil.Discard
gui.SubProcess.Stdin = nil
gui.SubProcess = nil
} else { } else {
return err return err
} }
@ -559,20 +554,29 @@ func (gui *Gui) RunWithSubprocesses() error {
return nil return nil
} }
// adapted from https://blog.kowalczyk.info/article/wOYk/advanced-command-execution-in-go-with-osexec.html func (gui *Gui) runCommand() error {
func (gui *Gui) runCommand(cmd *exec.Cmd) (string, error) { gui.SubProcess.Stdout = os.Stdout
var stdoutBuf bytes.Buffer gui.SubProcess.Stderr = os.Stdout
cmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf) gui.SubProcess.Stdin = os.Stdin
cmd.Stderr = io.MultiWriter(os.Stderr, &stdoutBuf)
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 // not handling the error explicitly because usually we're going to see it
// in the output anyway // in the output anyway
gui.Log.Error(err) gui.Log.Error(err)
} }
outStr := stdoutBuf.String() gui.SubProcess.Stdout = ioutil.Discard
return outStr, nil 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 { func (gui *Gui) quit(g *gocui.Gui, v *gocui.View) error {