Skip to content

Commit

Permalink
feat: Add multi input console type to clean up final questions (#46)
Browse files Browse the repository at this point in the history
* feat: Add multi input console type to clean up final questions

cleaning up the final questions in the CLI was a suggestion by Adam. This does this by cleanly showing the other options in a form-like component.

* various bits of QOL, console research, debugging, and fixes

* run make format

* make format/make lint
  • Loading branch information
Jake Gealer authored Nov 26, 2021
1 parent 36e23f0 commit c63fa42
Show file tree
Hide file tree
Showing 45 changed files with 5,234 additions and 523 deletions.
77 changes: 77 additions & 0 deletions cmd/katapult/console/chunk_reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package console

import (
"bytes"
"io"
"sync"
"sync/atomic"
)

type chunk struct {
a []byte
n int
}

type chunkReader struct {
chunks []chunk
pendingErr error
pipe io.Reader
stopLoop uintptr
m sync.Mutex
}

func (c *chunkReader) loop() {
// Used to stop racing before the start of the loop.
c.m.Unlock()

for atomic.LoadUintptr(&c.stopLoop) == 0 {
// Read from the pipe.
a := make([]byte, 3)
n, err := c.pipe.Read(a)
if atomic.LoadUintptr(&c.stopLoop) == 1 {
// The loop was stopped in the time since last run.
return
}

// Handle setting the values in the struct.
c.m.Lock()
if err == nil {
// Append to the chunks.
doAppend := true
for _, v := range c.chunks {
if bytes.Equal(v.a, a) && v.n == n {
doAppend = false
break
}
}
if doAppend {
c.chunks = append(c.chunks, chunk{a: a, n: n})
}
c.m.Unlock()
} else {
// In this case, put the error and stop the loop.
c.pendingErr = err
c.m.Unlock()
return
}
}
}

func (c *chunkReader) close() {
atomic.StoreUintptr(&c.stopLoop, 1)
}

func (c *chunkReader) flush() []chunk {
c.m.Lock()
defer c.m.Unlock()
a := c.chunks
c.chunks = []chunk{}
return a
}

func newChunkReader(r io.Reader) *chunkReader {
c := chunkReader{chunks: []chunk{}, pipe: r}
c.m.Lock()
go c.loop()
return &c
}
131 changes: 131 additions & 0 deletions cmd/katapult/console/default_terminal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package console

import (
"os"
"sync"

"github.com/buger/goterm"
"golang.org/x/term"
)

type gotermTerminal struct {
raw *term.State
m sync.Mutex
}

func (*gotermTerminal) Height() int {
return goterm.Height()
}

func (*gotermTerminal) Width() int {
return goterm.Width()
}

func (g *gotermTerminal) Print(items ...interface{}) (int, error) {
g.m.Lock()
defer g.m.Unlock()
if g.raw != nil {
_ = term.Restore(0, g.raw)
}
n, err := goterm.Print(items...)
if err == nil && g.raw != nil {
g.raw, err = term.MakeRaw(0)
if err != nil {
return 0, err
}
}
return n, err
}

func (g *gotermTerminal) Sync(s string) error {
// TODO: There are better ways of doing this. I took a few hours to attempt to resync the screen line by line.
// TODO-1: However, by piping the whole chunk out, I find this stops the tearing a lot.
g.m.Lock()
defer g.m.Unlock()
if g.raw != nil {
_ = term.Restore(0, g.raw)
}
_, err := os.Stdout.Write([]byte(s))
if err == nil && g.raw != nil {
g.raw, err = term.MakeRaw(0)
if err != nil {
return err
}
}
return err
}

func (g *gotermTerminal) Clear() {
g.m.Lock()
defer g.m.Unlock()
if g.raw != nil {
_ = term.Restore(0, g.raw)
}
goterm.Clear()
if g.raw != nil {
g.raw, _ = term.MakeRaw(0)
}
}

func (g *gotermTerminal) Println(items ...interface{}) (int, error) {
g.m.Lock()
defer g.m.Unlock()
if g.raw != nil {
_ = term.Restore(0, g.raw)
}
n, err := goterm.Println(items...)
if err == nil && g.raw != nil {
g.raw, err = term.MakeRaw(0)
if err != nil {
return 0, err
}
}
return n, err
}

func (g *gotermTerminal) Flush() {
g.m.Lock()
defer g.m.Unlock()
if g.raw != nil {
_ = term.Restore(0, g.raw)
}
goterm.Flush()
if g.raw != nil {
g.raw, _ = term.MakeRaw(0)
}
}

func (g *gotermTerminal) SignalInterrupt() {
g.m.Lock()
if g.raw != nil {
_ = term.Restore(0, g.raw)
g.raw = nil
}
g.m.Unlock()
os.Exit(1)
}

func (g *gotermTerminal) MakeRaw() error {
g.m.Lock()
raw, err := term.MakeRaw(0)
if raw != nil {
g.raw = raw
}
g.m.Unlock()
return err
}

func (g *gotermTerminal) Unraw() error {
var err error
g.m.Lock()
if g.raw != nil {
err = term.Restore(0, g.raw)
g.raw = nil
}
g.m.Unlock()
return err
}

func (*gotermTerminal) BufferInputs() bool {
return true
}
Loading

0 comments on commit c63fa42

Please sign in to comment.