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 }