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

     1  package mode
     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 accpeting.
    28  	// If unspecified, the Accept function default to a function that does
    29  	// nothing other than returning false.
    30  	Accept func(string) bool
    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) bool { return false }
    52  	}
    53  	if spec.Caption == "" {
    54  		spec.Caption = " LISTING "
    55  	}
    56  	accept := func(s string) {
    57  		retain := spec.Accept(s)
    58  		if !retain {
    59  			app.SetAddon(nil, false)
    60  		}
    61  	}
    62  	w := tk.NewComboBox(tk.ComboBoxSpec{
    63  		CodeArea: tk.CodeAreaSpec{
    64  			Prompt: modePrompt(spec.Caption, true),
    65  		},
    66  		ListBox: tk.ListBoxSpec{
    67  			Bindings: spec.Bindings,
    68  			OnAccept: func(it tk.Items, i int) {
    69  				accept(it.(listingItems)[i].ToAccept)
    70  			},
    71  			ExtendStyle: true,
    72  		},
    73  		OnFilter: func(w tk.ComboBox, q string) {
    74  			it, selected := spec.GetItems(q)
    75  			w.ListBox().Reset(listingItems(it), selected)
    76  			if spec.AutoAccept && len(it) == 1 {
    77  				accept(it[0].ToAccept)
    78  			}
    79  		},
    80  	})
    81  	return w, nil
    82  }
    83  
    84  type listingItems []ListingItem
    85  
    86  func (it listingItems) Len() int           { return len(it) }
    87  func (it listingItems) Show(i int) ui.Text { return it[i].ToShow }