elvish/pkg/edit/instant.go
Qi Xiao d77ad829b6 pkg/eval: Add Evaler.ParseAndCompile.
Also require a name argument in NewInteractiveSource.
2020-04-14 23:54:45 +01:00

83 lines
2.1 KiB
Go

package edit
import (
"bufio"
"io"
"os"
"strings"
"sync"
"github.com/elves/elvish/pkg/cli"
"github.com/elves/elvish/pkg/cli/addons/instant"
"github.com/elves/elvish/pkg/eval"
"github.com/elves/elvish/pkg/eval/vals"
)
//elvdoc:var -instant:binding
//
// Binding for the instant mode.
//elvdoc:fn -instant:start
//
// Starts the instant mode. In instant mode, any text entered at the command
// line is evaluated immediately, with the output displayed.
//
// **WARNING**: Beware of unintended consequences when using destructive
// commands. For example, if you type `sudo rm -rf /tmp/*` in the instant mode,
// Elvish will attempt to evaluate `sudo rm -rf /` when you typed that far.
func initInstant(app cli.App, ev *eval.Evaler, ns eval.Ns) {
bindingVar := newBindingVar(EmptyBindingMap)
binding := newMapBinding(app, ev, bindingVar)
ns.AddNs("-instant",
eval.Ns{
"binding": bindingVar,
}.AddGoFns("<edit:-instant>:", map[string]interface{}{
"start": func() { instantStart(app, ev, binding) },
}))
}
func instantStart(app cli.App, ev *eval.Evaler, binding cli.Handler) {
execute := func(code string) ([]string, error) {
src := eval.NewInteractiveSource("[instant]", code)
op, err := ev.ParseAndCompile(src)
if err != nil {
return nil, err
}
fm := eval.NewTopFrame(ev, src, []*eval.Port{
{File: eval.DevNull},
{}, // Will be replaced in CaptureOutput
{File: eval.DevNull},
})
var output []string
var outputMutex sync.Mutex
addLine := func(line string) {
outputMutex.Lock()
defer outputMutex.Unlock()
output = append(output, line)
}
valuesCb := func(ch <-chan interface{}) {
for v := range ch {
addLine("▶ " + vals.ToString(v))
}
}
bytesCb := func(r *os.File) {
bufr := bufio.NewReader(r)
for {
line, err := bufr.ReadString('\n')
if err != nil {
if err != io.EOF {
addLine("i/o error: " + err.Error())
}
break
}
addLine(strings.TrimSuffix(line, "\n"))
}
}
err = fm.PipeOutput(
func(fm *eval.Frame) error { return fm.Eval(op) }, valuesCb, bytesCb)
return output, err
}
instant.Start(app, instant.Config{Binding: binding, Execute: execute})
}