From 7b1b902128e784fdf6ddeeb2347417f091fc8dc5 Mon Sep 17 00:00:00 2001 From: Eyal Posener Date: Thu, 11 May 2017 21:05:52 +0300 Subject: [PATCH 1/4] Don't complete when last argument is previous directory --- predict.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/predict.go b/predict.go index 9e1cce9..1db33f5 100644 --- a/predict.go +++ b/predict.go @@ -3,6 +3,7 @@ package complete import ( "os" "path/filepath" + "strings" "github.com/posener/complete/match" ) @@ -85,6 +86,9 @@ func PredictFilesOrDirs(pattern string) Predictor { func files(pattern string, allowDirs, allowFiles bool) PredictFunc { return func(a Args) (prediction []string) { + if strings.HasSuffix(a.Last, "/..") { + return + } dir := dirFromLast(a.Last) Log("looking for files in %s (last=%s)", dir, a.Last) files, err := filepath.Glob(filepath.Join(dir, pattern)) From d6cbc0e9690035368477b3ed8aa22aece0381b77 Mon Sep 17 00:00:00 2001 From: Eyal Posener Date: Thu, 11 May 2017 21:57:44 +0300 Subject: [PATCH 2/4] predict: when searching for files, show also directories --- complete_test.go | 10 ++--- predict.go | 106 ++++++++++++++++++++++++----------------------- predict_test.go | 14 +++---- 3 files changed, 65 insertions(+), 65 deletions(-) diff --git a/complete_test.go b/complete_test.go index ee5a133..c9d544b 100644 --- a/complete_test.go +++ b/complete_test.go @@ -23,7 +23,7 @@ func TestCompleter_Complete(t *testing.T) { "-flag2": PredictNothing, "-flag3": PredictSet("opt1", "opt2", "opt12"), }, - Args: PredictOr(PredictDirs("*"), PredictFiles("*.md")), + Args: PredictFiles("*.md"), }, }, Flags: map[string]Predictor{ @@ -129,7 +129,7 @@ func TestCompleter_Complete(t *testing.T) { }, { args: "-o ", - want: testTXTFiles, + want: append(testTXTFiles, "./", "./dir/"), }, { args: "-o ./no-su", @@ -137,11 +137,7 @@ func TestCompleter_Complete(t *testing.T) { }, { args: "-o ./", - want: testTXTFiles, - }, - { - args: "-o ", - want: testTXTFiles, + want: append(testTXTFiles, "./", "./dir/"), }, { args: "-o ./read", diff --git a/predict.go b/predict.go index 1db33f5..64e793b 100644 --- a/predict.go +++ b/predict.go @@ -1,6 +1,7 @@ package complete import ( + "io/ioutil" "os" "path/filepath" "strings" @@ -68,7 +69,7 @@ func (p predictSet) Predict(a Args) (prediction []string) { // path, if no path was started to be typed, it will complete to directories // in the current working directory. func PredictDirs(pattern string) Predictor { - return files(pattern, true, false) + return files(pattern, false) } // PredictFiles will search for files matching the given pattern in the started to @@ -76,34 +77,40 @@ func PredictDirs(pattern string) Predictor { // match the pattern in the current working directory. // To match any file, use "*" as pattern. To match go files use "*.go", and so on. func PredictFiles(pattern string) Predictor { - return files(pattern, false, true) + return files(pattern, true) } -// PredictFilesOrDirs any file or directory that matches the pattern -func PredictFilesOrDirs(pattern string) Predictor { - return files(pattern, true, true) -} - -func files(pattern string, allowDirs, allowFiles bool) PredictFunc { +func files(pattern string, allowFiles bool) PredictFunc { return func(a Args) (prediction []string) { if strings.HasSuffix(a.Last, "/..") { return } dir := dirFromLast(a.Last) + rel := !filepath.IsAbs(pattern) Log("looking for files in %s (last=%s)", dir, a.Last) - files, err := filepath.Glob(filepath.Join(dir, pattern)) + files := listFiles(dir, pattern) + + // get wording directory for relative name + workDir, err := os.Getwd() if err != nil { - Log("failed glob operation with pattern '%s': %s", pattern, err) - } - if allowDirs { - files = append(files, dir) - } - files = selectByType(files, allowDirs, allowFiles) - if !filepath.IsAbs(pattern) { - filesToRel(files) + workDir = "" } + + // add dir if match + files = append(files, dir) + // add all matching files to prediction for _, f := range files { + if stat, err := os.Stat(f); err != nil || (!stat.IsDir() && !allowFiles) { + continue + } + + // change file name to relative if necessary + if rel && workDir != "" { + f = toRel(workDir, f) + } + + // test matching of file to the argument if match.File(f, a.Last) { prediction = append(prediction, f) } @@ -111,47 +118,44 @@ func files(pattern string, allowDirs, allowFiles bool) PredictFunc { return } } - -func selectByType(names []string, allowDirs bool, allowFiles bool) []string { - filtered := make([]string, 0, len(names)) - for _, name := range names { - stat, err := os.Stat(name) - if err != nil { - continue +func listFiles(dir, pattern string) []string { + m := map[string]bool{} + if files, err := filepath.Glob(filepath.Join(dir, pattern)); err == nil { + for _, f := range files { + m[f] = true } - if (stat.IsDir() && !allowDirs) || (!stat.IsDir() && !allowFiles) { - continue - } - filtered = append(filtered, name) } - return filtered + if dirs, err := ioutil.ReadDir(dir); err == nil { + for _, d := range dirs { + if d.IsDir() { + m[d.Name()] = true + } + } + } + list := make([]string, 0, len(m)) + for k := range m { + list = append(list, k) + } + return list } -// filesToRel, change list of files to their names in the relative -// to current directory form. -func filesToRel(files []string) { - wd, err := os.Getwd() +// toRel changes a file name to a relative name +func toRel(wd, file string) string { + abs, err := filepath.Abs(file) if err != nil { - return + return file } - for i := range files { - abs, err := filepath.Abs(files[i]) - if err != nil { - continue - } - rel, err := filepath.Rel(wd, abs) - if err != nil { - continue - } - if rel != "." { - rel = "./" + rel - } - if info, err := os.Stat(rel); err == nil && info.IsDir() { - rel += "/" - } - files[i] = rel + rel, err := filepath.Rel(wd, abs) + if err != nil { + return file } - return + if rel != "." { + rel = "./" + rel + } + if info, err := os.Stat(rel); err == nil && info.IsDir() { + rel += "/" + } + return rel } // dirFromLast gives the directory of the current written diff --git a/predict_test.go b/predict_test.go index 6b77fbe..ebe8aa1 100644 --- a/predict_test.go +++ b/predict_test.go @@ -60,30 +60,30 @@ func TestPredicate(t *testing.T) { { name: "files/txt", p: PredictFiles("*.txt"), - want: []string{"./a.txt", "./b.txt", "./c.txt", "./.dot.txt"}, + want: []string{"./", "./dir/", "./a.txt", "./b.txt", "./c.txt", "./.dot.txt"}, }, { name: "files/txt", p: PredictFiles("*.txt"), arg: "./dir/", - want: []string{}, + want: []string{"./dir/"}, }, { name: "files/x", p: PredictFiles("x"), arg: "./dir/", - want: []string{"./dir/x"}, + want: []string{"./dir/", "./dir/x"}, }, { name: "files/*", p: PredictFiles("x*"), arg: "./dir/", - want: []string{"./dir/x"}, + want: []string{"./dir/", "./dir/x"}, }, { name: "files/md", p: PredictFiles("*.md"), - want: []string{"./readme.md"}, + want: []string{"./", "./dir/", "./readme.md"}, }, { name: "dirs", @@ -93,7 +93,7 @@ func TestPredicate(t *testing.T) { }, { name: "dirs and files", - p: PredictFilesOrDirs("*"), + p: PredictFiles("*"), arg: "./dir", want: []string{"./dir/", "./dir/x"}, }, @@ -106,7 +106,7 @@ func TestPredicate(t *testing.T) { name: "subdir", p: PredictFiles("*"), arg: "./dir/", - want: []string{"./dir/x"}, + want: []string{"./dir/", "./dir/x"}, }, } From 200b1b5b690cf8b9202c52b6d85e52d99a3f0b4d Mon Sep 17 00:00:00 2001 From: Eyal Posener Date: Thu, 11 May 2017 22:00:21 +0300 Subject: [PATCH 3/4] predict: split file --- predict.go | 133 ----------------------------------------------- predict_files.go | 119 ++++++++++++++++++++++++++++++++++++++++++ predict_set.go | 19 +++++++ 3 files changed, 138 insertions(+), 133 deletions(-) create mode 100644 predict_files.go create mode 100644 predict_set.go diff --git a/predict.go b/predict.go index 64e793b..8207063 100644 --- a/predict.go +++ b/predict.go @@ -1,14 +1,5 @@ package complete -import ( - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/posener/complete/match" -) - // Predictor implements a predict method, in which given // command line arguments returns a list of options it predicts. type Predictor interface { @@ -48,127 +39,3 @@ var PredictNothing Predictor // PredictAnything expects something, but nothing particular, such as a number // or arbitrary name. var PredictAnything = PredictFunc(func(Args) []string { return nil }) - -// PredictSet expects specific set of terms, given in the options argument. -func PredictSet(options ...string) Predictor { - return predictSet(options) -} - -type predictSet []string - -func (p predictSet) Predict(a Args) (prediction []string) { - for _, m := range p { - if match.Prefix(m, a.Last) { - prediction = append(prediction, m) - } - } - return -} - -// PredictDirs will search for directories in the given started to be typed -// path, if no path was started to be typed, it will complete to directories -// in the current working directory. -func PredictDirs(pattern string) Predictor { - return files(pattern, false) -} - -// PredictFiles will search for files matching the given pattern in the started to -// be typed path, if no path was started to be typed, it will complete to files that -// match the pattern in the current working directory. -// To match any file, use "*" as pattern. To match go files use "*.go", and so on. -func PredictFiles(pattern string) Predictor { - return files(pattern, true) -} - -func files(pattern string, allowFiles bool) PredictFunc { - return func(a Args) (prediction []string) { - if strings.HasSuffix(a.Last, "/..") { - return - } - dir := dirFromLast(a.Last) - rel := !filepath.IsAbs(pattern) - Log("looking for files in %s (last=%s)", dir, a.Last) - files := listFiles(dir, pattern) - - // get wording directory for relative name - workDir, err := os.Getwd() - if err != nil { - workDir = "" - } - - // add dir if match - files = append(files, dir) - - // add all matching files to prediction - for _, f := range files { - if stat, err := os.Stat(f); err != nil || (!stat.IsDir() && !allowFiles) { - continue - } - - // change file name to relative if necessary - if rel && workDir != "" { - f = toRel(workDir, f) - } - - // test matching of file to the argument - if match.File(f, a.Last) { - prediction = append(prediction, f) - } - } - return - } -} -func listFiles(dir, pattern string) []string { - m := map[string]bool{} - if files, err := filepath.Glob(filepath.Join(dir, pattern)); err == nil { - for _, f := range files { - m[f] = true - } - } - if dirs, err := ioutil.ReadDir(dir); err == nil { - for _, d := range dirs { - if d.IsDir() { - m[d.Name()] = true - } - } - } - list := make([]string, 0, len(m)) - for k := range m { - list = append(list, k) - } - return list -} - -// toRel changes a file name to a relative name -func toRel(wd, file string) string { - abs, err := filepath.Abs(file) - if err != nil { - return file - } - rel, err := filepath.Rel(wd, abs) - if err != nil { - return file - } - if rel != "." { - rel = "./" + rel - } - if info, err := os.Stat(rel); err == nil && info.IsDir() { - rel += "/" - } - return rel -} - -// dirFromLast gives the directory of the current written -// last argument if it represents a file name being written. -// in case that it is not, we fall back to the current directory. -func dirFromLast(last string) string { - if info, err := os.Stat(last); err == nil && info.IsDir() { - return last - } - dir := filepath.Dir(last) - _, err := os.Stat(dir) - if err != nil { - return "./" - } - return dir -} diff --git a/predict_files.go b/predict_files.go new file mode 100644 index 0000000..cdd1206 --- /dev/null +++ b/predict_files.go @@ -0,0 +1,119 @@ +package complete + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/posener/complete/match" +) + +// PredictDirs will search for directories in the given started to be typed +// path, if no path was started to be typed, it will complete to directories +// in the current working directory. +func PredictDirs(pattern string) Predictor { + return files(pattern, false) +} + +// PredictFiles will search for files matching the given pattern in the started to +// be typed path, if no path was started to be typed, it will complete to files that +// match the pattern in the current working directory. +// To match any file, use "*" as pattern. To match go files use "*.go", and so on. +func PredictFiles(pattern string) Predictor { + return files(pattern, true) +} + +func files(pattern string, allowFiles bool) PredictFunc { + return func(a Args) (prediction []string) { + if strings.HasSuffix(a.Last, "/..") { + return + } + dir := dirFromLast(a.Last) + rel := !filepath.IsAbs(pattern) + Log("looking for files in %s (last=%s)", dir, a.Last) + files := listFiles(dir, pattern) + + // get wording directory for relative name + workDir, err := os.Getwd() + if err != nil { + workDir = "" + } + + // add dir if match + files = append(files, dir) + + // add all matching files to prediction + for _, f := range files { + if stat, err := os.Stat(f); err != nil || (!stat.IsDir() && !allowFiles) { + continue + } + + // change file name to relative if necessary + if rel && workDir != "" { + f = toRel(workDir, f) + } + + // test matching of file to the argument + if match.File(f, a.Last) { + prediction = append(prediction, f) + } + } + return + } +} + +func listFiles(dir, pattern string) []string { + m := map[string]bool{} + if files, err := filepath.Glob(filepath.Join(dir, pattern)); err == nil { + for _, f := range files { + m[f] = true + } + } + if dirs, err := ioutil.ReadDir(dir); err == nil { + for _, d := range dirs { + if d.IsDir() { + m[d.Name()] = true + } + } + } + list := make([]string, 0, len(m)) + for k := range m { + list = append(list, k) + } + return list +} + +// toRel changes a file name to a relative name +func toRel(wd, file string) string { + abs, err := filepath.Abs(file) + if err != nil { + return file + } + rel, err := filepath.Rel(wd, abs) + if err != nil { + return file + } + if rel != "." { + rel = "./" + rel + } + if info, err := os.Stat(rel); err == nil && info.IsDir() { + rel += "/" + } + return rel +} + +// dirFromLast gives the directory of the current written +// last argument if it represents a file name being written. +// in case that it is not, we fall back to the current directory. +func dirFromLast(last string) string { + if info, err := os.Stat(last); err == nil && info.IsDir() { + return last + } + dir := filepath.Dir(last) + _, err := os.Stat(dir) + if err != nil { + return "./" + } + return dir +} diff --git a/predict_set.go b/predict_set.go new file mode 100644 index 0000000..8fc59d7 --- /dev/null +++ b/predict_set.go @@ -0,0 +1,19 @@ +package complete + +import "github.com/posener/complete/match" + +// PredictSet expects specific set of terms, given in the options argument. +func PredictSet(options ...string) Predictor { + return predictSet(options) +} + +type predictSet []string + +func (p predictSet) Predict(a Args) (prediction []string) { + for _, m := range p { + if match.Prefix(m, a.Last) { + prediction = append(prediction, m) + } + } + return +} From 026906bf106ac1c18117443a26e6efa196bd9dae Mon Sep 17 00:00:00 2001 From: Eyal Posener Date: Thu, 11 May 2017 22:19:23 +0300 Subject: [PATCH 4/4] predict files: extract predictFiles function --- predict_files.go | 72 ++++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/predict_files.go b/predict_files.go index cdd1206..8ad5368 100644 --- a/predict_files.go +++ b/predict_files.go @@ -26,43 +26,49 @@ func PredictFiles(pattern string) Predictor { func files(pattern string, allowFiles bool) PredictFunc { return func(a Args) (prediction []string) { - if strings.HasSuffix(a.Last, "/..") { - return - } - dir := dirFromLast(a.Last) - rel := !filepath.IsAbs(pattern) - Log("looking for files in %s (last=%s)", dir, a.Last) - files := listFiles(dir, pattern) - - // get wording directory for relative name - workDir, err := os.Getwd() - if err != nil { - workDir = "" - } - - // add dir if match - files = append(files, dir) - - // add all matching files to prediction - for _, f := range files { - if stat, err := os.Stat(f); err != nil || (!stat.IsDir() && !allowFiles) { - continue - } - - // change file name to relative if necessary - if rel && workDir != "" { - f = toRel(workDir, f) - } - - // test matching of file to the argument - if match.File(f, a.Last) { - prediction = append(prediction, f) - } - } + prediction = predictFiles(a.Last, pattern, allowFiles) return } } +func predictFiles(last string, pattern string, allowFiles bool) (prediction []string) { + if strings.HasSuffix(last, "/..") { + return + } + + dir := dirFromLast(last) + rel := !filepath.IsAbs(pattern) + files := listFiles(dir, pattern) + + // get wording directory for relative name + workDir, err := os.Getwd() + if err != nil { + workDir = "" + } + + // add dir if match + files = append(files, dir) + + // add all matching files to prediction + for _, f := range files { + if stat, err := os.Stat(f); err != nil || (!stat.IsDir() && !allowFiles) { + continue + } + + // change file name to relative if necessary + if rel && workDir != "" { + f = toRel(workDir, f) + } + + // test matching of file to the argument + if match.File(f, last) { + prediction = append(prediction, f) + } + } + return + +} + func listFiles(dir, pattern string) []string { m := map[string]bool{} if files, err := filepath.Glob(filepath.Join(dir, pattern)); err == nil {