cliedit: Support passing a query callback to edit:listing:start-custom.

This commit is contained in:
Qi Xiao 2019-12-13 22:36:27 +00:00
parent db38b32717
commit 51323b609f
3 changed files with 115 additions and 26 deletions

View File

@ -32,8 +32,8 @@ func initListings(app cli.App, ev *eval.Evaler, ns eval.Ns, st storedefs.Store,
"down-cycle": func() { listingDownCycle(app) },
"page-up": func() { listingPageUp(app) },
"page-down": func() { listingPageDown(app) },
"start-custom": func(fm *eval.Frame, opts customListingOpts, items vals.List) {
listingStartCustom(app, fm.Evaler, opts, items)
"start-custom": func(fm *eval.Frame, opts customListingOpts, items interface{}) {
listingStartCustom(app, fm, opts, items)
},
/*
"toggle-filtering": cli.ListingToggleFiltering,

View File

@ -1,7 +1,10 @@
package cliedit
import (
"bufio"
"os"
"strings"
"sync"
"github.com/elves/elvish/cli"
"github.com/elves/elvish/cli/addons/listing"
@ -26,41 +29,89 @@ func (*customListingOpts) SetDefaultOptions() {}
//
// Starts a custom listing addon.
func listingStartCustom(app cli.App, ev *eval.Evaler, opts customListingOpts, items vals.List) {
func listingStartCustom(app cli.App, fm *eval.Frame, opts customListingOpts, items interface{}) {
var binding el.Handler
if opts.Binding.Map != nil {
binding = newMapBinding(app, ev, vars.FromPtr(&opts.Binding))
binding = newMapBinding(app, fm.Evaler, vars.FromPtr(&opts.Binding))
}
var getItems func(string) []listing.Item
if fn, isFn := items.(eval.Callable); isFn {
getItems = func(q string) []listing.Item {
var items []listing.Item
var itemsMutex sync.Mutex
collect := func(item listing.Item) {
itemsMutex.Lock()
defer itemsMutex.Unlock()
items = append(items, item)
}
valuesCb := func(ch <-chan interface{}) {
for v := range ch {
if item, itemOk := getListingItem(v); itemOk {
collect(item)
}
}
}
bytesCb := func(r *os.File) {
buffered := bufio.NewReader(r)
for {
line, err := buffered.ReadString('\n')
if line != "" {
s := strings.TrimSuffix(line, "\n")
collect(listing.Item{ToAccept: s, ToShow: ui.T(s)})
}
if err != nil {
break
}
}
}
err := fm.CallWithOutputCallback(
fn, []interface{}{q}, eval.NoOpts, valuesCb, bytesCb)
// TODO(xiaq): Report the error.
_ = err
return items
}
} else {
getItems = func(q string) []listing.Item {
convertedItems := []listing.Item{}
vals.Iterate(items, func(v interface{}) bool {
toFilter, toFilterOk := getToFilter(v)
item, itemOk := getListingItem(v)
if toFilterOk && itemOk && strings.Contains(toFilter, q) {
// TODO(xiaq): Report type error when ok is false.
convertedItems = append(convertedItems, item)
}
return true
})
return convertedItems
}
}
listing.Start(app, listing.Config{
Binding: binding,
Caption: opts.Caption,
GetItems: func(q string) ([]listing.Item, int) {
parsedItems := []listing.Item{}
vals.Iterate(items, func(v interface{}) bool {
toFilter, item, ok := indexListingItem(v)
if ok && strings.Contains(toFilter, q) {
// TODO(xiaq): Report type error when ok is false.
parsedItems = append(parsedItems, item)
}
return true
})
items := getItems(q)
selected := 0
if opts.KeepBottom {
selected = len(parsedItems) - 1
selected = len(items) - 1
}
return parsedItems, selected
return items, selected
},
Accept: func(s string) bool {
callWithNotifyPorts(app, ev, opts.Accept, s)
callWithNotifyPorts(app, fm.Evaler, opts.Accept, s)
return false
},
AutoAccept: opts.AutoAccept,
})
}
func indexListingItem(v interface{}) (toFilter string, item listing.Item, ok bool) {
func getToFilter(v interface{}) (string, bool) {
toFilterValue, _ := vals.Index(v, "to-filter")
toFilter, toFilterOk := toFilterValue.(string)
return toFilter, toFilterOk
}
func getListingItem(v interface{}) (item listing.Item, ok bool) {
toAcceptValue, _ := vals.Index(v, "to-accept")
toAccept, toAcceptOk := toAcceptValue.(string)
toShowValue, _ := vals.Index(v, "to-show")
@ -69,7 +120,5 @@ func indexListingItem(v interface{}) (toFilter string, item listing.Item, ok boo
toShow = ui.T(toShowString)
toShowOk = true
}
return toFilter,
listing.Item{ToAccept: toAccept, ToShow: toShow},
toFilterOk && toAcceptOk && toShowOk
return listing.Item{ToAccept: toAccept, ToShow: toShow}, toAcceptOk && toShowOk
}

View File

@ -81,13 +81,13 @@ func TestLastCmdAddon(t *testing.T) {
)
}
func TestCustomListing(t *testing.T) {
func TestCustomListing_PassingList(t *testing.T) {
f := setup()
defer f.Cleanup()
evals(f.Evaler,
`items = [[&to-filter=echo &to-accept=echo &to-show=echo]
[&to-filter=put &to-accept=put &to-show=put]]`,
`items = [[&to-filter=1 &to-accept=echo &to-show=echo]
[&to-filter=2 &to-accept=put &to-show=(styled put green)]]`,
`edit:listing:start-custom $items &accept=$edit:insert-at-dot~ &caption=A`)
f.TestTTY(t,
"~> \n",
@ -95,11 +95,51 @@ func TestCustomListing(t *testing.T) {
"* ", term.DotHere, "\n",
"echo \n", Styles,
"++++++++++++++++++++++++++++++++++++++++++++++++++",
"put",
"put ", Styles,
"vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv",
)
// Filter - "put" will be selected.
f.TTYCtrl.Inject(term.K('2'))
// Accept.
f.TTYCtrl.Inject(term.K('\n'))
f.TestTTY(t,
"~> echo", Styles,
" vvvv", term.DotHere,
"~> put", Styles,
" vvv", term.DotHere,
)
}
func TestCustomListing_PassingValueCallback(t *testing.T) {
f := setup()
defer f.Cleanup()
evals(f.Evaler,
`f = [q]{ put [&to-accept='q '$q &to-show=(styled 'q '$q blue)] }`,
`edit:listing:start-custom $f &accept=$edit:insert-at-dot~ &caption=A`)
// Query.
f.TTYCtrl.Inject(term.K('x'))
f.TestTTY(t,
"~> \n",
"A x", Styles,
"* ", term.DotHere, "\n",
"q x ", Styles,
"##################################################",
)
}
func TestCustomListing_PassingBytesCallback(t *testing.T) {
f := setup()
defer f.Cleanup()
evals(f.Evaler,
`f = [q]{ echo 'q '$q }`,
`edit:listing:start-custom $f &accept=$edit:insert-at-dot~ &caption=A`)
// Query.
f.TTYCtrl.Inject(term.K('x'))
f.TestTTY(t,
"~> \n",
"A x", Styles,
"* ", term.DotHere, "\n",
"q x ", Styles,
"++++++++++++++++++++++++++++++++++++++++++++++++++",
)
}