github.com/elves/elvish@v0.15.0/pkg/edit/key_binding.go (about)

     1  package edit
     2  
     3  import (
     4  	"bufio"
     5  	"io"
     6  	"os"
     7  	"sync"
     8  
     9  	"github.com/elves/elvish/pkg/cli"
    10  	"github.com/elves/elvish/pkg/cli/term"
    11  	"github.com/elves/elvish/pkg/eval"
    12  	"github.com/elves/elvish/pkg/eval/vals"
    13  	"github.com/elves/elvish/pkg/eval/vars"
    14  	"github.com/elves/elvish/pkg/parse"
    15  	"github.com/elves/elvish/pkg/ui"
    16  )
    17  
    18  type mapBinding struct {
    19  	nt      notifier
    20  	ev      *eval.Evaler
    21  	mapVars []vars.PtrVar
    22  }
    23  
    24  func newMapBinding(nt notifier, ev *eval.Evaler, mapVars ...vars.PtrVar) cli.Handler {
    25  	return mapBinding{nt, ev, mapVars}
    26  }
    27  
    28  func (b mapBinding) Handle(e term.Event) bool {
    29  	k, ok := e.(term.KeyEvent)
    30  	if !ok {
    31  		return false
    32  	}
    33  	maps := make([]BindingMap, len(b.mapVars))
    34  	for i, v := range b.mapVars {
    35  		maps[i] = v.GetRaw().(BindingMap)
    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, bindings ...BindingMap) eval.Callable {
    48  	for _, binding := range bindings {
    49  		if binding.HasKey(k) {
    50  			return binding.GetKey(k)
    51  		}
    52  	}
    53  	for _, binding := range bindings {
    54  		if binding.HasKey(ui.Default) {
    55  			return binding.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.Repr(v, vals.NoPretty))
    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  }