forked from mirror/readline
add new interface and fixed crash if stdout isn't a tty
This commit is contained in:
parent
6368045a0b
commit
15e7be4ac2
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
21
readline.go
21
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
|
||||
|
|
31
runebuf.go
31
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
|
||||
|
|
|
@ -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
33
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()
|
||||
}
|
||||
|
|
4
utils.go
4
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++
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue