mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-11-28 07:21:21 +08:00
cliedit: Consolidate files and add tests.
This commit is contained in:
parent
85a75c7722
commit
795281efdf
116
cliedit/api.go
Normal file
116
cliedit/api.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
package cliedit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/elves/elvish/cli"
|
||||
"github.com/elves/elvish/diag"
|
||||
"github.com/elves/elvish/eval"
|
||||
"github.com/elves/elvish/eval/vals"
|
||||
"github.com/elves/elvish/eval/vars"
|
||||
"github.com/xiaq/persistent/hashmap"
|
||||
)
|
||||
|
||||
func initAPI(app *cli.App, ev *eval.Evaler, ns eval.Ns) {
|
||||
initMaxHeight(app, ns)
|
||||
initBeforeReadline(app, ev, ns)
|
||||
initAfterReadline(app, ev, ns)
|
||||
initInsert(app, ev, ns)
|
||||
}
|
||||
|
||||
func initMaxHeight(app *cli.App, ns eval.Ns) {
|
||||
maxHeight := -1
|
||||
maxHeightVar := vars.FromPtr(&maxHeight)
|
||||
app.Config.MaxHeight = func() int { return maxHeightVar.Get().(int) }
|
||||
ns.Add("max-height", maxHeightVar)
|
||||
}
|
||||
|
||||
func initBeforeReadline(app *cli.App, ev *eval.Evaler, ns eval.Ns) {
|
||||
hook := vals.EmptyList
|
||||
hookVar := vars.FromPtr(&hook)
|
||||
ns["before-readline"] = hookVar
|
||||
app.Config.BeforeReadline = func() {
|
||||
i := -1
|
||||
hook := hookVar.Get().(vals.List)
|
||||
for it := hook.Iterator(); it.HasElem(); it.Next() {
|
||||
i++
|
||||
name := fmt.Sprintf("$before-readline[%d]", i)
|
||||
fn, ok := it.Elem().(eval.Callable)
|
||||
if !ok {
|
||||
// TODO(xiaq): This is not testable as it depends on stderr.
|
||||
// Make it testable.
|
||||
diag.Complainf("%s not function", name)
|
||||
continue
|
||||
}
|
||||
// TODO(xiaq): This should use stdPorts, but stdPorts is currently
|
||||
// unexported from eval.
|
||||
ports := []*eval.Port{
|
||||
{File: os.Stdin}, {File: os.Stdout}, {File: os.Stderr}}
|
||||
fm := eval.NewTopFrame(ev, eval.NewInternalSource(name), ports)
|
||||
fm.Call(fn, eval.NoArgs, eval.NoOpts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func initAfterReadline(app *cli.App, ev *eval.Evaler, ns eval.Ns) {
|
||||
hook := vals.EmptyList
|
||||
hookVar := vars.FromPtr(&hook)
|
||||
ns["after-readline"] = hookVar
|
||||
app.Config.AfterReadline = func(code string) {
|
||||
i := -1
|
||||
hook := hookVar.Get().(vals.List)
|
||||
for it := hook.Iterator(); it.HasElem(); it.Next() {
|
||||
i++
|
||||
name := fmt.Sprintf("$after-readline[%d]", i)
|
||||
fn, ok := it.Elem().(eval.Callable)
|
||||
if !ok {
|
||||
// TODO(xiaq): This is not testable as it depends on stderr.
|
||||
// Make it testable.
|
||||
diag.Complainf("%s not function", name)
|
||||
continue
|
||||
}
|
||||
// TODO(xiaq): This should use stdPorts, but stdPorts is currently
|
||||
// unexported from eval.
|
||||
ports := []*eval.Port{
|
||||
{File: os.Stdin}, {File: os.Stdout}, {File: os.Stderr}}
|
||||
fm := eval.NewTopFrame(ev, eval.NewInternalSource(name), ports)
|
||||
fm.Call(fn, []interface{}{code}, eval.NoOpts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func initInsert(app *cli.App, ev *eval.Evaler, ns eval.Ns) {
|
||||
abbr := vals.EmptyMap
|
||||
abbrVar := vars.FromPtr(&abbr)
|
||||
app.CodeArea.Abbreviations = makeMapIterator(abbrVar)
|
||||
|
||||
// TODO(xiaq): Synchronize properly.
|
||||
binding := emptyBindingMap
|
||||
bindingVar := vars.FromPtr(&binding)
|
||||
app.CodeArea.OverlayHandler = newMapBinding(app, ev, &binding)
|
||||
|
||||
quotePaste := false
|
||||
quotePasteVar := vars.FromPtr("ePaste)
|
||||
app.CodeArea.QuotePaste = func() bool { return quotePasteVar.Get().(bool) }
|
||||
|
||||
ns.AddNs("insert", eval.Ns{
|
||||
"abbr": abbrVar,
|
||||
"binding": bindingVar,
|
||||
"quote-paste": quotePasteVar,
|
||||
})
|
||||
}
|
||||
|
||||
func makeMapIterator(mv vars.Var) func(func(a, b string)) {
|
||||
return func(f func(a, b string)) {
|
||||
for it := mv.Get().(hashmap.Map).Iterator(); it.HasElem(); it.Next() {
|
||||
k, v := it.Elem()
|
||||
ks, kok := k.(string)
|
||||
vs, vok := v.(string)
|
||||
if !kok || !vok {
|
||||
continue
|
||||
}
|
||||
f(ks, vs)
|
||||
}
|
||||
}
|
||||
}
|
110
cliedit/api_test.go
Normal file
110
cliedit/api_test.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
package cliedit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/elves/elvish/cli"
|
||||
"github.com/elves/elvish/cli/clitypes"
|
||||
"github.com/elves/elvish/cli/term"
|
||||
"github.com/elves/elvish/eval"
|
||||
"github.com/elves/elvish/eval/vals"
|
||||
"github.com/elves/elvish/eval/vars"
|
||||
)
|
||||
|
||||
func setupAPI() (*cli.App, *eval.Evaler, eval.Ns) {
|
||||
app := cli.NewApp(cli.NewStdTTY())
|
||||
ev := eval.NewEvaler()
|
||||
ns := eval.Ns{}
|
||||
initAPI(app, ev, ns)
|
||||
return app, ev, ns
|
||||
}
|
||||
|
||||
func TestInitAPI_BeforeReadline(t *testing.T) {
|
||||
app, _, ns := setupAPI()
|
||||
|
||||
var called int
|
||||
ns["before-readline"].Set(vals.MakeList(eval.NewGoFn("[test]", func() {
|
||||
called++
|
||||
})))
|
||||
app.Config.BeforeReadline()
|
||||
if called != 1 {
|
||||
t.Errorf("before-readline called %d times, want once", called)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitAPI_AfterReadline(t *testing.T) {
|
||||
app, _, ns := setupAPI()
|
||||
|
||||
var called int
|
||||
var calledWith string
|
||||
ns["after-readline"].Set(vals.MakeList(eval.NewGoFn("[test]", func(s string) {
|
||||
called++
|
||||
calledWith = s
|
||||
})))
|
||||
app.Config.AfterReadline("code")
|
||||
if called != 1 {
|
||||
t.Errorf("after-readline called %d times, want once", called)
|
||||
}
|
||||
if calledWith != "code" {
|
||||
t.Errorf("after-readline called with %q, want %q", calledWith, "code")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitAPI_Insert_Abbr(t *testing.T) {
|
||||
app, _, ns := setupAPI()
|
||||
m := vals.MakeMap("xx", "xx full", "yy", "yy full")
|
||||
getNs(ns, "insert")["abbr"].Set(m)
|
||||
|
||||
collected := vals.EmptyMap
|
||||
app.CodeArea.Abbreviations(func(a, f string) {
|
||||
collected = collected.Assoc(a, f)
|
||||
})
|
||||
|
||||
if !vals.Equal(m, collected) {
|
||||
t.Errorf("Callback collected %v, var set %v", collected, m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitAPI_Insert_Binding(t *testing.T) {
|
||||
app, _, ns := setupAPI()
|
||||
testKeyBinding(t, getNs(ns, "insert")["binding"], app.CodeArea.OverlayHandler)
|
||||
}
|
||||
|
||||
func TestInitAPI_Insert_QuotePaste(t *testing.T) {
|
||||
app, _, ns := setupAPI()
|
||||
for _, quote := range []bool{false, true} {
|
||||
getNs(ns, "insert")["quote-paste"].Set(quote)
|
||||
if got := app.CodeArea.QuotePaste(); got != quote {
|
||||
t.Errorf("quote paste = %v, want %v", got, quote)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testKeyBinding(t *testing.T, v vars.Var, h clitypes.Handler) {
|
||||
t.Helper()
|
||||
|
||||
var called int
|
||||
binding, err := emptyBindingMap.Assoc(
|
||||
"a", eval.NewGoFn("[binding]", func() { called++ }))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
v.Set(binding)
|
||||
|
||||
handled := h.Handle(term.K('a'))
|
||||
|
||||
if !handled {
|
||||
t.Errorf("handled = false, want true")
|
||||
}
|
||||
if called != 1 {
|
||||
t.Errorf("handler called %d times, want once", called)
|
||||
}
|
||||
}
|
||||
|
||||
func getNs(ns eval.Ns, name string) eval.Ns {
|
||||
return ns[name+eval.NsSuffix].Get().(eval.Ns)
|
||||
}
|
||||
|
||||
func getFn(ns eval.Ns, name string) eval.Callable {
|
||||
return ns[name+eval.FnSuffix].Get().(eval.Callable)
|
||||
}
|
|
@ -7,7 +7,6 @@ import (
|
|||
"github.com/elves/elvish/cli"
|
||||
"github.com/elves/elvish/cli/histutil"
|
||||
"github.com/elves/elvish/eval"
|
||||
"github.com/elves/elvish/eval/vars"
|
||||
"github.com/elves/elvish/parse"
|
||||
"github.com/elves/elvish/store/storedefs"
|
||||
)
|
||||
|
@ -52,13 +51,9 @@ func NewEditor(in, out *os.File, ev *eval.Evaler, st storedefs.Store) *Editor {
|
|||
ns := eval.NewNs()
|
||||
app := cli.NewApp(cli.NewTTY(in, out))
|
||||
|
||||
initAPI(app, ev, ns)
|
||||
app.Config.Highlighter = makeHighlighter(ev)
|
||||
|
||||
maxHeight := -1
|
||||
maxHeightVar := vars.FromPtr(&maxHeight)
|
||||
app.Config.MaxHeight = func() int { return maxHeightVar.Get().(int) }
|
||||
ns.Add("max-height", maxHeightVar)
|
||||
|
||||
// TODO: BindingMap should pass event context to event handlers
|
||||
ns.AddGoFns("<edit>", map[string]interface{}{
|
||||
"binding-map": makeBindingMap,
|
||||
|
@ -67,22 +62,10 @@ func NewEditor(in, out *os.File, ev *eval.Evaler, st storedefs.Store) *Editor {
|
|||
// "reset-mode": cli.ResetMode,
|
||||
}).AddGoFns("<edit>", bufferBuiltins(app))
|
||||
|
||||
// Elvish hook APIs
|
||||
var beforeReadline func()
|
||||
ns["before-readline"], beforeReadline = initBeforeReadline(ev)
|
||||
var afterReadline func(string)
|
||||
ns["after-readline"], afterReadline = initAfterReadline(ev)
|
||||
app.Config.BeforeReadline = beforeReadline
|
||||
app.Config.AfterReadline = afterReadline
|
||||
|
||||
// Prompts
|
||||
app.Config.Prompt = makePrompt(app, ev, ns, defaultPrompt, "prompt")
|
||||
app.Config.RPrompt = makePrompt(app, ev, ns, defaultRPrompt, "rprompt")
|
||||
|
||||
// Insert mode
|
||||
insertNs := initInsert(ev, app)
|
||||
ns.AddNs("insert", insertNs)
|
||||
|
||||
// Listing modes.
|
||||
lsBinding, lsNs := initListing()
|
||||
ns.AddNs("listing", lsNs)
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
package cliedit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/elves/elvish/diag"
|
||||
"github.com/elves/elvish/eval"
|
||||
"github.com/elves/elvish/eval/vals"
|
||||
"github.com/elves/elvish/eval/vars"
|
||||
)
|
||||
|
||||
func initBeforeReadline(ev *eval.Evaler) (vars.Var, func()) {
|
||||
hook := vals.EmptyList
|
||||
return vars.FromPtr(&hook), func() {
|
||||
i := -1
|
||||
for it := hook.Iterator(); it.HasElem(); it.Next() {
|
||||
i++
|
||||
name := fmt.Sprintf("$before-readline[%d]", i)
|
||||
fn, ok := it.Elem().(eval.Callable)
|
||||
if !ok {
|
||||
// TODO(xiaq): This is not testable as it depends on stderr.
|
||||
// Make it testable.
|
||||
diag.Complainf("%s not function", name)
|
||||
continue
|
||||
}
|
||||
// TODO(xiaq): This should use stdPorts, but stdPorts is currently
|
||||
// unexported from eval.
|
||||
ports := []*eval.Port{
|
||||
{File: os.Stdin}, {File: os.Stdout}, {File: os.Stderr}}
|
||||
fm := eval.NewTopFrame(ev, eval.NewInternalSource(name), ports)
|
||||
fm.Call(fn, eval.NoArgs, eval.NoOpts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func initAfterReadline(ev *eval.Evaler) (vars.Var, func(string)) {
|
||||
hook := vals.EmptyList
|
||||
return vars.FromPtr(&hook), func(code string) {
|
||||
i := -1
|
||||
for it := hook.Iterator(); it.HasElem(); it.Next() {
|
||||
i++
|
||||
name := fmt.Sprintf("$after-readline[%d]", i)
|
||||
fn, ok := it.Elem().(eval.Callable)
|
||||
if !ok {
|
||||
// TODO(xiaq): This is not testable as it depends on stderr.
|
||||
// Make it testable.
|
||||
diag.Complainf("%s not function", name)
|
||||
continue
|
||||
}
|
||||
// TODO(xiaq): This should use stdPorts, but stdPorts is currently
|
||||
// unexported from eval.
|
||||
ports := []*eval.Port{
|
||||
{File: os.Stdin}, {File: os.Stdout}, {File: os.Stderr}}
|
||||
fm := eval.NewTopFrame(ev, eval.NewInternalSource(name), ports)
|
||||
fm.Call(fn, []interface{}{code}, eval.NoOpts)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package cliedit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/elves/elvish/eval"
|
||||
"github.com/elves/elvish/eval/vals"
|
||||
)
|
||||
|
||||
func TestInitBeforeReadline(t *testing.T) {
|
||||
variable, cb := initBeforeReadline(eval.NewEvaler())
|
||||
called := 0
|
||||
variable.Set(vals.EmptyList.Cons(eval.NewGoFn("[test]", func() {
|
||||
called++
|
||||
})))
|
||||
cb()
|
||||
if called != 1 {
|
||||
t.Errorf("Called %d times, want once", called)
|
||||
}
|
||||
// TODO: Test input and output
|
||||
}
|
||||
|
||||
func TestInitAfterReadline(t *testing.T) {
|
||||
variable, cb := initAfterReadline(eval.NewEvaler())
|
||||
called := 0
|
||||
calledWith := ""
|
||||
variable.Set(vals.EmptyList.Cons(eval.NewGoFn("[test]", func(s string) {
|
||||
called++
|
||||
calledWith = s
|
||||
})))
|
||||
cb("code")
|
||||
if called != 1 {
|
||||
t.Errorf("Called %d times, want once", called)
|
||||
}
|
||||
if calledWith != "code" {
|
||||
t.Errorf("Called with %q, want %q", calledWith, "code")
|
||||
}
|
||||
// TODO: Test input and output
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package cliedit
|
||||
|
||||
import (
|
||||
"github.com/elves/elvish/cli"
|
||||
"github.com/elves/elvish/eval"
|
||||
"github.com/elves/elvish/eval/vals"
|
||||
"github.com/elves/elvish/eval/vars"
|
||||
"github.com/xiaq/persistent/hashmap"
|
||||
)
|
||||
|
||||
func initInsert(ev *eval.Evaler, app *cli.App) eval.Ns {
|
||||
abbr := vals.EmptyMap
|
||||
app.CodeArea.Abbreviations = makeMapIterator(&abbr)
|
||||
|
||||
binding := emptyBindingMap
|
||||
app.CodeArea.OverlayHandler = newMapBinding(app, ev, &binding)
|
||||
|
||||
quotePaste := false
|
||||
quotePasteVar := vars.FromPtr("ePaste)
|
||||
app.CodeArea.QuotePaste = func() bool { return quotePasteVar.Get().(bool) }
|
||||
|
||||
return eval.Ns{
|
||||
"abbr": vars.FromPtr(&abbr),
|
||||
"binding": vars.FromPtr(&binding),
|
||||
"quote-paste": quotePasteVar,
|
||||
}
|
||||
}
|
||||
|
||||
func makeMapIterator(m *hashmap.Map) func(func(a, b string)) {
|
||||
return func(f func(a, b string)) {
|
||||
for it := (*m).Iterator(); it.HasElem(); it.Next() {
|
||||
k, v := it.Elem()
|
||||
ks, kok := k.(string)
|
||||
vs, vok := v.(string)
|
||||
if !kok || !vok {
|
||||
continue
|
||||
}
|
||||
f(ks, vs)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
package cliedit
|
||||
|
||||
import (
|
||||
"github.com/elves/elvish/eval"
|
||||
)
|
||||
|
||||
var abbrData = [][2]string{{"xx", "xx full"}, {"yy", "yy full"}}
|
||||
|
||||
/*
|
||||
func TestInitInsert_Abbr(t *testing.T) {
|
||||
m, ns := initInsert(&fakeApp{}, eval.NewEvaler())
|
||||
|
||||
abbrValue := vals.EmptyMap
|
||||
for _, pair := range abbrData {
|
||||
abbrValue = abbrValue.Assoc(pair[0], pair[1])
|
||||
}
|
||||
ns["abbr"].Set(abbrValue)
|
||||
|
||||
var cbData [][2]string
|
||||
m.AbbrIterate(func(a, f string) {
|
||||
cbData = append(cbData, [2]string{a, f})
|
||||
})
|
||||
|
||||
if !reflect.DeepEqual(cbData, abbrData) {
|
||||
t.Errorf("Callback called with %v, want %v", cbData, abbrData)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitInsert_Binding(t *testing.T) {
|
||||
m, ns := initInsert(&fakeApp{}, eval.NewEvaler())
|
||||
called := 0
|
||||
binding, err := emptyBindingMap.Assoc("a",
|
||||
eval.NewGoFn("test binding", func() { called++ }))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ns["binding"].Set(binding)
|
||||
|
||||
m.HandleEvent(tty.KeyEvent{Rune: 'a'}, &clitypes.State{})
|
||||
|
||||
if called != 1 {
|
||||
t.Errorf("Handler called %d times, want once", called)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitInsert_QuotePaste(t *testing.T) {
|
||||
m, ns := initInsert(&fakeApp{}, eval.NewEvaler())
|
||||
|
||||
ns["quote-paste"].Set(true)
|
||||
|
||||
if !m.Config.QuotePaste() {
|
||||
t.Errorf("QuotePaste not set via namespae")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestInitInsert_Start(t *testing.T) {
|
||||
ed := &fakeApp{}
|
||||
ev := eval.NewEvaler()
|
||||
m, ns := initInsert(ed, ev)
|
||||
|
||||
fm := eval.NewTopFrame(ev, eval.NewInternalSource("[test]"), nil)
|
||||
fm.Call(getFn(ns, "start"), nil, eval.NoOpts)
|
||||
|
||||
if ed.state.Mode() != m {
|
||||
t.Errorf("state is not insert mode after calling start")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitInsert_DefaultHandler(t *testing.T) {
|
||||
ed := &fakeApp{}
|
||||
ev := eval.NewEvaler()
|
||||
_, ns := initInsert(ed, ev)
|
||||
|
||||
// Pretend that we are executing a binding for "a".
|
||||
ed.state.SetBindingKey(ui.Key{Rune: 'a'})
|
||||
|
||||
// Call <edit:insert>:default-binding.
|
||||
fm := eval.NewTopFrame(ev, eval.NewInternalSource("[test]"), nil)
|
||||
fm.Call(getFn(ns, "default-handler"), nil, eval.NoOpts)
|
||||
|
||||
// Verify that the default handler has executed, inserting "a".
|
||||
if ed.state.Raw.Code != "a" {
|
||||
t.Errorf("state.Raw.Code = %q, want %q", ed.state.Raw.Code, "a")
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
func getFn(ns eval.Ns, name string) eval.Callable {
|
||||
return ns[name+eval.FnSuffix].Get().(eval.Callable)
|
||||
}
|
Loading…
Reference in New Issue
Block a user