github.com/elves/elvish@v0.15.0/pkg/edit/listing_custom.go (about) 1 package edit 2 3 import ( 4 "bufio" 5 "os" 6 "strings" 7 "sync" 8 9 "github.com/elves/elvish/pkg/cli" 10 "github.com/elves/elvish/pkg/cli/addons/listing" 11 "github.com/elves/elvish/pkg/eval" 12 "github.com/elves/elvish/pkg/eval/vals" 13 "github.com/elves/elvish/pkg/eval/vars" 14 "github.com/elves/elvish/pkg/strutil" 15 "github.com/elves/elvish/pkg/ui" 16 ) 17 18 type customListingOpts struct { 19 Binding BindingMap 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 binding cli.Handler 34 if opts.Binding.Map != nil { 35 binding = newMapBinding(ed, fm.Evaler, vars.FromPtr(&opts.Binding)) 36 } 37 var getItems func(string) []listing.Item 38 if fn, isFn := items.(eval.Callable); isFn { 39 getItems = func(q string) []listing.Item { 40 var items []listing.Item 41 var itemsMutex sync.Mutex 42 collect := func(item listing.Item) { 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(listing.Item{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) []listing.Item { 75 convertedItems := []listing.Item{} 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 listing.Start(ed.app, listing.Config{ 90 Binding: binding, 91 Caption: opts.Caption, 92 GetItems: func(q string) ([]listing.Item, 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 } 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 listing.Item, 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 listing.Item{ToAccept: toAccept, ToShow: toShow}, toAcceptOk && toShowOk 126 }