fzf/src/algo.go

153 lines
3.3 KiB
Go

package fzf
import "strings"
/*
* String matching algorithms here do not use strings.ToLower to avoid
* performance penalty. And they assume pattern runes are given in lowercase
* letters when caseSensitive is false.
*
* In short: They try to do as little work as possible.
*/
func FuzzyMatch(caseSensitive bool, input *string, pattern []rune) (int, int) {
runes := []rune(*input)
// 0. (FIXME) How to find the shortest match?
// a_____b__c__abc
// ^^^^^^^^^^ ^^^
// 1. forward scan (abc)
// *-----*-----*>
// a_____b___abc__
// 2. reverse scan (cba)
// a_____b___abc__
// <***
pidx := 0
sidx := -1
eidx := -1
for index, char := range runes {
// This is considerably faster than blindly applying strings.ToLower to the
// whole string
if !caseSensitive && char >= 65 && char <= 90 {
char += 32
}
if char == pattern[pidx] {
if sidx < 0 {
sidx = index
}
if pidx += 1; pidx == len(pattern) {
eidx = index + 1
break
}
}
}
if sidx >= 0 && eidx >= 0 {
pidx -= 1
for index := eidx - 1; index >= sidx; index-- {
char := runes[index]
if !caseSensitive && char >= 65 && char <= 90 {
char += 32
}
if char == pattern[pidx] {
if pidx -= 1; pidx < 0 {
sidx = index
break
}
}
}
return sidx, eidx
}
return -1, -1
}
func ExactMatchStrings(caseSensitive bool, input *string, pattern []rune) (int, int) {
var str string
if caseSensitive {
str = *input
} else {
str = strings.ToLower(*input)
}
if idx := strings.Index(str, string(pattern)); idx >= 0 {
prefixRuneLen := len([]rune((*input)[:idx]))
return prefixRuneLen, prefixRuneLen + len(pattern)
}
return -1, -1
}
/*
* This is a basic string searching algorithm that handles case sensitivity.
* Although naive, it still performs better than the combination of
* strings.ToLower + strings.Index for typical fzf use cases where input
* strings and patterns are not very long.
*
* We might try to implement better algorithms in the future:
* http://en.wikipedia.org/wiki/String_searching_algorithm
*/
func ExactMatchNaive(caseSensitive bool, input *string, pattern []rune) (int, int) {
runes := []rune(*input)
numRunes := len(runes)
plen := len(pattern)
if len(runes) < plen {
return -1, -1
}
pidx := 0
for index := 0; index < numRunes; index++ {
char := runes[index]
if !caseSensitive && char >= 65 && char <= 90 {
char += 32
}
if pattern[pidx] == char {
pidx += 1
if pidx == plen {
return index - plen + 1, index + 1
}
} else {
index -= pidx
pidx = 0
}
}
return -1, -1
}
func PrefixMatch(caseSensitive bool, input *string, pattern []rune) (int, int) {
runes := []rune(*input)
if len(runes) < len(pattern) {
return -1, -1
}
for index, r := range pattern {
char := runes[index]
if !caseSensitive && char >= 65 && char <= 90 {
char += 32
}
if char != r {
return -1, -1
}
}
return 0, len(pattern)
}
func SuffixMatch(caseSensitive bool, input *string, pattern []rune) (int, int) {
runes := []rune(strings.TrimRight(*input, " "))
trimmedLen := len(runes)
diff := trimmedLen - len(pattern)
if diff < 0 {
return -1, -1
}
for index, r := range pattern {
char := runes[index+diff]
if !caseSensitive && char >= 65 && char <= 90 {
char += 32
}
if char != r {
return -1, -1
}
}
return trimmedLen - len(pattern), trimmedLen
}