Skip to content

Commit

Permalink
Refactor type names and make interfaces more idiomatic
Browse files Browse the repository at this point in the history
  • Loading branch information
Verseth committed Jul 5, 2023
1 parent bce683f commit 27e9a8c
Show file tree
Hide file tree
Showing 15 changed files with 152 additions and 168 deletions.
7 changes: 5 additions & 2 deletions _example/simple-echo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ func completer(in prompt.Document) []prompt.Suggest {
}

func main() {
in := prompt.Input(">>> ", completer,
in := prompt.Input(
">>> ",
completer,
prompt.OptionTitle("sql-prompt"),
prompt.OptionHistory([]string{"SELECT * FROM users;"}),
prompt.OptionPrefixTextColor(prompt.Yellow),
prompt.OptionPreviewSuggestionTextColor(prompt.Blue),
prompt.OptionSelectedSuggestionBGColor(prompt.LightGray),
prompt.OptionSuggestionBGColor(prompt.DarkGray))
prompt.OptionSuggestionBGColor(prompt.DarkGray),
)
fmt.Println("Your input: " + in)
}
14 changes: 7 additions & 7 deletions option.go → constructor.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ package prompt
// prompt.New accepts any number of options (this is functional option pattern).
type Option func(prompt *Prompt) error

// OptionParser to set a custom ConsoleParser object. An argument should implement ConsoleParser interface.
func OptionParser(x ConsoleParser) Option {
// OptionParser to set a custom Reader object. An argument should implement Reader interface.
func OptionParser(x Reader) Option {
return func(p *Prompt) error {
p.in = x
return nil
}
}

// OptionWriter to set a custom ConsoleWriter object. An argument should implement ConsoleWriter interface.
func OptionWriter(x ConsoleWriter) Option {
// OptionWriter to set a custom Writer object. An argument should implement Writer interface.
func OptionWriter(x Writer) Option {
return func(p *Prompt) error {
registerConsoleWriter(x)
registerWriter(x)
p.renderer.out = x
return nil
}
Expand Down Expand Up @@ -277,10 +277,10 @@ func OptionSetLexer(lex Lexer) Option {
// New returns a Prompt with powerful auto-completion.
func New(executor Executor, completer Completer, opts ...Option) *Prompt {
defaultWriter := NewStdoutWriter()
registerConsoleWriter(defaultWriter)
registerWriter(defaultWriter)

pt := &Prompt{
in: NewStandardInputParser(),
in: NewStdinReader(),
renderer: &Render{
prefix: "> ",
out: defaultWriter,
Expand Down
4 changes: 2 additions & 2 deletions internal/debug/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ func init() {
logger = log.New(ioutil.Discard, "", log.Llongfile)
}

// Teardown to close logfile
func Teardown() {
// Close to close logfile
func Close() {
if logfile == nil {
return
}
Expand Down
61 changes: 34 additions & 27 deletions prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"github.com/elk-language/go-prompt/internal/debug"
)

const inputBufferSize = 1024

// Executor is called when the user
// inputs a line of text.
type Executor func(string)
Expand All @@ -27,7 +29,7 @@ type Completer func(Document) []Suggest

// Prompt is a core struct of go-prompt.
type Prompt struct {
in ConsoleParser
in Reader
buf *Buffer
renderer *Render
executor Executor
Expand All @@ -39,21 +41,21 @@ type Prompt struct {
keyBindMode KeyBindMode
completionOnDown bool
exitChecker ExitChecker
skipTearDown bool
skipClose bool
}

// Exec is the struct that contains the user input context.
type Exec struct {
// UserInput is the struct that contains the user input context.
type UserInput struct {
input string
}

// Run starts the prompt.
func (p *Prompt) Run() {
p.skipTearDown = false
defer debug.Teardown()
p.skipClose = false
defer debug.Close()
debug.Log("start prompt")
p.setUp()
defer p.tearDown()
p.setup()
defer p.Close()

if p.completion.showAtStart {
p.completion.Update(*p.buf.Document())
Expand Down Expand Up @@ -85,19 +87,19 @@ func (p *Prompt) Run() {

// Unset raw mode
// Reset to Blocking mode because returned EAGAIN when still set non-blocking mode.
debug.AssertNoError(p.in.TearDown())
debug.AssertNoError(p.in.Close())
p.executor(e.input)

p.completion.Update(*p.buf.Document())

p.renderer.Render(p.buf, p.completion, p.lexer)

if p.exitChecker != nil && p.exitChecker(e.input, true) {
p.skipTearDown = true
p.skipClose = true
return
}
// Set raw mode
debug.AssertNoError(p.in.Setup())
debug.AssertNoError(p.in.Open())
go p.readBuffer(bufCh, stopReadBufCh)
go p.handleSignals(exitCh, winSizeCh, stopHandleSignalCh)
} else {
Expand All @@ -109,7 +111,7 @@ func (p *Prompt) Run() {
p.renderer.Render(p.buf, p.completion, p.lexer)
case code := <-exitCh:
p.renderer.BreakLine(p.buf, p.lexer)
p.tearDown()
p.Close()
os.Exit(code)
default:
time.Sleep(10 * time.Millisecond)
Expand All @@ -126,7 +128,7 @@ func (p *Prompt) Run() {
// fmt.Fprintf(f, format, a...)
// }

func (p *Prompt) feed(b []byte) (shouldExit bool, exec *Exec) {
func (p *Prompt) feed(b []byte) (shouldExit bool, userInput *UserInput) {
key := GetKey(b)
p.buf.lastKeyStroke = key
// completion
Expand All @@ -137,10 +139,10 @@ func (p *Prompt) feed(b []byte) (shouldExit bool, exec *Exec) {
case Enter, ControlJ, ControlM:
p.renderer.BreakLine(p.buf, p.lexer)

exec = &Exec{input: p.buf.Text()}
userInput = &UserInput{input: p.buf.Text()}
p.buf = NewBuffer()
if exec.input != "" {
p.history.Add(exec.input)
if userInput.input != "" {
p.history.Add(userInput.input)
}
case ControlC:
p.renderer.BreakLine(p.buf, p.lexer)
Expand Down Expand Up @@ -268,10 +270,10 @@ func (p *Prompt) handleASCIICodeBinding(b []byte) bool {
// Input starts the prompt, lets the user
// input a single line and returns this line as a string.
func (p *Prompt) Input() string {
defer debug.Teardown()
defer debug.Close()
debug.Log("start prompt")
p.setUp()
defer p.tearDown()
p.setup()
defer p.Close()

if p.completion.showAtStart {
p.completion.Update(*p.buf.Document())
Expand Down Expand Up @@ -311,8 +313,13 @@ func (p *Prompt) readBuffer(bufCh chan []byte, stopCh chan struct{}) {
debug.Log("stop reading buffer")
return
default:
if bytes, err := p.in.Read(); err == nil && !(len(bytes) == 1 && bytes[0] == 0) {
// bufCh <- bytes
bytes := make([]byte, inputBufferSize)
n, err := p.in.Read(bytes)
if err != nil {
break
}
bytes = bytes[:n]
if len(bytes) != 1 || bytes[0] != 0 {
newBytes := make([]byte, len(bytes))
for i, byt := range bytes {
// translate raw mode \r into \n
Expand All @@ -332,15 +339,15 @@ func (p *Prompt) readBuffer(bufCh chan []byte, stopCh chan struct{}) {
}
}

func (p *Prompt) setUp() {
debug.AssertNoError(p.in.Setup())
func (p *Prompt) setup() {
debug.AssertNoError(p.in.Open())
p.renderer.Setup()
p.renderer.UpdateWinSize(p.in.GetWinSize())
}

func (p *Prompt) tearDown() {
if !p.skipTearDown {
debug.AssertNoError(p.in.TearDown())
func (p *Prompt) Close() {
if !p.skipClose {
debug.AssertNoError(p.in.Close())
}
p.renderer.TearDown()
p.renderer.Close()
}
18 changes: 9 additions & 9 deletions input.go → reader.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
package prompt

import "bytes"
import (
"bytes"
"io"
)

// WinSize represents the width and height of terminal.
type WinSize struct {
Row uint16
Col uint16
}

// ConsoleParser is an interface to abstract input layer.
type ConsoleParser interface {
// Setup should be called before starting input
Setup() error
// TearDown should be called after stopping input
TearDown() error
// Reader is an interface to abstract input layer.
type Reader interface {
// Open should be called before starting reading
Open() error
// GetWinSize returns WinSize object to represent width and height of terminal.
GetWinSize() *WinSize
// Read returns byte array.
Read() ([]byte, error)
io.ReadCloser
}

// GetKey returns Key correspond to input byte codes.
Expand Down
33 changes: 13 additions & 20 deletions input_posix.go → reader_posix.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,13 @@ import (
"golang.org/x/sys/unix"
)

const maxReadBytes = 1024

// PosixParser is a ConsoleParser implementation for POSIX environment.
type PosixParser struct {
// PosixReader is a Reader implementation for the POSIX environment.
type PosixReader struct {
fd int
}

// Setup should be called before starting input
func (t *PosixParser) Setup() error {
// Open should be called before starting input
func (t *PosixReader) Open() error {
in, err := syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
if os.IsNotExist(err) {
in = syscall.Stdin
Expand All @@ -37,8 +35,8 @@ func (t *PosixParser) Setup() error {
return nil
}

// TearDown should be called after stopping input
func (t *PosixParser) TearDown() error {
// Close should be called after stopping input
func (t *PosixReader) Close() error {
if err := syscall.Close(t.fd); err != nil {
return err
}
Expand All @@ -49,17 +47,12 @@ func (t *PosixParser) TearDown() error {
}

// Read returns byte array.
func (t *PosixParser) Read() ([]byte, error) {
buf := make([]byte, maxReadBytes)
n, err := syscall.Read(t.fd, buf)
if err != nil {
return []byte{}, err
}
return buf[:n], nil
func (t *PosixReader) Read(buff []byte) (int, error) {
return syscall.Read(t.fd, buff)
}

// GetWinSize returns WinSize object to represent width and height of terminal.
func (t *PosixParser) GetWinSize() *WinSize {
func (t *PosixReader) GetWinSize() *WinSize {
ws, err := unix.IoctlGetWinsize(t.fd, unix.TIOCGWINSZ)
if err != nil {
// If this errors, we simply return the default window size as
Expand All @@ -75,9 +68,9 @@ func (t *PosixParser) GetWinSize() *WinSize {
}
}

var _ ConsoleParser = &PosixParser{}
var _ Reader = &PosixReader{}

// NewStandardInputParser returns ConsoleParser object to read from stdin.
func NewStandardInputParser() *PosixParser {
return &PosixParser{}
// NewStdinReader returns Reader object to read from stdin.
func NewStdinReader() *PosixReader {
return &PosixReader{}
}
File renamed without changes.
Loading

0 comments on commit 27e9a8c

Please sign in to comment.