add new interface and fixed crash if stdout isn't a tty

This commit is contained in:
Cheney 2016-02-18 11:25:41 +08:00
parent 6368045a0b
commit 15e7be4ac2
9 changed files with 90 additions and 22 deletions

View File

@ -183,7 +183,7 @@ func (o *opCompleter) CompleteRefresh() {
colWidth = w colWidth = w
} }
} }
colNum := getWidth() / (colWidth + o.candidateOff + 2) colNum := getWidth(o.op.cfg.StdoutFd) / (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

@ -58,7 +58,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), buf: NewRuneBuffer(t, cfg.Prompt, cfg.MaskRune, cfg),
outchan: make(chan []rune), outchan: make(chan []rune),
errchan: make(chan error), errchan: make(chan error),
} }
@ -392,11 +392,12 @@ func (op *Operation) SetConfig(cfg *Config) (*Config, error) {
op.cfg = cfg op.cfg = cfg
op.SetPrompt(cfg.Prompt) op.SetPrompt(cfg.Prompt)
op.SetMaskRune(cfg.MaskRune) op.SetMaskRune(cfg.MaskRune)
op.buf.SetConfig(cfg)
if cfg.opHistory == nil { if cfg.opHistory == nil {
op.SetHistoryPath(cfg.HistoryFile) op.SetHistoryPath(cfg.HistoryFile)
cfg.opHistory = op.history cfg.opHistory = op.history
cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history) cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history, cfg)
} }
op.history = cfg.opHistory op.history = cfg.opHistory

View File

