newedit: Implement a basic location mode.

This commit is contained in:
Qi Xiao 2019-04-25 09:12:58 +01:00
parent 5e41a4cd40
commit 5eaec24f11
4 changed files with 206 additions and 0 deletions

View File

@ -0,0 +1,65 @@
package location
import (
"fmt"
"strings"
"github.com/elves/elvish/edit/ui"
"github.com/elves/elvish/newedit/listing"
"github.com/elves/elvish/newedit/types"
"github.com/elves/elvish/store/storedefs"
"github.com/elves/elvish/styled"
)
// Mode represents the location mode. It implements the types.Mode interface by
// embedding a *listing.Mode.
type Mode struct {
*listing.Mode
KeyHandler func(ui.Key) types.HandlerAction
Cd func(string) error
}
// Start starts the location mode.
func (m *Mode) Start(dirs []storedefs.Dir) {
m.Mode.Start(listing.StartConfig{
Name: "LOCATION",
KeyHandler: m.KeyHandler,
ItemsGetter: func(p string) listing.Items {
return getItems(dirs, p, m.Cd)
},
StartFilter: true,
})
}
func getItems(dirs []storedefs.Dir, p string, cd func(string) error) items {
var entries []storedefs.Dir
for _, dir := range dirs {
if strings.Contains(dir.Path, p) {
entries = append(entries, dir)
}
}
return items{entries, cd}
}
// A slice of entries plus a cd callback, implementing the listing.Items
// interface.
type items struct {
entries []storedefs.Dir
cd func(string) error
}
func (it items) Len() int {
return len(it.entries)
}
func (it items) Show(i int) styled.Text {
return styled.Unstyled(
fmt.Sprintf("%3.0f %s", it.entries[i].Score, it.entries[i].Path))
}
func (it items) Accept(i int, st *types.State) {
err := it.cd(it.entries[i].Path)
if err != nil {
st.AddNote(err.Error())
}
}

View File

@ -0,0 +1,63 @@
package location
import (
"errors"
"reflect"
"testing"
"github.com/elves/elvish/newedit/listing"
"github.com/elves/elvish/newedit/types"
"github.com/elves/elvish/store/storedefs"
"github.com/elves/elvish/styled"
"github.com/elves/elvish/tt"
)
var Args = tt.Args
func TestGetEntries(t *testing.T) {
dirs := []storedefs.Dir{
{Path: "/home/elf", Score: 20},
{Path: "/usr/bin", Score: 10},
}
dummyCd := func(string) error { return nil }
tt.Test(t, tt.Fn("getItems", getItems), tt.Table{
Args(dirs, "", dummyCd).Rets(listing.MatchItems(
styled.Unstyled(" 20 /home/elf"),
styled.Unstyled(" 10 /usr/bin"),
)),
Args(dirs, "/usr", dummyCd).Rets(listing.MatchItems(
styled.Unstyled(" 10 /usr/bin"),
))})
}
func TestAccept_OK(t *testing.T) {
dirs := []storedefs.Dir{
{Path: "/home/elf", Score: 20},
{Path: "/usr/bin", Score: 10},
}
calledDir := ""
cd := func(dir string) error {
calledDir = dir
return nil
}
getItems(dirs, "", cd).Accept(0, &types.State{})
if calledDir != "/home/elf" {
t.Errorf("Accept did not call cd")
}
}
func TestAccept_Error(t *testing.T) {
dirs := []storedefs.Dir{
{Path: "/home/elf", Score: 20},
{Path: "/usr/bin", Score: 10},
}
cd := func(string) error { return errors.New("cannot cd") }
state := types.State{}
getItems(dirs, "", cd).Accept(0, &state)
wantNotes := []string{"cannot cd"}
if !reflect.DeepEqual(state.Raw.Notes, wantNotes) {
t.Errorf("cd errors not added to notes")
}
}

31
newedit/location_api.go Normal file
View File

@ -0,0 +1,31 @@
package newedit
import (
"github.com/elves/elvish/eval"
"github.com/elves/elvish/newedit/listing"
"github.com/elves/elvish/newedit/location"
"github.com/elves/elvish/store/storedefs"
)
func initLocation(ed editor, ev *eval.Evaler, getDirs func() ([]storedefs.Dir, error), cd func(string) error, lsMode *listing.Mode, lsBinding *bindingMap) eval.Ns {
binding := emptyBindingMap
mode := location.Mode{
Mode: lsMode,
KeyHandler: keyHandlerFromBindings(ed, ev, &binding, lsBinding),
Cd: cd,
}
ns := eval.Ns{}.
AddGoFn("<edit:location>", "start", func() {
startLocation(ed, getDirs, &mode)
})
return ns
}
func startLocation(ed editor, getDirs func() ([]storedefs.Dir, error), mode *location.Mode) {
dirs, err := getDirs()
if err != nil {
ed.Notify("db error: " + err.Error())
}
mode.Start(dirs)
ed.State().SetMode(mode)
}

View File

@ -0,0 +1,47 @@
package newedit
import (
"reflect"
"testing"
"github.com/elves/elvish/edit/ui"
"github.com/elves/elvish/eval"
"github.com/elves/elvish/newedit/listing"
"github.com/elves/elvish/newedit/types"
"github.com/elves/elvish/store/storedefs"
)
func TestLocation_Start(t *testing.T) {
ed := &fakeEditor{}
ev := eval.NewEvaler()
lsMode := listing.Mode{}
lsBinding := emptyBindingMap
getDirs := func() ([]storedefs.Dir, error) {
return []storedefs.Dir{
{Path: "/usr/bin", Score: 20},
{Path: "/home/elf", Score: 10},
}, nil
}
cd := func(string) error { return nil }
ns := initLocation(ed, ev, getDirs, cd, &lsMode, &lsBinding)
// Call <edit:location>:start.
fm := eval.NewTopFrame(ev, eval.NewInternalSource("[test]"), nil)
fm.Call(getFn(ns, "start"), eval.NoArgs, eval.NoOpts)
// Verify that the current mode supports listing.
lister, ok := ed.state.Mode().(types.Lister)
if !ok {
t.Errorf("Mode is not Lister after <edit:location>:start")
}
// Verify the actual listing.
buf := ui.Render(lister.List(10), 30)
wantBuf := ui.NewBufferBuilder(30).
WriteString(" 20 /usr/bin", "7").Newline().
WriteString(" 10 /home/elf", "").Buffer()
if !reflect.DeepEqual(buf, wantBuf) {
t.Errorf("Rendered listing is %v, want %v", buf, wantBuf)
}
}