Skip to content

Commit

Permalink
Reimplement edit.BindingTable with persistent hashmap.
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaq committed Aug 31, 2017
1 parent e8ad6b9 commit c499071
Show file tree
Hide file tree
Showing 14 changed files with 133 additions and 145 deletions.
11 changes: 3 additions & 8 deletions edit/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,17 +138,12 @@ func installModules(modules map[string]eval.Namespace, ed *Editor) {
}
}

// Add $edit:xxx:binding variables.
// TODO Make binding specific to the Editor.
for _, mode := range []string{
modeInsert, modeCommand, modeCompletion, modeNavigation, modeHistory,
modeHistoryListing, modeLocation, modeLastCmd, modeListing, modeNarrow} {

// Add $edit:{mode}:binding variables.
for mode, bindingVar := range ed.bindings {
if modules["edit:"+mode] == nil {
modules["edit:"+mode] = make(eval.Namespace)
}
modules["edit:"+mode]["binding"] =
eval.NewRoVariable(BindingTable{keyBindings[mode]})
modules["edit:"+mode]["binding"] = bindingVar
}
}

Expand Down
87 changes: 0 additions & 87 deletions edit/binding.go

This file was deleted.

57 changes: 57 additions & 0 deletions edit/binding_table.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package edit

import (
"github.com/elves/elvish/edit/ui"
"github.com/elves/elvish/eval"
"github.com/elves/elvish/parse"
)

func getBinding(bindingVar eval.Variable, k ui.Key) eval.CallableValue {
binding := bindingVar.Get().(BindingTable)
switch {
case binding.HasKey(k):
return binding.IndexOne(k).(eval.CallableValue)
case binding.HasKey(ui.Default):
return binding.IndexOne(ui.Default).(eval.CallableValue)
default:
return nil
}
}

// BindingTable is a special Map that converts its key to ui.Key and ensures
// that its values satisfy eval.CallableValue.
type BindingTable struct {
eval.Map
}

// Repr returns the representation of the binding table as if it were an
// ordinary map keyed by strings.
func (bt BindingTable) Repr(indent int) string {
var builder eval.MapReprBuilder
builder.Indent = indent
bt.Map.IteratePair(func(k, v eval.Value) bool {
builder.WritePair(parse.Quote(k.(ui.Key).String()), indent+2, v.Repr(indent+2))
return true
})
return builder.String()
}

// IndexOne converts the index to ui.Key and uses the IndexOne of the inner Map.
func (bt BindingTable) IndexOne(idx eval.Value) eval.Value {
return bt.Map.IndexOne(ui.ToKey(idx))
}

func (bt BindingTable) get(k ui.Key) eval.CallableValue {
return bt.Map.IndexOne(k).(eval.CallableValue)
}

// Assoc converts the index to ui.Key, ensures that the value is CallableValue,
// uses the Assoc of the inner Map and converts the result to a BindingTable.
func (bt BindingTable) Assoc(k, v eval.Value) eval.Value {
key := ui.ToKey(k)
f, ok := v.(eval.CallableValue)
if !ok {
throwf("want function, got %s", v.Kind())
}
return BindingTable{bt.Map.Assoc(key, f).(eval.Map)}
}
4 changes: 2 additions & 2 deletions edit/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ type completion struct {
height int
}

