feat(ui/chat): add channels picker (quickswitcher) (#723)
This commit is contained in:
parent
314bad64cf
commit
3a4fd37bf6
12 changed files with 435 additions and 28 deletions
BIN
.github/preview.png
vendored
BIN
.github/preview.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.6 MiB |
|
|
@ -51,6 +51,11 @@ type (
|
|||
GuildStore string `toml:"guild_store"`
|
||||
}
|
||||
|
||||
PickerConfig struct {
|
||||
Width int `toml:"width"`
|
||||
Height int `toml:"height"`
|
||||
}
|
||||
|
||||
Config struct {
|
||||
AutoFocus bool `toml:"auto_focus"`
|
||||
Mouse bool `toml:"mouse"`
|
||||
|
|
@ -66,6 +71,7 @@ type (
|
|||
AutocompleteLimit uint8 `toml:"autocomplete_limit"`
|
||||
MessagesLimit uint8 `toml:"messages_limit"`
|
||||
|
||||
Picker PickerConfig `toml:"picker"`
|
||||
Timestamps Timestamps `toml:"timestamps"`
|
||||
Notifications Notifications `toml:"notifications"`
|
||||
TypingIndicator TypingIndicator `toml:"typing_indicator"`
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ autocomplete_limit = 20
|
|||
# The number of messages to fetch when a text-based channel is selected from guilds tree. The minimum and maximum value is 1 and 100, respectively.
|
||||
messages_limit = 50
|
||||
|
||||
[picker]
|
||||
width = 60
|
||||
height = 20
|
||||
|
||||
[timestamps]
|
||||
enabled = true
|
||||
# https://pkg.go.dev/time#Layout
|
||||
|
|
@ -69,6 +73,15 @@ quit = "Ctrl+C"
|
|||
# Requires re-login upon restart.
|
||||
logout = "Ctrl+D"
|
||||
|
||||
[keys.picker]
|
||||
toggle = "Ctrl+K"
|
||||
cancel = "Esc"
|
||||
up = "Ctrl+P"
|
||||
down = "Ctrl+N"
|
||||
top = "Home"
|
||||
bottom = "End"
|
||||
select = "Enter"
|
||||
|
||||
# Only while focusing on the guilds tree
|
||||
[keys.guilds_tree]
|
||||
select_previous = "Rune[k]"
|
||||
|
|
|
|||
|
|
@ -53,6 +53,20 @@ type (
|
|||
SelectionKeys
|
||||
}
|
||||
|
||||
NavigationKeys struct {
|
||||
Up string `toml:"up"`
|
||||
Down string `toml:"down"`
|
||||
Top string `toml:"top"`
|
||||
Bottom string `toml:"bottom"`
|
||||
}
|
||||
|
||||
PickerKeys struct {
|
||||
Toggle string `toml:"toggle"`
|
||||
Cancel string `toml:"cancel"`
|
||||
NavigationKeys
|
||||
Select string `toml:"select"`
|
||||
}
|
||||
|
||||
Keys struct {
|
||||
FocusGuildsTree string `toml:"focus_guilds_tree"`
|
||||
FocusMessagesList string `toml:"focus_messages_list"`
|
||||
|
|
@ -61,6 +75,7 @@ type (
|
|||
FocusNext string `toml:"focus_next"`
|
||||
ToggleGuildsTree string `toml:"toggle_guilds_tree"`
|
||||
|
||||
Picker PickerKeys `toml:"picker"`
|
||||
GuildsTree GuildsTreeKeys `toml:"guilds_tree"`
|
||||
MessagesList MessagesListKeys `toml:"messages_list"`
|
||||
MessageInput MessageInputKeys `toml:"message_input"`
|
||||
|
|
|
|||
118
internal/ui/chat/channels_picker.go
Normal file
118
internal/ui/chat/channels_picker.go
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
package chat
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"github.com/ayn2op/discordo/internal/config"
|
||||
"github.com/ayn2op/discordo/internal/ui"
|
||||
"github.com/ayn2op/discordo/pkg/picker"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/diamondburned/arikawa/v3/discord"
|
||||
)
|
||||
|
||||
type channelsPicker struct {
|
||||
*picker.Picker
|
||||
chatView *View
|
||||
}
|
||||
|
||||
func newChannelsPicker(cfg *config.Config, chatView *View) *channelsPicker {
|
||||
cp := &channelsPicker{picker.New(), chatView}
|
||||
cp.Box = ui.ConfigureBox(tview.NewBox(), &cfg.Theme)
|
||||
// When a child of tview.Flex is focused, tview.Flex itself is not reported as focused. Instead, the focused child (picker) is considered focused. Therefore, we manually set the active border style on the picker to ensure it displays the correct focused appearance.
|
||||
cp.
|
||||
SetBlurFunc(nil).
|
||||
SetFocusFunc(nil).
|
||||
SetBorderSet(cfg.Theme.Border.ActiveSet.BorderSet).
|
||||
SetBorderStyle(cfg.Theme.Border.ActiveStyle.Style).
|
||||
SetTitleStyle(cfg.Theme.Title.ActiveStyle.Style).
|
||||
SetFooterStyle(cfg.Theme.Footer.ActiveStyle.Style)
|
||||
|
||||
cp.SetSelectedFunc(cp.onSelected)
|
||||
cp.SetTitle("Channels")
|
||||
cp.SetKeyMap(&picker.KeyMap{
|
||||
Cancel: cfg.Keys.Picker.Cancel,
|
||||
Up: cfg.Keys.Picker.Up,
|
||||
Down: cfg.Keys.Picker.Down,
|
||||
Top: cfg.Keys.Picker.Top,
|
||||
Bottom: cfg.Keys.Picker.Bottom,
|
||||
Select: cfg.Keys.Picker.Select,
|
||||
})
|
||||
return cp
|
||||
}
|
||||
|
||||
func (cp *channelsPicker) onSelected(item picker.Item) {
|
||||
channelID, ok := item.Reference.(discord.ChannelID)
|
||||
if !ok || !channelID.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
channel, err := cp.chatView.state.Cabinet.Channel(channelID)
|
||||
if err != nil {
|
||||
slog.Error("failed to get channel from state", "err", err, "channel_id", channelID)
|
||||
return
|
||||
}
|
||||
|
||||
node := cp.chatView.guildsTree.findNodeByChannelID(channel.ID)
|
||||
if node == nil {
|
||||
slog.Error("failed to locate channel in tree", "channel_id", channel.ID)
|
||||
return
|
||||
}
|
||||
|
||||
cp.chatView.guildsTree.expandPathToNode(node)
|
||||
cp.chatView.guildsTree.SetCurrentNode(node)
|
||||
if channel.Type != discord.GuildCategory {
|
||||
cp.chatView.guildsTree.onSelected(node)
|
||||
}
|
||||
cp.chatView.closePicker()
|
||||
cp.chatView.focusMessageInput()
|
||||
}
|
||||
|
||||
func (cp *channelsPicker) update() {
|
||||
cp.ClearItems()
|
||||
state := cp.chatView.state
|
||||
|
||||
privateChannels, err := state.Cabinet.PrivateChannels()
|
||||
if err != nil {
|
||||
slog.Error("failed to get private channels from state", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
ui.SortPrivateChannels(privateChannels)
|
||||
for _, channel := range privateChannels {
|
||||
cp.addChannel(nil, channel)
|
||||
}
|
||||
|
||||
guilds, err := state.Cabinet.Guilds()
|
||||
if err != nil {
|
||||
slog.Error("failed to get guilds from state", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, guild := range guilds {
|
||||
channels, err := state.Cabinet.Channels(guild.ID)
|
||||
if err != nil {
|
||||
slog.Error("failed to get channels from state", "err", err, "guild_id", guild.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, channel := range channels {
|
||||
cp.addChannel(&guild, channel)
|
||||
}
|
||||
}
|
||||
|
||||
cp.Update()
|
||||
}
|
||||
|
||||
func (cp *channelsPicker) addChannel(guild *discord.Guild, channel discord.Channel) {
|
||||
var b strings.Builder
|
||||
b.WriteString(ui.ChannelToString(channel, cp.chatView.cfg.Icons))
|
||||
|
||||
if guild != nil {
|
||||
b.WriteString(" - ")
|
||||
b.WriteString(guild.Name)
|
||||
}
|
||||
|
||||
name := b.String()
|
||||
cp.AddItem(picker.Item{Text: name, FilterText: name, Reference: channel.ID})
|
||||
}
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
package chat
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"slices"
|
||||
|
||||
"github.com/ayn2op/discordo/internal/clipboard"
|
||||
"github.com/ayn2op/discordo/internal/config"
|
||||
|
|
@ -16,6 +14,8 @@ import (
|
|||
"github.com/gdamore/tcell/v3"
|
||||
)
|
||||
|
||||
type dmNode struct{}
|
||||
|
||||
type guildsTree struct {
|
||||
*tview.TreeView
|
||||
cfg *config.Config
|
||||
|
|
@ -159,15 +159,12 @@ func (gt *guildsTree) onSelected(node *tview.TreeNode) {
|
|||
return
|
||||
}
|
||||
|
||||
slices.SortFunc(channels, func(a, b discord.Channel) int {
|
||||
return cmp.Compare(a.Position, b.Position)
|
||||
})
|
||||
|
||||
ui.SortGuildChannels(channels)
|
||||
gt.createChannelNodes(node, channels)
|
||||
case discord.ChannelID:
|
||||
channel, err := gt.chatView.state.Cabinet.Channel(ref)
|
||||
if err != nil {
|
||||
slog.Error("failed to get channel", "channel_id", ref)
|
||||
slog.Error("failed to get channel from state", "channel_id", ref)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -194,9 +191,6 @@ func (gt *guildsTree) onSelected(node *tview.TreeNode) {
|
|||
for _, thread := range forumThreads {
|
||||
gt.createChannelNode(node, thread)
|
||||
}
|
||||
|
||||
// Expand the node to show threads
|
||||
node.SetExpanded(true)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -232,26 +226,14 @@ func (gt *guildsTree) onSelected(node *tview.TreeNode) {
|
|||
gt.chatView.app.SetFocus(gt.chatView.messageInput)
|
||||
}
|
||||
}
|
||||
|
||||
case nil: // Direct messages folder
|
||||
case dmNode: // Direct messages folder
|
||||
channels, err := gt.chatView.state.PrivateChannels()
|
||||
if err != nil {
|
||||
slog.Error("failed to get private channels", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
msgID := func(ch discord.Channel) discord.MessageID {
|
||||
if ch.LastMessageID.IsValid() {
|
||||
return ch.LastMessageID
|
||||
}
|
||||
return discord.MessageID(ch.ID)
|
||||
}
|
||||
|
||||
slices.SortFunc(channels, func(a, b discord.Channel) int {
|
||||
// Descending order
|
||||
return cmp.Compare(msgID(b), msgID(a))
|
||||
})
|
||||
|
||||
ui.SortPrivateChannels(channels)
|
||||
for _, c := range channels {
|
||||
gt.createChannelNode(node, c)
|
||||
}
|
||||
|
|
@ -313,3 +295,48 @@ func (gt *guildsTree) yankID() {
|
|||
go clipboard.Write(clipboard.FmtText, []byte(id.String()))
|
||||
}
|
||||
}
|
||||
|
||||
func (gt *guildsTree) findNodeByReference(reference any) *tview.TreeNode {
|
||||
var found *tview.TreeNode
|
||||
gt.GetRoot().Walk(func(node, _ *tview.TreeNode) bool {
|
||||
if node.GetReference() == reference {
|
||||
found = node
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
func (gt *guildsTree) findNodeByChannelID(channelID discord.ChannelID) *tview.TreeNode {
|
||||
channel, err := gt.chatView.state.Cabinet.Channel(channelID)
|
||||
if err != nil {
|
||||
slog.Error("failed to get channel", "channel_id", channelID, "err", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
var reference any
|
||||
if guildID := channel.GuildID; guildID.IsValid() {
|
||||
reference = guildID
|
||||
} else {
|
||||
reference = dmNode{}
|
||||
}
|
||||
if parentNode := gt.findNodeByReference(reference); parentNode != nil {
|
||||
if len(parentNode.GetChildren()) == 0 {
|
||||
gt.onSelected(parentNode)
|
||||
}
|
||||
}
|
||||
|
||||
node := gt.findNodeByReference(channelID)
|
||||
return node
|
||||
}
|
||||
|
||||
func (gt *guildsTree) expandPathToNode(node *tview.TreeNode) {
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
for _, n := range gt.GetPath(node) {
|
||||
n.Expand()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ func (v *View) onRaw(event *ws.RawEvent) {
|
|||
}
|
||||
|
||||
func (v *View) onReady(r *gateway.ReadyEvent) {
|
||||
dmNode := tview.NewTreeNode("Direct Messages")
|
||||
dmNode := tview.NewTreeNode("Direct Messages").SetReference(dmNode{})
|
||||
root := v.guildsTree.
|
||||
GetRoot().
|
||||
ClearChildren().
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ const (
|
|||
mentionsListPageName = "mentionsList"
|
||||
attachmentsListPageName = "attachmentsList"
|
||||
confirmModalPageName = "confirmModal"
|
||||
channelsPickerPageName = "channelsPicker"
|
||||
)
|
||||
|
||||
type View struct {
|
||||
|
|
@ -31,9 +32,10 @@ type View struct {
|
|||
mainFlex *tview.Flex
|
||||
rightFlex *tview.Flex
|
||||
|
||||
guildsTree *guildsTree
|
||||
messagesList *messagesList
|
||||
messageInput *messageInput
|
||||
guildsTree *guildsTree
|
||||
messagesList *messagesList
|
||||
messageInput *messageInput
|
||||
channelsPicker *channelsPicker
|
||||
|
||||
selectedChannel *discord.Channel
|
||||
selectedChannelMu sync.RWMutex
|
||||
|
|
@ -64,6 +66,8 @@ func NewView(app *tview.Application, cfg *config.Config, onLogout func()) *View
|
|||
v.guildsTree = newGuildsTree(cfg, v)
|
||||
v.messagesList = newMessagesList(cfg, v)
|
||||
v.messageInput = newMessageInput(cfg, v)
|
||||
v.channelsPicker = newChannelsPicker(cfg, v)
|
||||
v.channelsPicker.SetCancelFunc(v.closePicker)
|
||||
|
||||
v.SetInputCapture(v.onInputCapture)
|
||||
v.buildLayout()
|
||||
|
|
@ -99,6 +103,24 @@ func (v *View) buildLayout() {
|
|||
v.AddAndSwitchToPage(flexPageName, v.mainFlex, true)
|
||||
}
|
||||
|
||||
func (v *View) togglePicker() {
|
||||
if v.HasPage(channelsPickerPageName) {
|
||||
v.closePicker()
|
||||
} else {
|
||||
v.openPicker()
|
||||
}
|
||||
}
|
||||
|
||||
func (v *View) openPicker() {
|
||||
v.AddAndSwitchToPage(channelsPickerPageName, ui.Centered(v.channelsPicker, v.cfg.Picker.Width, v.cfg.Picker.Height), true).ShowPage(flexPageName)
|
||||
v.channelsPicker.update()
|
||||
}
|
||||
|
||||
func (v *View) closePicker() {
|
||||
v.RemovePage(channelsPickerPageName).SwitchToPage(flexPageName)
|
||||
v.channelsPicker.Update()
|
||||
}
|
||||
|
||||
func (v *View) toggleGuildsTree() {
|
||||
// The guilds tree is visible if the number of items is two.
|
||||
if v.mainFlex.GetItemCount() == 2 {
|
||||
|
|
@ -190,6 +212,9 @@ func (v *View) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
|||
case v.cfg.Keys.ToggleGuildsTree:
|
||||
v.toggleGuildsTree()
|
||||
return nil
|
||||
case v.cfg.Keys.Picker.Toggle:
|
||||
v.togglePicker()
|
||||
return nil
|
||||
}
|
||||
|
||||
return event
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/ayn2op/discordo/internal/config"
|
||||
|
|
@ -99,3 +101,23 @@ func ChannelToString(channel discord.Channel, icons config.Icons) string {
|
|||
|
||||
return icon + channel.Name
|
||||
}
|
||||
|
||||
func SortGuildChannels(channels []discord.Channel) {
|
||||
slices.SortFunc(channels, func(a, b discord.Channel) int {
|
||||
return cmp.Compare(a.Position, b.Position)
|
||||
})
|
||||
}
|
||||
|
||||
func SortPrivateChannels(channels []discord.Channel) {
|
||||
slices.SortFunc(channels, func(a, b discord.Channel) int {
|
||||
// Descending order
|
||||
return cmp.Compare(getMessageIDFromChannel(b), getMessageIDFromChannel(a))
|
||||
})
|
||||
}
|
||||
|
||||
func getMessageIDFromChannel(channel discord.Channel) discord.MessageID {
|
||||
if channel.LastMessageID.IsValid() {
|
||||
return channel.LastMessageID
|
||||
}
|
||||
return discord.MessageID(channel.ID)
|
||||
}
|
||||
|
|
|
|||
17
pkg/picker/item.go
Normal file
17
pkg/picker/item.go
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package picker
|
||||
|
||||
type Item struct {
|
||||
Text string
|
||||
FilterText string
|
||||
Reference any
|
||||
}
|
||||
|
||||
type Items []Item
|
||||
|
||||
func (is Items) String(index int) string {
|
||||
return is[index].FilterText
|
||||
}
|
||||
|
||||
func (is Items) Len() int {
|
||||
return len(is)
|
||||
}
|
||||
11
pkg/picker/keymap.go
Normal file
11
pkg/picker/keymap.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package picker
|
||||
|
||||
type KeyMap struct {
|
||||
Cancel string
|
||||
|
||||
Up string
|
||||
Down string
|
||||
Top string
|
||||
Bottom string
|
||||
Select string
|
||||
}
|
||||
153
pkg/picker/picker.go
Normal file
153
pkg/picker/picker.go
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
package picker
|
||||
|
||||
import (
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/gdamore/tcell/v3"
|
||||
"github.com/sahilm/fuzzy"
|
||||
)
|
||||
|
||||
type (
|
||||
SelectedFunc func(item Item)
|
||||
CancelFunc func()
|
||||
)
|
||||
|
||||
type Picker struct {
|
||||
*tview.Flex
|
||||
input *tview.InputField
|
||||
list *tview.List
|
||||
|
||||
onSelected SelectedFunc
|
||||
onCancel CancelFunc
|
||||
keyMap *KeyMap
|
||||
|
||||
items Items
|
||||
filtered Items
|
||||
}
|
||||
|
||||
func New() *Picker {
|
||||
p := &Picker{
|
||||
Flex: tview.NewFlex(),
|
||||
input: tview.NewInputField(),
|
||||
list: tview.NewList(),
|
||||
}
|
||||
|
||||
// Show a horizontal bottom border to visually separate input from list.
|
||||
borderSet := tview.BorderSet{
|
||||
Bottom: tview.BoxDrawingsLightHorizontal,
|
||||
}
|
||||
borderSet.BottomLeft = borderSet.Bottom
|
||||
borderSet.BottomRight = borderSet.Bottom
|
||||
p.input.
|
||||
SetChangedFunc(p.onInputChanged).
|
||||
SetLabel("> ").
|
||||
SetBorders(tview.BordersBottom).
|
||||
SetBorderSet(borderSet).
|
||||
SetBorderStyle(tcell.StyleDefault.Dim(true)).
|
||||
SetInputCapture(p.onInputCapture)
|
||||
|
||||
p.list.
|
||||
SetSelectedFunc(p.onListSelected).
|
||||
ShowSecondaryText(false).
|
||||
SetHighlightFullLine(true)
|
||||
|
||||
p.
|
||||
SetDirection(tview.FlexRow).
|
||||
// bottom border + value
|
||||
AddItem(p.input, 2, 0, true).
|
||||
AddItem(p.list, 0, 1, false)
|
||||
|
||||
p.Update()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Picker) SetKeyMap(keyMap *KeyMap) {
|
||||
p.keyMap = keyMap
|
||||
}
|
||||
|
||||
func (p *Picker) SetSelectedFunc(onSelected SelectedFunc) {
|
||||
p.onSelected = onSelected
|
||||
}
|
||||
|
||||
func (p *Picker) SetCancelFunc(onCancel CancelFunc) {
|
||||
p.onCancel = onCancel
|
||||
}
|
||||
|
||||
func (p *Picker) ClearInput() {
|
||||
p.input.SetText("")
|
||||
}
|
||||
|
||||
func (p *Picker) ClearList() {
|
||||
p.list.Clear()
|
||||
}
|
||||
|
||||
func (p *Picker) ClearItems() {
|
||||
p.items = nil
|
||||
p.filtered = nil
|
||||
}
|
||||
|
||||
func (p *Picker) AddItem(item Item) {
|
||||
p.items = append(p.items, item)
|
||||
}
|
||||
|
||||
func (p *Picker) Update() {
|
||||
p.ClearInput()
|
||||
p.onInputChanged("")
|
||||
}
|
||||
|
||||
func (p *Picker) onListSelected(index int, text, _ string, _ rune) {
|
||||
if p.onSelected != nil {
|
||||
if index >= 0 && index < len(p.filtered) {
|
||||
item := p.filtered[index]
|
||||
p.onSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Picker) onInputChanged(text string) {
|
||||
var fuzzied Items
|
||||
if text == "" {
|
||||
fuzzied = append(fuzzied, p.items...)
|
||||
} else {
|
||||
matches := fuzzy.FindFrom(text, p.items)
|
||||
for _, match := range matches {
|
||||
fuzzied = append(fuzzied, p.items[match.Index])
|
||||
}
|
||||
}
|
||||
p.filtered = fuzzied
|
||||
|
||||
p.ClearList()
|
||||
for _, item := range fuzzied {
|
||||
p.list.AddItem(item.Text, "", 0, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Picker) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
||||
if p.keyMap == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
handler := p.list.InputHandler()
|
||||
switch event.Name() {
|
||||
case p.keyMap.Up:
|
||||
handler(tcell.NewEventKey(tcell.KeyUp, "", tcell.ModNone), nil)
|
||||
return nil
|
||||
case p.keyMap.Down:
|
||||
handler(tcell.NewEventKey(tcell.KeyDown, "", tcell.ModNone), nil)
|
||||
return nil
|
||||
case p.keyMap.Top:
|
||||
handler(tcell.NewEventKey(tcell.KeyHome, "", tcell.ModNone), nil)
|
||||
return nil
|
||||
case p.keyMap.Bottom:
|
||||
handler(tcell.NewEventKey(tcell.KeyEnd, "", tcell.ModNone), nil)
|
||||
case p.keyMap.Select:
|
||||
handler(tcell.NewEventKey(tcell.KeyEnter, "", tcell.ModNone), nil)
|
||||
|
||||
case p.keyMap.Cancel:
|
||||
if p.onCancel != nil {
|
||||
p.onCancel()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return event
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue