github.com/oweisse/u-root@v0.0.0-20181109060735-d005ad25fef1/cmds/elvish/edit/completion/completion.go (about) 1 package completion 2 3 // Completion in Elvish is organized around the concept of "completers", 4 // functions that take the current AST Node (the Node that the cursor is at, 5 // always a leaf in the AST) and an eval.Evaler and returns a specification for 6 // the completion (a complSpec) -- a list of completion candidates, and which 7 // part of the source code they can **replace**. When completion is requested, 8 // the editor calls each completer; it is up to the completer to decide whether 9 // they apply to the current context. As soon as one completer returns results, 10 // the remaining completers are not tried. 11 // 12 // As an example instance, if the user writes the following and presses Tab: 13 // 14 // echo $p 15 // 16 // assuming that only the builtin variables $paths, $pid and $pwd are viable 17 // candidates, one of the completers -- the variable completer -- will return a 18 // complSpec that means "any of paths, pid and pwd can replace the 'p' in the 19 // source code". 20 // 21 // Note that the "replace" part in the semantics of complSpec is important: in 22 // the default setting of prefix matching, it might be easier to define 23 // complSpec in such a way that completers say "any of aths, id and wd can be 24 // appended to the 'p' in the source code". However, this is not flexible enough 25 // for alternative matching mechanism like substring matching or subsequence 26 // matching, where the "seed" of completion (here, p) may not be a prefix of the 27 // candidates. 28 // 29 // There is one completer that deserves more attention than others, the 30 // completer for arguments. Unlike other completers, it delegates most of its 31 // work to argument completers. See the comment in arg_completers.go for 32 // details. 33 34 import ( 35 "github.com/u-root/u-root/cmds/elvish/eval" 36 "github.com/u-root/u-root/cmds/elvish/parse" 37 "github.com/u-root/u-root/cmds/elvish/util" 38 "github.com/u-root/u-root/cmds/elvish/hashmap" 39 ) 40 41 var logger = util.GetLogger("[edit/completion] ") 42 43 type complContext interface { 44 name() string 45 common() *complContextCommon 46 generate(*complEnv, chan<- rawCandidate) error 47 } 48 49 type complContextCommon struct { 50 seed string 51 quoting parse.PrimaryType 52 begin, end int 53 } 54 55 func (c *complContextCommon) common() *complContextCommon { return c } 56 57 // complEnv contains environment information that may affect candidate 58 // generation. 59 type complEnv struct { 60 evaler *eval.Evaler 61 matcher hashmap.Map 62 argCompleter hashmap.Map 63 } 64 65 // complSpec is the result of a completion, meaning that any of the candidates 66 // can replace the text in the interval [begin, end). 67 type complSpec struct { 68 begin int 69 end int 70 candidates []*candidate 71 } 72 73 // A complContextFinder takes the current Node (always a leaf in the AST) and an 74 // Evaler, and returns a complContext. If the complContext does not apply to the 75 // type of the current Node, it should return nil. 76 type complContextFinder func(parse.Node, pureEvaler) complContext 77 78 type pureEvaler interface { 79 PurelyEvalCompound(*parse.Compound) (string, error) 80 PurelyEvalPartialCompound(cn *parse.Compound, upto *parse.Indexing) (string, error) 81 PurelyEvalPrimary(*parse.Primary) interface{} 82 } 83 84 var complContextFinders = []complContextFinder{ 85 findVariableComplContext, 86 findCommandComplContext, 87 findIndexComplContext, 88 findRedirComplContext, 89 findArgComplContext, 90 } 91 92 // complete takes a Node and Evaler and tries all complContexts. It returns the 93 // name of the complContext, and the result and error it gave. If no complContext is 94 // available, it returns an empty complContext name. 95 func complete(n parse.Node, env *complEnv) (string, *complSpec, error) { 96 for _, finder := range complContextFinders { 97 ctx := finder(n, env.evaler) 98 if ctx == nil { 99 continue 100 } 101 name := ctx.name() 102 ctxCommon := ctx.common() 103 104 matcher, ok := lookupMatcher(env.matcher, name) 105 if !ok { 106 return name, nil, errMatcherMustBeFn 107 } 108 109 chanRawCandidate := make(chan rawCandidate) 110 chanErrGenerate := make(chan error) 111 go func() { 112 err := ctx.generate(env, chanRawCandidate) 113 close(chanRawCandidate) 114 chanErrGenerate <- err 115 }() 116 117 rawCandidates, errFilter := filterRawCandidates(env.evaler, matcher, ctxCommon.seed, chanRawCandidate) 118 candidates := make([]*candidate, len(rawCandidates)) 119 for i, raw := range rawCandidates { 120 candidates[i] = raw.cook(ctxCommon.quoting) 121 } 122 spec := &complSpec{ctxCommon.begin, ctxCommon.end, candidates} 123 return name, spec, util.Errors(<-chanErrGenerate, errFilter) 124 125 } 126 return "", nil, nil 127 }