diff --git a/go.mod b/go.mod index e5ae6fa..fe8144a 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index e17a07c..f00cc33 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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= diff --git a/model.go b/model.go index 857a958..82b2aed 100644 --- a/model.go +++ b/model.go @@ -7,7 +7,6 @@ import ( "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/sahilm/fuzzy" ) var ( @@ -35,7 +34,7 @@ type model struct { cursorLineStyle lipgloss.Style cursorLineMatchesStyle lipgloss.Style - matches fuzzy.Matches + matches matches choices []int // window @@ -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, }) @@ -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() @@ -250,7 +249,7 @@ func (m *model) choice() { return } - if m.matches.Len() == 0 { + if len(m.matches) == 0 { return } @@ -258,7 +257,7 @@ func (m *model) choice() { } func (m *model) toggle() { - if m.matches.Len() == 0 { + if len(m.matches) == 0 { return } @@ -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, }) @@ -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() { @@ -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 } } @@ -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 } } diff --git a/search.go b/search.go new file mode 100644 index 0000000..05d8de9 --- /dev/null +++ b/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 +}