mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-06-23 00:39:13 +02:00
Start on supporting auto-suggestions when checking out a branch
switch to other fuzzy package with no dependencies
This commit is contained in:
2
vendor/github.com/sahilm/fuzzy/.gitignore
generated
vendored
Normal file
2
vendor/github.com/sahilm/fuzzy/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
vendor/
|
||||
coverage/
|
1
vendor/github.com/sahilm/fuzzy/CONTRIBUTING.md
generated
vendored
Normal file
1
vendor/github.com/sahilm/fuzzy/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
Everyone is welcome to contribute. Please send me a pull request or file an issue. I promise to respond promptly.
|
20
vendor/github.com/sahilm/fuzzy/Gopkg.lock
generated
vendored
Normal file
20
vendor/github.com/sahilm/fuzzy/Gopkg.lock
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:ee97ec8a00b2424570c1ce53d7b410e96fbd4c241b29df134276ff6aa3750335"
|
||||
name = "github.com/kylelemons/godebug"
|
||||
packages = [
|
||||
"diff",
|
||||
"pretty",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "d65d576e9348f5982d7f6d83682b694e731a45c6"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = ["github.com/kylelemons/godebug/pretty"]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
4
vendor/github.com/sahilm/fuzzy/Gopkg.toml
generated
vendored
Normal file
4
vendor/github.com/sahilm/fuzzy/Gopkg.toml
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# Test dependency
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/kylelemons/godebug"
|
21
vendor/github.com/sahilm/fuzzy/LICENSE
generated
vendored
Normal file
21
vendor/github.com/sahilm/fuzzy/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Sahil Muthoo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
57
vendor/github.com/sahilm/fuzzy/Makefile
generated
vendored
Normal file
57
vendor/github.com/sahilm/fuzzy/Makefile
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
.PHONY: all
|
||||
all: setup lint test
|
||||
|
||||
.PHONY: test
|
||||
test: setup
|
||||
go test -bench ./...
|
||||
|
||||
.PHONY: cover
|
||||
cover: setup
|
||||
mkdir -p coverage
|
||||
gocov test ./... | gocov-html > coverage/coverage.html
|
||||
|
||||
sources = $(shell find . -name '*.go' -not -path './vendor/*')
|
||||
.PHONY: goimports
|
||||
goimports: setup
|
||||
goimports -w $(sources)
|
||||
|
||||
.PHONY: lint
|
||||
lint: setup
|
||||
gometalinter ./... --enable=goimports --disable=gocyclo --vendor -t
|
||||
|
||||
.PHONY: install
|
||||
install: setup
|
||||
go install
|
||||
|
||||
BIN_DIR := $(GOPATH)/bin
|
||||
GOIMPORTS := $(BIN_DIR)/goimports
|
||||
GOMETALINTER := $(BIN_DIR)/gometalinter
|
||||
DEP := $(BIN_DIR)/dep
|
||||
GOCOV := $(BIN_DIR)/gocov
|
||||
GOCOV_HTML := $(BIN_DIR)/gocov-html
|
||||
|
||||
$(GOIMPORTS):
|
||||
go get -u golang.org/x/tools/cmd/goimports
|
||||
|
||||
$(GOMETALINTER):
|
||||
go get -u github.com/alecthomas/gometalinter
|
||||
gometalinter --install &> /dev/null
|
||||
|
||||
$(GOCOV):
|
||||
go get -u github.com/axw/gocov/gocov
|
||||
|
||||
$(GOCOV_HTML):
|
||||
go get -u gopkg.in/matm/v1/gocov-html
|
||||
|
||||
$(DEP):
|
||||
go get -u github.com/golang/dep/cmd/dep
|
||||
|
||||
tools: $(GOIMPORTS) $(GOMETALINTER) $(GOCOV) $(GOCOV_HTML) $(DEP)
|
||||
|
||||
vendor: $(DEP)
|
||||
dep ensure
|
||||
|
||||
setup: tools vendor
|
||||
|
||||
updatedeps:
|
||||
dep ensure -update
|
184
vendor/github.com/sahilm/fuzzy/README.md
generated
vendored
Normal file
184
vendor/github.com/sahilm/fuzzy/README.md
generated
vendored
Normal file
@ -0,0 +1,184 @@
|
||||
<img src="assets/search-gopher-1.png" alt="gopher looking for stuff"> <img src="assets/search-gopher-2.png" alt="gopher found stuff">
|
||||
|
||||
# fuzzy
|
||||
[](https://travis-ci.org/sahilm/fuzzy)
|
||||
[](https://godoc.org/github.com/sahilm/fuzzy)
|
||||
|
||||
Go library that provides fuzzy string matching optimized for filenames and code symbols in the style of Sublime Text,
|
||||
VSCode, IntelliJ IDEA et al. This library is external dependency-free. It only depends on the Go standard library.
|
||||
|
||||
## Features
|
||||
|
||||
- Intuitive matching. Results are returned in descending order of match quality. Quality is determined by:
|
||||
- The first character in the pattern matches the first character in the match string.
|
||||
- The matched character is camel cased.
|
||||
- The matched character follows a separator such as an underscore character.
|
||||
- The matched character is adjacent to a previous match.
|
||||
|
||||
- Speed. Matches are returned in milliseconds. It's perfect for interactive search boxes.
|
||||
|
||||
- The positions of matches is returned. Allows you to highlight matching characters.
|
||||
|
||||
- Unicode aware.
|
||||
|
||||
## Demo
|
||||
|
||||
Here is a [demo](_example/main.go) of matching various patterns against ~16K files from the Unreal Engine 4 codebase.
|
||||
|
||||

|
||||
|
||||
You can run the demo yourself like so:
|
||||
|
||||
```
|
||||
cd _example/
|
||||
go get github.com/jroimartin/gocui
|
||||
go run main.go
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The following example prints out matches with the matched chars in bold.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sahilm/fuzzy"
|
||||
)
|
||||
|
||||
func main() {
|
||||
const bold = "\033[1m%s\033[0m"
|
||||
pattern := "mnr"
|
||||
data := []string{"game.cpp", "moduleNameResolver.ts", "my name is_Ramsey"}
|
||||
|
||||
matches := fuzzy.Find(pattern, data)
|
||||
|
||||
for _, match := range matches {
|
||||
for i := 0; i < len(match.Str); i++ {
|
||||
if contains(i, match.MatchedIndexes) {
|
||||
fmt.Print(fmt.Sprintf(bold, string(match.Str[i])))
|
||||
} else {
|
||||
fmt.Print(string(match.Str[i]))
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
func contains(needle int, haystack []int) bool {
|
||||
for _, i := range haystack {
|
||||
if needle == i {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
```
|
||||
If the data you want to match isn't a slice of strings, you can use `FindFromSource` by implementing
|
||||
the provided `Source` interface. Here's an example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sahilm/fuzzy"
|
||||
)
|
||||
|
||||
type employee struct {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
type employees []employee
|
||||
|
||||
func (e employees) String(i int) string {
|
||||
return e[i].name
|
||||
}
|
||||
|
||||
func (e employees) Len() int {
|
||||
return len(e)
|
||||
}
|
||||
|
||||
func main() {
|
||||
emps := employees{
|
||||
{
|
||||
name: "Alice",
|
||||
age: 45,
|
||||
},
|
||||
{
|
||||
name: "Bob",
|
||||
age: 35,
|
||||
},
|
||||
{
|
||||
name: "Allie",
|
||||
age: 35,
|
||||
},
|
||||
}
|
||||
results := fuzzy.FindFrom("al", emps)
|
||||
fmt.Println(results)
|
||||
}
|
||||
```
|
||||
|
||||
Check out the [godoc](https://godoc.org/github.com/sahilm/fuzzy) for detailed documentation.
|
||||
|
||||
## Installation
|
||||
|
||||
`go get github.com/sahilm/fuzzy` or use your favorite dependency management tool.
|
||||
|
||||
## Speed
|
||||
|
||||
Here are a few benchmark results on a normal laptop.
|
||||
|
||||
```
|
||||
BenchmarkFind/with_unreal_4_(~16K_files)-4 100 12915315 ns/op
|
||||
BenchmarkFind/with_linux_kernel_(~60K_files)-4 50 30885038 ns/op
|
||||
```
|
||||
|
||||
Matching a pattern against ~60K files from the Linux kernel takes about 30ms.
|
||||
|
||||
## Contributing
|
||||
|
||||
Everyone is welcome to contribute. Please send me a pull request or file an issue. I promise
|
||||
to respond promptly.
|
||||
|
||||
## Credits
|
||||
|
||||
* [@ericpauley](https://github.com/ericpauley) & [@lunixbochs](https://github.com/lunixbochs) contributed Unicode awareness and various performance optimisations.
|
||||
|
||||
* The algorithm is based of the awesome work of [forrestthewoods](https://github.com/forrestthewoods/lib_fts/blob/master/code/fts_fuzzy_match.js).
|
||||
See [this](https://blog.forrestthewoods.com/reverse-engineering-sublime-text-s-fuzzy-match-4cffeed33fdb#.d05n81yjy)
|
||||
blog post for details of the algorithm.
|
||||
|
||||
* The artwork is by my lovely wife Sanah. It's based on the Go Gopher.
|
||||
|
||||
* The Go gopher was designed by Renee French (http://reneefrench.blogspot.com/).
|
||||
The design is licensed under the Creative Commons 3.0 Attributions license.
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Sahil Muthoo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
235
vendor/github.com/sahilm/fuzzy/fuzzy.go
generated
vendored
Normal file
235
vendor/github.com/sahilm/fuzzy/fuzzy.go
generated
vendored
Normal file
@ -0,0 +1,235 @@
|
||||
/*
|
||||
Package fuzzy provides fuzzy string matching optimized
|
||||
for filenames and code symbols in the style of Sublime Text,
|
||||
VSCode, IntelliJ IDEA et al.
|
||||
*/
|
||||
package fuzzy
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Match represents a matched string.
|
||||
type Match struct {
|
||||
// The matched string.
|
||||
Str string
|
||||
// The index of the matched string in the supplied slice.
|
||||
Index int
|
||||
// The indexes of matched characters. Useful for highlighting matches.
|
||||
MatchedIndexes []int
|
||||
// Score used to rank matches
|
||||
Score int
|
||||
}
|
||||
|
||||
const (
|
||||
firstCharMatchBonus = 10
|
||||
matchFollowingSeparatorBonus = 20
|
||||
camelCaseMatchBonus = 20
|
||||
adjacentMatchBonus = 5
|
||||
unmatchedLeadingCharPenalty = -5
|
||||
maxUnmatchedLeadingCharPenalty = -15
|
||||
)
|
||||
|
||||
var separators = []rune("/-_ .\\")
|
||||
|
||||
// Matches is a slice of Match structs
|
||||
type Matches []Match
|
||||
|
||||
func (a Matches) Len() int { return len(a) }
|
||||
func (a Matches) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a Matches) Less(i, j int) bool { return a[i].Score >= a[j].Score }
|
||||
|
||||
// Source represents an abstract source of a list of strings. Source must be iterable type such as a slice.
|
||||
// The source will be iterated over till Len() with String(i) being called for each element where i is the
|
||||
// index of the element. You can find a working example in the README.
|
||||
type Source interface {
|
||||
// The string to be matched at position i.
|
||||
String(i int) string
|
||||
// The length of the source. Typically is the length of the slice of things that you want to match.
|
||||
Len() int
|
||||
}
|
||||
|
||||
type stringSource []string
|
||||
|
||||
func (ss stringSource) String(i int) string {
|
||||
return ss[i]
|
||||
}
|
||||
|
||||
func (ss stringSource) Len() int { return len(ss) }
|
||||
|
||||
/*
|
||||
Find looks up pattern in data and returns matches
|
||||
in descending order of match quality. Match quality
|
||||
is determined by a set of bonus and penalty rules.
|
||||
|
||||
The following types of matches apply a bonus:
|
||||
|
||||
* The first character in the pattern matches the first character in the match string.
|
||||
|
||||
* The matched character is camel cased.
|
||||
|
||||
* The matched character follows a separator such as an underscore character.
|
||||
|
||||
* The matched character is adjacent to a previous match.
|
||||
|
||||
Penalties are applied for every character in the search string that wasn't matched and all leading
|
||||
characters upto the first match.
|
||||
*/
|
||||
func Find(pattern string, data []string) Matches {
|
||||
return FindFrom(pattern, stringSource(data))
|
||||
}
|
||||
|
||||
/*
|
||||
FindFrom is an alternative implementation of Find using a Source
|
||||
instead of a list of strings.
|
||||
*/
|
||||
func FindFrom(pattern string, data Source) Matches {
|
||||
if len(pattern) == 0 {
|
||||
return nil
|
||||
}
|
||||
runes := []rune(pattern)
|
||||
var matches Matches
|
||||
var matchedIndexes []int
|
||||
for i := 0; i < data.Len(); i++ {
|
||||
var match Match
|
||||
match.Str = data.String(i)
|
||||
match.Index = i
|
||||
if matchedIndexes != nil {
|
||||
match.MatchedIndexes = matchedIndexes
|
||||
} else {
|
||||
match.MatchedIndexes = make([]int, 0, len(runes))
|
||||
}
|
||||
var score int
|
||||
patternIndex := 0
|
||||
bestScore := -1
|
||||
matchedIndex := -1
|
||||
currAdjacentMatchBonus := 0
|
||||
var last rune
|
||||
var lastIndex int
|
||||
nextc, nextSize := utf8.DecodeRuneInString(data.String(i))
|
||||
var candidate rune
|
||||
var candidateSize int
|
||||
for j := 0; j < len(data.String(i)); j += candidateSize {
|
||||
candidate, candidateSize = nextc, nextSize
|
||||
if equalFold(candidate, runes[patternIndex]) {
|
||||
score = 0
|
||||
if j == 0 {
|
||||
score += firstCharMatchBonus
|
||||
}
|
||||
if unicode.IsLower(last) && unicode.IsUpper(candidate) {
|
||||
score += camelCaseMatchBonus
|
||||
}
|
||||
if j != 0 && isSeparator(last) {
|
||||
score += matchFollowingSeparatorBonus
|
||||
}
|
||||
if len(match.MatchedIndexes) > 0 {
|
||||
lastMatch := match.MatchedIndexes[len(match.MatchedIndexes)-1]
|
||||
bonus := adjacentCharBonus(lastIndex, lastMatch, currAdjacentMatchBonus)
|
||||
score += bonus
|
||||
// adjacent matches are incremental and keep increasing based on previous adjacent matches
|
||||
// thus we need to maintain the current match bonus
|
||||
currAdjacentMatchBonus += bonus
|
||||
}
|
||||
if score > bestScore {
|
||||
bestScore = score
|
||||
matchedIndex = j
|
||||
}
|
||||
}
|
||||
var nextp rune
|
||||
if patternIndex < len(runes)-1 {
|
||||
nextp = runes[patternIndex+1]
|
||||
}
|
||||
if j+candidateSize < len(data.String(i)) {
|
||||
if data.String(i)[j+candidateSize] < utf8.RuneSelf { // Fast path for ASCII
|
||||
nextc, nextSize = rune(data.String(i)[j+candidateSize]), 1
|
||||
} else {
|
||||
nextc, nextSize = utf8.DecodeRuneInString(data.String(i)[j+candidateSize:])
|
||||
}
|
||||
} else {
|
||||
nextc, nextSize = 0, 0
|
||||
}
|
||||
// We apply the best score when we have the next match coming up or when the search string has ended.
|
||||
// Tracking when the next match is coming up allows us to exhaustively find the best match and not necessarily
|
||||
// the first match.
|
||||
// For example given the pattern "tk" and search string "The Black Knight", exhaustively matching allows us
|
||||
// to match the second k thus giving this string a higher score.
|
||||
if equalFold(nextp, nextc) || nextc == 0 {
|
||||
if matchedIndex > -1 {
|
||||
if len(match.MatchedIndexes) == 0 {
|
||||
penalty := matchedIndex * unmatchedLeadingCharPenalty
|
||||
bestScore += max(penalty, maxUnmatchedLeadingCharPenalty)
|
||||
}
|
||||
match.Score += bestScore
|
||||
match.MatchedIndexes = append(match.MatchedIndexes, matchedIndex)
|
||||
score = 0
|
||||
bestScore = -1
|
||||
patternIndex++
|
||||
}
|
||||
}
|
||||
lastIndex = j
|
||||
last = candidate
|
||||
}
|
||||
// apply penalty for each unmatched character
|
||||
penalty := len(match.MatchedIndexes) - len(data.String(i))
|
||||
match.Score += penalty
|
||||
if len(match.MatchedIndexes) == len(runes) {
|
||||
matches = append(matches, match)
|
||||
matchedIndexes = nil
|
||||
} else {
|
||||
matchedIndexes = match.MatchedIndexes[:0] // Recycle match index slice
|
||||
}
|
||||
}
|
||||
sort.Stable(matches)
|
||||
return matches
|
||||
}
|
||||
|
||||
// Taken from strings.EqualFold
|
||||
func equalFold(tr, sr rune) bool {
|
||||
if tr == sr {
|
||||
return true
|
||||
}
|
||||
if tr < sr {
|
||||
tr, sr = sr, tr
|
||||
}
|
||||
// Fast check for ASCII.
|
||||
if tr < utf8.RuneSelf {
|
||||
// ASCII, and sr is upper case. tr must be lower case.
|
||||
if 'A' <= sr && sr <= 'Z' && tr == sr+'a'-'A' {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// General case. SimpleFold(x) returns the next equivalent rune > x
|
||||
// or wraps around to smaller values.
|
||||
r := unicode.SimpleFold(sr)
|
||||
for r != sr && r < tr {
|
||||
r = unicode.SimpleFold(r)
|
||||
}
|
||||
return r == tr
|
||||
}
|
||||
|
||||
func adjacentCharBonus(i int, lastMatch int, currentBonus int) int {
|
||||
if lastMatch == i {
|
||||
return currentBonus*2 + adjacentMatchBonus
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func isSeparator(s rune) bool {
|
||||
for _, sep := range separators {
|
||||
if s == sep {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func max(x int, y int) int {
|
||||
if x > y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
Reference in New Issue
Block a user