github.com/oweisse/u-root@v0.0.0-20181109060735-d005ad25fef1/cmds/elvish/edit/completion/arg_completers.go (about) 1 package completion 2 3 import ( 4 "bufio" 5 "errors" 6 "io" 7 "os" 8 "strings" 9 10 "github.com/u-root/u-root/cmds/elvish/eval" 11 "github.com/u-root/u-root/cmds/elvish/hash" 12 "github.com/u-root/u-root/cmds/elvish/hashmap" 13 ) 14 15 // For an overview of completion, see the comment in completers.go. 16 // 17 // When completing arguments of commands (as opposed to variable names, map 18 // indicies, etc.), the list of candidates often depends on the command in 19 // question; e.g. "ls <Tab>" and "apt <Tab>" should yield different results 20 // because the commands "ls" and "apt" accept different arguments. To reflec 21 // this, Elvish has a map of "argument completers", with the key being the name 22 // of the command, and the value being the argument completer itself, accessible 23 // to script as $edit:arg-completer. The one entry with an empty string as the 24 // key is the fallback completer, and is used when an argument completer for the 25 // current command has not been defined. 26 // 27 // When completing an argument, Elvish first finds out the name of the command 28 // (e.g. "ls" or "apt") can tries to evaluate its arguments. It then calls the 29 // suitable completer with the name of the command and the arguments. The 30 // arguments are in evaluated forms: e.g. if an argument is 'foo' (with quotes), 31 // the argument is its value foo, not a literal 'foo'. The last argument is what 32 // needs to be completed; if the user is starting a new argument, e.g. by typing 33 // "ls a " (without quotes), the last argument passed to the argument completer 34 // will be an empty string. 35 // 36 // The argument completer should then return a list of what can replace the last 37 // argument. The results are of type rawCandidate, which basically means that 38 // argument completers do not need to worry about quoting of candidates; the raw 39 // candidates will be "cooked" into actual candidates that appear in the 40 // interface, which includes quoting. 41 // 42 // There are a few finer points in this process: 43 // 44 // 1. If some of the arguments cannot be evaluated statically (for instance, 45 // consider this: echo (uname)), it will be an empty string. There needs 46 // probably be a better way to distinguish empty arguments and unknown 47 // arguments, but normally there is not much argument completers can do for 48 // unknown arguments. 49 // 50 // 2. The argument completer normally **should not** perform filtering. For 51 // instance, if the user has typed "ls x", the argument completer for "ls" 52 // should return **all** files, not just those whose names start with x. This 53 // is to make it possible for user to specify a different matching algorithm 54 // than the default prefix matching. 55 // 56 // However, argument completers **should** look at the argument to decide 57 // which **type** of candidates to generate. For instance, if the user has 58 // typed "ls --x", the argument completer should generate all long options 59 // for "ls", but not only those starting with "x". 60 61 var ( 62 // errCompleterMustBeFn is thrown if the user has put a non-function entry 63 // in $edit:completer, and that entry needs to be used for completion. 64 // TODO(xiaq): Detect the type violation when the user modifies 65 // $edit:completer. 66 errCompleterMustBeFn = errors.New("completer must be fn") 67 // errNoMatchingCompleter is thrown if there is no completer matching the 68 // current command. 69 errNoMatchingCompleter = errors.New("no matching completer") 70 // errCompleterArgMustBeString is thrown when a builtin argument completer 71 // is called with non-string arguments. 72 errCompleterArgMustBeString = errors.New("arguments to arg completers must be string") 73 // errTooFewArguments is thrown when a builtin argument completer is called 74 // with too few arguments. 75 errTooFewArguments = errors.New("too few arguments") 76 ) 77 78 var ( 79 argCompletersData = map[string]*argCompleterEntry{ 80 "": {"complete-filename", complFilename}, 81 "sudo": {"complete-sudo", complSudo}, 82 } 83 ) 84 85 type argCompleterEntry struct { 86 name string 87 impl func([]string, *eval.Evaler, hashmap.Map, chan<- rawCandidate) error 88 } 89 90 // completeArg calls the correct argument completers according to the command 91 // name. It is used by complArg and can also be useful when further dispatching 92 // based on command name is needed -- e.g. in the argument completer for "sudo". 93 func completeArg(words []string, ev *eval.Evaler, ac hashmap.Map, rawCands chan<- rawCandidate) error { 94 logger.Printf("completing argument: %q", words) 95 var v interface{} 96 index := words[0] 97 v, ok := ac.Index(index) 98 if !ok { 99 v, ok = ac.Index("") 100 if !ok { 101 return errNoMatchingCompleter 102 } 103 } 104 fn, ok := v.(eval.Callable) 105 if !ok { 106 return errCompleterMustBeFn 107 } 108 return callArgCompleter(fn, ev, words, rawCands) 109 } 110 111 type builtinArgCompleter struct { 112 name string 113 impl func([]string, *eval.Evaler, hashmap.Map, chan<- rawCandidate) error 114 115 argCompleter hashmap.Map 116 } 117 118 var _ eval.Callable = &builtinArgCompleter{} 119 120 func (bac *builtinArgCompleter) Kind() string { 121 return "fn" 122 } 123 124 // Equal compares by identity. 125 func (bac *builtinArgCompleter) Equal(a interface{}) bool { 126 return bac == a 127 } 128 129 func (bac *builtinArgCompleter) Hash() uint32 { 130 return hash.Hash(bac) 131 } 132 133 func (bac *builtinArgCompleter) Repr(int) string { 134 return "$edit:" + bac.name + eval.FnSuffix 135 } 136 137 func (bac *builtinArgCompleter) Call(ec *eval.Frame, args []interface{}, opts map[string]interface{}) error { 138 eval.TakeNoOpt(opts) 139 words := make([]string, len(args)) 140 for i, arg := range args { 141 s, ok := arg.(string) 142 if !ok { 143 throw(errCompleterArgMustBeString) 144 } 145 words[i] = s 146 } 147 148 rawCands := make(chan rawCandidate) 149 var err error 150 go func() { 151 defer close(rawCands) 152 err = bac.impl(words, ec.Evaler, bac.argCompleter, rawCands) 153 }() 154 155 output := ec.OutputChan() 156 for rc := range rawCands { 157 output <- rc 158 } 159 return err 160 } 161 162 func complFilename(words []string, ev *eval.Evaler, ac hashmap.Map, rawCands chan<- rawCandidate) error { 163 if len(words) < 1 { 164 return errTooFewArguments 165 } 166 return complFilenameInner(words[len(words)-1], false, rawCands) 167 } 168 169 func complSudo(words []string, ev *eval.Evaler, ac hashmap.Map, rawCands chan<- rawCandidate) error { 170 if len(words) < 2 { 171 return errTooFewArguments 172 } 173 if len(words) == 2 { 174 return complFormHeadInner(words[1], ev, rawCands) 175 } 176 return completeArg(words[1:], ev, ac, rawCands) 177 } 178 179 // callArgCompleter calls a Fn, assuming that it is an arg completer. It calls 180 // the Fn with specified arguments and closed input, and converts its output to 181 // candidate objects. 182 func callArgCompleter(fn eval.Callable, ev *eval.Evaler, words []string, rawCands chan<- rawCandidate) error { 183 184 // Quick path for builtin arg completers. 185 if builtin, ok := fn.(*builtinArgCompleter); ok { 186 return builtin.impl(words, ev, builtin.argCompleter, rawCands) 187 } 188 189 args := make([]interface{}, len(words)) 190 for i, word := range words { 191 args[i] = word 192 } 193 194 ports := []*eval.Port{ 195 eval.DevNullClosedChan, 196 {}, // Will be replaced when capturing output 197 {File: os.Stderr}, 198 } 199 200 valuesCb := func(ch <-chan interface{}) { 201 for v := range ch { 202 switch v := v.(type) { 203 case rawCandidate: 204 rawCands <- v 205 case string: 206 rawCands <- plainCandidate(v) 207 default: 208 logger.Printf("completer must output string or candidate") 209 } 210 } 211 } 212 213 bytesCb := func(r *os.File) { 214 buffered := bufio.NewReader(r) 215 for { 216 line, err := buffered.ReadString('\n') 217 if line != "" { 218 rawCands <- plainCandidate(strings.TrimSuffix(line, "\n")) 219 } 220 if err != nil { 221 if err != io.EOF { 222 logger.Println("error on reading:", err) 223 } 224 break 225 } 226 } 227 } 228 229 // XXX There is no source to pass to NewTopEvalCtx. 230 ec := eval.NewTopFrame(ev, eval.NewInternalSource("[editor completer]"), ports) 231 err := ec.CallWithOutputCallback(fn, args, eval.NoOpts, valuesCb, bytesCb) 232 if err != nil { 233 err = errors.New("completer error: " + err.Error()) 234 } 235 236 return err 237 }