listing_custom.go 3.1 KB

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