diff --git a/go.mod b/go.mod index 4ad43d5..312dbc4 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/mattn/go-runewidth v0.0.16 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/sahilm/fuzzy v0.1.1 github.com/spf13/pflag v1.0.6 // indirect github.com/twmb/murmur3 v1.1.8 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect diff --git a/go.sum b/go.sum index a2ebe40..f33257a 100644 --- a/go.sum +++ b/go.sum @@ -102,8 +102,6 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57 h1:LmsF7Fk5jyEDhJk0fYIqdWNuTxSyid2W42A0L2YWjGE= -github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss= github.com/rivo/tview v0.0.0-20250325173046-7b72abf45814 h1:pJIO3sp+rkDbJTeqqpe2Oihq3hegiM5ASvsd6S0pvjg= github.com/rivo/tview v0.0.0-20250325173046-7b72abf45814/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -113,6 +111,8 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= +github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= +github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= diff --git a/internal/picker/item.go b/internal/picker/item.go new file mode 100644 index 0000000..1da895a --- /dev/null +++ b/internal/picker/item.go @@ -0,0 +1,23 @@ +package picker + +type Item struct { + text string + selected func() +} + +func NewItem(text string, selected func()) *Item { + return &Item{ + text: text, + selected: selected, + } +} + +type Items []*Item + +func (is Items) String(i int) string { + return is[i].text +} + +func (is Items) Len() int { + return len(is) +} diff --git a/internal/picker/picker.go b/internal/picker/picker.go new file mode 100644 index 0000000..ecdc3ab --- /dev/null +++ b/internal/picker/picker.go @@ -0,0 +1,57 @@ +package picker + +import ( + "github.com/rivo/tview" + "github.com/sahilm/fuzzy" +) + +type Picker struct { + *tview.Flex + Input *tview.InputField + List *tview.List + + app *tview.Application + items Items +} + +func New(app *tview.Application, items Items) *Picker { + p := &Picker{ + Flex: tview.NewFlex(), + Input: tview.NewInputField(), + List: tview.NewList(), + + app: app, + items: items, + } + + // Render all of the items initially. + p.changed("") + + p.Input.SetChangedFunc(p.changed) + p.List.ShowSecondaryText(false).SetFocusFunc(func() { + app.SetFocus(p.Input) + }) + p.Flex. + SetDirection(tview.FlexRow). + AddItem(p.Input, 3, 1, true). + AddItem(p.List, 0, 1, false) + return p +} + +func (p *Picker) changed(text string) { + var fuzzied Items + if text == "" { + fuzzied = append(fuzzied, p.items...) + } else { + matches := fuzzy.FindFrom(text, p.items) + for _, match := range matches { + fuzzied = append(fuzzied, p.items[match.Index]) + } + } + + p.List.Clear() + + for _, item := range fuzzied { + p.List.AddItem(item.text, "", 0, item.selected) + } +}