github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/edit/key_binding.go (about) 1 package edit 2 3 import ( 4 "bufio" 5 "io" 6 "os" 7 "sync" 8 9 "github.com/markusbkk/elvish/pkg/cli/term" 10 "github.com/markusbkk/elvish/pkg/cli/tk" 11 "github.com/markusbkk/elvish/pkg/eval" 12 "github.com/markusbkk/elvish/pkg/eval/vals" 13 "github.com/markusbkk/elvish/pkg/eval/vars" 14 "github.com/markusbkk/elvish/pkg/parse" 15 "github.com/markusbkk/elvish/pkg/ui" 16 ) 17 18 type mapBindings struct { 19 nt notifier 20 ev *eval.Evaler 21 mapVars []vars.PtrVar 22 } 23 24 func newMapBindings(nt notifier, ev *eval.Evaler, mapVars ...vars.PtrVar) tk.Bindings { 25 return mapBindings{nt, ev, mapVars} 26 } 27 28 func (b mapBindings) Handle(w tk.Widget, e term.Event) bool { 29 k, ok := e.(term.KeyEvent) 30 if !ok { 31 return false 32 } 33 maps := make([]bindingsMap, len(b.mapVars)) 34 for i, v := range b.mapVars { 35 maps[i] = v.GetRaw().(bindingsMap) 36 } 37 f := indexLayeredBindings(ui.Key(k), maps...) 38 if f == nil { 39 return false 40 } 41 callWithNotifyPorts(b.nt, b.ev, f) 42 return true 43 } 44 45 // Indexes a series of layered bindings. Returns nil if none of the bindings 46 // have the required key or a default. 47 func indexLayeredBindings(k ui.Key, maps ...bindingsMap) eval.Callable { 48 for _, m := range maps { 49 if m.HasKey(k) { 50 return m.GetKey(k) 51 } 52 } 53 for _, m := range maps { 54 if m.HasKey(ui.Default) { 55 return m.GetKey(ui.Default) 56 } 57 } 58 return nil 59 } 60 61 var bindingSource = parse.Source{Name: "[editor binding]"} 62 63 func callWithNotifyPorts(nt notifier, ev *eval.Evaler, f eval.Callable, args ...interface{}) { 64 notifyPort, cleanup := makeNotifyPort(nt) 65 defer cleanup() 66 67 err := ev.Call(f, 68 eval.CallCfg{Args: args, From: "[editor binding]"}, 69 eval.EvalCfg{Ports: []*eval.Port{nil, notifyPort, notifyPort}}) 70 if err != nil { 71 nt.notifyError("binding", err) 72 } 73 } 74 75 func makeNotifyPort(nt notifier) (*eval.Port, func()) { 76 ch := make(chan interface{}) 77 r, w, err := os.Pipe() 78 if err != nil { 79 panic(err) 80 } 81 var wg sync.WaitGroup 82 wg.Add(2) 83 go func() { 84 // Relay value outputs 85 for v := range ch { 86 nt.notifyf("[value out] %s", vals.ReprPlain(v)) 87 } 88 wg.Done() 89 }() 90 go func() { 91 // Relay byte outputs 92 reader := bufio.NewReader(r) 93 for { 94 line, err := reader.ReadString('\n') 95 if err != nil { 96 if line != "" { 97 nt.notifyf("[bytes out] %s", line) 98 } 99 if err != io.EOF { 100 nt.notifyf("[bytes error] %s", err) 101 } 102 break 103 } 104 nt.notifyf("[bytes out] %s", line[:len(line)-1]) 105 } 106 r.Close() 107 wg.Done() 108 }() 109 port := &eval.Port{Chan: ch, File: w} 110 cleanup := func() { 111 close(ch) 112 w.Close() 113 wg.Wait() 114 } 115 return port, cleanup 116 }