github.com/hoop33/elvish@v0.0.0-20160801152013-6d25485beab4/edit/binding.go (about) 1 package edit 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "os" 8 "sync" 9 10 "github.com/elves/elvish/eval" 11 "github.com/elves/elvish/parse" 12 ) 13 14 // Errors thrown to Evaler. 15 var ( 16 ErrTakeNoArg = errors.New("editor builtins take no arguments") 17 ErrEditorInactive = errors.New("editor inactive") 18 ErrKeyMustBeString = errors.New("key must be string") 19 ) 20 21 // BindingTable adapts a binding table to eval.IndexSetter, so that it can be 22 // manipulated in elvish script. 23 type BindingTable struct { 24 inner map[Key]BoundFunc 25 } 26 27 func (BindingTable) Kind() string { 28 return "map" 29 } 30 31 func (bt BindingTable) Repr(indent int) string { 32 var builder eval.MapReprBuilder 33 builder.Indent = indent 34 for k, v := range bt.inner { 35 builder.WritePair(parse.Quote(k.String()), v.Repr(eval.IncIndent(indent, 1))) 36 } 37 return builder.String() 38 } 39 40 func (bt BindingTable) IndexOne(idx eval.Value) eval.Value { 41 key := keyIndex(idx) 42 switch f := bt.inner[key].(type) { 43 case Builtin: 44 return eval.String(f.name) 45 case FnAsBoundFunc: 46 return f.Fn 47 } 48 throw(errors.New("bug")) 49 panic("unreachable") 50 } 51 52 func (bt BindingTable) IndexSet(idx, v eval.Value) { 53 key := keyIndex(idx) 54 55 var f BoundFunc 56 switch v := v.(type) { 57 case eval.String: 58 builtin, ok := builtinMap[string(v)] 59 if !ok { 60 throw(fmt.Errorf("no builtin named %s", v.Repr(eval.NoPretty))) 61 } 62 f = builtin 63 case eval.FnValue: 64 f = FnAsBoundFunc{v} 65 default: 66 throw(fmt.Errorf("bad function type %s", v.Kind())) 67 } 68 69 bt.inner[key] = f 70 } 71 72 func keyIndex(idx eval.Value) Key { 73 skey, ok := idx.(eval.String) 74 if !ok { 75 throw(ErrKeyMustBeString) 76 } 77 key, err := parseKey(string(skey)) 78 if err != nil { 79 throw(err) 80 } 81 return key 82 } 83 84 // BuiltinAsFnValue adapts a Builtin to satisfy eval.FnValue, so that it can be 85 // called from elvish script. 86 type BuiltinAsFnValue struct { 87 b Builtin 88 ed *Editor 89 } 90 91 func (*BuiltinAsFnValue) Kind() string { 92 return "fn" 93 } 94 95 func (eb *BuiltinAsFnValue) Repr(int) string { 96 return "<editor builtin " + eb.b.name + ">" 97 } 98 99 func (eb *BuiltinAsFnValue) Call(ec *eval.EvalCtx, args []eval.Value) { 100 if len(args) > 0 { 101 throw(ErrTakeNoArg) 102 } 103 if !eb.ed.active { 104 throw(ErrEditorInactive) 105 } 106 eb.b.impl(eb.ed) 107 } 108 109 // BoundFunc is a function bound to a key. It is either a Builtin or an 110 // FnAsBoundFunc. 111 type BoundFunc interface { 112 eval.Reprer 113 Call(ed *Editor) 114 } 115 116 func (b Builtin) Repr(int) string { 117 return b.name 118 } 119 120 func (b Builtin) Call(ed *Editor) { 121 b.impl(ed) 122 } 123 124 // FnAsBoundFunc adapts eval.Fn to BoundFunc, so that functions in elvish 125 // script can be bound to keys. 126 type FnAsBoundFunc struct { 127 Fn eval.FnValue 128 } 129 130 func (c FnAsBoundFunc) Repr(indent int) string { 131 return c.Fn.Repr(indent) 132 } 133 134 func (c FnAsBoundFunc) Call(ed *Editor) { 135 rout, chanOut, ports, err := makePorts() 136 if err != nil { 137 return 138 } 139 140 // Goroutines to collect output. 141 var wg sync.WaitGroup 142 wg.Add(2) 143 go func() { 144 rd := bufio.NewReader(rout) 145 for { 146 line, err := rd.ReadString('\n') 147 if err != nil { 148 break 149 } 150 // XXX notify is not concurrency-safe. 151 ed.notify("[bound fn bytes] %s", line[:len(line)-1]) 152 } 153 rout.Close() 154 wg.Done() 155 }() 156 go func() { 157 for v := range chanOut { 158 ed.notify("[bound fn value] %s", v.Repr(eval.NoPretty)) 159 } 160 wg.Done() 161 }() 162 163 // XXX There is no source to pass to NewTopEvalCtx. 164 ec := eval.NewTopEvalCtx(ed.evaler, "[editor]", "", ports) 165 ex := ec.PCall(c.Fn, []eval.Value{}) 166 if ex != nil { 167 ed.notify("function error: %s", ex.Error()) 168 } 169 170 eval.ClosePorts(ports) 171 wg.Wait() 172 ed.refresh(true, true) 173 } 174 175 // makePorts connects stdin to /dev/null and a closed channel, identifies 176 // stdout and stderr and connects them to a pipe and channel. It returns the 177 // other end of stdout and the resulting []*eval.Port. The caller is 178 // responsible for closing the returned file and calling eval.ClosePorts on the 179 // ports. 180 func makePorts() (*os.File, chan eval.Value, []*eval.Port, error) { 181 in, err := makeClosedStdin() 182 if err != nil { 183 return nil, nil, nil, err 184 } 185 186 // Output 187 rout, out, err := os.Pipe() 188 if err != nil { 189 Logger.Println(err) 190 return nil, nil, nil, err 191 } 192 chanOut := make(chan eval.Value) 193 194 return rout, chanOut, []*eval.Port{ 195 in, 196 {File: out, CloseFile: true, Chan: chanOut, CloseChan: true}, 197 {File: out, Chan: chanOut}, 198 }, nil 199 } 200 201 func makeClosedStdin() (*eval.Port, error) { 202 // Input 203 devnull, err := os.Open("/dev/null") 204 if err != nil { 205 Logger.Println(err) 206 return nil, err 207 } 208 in := make(chan eval.Value) 209 close(in) 210 return &eval.Port{File: devnull, CloseFile: true, Chan: in}, nil 211 }