KeyMap Expansion

This commit is contained in:
2025-07-31 13:31:14 -05:00
parent 174fab2c6d
commit 9e88799391
7 changed files with 111 additions and 54 deletions

18
chat.go
View File

@@ -166,12 +166,12 @@ func (w *Chat) WantW() int { return w.w }
func (w *Chat) WantH() int { return w.h } func (w *Chat) WantH() int { return w.h }
func (w *Chat) initKeyMap() { func (w *Chat) initKeyMap() {
w.keyMap = NewKeyMap(map[tcell.Key]func() bool{ w.keyMap = NewKeyMap(map[tcell.Key]func(ev *tcell.EventKey) bool{
tcell.KeyEsc: func() bool { tcell.KeyEsc: func(ev *tcell.EventKey) bool {
w.SetActive(false) w.SetActive(false)
return true return true
}, },
tcell.KeyUp: func() bool { tcell.KeyUp: func(ev *tcell.EventKey) bool {
if w.historyPosition < len(w.history)-1 { if w.historyPosition < len(w.history)-1 {
w.historyPosition++ w.historyPosition++
w.value = w.history[w.historyPosition].Message w.value = w.history[w.historyPosition].Message
@@ -180,7 +180,7 @@ func (w *Chat) initKeyMap() {
} }
return false return false
}, },
tcell.KeyDown: func() bool { tcell.KeyDown: func(ev *tcell.EventKey) bool {
if w.historyPosition > -1 { if w.historyPosition > -1 {
w.historyPosition-- w.historyPosition--
if w.historyPosition == -1 { if w.historyPosition == -1 {
@@ -193,31 +193,31 @@ func (w *Chat) initKeyMap() {
} }
return false return false
}, },
tcell.KeyLeft: func() bool { tcell.KeyLeft: func(ev *tcell.EventKey) bool {
if w.cursor > 0 { if w.cursor > 0 {
w.cursor-- w.cursor--
return true return true
} }
return false return false
}, },
tcell.KeyRight: func() bool { tcell.KeyRight: func(ev *tcell.EventKey) bool {
if w.cursor < len(w.value) { if w.cursor < len(w.value) {
w.cursor++ w.cursor++
return true return true
} }
return false return false
}, },
tcell.KeyCtrlU: func() bool { tcell.KeyCtrlU: func(ev *tcell.EventKey) bool {
w.value = w.value[w.cursor:] w.value = w.value[w.cursor:]
w.cursor = 0 w.cursor = 0
return true return true
}, },
tcell.KeyTab: func() bool { tcell.KeyTab: func(ev *tcell.EventKey) bool {
// Auto-complete // Auto-complete
// TODO: Find best guess for current word // TODO: Find best guess for current word
return false return false
}, },
tcell.KeyEnter: func() bool { tcell.KeyEnter: func(ev *tcell.EventKey) bool {
// TODO: Submit the message // TODO: Submit the message
return false return false
}, },

18
cli.go
View File

@@ -171,12 +171,12 @@ func (w *Cli) WantW() int { return w.w }
func (w *Cli) WantH() int { return w.h } func (w *Cli) WantH() int { return w.h }
func (w *Cli) initKeyMap() { func (w *Cli) initKeyMap() {
w.keyMap = NewKeyMap(map[tcell.Key]func() bool{ w.keyMap = NewKeyMap(map[tcell.Key]func(ev *tcell.EventKey) bool{
tcell.KeyEsc: func() bool { tcell.KeyEsc: func(ev *tcell.EventKey) bool {
w.SetActive(false) w.SetActive(false)
return true return true
}, },
tcell.KeyUp: func() bool { tcell.KeyUp: func(ev *tcell.EventKey) bool {
if w.historyPosition < len(w.history)-1 { if w.historyPosition < len(w.history)-1 {
w.historyPosition++ w.historyPosition++
w.value = w.history[w.historyPosition] w.value = w.history[w.historyPosition]
@@ -185,7 +185,7 @@ func (w *Cli) initKeyMap() {
} }
return false return false
}, },
tcell.KeyDown: func() bool { tcell.KeyDown: func(ev *tcell.EventKey) bool {
if w.historyPosition > -1 { if w.historyPosition > -1 {
w.historyPosition-- w.historyPosition--
if w.historyPosition == -1 { if w.historyPosition == -1 {
@@ -198,26 +198,26 @@ func (w *Cli) initKeyMap() {
} }
return false return false
}, },
tcell.KeyLeft: func() bool { tcell.KeyLeft: func(ev *tcell.EventKey) bool {
if w.cursor > 0 { if w.cursor > 0 {
w.cursor-- w.cursor--
return true return true
} }
return false return false
}, },
tcell.KeyRight: func() bool { tcell.KeyRight: func(ev *tcell.EventKey) bool {
if w.cursor < len(w.value) { if w.cursor < len(w.value) {
w.cursor++ w.cursor++
return true return true
} }
return false return false
}, },
tcell.KeyCtrlU: func() bool { tcell.KeyCtrlU: func(ev *tcell.EventKey) bool {
w.value = w.value[w.cursor:] w.value = w.value[w.cursor:]
w.cursor = 0 w.cursor = 0
return true return true
}, },
tcell.KeyTab: func() bool { tcell.KeyTab: func(ev *tcell.EventKey) bool {
// Auto-complete // Auto-complete
guess := w.findBestGuess() guess := w.findBestGuess()
if guess != "" { if guess != "" {
@@ -227,7 +227,7 @@ func (w *Cli) initKeyMap() {
} }
return false return false
}, },
tcell.KeyEnter: func() bool { tcell.KeyEnter: func(ev *tcell.EventKey) bool {
cmds := strings.Split(w.value, " ") cmds := strings.Split(w.value, " ")
if v, ok := w.commandMap[cmds[0]]; ok { if v, ok := w.commandMap[cmds[0]]; ok {
w.history = append(w.history, w.value) w.history = append(w.history, w.value)

View File

@@ -63,7 +63,7 @@ func (w *Field) Init(id string, style tcell.Style) {
return h.IsBS(*ev) || return h.IsBS(*ev) ||
h.KeyIsDisplayable(*ev) h.KeyIsDisplayable(*ev)
} }
w.keyMap = NewKeyMap(map[tcell.Key]func() bool{ w.keyMap = NewKeyMap(map[tcell.Key]func(ev *tcell.EventKey) bool{
tcell.KeyLeft: w.handleLeft, tcell.KeyLeft: w.handleLeft,
tcell.KeyRight: w.handleRight, tcell.KeyRight: w.handleRight,
tcell.KeyHome: w.handleHome, tcell.KeyHome: w.handleHome,
@@ -79,7 +79,7 @@ func (w *Field) HandleKey(ev *tcell.EventKey) bool {
return false return false
} }
if h.IsBS(*ev) { if h.IsBS(*ev) {
return w.handleBackspace() return w.handleBackspace(ev)
} }
if ok := w.keyMap.Handle(ev); ok { if ok := w.keyMap.Handle(ev); ok {
return true return true
@@ -153,7 +153,7 @@ func (w *Field) SetSize(c Coord) { w.w, w.h = c.X, c.Y }
func (w *Field) Focusable() bool { return true } func (w *Field) Focusable() bool { return true }
/* Non-Widget-Interface Functions */ /* Non-Widget-Interface Functions */
func (w *Field) handleBackspace() bool { func (w *Field) handleBackspace(ev *tcell.EventKey) bool {
st := w.cursor st := w.cursor
if w.cursor > 0 { if w.cursor > 0 {
w.cursor-- w.cursor--
@@ -167,7 +167,7 @@ func (w *Field) handleBackspace() bool {
return false return false
} }
func (w *Field) handleLeft() bool { func (w *Field) handleLeft(ev *tcell.EventKey) bool {
if w.cursor > 0 { if w.cursor > 0 {
w.cursor-- w.cursor--
return true return true
@@ -175,7 +175,7 @@ func (w *Field) handleLeft() bool {
return false return false
} }
func (w *Field) handleRight() bool { func (w *Field) handleRight(ev *tcell.EventKey) bool {
if w.cursor < len(w.value) { if w.cursor < len(w.value) {
w.cursor++ w.cursor++
return true return true
@@ -183,18 +183,18 @@ func (w *Field) handleRight() bool {
return false return false
} }
func (w *Field) clearValueBeforeCursor() bool { func (w *Field) clearValueBeforeCursor(ev *tcell.EventKey) bool {
w.SetValue(w.value[w.cursor:]) w.SetValue(w.value[w.cursor:])
w.cursor = 0 w.cursor = 0
return true return true
} }
func (w *Field) handleHome() bool { func (w *Field) handleHome(ev *tcell.EventKey) bool {
w.cursor = 0 w.cursor = 0
return true return true
} }
func (w *Field) handleEnd() bool { func (w *Field) handleEnd(ev *tcell.EventKey) bool {
w.cursor = len(w.value) w.cursor = len(w.value)
return true return true
} }

View File

@@ -23,27 +23,69 @@ package widgets
import "github.com/gdamore/tcell" import "github.com/gdamore/tcell"
type KeyMap map[tcell.Key]func() bool type KeyMap struct {
Keys map[tcell.Key]func(*tcell.EventKey) bool
Runes map[rune]func(*tcell.EventKey) bool
}
func BlankKeyMap() KeyMap { func BlankKeyMap() KeyMap {
return KeyMap(make(map[tcell.Key]func() bool)) return KeyMap{
Keys: make(map[tcell.Key]func(*tcell.EventKey) bool),
Runes: make(map[rune]func(*tcell.EventKey) bool),
}
} }
func NewKeyMap(m map[tcell.Key]func() bool) KeyMap { func NewKeyMap(m map[tcell.Key]func(*tcell.EventKey) bool) KeyMap {
return KeyMap(m) return KeyMap{
Keys: m,
Runes: make(map[rune]func(*tcell.EventKey) bool),
}
} }
func (m KeyMap) Add(k tcell.Key, do func() bool) { m[k] = do } func NewRuneMap(m map[rune]func(*tcell.EventKey) bool) KeyMap {
return KeyMap{
Keys: make(map[tcell.Key]func(*tcell.EventKey) bool),
Runes: m,
}
}
func (m KeyMap) Add(k tcell.Key, do func(*tcell.EventKey) bool) { m.Keys[k] = do }
func (m KeyMap) Remove(k tcell.Key) { func (m KeyMap) Remove(k tcell.Key) {
if _, ok := m[k]; ok { if _, ok := m.Keys[k]; ok {
delete(m, k) delete(m.Keys, k)
}
}
func (m KeyMap) AddAll(all map[tcell.Key]func(*tcell.EventKey) bool) {
for k, v := range all {
m.Add(k, v)
}
}
func (m KeyMap) AddRune(k rune, do func(*tcell.EventKey) bool) { m.Runes[k] = do }
func (m KeyMap) RemoveRune(k rune) {
if _, ok := m.Runes[k]; ok {
delete(m.Runes, k)
}
}
func (m KeyMap) AddRunes(all map[rune]func(*tcell.EventKey) bool) {
for k, v := range all {
m.AddRune(k, v)
} }
} }
func (m KeyMap) Handle(ev *tcell.EventKey) bool { func (m KeyMap) Handle(ev *tcell.EventKey) bool {
for k, v := range m { if ev.Key() == tcell.KeyRune {
for k, v := range m.Runes {
if ev.Rune() == k {
return v(ev)
}
}
} else {
for k, v := range m.Keys {
if ev.Key() == k { if ev.Key() == k {
return v() return v(ev)
}
} }
} }
return false return false

22
list.go
View File

@@ -46,6 +46,7 @@ type List struct {
onSelect func(int, string) bool onSelect func(int, string) bool
keyMap KeyMap keyMap KeyMap
vimMode bool
} }
var _ Widget = (*List)(nil) var _ Widget = (*List)(nil)
@@ -59,16 +60,28 @@ func NewList(id string, style tcell.Style) *List {
func (w *List) Init(id string, style tcell.Style) { func (w *List) Init(id string, style tcell.Style) {
w.id = id w.id = id
w.style = style w.style = style
w.keyMap = NewKeyMap(map[tcell.Key]func() bool{ w.keyMap = NewKeyMap(map[tcell.Key]func(ev *tcell.EventKey) bool{
tcell.KeyUp: w.MoveUp, tcell.KeyUp: w.MoveUp,
tcell.KeyDown: w.MoveDown, tcell.KeyDown: w.MoveDown,
tcell.KeyEnter: func() bool { tcell.KeyEnter: func(ev *tcell.EventKey) bool {
if w.onSelect != nil && w.cursor < len(w.list) { if w.onSelect != nil && w.cursor < len(w.list) {
return w.onSelect(w.cursor, w.list[w.cursor]) return w.onSelect(w.cursor, w.list[w.cursor])
} }
return false return false
}, },
}) })
w.keyMap.AddRune('j', func(ev *tcell.EventKey) bool {
if w.vimMode {
return w.MoveDown(ev)
}
return false
})
w.keyMap.AddRune('k', func(ev *tcell.EventKey) bool {
if w.vimMode {
return w.MoveUp(ev)
}
return false
})
w.itemsStyle = make(map[int]tcell.Style) w.itemsStyle = make(map[int]tcell.Style)
} }
func (w *List) Id() string { return w.id } func (w *List) Id() string { return w.id }
@@ -149,7 +162,7 @@ func (w *List) WantH() int {
func (w *List) SetFocusable(f bool) { w.focusable = f } func (w *List) SetFocusable(f bool) { w.focusable = f }
func (w *List) SetCursorWrap(b bool) { w.cursorWrap = b } func (w *List) SetCursorWrap(b bool) { w.cursorWrap = b }
func (w *List) MoveUp() bool { func (w *List) MoveUp(ev *tcell.EventKey) bool {
if w.cursor > 0 { if w.cursor > 0 {
w.cursor-- w.cursor--
return true return true
@@ -160,7 +173,7 @@ func (w *List) MoveUp() bool {
return false return false
} }
func (w *List) MoveDown() bool { func (w *List) MoveDown(ev *tcell.EventKey) bool {
if w.cursor < len(w.list)-1 { if w.cursor < len(w.list)-1 {
w.cursor++ w.cursor++
return true return true
@@ -204,3 +217,4 @@ func (w *List) SetItem(idx int, txt string) {
func (w *List) GetIdx() int { return w.cursor } func (w *List) GetIdx() int { return w.cursor }
func (w *List) ClearBorder() { w.border = []rune{} } func (w *List) ClearBorder() { w.border = []rune{} }
func (w *List) SetOnSelect(s func(int, string) bool) { w.onSelect = s } func (w *List) SetOnSelect(s func(int, string) bool) { w.onSelect = s }
func (w *List) SetVimMode(b bool) { w.vimMode = b }

13
menu.go
View File

@@ -41,6 +41,7 @@ type Menu struct {
onPressed func() bool onPressed func() bool
manualExpand bool manualExpand bool
expanded bool expanded bool
vimMode bool
keyMap KeyMap keyMap KeyMap
} }
@@ -62,12 +63,12 @@ func (w *Menu) Init(id string, style tcell.Style) {
w.id = id w.id = id
w.style = style w.style = style
w.visible = true w.visible = true
w.keyMap = NewKeyMap(map[tcell.Key]func() bool{ w.keyMap = NewKeyMap(map[tcell.Key]func(ev *tcell.EventKey) bool{
tcell.KeyRight: w.MoveRight, tcell.KeyRight: w.MoveRight,
tcell.KeyLeft: w.MoveLeft, tcell.KeyLeft: w.MoveLeft,
tcell.KeyUp: w.MoveUp, tcell.KeyUp: w.MoveUp,
tcell.KeyDown: w.MoveDown, tcell.KeyDown: w.MoveDown,
tcell.KeyEnter: func() bool { tcell.KeyEnter: func(ev *tcell.EventKey) bool {
if w.onPressed != nil { if w.onPressed != nil {
return w.onPressed() return w.onPressed()
} }
@@ -201,7 +202,7 @@ func (w *Menu) AddItems(iL ...Widget) {
w.SetW(maxW) w.SetW(maxW)
} }
func (w *Menu) MoveRight() bool { func (w *Menu) MoveRight(ev *tcell.EventKey) bool {
if w.menuType != MenuTypeH { if w.menuType != MenuTypeH {
return false return false
} }
@@ -209,7 +210,7 @@ func (w *Menu) MoveRight() bool {
return true return true
} }
func (w *Menu) MoveLeft() bool { func (w *Menu) MoveLeft(ev *tcell.EventKey) bool {
if w.menuType != MenuTypeH { if w.menuType != MenuTypeH {
return false return false
} }
@@ -217,7 +218,7 @@ func (w *Menu) MoveLeft() bool {
return true return true
} }
func (w *Menu) MoveUp() bool { func (w *Menu) MoveUp(ev *tcell.EventKey) bool {
if w.menuType != MenuTypeV { if w.menuType != MenuTypeV {
return false return false
} }
@@ -225,7 +226,7 @@ func (w *Menu) MoveUp() bool {
return true return true
} }
func (w *Menu) MoveDown() bool { func (w *Menu) MoveDown(ev *tcell.EventKey) bool {
if w.menuType != MenuTypeV { if w.menuType != MenuTypeV {
return false return false
} }

View File

@@ -68,7 +68,7 @@ func (w *Searcher) Init(id string, style tcell.Style) {
w.search.SetOnChange(func(prev, curr string) { w.search.SetOnChange(func(prev, curr string) {
w.updateFilter() w.updateFilter()
}) })
w.keyMap = NewKeyMap(map[tcell.Key]func() bool{ w.keyMap = NewKeyMap(map[tcell.Key]func(ev *tcell.EventKey) bool{
tcell.KeyUp: w.handleKeyUp, tcell.KeyUp: w.handleKeyUp,
tcell.KeyDown: w.handleKeyDown, tcell.KeyDown: w.handleKeyDown,
tcell.KeyHome: w.handleKeyHome, tcell.KeyHome: w.handleKeyHome,
@@ -90,17 +90,17 @@ func (w *Searcher) HandleKey(ev *tcell.EventKey) bool {
return w.search.HandleKey(ev) return w.search.HandleKey(ev)
} }
func (w *Searcher) handleKeyUp() bool { func (w *Searcher) handleKeyUp(ev *tcell.EventKey) bool {
w.cursor = ((w.cursor - 1) + len(w.filteredData)) % len(w.filteredData) w.cursor = ((w.cursor - 1) + len(w.filteredData)) % len(w.filteredData)
return true return true
} }
func (w *Searcher) handleKeyDown() bool { func (w *Searcher) handleKeyDown(ev *tcell.EventKey) bool {
w.cursor = ((w.cursor + 1) + len(w.filteredData)) % len(w.filteredData) w.cursor = ((w.cursor + 1) + len(w.filteredData)) % len(w.filteredData)
return true return true
} }
func (w *Searcher) handleKeyHome() bool { func (w *Searcher) handleKeyHome(ev *tcell.EventKey) bool {
if w.cursor == 0 { if w.cursor == 0 {
return false return false
} }
@@ -108,7 +108,7 @@ func (w *Searcher) handleKeyHome() bool {
return true return true
} }
func (w *Searcher) handleKeyEnd() bool { func (w *Searcher) handleKeyEnd(ev *tcell.EventKey) bool {
if w.cursor == len(w.filteredData)-1 { if w.cursor == len(w.filteredData)-1 {
return false return false
} }
@@ -116,7 +116,7 @@ func (w *Searcher) handleKeyEnd() bool {
return true return true
} }
func (w *Searcher) handleKeyPgUp() bool { func (w *Searcher) handleKeyPgUp(ev *tcell.EventKey) bool {
if w.cursor == 0 { if w.cursor == 0 {
return false return false
} }
@@ -128,7 +128,7 @@ func (w *Searcher) handleKeyPgUp() bool {
return false return false
} }
func (w *Searcher) handleKeyPgDn() bool { func (w *Searcher) handleKeyPgDn(ev *tcell.EventKey) bool {
mx := len(w.filteredData) - 1 mx := len(w.filteredData) - 1
if w.cursor == mx { if w.cursor == mx {
return false return false
@@ -140,7 +140,7 @@ func (w *Searcher) handleKeyPgDn() bool {
return false return false
} }
func (w *Searcher) handleKeyEnter() bool { func (w *Searcher) handleKeyEnter(ev *tcell.EventKey) bool {
if w.hideOnSelect { if w.hideOnSelect {
w.visible = false w.visible = false
} }