src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/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/modes"
    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  func listingStartCustom(ed *Editor, fm *eval.Frame, opts customListingOpts, items any) {
    29  	var bindings tk.Bindings
    30  	if opts.Binding.Map != nil {
    31  		bindings = newMapBindings(ed, fm.Evaler, vars.FromPtr(&opts.Binding))
    32  	}
    33  	var getItems func(string) []modes.ListingItem
    34  	if fn, isFn := items.(eval.Callable); isFn {
    35  		getItems = func(q string) []modes.ListingItem {
    36  			var items []modes.ListingItem
    37  			var itemsMutex sync.Mutex
    38  			collect := func(item modes.ListingItem) {
    39  				itemsMutex.Lock()
    40  				defer itemsMutex.Unlock()
    41  				items = append(items, item)
    42  			}
    43  			valuesCb := func(ch <-chan any) {
    44  				for v := range ch {
    45  					if item, itemOk := getListingItem(v); itemOk {
    46  						collect(item)
    47  					}
    48  				}
    49  			}
    50  			bytesCb := func(r *os.File) {
    51  				buffered := bufio.NewReader(r)
    52  				for {
    53  					line, err := buffered.ReadString('\n')
    54  					if line != "" {
    55  						s := strutil.ChopLineEnding(line)
    56  						collect(modes.ListingItem{ToAccept: s, ToShow: ui.T(s)})
    57  					}
    58  					if err != nil {
    59  						break
    60  					}
    61  				}
    62  			}
    63  			f := func(fm *eval.Frame) error { return fn.Call(fm, []any{q}, eval.NoOpts) }
    64  			err := fm.PipeOutput(f, valuesCb, bytesCb)
    65  			// TODO(xiaq): Report the error.
    66  			_ = err
    67  			return items
    68  		}
    69  	} else {
    70  		getItems = func(q string) []modes.ListingItem {
    71  			convertedItems := []modes.ListingItem{}
    72  			vals.Iterate(items, func(v any) bool {
    73  				toFilter, toFilterOk := getToFilter(v)
    74  				item, itemOk := getListingItem(v)
    75  				if toFilterOk && itemOk && strings.Contains(toFilter, q) {
    76  					// TODO(xiaq): Report type error when ok is false.
    77  					convertedItems = append(convertedItems, item)
    78  				}
    79  				return true
    80  			})
    81  			return convertedItems
    82  		}
    83  	}
    84  
    85  	w, err := modes.NewListing(ed.app, modes.ListingSpec{
    86  		Bindings: bindings,
    87  		Caption:  opts.Caption,
    88  		GetItems: func(q string) ([]modes.ListingItem, int) {
    89  			items := getItems(q)
    90  			selected := 0
    91  			if opts.KeepBottom {
    92  				selected = len(items) - 1
    93  			}
    94  			return items, selected
    95  		},
    96  		Accept: func(s string) {
    97  			if opts.Accept != nil {
    98  				callWithNotifyPorts(ed, fm.Evaler, opts.Accept, s)
    99  			}
   100  		},
   101  		AutoAccept: opts.AutoAccept,
   102  	})
   103  	startMode(ed.app, w, err)
   104  }
   105  
   106  func getToFilter(v any) (string, bool) {
   107  	toFilterValue, _ := vals.Index(v, "to-filter")
   108  	toFilter, toFilterOk := toFilterValue.(string)
   109  	return toFilter, toFilterOk
   110  }
   111  
   112  func getListingItem(v any) (item modes.ListingItem, ok bool) {
   113  	toAcceptValue, _ := vals.Index(v, "to-accept")
   114  	toAccept, toAcceptOk := toAcceptValue.(string)
   115  	toShowValue, _ := vals.Index(v, "to-show")
   116  	toShow, toShowOk := toShowValue.(ui.Text)
   117  	if toShowString, ok := toShowValue.(string); ok {
   118  		toShow = ui.T(toShowString)
   119  		toShowOk = true
   120  	}
   121  	return modes.ListingItem{ToAccept: toAccept, ToShow: toShow}, toAcceptOk && toShowOk
   122  }