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 }