github.com/oweisse/u-root@v0.0.0-20181109060735-d005ad25fef1/cmds/elvish/edit/completion/candidate.go (about)

     1  package completion
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"sort"
     7  
     8  	"github.com/u-root/u-root/cmds/elvish/edit/ui"
     9  	"github.com/u-root/u-root/cmds/elvish/eval"
    10  	"github.com/u-root/u-root/cmds/elvish/eval/vals"
    11  	"github.com/u-root/u-root/cmds/elvish/parse"
    12  	"github.com/u-root/u-root/cmds/elvish/hash"
    13  )
    14  
    15  type candidate struct {
    16  	code string    // This is what will be substituted on the command line.
    17  	menu ui.Styled // This is what is displayed in the completion menu.
    18  }
    19  
    20  // rawCandidate is what can be converted to a candidate.
    21  type rawCandidate interface {
    22  	text() string
    23  	cook(q parse.PrimaryType) *candidate
    24  }
    25  
    26  type rawCandidates []rawCandidate
    27  
    28  func (cs rawCandidates) Len() int           { return len(cs) }
    29  func (cs rawCandidates) Swap(i, j int)      { cs[i], cs[j] = cs[j], cs[i] }
    30  func (cs rawCandidates) Less(i, j int) bool { return cs[i].text() < cs[j].text() }
    31  
    32  // plainCandidate is a minimal implementation of rawCandidate.
    33  type plainCandidate string
    34  
    35  func (plainCandidate) Kind() string               { return "string" }
    36  func (p plainCandidate) Equal(a interface{}) bool { return p == a }
    37  func (p plainCandidate) Hash() uint32             { return hash.Hash(string(p)) }
    38  func (p plainCandidate) Repr(l int) string        { return vals.Repr(string(p), l) }
    39  func (p plainCandidate) text() string             { return string(p) }
    40  
    41  func (p plainCandidate) cook(q parse.PrimaryType) *candidate {
    42  	s := string(p)
    43  	quoted, _ := parse.QuoteAs(s, q)
    44  	return &candidate{code: quoted, menu: ui.Unstyled(s)}
    45  }
    46  
    47  // noQuoteCandidate is a rawCandidate that does not quote when cooked.
    48  type noQuoteCandidate string
    49  
    50  func (noQuoteCandidate) Kind() string                { return "string" }
    51  func (nq noQuoteCandidate) Equal(a interface{}) bool { return nq == a }
    52  func (nq noQuoteCandidate) Hash() uint32             { return hash.Hash(string(nq)) }
    53  func (nq noQuoteCandidate) Repr(l int) string        { return vals.Repr(string(nq), l) }
    54  func (nq noQuoteCandidate) text() string             { return string(nq) }
    55  
    56  func (nq noQuoteCandidate) cook(parse.PrimaryType) *candidate {
    57  	s := string(nq)
    58  	return &candidate{code: s, menu: ui.Unstyled(s)}
    59  }
    60  
    61  // complexCandidate is an implementation of rawCandidate that offers
    62  // customization options.
    63  type complexCandidate struct {
    64  	stem          string    // Used in the code and the menu.
    65  	codeSuffix    string    // Appended to the code.
    66  	displaySuffix string    // Appended to the display.
    67  	style         ui.Styles // Used in the menu.
    68  }
    69  
    70  func (c *complexCandidate) Kind() string { return "map" }
    71  
    72  func (c *complexCandidate) Equal(a interface{}) bool {
    73  	rhs, ok := a.(*complexCandidate)
    74  	return ok && c.stem == rhs.stem && c.codeSuffix == rhs.codeSuffix && c.displaySuffix == rhs.displaySuffix && c.style.Eq(rhs.style)
    75  }
    76  
    77  func (c *complexCandidate) Hash() uint32 {
    78  	h := hash.DJBInit
    79  	h = hash.DJBCombine(h, hash.Hash(c.stem))
    80  	h = hash.DJBCombine(h, hash.Hash(c.codeSuffix))
    81  	h = hash.DJBCombine(h, hash.Hash(c.displaySuffix))
    82  	h = hash.DJBCombine(h, c.style.Hash())
    83  	return h
    84  }
    85  
    86  func (c *complexCandidate) Repr(indent int) string {
    87  	// TODO(xiaq): Pretty-print when indent >= 0
    88  	return fmt.Sprintf("(edit:complex-candidate %s &code-suffix=%s &display-suffix=%s style=%s)",
    89  		parse.Quote(c.stem), parse.Quote(c.codeSuffix),
    90  		parse.Quote(c.displaySuffix), parse.Quote(c.style.String()))
    91  }
    92  
    93  func (c *complexCandidate) text() string { return c.stem }
    94  
    95  func (c *complexCandidate) cook(q parse.PrimaryType) *candidate {
    96  	quoted, _ := parse.QuoteAs(c.stem, q)
    97  	return &candidate{
    98  		code: quoted + c.codeSuffix,
    99  		menu: ui.Styled{c.stem + c.displaySuffix, c.style},
   100  	}
   101  }
   102  
   103  // makeComplexCandidate composes a complexCandidate.
   104  func makeComplexCandidate(rawOpts eval.RawOptions, stem string) *complexCandidate {
   105  	opts := struct {
   106  		CodeSuffix    string
   107  		DisplaySuffix string
   108  		Style         string
   109  	}{}
   110  	rawOpts.Scan(&opts)
   111  
   112  	return &complexCandidate{
   113  		stem:          stem,
   114  		codeSuffix:    opts.CodeSuffix,
   115  		displaySuffix: opts.DisplaySuffix,
   116  		style:         ui.StylesFromString(opts.Style),
   117  	}
   118  }
   119  
   120  func filterRawCandidates(ev *eval.Evaler, matcher eval.Callable, seed string, chanRawCandidate <-chan rawCandidate) ([]rawCandidate, error) {
   121  
   122  	matcherInput := make(chan interface{})
   123  	stopCollector := make(chan struct{})
   124  	var collected []rawCandidate
   125  	go func() {
   126  		defer close(matcherInput)
   127  		for rc := range chanRawCandidate {
   128  			collected = append(collected, rc)
   129  			select {
   130  			case matcherInput <- rc.text():
   131  			case <-stopCollector:
   132  				return
   133  			}
   134  		}
   135  	}()
   136  	defer close(stopCollector)
   137  
   138  	ports := []*eval.Port{
   139  		{Chan: matcherInput, File: eval.DevNull}, {File: os.Stdout}, {File: os.Stderr}}
   140  	ec := eval.NewTopFrame(ev, eval.NewInternalSource("[editor matcher]"), ports)
   141  
   142  	args := []interface{}{seed}
   143  	values, err := ec.CaptureOutput(matcher, args, eval.NoOpts)
   144  	if err != nil {
   145  		return nil, err
   146  	} else if len(values) != len(collected) {
   147  		return nil, errIncorrectNumOfResults
   148  	}
   149  
   150  	var filtered []rawCandidate
   151  	for i, value := range values {
   152  		if vals.Bool(value) {
   153  			filtered = append(filtered, collected[i])
   154  		}
   155  	}
   156  	sort.Sort(rawCandidates(filtered))
   157  	return filtered, nil
   158  }