github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/cli/mode/lastcmd.go (about)

     1  package mode
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"src.elv.sh/pkg/cli"
     9  	"src.elv.sh/pkg/cli/histutil"
    10  	"src.elv.sh/pkg/cli/tk"
    11  	"src.elv.sh/pkg/ui"
    12  )
    13  
    14  // Lastcmd is a mode for inspecting the last command, and inserting part of all
    15  // of it. It is based on the ComboBox widget.
    16  type Lastcmd interface {
    17  	tk.ComboBox
    18  }
    19  
    20  // LastcmdSpec specifies the configuration for the lastcmd mode.
    21  type LastcmdSpec struct {
    22  	// Key bindings.
    23  	Bindings tk.Bindings
    24  	// Store provides the source for the last command.
    25  	Store LastcmdStore
    26  	// Wordifier breaks a command into words.
    27  	Wordifier func(string) []string
    28  }
    29  
    30  // LastcmdStore is a subset of histutil.Store used in lastcmd mode.
    31  type LastcmdStore interface {
    32  	Cursor(prefix string) histutil.Cursor
    33  }
    34  
    35  var _ = LastcmdStore(histutil.Store(nil))
    36  
    37  // NewLastcmd creates a new lastcmd mode.
    38  func NewLastcmd(app cli.App, cfg LastcmdSpec) (Lastcmd, error) {
    39  	if cfg.Store == nil {
    40  		return nil, errNoHistoryStore
    41  	}
    42  	c := cfg.Store.Cursor("")
    43  	c.Prev()
    44  	cmd, err := c.Get()
    45  	if err != nil {
    46  		return nil, fmt.Errorf("db error: %v", err)
    47  	}
    48  	wordifier := cfg.Wordifier
    49  	if wordifier == nil {
    50  		wordifier = strings.Fields
    51  	}
    52  	cmdText := cmd.Text
    53  	words := wordifier(cmdText)
    54  	entries := make([]lastcmdEntry, len(words)+1)
    55  	entries[0] = lastcmdEntry{content: cmdText}
    56  	for i, word := range words {
    57  		entries[i+1] = lastcmdEntry{strconv.Itoa(i), strconv.Itoa(i - len(words)), word}
    58  	}
    59  
    60  	accept := func(text string) {
    61  		app.CodeArea().MutateState(func(s *tk.CodeAreaState) {
    62  			s.Buffer.InsertAtDot(text)
    63  		})
    64  		app.SetAddon(nil, false)
    65  	}
    66  	w := tk.NewComboBox(tk.ComboBoxSpec{
    67  		CodeArea: tk.CodeAreaSpec{Prompt: modePrompt(" LASTCMD ", true)},
    68  		ListBox: tk.ListBoxSpec{
    69  			Bindings: cfg.Bindings,
    70  			OnAccept: func(it tk.Items, i int) {
    71  				accept(it.(lastcmdItems).entries[i].content)
    72  			},
    73  		},
    74  		OnFilter: func(w tk.ComboBox, p string) {
    75  			items := filterLastcmdItems(entries, p)
    76  			if len(items.entries) == 1 {
    77  				accept(items.entries[0].content)
    78  			} else {
    79  				w.ListBox().Reset(items, 0)
    80  			}
    81  		},
    82  	})
    83  	return w, nil
    84  }
    85  
    86  type lastcmdItems struct {
    87  	negFilter bool
    88  	entries   []lastcmdEntry
    89  }
    90  
    91  type lastcmdEntry struct {
    92  	posIndex string
    93  	negIndex string
    94  	content  string
    95  }
    96  
    97  func filterLastcmdItems(allEntries []lastcmdEntry, p string) lastcmdItems {
    98  	if p == "" {
    99  		return lastcmdItems{false, allEntries}
   100  	}
   101  	var entries []lastcmdEntry
   102  	negFilter := strings.HasPrefix(p, "-")
   103  	for _, entry := range allEntries {
   104  		if (negFilter && strings.HasPrefix(entry.negIndex, p)) ||
   105  			(!negFilter && strings.HasPrefix(entry.posIndex, p)) {
   106  			entries = append(entries, entry)
   107  		}
   108  	}
   109  	return lastcmdItems{negFilter, entries}
   110  }
   111  
   112  func (it lastcmdItems) Show(i int) ui.Text {
   113  	index := ""
   114  	entry := it.entries[i]
   115  	if it.negFilter {
   116  		index = entry.negIndex
   117  	} else {
   118  		index = entry.posIndex
   119  	}
   120  	// NOTE: We now use a hardcoded width of 3 for the index, which will work as
   121  	// long as the command has less than 1000 words (when filter is positive) or
   122  	// 100 words (when filter is negative).
   123  	return ui.T(fmt.Sprintf("%3s %s", index, entry.content))
   124  }
   125  
   126  func (it lastcmdItems) Len() int { return len(it.entries) }