From 66402080675c250edabe50839555942d1b1aa189 Mon Sep 17 00:00:00 2001 From: Eyal Posener Date: Fri, 12 May 2017 22:43:33 +0300 Subject: [PATCH] Recursive directory lookup When only one directory matches the result, search recursively whithin this directory for files. --- args.go | 6 +-- predict_files.go | 30 ++++++++++++- predict_test.go | 104 ++++++++++++++++++++++--------------------- tests/dir/{x => bar} | 0 tests/dir/foo | 0 5 files changed, 85 insertions(+), 55 deletions(-) rename tests/dir/{x => bar} (100%) create mode 100644 tests/dir/foo diff --git a/args.go b/args.go index 6d4fea6..0d2b94d 100644 --- a/args.go +++ b/args.go @@ -3,15 +3,15 @@ package complete // Args describes command line arguments type Args struct { // All lists of all arguments in command line (not including the command itself) - All []string + All []string // Completed lists of all completed arguments in command line, // If the last one is still being typed - no space after it, // it won't appear in this list of arguments. - Completed []string + Completed []string // Last argument in command line, the one being typed, if the last // character in the command line is a space, this argument will be empty, // otherwise this would be the last word. - Last string + Last string // LastCompleted is the last argument that was fully typed. // If the last character in the command line is space, this would be the // last word, otherwise, it would be the word before that. diff --git a/predict_files.go b/predict_files.go index 8ad5368..fe70c97 100644 --- a/predict_files.go +++ b/predict_files.go @@ -25,9 +25,35 @@ func PredictFiles(pattern string) Predictor { } func files(pattern string, allowFiles bool) PredictFunc { + + // search for files according to arguments, + // if only one directory has matched the result, search recursively into + // this directory to give more results. return func(a Args) (prediction []string) { - prediction = predictFiles(a.Last, pattern, allowFiles) - return + last := a.Last + for { + + prediction = predictFiles(last, pattern, allowFiles) + + // if the number of prediction is not 1, we either have many results or + // have no results, so we return it. + if len(prediction) != 1 { + return + } + + // if the result is only one item, we might want to recursively check + // for more accurate results. + if prediction[0] == last { // avoid loop forever + return + } + + // only try deeper, if the one item is a directory + if stat, err := os.Stat(prediction[0]); err != nil || !stat.IsDir() { + return + } + + last = prediction[0] + } } } diff --git a/predict_test.go b/predict_test.go index ebe8aa1..5e3e5d4 100644 --- a/predict_test.go +++ b/predict_test.go @@ -11,10 +11,10 @@ func TestPredicate(t *testing.T) { initTests() tests := []struct { - name string - p Predictor - arg string - want []string + name string + p Predictor + argList []string + want []string }{ { name: "set", @@ -22,10 +22,10 @@ func TestPredicate(t *testing.T) { want: []string{"a", "b", "c"}, }, { - name: "set with does", - p: PredictSet("./..", "./x"), - arg: "./.", - want: []string{"./.."}, + name: "set with does", + p: PredictSet("./..", "./x"), + argList: []string{"./.", "./.."}, + want: []string{"./.."}, }, { name: "set/empty", @@ -63,67 +63,71 @@ func TestPredicate(t *testing.T) { want: []string{"./", "./dir/", "./a.txt", "./b.txt", "./c.txt", "./.dot.txt"}, }, { - name: "files/txt", - p: PredictFiles("*.txt"), - arg: "./dir/", - want: []string{"./dir/"}, + name: "files/txt", + p: PredictFiles("*.txt"), + argList: []string{"./dir/"}, + want: []string{"./dir/"}, }, { - name: "files/x", - p: PredictFiles("x"), - arg: "./dir/", - want: []string{"./dir/", "./dir/x"}, + name: "complete files inside dir if it is the only match", + p: PredictFiles("foo"), + argList: []string{"./dir/", "./d"}, + want: []string{"./dir/", "./dir/foo"}, }, { - name: "files/*", - p: PredictFiles("x*"), - arg: "./dir/", - want: []string{"./dir/", "./dir/x"}, + name: "complete files inside dir when argList includes file name", + p: PredictFiles("*"), + argList: []string{"./dir/f", "./dir/foo"}, + want: []string{"./dir/foo"}, }, { - name: "files/md", - p: PredictFiles("*.md"), - want: []string{"./", "./dir/", "./readme.md"}, + name: "files/md", + p: PredictFiles("*.md"), + argList: []string{"", ".", "./"}, + want: []string{"./", "./dir/", "./readme.md"}, }, { - name: "dirs", - p: PredictDirs("*"), - arg: "./dir/", - want: []string{"./dir/"}, + name: "dirs", + p: PredictDirs("*"), + argList: []string{"./dir/", "./di", "di", "dir", "dir/"}, + want: []string{"./dir/"}, }, { - name: "dirs and files", - p: PredictFiles("*"), - arg: "./dir", - want: []string{"./dir/", "./dir/x"}, + name: "predict anything in dir", + p: PredictFiles("*"), + argList: []string{"./dir", "dir", "./dir/", "./di"}, + want: []string{"./dir/", "./dir/foo", "./dir/bar"}, }, { - name: "dirs", - p: PredictDirs("*"), - want: []string{"./", "./dir/"}, - }, - { - name: "subdir", - p: PredictFiles("*"), - arg: "./dir/", - want: []string{"./dir/", "./dir/x"}, + name: "root directories", + p: PredictDirs("*"), + argList: []string{"", ".", "./"}, + want: []string{"./", "./dir/"}, }, } for _, tt := range tests { - t.Run(tt.name+"?arg='"+tt.arg+"'", func(t *testing.T) { - matches := tt.p.Predict(newArgs(strings.Split(tt.arg, " "))) + // no args in argList, means an empty argument + if len(tt.argList) == 0 { + tt.argList = append(tt.argList, "") + } - sort.Strings(matches) - sort.Strings(tt.want) + for _, arg := range tt.argList { + t.Run(tt.name+"?arg='"+arg+"'", func(t *testing.T) { - got := strings.Join(matches, ",") - want := strings.Join(tt.want, ",") + matches := tt.p.Predict(newArgs(strings.Split(arg, " "))) - if got != want { - t.Errorf("failed %s\ngot = %s\nwant: %s", t.Name(), got, want) - } - }) + sort.Strings(matches) + sort.Strings(tt.want) + + got := strings.Join(matches, ",") + want := strings.Join(tt.want, ",") + + if got != want { + t.Errorf("failed %s\ngot = %s\nwant: %s", t.Name(), got, want) + } + }) + } } } diff --git a/tests/dir/x b/tests/dir/bar similarity index 100% rename from tests/dir/x rename to tests/dir/bar diff --git a/tests/dir/foo b/tests/dir/foo new file mode 100644 index 0000000..e69de29