elvish/pkg/edit/key_binding.go
2020-05-02 23:29:55 +01:00

117 lines
2.6 KiB
Go

package edit
import (
"bufio"
"io"
"os"
"sync"
"github.com/elves/elvish/pkg/cli"
"github.com/elves/elvish/pkg/cli/term"
"github.com/elves/elvish/pkg/eval"
"github.com/elves/elvish/pkg/eval/vals"
"github.com/elves/elvish/pkg/eval/vars"
"github.com/elves/elvish/pkg/parse"
"github.com/elves/elvish/pkg/ui"
)
type mapBinding struct {
nt notifier
ev *eval.Evaler
mapVars []vars.PtrVar
}
func newMapBinding(nt notifier, ev *eval.Evaler, mapVars ...vars.PtrVar) cli.Handler {
return mapBinding{nt, ev, mapVars}
}
func (b mapBinding) Handle(e term.Event) bool {
k, ok := e.(term.KeyEvent)
if !ok {
return false
}
maps := make([]BindingMap, len(b.mapVars))
for i, v := range b.mapVars {
maps[i] = v.GetRaw().(BindingMap)
}
f := indexLayeredBindings(ui.Key(k), maps...)
if f == nil {
return false
}
callWithNotifyPorts(b.nt, b.ev, f)
return true
}
// Indexes a series of layered bindings. Returns nil if none of the bindings
// have the required key or a default.
func indexLayeredBindings(k ui.Key, bindings ...BindingMap) eval.Callable {
for _, binding := range bindings {
if binding.HasKey(k) {
return binding.GetKey(k)
}
}
for _, binding := range bindings {
if binding.HasKey(ui.Default) {
return binding.GetKey(ui.Default)
}
}
return nil
}
var bindingSource = parse.Source{Name: "[editor binding]"}
func callWithNotifyPorts(nt notifier, ev *eval.Evaler, f eval.Callable, args ...interface{}) {
// TODO(xiaq): Use CallWithOutputCallback when it supports redirecting the
// stderr port.
notifyPort, cleanup := makeNotifyPort(nt)
defer cleanup()
ports := []*eval.Port{eval.DevNullClosedChan, notifyPort, notifyPort}
frame := eval.NewTopFrame(ev, bindingSource, ports)
err := f.Call(frame, args, eval.NoOpts)
if err != nil {
nt.notifyError("binding", err)
}
}
func makeNotifyPort(nt notifier) (*eval.Port, func()) {
ch := make(chan interface{})
r, w, err := os.Pipe()
if err != nil {
panic(err)
}
var wg sync.WaitGroup
wg.Add(2)
go func() {
// Relay value outputs
for v := range ch {
nt.notifyf("[value out] %s", vals.Repr(v, vals.NoPretty))
}
wg.Done()
}()
go func() {
// Relay byte outputs
reader := bufio.NewReader(r)
for {
line, err := reader.ReadString('\n')
if err != nil {
if line != "" {
nt.notifyf("[bytes out] %s", line)
}
if err != io.EOF {
nt.notifyf("[bytes error] %s", err)
}
break
}
nt.notifyf("[bytes out] %s", line[:len(line)-1])
}
wg.Done()
}()
port := &eval.Port{Chan: ch, File: w, CloseChan: true, CloseFile: true}
cleanup := func() {
port.Close()
wg.Wait()
}
return port, cleanup
}