Skip to content

Commit

Permalink
Implement namespaces using slices instead of maps.
Browse files Browse the repository at this point in the history
More related improvements and cleanups will be done in followup commits.

This fixes #1139.
  • Loading branch information
xiaq committed Dec 25, 2020
1 parent 09829ae commit 6419f45
Show file tree
Hide file tree
Showing 56 changed files with 799 additions and 586 deletions.
12 changes: 6 additions & 6 deletions pkg/edit/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,15 +177,15 @@ func wordify(fm *eval.Frame, code string) {
}
}

func initTTYBuiltins(app cli.App, tty cli.TTY, ns eval.Ns) {
ns.AddGoFns("<edit>", map[string]interface{}{
func initTTYBuiltins(app cli.App, tty cli.TTY, nb eval.NsBuilder) {
nb.AddGoFns("<edit>", map[string]interface{}{
"-dump-buf": func() string { return dumpBuf(tty) },
"insert-raw": func() { insertRaw(app, tty) },
})
}

func initMiscBuiltins(app cli.App, ns eval.Ns) {
ns.AddGoFns("<edit>", map[string]interface{}{
func initMiscBuiltins(app cli.App, nb eval.NsBuilder) {
nb.AddGoFns("<edit>", map[string]interface{}{
"binding-table": MakeBindingMap,
"close-listing": func() { closeListing(app) },
"end-of-history": func() { endOfHistory(app) },
Expand Down Expand Up @@ -225,8 +225,8 @@ var bufferBuiltinsData = map[string]func(*cli.CodeBuffer){
"kill-line-right": makeKill(moveDotEOL),
}

func initBufferBuiltins(app cli.App, ns eval.Ns) {
ns.AddGoFns("<edit>", bufferBuiltins(app))
func initBufferBuiltins(app cli.App, nb eval.NsBuilder) {
nb.AddGoFns("<edit>", bufferBuiltins(app))
}

func bufferBuiltins(app cli.App) map[string]interface{} {
Expand Down
4 changes: 2 additions & 2 deletions pkg/edit/builtins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func TestKey(t *testing.T) {

evals(f.Evaler, `k = (edit:key a)`)
wantK := ui.K('a')
if k := f.Evaler.Global["k"].Get(); k != wantK {
if k, _ := f.Evaler.Global.Index("k"); k != wantK {
t.Errorf("$k is %v, want %v", k, wantK)
}
}
Expand Down Expand Up @@ -175,7 +175,7 @@ func TestWordify(t *testing.T) {

evals(f.Evaler, `@words = (edit:wordify 'ls str [list]')`)
wantWords := vals.MakeList("ls", "str", "[list]")
if words := f.Evaler.Global["words"].Get(); !vals.Equal(words, wantWords) {
if words, _ := f.Evaler.Global.Index("words"); !vals.Equal(words, wantWords) {
t.Errorf("$words is %v, want %v", words, wantWords)
}
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/edit/command_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import (
//
// Starts the command mode.

func initCommandAPI(ed *Editor, ev *eval.Evaler) {
func initCommandAPI(ed *Editor, ev *eval.Evaler, nb eval.NsBuilder) {
bindingVar := newBindingVar(EmptyBindingMap)
binding := newMapBinding(ed, ev, bindingVar)
ed.ns.AddNs("command",
eval.Ns{
nb.AddNs("command",
eval.NsBuilder{
"binding": bindingVar,
}.AddGoFns("<edit:command>:", map[string]interface{}{
"start": func() {
Expand All @@ -23,5 +23,5 @@ func initCommandAPI(ed *Editor, ev *eval.Evaler) {
Focus: false,
})
},
}))
}).Ns())
}
10 changes: 5 additions & 5 deletions pkg/edit/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ func completionStart(app cli.App, binding cli.Handler, cfg complete.Config, smar
//
// Closes the completion mode UI.

func initCompletion(ed *Editor, ev *eval.Evaler) {
func initCompletion(ed *Editor, ev *eval.Evaler, nb eval.NsBuilder) {
bindingVar := newBindingVar(EmptyBindingMap)
binding := newMapBinding(ed, ev, bindingVar)
matcherMapVar := newMapVar(vals.EmptyMap)
Expand All @@ -220,7 +220,7 @@ func initCompletion(ed *Editor, ev *eval.Evaler) {
generateForSudo := func(args []string) ([]complete.RawItem, error) {
return complete.GenerateForSudo(cfg(), args)
}
ed.ns.AddGoFns("<edit>", map[string]interface{}{
nb.AddGoFns("<edit>", map[string]interface{}{
"complete-filename": wrapArgGenerator(complete.GenerateFileNames),
"complete-getopt": completeGetopt,
"complete-sudo": wrapArgGenerator(generateForSudo),
Expand All @@ -230,8 +230,8 @@ func initCompletion(ed *Editor, ev *eval.Evaler) {
"match-substr": wrapMatcher(strings.Contains),
})
app := ed.app
ed.ns.AddNs("completion",
eval.Ns{
nb.AddNs("completion",
eval.NsBuilder{
"arg-completer": argGeneratorMapVar,
"binding": bindingVar,
"matcher": matcherMapVar,
Expand All @@ -246,7 +246,7 @@ func initCompletion(ed *Editor, ev *eval.Evaler) {
"down-cycle": func() { listingDownCycle(app) },
"left": func() { listingLeft(app) },
"right": func() { listingRight(app) },
}))
}).Ns())
}

// A wrapper type implementing Elvish value methods.
Expand Down
2 changes: 1 addition & 1 deletion pkg/edit/completion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func TestComplexCandidate(t *testing.T) {
defer restore()

TestWithSetup(t, func(ev *eval.Evaler) {
ev.Global.AddGoFn("", "cc", complexCandidate)
ev.Global = eval.NsBuilder{}.AddGoFn("", "cc", complexCandidate).Ns()
},
That("kind-of (cc stem)").Puts("map"),
That("keys (cc stem)").Puts("stem", "code-suffix", "display"),
Expand Down
22 changes: 11 additions & 11 deletions pkg/edit/config_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,25 @@ import (
// it can often occupy the entire terminal, and push up your scrollback buffer.
// Change this variable to a finite number to restrict the height of the editor.

func initMaxHeight(appSpec *cli.AppSpec, ns eval.Ns) {
func initMaxHeight(appSpec *cli.AppSpec, nb eval.NsBuilder) {
maxHeight := newIntVar(-1)
appSpec.MaxHeight = func() int { return maxHeight.GetRaw().(int) }
ns.Add("max-height", maxHeight)
nb.Add("max-height", maxHeight)
}

func initReadlineHooks(appSpec *cli.AppSpec, ev *eval.Evaler, ns eval.Ns) {
initBeforeReadline(appSpec, ev, ns)
initAfterReadline(appSpec, ev, ns)
func initReadlineHooks(appSpec *cli.AppSpec, ev *eval.Evaler, nb eval.NsBuilder) {
initBeforeReadline(appSpec, ev, nb)
initAfterReadline(appSpec, ev, nb)
}

//elvdoc:var before-readline
//
// A list of functions to call before each readline cycle. Each function is
// called without any arguments.

func initBeforeReadline(appSpec *cli.AppSpec, ev *eval.Evaler, ns eval.Ns) {
func initBeforeReadline(appSpec *cli.AppSpec, ev *eval.Evaler, nb eval.NsBuilder) {
hook := newListVar(vals.EmptyList)
ns["before-readline"] = hook
nb["before-readline"] = hook
appSpec.BeforeReadline = append(appSpec.BeforeReadline, func() {
callHooks(ev, "$<edit>:before-readline", hook.Get().(vals.List))
})
Expand All @@ -53,9 +53,9 @@ func initBeforeReadline(appSpec *cli.AppSpec, ev *eval.Evaler, ns eval.Ns) {
// A list of functions to call after each readline cycle. Each function is
// called with a single string argument containing the code that has been read.

func initAfterReadline(appSpec *cli.AppSpec, ev *eval.Evaler, ns eval.Ns) {
func initAfterReadline(appSpec *cli.AppSpec, ev *eval.Evaler, nb eval.NsBuilder) {
hook := newListVar(vals.EmptyList)
ns["after-readline"] = hook
nb["after-readline"] = hook
appSpec.AfterReadline = append(appSpec.AfterReadline, func(code string) {
callHooks(ev, "$<edit>:after-readline", hook.Get().(vals.List), code)
})
Expand All @@ -71,11 +71,11 @@ func initAfterReadline(appSpec *cli.AppSpec, ev *eval.Evaler, ns eval.Ns) {
// not run. The default value of this list contains a filter which
// ignores command starts with space.

func initAddCmdFilters(appSpec *cli.AppSpec, ev *eval.Evaler, ns eval.Ns, s histutil.Store) {
func initAddCmdFilters(appSpec *cli.AppSpec, ev *eval.Evaler, nb eval.NsBuilder, s histutil.Store) {
ignoreLeadingSpace := eval.NewGoFn("<ignore-cmd-with-leading-space>",
func(s string) bool { return !strings.HasPrefix(s, " ") })
filters := newListVar(vals.MakeList(ignoreLeadingSpace))
ns["add-cmd-filters"] = filters
nb["add-cmd-filters"] = filters

appSpec.AfterReadline = append(appSpec.AfterReadline, func(code string) {
if code != "" &&
Expand Down
53 changes: 28 additions & 25 deletions pkg/edit/editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
// Editor is the interface line editor for Elvish.
type Editor struct {
app cli.App
ns eval.Ns
ns *eval.Ns

excMutex sync.RWMutex
excList vals.List
Expand All @@ -39,7 +39,8 @@ type notifier interface {
func NewEditor(tty cli.TTY, ev *eval.Evaler, st store.Store) *Editor {
// Declare the Editor with a nil App first; some initialization functions
// require a notifier as an argument, but does not use it immediately.
ed := &Editor{ns: eval.Ns{}, excList: vals.EmptyList}
ed := &Editor{excList: vals.EmptyList}
nb := eval.NsBuilder{}
appSpec := cli.AppSpec{TTY: tty}

hs, err := newHistStore(st)
Expand All @@ -48,27 +49,29 @@ func NewEditor(tty cli.TTY, ev *eval.Evaler, st store.Store) *Editor {
}

initHighlighter(&appSpec, ev)
initMaxHeight(&appSpec, ed.ns)
initReadlineHooks(&appSpec, ev, ed.ns)
initAddCmdFilters(&appSpec, ev, ed.ns, hs)
initInsertAPI(&appSpec, ed, ev, ed.ns)
initPrompts(&appSpec, ed, ev, ed.ns)
initMaxHeight(&appSpec, nb)
initReadlineHooks(&appSpec, ev, nb)
initAddCmdFilters(&appSpec, ev, nb, hs)
initInsertAPI(&appSpec, ed, ev, nb)
initPrompts(&appSpec, ed, ev, nb)
ed.app = cli.NewApp(appSpec)

initExceptionsAPI(ed)
initCommandAPI(ed, ev)
initListings(ed, ev, st, hs)
initNavigation(ed, ev)
initCompletion(ed, ev)
initHistWalk(ed, ev, hs)
initInstant(ed, ev)
initMinibuf(ed, ev)

initBufferBuiltins(ed.app, ed.ns)
initTTYBuiltins(ed.app, tty, ed.ns)
initMiscBuiltins(ed.app, ed.ns)
initStateAPI(ed.app, ed.ns)
initStoreAPI(ed.app, ed.ns, hs)
initExceptionsAPI(ed, nb)
initCommandAPI(ed, ev, nb)
initListings(ed, ev, st, hs, nb)
initNavigation(ed, ev, nb)
initCompletion(ed, ev, nb)
initHistWalk(ed, ev, hs, nb)
initInstant(ed, ev, nb)
initMinibuf(ed, ev, nb)

initBufferBuiltins(ed.app, nb)
initTTYBuiltins(ed.app, tty, nb)
initMiscBuiltins(ed.app, nb)
initStateAPI(ed.app, nb)
initStoreAPI(ed.app, nb, hs)

ed.ns = nb.Ns()
evalDefaultBinding(ev, ed.ns)

return ed
Expand All @@ -79,11 +82,11 @@ func NewEditor(tty cli.TTY, ev *eval.Evaler, st store.Store) *Editor {
// A list of exceptions thrown from callbacks such as prompts. Useful for
// examining tracebacks and other metadata.

func initExceptionsAPI(ed *Editor) {
ed.ns.Add("exceptions", vars.FromPtrWithMutex(&ed.excList, &ed.excMutex))
func initExceptionsAPI(ed *Editor, nb eval.NsBuilder) {
nb.Add("exceptions", vars.FromPtrWithMutex(&ed.excList, &ed.excMutex))
}

func evalDefaultBinding(ev *eval.Evaler, ns eval.Ns) {
func evalDefaultBinding(ev *eval.Evaler, ns *eval.Ns) {
// TODO(xiaq): The evaler API should accodomate the use case of evaluating a
// piece of code in an alternative global namespace.

Expand Down Expand Up @@ -113,7 +116,7 @@ func (ed *Editor) ReadCode() (string, error) {
}

// Ns returns a namespace for manipulating the editor from Elvish code.
func (ed *Editor) Ns() eval.Ns {
func (ed *Editor) Ns() *eval.Ns {
return ed.ns
}

Expand Down
22 changes: 11 additions & 11 deletions pkg/edit/highlight.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,37 +61,37 @@ func hasQualifiedFn(ev *eval.Evaler, firstNs string, rest string) bool {
if rest == "" {
return false
}
modVar := ev.Global[firstNs]
if modVar == nil {
modVar = ev.Builtin[firstNs]
if modVar == nil {
modVal, ok := ev.Global.Index(firstNs)
if !ok {
modVal, ok = ev.Builtin.Index(firstNs)
if !ok {
return false
}
}
mod, ok := modVar.Get().(eval.Ns)
mod, ok := modVal.(*eval.Ns)
if !ok {
return false
}
segs := eval.SplitQNameNsSegs(rest)
for _, seg := range segs[:len(segs)-1] {
modVar = mod[seg]
if modVar == nil {
modVal, ok = mod.Index(seg)
if !ok {
return false
}
mod, ok = modVar.Get().(eval.Ns)
mod, ok = modVal.(*eval.Ns)
if !ok {
return false
}
}
return hasFn(mod, segs[len(segs)-1])
}

func hasFn(ns eval.Ns, name string) bool {
fnVar, ok := ns[name+eval.FnSuffix]
func hasFn(ns *eval.Ns, name string) bool {
fnVar, ok := ns.Index(name + eval.FnSuffix)
if !ok {
return false
}
_, ok = fnVar.Get().(eval.Callable)
_, ok = fnVar.(eval.Callable)
return ok
}

Expand Down
15 changes: 9 additions & 6 deletions pkg/edit/highlight_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestHighlighter(t *testing.T) {

func TestCheck(t *testing.T) {
ev := eval.NewEvaler()
ev.Global.Add("good", vars.FromInit(0))
ev.Global = eval.NsBuilder{"good": vars.FromInit(0)}.Ns()

tt.Test(t, tt.Fn("check", check), tt.Table{
tt.Args(ev, mustParse("")).Rets(noError),
Expand Down Expand Up @@ -69,11 +69,14 @@ func TestMakeHasCommand(t *testing.T) {

// Set up global functions and modules in the evaler.
goodFn := eval.NewGoFn("good", func() {})
ev.Global.AddFn("good", goodFn)
aNs := eval.Ns{}.AddFn("good", goodFn)
bNs := eval.Ns{}.AddFn("good", goodFn)
aNs.AddNs("b", bNs)
ev.Global.AddNs("a", aNs)
ev.Global = eval.NsBuilder{}.
AddFn("good", goodFn).
AddNs("a",
eval.NsBuilder{}.
AddFn("good", goodFn).
AddNs("b", eval.NsBuilder{}.AddFn("good", goodFn).Ns()).
Ns()).
Ns()

// Set up environment.
testDir, cleanup := testutil.InTestDir()
Expand Down
8 changes: 4 additions & 4 deletions pkg/edit/histwalk.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import (
// Import command history entries that happened after the current session
// started.

func initHistWalk(ed *Editor, ev *eval.Evaler, hs *histStore) {
func initHistWalk(ed *Editor, ev *eval.Evaler, hs *histStore, nb eval.NsBuilder) {
bindingVar := newBindingVar(EmptyBindingMap)
binding := newMapBinding(ed, ev, bindingVar)
app := ed.app
ed.ns.AddNs("history",
eval.Ns{
nb.AddNs("history",
eval.NsBuilder{
"binding": bindingVar,
}.AddGoFns("<edit:history>", map[string]interface{}{
"start": func() { histWalkStart(app, hs, binding) },
Expand All @@ -35,7 +35,7 @@ func initHistWalk(ed *Editor, ev *eval.Evaler, hs *histStore) {
"close": func() { histwalk.Close(app) },

"fast-forward": hs.FastForward,
}))
}).Ns())
}

func histWalkStart(app cli.App, hs *histStore, binding cli.Handler) {
Expand Down
12 changes: 6 additions & 6 deletions pkg/edit/insert_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ import (
//
// @cf edit:abbr

func initInsertAPI(appSpec *cli.AppSpec, nt notifier, ev *eval.Evaler, ns eval.Ns) {
func initInsertAPI(appSpec *cli.AppSpec, nt notifier, ev *eval.Evaler, nb eval.NsBuilder) {
abbr := vals.EmptyMap
abbrVar := vars.FromPtr(&abbr)
appSpec.Abbreviations = makeMapIterator(abbrVar)
Expand All @@ -121,13 +121,13 @@ func initInsertAPI(appSpec *cli.AppSpec, nt notifier, ev *eval.Evaler, ns eval.N
quotePaste.Set(!quotePaste.Get().(bool))
}

ns.Add("abbr", abbrVar)
ns.Add("small-word-abbr", SmallWordAbbrVar)
ns.AddGoFn("<edit>", "toggle-quote-paste", toggleQuotePaste)
ns.AddNs("insert", eval.Ns{
nb.Add("abbr", abbrVar)
nb.Add("small-word-abbr", SmallWordAbbrVar)
nb.AddGoFn("<edit>", "toggle-quote-paste", toggleQuotePaste)
nb.AddNs("insert", eval.NsBuilder{
"binding": binding,
"quote-paste": quotePaste,
})
}.Ns())
}

func makeMapIterator(mv vars.PtrVar) func(func(a, b string)) {
Expand Down

0 comments on commit 6419f45

Please sign in to comment.