diff --git a/complete.go b/complete.go index 8ee823d..6d5bc16 100644 --- a/complete.go +++ b/complete.go @@ -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)) diff --git a/operation.go b/operation.go index ffb3fe3..5842480 100644 --- a/operation.go +++ b/operation.go @@ -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 diff --git a/readline.go b/readline.go index 4069b03..04ddf13 100644 --- a/readline.go +++ b/readline.go @@ -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 diff --git a/runebuf.go b/runebuf.go index 358f3bf..e4bcc74 100644 --- a/runebuf.go +++ b/runebuf.go @@ -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 diff --git a/search.go b/search.go index 53d9e50..1b82ae4 100644 --- a/search.go +++ b/search.go @@ -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") diff --git a/std.go b/std.go index 3baf5a7..200420b 100644 --- a/std.go +++ b/std.go @@ -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() +} diff --git a/utils.go b/utils.go index 386bcba..6be2e38 100644 --- a/utils.go +++ b/utils.go @@ -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++ diff --git a/utils_unix.go b/utils_unix.go index 7a40cff..e862507 100644 --- a/utils_unix.go +++ b/utils_unix.go @@ -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) } diff --git a/utils_windows.go b/utils_windows.go index 4cee4b6..016bb05 100644 --- a/utils_windows.go +++ b/utils_windows.go @@ -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) }