src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/cli/modes/listing.go (about)

     1  package modes
     2  
     3  import (
     4  	"errors"
     5  
     6  	"src.elv.sh/pkg/cli"
     7  	"src.elv.sh/pkg/cli/tk"
     8  	"src.elv.sh/pkg/ui"
     9  )
    10  
    11  // Listing is a customizable mode for browsing through a list of items. It is
    12  // based on the ComboBox widget.
    13  type Listing interface {
    14  	tk.ComboBox
    15  }
    16  
    17  // ListingSpec specifies the configuration for the listing mode.
    18  type ListingSpec struct {
    19  	// Key bindings.
    20  	Bindings tk.Bindings
    21  	// Caption of the listing. If empty, defaults to " LISTING ".
    22  	Caption string
    23  	// A function that takes the query string and returns a list of Item's and
    24  	// the index of the Item to select. Required.
    25  	GetItems func(query string) (items []ListingItem, selected int)
    26  	// A function to call when the user has accepted the selected item. If the
    27  	// return value is true, the listing will not be closed after accepting.
    28  	// If unspecified, the Accept function default to a function that does
    29  	// nothing other than returning false.
    30  	Accept func(string)
    31  	// Whether to automatically accept when there is only one item.
    32  	AutoAccept bool
    33  }
    34  
    35  // ListingItem is an item to show in the listing.
    36  type ListingItem struct {
    37  	// Passed to the Accept callback in Config.
    38  	ToAccept string
    39  	// How the item is shown.
    40  	ToShow ui.Text
    41  }
    42  
    43  var errGetItemsMustBeSpecified = errors.New("GetItems must be specified")
    44  
    45  // NewListing creates a new listing mode.
    46  func NewListing(app cli.App, spec ListingSpec) (Listing, error) {
    47  	if spec.GetItems == nil {
    48  		return nil, errGetItemsMustBeSpecified
    49  	}
    50  	if spec.Accept == nil {
    51  		spec.Accept = func(string) {}
    52  	}
    53  	if spec.Caption == "" {
    54  		spec.Caption = " LISTING "
    55  	}
    56  	accept := func(s string) {
    57  		app.PopAddon()
    58  		spec.Accept(s)
    59  	}
    60  	w := tk.NewComboBox(tk.ComboBoxSpec{
    61  		CodeArea: tk.CodeAreaSpec{
    62  			Prompt: modePrompt(spec.Caption, true),
    63  		},
    64  		ListBox: tk.ListBoxSpec{
    65  			Bindings: spec.Bindings,
    66  			OnAccept: func(it tk.Items, i int) {
    67  				accept(it.(listingItems)[i].ToAccept)
    68  			},
    69  			ExtendStyle: true,
    70  		},
    71  		OnFilter: func(w tk.ComboBox, q string) {
    72  			it, selected := spec.GetItems(q)
    73  			w.ListBox().Reset(listingItems(it), selected)
    74  			if spec.AutoAccept && len(it) == 1 {
    75  				accept(it[0].ToAccept)
    76  			}
    77  		},
    78  	})
    79  	return w, nil
    80  }
    81  
    82  type listingItems []ListingItem
    83  
    84  func (it listingItems) Len() int           { return len(it) }
    85  func (it listingItems) Show(i int) ui.Text { return it[i].ToShow }