mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-14 02:57:52 +08:00
newedit: Support accepting in listing mode. Add tests.
This commit is contained in:
parent
acb95dac00
commit
06fae6494a
|
@ -34,6 +34,8 @@ listing:binding = (binding-map [
|
|||
&Down= $listing:down~
|
||||
&Tab= $listing:down-cycle~
|
||||
&Shift-Tab= $listing:up-cycle~
|
||||
&Enter= $listing:accept-close~
|
||||
&Alt-Enter= $listing:accept~
|
||||
])
|
||||
`
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ func NewEditor(in, out *os.File, ev *eval.Evaler) *Editor {
|
|||
ns.AddNs("insert", insertNs)
|
||||
|
||||
// Listing modes.
|
||||
lsMode, lsBinding, lsNs := initListing()
|
||||
lsMode, lsBinding, lsNs := initListing(ed)
|
||||
ns.AddNs("listing", lsNs)
|
||||
lastcmdNs := initLastcmd(ed, ev, lsMode, lsBinding)
|
||||
ns.AddNs("lastcmd", lastcmdNs)
|
||||
|
|
|
@ -113,7 +113,7 @@ func (m *Mode) handlePasteEnd(st *types.State) {
|
|||
if m.paste == quotePaste {
|
||||
text = parse.Quote(text)
|
||||
}
|
||||
insert(st, text)
|
||||
st.InsertAtDot(text)
|
||||
m.pastes = nil
|
||||
m.paste = noPaste
|
||||
}
|
||||
|
@ -165,11 +165,3 @@ func (m *Mode) handleKey(k ui.Key, st *types.State) types.HandlerAction {
|
|||
}
|
||||
return action
|
||||
}
|
||||
|
||||
func insert(st *types.State, text string) {
|
||||
st.Mutex.Lock()
|
||||
defer st.Mutex.Unlock()
|
||||
raw := &st.Raw
|
||||
raw.Code = raw.Code[:raw.Dot] + text + raw.Code[raw.Dot:]
|
||||
raw.Dot += len(text)
|
||||
}
|
||||
|
|
|
@ -94,3 +94,7 @@ func (it items) Show(i int) styled.Text {
|
|||
}
|
||||
return styled.Unstyled(fmt.Sprintf("%3s %s", index, entry.content))
|
||||
}
|
||||
|
||||
func (it items) Accept(i int, st *types.State) {
|
||||
st.InsertAtDot(it.entries[i].content)
|
||||
}
|
||||
|
|
|
@ -35,7 +35,6 @@ type StartConfig struct {
|
|||
KeyHandler func(ui.Key) types.HandlerAction
|
||||
ItemsGetter func(filter string) Items
|
||||
// TODO(xiaq): Support the following config options.
|
||||
// AcceptItem func(i int)
|
||||
// AutoAccept bool
|
||||
// StartFiltering bool
|
||||
}
|
||||
|
@ -44,6 +43,7 @@ type StartConfig struct {
|
|||
type Items interface {
|
||||
Len() int
|
||||
Show(int) styled.Text
|
||||
Accept(int, *types.State)
|
||||
}
|
||||
|
||||
// SliceItems returns an Items consisting of the given texts.
|
||||
|
@ -51,13 +51,19 @@ func SliceItems(texts ...styled.Text) Items { return sliceItems{texts} }
|
|||
|
||||
type sliceItems struct{ texts []styled.Text }
|
||||
|
||||
func (it sliceItems) Len() int { return len(it.texts) }
|
||||
func (it sliceItems) Show(i int) styled.Text { return it.texts[i] }
|
||||
func (it sliceItems) Len() int { return len(it.texts) }
|
||||
func (it sliceItems) Show(i int) styled.Text { return it.texts[i] }
|
||||
func (it sliceItems) Accept(int, *types.State) {}
|
||||
|
||||
// Start starts the listing mode, using the given config and resetting all
|
||||
// states.
|
||||
func (m *Mode) Start(cfg StartConfig) {
|
||||
*m = Mode{StartConfig: cfg}
|
||||
if cfg.ItemsGetter != nil {
|
||||
m.state.items = cfg.ItemsGetter("")
|
||||
} else {
|
||||
m.state.items = sliceItems{}
|
||||
}
|
||||
}
|
||||
|
||||
// ModeLine returns a modeline showing the specified name of the mode.
|
||||
|
@ -110,6 +116,22 @@ func (m *Mode) MutateStates(f func(*State)) {
|
|||
f(&m.state)
|
||||
}
|
||||
|
||||
// AcceptItem accepts the currently selected item.
|
||||
func (m *Mode) AcceptItem(st *types.State) {
|
||||
m.stateMutex.Lock()
|
||||
defer m.stateMutex.Unlock()
|
||||
m.state.items.Accept(m.state.selected, st)
|
||||
}
|
||||
|
||||
// AcceptItemAndClose accepts the currently selected item and closes the listing
|
||||
// mode.
|
||||
func (m *Mode) AcceptItemAndClose(st *types.State) {
|
||||
m.stateMutex.Lock()
|
||||
defer m.stateMutex.Unlock()
|
||||
m.state.items.Accept(m.state.selected, st)
|
||||
st.SetMode(nil)
|
||||
}
|
||||
|
||||
// The number of lines the listing mode keeps between the current selected item
|
||||
// and the top and bottom edges of the window, unless the available height is
|
||||
// too small or if the selected item is near the top or bottom of the list.
|
||||
|
@ -126,10 +148,6 @@ func (m *Mode) List(maxHeight int) ui.Renderer {
|
|||
defer m.stateMutex.Unlock()
|
||||
st := &m.state
|
||||
|
||||
if st.items == nil {
|
||||
// This is the first time List is called, get initial items.
|
||||
st.items = m.ItemsGetter(st.filter)
|
||||
}
|
||||
n := st.items.Len()
|
||||
if n == 0 {
|
||||
// No result.
|
||||
|
|
|
@ -12,6 +12,30 @@ import (
|
|||
"github.com/elves/elvish/tt"
|
||||
)
|
||||
|
||||
// Implementation of Items that emulates a list of numbers from 0 to n-1.
|
||||
type fakeItems struct{ n int }
|
||||
|
||||
func (it fakeItems) Len() int { return it.n }
|
||||
|
||||
func (it fakeItems) Show(i int) styled.Text {
|
||||
return styled.Unstyled(strconv.Itoa(i))
|
||||
}
|
||||
|
||||
func (it fakeItems) Accept(int, *types.State) {}
|
||||
|
||||
// Implementation of Items that emulate 10 empty texts, but can be accepted.
|
||||
type fakeAcceptableItems struct{ accept func(int, *types.State) }
|
||||
|
||||
func (it fakeAcceptableItems) Len() int { return 10 }
|
||||
|
||||
func (it fakeAcceptableItems) Show(int) styled.Text {
|
||||
return styled.Unstyled("")
|
||||
}
|
||||
|
||||
func (it fakeAcceptableItems) Accept(i int, st *types.State) {
|
||||
it.accept(i, st)
|
||||
}
|
||||
|
||||
func TestModeLine(t *testing.T) {
|
||||
m := Mode{}
|
||||
m.Start(StartConfig{Name: "LISTING"})
|
||||
|
@ -47,11 +71,50 @@ func TestHandleEvent_CallsKeyHandler(t *testing.T) {
|
|||
|
||||
func TestHandleEvent_DefaultHandler(t *testing.T) {
|
||||
m := Mode{}
|
||||
m.Start(StartConfig{ItemsGetter: func(string) Items {
|
||||
return fakeItems{10}
|
||||
}})
|
||||
st := types.State{}
|
||||
st.SetMode(&m)
|
||||
|
||||
m.HandleEvent(tty.KeyEvent{ui.Down, 0}, &st)
|
||||
if m.state.selected != 1 {
|
||||
t.Errorf("Down did not move selection down")
|
||||
}
|
||||
|
||||
m.HandleEvent(tty.KeyEvent{ui.Up, 0}, &st)
|
||||
if m.state.selected != 0 {
|
||||
t.Errorf("Up did not move selection up")
|
||||
}
|
||||
|
||||
m.HandleEvent(tty.KeyEvent{ui.Up, 0}, &st)
|
||||
if m.state.selected != 0 {
|
||||
t.Errorf("Up did not stop at first item")
|
||||
}
|
||||
|
||||
m.HandleEvent(tty.KeyEvent{ui.Tab, ui.Shift}, &st)
|
||||
if m.state.selected != 9 {
|
||||
t.Errorf("Shift-Tab did not wrap to last item")
|
||||
}
|
||||
|
||||
m.HandleEvent(tty.KeyEvent{ui.Tab, 0}, &st)
|
||||
if m.state.selected != 0 {
|
||||
t.Errorf("Tab did not wrap to first item")
|
||||
}
|
||||
|
||||
m.HandleEvent(tty.KeyEvent{ui.Tab, 0}, &st)
|
||||
if m.state.selected != 1 {
|
||||
t.Errorf("Tab did not move selection down")
|
||||
}
|
||||
|
||||
m.HandleEvent(tty.KeyEvent{ui.Tab, ui.Shift}, &st)
|
||||
if m.state.selected != 0 {
|
||||
t.Errorf("Shift-Tab did not move selection up")
|
||||
}
|
||||
|
||||
m.HandleEvent(tty.KeyEvent{'[', ui.Ctrl}, &st)
|
||||
if st.Mode() != nil {
|
||||
t.Errorf("C-[ of the default handler did not set mode to nil")
|
||||
t.Errorf("Ctrl-[ did not set mode to nil")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,12 +126,45 @@ func TestHandleEvent_NonKeyEvent(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type fakeItems struct{ n int }
|
||||
func TestMutateState(t *testing.T) {
|
||||
m := Mode{}
|
||||
m.MutateStates(func(st *State) {
|
||||
st.selected = 10
|
||||
})
|
||||
if m.state.selected != 10 {
|
||||
t.Errorf("state not mutated")
|
||||
}
|
||||
}
|
||||
|
||||
func (it fakeItems) Len() int { return it.n }
|
||||
func TestAcceptItem(t *testing.T) {
|
||||
m := Mode{}
|
||||
accepted := -1
|
||||
m.Start(StartConfig{ItemsGetter: func(string) Items {
|
||||
return fakeAcceptableItems{func(i int, st *types.State) { accepted = i }}
|
||||
}})
|
||||
m.state.selected = 7
|
||||
m.AcceptItem(&types.State{})
|
||||
if accepted != 7 {
|
||||
t.Errorf("accept called with %v, want 7", accepted)
|
||||
}
|
||||
}
|
||||
|
||||
func (it fakeItems) Show(i int) styled.Text {
|
||||
return styled.Unstyled(strconv.Itoa(i))
|
||||
func TestAcceptItemAndClose(t *testing.T) {
|
||||
m := Mode{}
|
||||
accepted := -1
|
||||
m.Start(StartConfig{ItemsGetter: func(string) Items {
|
||||
return fakeAcceptableItems{func(i int, st *types.State) { accepted = i }}
|
||||
}})
|
||||
m.state.selected = 7
|
||||
st := &types.State{}
|
||||
st.SetMode(&m)
|
||||
m.AcceptItemAndClose(st)
|
||||
if accepted != 7 {
|
||||
t.Errorf("accept called with %v, want 7", accepted)
|
||||
}
|
||||
if st.Raw.Mode != nil {
|
||||
t.Errorf("mode not reset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestList_Normal(t *testing.T) {
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"github.com/elves/elvish/newedit/listing"
|
||||
)
|
||||
|
||||
func initListing() (*listing.Mode, *BindingMap, eval.Ns) {
|
||||
func initListing(ed editor) (*listing.Mode, *BindingMap, eval.Ns) {
|
||||
mode := &listing.Mode{}
|
||||
binding := EmptyBindingMap
|
||||
ns := eval.Ns{
|
||||
|
@ -16,6 +16,9 @@ func initListing() (*listing.Mode, *BindingMap, eval.Ns) {
|
|||
"down": func() { mode.MutateStates((*listing.State).Down) },
|
||||
"up-cycle": func() { mode.MutateStates((*listing.State).UpCycle) },
|
||||
"down-cycle": func() { mode.MutateStates((*listing.State).DownCycle) },
|
||||
|
||||
"accept": func() { mode.AcceptItem(ed.State()) },
|
||||
"accept-close": func() { mode.AcceptItemAndClose(ed.State()) },
|
||||
})
|
||||
return mode, &binding, ns
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
func TestInitListing_Binding(t *testing.T) {
|
||||
// Test that the binding variable in the returned namespace indeed refers to
|
||||
// the BindingMap returned.
|
||||
_, binding, ns := initListing()
|
||||
_, binding, ns := initListing(&fakeEditor{})
|
||||
if ns["binding"].Get() != *binding {
|
||||
t.Errorf("The binding var in the ns is not the same as the BindingMap")
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
package types
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestTODO(t *testing.T) {
|
||||
}
|
|
@ -75,6 +75,15 @@ func (s *State) CodeAfterDot() string {
|
|||
return s.Raw.Code[s.Raw.Dot:]
|
||||
}
|
||||
|
||||
// InsertAtDot inserts the given text at the dot.
|
||||
func (s *State) InsertAtDot(text string) {
|
||||
s.Mutex.Lock()
|
||||
defer s.Mutex.Unlock()
|
||||
raw := &s.Raw
|
||||
raw.Code = raw.Code[:raw.Dot] + text + raw.Code[raw.Dot:]
|
||||
raw.Dot += len(text)
|
||||
}
|
||||
|
||||
// AddNote adds a note.
|
||||
func (s *State) AddNote(note string) {
|
||||
s.Mutex.Lock()
|
||||
|
|
15
newedit/types/state_test.go
Normal file
15
newedit/types/state_test.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInsertAtDot(t *testing.T) {
|
||||
st := &State{Raw: RawState{Code: "ab", Dot: 1}}
|
||||
st.InsertAtDot("xy")
|
||||
wantRawState := RawState{Code: "axyb", Dot: 3}
|
||||
if !reflect.DeepEqual(st.Raw, wantRawState) {
|
||||
t.Errorf("got raw state %v, want %v", st.Raw, wantRawState)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user