@ -47,12 +47,20 @@ type Config struct {
UniqueEditLine bool UniqueEditLine bool
// force use interactive even stdout is not a tty
StdoutFd int
ForceUseInteractive bool
// private fields // private fields
inited bool inited bool
opHistory *opHistory opHistory *opHistory
opSearch *opSearch opSearch *opSearch
} }
func (c *Config) useInteractive() bool {
return c.ForceUseInteractive || IsTerminal(c.StdoutFd)
}
func (c *Config) Init() error { func (c *Config) Init() error {
if c.inited { if c.inited {
return nil return nil
@ -67,6 +75,9 @@ func (c *Config) Init() error {
if c.Stderr == nil { if c.Stderr == nil {
c.Stderr = Stderr c.Stderr = Stderr
} }
if c.StdoutFd == 0 {
c.StdoutFd = StdoutFd
}
if c.HistoryLimit == 0 { if c.HistoryLimit == 0 {
c.HistoryLimit = 500 c.HistoryLimit = 500
} }
@ -85,6 +96,12 @@ func (c *Config) Init() error {
return nil return nil
} }
func (c Config) Clone() *Config {
c.opHistory = nil
c.opSearch = nil
return &c
}
func (c *Config) SetListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) { func (c *Config) SetListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) {
c.Listener = FuncListener(f) c.Listener = FuncListener(f)
} }
@ -160,8 +177,8 @@ func (i *Instance) Readline() (string, error) {
return i.Operation.String() return i.Operation.String()
} }
func (i *Instance) SaveHistory(content string) { func (i *Instance) SaveHistory(content string) error {
i.Operation.SaveHistory(content) return i.Operation.SaveHistory(content)
} }
// same as readline // same as readline

View File

@ -21,6 +21,8 @@ type RuneBuffer struct {
mask rune mask rune
cleanInScreen bool cleanInScreen bool
interactive bool
cfg *Config
bck *runeBufferBck bck *runeBufferBck
} }
@ -39,15 +41,22 @@ func (r *RuneBuffer) Restore() {
}) })
} }
func NewRuneBuffer(w io.Writer, prompt string, mask rune) *RuneBuffer { func NewRuneBuffer(w io.Writer, prompt string, mask rune, cfg *Config) *RuneBuffer {
rb := &RuneBuffer{ rb := &RuneBuffer{
w: w, w: w,
mask: mask, mask: mask,
interactive: cfg.useInteractive(),
cfg: cfg,
} }
rb.SetPrompt(prompt) rb.SetPrompt(prompt)
return rb return rb
} }
func (r *RuneBuffer) SetConfig(cfg *Config) {
r.cfg = cfg
r.interactive = cfg.useInteractive()
}
func (r *RuneBuffer) SetMask(m rune) { func (r *RuneBuffer) SetMask(m rune) {
r.mask = m r.mask = m
} }
@ -276,7 +285,7 @@ func (r *RuneBuffer) MoveToLineEnd() {
} }
func (r *RuneBuffer) LineCount() int { func (r *RuneBuffer) LineCount() int {
return LineCount(runes.WidthAll(r.buf) + r.PromptLen()) return LineCount(r.cfg.StdoutFd, 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) {
@ -310,9 +319,9 @@ 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() w := getWidth(r.cfg.StdoutFd)
if w == 0 { if w < 0 {
return 0 return -1
} }
line := totalWidth / w line := totalWidth / w
@ -332,6 +341,12 @@ func (r *RuneBuffer) CursorLineCount() int {
} }
func (r *RuneBuffer) Refresh(f func()) { func (r *RuneBuffer) Refresh(f func()) {
if !r.interactive {
if f != nil {
f()
}
return
}
r.Clean() r.Clean()
if f != nil { if f != nil {
f() f()
@ -424,7 +439,7 @@ func (r *RuneBuffer) cleanOutput() []byte {
} }
func (r *RuneBuffer) Clean() { func (r *RuneBuffer) Clean() {
if r.cleanInScreen { if r.cleanInScreen || !r.interactive {
return return
} }
r.cleanInScreen = true r.cleanInScreen = true

View File

@ -26,11 +26,12 @@ type opSearch struct {
buf *RuneBuffer buf *RuneBuffer
data []rune data []rune
history *opHistory history *opHistory
cfg *Config
markStart int markStart int
markEnd int markEnd int
} }
func newOpSearch(w io.Writer, buf *RuneBuffer, history *opHistory) *opSearch { func newOpSearch(w io.Writer, buf *RuneBuffer, history *opHistory, cfg *Config) *opSearch {
return &opSearch{ return &opSearch{
w: w, w: w,
buf: buf, buf: buf,
@ -123,7 +124,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() x = x % getWidth(o.cfg.StdoutFd)
if o.markStart > 0 { if o.markStart > 0 {
o.buf.SetStyle(o.markStart, o.markEnd, "4") o.buf.SetStyle(o.markStart, o.markEnd, "4")

33
std.go
View File

@ -3,6 +3,7 @@ package readline
import ( import (
"io" "io"
"os" "os"
"sync"
) )
var ( var (
@ -10,3 +11,35 @@ var (
Stdout io.WriteCloser = os.Stdout Stdout io.WriteCloser = os.Stdout
Stderr io.WriteCloser = os.Stderr Stderr io.WriteCloser = os.Stderr
) )
var (
std *Instance
stdOnce sync.Once
)
func getInstance() *Instance {
stdOnce.Do(func() {
std, _ = NewEx(&Config{
DisableAutoSaveHistory: true,
})
})
return std
}
func SetHistoryPath(fp string) {
ins := getInstance()
cfg := ins.Config.Clone()
cfg.HistoryFile = fp
ins.SetConfig(cfg)
}
func AddHistory(content string) error {
ins := getInstance()
return ins.SaveHistory(content)
}
func Line(prompt string) (string, error) {
ins := getInstance()
ins.SetPrompt(prompt)
return ins.Readline()
}

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(w int) int { func LineCount(stdoutFd int, w int) int {
screenWidth := getWidth() screenWidth := getWidth(stdoutFd)
r := w / screenWidth r := w / screenWidth
if w%screenWidth != 0 { if w%screenWidth != 0 {
r++ r++

View File

@ -15,15 +15,16 @@ type winsize struct {
} }
// get width of the terminal // get width of the terminal
func getWidth() int { func getWidth(stdoutFd int) int {
ws := &winsize{} ws := &winsize{}
retCode, _, errno := syscall.Syscall(syscall.SYS_IOCTL, retCode, _, errno := syscall.Syscall(syscall.SYS_IOCTL,
uintptr(StdoutFd), uintptr(stdoutFd),
uintptr(syscall.TIOCGWINSZ), uintptr(syscall.TIOCGWINSZ),
uintptr(unsafe.Pointer(ws))) uintptr(unsafe.Pointer(ws)))
if int(retCode) == -1 { if int(retCode) == -1 {
panic(errno) _ = errno
return -1
} }
return int(ws.Col) return int(ws.Col)
} }

View File

@ -7,10 +7,10 @@ func init() {
} }
// get width of the terminal // get width of the terminal
func getWidth() int { func getWidth(fd int) int {
info, _ := GetConsoleScreenBufferInfo() info, _ := GetConsoleScreenBufferInfo()
if info == nil { if info == nil {
return 0 return -1
} }
return int(info.dwSize.x) return int(info.dwSize.x)
} }