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  }