github.com/hoop33/elvish@v0.0.0-20160801152013-6d25485beab4/edit/arg_completer.go (about) 1 package edit 2 3 import ( 4 "errors" 5 "os" 6 7 "github.com/elves/elvish/eval" 8 ) 9 10 // CompleterTable provides $le:completer. It implements eval.IndexSetter. 11 type CompleterTable map[string]ArgCompleter 12 13 var _ eval.IndexSetter = CompleterTable(nil) 14 15 var ( 16 ErrCompleterIndexMustBeString = errors.New("index of completer table must be string") 17 ErrCompleterValueMustBeFunc = errors.New("value of completer table must be function") 18 ) 19 20 func (CompleterTable) Kind() string { 21 return "map" 22 } 23 24 func (ct CompleterTable) Repr(indent int) string { 25 return "<repr not implemented yet>" 26 } 27 28 func (ct CompleterTable) IndexOne(idx eval.Value) eval.Value { 29 head, ok := idx.(eval.String) 30 if !ok { 31 throw(ErrCompleterIndexMustBeString) 32 } 33 v := ct[string(head)] 34 if fac, ok := v.(FnAsArgCompleter); ok { 35 return fac.Fn 36 } 37 return eval.String("<get not implemented yet>") 38 } 39 40 func (ct CompleterTable) IndexSet(idx eval.Value, v eval.Value) { 41 head, ok := idx.(eval.String) 42 if !ok { 43 throw(ErrCompleterIndexMustBeString) 44 } 45 value, ok := v.(eval.FnValue) 46 if !ok { 47 throw(ErrCompleterValueMustBeFunc) 48 } 49 ct[string(head)] = FnAsArgCompleter{value} 50 } 51 52 // ArgCompleter is an argument completer. Its Complete method is called with all 53 // words of the form. There are at least two words: the first one being the form 54 // head and the last word being the current argument to complete. It should 55 // return a list of candidates for the current argument and errors. 56 type ArgCompleter interface { 57 Complete([]string, *Editor) ([]*candidate, error) 58 } 59 60 type FuncArgCompleter struct { 61 impl func([]string, *Editor) ([]*candidate, error) 62 } 63 64 func (fac FuncArgCompleter) Complete(words []string, ed *Editor) ([]*candidate, error) { 65 return fac.impl(words, ed) 66 } 67 68 var DefaultArgCompleter = "" 69 var argCompleter map[string]ArgCompleter 70 71 func init() { 72 argCompleter = map[string]ArgCompleter{ 73 DefaultArgCompleter: FuncArgCompleter{complFilename}, 74 "sudo": FuncArgCompleter{complSudo}, 75 } 76 } 77 78 func completeArg(words []string, ed *Editor) ([]*candidate, error) { 79 Logger.Printf("completing argument: %q", words) 80 compl, ok := argCompleter[words[0]] 81 if !ok { 82 compl = argCompleter[DefaultArgCompleter] 83 } 84 return compl.Complete(words, ed) 85 } 86 87 func complFilename(words []string, ed *Editor) ([]*candidate, error) { 88 return complFilenameInner(words[len(words)-1], false) 89 } 90 91 func complSudo(words []string, ed *Editor) ([]*candidate, error) { 92 if len(words) == 2 { 93 return complFormHeadInner(words[1], ed) 94 } 95 return completeArg(words[1:], ed) 96 } 97 98 type FnAsArgCompleter struct { 99 Fn eval.FnValue 100 } 101 102 func (fac FnAsArgCompleter) Complete(words []string, ed *Editor) ([]*candidate, error) { 103 in, err := makeClosedStdin() 104 if err != nil { 105 return nil, err 106 } 107 ports := []*eval.Port{in, &eval.Port{File: os.Stdout}, &eval.Port{File: os.Stderr}} 108 109 wordValues := make([]eval.Value, len(words)) 110 for i, word := range words { 111 wordValues[i] = eval.String(word) 112 } 113 114 // XXX There is no source to pass to NewTopEvalCtx. 115 ec := eval.NewTopEvalCtx(ed.evaler, "[editor completer]", "", ports) 116 values, err := ec.PCaptureOutput(fac.Fn, wordValues) 117 if err != nil { 118 ed.notify("completer error: %v", err) 119 return nil, err 120 } 121 122 cands := make([]*candidate, len(values)) 123 for i, v := range values { 124 s := eval.ToString(v) 125 cands[i] = &candidate{text: s} 126 } 127 return cands, nil 128 }