Skip to content

Commit

Permalink
Merge pull request #38 from koki-develop/search-test
Browse files Browse the repository at this point in the history
  • Loading branch information
koki-develop committed Apr 6, 2023
2 parents fe841e8 + 648094b commit 42d1755
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 22 deletions.
4 changes: 4 additions & 0 deletions go.mod
Expand Up @@ -7,13 +7,15 @@ require (
github.com/charmbracelet/bubbletea v0.23.2
github.com/charmbracelet/lipgloss v0.7.1
github.com/spf13/cobra v1.6.1
github.com/stretchr/testify v1.8.2
golang.org/x/sync v0.1.0
)

require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
Expand All @@ -23,9 +25,11 @@ require (
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
15 changes: 15 additions & 0 deletions go.sum
Expand Up @@ -16,6 +16,9 @@ github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNW
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
Expand Down Expand Up @@ -44,6 +47,8 @@ github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4Y
github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8=
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
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/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
Expand All @@ -53,6 +58,13 @@ github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -68,5 +80,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
53 changes: 31 additions & 22 deletions search.go
Expand Up @@ -103,28 +103,9 @@ func Search(items Items, search string, opts ...SearchOption) Matches {

// Perform the search for each item.
for index := start; index < end; index++ {
item := items.ItemString(index)

// If case-insensitive, convert the item to lowercase.
if !o.caseSensitive {
item = strings.ToLower(item)
}

// Create a slice to store the matched indexes.
matchedIndexes := make([]int, 0, len(search))
j := 0

// Check for matching between the item's characters and the search string.
for i, r := range item {
if j < len(search) && r == rune(search[j]) {
matchedIndexes = append(matchedIndexes, i)
j++
}
}

// If all characters in the search string match, add the match to the local matches.
if j == len(search) {
m := Match{Str: items.ItemString(index), Index: index, MatchedIndexes: matchedIndexes}
m, ok := fuzzySearch(items.ItemString(index), search, o)
if ok {
m.Index = index
localMatches = append(localMatches, m)
}
}
Expand Down Expand Up @@ -152,3 +133,31 @@ func Search(items Items, search string, opts ...SearchOption) Matches {
result.sort()
return result
}

func fuzzySearch(str, search string, option searchOption) (Match, bool) {
item := str

// If case-insensitive, convert the item to lowercase.
if !option.caseSensitive {
item = strings.ToLower(item)
}

// Create a slice to store the matched indexes.
matchedIndexes := make([]int, 0, len(search))
j := 0

// Check for matching between the item's characters and the search string.
for i, r := range item {
if j < len(search) && r == rune(search[j]) {
matchedIndexes = append(matchedIndexes, i)
j++
}
}

// Returns Match if all characters in the search string match.
if j == len(search) {
return Match{Str: str, MatchedIndexes: matchedIndexes}, true
} else {
return Match{}, false
}
}
96 changes: 96 additions & 0 deletions search_test.go
@@ -0,0 +1,96 @@
package fzf

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

type fuzzySearchArgs struct {
str string
search string
option searchOption
}

func assertFuzzySearch(t *testing.T, args fuzzySearchArgs, want Match, ok bool) {
got1, got2 := fuzzySearch(args.str, args.search, args.option)

if ok {
assert.True(t, got2)
assert.Equal(t, want, got1)
} else {
assert.False(t, got2)
}
}

func Test_fuzzySearch(t *testing.T) {
tests := []struct {
str string
search string
matchedIndexes []int
}{
{str: "abc", search: "", matchedIndexes: []int{}},
{str: "abc", search: "a", matchedIndexes: []int{0}},
{str: "abc", search: "ab", matchedIndexes: []int{0, 1}},
{str: "abc", search: "ac", matchedIndexes: []int{0, 2}},
{str: "abc", search: "abc", matchedIndexes: []int{0, 1, 2}},
{str: "abc", search: "b", matchedIndexes: []int{1}},
{str: "abc", search: "bc", matchedIndexes: []int{1, 2}},
{str: "abc", search: "c", matchedIndexes: []int{2}},
{str: "abc", search: "cba"},
{str: "abc", search: "d"},
{str: "abc", search: "abcd"},

{str: "xaxbxc", search: "a", matchedIndexes: []int{1}},
{str: "xaxbxc", search: "ab", matchedIndexes: []int{1, 3}},
{str: "xaxbxc", search: "ac", matchedIndexes: []int{1, 5}},
{str: "xaxbxc", search: "abc", matchedIndexes: []int{1, 3, 5}},
{str: "xaxbxc", search: "b", matchedIndexes: []int{3}},
{str: "xaxbxc", search: "bc", matchedIndexes: []int{3, 5}},
{str: "xaxbxc", search: "c", matchedIndexes: []int{5}},
{str: "xaxbxc", search: "cba"},
{str: "xaxbxc", search: "d"},
{str: "xaxbxc", search: "abcd"},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
assertFuzzySearch(
t,
fuzzySearchArgs{str: tt.str, search: tt.search},
Match{Str: tt.str, MatchedIndexes: tt.matchedIndexes},
tt.matchedIndexes != nil,
)
})
}
}

func Test_fuzzySearch_caseSensitive(t *testing.T) {
tests := []struct {
str string
search string
matchedIndexes []int
}{
{str: "abc", search: "abc", matchedIndexes: []int{0, 1, 2}},
{str: "abc", search: "Abc"},
{str: "abc", search: "ABC"},

{str: "Abc", search: "abc"},
{str: "Abc", search: "Abc", matchedIndexes: []int{0, 1, 2}},
{str: "Abc", search: "ABC"},

{str: "ABC", search: "abc"},
{str: "ABC", search: "Abc"},
{str: "ABC", search: "ABC", matchedIndexes: []int{0, 1, 2}},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
assertFuzzySearch(
t,
fuzzySearchArgs{str: tt.str, search: tt.search, option: searchOption{caseSensitive: true}},
Match{Str: tt.str, MatchedIndexes: tt.matchedIndexes},
tt.matchedIndexes != nil,
)
})
}
}

0 comments on commit 42d1755

Please sign in to comment.