github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/edit/store_api.go (about) 1 package edit 2 3 import ( 4 "errors" 5 6 "github.com/markusbkk/elvish/pkg/cli" 7 "github.com/markusbkk/elvish/pkg/cli/histutil" 8 "github.com/markusbkk/elvish/pkg/cli/tk" 9 "github.com/markusbkk/elvish/pkg/eval" 10 "github.com/markusbkk/elvish/pkg/eval/vals" 11 "github.com/markusbkk/elvish/pkg/parse/parseutil" 12 "github.com/markusbkk/elvish/pkg/store/storedefs" 13 ) 14 15 var errStoreOffline = errors.New("store offline") 16 17 //elvdoc:fn command-history 18 // 19 // ```elvish 20 // edit:command-history &cmd-only=$false &dedup=$false &newest-first 21 // ``` 22 // 23 // Outputs the command history. 24 // 25 // By default, each entry is represented as a map, with an `id` key key for the 26 // sequence number of the command, and a `cmd` key for the text of the command. 27 // If `&cmd-only` is `$true`, only the text of each command is output. 28 // 29 // All entries are output by default. If `&dedup` is `$true`, only the most 30 // recent instance of each command (when comparing just the `cmd` key) is 31 // output. 32 // 33 // Commands are are output in oldest to newest order by default. If 34 // `&newest-first` is `$true` the output is in newest to oldest order instead. 35 // 36 // As an example, either of the following extracts the text of the most recent 37 // command: 38 // 39 // ```elvish 40 // edit:command-history | put [(all)][-1][cmd] 41 // edit:command-history &cmd-only &newest-first | take 1 42 // ``` 43 44 type cmdhistOpt struct{ CmdOnly, Dedup, NewestFirst bool } 45 46 func (o *cmdhistOpt) SetDefaultOptions() {} 47 48 func commandHistory(opts cmdhistOpt, fuser histutil.Store, out eval.ValueOutput) error { 49 if fuser == nil { 50 return errStoreOffline 51 } 52 cmds, err := fuser.AllCmds() 53 if err != nil { 54 return err 55 } 56 if opts.Dedup { 57 cmds = dedupCmds(cmds, opts.NewestFirst) 58 } else if opts.NewestFirst { 59 reverseCmds(cmds) 60 } 61 if opts.CmdOnly { 62 for _, cmd := range cmds { 63 err := out.Put(cmd.Text) 64 if err != nil { 65 return err 66 } 67 } 68 } else { 69 for _, cmd := range cmds { 70 err := out.Put(vals.MakeMap("id", cmd.Seq, "cmd", cmd.Text)) 71 if err != nil { 72 return err 73 } 74 } 75 } 76 return nil 77 } 78 79 func dedupCmds(allCmds []storedefs.Cmd, newestFirst bool) []storedefs.Cmd { 80 // Capacity allocation below is based on some personal empirical observation. 81 uniqCmds := make([]storedefs.Cmd, 0, len(allCmds)/4) 82 seenCmds := make(map[string]bool, len(allCmds)/4) 83 for i := len(allCmds) - 1; i >= 0; i-- { 84 if !seenCmds[allCmds[i].Text] { 85 seenCmds[allCmds[i].Text] = true 86 uniqCmds = append(uniqCmds, allCmds[i]) 87 } 88 } 89 if !newestFirst { 90 reverseCmds(uniqCmds) 91 } 92 return uniqCmds 93 } 94 95 // Reverse the order of commands, in place, in the slice. This reorders the 96 // command history between oldest or newest command being first in the slice. 97 func reverseCmds(cmds []storedefs.Cmd) { 98 for i, j := 0, len(cmds)-1; i < j; i, j = i+1, j-1 { 99 cmds[i], cmds[j] = cmds[j], cmds[i] 100 } 101 } 102 103 //elvdoc:fn insert-last-word 104 // 105 // Inserts the last word of the last command. 106 107 func insertLastWord(app cli.App, histStore histutil.Store) error { 108 codeArea, ok := focusedCodeArea(app) 109 if !ok { 110 return nil 111 } 112 c := histStore.Cursor("") 113 c.Prev() 114 cmd, err := c.Get() 115 if err != nil { 116 return err 117 } 118 words := parseutil.Wordify(cmd.Text) 119 if len(words) > 0 { 120 codeArea.MutateState(func(s *tk.CodeAreaState) { 121 s.Buffer.InsertAtDot(words[len(words)-1]) 122 }) 123 } 124 return nil 125 } 126 127 func initStoreAPI(app cli.App, nb eval.NsBuilder, fuser histutil.Store) { 128 nb.AddGoFns(map[string]interface{}{ 129 "command-history": func(fm *eval.Frame, opts cmdhistOpt) error { 130 return commandHistory(opts, fuser, fm.ValueOutput()) 131 }, 132 "insert-last-word": func() { insertLastWord(app, fuser) }, 133 }) 134 }