Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 18b216c
Showing
4 changed files
with
155 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
similar-sort is licensed CC BY-SA 4.0. | ||
|
||
"Wait, why?", I hear you say, "That's an unusual choice for source code!" | ||
Put quite simply, it's because this package is a wrapper around a Levenshtein distance implementation I got from WikiPedia, which is licensed under CC BY-SA 3.0. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# Similar Sort | ||
|
||
This is a small Go program that will: | ||
|
||
1. take a reference string as the first argument | ||
2. and a list of candidate strings in stdin | ||
3. and output the candidates sorted according to their edit distance from the reference, lowest first. | ||
|
||
"What use is this?" you may ask! | ||
Well! | ||
It turns out to be really useful to do fuzzy file finding a large project. | ||
|
||
When I am in some filesystem hierarchy and I trigger my fuzzy-finder, I want to see sibling files before I see similarly-named files further away. | ||
I also want to match on test files pretty easily. | ||
Say I have this project structure: | ||
|
||
``` | ||
example | ||
└── src | ||
├── Main.elm | ||
└── Page | ||
└── Learn | ||
└── Home | ||
├── Main.elm | ||
└── View.elm | ||
``` | ||
|
||
If I am in `src/Page/Learn/Home/View.elm` and I want to get to the sibling file `Main.elm`, the default `fzf` config shows me `src/Main.elm` first. | ||
That's not what I wanted! | ||
|
||
But if I sort the files instead by passing them through `similar-sort src/Page/Learn/Home/View.elm`, the sibling file will show up first. | ||
This works surprisingly well, and I really like it! | ||
|
||
It could probably perform a *little* better by doing some heuristic based on equivalent file structure except for the addition/removal of "tests", "specs", etc, but I haven't bothered yet. | ||
|
||
## Installing | ||
|
||
You can look in `dotfiles/kakoune.nix` in the root of this project to see how to use this in a home-manager context. | ||
If you're not using home-manager, or you just want to install it globally, `cd` here and type: | ||
|
||
```sh | ||
nix-env -if . | ||
``` | ||
|
||
OR if you have `go` installed but not `nix`, just `go build similar-sort.go`; it has no external dependencies and will result in a static binary you can put wherever. | ||
|
||
### Adding to Vim | ||
|
||
Add this to your vim config: | ||
|
||
```vim | ||
nnoremap <silent> <C-t> :call fzf#run(fzf#wrap({ | ||
\ "source": "git ls-files --others --cached --exclude-standard \| similar-sort " . @% . " \| grep -v " . @%, | ||
\ "sink": "edit", | ||
\ "options": "--tiebreak index" | ||
\ }))<CR> | ||
``` | ||
|
||
(You'll need `fzf` and `fzf.vim` installed.) | ||
This will bind ctrl-t to the fuzzy finder. | ||
When you select a match, it will open in the current pane. | ||
|
||
If you want to split or vsplit, change `"sink": "edit"` to `"sink": "split"` or `"sink": "vsplit"`. | ||
See the docs for `fzf#run` for more customization options. | ||
|
||
### Adding to Kakoune | ||
|
||
I use [connect.kak](https://github.com/alexherbo2/connect.kak) to spawn a terminal window with about the same command line as in the vim config above. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ pkgs ? import <nixpkgs> { }, ... }: | ||
pkgs.stdenv.mkDerivation { | ||
name = "similar-sort"; | ||
buildInputs = [ pkgs.go ]; | ||
src = ./.; | ||
|
||
buildPhase = '' | ||
env HOME=$(pwd) GOPATH=$(pwd) go build similar-sort.go | ||
''; | ||
|
||
installPhase = '' | ||
mkdir -p $out/bin | ||
cp similar-sort $out/bin | ||
''; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package main | ||
|
||
import "bufio" | ||
import "fmt" | ||
import "os" | ||
import "sort" | ||
import "strings" | ||
import "unicode/utf8" | ||
|
||
func main() { | ||
target := strings.Join(os.Args[1:], " ") | ||
|
||
s := bufio.NewScanner(os.Stdin) | ||
var lines []WithDistance | ||
for s.Scan() { | ||
lines = append(lines, WithDistance{s.Text(), Levenshtein(target, s.Text())}) | ||
} | ||
|
||
sort.Slice(lines, func(i, j int) bool { | ||
return lines[i].distance < lines[j].distance | ||
}) | ||
|
||
for _, line := range lines { | ||
fmt.Println(line.text) | ||
} | ||
} | ||
|
||
type WithDistance struct { | ||
text string | ||
distance int | ||
} | ||
|
||
// https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Go | ||
// made available under CC BY-SA 3.0 https://creativecommons.org/licenses/by-sa/3.0/ | ||
func Levenshtein(a, b string) int { | ||
f := make([]int, utf8.RuneCountInString(b)+1) | ||
|
||
for j := range f { | ||
f[j] = j | ||
} | ||
|
||
for _, ca := range a { | ||
j := 1 | ||
fj1 := f[0] // fj1 is the value of f[j - 1] in last iteration | ||
f[0]++ | ||
for _, cb := range b { | ||
mn := min(f[j]+1, f[j-1]+1) // delete & insert | ||
if cb != ca { | ||
mn = min(mn, fj1+1) // change | ||
} else { | ||
mn = min(mn, fj1) // matched | ||
} | ||
|
||
fj1, f[j] = f[j], mn // save f[j] to fj1(j is about to increase), update f[j] to mn | ||
j++ | ||
} | ||
} | ||
|
||
return f[len(f)-1] | ||
} | ||
|
||
func min(a, b int) int { | ||
if a <= b { | ||
return a | ||
} else { | ||
return b | ||
} | ||
} |