mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-14 19:27:58 +08:00
c53c094e78
This fixes #945.
117 lines
2.6 KiB
Go
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
|
|
}
|