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