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

     1  package edit
     2  
     3  import (
     4  	"bufio"
     5  	"os"
     6  	"strings"
     7  	"sync"
     8  
     9  	"github.com/markusbkk/elvish/pkg/cli/modes"
    10  	"github.com/markusbkk/elvish/pkg/cli/tk"
    11  	"github.com/markusbkk/elvish/pkg/eval"
    12  	"github.com/markusbkk/elvish/pkg/eval/vals"
    13  	"github.com/markusbkk/elvish/pkg/eval/vars"
    14  	"github.com/markusbkk/elvish/pkg/strutil"
    15  	"github.com/markusbkk/elvish/pkg/ui"
    16  )
    17  
    18  type customListingOpts struct {
    19  	Binding    bindingsMap
    20  	Caption    string
    21  	KeepBottom bool
    22  	Accept     eval.Callable
    23  	AutoAccept bool
    24  }
    25  
    26  func (*customListingOpts) SetDefaultOptions() {}
    27  
    28  //elvdoc:fn listing:start-custom
    29  //
    30  // Starts a custom listing addon.
    31  
    32  func listingStartCustom(ed *Editor, fm *eval.Frame, opts customListingOpts, items interface{}) {
    33  	var bindings tk.Bindings
    34  	if opts.Binding.Map != nil {
    35  		bindings = newMapBindings(ed, fm.Evaler, vars.FromPtr(&opts.Binding))
    36  	}
    37  	var getItems func(string) []modes.ListingItem
    38  	if fn, isFn := items.(eval.Callable); isFn {
    39  		getItems = func(q string) []modes.ListingItem {
    40  			var items []modes.ListingItem
    41  			var itemsMutex sync.Mutex
    42  			collect := func(item modes.ListingItem) {
    43  				itemsMutex.Lock()
    44  				defer itemsMutex.Unlock()
    45  				items = append(items, item)
    46  			}
    47  			valuesCb := func(ch <-chan interface{}) {
    48  				for v := range ch {
    49  					if item, itemOk := getListingItem(v); itemOk {
    50  						collect(item)
    51  					}
    52  				}
    53  			}
    54  			bytesCb := func(r *os.File) {
    55  				buffered := bufio.NewReader(r)
    56  				for {
    57  					line, err := buffered.ReadString('\n')
    58  					if line != "" {
    59  						s := strutil.ChopLineEnding(line)
    60  						collect(modes.ListingItem{ToAccept: s, ToShow: ui.T(s)})
    61  					}
    62  					if err != nil {
    63  						break
    64  					}
    65  				}
    66  			}
    67  			f := func(fm *eval.Frame) error { return fn.Call(fm, []interface{}{q}, eval.NoOpts) }
    68  			err := fm.PipeOutput(f, valuesCb, bytesCb)
    69  			// TODO(xiaq): Report the error.
    70  			_ = err
    71  			return items
    72  		}
    73  	} else {
    74  		getItems = func(q string) []modes.ListingItem {
    75  			convertedItems := []modes.ListingItem{}
    76  			vals.Iterate(items, func(v interface{}) bool {
    77  				toFilter, toFilterOk := getToFilter(v)
    78  				item, itemOk := getListingItem(v)
    79  				if toFilterOk && itemOk && strings.Contains(toFilter, q) {
    80  					// TODO(xiaq): Report type error when ok is false.
    81  					convertedItems = append(convertedItems, item)
    82  				}
    83  				return true
    84  			})
    85  			return convertedItems
    86  		}
    87  	}
    88  
    89  	w, err := modes.NewListing(ed.app, modes.ListingSpec{
    90  		Bindings: bindings,
    91  		Caption:  opts.Caption,
    92  		GetItems: func(q string) ([]modes.ListingItem, int) {
    93  			items := getItems(q)
    94  			selected := 0
    95  			if opts.KeepBottom {
    96  				selected = len(items) - 1
    97  			}
    98  			return items, selected
    99  		},
   100  		Accept: func(s string) {
   101  			if opts.Accept != nil {
   102  				callWithNotifyPorts(ed, fm.Evaler, opts.Accept, s)
   103  			}
   104  		},
   105  		AutoAccept: opts.AutoAccept,
   106  	})
   107  	startMode(ed.app, w, err)
   108  }
   109  
   110  func getToFilter(v interface{}) (string, bool) {
   111  	toFilterValue, _ := vals.Index(v, "to-filter")
   112  	toFilter, toFilterOk := toFilterValue.(string)
   113  	return toFilter, toFilterOk
   114  }
   115  
   116  func getListingItem(v interface{}) (item modes.ListingItem, ok bool) {
   117  	toAcceptValue, _ := vals.Index(v, "to-accept")
   118  	toAccept, toAcceptOk := toAcceptValue.(string)
   119  	toShowValue, _ := vals.Index(v, "to-show")
   120  	toShow, toShowOk := toShowValue.(ui.Text)
   121  	if toShowString, ok := toShowValue.(string); ok {
   122  		toShow = ui.T(toShowString)
   123  		toShowOk = true
   124  	}
   125  	return modes.ListingItem{ToAccept: toAccept, ToShow: toShow}, toAcceptOk && toShowOk
   126  }