Use persistent history in the editor

This resolves issue #45.
This commit is contained in:
Cheer Xiao 2015-02-26 00:40:35 +01:00
parent d804f82350
commit f407b94d09
3 changed files with 96 additions and 15 deletions

View File

@ -10,6 +10,7 @@ import (
"github.com/elves/elvish/edit/tty" "github.com/elves/elvish/edit/tty"
"github.com/elves/elvish/parse" "github.com/elves/elvish/parse"
"github.com/elves/elvish/store"
) )
const lackEOL = "\033[7m\u23ce\033[m\n" const lackEOL = "\033[7m\u23ce\033[m\n"
@ -51,6 +52,8 @@ type Editor struct {
reader *Reader reader *Reader
sigs <-chan os.Signal sigs <-chan os.Signal
histories []string histories []string
store *store.Store
cmdSeq int
editorState editorState
} }
@ -62,39 +65,102 @@ type LineRead struct {
Err error Err error
} }
func (h *history) jump(i int, line string) {
h.current = i
h.line = line
}
func (ed *Editor) appendHistory(line string) { func (ed *Editor) appendHistory(line string) {
ed.histories = append(ed.histories, line) ed.histories = append(ed.histories, line)
if ed.store != nil {
ed.store.AddCmd(line)
// TODO(xiaq): Report possible error
}
}
func lastHistory(histories []string, upto int, prefix string) (int, string) {
for i := upto - 1; i >= 0; i-- {
if strings.HasPrefix(histories[i], prefix) {
return i, histories[i]
}
}
return -1, ""
}
func firstHistory(histories []string, from int, prefix string) (int, string) {
for i := from; i < len(histories); i++ {
if strings.HasPrefix(histories[i], prefix) {
return i, histories[i]
}
}
return -1, ""
} }
func (ed *Editor) prevHistory() bool { func (ed *Editor) prevHistory() bool {
for i := ed.history.current - 1; i >= 0; i-- { if ed.history.current > 0 {
if strings.HasPrefix(ed.histories[i], ed.history.prefix) { // Session history
ed.history.current = i i, line := lastHistory(ed.histories, ed.history.current, ed.history.prefix)
ed.history.line = ed.histories[i] if i >= 0 {
ed.history.jump(i, line)
return true return true
} }
} }
if ed.store != nil {
// Persistent history
upto := ed.cmdSeq + min(0, ed.history.current)
i, line, err := ed.store.LastCmd(upto, ed.history.prefix)
if err == nil {
ed.history.jump(i-ed.cmdSeq, line)
return true
}
}
// TODO(xiaq): Errors other than ErrNoMatchingCmd should be reported
return false return false
} }
func (ed *Editor) nextHistory() bool { func (ed *Editor) nextHistory() bool {
for i := ed.history.current + 1; i < len(ed.histories); i++ { if ed.store != nil {
if strings.HasPrefix(ed.histories[i], ed.history.prefix) { // Persistent history
ed.history.current = i if ed.history.current < -1 {
ed.history.line = ed.histories[i] from := ed.cmdSeq + ed.history.current + 1
return true i, line, err := ed.store.FirstCmd(from, ed.history.prefix)
if err == nil {
ed.history.jump(i-ed.cmdSeq, line)
return true
}
// TODO(xiaq): Errors other than ErrNoMatchingCmd should be reported
} }
} }
from := max(0, ed.history.current+1)
i, line := firstHistory(ed.histories, from, ed.history.prefix)
if i >= 0 {
ed.history.jump(i, line)
return true
}
return false return false
} }
// NewEditor creates an Editor. // NewEditor creates an Editor.
func NewEditor(file *os.File, sigs <-chan os.Signal) *Editor { func NewEditor(file *os.File, sigs <-chan os.Signal, st *store.Store) *Editor {
seq := -1
if st != nil {
var err error
seq, err = st.NextCmdSeq()
if err != nil {
// TODO(xiaq): Also report the error
seq = -1
}
}
return &Editor{ return &Editor{
file: file, file: file,
writer: newWriter(file), writer: newWriter(file),
reader: NewReader(file), reader: NewReader(file),
sigs: sigs, sigs: sigs,
store: st,
cmdSeq: seq,
} }
} }

15
edit/minmax.go Normal file
View File

@ -0,0 +1,15 @@
package edit
func min(a, b int) int {
if a <= b {
return a
}
return b
}
func max(a, b int) int {
if a >= b {
return a
}
return b
}

10
main.go
View File

@ -22,7 +22,7 @@ const (
outChanLeader = "▶ " outChanLeader = "▶ "
) )
func newEvaler() *eval.Evaler { func newEvalerAndStore() (*eval.Evaler, *store.Store) {
dataDir, err := store.EnsureDataDir() dataDir, err := store.EnsureDataDir()
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, "Warning: cannot create data dir ~/.elvish") fmt.Fprintln(os.Stderr, "Warning: cannot create data dir ~/.elvish")
@ -36,7 +36,7 @@ func newEvaler() *eval.Evaler {
} }
} }
return eval.NewEvaler(st, dataDir) return eval.NewEvaler(st, dataDir), st
} }
func printError(err error) { func printError(err error) {
@ -51,7 +51,7 @@ func printError(err error) {
// TODO(xiaq): Currently only the editor deals with signals. // TODO(xiaq): Currently only the editor deals with signals.
func interact() { func interact() {
ev := newEvaler() ev, st := newEvalerAndStore()
datadir, err := store.EnsureDataDir() datadir, err := store.EnsureDataDir()
printError(err) printError(err)
if err == nil { if err == nil {
@ -76,7 +76,7 @@ func interact() {
sigch := make(chan os.Signal, sigchSize) sigch := make(chan os.Signal, sigchSize)
ed := edit.NewEditor(os.Stdin, sigch) ed := edit.NewEditor(os.Stdin, sigch, st)
for { for {
cmdNum++ cmdNum++
@ -111,7 +111,7 @@ func interact() {
} }
func script(fname string) { func script(fname string) {
ev := newEvaler() ev, _ := newEvalerAndStore()
err := ev.Source(fname) err := ev.Source(fname)
printError(err) printError(err)
if err != nil { if err != nil {