-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
174 lines (162 loc) · 3.9 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"syscall"
"github.com/docopt/docopt-go"
"github.com/manifoldco/promptui"
"github.com/renstrom/fuzzysearch/fuzzy"
)
var exit_ctrl_c = 130
var debug bool
// TODO josh: read this out of .gitignore if available
var ignorePatterns = []string{".min.js", ".git", "node_modules"}
func files(dir string, customIgnore []string) []string {
files := make([]string, 0, 30)
ignores := append(ignorePatterns, customIgnore...)
// TODO josh: skip directories
filepath.Walk(dir, func(path string, file os.FileInfo, err error) error {
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
for _, pattern := range ignores {
if strings.Contains(path, pattern) {
if (file.IsDir()) {
return filepath.SkipDir
} else {
return nil
}
}
}
if (!file.IsDir()) {
files = append(files, path)
}
return nil
})
return files
}
func mustBaseDir(gitRoot bool) string {
dir, err := os.Getwd()
if err != nil {
panic(err)
}
if (gitRoot) {
output, err := exec.Command("git", "rev-parse", "--show-toplevel").Output()
if err != nil {
fmt.Println()
fmt.Fprintln(os.Stderr, "Could not find git root")
os.Exit(1)
}
dir = strings.Trim(string(output), "\n")
}
return dir+"/"
}
func fileMatches(gitRoot bool, pattern string, ignore []string) []string {
dir := mustBaseDir(gitRoot)
if debug {
fmt.Fprintln(os.Stderr, "searching "+pattern+" in "+dir)
}
rel_files := make([]string, 0, 20)
for _, path := range files(dir, ignore) {
rel_path := path[len(dir):]
rel_files = append(rel_files, rel_path)
}
out_files := make([]string, 0, 20)
ranked := fuzzy.RankFind(pattern, rel_files)
sort.Slice(ranked, func(a, b int) bool {
return ranked[a].Distance < ranked[b].Distance
})
for _, item := range ranked {
if debug {
fmt.Printf("distance=%d %s\n", item.Distance, item.Target)
}
out_files = append(out_files, item.Target)
}
return out_files
}
func chooseFile(files []string) string {
prompt := promptui.Select{
Label: "Choose file",
Items: files,
Size: 10,
Searcher: func(input string, index int) bool {
return fuzzy.Match(input, files[index])
},
}
_, file, err := prompt.Run()
if err == promptui.ErrInterrupt {
os.Exit(exit_ctrl_c)
} else if err != nil {
fmt.Println(err)
panic(err)
}
return file
}
func openFile(path string) {
editor := os.Getenv("EDITOR")
if editor == "" {
fmt.Fprintf(os.Stderr, "$EDITOR not configured")
fmt.Println(path)
}
args := []string{editor, path}
env := os.Environ()
if debug {
fmt.Fprintf(os.Stderr, "running %s %v\n", editor, args)
}
if err := syscall.Exec(editor, args, env); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
type options struct{
Print bool
GitRoot bool
Pattern string
Ignore []string
}
func parseArgs() options {
parsed , _ := docopt.ParseDoc(`Find File (and open it)
Usage:
ff [-p|--print] [-r|--git-root] [--ignore=<dir>]... <pattern>
ff -h|--help
Options:
-h --help Show this screen
-p --print Print the path of the file instead of opening it
-r --git-root Search for the file from the project git root
`)
opt := options{}
if err := parsed.Bind(&opt); err != nil {
panic(err)
}
return opt
}
func main() {
args := parseArgs()
debug = os.Getenv("DEBUG") != ""
files := fileMatches(args.GitRoot, args.Pattern, args.Ignore)
if len(files) == 0 {
fmt.Fprintln(os.Stderr, "no matches")
os.Exit(1)
}
file := files[0]
if len(files) > 1 {
if args.Print {
for _, path := range files {
fmt.Println(path)
}
os.Exit(0)
} else {
file = chooseFile(files)
}
}
workDir := mustBaseDir(args.GitRoot)
if args.Print {
fmt.Println(workDir+file)
} else {
openFile(workDir+file)
}
}