Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve fuzzy search performance #14

Merged
merged 3 commits into from Mar 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion go.mod
Expand Up @@ -6,7 +6,6 @@ require (
github.com/charmbracelet/bubbles v0.15.0
github.com/charmbracelet/bubbletea v0.23.2
github.com/charmbracelet/lipgloss v0.7.1
github.com/sahilm/fuzzy v0.1.0
github.com/spf13/cobra v1.6.1
)

Expand Down
2 changes: 0 additions & 2 deletions go.sum
Expand Up @@ -18,7 +18,6 @@ github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkX
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
Expand Down Expand Up @@ -49,7 +48,6 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
Expand Down
23 changes: 11 additions & 12 deletions model.go
Expand Up @@ -7,7 +7,6 @@ import (
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/sahilm/fuzzy"
)

var (
Expand Down Expand Up @@ -35,7 +34,7 @@ type model struct {
cursorLineStyle lipgloss.Style
cursorLineMatchesStyle lipgloss.Style

matches fuzzy.Matches
matches matches
choices []int

// window
Expand All @@ -57,9 +56,9 @@ func newModel(fzf *FZF, items *items, opt *findOption) *model {
fzf.option.keymap.Toggle.SetEnabled(false)
}

var matches fuzzy.Matches
var matches matches
for i := 0; i < items.Len(); i++ {
matches = append(matches, fuzzy.Match{
matches = append(matches, match{
Str: items.String(i),
Index: i,
})
Expand Down Expand Up @@ -125,7 +124,7 @@ func (m *model) headerView() string {
// count
if m.fzf.option.countViewEnabled {
_, _ = v.WriteRune('\n')
_, _ = v.WriteString(m.fzf.option.countViewFunc(m.items.Len(), m.matches.Len(), m.windowWidth))
_, _ = v.WriteString(m.fzf.option.countViewFunc(m.items.Len(), len(m.matches), m.windowWidth))
}

return v.String()
Expand Down Expand Up @@ -250,15 +249,15 @@ func (m *model) choice() {
return
}

if m.matches.Len() == 0 {
if len(m.matches) == 0 {
return
}

m.choices = append(m.choices, m.matches[m.cursorPosition].Index)
}

func (m *model) toggle() {
if m.matches.Len() == 0 {
if len(m.matches) == 0 {
return
}

Expand Down Expand Up @@ -287,9 +286,9 @@ func (m *model) cursorDown() {
func (m *model) filter() {
s := m.input.Value()
if s == "" {
var matches fuzzy.Matches
var matches matches
for i := 0; i < m.items.Len(); i++ {
matches = append(matches, fuzzy.Match{
matches = append(matches, match{
Str: m.items.String(i),
Index: i,
})
Expand All @@ -298,7 +297,7 @@ func (m *model) filter() {
return
}

m.matches = fuzzy.FindFrom(s, m.items)
m.matches = fuzzySearch(m.items, s)
}

func (m *model) fixCursor() {
Expand All @@ -308,7 +307,7 @@ func (m *model) fixCursor() {
}

if m.cursorPosition+1 > len(m.matches) {
m.cursorPosition = len(m.matches) - 1
m.cursorPosition = max(len(m.matches)-1, 0)
return
}
}
Expand All @@ -327,7 +326,7 @@ func (m *model) fixYPosition() {
}

if m.cursorPosition+1 >= (m.windowHeight-headerHeight)+m.windowYPosition {
m.windowYPosition = m.cursorPosition + 1 - (m.windowHeight - headerHeight)
m.windowYPosition = max(m.cursorPosition+1-(m.windowHeight-headerHeight), 0)
return
}
}
90 changes: 90 additions & 0 deletions search.go
@@ -0,0 +1,90 @@
package fzf

import (
"sort"
"sync"
)

type match struct {
Str string
Index int
MatchedIndexes []int
}

type matches []match

func (m matches) Sort() {
sort.Slice(m, func(i, j int) bool {
mi, mj := m[i].MatchedIndexes, m[j].MatchedIndexes
li, lj := len(mi), len(mj)

if li != lj {
return li < lj
}

for k := 0; k < li; k++ {
if mi[k] != mj[k] {
return mi[k] < mj[k]
}
}

return m[i].Index < m[j].Index
})
}

func fuzzySearch(items *items, search string) matches {
result := make(matches, 0, items.Len())
resultMutex := sync.Mutex{}
wg := sync.WaitGroup{}

numWorkers := 8
chunkSize := (items.Len() + numWorkers - 1) / numWorkers
chunks := make(chan int, numWorkers)

for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()

for start := range chunks {
end := start + chunkSize
if end > items.Len() {
end = items.Len()
}

localMatches := make(matches, 0)

for index := start; index < end; index++ {
item := items.String(index)
matchedIndexes := make([]int, 0, len(search))
j := 0

for i, r := range item {
if j < len(search) && r == rune(search[j]) {
matchedIndexes = append(matchedIndexes, i)
j++
}
}

if j == len(search) {
m := match{Str: item, Index: index, MatchedIndexes: matchedIndexes}
localMatches = append(localMatches, m)
}
}

resultMutex.Lock()
result = append(result, localMatches...)
resultMutex.Unlock()
}
}()
}

for i := 0; i < items.Len(); i += chunkSize {
chunks <- i
}
close(chunks)
wg.Wait()

result.Sort()
return result
}