monitor: add common shell features to dev prompt

This makes the monitor mode from —invoke more
user friendly by enabling features user might expect
from dev shell.

Interactive mode now has colors, history and keyboard
control for movement (arrows, moving to beginning/end,
by word, emacs controls etc).

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
pull/1289/head
Tonis Tiigi 3 years ago
parent 44b8a338f7
commit 6e05092055

@ -1,16 +1,15 @@
package monitor package monitor
import ( import (
"bufio"
"context" "context"
"fmt" "fmt"
"io" "io"
"sync" "sync"
prompt "github.com/c-bata/go-prompt"
"github.com/docker/buildx/build" "github.com/docker/buildx/build"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/term"
) )
const helpMessage = ` const helpMessage = `
@ -69,16 +68,7 @@ func RunMonitor(ctx context.Context, containerConfig build.ContainerConfig, relo
<-ctx.Done() <-ctx.Done()
in.Close() in.Close()
}() }()
t := term.NewTerminal(readWriter{in.stdin, in.stdout}, "(buildx) ") exec := func(l string) {
for {
l, err := t.ReadLine()
if err != nil {
if err != io.EOF {
errCh <- err
return
}
return
}
switch l { switch l {
case "": case "":
// nop // nop
@ -104,6 +94,36 @@ func RunMonitor(ctx context.Context, containerConfig build.ContainerConfig, relo
fmt.Fprint(stdout, helpMessage) fmt.Fprint(stdout, helpMessage)
} }
} }
completer := func(d prompt.Document) []prompt.Suggest {
return []prompt.Suggest{}
}
prompt.New(exec, completer, prompt.OptionHistory([]string{
"reload", "rollback", "exit", "help",
}),
prompt.OptionPrefix("(buildx) "),
prompt.OptionParser(&customParser{
PosixParser: *prompt.NewStandardInputParser(),
reader: in.stdin,
}),
prompt.OptionWriter(&posixWriter{
w: in.stdout,
}),
prompt.OptionAddASCIICodeBind(
prompt.ASCIICodeBind{
ASCIICode: []byte{0x1b, 'f'},
Fn: func(b *prompt.Buffer) {
b.CursorRight(1 + b.Document().FindEndOfCurrentWordWithSpace())
},
},
prompt.ASCIICodeBind{
ASCIICode: []byte{0x1b, 'b'},
Fn: func(b *prompt.Buffer) {
b.CursorLeft(len(b.Document().TextBeforeCursor()) - b.Document().FindStartOfPreviousWordWithSpace())
},
},
),
).Run()
}() }()
select { select {
case <-doneCh: case <-doneCh:
@ -307,12 +327,12 @@ func newMuxIO(in ioSetIn, out []ioSetOutContext, initIdx int, toggleMessage func
errToggle := errors.Errorf("toggle IO") errToggle := errors.Errorf("toggle IO")
for { for {
prevIsControlSequence := false prevIsControlSequence := false
if err := copyToFunc(traceReader(in.stdin, func(r rune) (bool, error) { if err := copyToFunc(traceReader(in.stdin, func(r []byte) (bool, error) {
// Toggle IO when it detects C-a-c // Toggle IO when it detects C-a-c
// TODO: make it configurable if needed // TODO: make it configurable if needed
if int(r) == 1 { if len(r) == 1 && int(r[0]) == 1 {
prevIsControlSequence = true prevIsControlSequence = true
return false, nil return true, nil
} }
defer func() { prevIsControlSequence = false }() defer func() { prevIsControlSequence = false }()
if prevIsControlSequence { if prevIsControlSequence {
@ -404,12 +424,12 @@ func (m *muxIO) toggleIO() {
fmt.Fprint(m.in.stdout, m.toggleMessage(prev, res)) fmt.Fprint(m.in.stdout, m.toggleMessage(prev, res))
} }
func traceReader(r io.ReadCloser, f func(rune) (bool, error)) io.ReadCloser { func traceReader(r io.ReadCloser, f func([]byte) (bool, error)) io.ReadCloser {
dt := make([]byte, 32)
pr, pw := io.Pipe() pr, pw := io.Pipe()
go func() { go func() {
br := bufio.NewReader(r)
for { for {
rn, _, err := br.ReadRune() n, err := r.Read(dt)
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
pw.Close() pw.Close()
@ -418,13 +438,13 @@ func traceReader(r io.ReadCloser, f func(rune) (bool, error)) io.ReadCloser {
pw.CloseWithError(err) pw.CloseWithError(err)
return return
} }
if isWrite, err := f(rn); err != nil { if isWrite, err := f(dt[:n]); err != nil {
pw.CloseWithError(err) pw.CloseWithError(err)
return return
} else if !isWrite { } else if !isWrite {
continue continue
} }
if _, err := pw.Write([]byte(string(rn))); err != nil { if _, err := pw.Write(dt[:n]); err != nil {
pw.CloseWithError(err) pw.CloseWithError(err)
return return
} }
@ -514,3 +534,45 @@ type readerWithClose struct {
func (r *readerWithClose) Close() error { func (r *readerWithClose) Close() error {
return r.closeFunc() return r.closeFunc()
} }
type customParser struct {
prompt.PosixParser
reader io.Reader
}
func (cp *customParser) Read() ([]byte, error) {
buf := make([]byte, 1024)
n, err := cp.reader.Read(buf)
if err != nil {
return []byte{}, err
}
return buf[:n], nil
}
type posixWriter struct {
prompt.VT100Writer
w io.Writer
}
func (w *posixWriter) Flush() error {
buf := w.Buffer()
l := len(buf)
offset := 0
retry := 0
for {
n, err := w.w.Write(buf[offset:])
if err != nil {
if retry < 3 {
retry++
continue
}
return err
}
offset += n
if offset == l {
break
}
}
w.SetBuffer([]byte{})
return nil
}

@ -10,6 +10,14 @@ type VT100Writer struct {
buffer []byte buffer []byte
} }
func (w *VT100Writer) Buffer() []byte {
return w.buffer
}
func (w *VT100Writer) SetBuffer(buffer []byte) {
w.buffer = buffer
}
// WriteRaw to write raw byte array // WriteRaw to write raw byte array
func (w *VT100Writer) WriteRaw(data []byte) { func (w *VT100Writer) WriteRaw(data []byte) {
w.buffer = append(w.buffer, data...) w.buffer = append(w.buffer, data...)

Loading…
Cancel
Save