mirror of https://github.com/chzyer/readline.git
add history limitation
This commit is contained in:
parent
4dc2ce7141
commit
bfa8c1dfdb
58
history.go
58
history.go
|
@ -19,40 +19,81 @@ func (h *hisItem) Clean() {
|
||||||
}
|
}
|
||||||
|
|
||||||
type opHistory struct {
|
type opHistory struct {
|
||||||
path string
|
cfg *Config
|
||||||
history *list.List
|
history *list.List
|
||||||
historyVer int64
|
historyVer int64
|
||||||
current *list.Element
|
current *list.Element
|
||||||
fd *os.File
|
fd *os.File
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOpHistory(path string) (o *opHistory) {
|
func newOpHistory(cfg *Config) (o *opHistory) {
|
||||||
o = &opHistory{
|
o = &opHistory{
|
||||||
path: path,
|
cfg: cfg,
|
||||||
history: list.New(),
|
history: list.New(),
|
||||||
}
|
}
|
||||||
if o.path == "" {
|
if o.cfg.HistoryFile != "" {
|
||||||
return
|
o.historyUpdatePath(o.cfg.HistoryFile)
|
||||||
}
|
}
|
||||||
f, err := os.OpenFile(o.path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// only called by newOpHistory
|
||||||
|
func (o *opHistory) historyUpdatePath(path string) {
|
||||||
|
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
o.fd = f
|
o.fd = f
|
||||||
r := bufio.NewReader(o.fd)
|
r := bufio.NewReader(o.fd)
|
||||||
for {
|
total := 0
|
||||||
|
for ; ; total++ {
|
||||||
line, err := r.ReadSlice('\n')
|
line, err := r.ReadSlice('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
o.PushHistory([]rune(strings.TrimSpace(string(line))))
|
o.PushHistory([]rune(strings.TrimSpace(string(line))))
|
||||||
|
o.CompactHistory()
|
||||||
|
}
|
||||||
|
if total > o.cfg.HistoryLimit {
|
||||||
|
o.HistoryRewrite()
|
||||||
}
|
}
|
||||||
o.historyVer++
|
o.historyVer++
|
||||||
o.PushHistory(nil)
|
o.PushHistory(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *opHistory) Close() {
|
func (o *opHistory) CompactHistory() {
|
||||||
|
for o.history.Len() > o.cfg.HistoryLimit {
|
||||||
|
o.history.Remove(o.history.Front())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opHistory) HistoryRewrite() {
|
||||||
|
if o.cfg.HistoryFile == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpFile := o.cfg.HistoryFile + ".tmp"
|
||||||
|
fd, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
buf := bufio.NewWriter(fd)
|
||||||
|
for elem := o.history.Front(); elem != nil; elem = elem.Next() {
|
||||||
|
buf.WriteString(string(elem.Value.(*hisItem).Source))
|
||||||
|
}
|
||||||
|
buf.Flush()
|
||||||
|
|
||||||
|
if o.fd != nil {
|
||||||
|
o.fd.Close()
|
||||||
|
}
|
||||||
|
// fd is write only, just satisfy what we need.
|
||||||
|
o.fd = fd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opHistory) CloseHistory() {
|
||||||
if o.fd != nil {
|
if o.fd != nil {
|
||||||
o.fd.Close()
|
o.fd.Close()
|
||||||
}
|
}
|
||||||
|
@ -197,7 +238,6 @@ func (o *opHistory) UpdateHistory(s []rune, commit bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *opHistory) PushHistory(s []rune) {
|
func (o *opHistory) PushHistory(s []rune) {
|
||||||
// copy
|
|
||||||
newCopy := make([]rune, len(s))
|
newCopy := make([]rune, len(s))
|
||||||
copy(newCopy, s)
|
copy(newCopy, s)
|
||||||
elem := o.history.PushBack(&hisItem{Source: newCopy})
|
elem := o.history.PushBack(&hisItem{Source: newCopy})
|
||||||
|
|
20
operation.go
20
operation.go
|
@ -51,12 +51,12 @@ 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{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
t: t,
|
t: t,
|
||||||
buf: NewRuneBuffer(t, cfg.Prompt),
|
buf: NewRuneBuffer(t, cfg.Prompt),
|
||||||
outchan: make(chan []rune),
|
outchan: make(chan []rune),
|
||||||
opHistory: newOpHistory(cfg.HistoryFile),
|
|
||||||
}
|
}
|
||||||
|
op.SetHistoryPath(cfg.HistoryFile)
|
||||||
op.opVim = newVimMode(op)
|
op.opVim = newVimMode(op)
|
||||||
op.w = op.buf.w
|
op.w = op.buf.w
|
||||||
op.opSearch = newOpSearch(op.buf.w, op.buf, op.opHistory)
|
op.opSearch = newOpSearch(op.buf.w, op.buf, op.opHistory)
|
||||||
|
@ -286,5 +286,13 @@ func (o *Operation) Slice() ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Operation) Close() {
|
func (o *Operation) Close() {
|
||||||
o.opHistory.Close()
|
o.opHistory.CloseHistory()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Operation) SetHistoryPath(path string) {
|
||||||
|
if o.opHistory != nil {
|
||||||
|
o.opHistory.CloseHistory()
|
||||||
|
}
|
||||||
|
o.cfg.HistoryFile = path
|
||||||
|
o.opHistory = newOpHistory(o.cfg)
|
||||||
}
|
}
|
||||||
|
|
35
readline.go
35
readline.go
|
@ -3,17 +3,28 @@ package readline
|
||||||
import "io"
|
import "io"
|
||||||
|
|
||||||
type Instance struct {
|
type Instance struct {
|
||||||
|
Config *Config
|
||||||
Terminal *Terminal
|
Terminal *Terminal
|
||||||
Operation *Operation
|
Operation *Operation
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Prompt string
|
// prompt supports ANSI escape sequence, so we can color some characters even in windows
|
||||||
HistoryFile string
|
Prompt string
|
||||||
|
|
||||||
|
// readline will persist historys to file where HistoryFile specified
|
||||||
|
HistoryFile string
|
||||||
|
// specify the max length of historys, it's 500 by default
|
||||||
|
HistoryLimit int
|
||||||
|
|
||||||
|
// AutoCompleter will called once user press TAB
|
||||||
AutoComplete AutoCompleter
|
AutoComplete AutoCompleter
|
||||||
VimMode bool
|
|
||||||
Stdout io.Writer
|
// If VimMode is true, readline will in vim.insert mode by default
|
||||||
Stderr io.Writer
|
VimMode bool
|
||||||
|
|
||||||
|
Stdout io.Writer
|
||||||
|
Stderr io.Writer
|
||||||
|
|
||||||
inited bool
|
inited bool
|
||||||
}
|
}
|
||||||
|
@ -29,6 +40,9 @@ func (c *Config) Init() error {
|
||||||
if c.Stderr == nil {
|
if c.Stderr == nil {
|
||||||
c.Stderr = Stderr
|
c.Stderr = Stderr
|
||||||
}
|
}
|
||||||
|
if c.HistoryLimit < 0 {
|
||||||
|
c.HistoryLimit = 500
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +53,7 @@ func NewEx(cfg *Config) (*Instance, error) {
|
||||||
}
|
}
|
||||||
rl := t.Readline()
|
rl := t.Readline()
|
||||||
return &Instance{
|
return &Instance{
|
||||||
|
Config: cfg,
|
||||||
Terminal: t,
|
Terminal: t,
|
||||||
Operation: rl,
|
Operation: rl,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -52,14 +67,22 @@ func (i *Instance) SetPrompt(s string) {
|
||||||
i.Operation.SetPrompt(s)
|
i.Operation.SetPrompt(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// change hisotry persistence in runtime
|
||||||
|
func (i *Instance) SetHistoryPath(p string) {
|
||||||
|
i.Operation.SetHistoryPath(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readline will refresh automatic when write through Stdout()
|
||||||
func (i *Instance) Stdout() io.Writer {
|
func (i *Instance) Stdout() io.Writer {
|
||||||
return i.Operation.Stdout()
|
return i.Operation.Stdout()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readline will refresh automatic when write through Stdout()
|
||||||
func (i *Instance) Stderr() io.Writer {
|
func (i *Instance) Stderr() io.Writer {
|
||||||
return i.Operation.Stderr()
|
return i.Operation.Stderr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// switch VimMode in runtime
|
||||||
func (i *Instance) SetVimMode(on bool) {
|
func (i *Instance) SetVimMode(on bool) {
|
||||||
i.Operation.SetVimMode(on)
|
i.Operation.SetVimMode(on)
|
||||||
}
|
}
|
||||||
|
@ -76,10 +99,12 @@ func (i *Instance) Readline() (string, error) {
|
||||||
return i.Operation.String()
|
return i.Operation.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// same as readline
|
||||||
func (i *Instance) ReadSlice() ([]byte, error) {
|
func (i *Instance) ReadSlice() ([]byte, error) {
|
||||||
return i.Operation.Slice()
|
return i.Operation.Slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we must make sure that call Close() before process exit.
|
||||||
func (i *Instance) Close() error {
|
func (i *Instance) Close() error {
|
||||||
if err := i.Terminal.Close(); err != nil {
|
if err := i.Terminal.Close(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
Loading…
Reference in New Issue