github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/store/cmd.go (about)

     1  package store
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  
     7  	bolt "go.etcd.io/bbolt"
     8  	. "github.com/markusbkk/elvish/pkg/store/storedefs"
     9  )
    10  
    11  func init() {
    12  	initDB["initialize command history table"] = func(tx *bolt.Tx) error {
    13  		_, err := tx.CreateBucketIfNotExists([]byte(bucketCmd))
    14  		return err
    15  	}
    16  }
    17  
    18  // NextCmdSeq returns the next sequence number of the command history.
    19  func (s *dbStore) NextCmdSeq() (int, error) {
    20  	var seq uint64
    21  	err := s.db.View(func(tx *bolt.Tx) error {
    22  		b := tx.Bucket([]byte(bucketCmd))
    23  		seq = b.Sequence() + 1
    24  		return nil
    25  	})
    26  	return int(seq), err
    27  }
    28  
    29  // AddCmd adds a new command to the command history.
    30  func (s *dbStore) AddCmd(cmd string) (int, error) {
    31  	var (
    32  		seq uint64
    33  		err error
    34  	)
    35  	err = s.db.Update(func(tx *bolt.Tx) error {
    36  		b := tx.Bucket([]byte(bucketCmd))
    37  		seq, err = b.NextSequence()
    38  		if err != nil {
    39  			return err
    40  		}
    41  		return b.Put(marshalSeq(seq), []byte(cmd))
    42  	})
    43  	return int(seq), err
    44  }
    45  
    46  // DelCmd deletes a command history item with the given sequence number.
    47  func (s *dbStore) DelCmd(seq int) error {
    48  	return s.db.Update(func(tx *bolt.Tx) error {
    49  		b := tx.Bucket([]byte(bucketCmd))
    50  		return b.Delete(marshalSeq(uint64(seq)))
    51  	})
    52  }
    53  
    54  // Cmd queries the command history item with the specified sequence number.
    55  func (s *dbStore) Cmd(seq int) (string, error) {
    56  	var cmd string
    57  	err := s.db.View(func(tx *bolt.Tx) error {
    58  		b := tx.Bucket([]byte(bucketCmd))
    59  		v := b.Get(marshalSeq(uint64(seq)))
    60  		if v == nil {
    61  			return ErrNoMatchingCmd
    62  		}
    63  		cmd = string(v)
    64  		return nil
    65  	})
    66  	return cmd, err
    67  }
    68  
    69  // IterateCmds iterates all the commands in the specified range, and calls the
    70  // callback with the content of each command sequentially.
    71  func (s *dbStore) IterateCmds(from, upto int, f func(Cmd)) error {
    72  	return s.db.View(func(tx *bolt.Tx) error {
    73  		b := tx.Bucket([]byte(bucketCmd))
    74  		c := b.Cursor()
    75  		for k, v := c.Seek(marshalSeq(uint64(from))); k != nil && unmarshalSeq(k) < uint64(upto); k, v = c.Next() {
    76  			f(Cmd{Text: string(v), Seq: int(unmarshalSeq(k))})
    77  		}
    78  		return nil
    79  	})
    80  }
    81  
    82  // CmdsWithSeq returns all commands within the specified range.
    83  func (s *dbStore) CmdsWithSeq(from, upto int) ([]Cmd, error) {
    84  	var cmds []Cmd
    85  	err := s.IterateCmds(from, upto, func(cmd Cmd) {
    86  		cmds = append(cmds, cmd)
    87  	})
    88  	return cmds, err
    89  }
    90  
    91  // NextCmd finds the first command after the given sequence number (inclusive)
    92  // with the given prefix.
    93  func (s *dbStore) NextCmd(from int, prefix string) (Cmd, error) {
    94  	var cmd Cmd
    95  	err := s.db.View(func(tx *bolt.Tx) error {
    96  		b := tx.Bucket([]byte(bucketCmd))
    97  		c := b.Cursor()
    98  		p := []byte(prefix)
    99  		for k, v := c.Seek(marshalSeq(uint64(from))); k != nil; k, v = c.Next() {
   100  			if bytes.HasPrefix(v, p) {
   101  				cmd = Cmd{Text: string(v), Seq: int(unmarshalSeq(k))}
   102  				return nil
   103  			}
   104  		}
   105  		return ErrNoMatchingCmd
   106  	})
   107  	return cmd, err
   108  }
   109  
   110  // PrevCmd finds the last command before the given sequence number (exclusive)
   111  // with the given prefix.
   112  func (s *dbStore) PrevCmd(upto int, prefix string) (Cmd, error) {
   113  	var cmd Cmd
   114  	err := s.db.View(func(tx *bolt.Tx) error {
   115  		b := tx.Bucket([]byte(bucketCmd))
   116  		c := b.Cursor()
   117  		p := []byte(prefix)
   118  
   119  		var v []byte
   120  		k, _ := c.Seek(marshalSeq(uint64(upto)))
   121  		if k == nil { // upto > LAST
   122  			k, v = c.Last()
   123  			if k == nil {
   124  				return ErrNoMatchingCmd
   125  			}
   126  		} else {
   127  			k, v = c.Prev() // upto exists, find the previous one
   128  		}
   129  
   130  		for ; k != nil; k, v = c.Prev() {
   131  			if bytes.HasPrefix(v, p) {
   132  				cmd = Cmd{Text: string(v), Seq: int(unmarshalSeq(k))}
   133  				return nil
   134  			}
   135  		}
   136  		return ErrNoMatchingCmd
   137  	})
   138  	return cmd, err
   139  }
   140  
   141  func marshalSeq(seq uint64) []byte {
   142  	b := make([]byte, 8)
   143  	binary.BigEndian.PutUint64(b, seq)
   144  	return b
   145  }
   146  
   147  func unmarshalSeq(key []byte) uint64 {
   148  	return binary.BigEndian.Uint64(key)
   149  }