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  }