git.haldean.org e / f259a6d
fuzzy file finder haldean 1 year, 5 months ago
2 changed file(s) with 96 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
00 e
11 e.exe
22 err.txt
3 find
0 import algorithm
1 import options
2 import os
3 import re
4 import sets
5 import sequtils
6
7 type
8 Found = seq[string]
9 Score = (float, float)
10
11 let exclude = toHashSet([".git", "gen", "inst"])
12
13 proc includef(fname: string): bool =
14 var x = fname
15 while true:
16 let (dir, base) = splitPath(x)
17 if exclude.contains(base):
18 return false
19 if base[0] == '.':
20 return false
21 if dir == "/" or dir == "":
22 return true
23 x = dir
24
25 proc find*(root: string): Found =
26 var found = newSeq[string]()
27 for file in walkDirRec(root):
28 if includef(file):
29 found.add(file)
30 return found
31
32 proc lcss(s1, s2: string): int =
33 let
34 m1 = s1.len - 1
35 m2 = s2.len - 1
36 var
37 cache = newSeq[seq[int]]()
38 maxlen = 0
39
40 for i in 0..m1:
41 var s = newSeq[int]()
42 for i in 0..m2:
43 s.add(0)
44 cache.add(s)
45
46 for i1 in 0..m1:
47 for i2 in 0..m2:
48 if s1[i1] == s2[i2]:
49 var x = 0
50 if i1 == 0 or i2 == 0:
51 x = 1
52 else:
53 x = cache[i1-1][i2-1] + 1
54 cache[i1][i2] = x
55 if x > maxlen:
56 maxlen = x
57 return maxlen
58
59 proc score(path, search: string): Score =
60 let
61 (_, base) = splitPath(path)
62 basescore = lcss(search, base).float
63 fullscore = lcss(search, path).float
64 # In the event of a tie, break the tie by which has the longest match in the
65 # basename of the file. Negated so that lower scores are better, so we can
66 # ascending-sort on score and file name
67 return (-fullscore, -basescore)
68
69 proc find*(root, search: string): Found =
70 var patstr = ""
71 for ch in search:
72 patstr = patstr & ".*" & ch
73 patstr = patstr & ".*"
74 let pat = re(patstr, {reIgnoreCase})
75
76 let found = find(root).filter do (abspath: string) -> bool:
77 return match(relativePath(abspath, root), pat)
78
79 var scored = found.map do (abspath: string) -> (Score, string):
80 let relpath = relativePath(abspath, root)
81 return (score(relpath, search), relpath)
82
83 scored.sort()
84 return scored.map do (p: (Score, string)) -> string: p[1]
85
86 when isMainModule:
87 var found: Found
88 if paramCount() < 1:
89 found = find(getCurrentDir())
90 else:
91 let search = paramstr(1)
92 found = find(getCurrentDir(), search)
93 for f in found:
94 echo f