From e985f96dc4563ca3bc337638252e61e902ce2005 Mon Sep 17 00:00:00 2001 From: Nicolas Martin Date: Sat, 24 Oct 2020 22:45:05 +0200 Subject: [PATCH] Add fuzzy filtering for stash notes --- go.mod | 1 + go.sum | 10 ++------- ui/stash.go | 65 ++++++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 55 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 0960022..7746b36 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/charmbracelet/glamour v0.2.1-0.20200829234023-6c0e29c4dae5 github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac github.com/google/uuid v1.1.2 // indirect + github.com/lithammer/fuzzysearch v1.1.1 github.com/mattn/go-runewidth v0.0.9 github.com/meowgorithm/babyenv v1.3.0 github.com/microcosm-cc/bluemonday v1.0.4 // indirect diff --git a/go.sum b/go.sum index 1697f57..932681c 100644 --- a/go.sum +++ b/go.sum @@ -42,7 +42,6 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm github.com/calmh/randomart v1.1.0 h1:evl+iwc10LXtHdMZhzLxmsCQVmWnkXs44SbC6Uk0Il8= github.com/calmh/randomart v1.1.0/go.mod h1:DQUbPVyP+7PAs21w/AnfMKG5NioxS3TbZ2F9MSK/jFM= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/charmbracelet/bubbles v0.6.0 h1:i884UciNkSDSOQKgYrMFCLcVN8r0tX5K4pthOwBkZ7Q= github.com/charmbracelet/bubbles v0.6.0/go.mod h1:MxySU+YRGbAhZQJavZlW2os+fIeOW69MI3iXqA+2/WA= github.com/charmbracelet/bubbles v0.7.1 h1:qvXoIh7ItTA+IA+IO2xxuI9ntp+uVprDAK+IoMzC5OI= github.com/charmbracelet/bubbles v0.7.1/go.mod h1:UV3dot0XR45cnQX2d8gtohfUFMepquRG2WE56LPKWNc= @@ -159,6 +158,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lithammer/fuzzysearch v1.1.1 h1:8F9OAV2xPuYblToVohjanztdnPjbtA0MLgMvDKQ0Z08= +github.com/lithammer/fuzzysearch v1.1.1/go.mod h1:H2bng+w5gsR7NlfIJM8ElGZI0sX6C/9uzGqicVXGU6c= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -204,7 +205,6 @@ github.com/muesli/reflow v0.2.0/go.mod h1:qT22vjVmM9MIUeLgsVYe/Ye7eZlbv9dZjL3dVh github.com/muesli/sasquatch v0.0.0-20200811221207-66979d92330a h1:Hw/15RYEOUD6T9UCRkUmNBa33kJkH33Fui6hE4sRLKU= github.com/muesli/sasquatch v0.0.0-20200811221207-66979d92330a/go.mod h1:+XG0ne5zXWBTSbbe7Z3/RWxaT8PZY6zaZ1dX6KjprYY= github.com/muesli/termenv v0.7.0/go.mod h1:SohX91w6swWA4AYU+QmPx+aSgXhWO0juiyID9UZmbpA= -github.com/muesli/termenv v0.7.2 h1:r1raklL3uKE7rOvWgSenmEm2px+dnc33OTisZ8YR1fw= github.com/muesli/termenv v0.7.2/go.mod h1:ct2L5N2lmix82RaY3bMWwVu/jUFc9Ule0KGDCiKYPh8= github.com/muesli/termenv v0.7.4 h1:/pBqvU5CpkY53tU0vVn+xgs2ZTX63aH5nY+SSps5Xa8= github.com/muesli/termenv v0.7.4/go.mod h1:pZ7qY9l3F7e5xsAOS0zCew2tME+p7bWeBkotCEcIIcc= @@ -220,7 +220,6 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/term v0.0.0-20200520122047-c3ffed290a03/go.mod h1:Z9+Ul5bCbBKnbCvdOWbLqTHhJiYV414CURZJba6L8qA= -github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -255,7 +254,6 @@ github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= @@ -295,9 +293,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1VADv+S/80LGJmyl1ROJ2AI= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -364,9 +360,7 @@ golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200819171115-d785dc25833f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 h1:bNEHhJCnrwMKNMmOx3yAynp5vs5/gRy+XWFtZFu7NBM= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201020230747-6e5568b54d1a h1:e3IU37lwO4aq3uoRKINC7JikojFmE5gO7xhfxs8VC34= golang.org/x/sys v0.0.0-20201020230747-6e5568b54d1a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/ui/stash.go b/ui/stash.go index bce1ea5..1a0c574 100644 --- a/ui/stash.go +++ b/ui/stash.go @@ -17,6 +17,7 @@ import ( "github.com/charmbracelet/charm" "github.com/charmbracelet/charm/ui/common" "github.com/dustin/go-humanize" + "github.com/lithammer/fuzzysearch/fuzzy" runewidth "github.com/mattn/go-runewidth" "github.com/muesli/reflow/ansi" te "github.com/muesli/termenv" @@ -144,10 +145,13 @@ func (m stashModel) markdownIndex() int { // Return the current selected markdown in the stash. func (m stashModel) selectedMarkdown() *markdown { i := m.markdownIndex() - if i < 0 || len(m.markdowns) == 0 || len(m.markdowns) <= i { + markdowns := m.getNotes() + + if i < 0 || len(markdowns) == 0 || len(markdowns) <= i { return nil } - return m.markdowns[i] + + return markdowns[i] } // Adds markdown documents to the model. @@ -189,17 +193,49 @@ func (m *stashModel) removeLocalMarkdown(localPath string) error { // Return the number of markdown documents of a given type. func (m stashModel) countMarkdowns(t markdownType) (found int) { - if len(m.markdowns) == 0 { + if len(m.getNotes()) == 0 { return } - for i := 0; i < len(m.markdowns); i++ { - if m.markdowns[i].markdownType == t { + for i := 0; i < len(m.getNotes()); i++ { + if m.getNotes()[i].markdownType == t { found++ } } return } +// Returns the stashed markdown notes. When the model state indicates that +// filtering is desired this also filters and ranks the notes by the search term +// in the searchinput field. +func (m *stashModel) getNotes() []*markdown { + if m.searchInput.Value() == "" { + return m.markdowns + } + + if m.state != stashStateSearchNotes && + m.state != stashStateShowFiltered && + m.state != stashStatePromptDelete && + m.state != stashStateSettingNote { + return m.markdowns + } + + targets := []string{} + + for _, t := range m.markdowns { + targets = append(targets, t.Note) + } + + ranks := fuzzy.RankFindFold(m.searchInput.Value(), targets) + sort.Sort(ranks) + + filtered := []*markdown{} + for _, r := range ranks { + filtered = append(filtered, m.markdowns[r.OriginalIndex]) + } + + return filtered +} + func (m *stashModel) hideStatusMessage() { m.showStatusMessage = false if m.statusMessageTimer != nil { @@ -342,6 +378,8 @@ func stashUpdate(msg tea.Msg, m stashModel) (stashModel, tea.Cmd) { switch m.state { case stashStateReady, stashStateShowFiltered: + pages := len(m.getNotes()) + switch msg := msg.(type) { // Handle keys case tea.KeyMsg: @@ -377,13 +415,13 @@ func stashUpdate(msg tea.Msg, m stashModel) (stashModel, tea.Cmd) { // Go to the very end case "end", "G": m.paginator.Page = m.paginator.TotalPages - 1 - m.index = m.paginator.ItemsOnPage(len(m.markdowns)) - 1 + m.index = m.paginator.ItemsOnPage(pages) - 1 // Open document case "enter": m.hideStatusMessage() - if len(m.markdowns) == 0 { + if pages == 0 { break } @@ -502,7 +540,7 @@ func stashUpdate(msg tea.Msg, m stashModel) (stashModel, tea.Cmd) { } // Keep the index in bounds when paginating - itemsOnPage := m.paginator.ItemsOnPage(len(m.markdowns)) + itemsOnPage := m.paginator.ItemsOnPage(len(m.getNotes())) if m.index > itemsOnPage-1 { m.index = max(0, itemsOnPage-1) } @@ -763,9 +801,10 @@ func stashHeaderView(m stashModel) string { func stashPopulatedView(m stashModel) string { var b strings.Builder - if len(m.markdowns) > 0 { - start, end := m.paginator.GetSliceBounds(len(m.markdowns)) - docs := m.markdowns[start:end] + markdowns := m.getNotes() + if len(markdowns) > 0 { + start, end := m.paginator.GetSliceBounds(len(markdowns)) + docs := markdowns[start:end] for i, md := range docs { stashItemView(&b, m, i, md) @@ -778,10 +817,10 @@ func stashPopulatedView(m stashModel) string { // If there aren't enough items to fill up this page (always the last page) // then we need to add some newlines to fill up the space where stash items // would have been. - itemsOnPage := m.paginator.ItemsOnPage(len(m.markdowns)) + itemsOnPage := m.paginator.ItemsOnPage(len(markdowns)) if itemsOnPage < m.paginator.PerPage { n := (m.paginator.PerPage - itemsOnPage) * stashViewItemHeight - if len(m.markdowns) == 0 { + if len(markdowns) == 0 { n -= stashViewItemHeight - 1 } for i := 0; i < n; i++ {