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
}
}
colNum := getWidth() / (colWidth + o.candidateOff + 2)
colNum := getWidth(o.op.cfg.StdoutFd) / (colWidth + o.candidateOff + 2)
o.candidateColNum = colNum
buf := bytes.NewBuffer(nil)
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 {
op := &Operation{
t: t,
buf: NewRuneBuffer(t, cfg.Prompt, cfg.MaskRune),
buf: NewRuneBuffer(t, cfg.Prompt, cfg.MaskRune, cfg),
outchan: make(chan []rune),
errchan: make(chan error),
}
@ -392,11 +392,12 @@ func (op *Operation) SetConfig(cfg *Config) (*Config, error) {
op.cfg = cfg
op.SetPrompt(cfg.Prompt)
op.SetMaskRune(cfg.MaskRune)
op.buf.SetConfig(cfg)
if cfg.opHistory == nil {
op.SetHistoryPath(cfg.HistoryFile)
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

View File

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

View File

@ -21,6 +21,8 @@ type RuneBuffer struct {
mask rune
cleanInScreen bool
interactive bool
cfg *Config
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{
w: w,
mask: mask,
w: w,
mask: mask,
interactive: cfg.useInteractive(),
cfg: cfg,
}
rb.SetPrompt(prompt)
return rb
}
func (r *RuneBuffer) SetConfig(cfg *Config) {
r.cfg = cfg
r.interactive = cfg.useInteractive()
}
func (r *RuneBuffer) SetMask(m rune) {
r.mask = m
}
@ -276,7 +285,7 @@ func (r *RuneBuffer) MoveToLineEnd() {
}
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) {
@ -310,9 +319,9 @@ func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) {
func (r *RuneBuffer) IdxLine() int {
totalWidth := runes.WidthAll(r.buf[:r.idx]) + r.PromptLen()
w := getWidth()
if w == 0 {
return 0
w := getWidth(r.cfg.StdoutFd)
if w < 0 {
return -1
}
line := totalWidth / w
@ -332,6 +341,12 @@ func (r *RuneBuffer) CursorLineCount() int {
}
func (r *RuneBuffer) Refresh(f func()) {
if !r.interactive {
if f != nil {
f()
}
return
}
r.Clean()
if f != nil {
f()
@ -424,7 +439,7 @@ func (r *RuneBuffer) cleanOutput() []byte {
}
func (r *RuneBuffer) Clean() {
if r.cleanInScreen {
if r.cleanInScreen || !r.interactive {
return
}
r.cleanInScreen = true

View File

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

33
std.go
View File

@ -3,6 +3,7 @@ package readline
import (
"io"
"os"
"sync"
)
var (
@ -10,3 +11,35 @@ var (
Stdout io.WriteCloser = os.Stdout
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
func LineCount(w int) int {
screenWidth := getWidth()
func LineCount(stdoutFd int, w int) int {
screenWidth := getWidth(stdoutFd)
r := w / screenWidth
if w%screenWidth != 0 {
r++

View File

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

View File

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