diff --git a/edit/editor.go b/edit/editor.go index 9a855b3d..9031fc54 100644 --- a/edit/editor.go +++ b/edit/editor.go @@ -10,6 +10,7 @@ import ( "github.com/elves/elvish/edit/tty" "github.com/elves/elvish/parse" + "github.com/elves/elvish/store" ) const lackEOL = "\033[7m\u23ce\033[m\n" @@ -51,6 +52,8 @@ type Editor struct { reader *Reader sigs <-chan os.Signal histories []string + store *store.Store + cmdSeq int editorState } @@ -62,39 +65,102 @@ type LineRead struct { Err error } +func (h *history) jump(i int, line string) { + h.current = i + h.line = line +} + func (ed *Editor) appendHistory(line string) { 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 { - for i := ed.history.current - 1; i >= 0; i-- { - if strings.HasPrefix(ed.histories[i], ed.history.prefix) { - ed.history.current = i - ed.history.line = ed.histories[i] + if ed.history.current > 0 { + // Session history + i, line := lastHistory(ed.histories, ed.history.current, ed.history.prefix) + if i >= 0 { + ed.history.jump(i, line) 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 } func (ed *Editor) nextHistory() bool { - for i := ed.history.current + 1; i < len(ed.histories); i++ { - if strings.HasPrefix(ed.histories[i], ed.history.prefix) { - ed.history.current = i - ed.history.line = ed.histories[i] - return true + if ed.store != nil { + // Persistent history + if ed.history.current < -1 { + from := ed.cmdSeq + ed.history.current + 1 + 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 } // 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{ file: file, writer: newWriter(file), reader: NewReader(file), sigs: sigs, + store: st, + cmdSeq: seq, } } diff --git a/edit/minmax.go b/edit/minmax.go new file mode 100644 index 00000000..a5f919d6 --- /dev/null +++ b/edit/minmax.go @@ -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 +} diff --git a/main.go b/main.go index db6e2b86..32298392 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ const ( outChanLeader = "▶ " ) -func newEvaler() *eval.Evaler { +func newEvalerAndStore() (*eval.Evaler, *store.Store) { dataDir, err := store.EnsureDataDir() if err != nil { 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) { @@ -51,7 +51,7 @@ func printError(err error) { // TODO(xiaq): Currently only the editor deals with signals. func interact() { - ev := newEvaler() + ev, st := newEvalerAndStore() datadir, err := store.EnsureDataDir() printError(err) if err == nil { @@ -76,7 +76,7 @@ func interact() { sigch := make(chan os.Signal, sigchSize) - ed := edit.NewEditor(os.Stdin, sigch) + ed := edit.NewEditor(os.Stdin, sigch, st) for { cmdNum++ @@ -111,7 +111,7 @@ func interact() { } func script(fname string) { - ev := newEvaler() + ev, _ := newEvalerAndStore() err := ev.Source(fname) printError(err) if err != nil {