github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/edit/listing_custom.go (about)

     1  package edit
     2  
     3  import (
     4  	"bufio"
     5  	"os"
     6  	"strings"
     7  	"sync"
     8  
     9  	"src.elv.sh/pkg/cli/mode"
    10  	"src.elv.sh/pkg/cli/tk"
    11  	"src.elv.sh/pkg/eval"
    12  	"src.elv.sh/pkg/eval/vals"
    13  	"src.elv.sh/pkg/eval/vars"
    14  	"src.elv.sh/pkg/strutil"
    15  	"src.elv.sh/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) []mode.ListingItem
    38  	if fn, isFn := items.(eval.Callable); isFn {
    39  		getItems = func(q string) []mode.ListingItem {
    40  			var items []mode.ListingItem
    41  			var itemsMutex sync.Mutex
    42  			collect := func(item mode.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(mode.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) []mode.ListingItem {
    75  			convertedItems := []mode.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 := mode.NewListing(ed.app, mode.ListingSpec{
    90  		Bindings: bindings,
    91  		Caption:  opts.Caption,
    92  		GetItems: func(q string) ([]mode.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) bool {
   101  			if opts.Accept != nil {
   102  				callWithNotifyPorts(ed, fm.Evaler, opts.Accept, s)
   103  			}
   104  			return false
   105  		},
   106  		AutoAccept: opts.AutoAccept,
   107  	})
   108  	startMode(ed.app, w, err)
   109  }
   110  
   111  func getToFilter(v interface{}) (string, bool) {
   112  	toFilterValue, _ := vals.Index(v, "to-filter")
   113  	toFilter, toFilterOk := toFilterValue.(string)
   114  	return toFilter, toFilterOk
   115  }
   116  
   117  func getListingItem(v interface{}) (item mode.ListingItem, ok bool) {
   118  	toAcceptValue, _ := vals.Index(v, "to-accept")
   119  	toAccept, toAcceptOk := toAcceptValue.(string)
   120  	toShowValue, _ := vals.Index(v, "to-show")
   121  	toShow, toShowOk := toShowValue.(ui.Text)
   122  	if toShowString, ok := toShowValue.(string); ok {
   123  		toShow = ui.T(toShowString)
   124  		toShowOk = true
   125  	}
   126  	return mode.ListingItem{ToAccept: toAccept, ToShow: toShow}, toAcceptOk && toShowOk
   127  }