Skip to content

Commit

Permalink
monitor: add common shell features to dev prompt
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
tonistiigi committed Aug 22, 2022
1 parent 44b8a33 commit 6e05092
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 20 deletions.
102 changes: 82 additions & 20 deletions monitor/monitor.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package monitor

import (
"bufio"
"context"
"fmt"
"io"
"sync"

prompt "github.com/c-bata/go-prompt"
"github.com/docker/buildx/build"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/term"
)

const helpMessage = `
Expand Down Expand Up @@ -69,16 +68,7 @@ func RunMonitor(ctx context.Context, containerConfig build.ContainerConfig, relo
<-ctx.Done()
in.Close()
}()
t := term.NewTerminal(readWriter{in.stdin, in.stdout}, "(buildx) ")
for {
l, err := t.ReadLine()
if err != nil {
if err != io.EOF {
errCh <- err
return
}
return
}
exec := func(l string) {
switch l {
case "":
// nop
Expand All @@ -104,6 +94,36 @@ func RunMonitor(ctx context.Context, containerConfig build.ContainerConfig, relo
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 {
case <-doneCh:
Expand Down Expand Up @@ -307,12 +327,12 @@ func newMuxIO(in ioSetIn, out []ioSetOutContext, initIdx int, toggleMessage func
errToggle := errors.Errorf("toggle IO")
for {
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
// TODO: make it configurable if needed
if int(r) == 1 {
if len(r) == 1 && int(r[0]) == 1 {
prevIsControlSequence = true
return false, nil
return true, nil
}
defer func() { prevIsControlSequence = false }()
if prevIsControlSequence {
Expand Down Expand Up @@ -404,12 +424,12 @@ func (m *muxIO) toggleIO() {
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()
go func() {
br := bufio.NewReader(r)
for {
rn, _, err := br.ReadRune()
n, err := r.Read(dt)
if err != nil {
if err == io.EOF {
pw.Close()
Expand All @@ -418,13 +438,13 @@ func traceReader(r io.ReadCloser, f func(rune) (bool, error)) io.ReadCloser {
pw.CloseWithError(err)
return
}
if isWrite, err := f(rn); err != nil {
if isWrite, err := f(dt[:n]); err != nil {
pw.CloseWithError(err)
return
} else if !isWrite {
continue
}
if _, err := pw.Write([]byte(string(rn))); err != nil {
if _, err := pw.Write(dt[:n]); err != nil {
pw.CloseWithError(err)
return
}
Expand Down Expand Up @@ -514,3 +534,45 @@ type readerWithClose struct {
func (r *readerWithClose) Close() error {
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
}
8 changes: 8 additions & 0 deletions vendor/github.com/c-bata/go-prompt/output_vt100.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 6e05092

Please sign in to comment.