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  }