Fix './' prefix for file completion

This commit is contained in:
Eyal Posener 2017-05-15 23:52:04 +03:00
parent 659bd9e3d5
commit 61d9904ba1
8 changed files with 86 additions and 39 deletions

10
args.go
View File

@ -28,19 +28,13 @@ type Args struct {
// in case that it is not, we fall back to the current directory. // in case that it is not, we fall back to the current directory.
func (a Args) Directory() string { func (a Args) Directory() string {
if info, err := os.Stat(a.Last); err == nil && info.IsDir() { if info, err := os.Stat(a.Last); err == nil && info.IsDir() {
if !filepath.IsAbs(a.Last) { return fixPathForm(a.Last, a.Last)
return relativePath(a.Last)
}
return a.Last
} }
dir := filepath.Dir(a.Last) dir := filepath.Dir(a.Last)
if info, err := os.Stat(dir); err != nil || !info.IsDir() { if info, err := os.Stat(dir); err != nil || !info.IsDir() {
return "./" return "./"
} }
if !filepath.IsAbs(dir) { return fixPathForm(a.Last, dir)
dir = relativePath(dir)
}
return dir
} }
func newArgs(line []string) Args { func newArgs(line []string) Args {

View File

@ -162,7 +162,7 @@ func TestArgs_Directory(t *testing.T) {
}, },
{ {
line: "a b c /tmp", line: "a b c /tmp",
directory: "/tmp", directory: "/tmp/",
}, },
{ {
line: "a b c /tmp ", line: "a b c /tmp ",
@ -178,7 +178,7 @@ func TestArgs_Directory(t *testing.T) {
}, },
{ {
line: "a b c dir", line: "a b c dir",
directory: "./dir/", directory: "dir/",
}, },
{ {
line: "a b c ./di", line: "a b c ./di",

View File

@ -35,8 +35,6 @@ func TestCompleter_Complete(t *testing.T) {
}, },
} }
testTXTFiles := []string{"./a.txt", "./b.txt", "./c.txt", "./.dot.txt"}
tests := []struct { tests := []struct {
args string args string
want []string want []string
@ -75,7 +73,7 @@ func TestCompleter_Complete(t *testing.T) {
}, },
{ {
args: "sub2 ", args: "sub2 ",
want: []string{"./", "./dir/", "./outer/", "./readme.md", "-flag2", "-flag3", "-h", "-global1"}, want: []string{"./", "dir/", "outer/", "readme.md", "-flag2", "-flag3", "-h", "-global1"},
}, },
{ {
args: "sub2 ./", args: "sub2 ./",
@ -83,11 +81,15 @@ func TestCompleter_Complete(t *testing.T) {
}, },
{ {
args: "sub2 re", args: "sub2 re",
want: []string{"readme.md"},
},
{
args: "sub2 ./re",
want: []string{"./readme.md"}, want: []string{"./readme.md"},
}, },
{ {
args: "sub2 -flag2 ", args: "sub2 -flag2 ",
want: []string{"./", "./dir/", "./outer/", "./readme.md", "-flag2", "-flag3", "-h", "-global1"}, want: []string{"./", "dir/", "outer/", "readme.md", "-flag2", "-flag3", "-h", "-global1"},
}, },
{ {
args: "sub1 -fl", args: "sub1 -fl",
@ -123,7 +125,7 @@ func TestCompleter_Complete(t *testing.T) {
}, },
{ {
args: "-o ", args: "-o ",
want: append(testTXTFiles, "./", "./dir/", "./outer/"), want: []string{"a.txt", "b.txt", "c.txt", ".dot.txt", "./", "dir/", "outer/"},
}, },
{ {
args: "-o ./no-su", args: "-o ./no-su",
@ -131,7 +133,11 @@ func TestCompleter_Complete(t *testing.T) {
}, },
{ {
args: "-o ./", args: "-o ./",
want: append(testTXTFiles, "./", "./dir/", "./outer/"), want: []string{"./a.txt", "./b.txt", "./c.txt", "./.dot.txt", "./", "./dir/", "./outer/"},
},
{
args: "-o .",
want: []string{"./a.txt", "./b.txt", "./c.txt", "./.dot.txt", "./", "./dir/", "./outer/"},
}, },
{ {
args: "-o ./read", args: "-o ./read",

View File

@ -1,10 +1,11 @@
package main package main
import ( import (
"os"
"sort"
"testing" "testing"
"github.com/posener/complete" "github.com/posener/complete"
"os"
) )
func TestPredictions(t *testing.T) { func TestPredictions(t *testing.T) {
@ -49,7 +50,7 @@ func TestPredictions(t *testing.T) {
{ {
name: "predict runnable ok", name: "predict runnable ok",
predictor: complete.PredictFunc(predictRunnableFiles), predictor: complete.PredictFunc(predictRunnableFiles),
completion: []string{"./complete.go"}, completion: []string{"complete.go"},
}, },
{ {
name: "predict runnable not found", name: "predict runnable not found",
@ -79,6 +80,8 @@ func Example() {
} }
func equal(s1, s2 []string) bool { func equal(s1, s2 []string) bool {
sort.Strings(s1)
sort.Strings(s2)
if len(s1) != len(s2) { if len(s1) != len(s2) {
return false return false
} }

View File

@ -4,13 +4,16 @@ import "strings"
// File returns true if prefix can match the file // File returns true if prefix can match the file
func File(file, prefix string) bool { func File(file, prefix string) bool {
// special case for current directory completion // special case for current directory completion
if file == "./" && (prefix == "." || prefix == "") { if file == "./" && (prefix == "." || prefix == "") {
return true return true
} }
if prefix == "." && strings.HasPrefix(file, ".") {
return true
}
file = strings.TrimPrefix(file, "./") file = strings.TrimPrefix(file, "./")
prefix = strings.TrimPrefix(prefix, "./") prefix = strings.TrimPrefix(prefix, "./")
return strings.HasPrefix(file, prefix) return strings.HasPrefix(file, prefix)
} }

View File

@ -42,7 +42,7 @@ func files(pattern string, allowFiles bool) PredictFunc {
// if the result is only one item, we might want to recursively check // if the result is only one item, we might want to recursively check
// for more accurate results. // for more accurate results.
if prediction[0] == a.Last { // avoid loop forever if prediction[0] == a.Last {
return return
} }
@ -73,13 +73,9 @@ func predictFiles(a Args, pattern string, allowFiles bool) []string {
// PredictFilesSet predict according to file rules to a given set of file names // PredictFilesSet predict according to file rules to a given set of file names
func PredictFilesSet(files []string) PredictFunc { func PredictFilesSet(files []string) PredictFunc {
return func(a Args) (prediction []string) { return func(a Args) (prediction []string) {
rel := !filepath.IsAbs(a.Directory())
// add all matching files to prediction // add all matching files to prediction
for _, f := range files { for _, f := range files {
// change file name to relative if necessary f = fixPathForm(a.Last, f)
if rel {
f = relativePath(f)
}
// test matching of file to the argument // test matching of file to the argument
if match.File(f, a.Last) { if match.File(f, a.Last) {

View File

@ -60,7 +60,7 @@ func TestPredicate(t *testing.T) {
{ {
name: "files/txt", name: "files/txt",
p: PredictFiles("*.txt"), p: PredictFiles("*.txt"),
want: []string{"./", "./dir/", "./outer/", "./a.txt", "./b.txt", "./c.txt", "./.dot.txt"}, want: []string{"./", "dir/", "outer/", "a.txt", "b.txt", "c.txt", ".dot.txt"},
}, },
{ {
name: "files/txt", name: "files/txt",
@ -83,38 +83,68 @@ func TestPredicate(t *testing.T) {
{ {
name: "files/md", name: "files/md",
p: PredictFiles("*.md"), p: PredictFiles("*.md"),
argList: []string{"", ".", "./"}, argList: []string{""},
want: []string{"./", "dir/", "outer/", "readme.md"},
},
{
name: "files/md with ./ prefix",
p: PredictFiles("*.md"),
argList: []string{".", "./"},
want: []string{"./", "./dir/", "./outer/", "./readme.md"}, want: []string{"./", "./dir/", "./outer/", "./readme.md"},
}, },
{ {
name: "dirs", name: "dirs",
p: PredictDirs("*"), p: PredictDirs("*"),
argList: []string{"./dir/", "./di", "di", "dir", "dir/"}, argList: []string{"di", "dir", "dir/"},
want: []string{"dir/"},
},
{
name: "dirs with ./ prefix",
p: PredictDirs("*"),
argList: []string{"./di", "./dir", "./dir/"},
want: []string{"./dir/"}, want: []string{"./dir/"},
}, },
{ {
name: "predict anything in dir", name: "predict anything in dir",
p: PredictFiles("*"), p: PredictFiles("*"),
argList: []string{"./dir", "dir", "./dir/", "./di"}, argList: []string{"dir", "dir/", "di"},
want: []string{"dir/", "dir/foo", "dir/bar"},
},
{
name: "predict anything in dir with ./ prefix",
p: PredictFiles("*"),
argList: []string{"./dir", "./dir/", "./di"},
want: []string{"./dir/", "./dir/foo", "./dir/bar"}, want: []string{"./dir/", "./dir/foo", "./dir/bar"},
}, },
{ {
name: "root directories", name: "root directories",
p: PredictDirs("*"), p: PredictDirs("*"),
argList: []string{"", ".", "./"}, argList: []string{""},
want: []string{"./", "dir/", "outer/"},
},
{
name: "root directories with ./ prefix",
p: PredictDirs("*"),
argList: []string{".", "./"},
want: []string{"./", "./dir/", "./outer/"}, want: []string{"./", "./dir/", "./outer/"},
}, },
{ {
name: "nested directories", name: "nested directories",
p: PredictDirs("*.md"), p: PredictDirs("*.md"),
argList: []string{"ou", "./ou", "./outer", "./outer/"}, argList: []string{"ou", "outer", "outer/"},
want: []string{"outer/", "outer/inner/"},
},
{
name: "nested directories with ./ prefix",
p: PredictDirs("*.md"),
argList: []string{"./ou", "./outer", "./outer/"},
want: []string{"./outer/", "./outer/inner/"}, want: []string{"./outer/", "./outer/inner/"},
}, },
{ {
name: "nested inner directory", name: "nested inner directory",
p: PredictFiles("*.md"), p: PredictFiles("*.md"),
argList: []string{"outer/i"}, argList: []string{"outer/i"},
want: []string{"./outer/inner/", "./outer/inner/readme.md"}, want: []string{"outer/inner/", "outer/inner/readme.md"},
}, },
} }

View File

@ -3,10 +3,11 @@ package complete
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"strings"
) )
// relativePath changes a file name to a relative name // fixPathForm changes a file name to a relative name
func relativePath(file string) string { func fixPathForm(last string, file string) string {
// get wording directory for relative name // get wording directory for relative name
workDir, err := os.Getwd() workDir, err := os.Getwd()
if err != nil { if err != nil {
@ -17,15 +18,29 @@ func relativePath(file string) string {
if err != nil { if err != nil {
return file return file
} }
// if last is absolute, return path as absolute
if filepath.IsAbs(last) {
return fixDirPath(abs)
}
rel, err := filepath.Rel(workDir, abs) rel, err := filepath.Rel(workDir, abs)
if err != nil { if err != nil {
return file return file
} }
if rel != "." {
// fix ./ prefix of path
if rel != "." && strings.HasPrefix(last, ".") {
rel = "./" + rel rel = "./" + rel
} }
if info, err := os.Stat(rel); err == nil && info.IsDir() {
rel += "/" return fixDirPath(rel)
} }
return rel
func fixDirPath(path string) string {
info, err := os.Stat(path)
if err == nil && info.IsDir() && !strings.HasSuffix(path, "/") {
path += "/"
}
return path
} }