refactory

This commit is contained in:
Cheney 2016-03-05 10:46:11 +08:00
parent e9e6c35b06
commit 867002449c
9 changed files with 42 additions and 37 deletions

View File

@ -183,7 +183,7 @@ func (o *opCompleter) CompleteRefresh() {
colWidth = w colWidth = w
} }
} }
colNum := getWidth(o.op.cfg.StdoutFd) / (colWidth + o.candidateOff + 2) colNum := o.op.cfg.FuncGetWidth() / (colWidth + o.candidateOff + 2)
o.candidateColNum = colNum o.candidateColNum = colNum
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
buf.Write(bytes.Repeat([]byte("\n"), lineCnt)) buf.Write(bytes.Repeat([]byte("\n"), lineCnt))

View File

@ -2,10 +2,7 @@ package readline
import ( import (
"errors" "errors"
"fmt"
"io" "io"
"golang.org/x/crypto/ssh/terminal"
) )
var ( var (
@ -58,7 +55,7 @@ func (w *wrapWriter) Write(b []byte) (int, error) {
func NewOperation(t *Terminal, cfg *Config) *Operation { func NewOperation(t *Terminal, cfg *Config) *Operation {
op := &Operation{ op := &Operation{
t: t, t: t,
buf: NewRuneBuffer(t, cfg.Prompt, cfg.MaskRune, cfg), buf: NewRuneBuffer(t, cfg.Prompt, cfg),
outchan: make(chan []rune), outchan: make(chan []rune),
errchan: make(chan error), errchan: make(chan error),
} }
@ -341,16 +338,7 @@ func (o *Operation) PasswordWithConfig(cfg *Config) ([]byte, error) {
} }
func (o *Operation) Password(prompt string) ([]byte, error) { func (o *Operation) Password(prompt string) ([]byte, error) {
w := o.Stdout() return o.PasswordEx(prompt, nil)
if prompt != "" {
fmt.Fprintf(w, prompt)
}
o.t.EnterRawMode()
defer o.t.ExitRawMode()
b, err := terminal.ReadPassword(int(o.cfg.Stdin.Fd()))
fmt.Fprint(w, "\r\n")
return b, err
} }
func (o *Operation) SetTitle(t string) { func (o *Operation) SetTitle(t string) {

View File

@ -21,7 +21,7 @@ func (o *opPassword) EnterPasswordMode(cfg *Config) (err error) {
func (o *opPassword) PasswordConfig() *Config { func (o *opPassword) PasswordConfig() *Config {
return &Config{ return &Config{
MaskRune: '*', EnableMask: true,
InterruptPrompt: "\n", InterruptPrompt: "\n",
EOFPrompt: "\n", EOFPrompt: "\n",
HistoryLimit: -1, HistoryLimit: -1,

View File

@ -11,11 +11,6 @@ type Instance struct {
Operation *Operation Operation *Operation
} }
type FdReader interface {
io.Reader
Fd() uintptr
}
type Config struct { type Config struct {
// prompt supports ANSI escape sequence, so we can color some characters even in windows // prompt supports ANSI escape sequence, so we can color some characters even in windows
Prompt string Prompt string
@ -39,12 +34,17 @@ type Config struct {
InterruptPrompt string InterruptPrompt string
EOFPrompt string EOFPrompt string
Stdin FdReader FuncGetWidth func() int
Stdin io.Reader
Stdout io.Writer Stdout io.Writer
Stderr io.Writer Stderr io.Writer
MaskRune rune EnableMask bool
MaskRune rune
// erase the editing line after user submited it
// it often use in IM.
UniqueEditLine bool UniqueEditLine bool
// force use interactive even stdout is not a tty // force use interactive even stdout is not a tty
@ -100,6 +100,10 @@ func (c *Config) Init() error {
c.EOFPrompt = "" c.EOFPrompt = ""
} }
if c.FuncGetWidth == nil {
c.FuncGetWidth = genGetWidthFunc(c.StdoutFd)
}
return nil return nil
} }

View File

@ -18,7 +18,6 @@ type RuneBuffer struct {
idx int idx int
prompt []rune prompt []rune
w io.Writer w io.Writer
mask rune
cleanInScreen bool cleanInScreen bool
interactive bool interactive bool
@ -41,10 +40,9 @@ func (r *RuneBuffer) Restore() {
}) })
} }
func NewRuneBuffer(w io.Writer, prompt string, mask rune, cfg *Config) *RuneBuffer { func NewRuneBuffer(w io.Writer, prompt string, cfg *Config) *RuneBuffer {
rb := &RuneBuffer{ rb := &RuneBuffer{
w: w, w: w,
mask: mask,
interactive: cfg.useInteractive(), interactive: cfg.useInteractive(),
cfg: cfg, cfg: cfg,
} }
@ -58,7 +56,7 @@ func (r *RuneBuffer) SetConfig(cfg *Config) {
} }
func (r *RuneBuffer) SetMask(m rune) { func (r *RuneBuffer) SetMask(m rune) {
r.mask = m r.cfg.MaskRune = m
} }
func (r *RuneBuffer) CurrentWidth(x int) int { func (r *RuneBuffer) CurrentWidth(x int) int {
@ -295,7 +293,7 @@ func (r *RuneBuffer) MoveToLineEnd() {
} }
func (r *RuneBuffer) LineCount() int { func (r *RuneBuffer) LineCount() int {
return LineCount(r.cfg.StdoutFd, runes.WidthAll(r.buf)+r.PromptLen()) return LineCount(r.cfg.FuncGetWidth, runes.WidthAll(r.buf)+r.PromptLen())
} }
func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) { func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) {
@ -329,7 +327,7 @@ func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) {
func (r *RuneBuffer) IdxLine() int { func (r *RuneBuffer) IdxLine() int {
totalWidth := runes.WidthAll(r.buf[:r.idx]) + r.PromptLen() totalWidth := runes.WidthAll(r.buf[:r.idx]) + r.PromptLen()
w := getWidth(r.cfg.StdoutFd) w := r.cfg.FuncGetWidth()
if w <= 0 { if w <= 0 {
return -1 return -1
} }
@ -368,12 +366,12 @@ func (r *RuneBuffer) Refresh(f func()) {
func (r *RuneBuffer) output() []byte { func (r *RuneBuffer) output() []byte {
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
buf.WriteString(string(r.prompt)) buf.WriteString(string(r.prompt))
if r.mask != 0 && len(r.buf) > 0 { if r.cfg.EnableMask && len(r.buf) > 0 {
buf.Write([]byte(strings.Repeat(string(r.mask), len(r.buf)-1))) buf.Write([]byte(strings.Repeat(string(r.cfg.MaskRune), len(r.buf)-1)))
if r.buf[len(r.buf)-1] == '\n' { if r.buf[len(r.buf)-1] == '\n' {
buf.Write([]byte{'\n'}) buf.Write([]byte{'\n'})
} else { } else {
buf.Write([]byte(string(r.mask))) buf.Write([]byte(string(r.cfg.MaskRune)))
} }
} else { } else {
buf.Write([]byte(string(r.buf))) buf.Write([]byte(string(r.buf)))

View File

@ -125,7 +125,7 @@ func (o *opSearch) SearchRefresh(x int) {
} }
x = o.buf.CurrentWidth(x) x = o.buf.CurrentWidth(x)
x += o.buf.PromptLen() x += o.buf.PromptLen()
x = x % getWidth(o.cfg.StdoutFd) x = x % o.cfg.FuncGetWidth()
if o.markStart > 0 { if o.markStart > 0 {
o.buf.SetStyle(o.markStart, o.markEnd, "4") o.buf.SetStyle(o.markStart, o.markEnd, "4")

9
std.go
View File

@ -17,6 +17,7 @@ var (
stdOnce sync.Once stdOnce sync.Once
) )
// global instance will not submit history automatic
func getInstance() *Instance { func getInstance() *Instance {
stdOnce.Do(func() { stdOnce.Do(func() {
std, _ = NewEx(&Config{ std, _ = NewEx(&Config{
@ -26,6 +27,10 @@ func getInstance() *Instance {
return std return std
} }
// let readline load history from filepath
// and try to persist history into disk
// set fp to "" to prevent readline persisting history to disk
// so the `AddHistory` will return nil error forever.
func SetHistoryPath(fp string) { func SetHistoryPath(fp string) {
ins := getInstance() ins := getInstance()
cfg := ins.Config.Clone() cfg := ins.Config.Clone()
@ -33,6 +38,7 @@ func SetHistoryPath(fp string) {
ins.SetConfig(cfg) ins.SetConfig(cfg)
} }
// set auto completer to global instance
func SetAutoComplete(completer AutoCompleter) { func SetAutoComplete(completer AutoCompleter) {
ins := getInstance() ins := getInstance()
cfg := ins.Config.Clone() cfg := ins.Config.Clone()
@ -40,11 +46,14 @@ func SetAutoComplete(completer AutoCompleter) {
ins.SetConfig(cfg) ins.SetConfig(cfg)
} }
// add history to global instance manually
// raise error only if `SetHistoryPath` is set with a non-empty path
func AddHistory(content string) error { func AddHistory(content string) error {
ins := getInstance() ins := getInstance()
return ins.SaveHistory(content) return ins.SaveHistory(content)
} }
// readline with global configs
func Line(prompt string) (string, error) { func Line(prompt string) (string, error) {
ins := getInstance() ins := getInstance()
ins.SetPrompt(prompt) ins.SetPrompt(prompt)

View File

@ -36,7 +36,7 @@ func NewTerminal(cfg *Config) (*Terminal, error) {
} }
func (t *Terminal) EnterRawMode() (err error) { func (t *Terminal) EnterRawMode() (err error) {
t.state, err = MakeRaw(int(t.cfg.Stdin.Fd())) t.state, err = MakeRaw(int(t.cfg.StdinFd))
return err return err
} }
@ -44,7 +44,7 @@ func (t *Terminal) ExitRawMode() (err error) {
if t.state == nil { if t.state == nil {
return return
} }
err = Restore(int(t.cfg.Stdin.Fd()), t.state) err = Restore(int(t.cfg.StdinFd), t.state)
if err == nil { if err == nil {
t.state = nil t.state = nil
} }

View File

@ -87,8 +87,8 @@ func escapeKey(r rune) rune {
} }
// calculate how many lines for N character // calculate how many lines for N character
func LineCount(stdoutFd int, w int) int { func LineCount(getWidth func() int, w int) int {
screenWidth := getWidth(stdoutFd) screenWidth := getWidth()
r := w / screenWidth r := w / screenWidth
if w%screenWidth != 0 { if w%screenWidth != 0 {
r++ r++
@ -116,3 +116,9 @@ func GetInt(s []string, def int) int {
} }
return c return c
} }
func genGetWidthFunc(fd int) func() int {
return func() int {
return getWidth(fd)
}
}