mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-13 09:57:51 +08:00
135 lines
3.4 KiB
Go
135 lines
3.4 KiB
Go
package edit
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"src.elv.sh/pkg/cli"
|
|
"src.elv.sh/pkg/cli/histutil"
|
|
"src.elv.sh/pkg/cli/tk"
|
|
"src.elv.sh/pkg/eval"
|
|
"src.elv.sh/pkg/eval/vals"
|
|
"src.elv.sh/pkg/parse/parseutil"
|
|
"src.elv.sh/pkg/store/storedefs"
|
|
)
|
|
|
|
var errStoreOffline = errors.New("store offline")
|
|
|
|
//elvdoc:fn command-history
|
|
//
|
|
// ```elvish
|
|
// edit:command-history &cmd-only=$false &dedup=$false &newest-first
|
|
// ```
|
|
//
|
|
// Outputs the command history.
|
|
//
|
|
// By default, each entry is represented as a map, with an `id` key key for the
|
|
// sequence number of the command, and a `cmd` key for the text of the command.
|
|
// If `&cmd-only` is `$true`, only the text of each command is output.
|
|
//
|
|
// All entries are output by default. If `&dedup` is `$true`, only the most
|
|
// recent instance of each command (when comparing just the `cmd` key) is
|
|
// output.
|
|
//
|
|
// Commands are are output in oldest to newest order by default. If
|
|
// `&newest-first` is `$true` the output is in newest to oldest order instead.
|
|
//
|
|
// As an example, either of the following extracts the text of the most recent
|
|
// command:
|
|
//
|
|
// ```elvish
|
|
// edit:command-history | put [(all)][-1][cmd]
|
|
// edit:command-history &cmd-only &newest-first | take 1
|
|
// ```
|
|
|
|
type cmdhistOpt struct{ CmdOnly, Dedup, NewestFirst bool }
|
|
|
|
func (o *cmdhistOpt) SetDefaultOptions() {}
|
|
|
|
func commandHistory(opts cmdhistOpt, fuser histutil.Store, out eval.ValueOutput) error {
|
|
if fuser == nil {
|
|
return errStoreOffline
|
|
}
|
|
cmds, err := fuser.AllCmds()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if opts.Dedup {
|
|
cmds = dedupCmds(cmds, opts.NewestFirst)
|
|
} else if opts.NewestFirst {
|
|
reverseCmds(cmds)
|
|
}
|
|
if opts.CmdOnly {
|
|
for _, cmd := range cmds {
|
|
err := out.Put(cmd.Text)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else {
|
|
for _, cmd := range cmds {
|
|
err := out.Put(vals.MakeMap("id", cmd.Seq, "cmd", cmd.Text))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func dedupCmds(allCmds []storedefs.Cmd, newestFirst bool) []storedefs.Cmd {
|
|
// Capacity allocation below is based on some personal empirical observation.
|
|
uniqCmds := make([]storedefs.Cmd, 0, len(allCmds)/4)
|
|
seenCmds := make(map[string]bool, len(allCmds)/4)
|
|
for i := len(allCmds) - 1; i >= 0; i-- {
|
|
if !seenCmds[allCmds[i].Text] {
|
|
seenCmds[allCmds[i].Text] = true
|
|
uniqCmds = append(uniqCmds, allCmds[i])
|
|
}
|
|
}
|
|
if !newestFirst {
|
|
reverseCmds(uniqCmds)
|
|
}
|
|
return uniqCmds
|
|
}
|
|
|
|
// Reverse the order of commands, in place, in the slice. This reorders the
|
|
// command history between oldest or newest command being first in the slice.
|
|
func reverseCmds(cmds []storedefs.Cmd) {
|
|
for i, j := 0, len(cmds)-1; i < j; i, j = i+1, j-1 {
|
|
cmds[i], cmds[j] = cmds[j], cmds[i]
|
|
}
|
|
}
|
|
|
|
//elvdoc:fn insert-last-word
|
|
//
|
|
// Inserts the last word of the last command.
|
|
|
|
func insertLastWord(app cli.App, histStore histutil.Store) error {
|
|
codeArea, ok := focusedCodeArea(app)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
c := histStore.Cursor("")
|
|
c.Prev()
|
|
cmd, err := c.Get()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
words := parseutil.Wordify(cmd.Text)
|
|
if len(words) > 0 {
|
|
codeArea.MutateState(func(s *tk.CodeAreaState) {
|
|
s.Buffer.InsertAtDot(words[len(words)-1])
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func initStoreAPI(app cli.App, nb eval.NsBuilder, fuser histutil.Store) {
|
|
nb.AddGoFns(map[string]any{
|
|
"command-history": func(fm *eval.Frame, opts cmdhistOpt) error {
|
|
return commandHistory(opts, fuser, fm.ValueOutput())
|
|
},
|
|
"insert-last-word": func() { insertLastWord(app, fuser) },
|
|
})
|
|
}
|