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 }