mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-13 09:57:51 +08:00
newedit/listing: Add basic selection movement support.
This commit is contained in:
parent
4c654b6822
commit
c6d7be11ed
|
@ -14,6 +14,8 @@
|
|||
package listing
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/elves/elvish/edit/tty"
|
||||
"github.com/elves/elvish/edit/ui"
|
||||
"github.com/elves/elvish/newedit/types"
|
||||
|
@ -23,7 +25,8 @@ import (
|
|||
// Mode represents a listing mode, implementing the types.Mode interface.
|
||||
type Mode struct {
|
||||
StartConfig
|
||||
states
|
||||
state State
|
||||
stateMutex sync.Mutex
|
||||
}
|
||||
|
||||
// StartConfig is the configuration for starting the listing mode.
|
||||
|
@ -37,14 +40,6 @@ type StartConfig struct {
|
|||
// StartFiltering bool
|
||||
}
|
||||
|
||||
type states struct {
|
||||
filtering bool
|
||||
filter string
|
||||
items Items
|
||||
first int
|
||||
selected int
|
||||
}
|
||||
|
||||
// Items is an interface for accessing items to show in the listing mode.
|
||||
type Items interface {
|
||||
Len() int
|
||||
|
@ -75,11 +70,14 @@ func (m *Mode) ModeRenderFlag() types.ModeRenderFlag {
|
|||
return 0
|
||||
}
|
||||
|
||||
// HandleEvent handles key events and ignores other types of events.
|
||||
func (m *Mode) HandleEvent(e tty.Event, st *types.State) types.HandlerAction {
|
||||
switch e := e.(type) {
|
||||
case tty.KeyEvent:
|
||||
if m.KeyHandler == nil {
|
||||
return defaultHandler(ui.Key(e), st)
|
||||
m.stateMutex.Lock()
|
||||
defer m.stateMutex.Unlock()
|
||||
return defaultHandler(ui.Key(e), st, &m.state)
|
||||
}
|
||||
return m.KeyHandler(ui.Key(e))
|
||||
default:
|
||||
|
@ -87,15 +85,31 @@ func (m *Mode) HandleEvent(e tty.Event, st *types.State) types.HandlerAction {
|
|||
}
|
||||
}
|
||||
|
||||
func defaultHandler(k ui.Key, st *types.State) types.HandlerAction {
|
||||
func defaultHandler(k ui.Key, st *types.State, mst *State) types.HandlerAction {
|
||||
switch k {
|
||||
case ui.Key{'[', ui.Ctrl}:
|
||||
case ui.K('[', ui.Ctrl):
|
||||
// TODO(xiaq): Go back to previous mode instead of the initial mode.
|
||||
st.SetMode(nil)
|
||||
case ui.K(ui.Down):
|
||||
mst.Down()
|
||||
case ui.K(ui.Up):
|
||||
mst.Up()
|
||||
case ui.K(ui.Tab):
|
||||
mst.DownCycle()
|
||||
case ui.K(ui.Tab, ui.Shift):
|
||||
mst.UpCycle()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// MutateStates mutates the states using the given function, guarding the
|
||||
// mutation with the mutex.
|
||||
func (m *Mode) MutateStates(f func(*State)) {
|
||||
m.stateMutex.Lock()
|
||||
defer m.stateMutex.Unlock()
|
||||
f(&m.state)
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -106,29 +120,34 @@ var (
|
|||
styleForLastLine = "underlined"
|
||||
)
|
||||
|
||||
// List renders the listing.
|
||||
func (m *Mode) List(maxHeight int) ui.Renderer {
|
||||
if m.items == nil {
|
||||
m.stateMutex.Lock()
|
||||
defer m.stateMutex.Unlock()
|
||||
st := &m.state
|
||||
|
||||
if st.items == nil {
|
||||
// This is the first time List is called, get initial items.
|
||||
m.items = m.ItemsGetter(m.filter)
|
||||
st.items = m.ItemsGetter(st.filter)
|
||||
}
|
||||
n := m.items.Len()
|
||||
n := st.items.Len()
|
||||
if n == 0 {
|
||||
// No result.
|
||||
return ui.NewStringRenderer("(no result)")
|
||||
}
|
||||
|
||||
newFirst, firstCrop := findWindow(m.items, m.first, m.selected, maxHeight)
|
||||
m.first = newFirst
|
||||
newFirst, firstCrop := findWindow(st.items, st.first, st.selected, maxHeight)
|
||||
st.first = newFirst
|
||||
|
||||
var allLines []styled.Text
|
||||
upper := n
|
||||
lastCropped := false
|
||||
for i := m.first; i < n; i++ {
|
||||
lines := m.items.Show(i).SplitByRune('\n')
|
||||
if i == m.first && firstCrop > 0 {
|
||||
for i := st.first; i < n; i++ {
|
||||
lines := st.items.Show(i).SplitByRune('\n')
|
||||
if i == st.first && firstCrop > 0 {
|
||||
lines = lines[firstCrop:]
|
||||
}
|
||||
if i == m.selected {
|
||||
if i == st.selected {
|
||||
for i := range lines {
|
||||
lines[i] = styled.Transform(lines[i], styleForSelected)
|
||||
}
|
||||
|
@ -147,8 +166,8 @@ func (m *Mode) List(maxHeight int) ui.Renderer {
|
|||
}
|
||||
|
||||
rd := NewStyledTextsRenderer(allLines)
|
||||
if m.first > 0 || firstCrop > 0 || upper < n || lastCropped {
|
||||
rd = ui.NewRendererWithVerticalScrollbar(rd, n, m.first, upper)
|
||||
if st.first > 0 || firstCrop > 0 || upper < n || lastCropped {
|
||||
rd = ui.NewRendererWithVerticalScrollbar(rd, n, st.first, upper)
|
||||
}
|
||||
return rd
|
||||
}
|
||||
|
|
|
@ -75,8 +75,8 @@ func TestList_Normal(t *testing.T) {
|
|||
m := Mode{}
|
||||
m.Start(StartConfig{ItemsGetter: func(string) Items { return fakeItems{10} }})
|
||||
|
||||
m.selected = 3
|
||||
m.first = 1
|
||||
m.state.selected = 3
|
||||
m.state.first = 1
|
||||
|
||||
renderer := m.List(6)
|
||||
|
||||
|
@ -114,7 +114,7 @@ func TestList_Crop(t *testing.T) {
|
|||
styled.Unstyled("1a\n1b"), styled.Unstyled("2a\n2b"))
|
||||
}})
|
||||
|
||||
m.selected = 1
|
||||
m.state.selected = 1
|
||||
renderer := m.List(4)
|
||||
|
||||
wantBase := NewStyledTextsRenderer([]styled.Text{
|
||||
|
|
44
newedit/listing/state.go
Normal file
44
newedit/listing/state.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package listing
|
||||
|
||||
// State keeps the state of the listing mode.
|
||||
type State struct {
|
||||
filtering bool
|
||||
filter string
|
||||
items Items
|
||||
first int
|
||||
selected int
|
||||
}
|
||||
|
||||
// Up moves the selection up.
|
||||
func (st *State) Up() {
|
||||
if st.selected > 0 {
|
||||
st.selected--
|
||||
}
|
||||
}
|
||||
|
||||
// UpCycle moves the selection up, wrapping to the last item if the currently
|
||||
// selected item is the first item.
|
||||
func (st *State) UpCycle() {
|
||||
if st.selected > 0 {
|
||||
st.selected--
|
||||
} else {
|
||||
st.selected = st.items.Len() - 1
|
||||
}
|
||||
}
|
||||
|
||||
// Down moves the selection down.
|
||||
func (st *State) Down() {
|
||||
if st.selected < st.items.Len()-1 {
|
||||
st.selected++
|
||||
}
|
||||
}
|
||||
|
||||
// DownCycle moves the selection down, wrapping to the first item if the
|
||||
// currently selected item is the last item.
|
||||
func (st *State) DownCycle() {
|
||||
if st.selected < st.items.Len()-1 {
|
||||
st.selected++
|
||||
} else {
|
||||
st.selected = 0
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user