func (*completion) Binding(k ui.Key) eval.CallableValue {
return getBinding(modeCompletion, k)
func (*completion) Binding(m map[string]eval.Variable, k ui.Key) eval.CallableValue {
return getBinding(m[modeCompletion], k)
}

func (c *completion) needScrollbar() bool {
Expand Down
4 changes: 3 additions & 1 deletion edit/editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type Editor struct {
evaler *eval.Evaler

variables map[string]eval.Variable
bindings map[string]eval.Variable

active bool
activeMutex sync.Mutex
Expand Down Expand Up @@ -92,6 +93,7 @@ func NewEditor(in *os.File, out *os.File, sigs chan os.Signal, ev *eval.Evaler,
daemon: daemon,
evaler: ev,

bindings: makeBindings(),
variables: makeVariables(),
}
if daemon != nil {
Expand Down Expand Up @@ -437,7 +439,7 @@ MainLoop:
case tty.Key:
k := ui.Key(unit)
lookupKey:
fn := ed.mode.Binding(k)
fn := ed.mode.Binding(ed.bindings, k)
if fn == nil {
ed.addTip("Unbound and no default binding: %s", k)
continue MainLoop
Expand Down
4 changes: 2 additions & 2 deletions edit/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ type hist struct {
*history.Walker
}

func (*hist) Binding(k ui.Key) eval.CallableValue {
return getBinding(modeHistory, k)
func (*hist) Binding(m map[string]eval.Variable, k ui.Key) eval.CallableValue {
return getBinding(m[modeHistory], k)
}

func (h *hist) ModeLine() renderer {
Expand Down
8 changes: 4 additions & 4 deletions edit/insert.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ func (ins *insert) ModeLine() renderer {
return nil
}

func (*insert) Binding(k ui.Key) eval.CallableValue {
return getBinding(modeInsert, k)
func (*insert) Binding(m map[string]eval.Variable, k ui.Key) eval.CallableValue {
return getBinding(m[modeInsert], k)
}

type command struct{}
Expand All @@ -142,8 +142,8 @@ func (*command) ModeLine() renderer {
return modeLineRenderer{" COMMAND ", ""}
}

func (*command) Binding(k ui.Key) eval.CallableValue {
return getBinding(modeCommand, k)
func (*command) Binding(m map[string]eval.Variable, k ui.Key) eval.CallableValue {
return getBinding(m[modeCommand], k)
}

func insertStart(ed *Editor) {
Expand Down
30 changes: 16 additions & 14 deletions edit/listing.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,24 +80,26 @@ func newListing(t string, p listingProvider) listing {
return l
}

func (l *listing) Binding(k ui.Key) eval.CallableValue {
specificBindings := keyBindings[l.name]
if specificBindings == nil {
return getBinding(modeListing, k)
func (l *listing) Binding(m map[string]eval.Variable, k ui.Key) eval.CallableValue {
if m[l.name] == nil {
return getBinding(m[modeListing], k)
}
listingBindings := keyBindings[modeListing]
specificBindings := m[l.name].Get().(BindingTable)
listingBindings := m[modeListing].Get().(BindingTable)
// mode-specific binding -> listing binding ->
// mode-specific default -> listing default
if v, ok := specificBindings[k]; ok {
return v
switch {
case specificBindings.HasKey(k):
return specificBindings.get(k)
case listingBindings.HasKey(k):
return listingBindings.get(k)
case specificBindings.HasKey(ui.Default):
return specificBindings.get(ui.Default)
case listingBindings.HasKey(ui.Default):
return listingBindings.get(ui.Default)
default:
return nil
}
if v, ok := listingBindings[k]; ok {
return v
}
if v, ok := specificBindings[ui.Default]; ok {
return v
}
return listingBindings[ui.Default]
}

func (l *listing) ModeLine() renderer {
Expand Down
2 changes: 1 addition & 1 deletion edit/mode.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const (
// Mode is an editor mode.
type Mode interface {
ModeLine() renderer
Binding(ui.Key) eval.CallableValue
Binding(map[string]eval.Variable, ui.Key) eval.CallableValue
}

// CursorOnModeLiner is an optional interface that modes can implement. If a
Expand Down
9 changes: 2 additions & 7 deletions edit/narrow.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,14 @@ type narrow struct {
opts narrowOptions
}

func (l *narrow) Binding(k ui.Key) eval.CallableValue {
func (l *narrow) Binding(m map[string]eval.Variable, k ui.Key) eval.CallableValue {
if l.opts.bindingMap != nil {
if f, ok := l.opts.bindingMap[k]; ok {
return f
}
}

bindings := keyBindings[modeNarrow]
if f, ok := bindings[k]; ok {
return f
}

return bindings[ui.Default]
return getBinding(m[modeNarrow], k)
}

func (l *narrow) ModeLine() renderer {
Expand Down
6 changes: 3 additions & 3 deletions edit/navigation.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ type navigation struct {
chdir func(string) error
}

func (*navigation) Binding(k ui.Key) eval.CallableValue {
return getBinding(modeNavigation, k)
func (*navigation) Binding(m map[string]eval.Variable, k ui.Key) eval.CallableValue {
return getBinding(m[modeNavigation], k)
}

func (n *navigation) ModeLine() renderer {
Expand Down Expand Up @@ -141,7 +141,7 @@ func navDefault(ed *Editor) {
n.refreshDirPreview()
}
} else {
ed.CallFn(getBinding(modeInsert, k))
ed.CallFn(getBinding(ed.bindings[modeInsert], k))
}
}

Expand Down
2 changes: 1 addition & 1 deletion edit/raw_insert.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func insertRaw(ed *Editor, r rune) {
ed.mode = &ed.insert
}

func (rawInsert) Binding(k ui.Key) eval.CallableValue {
func (rawInsert) Binding(map[string]eval.Variable, ui.Key) eval.CallableValue {
// The raw insert mode does not handle keys.
return nil
}
Expand Down
24 changes: 24 additions & 0 deletions edit/registry.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package edit

import (
"errors"
"fmt"
"os"
"strings"

"github.com/elves/elvish/edit/ui"
"github.com/elves/elvish/eval"
"github.com/xiaq/persistent/hashmap"
)

// This file contains several "registries", data structure that are written
Expand Down Expand Up @@ -97,3 +99,25 @@ func registerBindings(
}
return struct{}{}
}

func makeBindings() map[string]eval.Variable {
bindings := make(map[string]eval.Variable)
for mode, binding := range keyBindings {
bindingValue := hashmap.Empty
for key, fn := range binding {
bindingValue = bindingValue.Assoc(key, fn)
}
bindings[mode] = eval.NewPtrVariableWithValidator(
BindingTable{eval.NewMap(bindingValue)}, shouldBeBindingTable)
}
return bindings
}

var errShouldBeBindingTable = errors.New("should be binding table")

func shouldBeBindingTable(v eval.Value) error {
if _, ok := v.(BindingTable); !ok {
return errShouldBeBindingTable
}
return nil
}

0 comments on commit c499071

Please sign in to comment.