github.com/gechr/complete@v0.0.0-20191016221035-401475e3ce1e/complete.go (about) 1 package complete 2 3 import ( 4 "flag" 5 "fmt" 6 "io" 7 "os" 8 "strconv" 9 10 "github.com/gechr/complete/cmd" 11 "github.com/sahilm/fuzzy" 12 ) 13 14 const ( 15 envLine = "COMP_LINE" 16 envPoint = "COMP_POINT" 17 envDebug = "COMP_DEBUG" 18 ) 19 20 // Complete structs define completion for a command with CLI options 21 type Complete struct { 22 Command Command 23 cmd.CLI 24 Out io.Writer 25 } 26 27 // New creates a new complete command. 28 // name is the name of command we want to auto complete. 29 // IMPORTANT: it must be the same name - if the auto complete 30 // completes the 'go' command, name must be equal to "go". 31 // command is the struct of the command completion. 32 func New(name string, command Command) *Complete { 33 return &Complete{ 34 Command: command, 35 CLI: cmd.CLI{Name: name}, 36 Out: os.Stdout, 37 } 38 } 39 40 // Run runs the completion and add installation flags beforehand. 41 // The flags are added to the main flag CommandLine variable. 42 func (c *Complete) Run() bool { 43 c.AddFlags(nil) 44 flag.Parse() 45 return c.Complete() 46 } 47 48 // Complete a command from completion line in environment variable, 49 // and print out the complete options. 50 // returns success if the completion ran or if the cli matched 51 // any of the given flags, false otherwise 52 // For installation: it assumes that flags were added and parsed before 53 // it was called. 54 func (c *Complete) Complete() bool { 55 line, point, ok := getEnv() 56 if !ok { 57 // make sure flags parsed, 58 // in case they were not added in the main program 59 return c.CLI.Run() 60 } 61 62 if point >= 0 && point < len(line) { 63 line = line[:point] 64 } 65 66 Log("Completing phrase: %s", line) 67 a := newArgs(line) 68 Log("Completing last field: %s", a.Last) 69 options := c.Command.Predict(a) 70 Log("Options: %s", options) 71 72 // filter only options that match the last argument 73 if a.Last == "" { 74 Log("Matches: %s", options) 75 c.output(options) 76 return true 77 } 78 fuzzyMatches := fuzzy.Find(a.Last, options) 79 matches := make([]string, len(fuzzyMatches)) 80 for i, fm := range fuzzyMatches { 81 matches[i] = fm.Str 82 } 83 Log("Matches: %s", matches) 84 c.output(matches) 85 return true 86 } 87 88 func getEnv() (line string, point int, ok bool) { 89 line = os.Getenv(envLine) 90 if line == "" { 91 return 92 } 93 point, err := strconv.Atoi(os.Getenv(envPoint)) 94 if err != nil { 95 // If failed parsing point for some reason, set it to point 96 // on the end of the line. 97 Log("Failed parsing point %s: %v", os.Getenv(envPoint), err) 98 point = len(line) 99 } 100 return line, point, true 101 } 102 103 func (c *Complete) output(options []string) { 104 // stdout of program defines the complete options 105 for _, option := range options { 106 fmt.Fprintln(c.Out, option) 107 } 108 }