mirror of https://github.com/chzyer/readline.git
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
|
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))
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
21
readline.go
21
readline.go
|
@ -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
|
||||||
|
|
31
runebuf.go
31
runebuf.go
|
@ -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
|
||||||
|
|
|
@ -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
33
std.go
|
@ -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()
|
||||||
|
}
|
||||||
|
|
4
utils.go
4
utils.go
|
@ -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++
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue