Move things.

This commit is contained in:
Qi Xiao 2016-02-28 01:47:56 +01:00
parent d7383574f6
commit b56aa4dd47
6 changed files with 218 additions and 210 deletions

211
edit/binding.go Normal file
View File

@ -0,0 +1,211 @@
package edit
import (
"bufio"
"errors"
"fmt"
"os"
"sync"
"github.com/elves/elvish/eval"
"github.com/elves/elvish/parse"
)
// Errors thrown to Evaler.
var (
ErrTakeNoArg = errors.New("editor builtins take no arguments")
ErrEditorInactive = errors.New("editor inactive")
ErrKeyMustBeString = errors.New("key must be string")
)
// BindingTable adapts a binding table to eval.IndexSetter, so that it can be
// manipulated in elvish script.
type BindingTable struct {
inner map[Key]BoundFunc
}
func (BindingTable) Kind() string {
return "map"
}
func (bt BindingTable) Repr(indent int) string {
var builder eval.MapReprBuilder
builder.Indent = indent
for k, v := range bt.inner {
builder.WritePair(parse.Quote(k.String()), v.Repr(eval.IncIndent(indent, 1)))
}
return builder.String()
}
func (bt BindingTable) IndexOne(idx eval.Value) eval.Value {
key := keyIndex(idx)
switch f := bt.inner[key].(type) {
case Builtin:
return eval.String(f.name)
case CallerBoundFunc:
return f.Caller
}
throw(errors.New("bug"))
panic("unreachable")
}
func (bt BindingTable) IndexSet(idx, v eval.Value) {
key := keyIndex(idx)
var f BoundFunc
switch v := v.(type) {
case eval.String:
builtin, ok := builtinMap[string(v)]
if !ok {
throw(fmt.Errorf("no builtin named %s", v.Repr(eval.NoPretty)))
}
f = builtin
case eval.CallerValue:
f = CallerBoundFunc{v}
default:
throw(fmt.Errorf("bad function type %s", v.Kind()))
}
bt.inner[key] = f
}
func keyIndex(idx eval.Value) Key {
skey, ok := idx.(eval.String)
if !ok {
throw(ErrKeyMustBeString)
}
key, err := parseKey(string(skey))
if err != nil {
throw(err)
}
return key
}
// BuiltinCallerValue adapts a Builtin to satisfy eval.Value and eval.Caller,
// so that it can be called from elvish script.
type BuiltinCallerValue struct {
b Builtin
ed *Editor
}
func (*BuiltinCallerValue) Kind() string {
return "fn"
}
func (eb *BuiltinCallerValue) Repr(int) string {
return "<editor builtin " + eb.b.name + ">"
}
func (eb *BuiltinCallerValue) Call(ec *eval.EvalCtx, args []eval.Value) {
if len(args) > 0 {
throw(ErrTakeNoArg)
}
if !eb.ed.active {
throw(ErrEditorInactive)
}
eb.b.impl(eb.ed)
}
// BoundFunc is a function bound to a key. It is either a Builtin or an
// EvalCaller.
type BoundFunc interface {
eval.Reprer
Call(ed *Editor)
}
func (b Builtin) Repr(int) string {
return b.name
}
func (b Builtin) Call(ed *Editor) {
b.impl(ed)
}
// CallerBoundFunc adapts eval.Caller to BoundFunc, so that functions in elvish
// script can be bound to keys.
type CallerBoundFunc struct {
Caller eval.CallerValue
}
func (c CallerBoundFunc) Repr(indent int) string {
return c.Caller.Repr(indent)
}
func (c CallerBoundFunc) Call(ed *Editor) {
rout, chanOut, ports, err := makePorts()
if err != nil {
return
}
// Goroutines to collect output.
var wg sync.WaitGroup
wg.Add(2)
go func() {
rd := bufio.NewReader(rout)
for {
line, err := rd.ReadString('\n')
if err != nil {
break
}
// XXX notify is not concurrency-safe.
ed.notify("[bound fn bytes] %s", line[:len(line)-1])
}
rout.Close()
wg.Done()
}()
go func() {
for v := range chanOut {
ed.notify("[bound fn value] %s", v.Repr(eval.NoPretty))
}
wg.Done()
}()
// XXX There is no source to pass to NewTopEvalCtx.
ec := eval.NewTopEvalCtx(ed.evaler, "[editor]", "", ports)
ex := ec.PCall(c.Caller, []eval.Value{})
if ex != nil {
ed.notify("function error: %s", ex.Error())
}
eval.ClosePorts(ports)
wg.Wait()
ed.refresh(true, true)
}
// makePorts connects stdin to /dev/null and a closed channel, identifies
// stdout and stderr and connects them to a pipe and channel. It returns the
// other end of stdout and the resulting []*eval.Port. The caller is
// responsible for closing the returned file and calling eval.ClosePorts on the
// ports.
func makePorts() (*os.File, chan eval.Value, []*eval.Port, error) {
in, err := makeClosedStdin()
if err != nil {
return nil, nil, nil, err
}
// Output
rout, out, err := os.Pipe()
if err != nil {
Logger.Println(err)
return nil, nil, nil, err
}
chanOut := make(chan eval.Value)
return rout, chanOut, []*eval.Port{
in,
{File: out, CloseFile: true, Chan: chanOut, CloseChan: true},
{File: out, Chan: chanOut},
}, nil
}
func makeClosedStdin() (*eval.Port, error) {
// Input
devnull, err := os.Open("/dev/null")
if err != nil {
Logger.Println(err)
return nil, err
}
in := make(chan eval.Value)
close(in)
return &eval.Port{File: devnull, CloseFile: true, Chan: in}, nil
}

View File

@ -3,6 +3,8 @@ package edit
import (
"fmt"
"unicode/utf8"
"github.com/elves/elvish/util"
)
// Completion subsystem.
@ -198,7 +200,7 @@ func (comp *completion) List(width, maxHeight int) *buffer {
if cols == 0 {
cols = 1
}
lines := CeilDiv(len(cands), cols)
lines := util.CeilDiv(len(cands), cols)
comp.lines = lines
// Determine the window to show.

View File

@ -1,26 +1,12 @@
package edit
import (
"bufio"
"errors"
"fmt"
"os"
"sync"
"github.com/elves/elvish/eval"
"github.com/elves/elvish/parse"
"github.com/elves/elvish/util"
)
// Interface exposed to elvish script.
// Errors thrown to Evaler.
var (
ErrTakeNoArg = errors.New("editor builtins take no arguments")
ErrEditorInactive = errors.New("editor inactive")
errKeyMustBeString = errors.New("key must be string")
)
// makeModule builds a module from an Editor.
func makeModule(ed *Editor) eval.Namespace {
ns := eval.Namespace{}
@ -49,198 +35,6 @@ func makeModule(ed *Editor) eval.Namespace {
return ns
}
// BindingTable adapts a binding table to eval.IndexSetter, so that it can be
// manipulated in elvish script.
type BindingTable struct {
inner map[Key]BoundFunc
}
func (BindingTable) Kind() string {
return "map"
}
func (bt BindingTable) Repr(indent int) string {
var builder eval.MapReprBuilder
builder.Indent = indent
for k, v := range bt.inner {
builder.WritePair(parse.Quote(k.String()), v.Repr(eval.IncIndent(indent, 1)))
}
return builder.String()
}
func (bt BindingTable) IndexOne(idx eval.Value) eval.Value {
key := keyIndex(idx)
switch f := bt.inner[key].(type) {
case Builtin:
return eval.String(f.name)
case CallerBoundFunc:
return f.Caller
}
throw(errors.New("bug"))
panic("unreachable")
}
func (bt BindingTable) IndexSet(idx, v eval.Value) {
key := keyIndex(idx)
var f BoundFunc
switch v := v.(type) {
case eval.String:
builtin, ok := builtinMap[string(v)]
if !ok {
throw(fmt.Errorf("no builtin named %s", v.Repr(eval.NoPretty)))
}
f = builtin
case eval.CallerValue:
f = CallerBoundFunc{v}
default:
throw(fmt.Errorf("bad function type %s", v.Kind()))
}
bt.inner[key] = f
}
func keyIndex(idx eval.Value) Key {
skey, ok := idx.(eval.String)
if !ok {
throw(errKeyMustBeString)
}
key, err := parseKey(string(skey))
if err != nil {
throw(err)
}
return key
}
// BuiltinCallerValue adapts a Builtin to satisfy eval.Value and eval.Caller,
// so that it can be called from elvish script.
type BuiltinCallerValue struct {
b Builtin
ed *Editor
}
func (*BuiltinCallerValue) Kind() string {
return "fn"
}
func (eb *BuiltinCallerValue) Repr(int) string {
return "<editor builtin " + eb.b.name + ">"
}
func (eb *BuiltinCallerValue) Call(ec *eval.EvalCtx, args []eval.Value) {
if len(args) > 0 {
throw(ErrTakeNoArg)
}
if !eb.ed.active {
throw(ErrEditorInactive)
}
eb.b.impl(eb.ed)
}
// BoundFunc is a function bound to a key. It is either a Builtin or an
// EvalCaller.
type BoundFunc interface {
eval.Reprer
Call(ed *Editor)
}
func (b Builtin) Repr(int) string {
return b.name
}
func (b Builtin) Call(ed *Editor) {
b.impl(ed)
}
// CallerBoundFunc adapts eval.Caller to BoundFunc, so that functions in elvish
// script can be bound to keys.
type CallerBoundFunc struct {
Caller eval.CallerValue
}
func (c CallerBoundFunc) Repr(indent int) string {
return c.Caller.Repr(indent)
}
func (c CallerBoundFunc) Call(ed *Editor) {
rout, chanOut, ports, err := makePorts()
if err != nil {
return
}
// Goroutines to collect output.
var wg sync.WaitGroup
wg.Add(2)
go func() {
rd := bufio.NewReader(rout)
for {
line, err := rd.ReadString('\n')
if err != nil {
break
}
// XXX notify is not concurrency-safe.
ed.notify("[bound fn bytes] %s", line[:len(line)-1])
}
rout.Close()
wg.Done()
}()
go func() {
for v := range chanOut {
ed.notify("[bound fn value] %s", v.Repr(eval.NoPretty))
}
wg.Done()
}()
// XXX There is no source to pass to NewTopEvalCtx.
ec := eval.NewTopEvalCtx(ed.evaler, "[editor]", "", ports)
ex := ec.PCall(c.Caller, []eval.Value{})
if ex != nil {
ed.notify("function error: %s", ex.Error())
}
eval.ClosePorts(ports)
wg.Wait()
ed.refresh(true, true)
}
// makePorts connects stdin to /dev/null and a closed channel, identifies
// stdout and stderr and connects them to a pipe and channel. It returns the
// other end of stdout and the resulting []*eval.Port. The caller is
// responsible for closing the returned file and calling eval.ClosePorts on the
// ports.
func makePorts() (*os.File, chan eval.Value, []*eval.Port, error) {
in, err := makeClosedStdin()
if err != nil {
return nil, nil, nil, err
}
// Output
rout, out, err := os.Pipe()
if err != nil {
Logger.Println(err)
return nil, nil, nil, err
}
chanOut := make(chan eval.Value)
return rout, chanOut, []*eval.Port{
in,
{File: out, CloseFile: true, Chan: chanOut, CloseChan: true},
{File: out, Chan: chanOut},
}, nil
}
func makeClosedStdin() (*eval.Port, error) {
// Input
devnull, err := os.Open("/dev/null")
if err != nil {
Logger.Println(err)
return nil, err
}
in := make(chan eval.Value)
close(in)
return &eval.Port{File: devnull, CloseFile: true, Chan: in}, nil
}
func throw(e error) {
util.Throw(e)
}

View File

@ -12,7 +12,8 @@ import (
var ErrPromptMustBeStringOrFunc = errors.New("prompt must be string or function")
// PromptVariable implements $le:prompt and $le:rprompt.
// PromptVariable is a prompt function variable. It may be set to a String, a
// Caller, or a BuiltinPrompt. It provides $le:prompt and $le:rprompt.
type PromptVariable struct {
Prompt *Prompt
}

View File

@ -1,4 +1,4 @@
package edit
package util
// CeilDiv computes ceil(float(a)/b) without using float arithmetics.
func CeilDiv(a, b int) int {

View File

@ -1,4 +1,4 @@
package edit
package util
import "testing"