diff --git a/command.go b/command.go index e2f5eb4..32c5456 100644 --- a/command.go +++ b/command.go @@ -19,7 +19,7 @@ func (c *Command) options(args []string) (options []Option, only bool) { // if prev has something that needs to follow it, // it is the most relevant completion - if predicate, ok := c.Flags[last(args)]; ok && predicate.Expects { + if predicate, ok := c.Flags[last(args)]; ok && !predicate.ExpectsNothing { return predicate.predict(), true } @@ -40,7 +40,7 @@ func (c *Command) options(args []string) (options []Option, only bool) { } // add additional expected argument of the command - if c.Args.Expects { + if !c.Args.ExpectsNothing { options = append(options, c.Args.predict()...) } diff --git a/complete.go b/complete.go index e7e209e..302486c 100644 --- a/complete.go +++ b/complete.go @@ -21,11 +21,11 @@ func New(c Command) *Completer { func (c *Completer) Complete() { args := getLine() - logger("Completing args: %s", args) + Log("Completing args: %s", args) options := c.complete(args) - logger("Completion: %s", options) + Log("Completion: %s", options) output(options) } diff --git a/gocomplete/complete.go b/gocomplete/complete.go index 9233b4e..57fbaa1 100644 --- a/gocomplete/complete.go +++ b/gocomplete/complete.go @@ -4,33 +4,187 @@ import ( "github.com/posener/complete" ) -var ( - build = complete.Command{ +var predictEllipsis = complete.Predicate{ + Predictor: func() []complete.Option { return []complete.Option{complete.Arg("./...")} }, +} + +var goFilesOrPackages = complete.PredictFiles("**.go"). + Or(complete.PredictDirs("./")). + Or(predictEllipsis) + +func main() { + build := complete.Command{ Flags: complete.Flags{ - "-o": complete.PredictFiles("*"), + "-o": complete.PredictFiles("**"), "-i": complete.PredictNothing, + + "-a": complete.PredictNothing, + "-n": complete.PredictNothing, + "-p": complete.PredictAnything, + "-race": complete.PredictNothing, + "-msan": complete.PredictNothing, + "-v": complete.PredictNothing, + "-work": complete.PredictNothing, + "-x": complete.PredictNothing, + "-asmflags": complete.PredictAnything, + "-buildmode": complete.PredictAnything, + "-compiler": complete.PredictAnything, + "-gccgoflags": complete.PredictAnything, + "-gcflags": complete.PredictAnything, + "-installsuffix": complete.PredictAnything, + "-ldflags": complete.PredictAnything, + "-linkshared": complete.PredictNothing, + "-pkgdir": complete.PredictDirs("./"), + "-tags": complete.PredictAnything, + "-toolexec": complete.PredictAnything, }, - Args: complete.PredictFiles("**.go").Or(complete.PredictDirs("./")), + Args: goFilesOrPackages, } - test = complete.Command{ + run := complete.Command{ Flags: complete.Flags{ - "-run": complete.PredictAnything, - "-count": complete.PredictAnything, + "-exec": complete.PredictAnything, }, + Args: complete.PredictFiles("**.go"), } - gogo = complete.Command{ + test := complete.Command{ + Flags: complete.Flags{ + "-args": complete.PredictAnything, + "-c": complete.PredictNothing, + "-exec": complete.PredictAnything, + + "-bench": predictTest("Benchmark"), + "-benchtime": complete.PredictAnything, + "-count": complete.PredictAnything, + "-cover": complete.PredictNothing, + "-covermode": complete.PredictSet([]string{"set", "count", "atomic"}), + "-coverpkg": complete.PredictDirs("./"), + "-cpu": complete.PredictAnything, + "-run": predictTest("test"), + "-short": complete.PredictNothing, + "-timeout": complete.PredictAnything, + + "-benchmem": complete.PredictNothing, + "-blockprofile": complete.PredictFiles("**.out"), + "-blockprofilerate": complete.PredictAnything, + "-coverprofile": complete.PredictFiles("**.out"), + "-cpuprofile": complete.PredictFiles("**.out"), + "-memprofile": complete.PredictFiles("**.out"), + "-memprofilerate": complete.PredictAnything, + "-mutexprofile": complete.PredictFiles("**.out"), + "-mutexprofilefraction": complete.PredictAnything, + "-outputdir": complete.PredictDirs("./"), + "-trace": complete.PredictFiles("**.out"), + }, + Args: goFilesOrPackages, + } + + fmt := complete.Command{ + Flags: complete.Flags{ + "-n": complete.PredictNothing, + "-x": complete.PredictNothing, + }, + Args: goFilesOrPackages, + } + + get := complete.Command{ + Flags: complete.Flags{ + "-d": complete.PredictNothing, + "-f": complete.PredictNothing, + "-fix": complete.PredictNothing, + "-insecure": complete.PredictNothing, + "-t": complete.PredictNothing, + "-u": complete.PredictNothing, + }, + Args: goFilesOrPackages, + } + + generate := complete.Command{ + Flags: complete.Flags{ + "-n": complete.PredictNothing, + "-x": complete.PredictNothing, + "-v": complete.PredictNothing, + "-run": complete.PredictAnything, + }, + Args: goFilesOrPackages, + } + + vet := complete.Command{ + Flags: complete.Flags{ + "-n": complete.PredictNothing, + "-x": complete.PredictNothing, + }, + Args: complete.PredictDirs("./"), + } + + list := complete.Command{ + Flags: complete.Flags{ + "-e": complete.PredictNothing, + "-f": complete.PredictAnything, + "-json": complete.PredictNothing, + }, + Args: complete.PredictDirs("./"), + } + + tool := complete.Command{ + Flags: complete.Flags{ + "-n": complete.PredictNothing, + }, + Args: complete.PredictAnything, + } + + clean := complete.Command{ + Flags: complete.Flags{ + "-i": complete.PredictNothing, + "-r": complete.PredictNothing, + "-n": complete.PredictNothing, + "-x": complete.PredictNothing, + }, + Args: complete.PredictDirs("./"), + } + + env := complete.Command{ + Args: complete.PredictAnything, + } + + bug := complete.Command{} + version := complete.Command{} + + fix := complete.Command{ + Args: complete.PredictDirs("./"), + } + + // commands that also accepts the build flags + for name, options := range build.Flags { + test.Flags[name] = options + run.Flags[name] = options + list.Flags[name] = options + vet.Flags[name] = options + } + + gogo := complete.Command{ Sub: complete.Commands{ - "build": build, - "test": test, + "build": build, + "install": build, // install and build have the same flags + "run": run, + "test": test, + "fmt": fmt, + "get": get, + "generate": generate, + "vet": vet, + "list": list, + "tool": tool, + "clean": clean, + "env": env, + "bug": bug, + "fix": fix, + "version": version, }, Flags: complete.Flags{ "-h": complete.PredictNothing, }, } -) -func main() { complete.New(gogo).Complete() } diff --git a/gocomplete/tests.go b/gocomplete/tests.go new file mode 100644 index 0000000..388b7b7 --- /dev/null +++ b/gocomplete/tests.go @@ -0,0 +1,57 @@ +package main + +import ( + "go/ast" + "go/parser" + "go/token" + "os" + "path/filepath" + "strings" + + "github.com/posener/complete" +) + +func predictTest(testType string) complete.Predicate { + return complete.Predicate{ + Predictor: func() []complete.Option { + tests := testNames(testType) + options := make([]complete.Option, len(tests)) + for i := range tests { + options[i] = complete.Arg(tests[i]) + } + return options + }, + } +} + +// get all test names in current directory +func testNames(testType string) (tests []string) { + filepath.Walk("./", func(path string, info os.FileInfo, err error) error { + // if not a test file, skip + if !strings.HasSuffix(path, "_test.go") { + return nil + } + // inspect test file and append all the test names + tests = append(tests, testsInFile(testType, path)...) + return nil + }) + return +} + +func testsInFile(testType, path string) (tests []string) { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, path, nil, 0) + if err != nil { + complete.Log("Failed parsing %s: %s", path, err) + return nil + } + for _, d := range f.Decls { + if f, ok := d.(*ast.FuncDecl); ok { + name := f.Name.String() + if strings.HasPrefix(name, testType) { + tests = append(tests, name) + } + } + } + return +} diff --git a/log.go b/log.go index 0b0a54a..797a80c 100644 --- a/log.go +++ b/log.go @@ -7,7 +7,12 @@ import ( "os" ) -var logger = getLogger() +// Log is used for debugging purposes +// since complete is running on tab completion, it is nice to +// have logs to the stderr (when writing your own completer) +// to write logs, set the COMP_DEBUG environment variable and +// use complete.Log in the complete program +var Log = getLogger() func getLogger() func(format string, args ...interface{}) { var logfile io.Writer = ioutil.Discard diff --git a/option.go b/option.go index 6a067ea..3915091 100644 --- a/option.go +++ b/option.go @@ -29,11 +29,11 @@ func (a ArgFileName) String() string { func (a ArgFileName) Matches(prefix string) bool { full, err := filepath.Abs(string(a)) if err != nil { - logger("failed getting abs path of %s: %s", a, err) + Log("failed getting abs path of %s: %s", a, err) } prefixFull, err := filepath.Abs(prefix) if err != nil { - logger("failed getting abs path of %s: %s", prefix, err) + Log("failed getting abs path of %s: %s", prefix, err) } // if the file has the prefix as prefix, diff --git a/predicate.go b/predicate.go index 1cc6cca..08a6722 100644 --- a/predicate.go +++ b/predicate.go @@ -7,10 +7,10 @@ import ( // Predicate determines what terms can follow a command or a flag type Predicate struct { - // Expects determine if the predicate expects something after. + // ExpectsNothing determine if the predicate expects something after. // flags/commands that do not expect any specific argument should // leave it on false - Expects bool + ExpectsNothing bool // Predictor is function that returns list of arguments that can // come after the flag/command Predictor func() []Option @@ -20,8 +20,8 @@ type Predicate struct { // returns the union of their predication func (p Predicate) Or(other Predicate) Predicate { return Predicate{ - Expects: p.Expects && other.Expects, - Predictor: func() []Option { return append(p.predict(), other.predict()...) }, + ExpectsNothing: p.ExpectsNothing && other.ExpectsNothing, + Predictor: func() []Option { return append(p.predict(), other.predict()...) }, } } @@ -33,23 +33,30 @@ func (p Predicate) predict() []Option { } var ( - PredictNothing = Predicate{Expects: false} - PredictAnything = Predicate{Expects: true} + PredictNothing = Predicate{ExpectsNothing: true} + PredictAnything = Predicate{} ) -func PredictFiles(pattern string) Predicate { +func PredictSet(options []string) Predicate { return Predicate{ - Expects: true, - Predictor: glob(pattern), + Predictor: func() []Option { + ret := make([]Option, len(options)) + for i := range options { + ret[i] = Arg(options[i]) + } + return ret + }, } } +func PredictFiles(pattern string) Predicate { + return Predicate{Predictor: glob(pattern)} +} + func PredictDirs(path string) Predicate { - return Predicate{ - Expects: true, - Predictor: dirs(path), - } + return Predicate{Predictor: dirs(path)} } + func dirs(path string) func() []Option { return func() (options []Option) { dirs := []string{} @@ -70,7 +77,7 @@ func glob(pattern string) func() []Option { return func() []Option { files, err := filepath.Glob(pattern) if err != nil { - logger("failed glob operation with pattern '%s': %s", pattern, err) + Log("failed glob operation with pattern '%s': %s", pattern, err) } if !filepath.IsAbs(pattern) { filesToRel(files)