store_api.go 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. package edit
  2. import (
  3. "errors"
  4. "src.elv.sh/pkg/cli"
  5. "src.elv.sh/pkg/cli/histutil"
  6. "src.elv.sh/pkg/cli/tk"
  7. "src.elv.sh/pkg/eval"
  8. "src.elv.sh/pkg/eval/vals"
  9. "src.elv.sh/pkg/parse/parseutil"
  10. "src.elv.sh/pkg/store/storedefs"
  11. )
  12. var errStoreOffline = errors.New("store offline")
  13. type cmdhistOpt struct{ CmdOnly, Dedup, NewestFirst bool }
  14. func (o *cmdhistOpt) SetDefaultOptions() {}
  15. func commandHistory(opts cmdhistOpt, fuser histutil.Store, out eval.ValueOutput) error {
  16. if fuser == nil {
  17. return errStoreOffline
  18. }
  19. cmds, err := fuser.AllCmds()
  20. if err != nil {
  21. return err
  22. }
  23. if opts.Dedup {
  24. cmds = dedupCmds(cmds, opts.NewestFirst)
  25. } else if opts.NewestFirst {
  26. reverseCmds(cmds)
  27. }
  28. if opts.CmdOnly {
  29. for _, cmd := range cmds {
  30. err := out.Put(cmd.Text)
  31. if err != nil {
  32. return err
  33. }
  34. }
  35. } else {
  36. for _, cmd := range cmds {
  37. err := out.Put(vals.MakeMap("id", cmd.Seq, "cmd", cmd.Text))
  38. if err != nil {
  39. return err
  40. }
  41. }
  42. }
  43. return nil
  44. }
  45. func dedupCmds(allCmds []storedefs.Cmd, newestFirst bool) []storedefs.Cmd {
  46. // Capacity allocation below is based on some personal empirical observation.
  47. uniqCmds := make([]storedefs.Cmd, 0, len(allCmds)/4)
  48. seenCmds := make(map[string]bool, len(allCmds)/4)
  49. for i := len(allCmds) - 1; i >= 0; i-- {
  50. if !seenCmds[allCmds[i].Text] {
  51. seenCmds[allCmds[i].Text] = true
  52. uniqCmds = append(uniqCmds, allCmds[i])
  53. }
  54. }
  55. if !newestFirst {
  56. reverseCmds(uniqCmds)
  57. }
  58. return uniqCmds
  59. }
  60. // Reverse the order of commands, in place, in the slice. This reorders the
  61. // command history between oldest or newest command being first in the slice.
  62. func reverseCmds(cmds []storedefs.Cmd) {
  63. for i, j := 0, len(cmds)-1; i < j; i, j = i+1, j-1 {
  64. cmds[i], cmds[j] = cmds[j], cmds[i]
  65. }
  66. }
  67. func insertLastWord(app cli.App, histStore histutil.Store) error {
  68. codeArea, ok := focusedCodeArea(app)
  69. if !ok {
  70. return nil
  71. }
  72. c := histStore.Cursor("")
  73. c.Prev()
  74. cmd, err := c.Get()
  75. if err != nil {
  76. return err
  77. }
  78. words := parseutil.Wordify(cmd.Text)
  79. if len(words) > 0 {
  80. codeArea.MutateState(func(s *tk.CodeAreaState) {
  81. s.Buffer.InsertAtDot(words[len(words)-1])
  82. })
  83. }
  84. return nil
  85. }
  86. func initStoreAPI(app cli.App, nb eval.NsBuilder, fuser histutil.Store) {
  87. nb.AddGoFns(map[string]any{
  88. "command-history": func(fm *eval.Frame, opts cmdhistOpt) error {
  89. return commandHistory(opts, fuser, fm.ValueOutput())
  90. },
  91. "insert-last-word": func() { insertLastWord(app, fuser) },
  92. })
  93. }