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