feat: add help bar (#750)
This commit is contained in:
parent
dda87c5bba
commit
20ef1a7be2
16 changed files with 751 additions and 219 deletions
4
go.mod
4
go.mod
|
|
@ -8,9 +8,9 @@ require (
|
|||
github.com/BurntSushi/toml v1.6.0
|
||||
github.com/alecthomas/chroma/v2 v2.23.1
|
||||
github.com/andybalholm/brotli v1.2.0
|
||||
github.com/ayn2op/tview v0.0.0-20260217063213-aa3df80a3074
|
||||
github.com/ayn2op/tview v0.0.0-20260223065535-6a0c066d0bdd
|
||||
github.com/deckarep/gosx-notifier v0.0.0-20180201035817-e127226297fb
|
||||
github.com/diamondburned/arikawa/v3 v3.6.1-0.20260221035217-15298f718931
|
||||
github.com/diamondburned/arikawa/v3 v3.6.1-0.20260221051847-b81b70d1a5cb
|
||||
github.com/diamondburned/ningen/v3 v3.0.1-0.20250920191746-98fbd92e134d
|
||||
github.com/gdamore/tcell/v3 v3.1.2
|
||||
github.com/gen2brain/beeep v0.11.2
|
||||
|
|
|
|||
8
go.sum
8
go.sum
|
|
@ -14,8 +14,8 @@ github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs
|
|||
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/ayn2op/tview v0.0.0-20260217063213-aa3df80a3074 h1:16GcP8SZ7SnHvZEMu1EPxPjAlb/rwAgt556blNFdIiQ=
|
||||
github.com/ayn2op/tview v0.0.0-20260217063213-aa3df80a3074/go.mod h1:lZ8RdOegQWBQafTOasGE7Ps1/Ymy4jmXoPt5vz2QsS0=
|
||||
github.com/ayn2op/tview v0.0.0-20260223065535-6a0c066d0bdd h1:DdR19f4xNbd3XM9vxmC9LtF5XY1nv0rDMMzn0FSTwd0=
|
||||
github.com/ayn2op/tview v0.0.0-20260223065535-6a0c066d0bdd/go.mod h1:lZ8RdOegQWBQafTOasGE7Ps1/Ymy4jmXoPt5vz2QsS0=
|
||||
github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ=
|
||||
github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
|
@ -25,8 +25,8 @@ github.com/dchest/jsmin v1.0.0 h1:Y2hWXmGZiRxtl+VcTksyucgTlYxnhPzTozCwx9gy9zI=
|
|||
github.com/dchest/jsmin v1.0.0/go.mod h1:AVBIund7Mr7lKXT70hKT2YgL3XEXUaUk5iw9DZ8b0Uc=
|
||||
github.com/deckarep/gosx-notifier v0.0.0-20180201035817-e127226297fb h1:6S+TKObz6+Io2c8IOkcbK4Sz7nj6RpEVU7TkvmsZZcw=
|
||||
github.com/deckarep/gosx-notifier v0.0.0-20180201035817-e127226297fb/go.mod h1:wf3nKtOnQqCp7kp9xB7hHnNlZ6m3NoiOxjrB9hFRq4Y=
|
||||
github.com/diamondburned/arikawa/v3 v3.6.1-0.20260221035217-15298f718931 h1:7QLI6WfhnmvCzsU11+39i8IUoHyt7T7CtDjXInKLCIo=
|
||||
github.com/diamondburned/arikawa/v3 v3.6.1-0.20260221035217-15298f718931/go.mod h1:TpV2GvCJIYSwAXUEAx4sutPcftyAbVidiFlG6l7K0go=
|
||||
github.com/diamondburned/arikawa/v3 v3.6.1-0.20260221051847-b81b70d1a5cb h1:sBcyty++NvgR6SiAV5CuiTW3AKiAdtJ1NWE7jovZnUc=
|
||||
github.com/diamondburned/arikawa/v3 v3.6.1-0.20260221051847-b81b70d1a5cb/go.mod h1:TpV2GvCJIYSwAXUEAx4sutPcftyAbVidiFlG6l7K0go=
|
||||
github.com/diamondburned/ningen/v3 v3.0.1-0.20250920191746-98fbd92e134d h1:wS6HWl86RFItw38sUSdXlulj0VarV4G+9Rlv8j+p3oQ=
|
||||
github.com/diamondburned/ningen/v3 v3.0.1-0.20250920191746-98fbd92e134d/go.mod h1:9PWnJlArEASrZQI89KQiBJBYOEDcsDyfhFcxRTL8eO4=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/ayn2op/discordo/internal/ui/chat"
|
||||
"github.com/ayn2op/discordo/internal/ui/login"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/keybind"
|
||||
"github.com/gdamore/tcell/v3"
|
||||
)
|
||||
|
||||
|
|
@ -100,11 +101,11 @@ func (a *App) quit() {
|
|||
}
|
||||
|
||||
func (a *App) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
||||
switch event.Name() {
|
||||
case a.cfg.Keybinds.Quit:
|
||||
switch {
|
||||
case keybind.Matches(event, a.cfg.Keybinds.Quit.Keybind):
|
||||
a.quit()
|
||||
return nil
|
||||
case "Ctrl+C":
|
||||
case keybind.Matches(event, keybind.NewKeybind(keybind.WithKeys("ctrl+c"))):
|
||||
// https://github.com/ayn2op/tview/blob/a64fc48d7654432f71922c8b908280cdb525805c/application.go#L153
|
||||
return tcell.NewEventKey(tcell.KeyCtrlC, "", tcell.ModNone)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,12 @@ type (
|
|||
Theme string `toml:"theme"`
|
||||
}
|
||||
|
||||
HelpConfig struct {
|
||||
CompactModifiers bool `toml:"compact_modifiers"`
|
||||
Padding [2]int `toml:"padding"`
|
||||
Separator string `toml:"separator"`
|
||||
}
|
||||
|
||||
Config struct {
|
||||
AutoFocus bool `toml:"auto_focus"`
|
||||
Mouse bool `toml:"mouse"`
|
||||
|
|
@ -82,6 +88,7 @@ type (
|
|||
MessagesLimit uint8 `toml:"messages_limit"`
|
||||
|
||||
Markdown MarkdownConfig `toml:"markdown"`
|
||||
Help HelpConfig `toml:"help"`
|
||||
Picker PickerConfig `toml:"picker"`
|
||||
Timestamps Timestamps `toml:"timestamps"`
|
||||
DateSeparator DateSeparator `toml:"date_separator"`
|
||||
|
|
@ -113,7 +120,9 @@ func DefaultPath() string {
|
|||
|
||||
// Load reads the configuration file and parses it.
|
||||
func Load(path string) (*Config, error) {
|
||||
var cfg Config
|
||||
cfg := Config{
|
||||
Keybinds: defaultKeybinds(),
|
||||
}
|
||||
if err := toml.Unmarshal(defaultCfg, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal default config: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,13 @@ enabled = true
|
|||
# Theme for fenced code blocks. Available themes: https://xyproto.github.io/splash/docs
|
||||
theme = "monokai"
|
||||
|
||||
[help]
|
||||
# Show compact key modifiers in help, e.g. "^x" instead of "ctrl+x".
|
||||
compact_modifiers = true
|
||||
# [left, right]
|
||||
padding = [1, 1]
|
||||
separator = " • "
|
||||
|
||||
[picker]
|
||||
width = 60
|
||||
height = 20
|
||||
|
|
@ -71,88 +78,89 @@ guild_store = "s-"
|
|||
# Global shortcuts
|
||||
# Esc: Reset message selection or close the channel selection popup.
|
||||
[keybinds]
|
||||
focus_guilds_tree = "Ctrl+G"
|
||||
focus_messages_list = "Ctrl+T"
|
||||
focus_message_input = "Ctrl+I"
|
||||
toggle_help = "ctrl+."
|
||||
focus_guilds_tree = "ctrl+g"
|
||||
focus_messages_list = "ctrl+t"
|
||||
focus_message_input = "ctrl+i"
|
||||
# Cycle focus between the widgets.
|
||||
focus_previous = "Ctrl+H"
|
||||
focus_next = "Ctrl+L"
|
||||
focus_previous = "ctrl+h"
|
||||
focus_next = "ctrl+l"
|
||||
# Hide/show the guilds tree.
|
||||
toggle_guilds_tree = "Ctrl+B"
|
||||
quit = "Ctrl+C"
|
||||
toggle_guilds_tree = "ctrl+b"
|
||||
quit = "ctrl+c"
|
||||
# Log out and remove the authentication token from keyring.
|
||||
# Requires re-login upon restart.
|
||||
logout = "Ctrl+D"
|
||||
logout = "ctrl+d"
|
||||
|
||||
[keybinds.picker]
|
||||
toggle = "Ctrl+K"
|
||||
cancel = "Esc"
|
||||
up = "Ctrl+P"
|
||||
down = "Ctrl+N"
|
||||
top = "Home"
|
||||
bottom = "End"
|
||||
select = "Enter"
|
||||
toggle = "ctrl+k"
|
||||
cancel = "esc"
|
||||
up = "ctrl+p"
|
||||
down = "ctrl+n"
|
||||
top = "home"
|
||||
bottom = "end"
|
||||
select = "enter"
|
||||
|
||||
# Only while focusing on the guilds tree
|
||||
[keybinds.guilds_tree]
|
||||
up = "Rune[k]"
|
||||
down = "Rune[j]"
|
||||
top = "Rune[g]"
|
||||
bottom = "Rune[G]"
|
||||
up = "k"
|
||||
down = "j"
|
||||
top = "g"
|
||||
bottom = "G"
|
||||
# Select the currently highlighted text-based channel or expand a guild or channel.
|
||||
select_current = "Enter"
|
||||
yank_id = "Rune[i]"
|
||||
collapse_parent_node = "Rune[-]"
|
||||
move_to_parent_node = "Rune[p]"
|
||||
select_current = "enter"
|
||||
yank_id = "i"
|
||||
collapse_parent_node = "-"
|
||||
move_to_parent_node = "p"
|
||||
|
||||
# Only while focusing on sent messages
|
||||
[keybinds.messages_list]
|
||||
select_up = "Rune[k]"
|
||||
select_down = "Rune[j]"
|
||||
select_top = "Rune[g]"
|
||||
select_bottom = "Rune[G]"
|
||||
select_up = "k"
|
||||
select_down = "j"
|
||||
select_top = "g"
|
||||
select_bottom = "G"
|
||||
# Scroll the messages list without changing the selection.
|
||||
scroll_up = "Rune[K]"
|
||||
scroll_down = "Rune[J]"
|
||||
scroll_top = "Home"
|
||||
scroll_bottom = "End"
|
||||
scroll_up = "K"
|
||||
scroll_down = "J"
|
||||
scroll_top = "home"
|
||||
scroll_bottom = "end"
|
||||
# Select the message reference (reply) of the selected channel.
|
||||
select_reply = "Rune[s]"
|
||||
select_reply = "s"
|
||||
# Reply to the selected message.
|
||||
reply = "Rune[R]"
|
||||
reply = "R"
|
||||
# Reply (with mention) to the selected message.
|
||||
reply_mention = "Rune[r]"
|
||||
cancel = "Esc"
|
||||
edit = "Rune[e]"
|
||||
delete = "Rune[D]"
|
||||
delete_confirm = "Rune[d]"
|
||||
reply_mention = "r"
|
||||
cancel = "esc"
|
||||
edit = "e"
|
||||
delete = "D"
|
||||
delete_confirm = "d"
|
||||
# Open the selected message's attachments or hyperlinks in the message
|
||||
# using the default browser application.
|
||||
open = "Rune[o]"
|
||||
open = "o"
|
||||
# Yank (copy) the selected message's content/url/id.
|
||||
yank_content = "Rune[y]"
|
||||
yank_url = "Rune[u]"
|
||||
yank_id = "Rune[i]"
|
||||
yank_content = "y"
|
||||
yank_url = "u"
|
||||
yank_id = "i"
|
||||
|
||||
# Only while typing a message
|
||||
# Alt+Enter: Insert a new line to the current text.
|
||||
[keybinds.message_input]
|
||||
# paste from clipboard (supports both text and images)
|
||||
paste = "Ctrl+V"
|
||||
send = "Enter"
|
||||
paste = "ctrl+v"
|
||||
send = "enter"
|
||||
# Remove existing text or cancel reply.
|
||||
cancel = "Esc"
|
||||
cancel = "esc"
|
||||
# Complete usernames when mentioning
|
||||
tab_complete = "Tab"
|
||||
tab_complete = "tab"
|
||||
|
||||
open_editor = "Ctrl+E"
|
||||
open_file_picker = "Ctrl+Rune[\\]"
|
||||
open_editor = "ctrl+e"
|
||||
open_file_picker = "ctrl+\\"
|
||||
|
||||
[keybinds.mentions_list]
|
||||
up = "Ctrl+P"
|
||||
down = "Ctrl+N"
|
||||
top = "Home"
|
||||
bottom = "End"
|
||||
up = "ctrl+p"
|
||||
down = "ctrl+n"
|
||||
top = "home"
|
||||
bottom = "end"
|
||||
|
||||
# style = { foreground = "", background = "", attributes = "" or [""] }
|
||||
[theme.title]
|
||||
|
|
@ -213,3 +221,9 @@ max_height = 0
|
|||
style = {}
|
||||
# Background style: everything else behind the dialog
|
||||
background_style = { attributes = "dim" }
|
||||
|
||||
[theme.help]
|
||||
short_key_style = { attributes = "dim" }
|
||||
short_desc_style = {}
|
||||
full_key_style = { attributes = "dim" }
|
||||
full_desc_style = {}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/ayn2op/discordo/internal/consts"
|
||||
tviewkeybind "github.com/ayn2op/tview/keybind"
|
||||
"github.com/gdamore/tcell/v3"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
|
@ -84,7 +85,12 @@ func TestLoad(t *testing.T) {
|
|||
}
|
||||
applyDefaults(&defCfg)
|
||||
|
||||
if diff := cmp.Diff(defCfg, *cfg, cmpopts.EquateComparable(tcell.Style{})); diff != "" {
|
||||
if diff := cmp.Diff(
|
||||
defCfg,
|
||||
*cfg,
|
||||
cmpopts.EquateComparable(tcell.Style{}),
|
||||
cmpopts.IgnoreUnexported(tviewkeybind.Keybind{}),
|
||||
); diff != "" {
|
||||
t.Fatalf("got = -, want = +, diff=%s", diff)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,69 +1,104 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/ayn2op/tview/keybind"
|
||||
)
|
||||
|
||||
type Keybind struct {
|
||||
keybind.Keybind
|
||||
}
|
||||
|
||||
var _ toml.Unmarshaler = (*Keybind)(nil)
|
||||
|
||||
func (k *Keybind) UnmarshalTOML(value any) error {
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
k.SetKeys(value)
|
||||
case []any:
|
||||
keys := make([]string, 0, len(value))
|
||||
for _, key := range value {
|
||||
if key, ok := key.(string); ok {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
k.SetKeys(keys...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newKeybind(key, desc string) Keybind {
|
||||
return Keybind{
|
||||
Keybind: keybind.NewKeybind(
|
||||
keybind.WithKeys(key),
|
||||
keybind.WithHelp(key, desc),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
type NavigationKeybinds struct {
|
||||
Up string `toml:"up"`
|
||||
Down string `toml:"down"`
|
||||
Top string `toml:"top"`
|
||||
Bottom string `toml:"bottom"`
|
||||
Up Keybind `toml:"up"`
|
||||
Down Keybind `toml:"down"`
|
||||
Top Keybind `toml:"top"`
|
||||
Bottom Keybind `toml:"bottom"`
|
||||
}
|
||||
|
||||
type ScrollKeybinds struct {
|
||||
ScrollUp string `toml:"scroll_up"`
|
||||
ScrollDown string `toml:"scroll_down"`
|
||||
ScrollTop string `toml:"scroll_top"`
|
||||
ScrollBottom string `toml:"scroll_bottom"`
|
||||
ScrollUp Keybind `toml:"scroll_up"`
|
||||
ScrollDown Keybind `toml:"scroll_down"`
|
||||
ScrollTop Keybind `toml:"scroll_top"`
|
||||
ScrollBottom Keybind `toml:"scroll_bottom"`
|
||||
}
|
||||
|
||||
type SelectionKeybinds struct {
|
||||
SelectUp string `toml:"select_up"`
|
||||
SelectDown string `toml:"select_down"`
|
||||
SelectTop string `toml:"select_top"`
|
||||
SelectBottom string `toml:"select_bottom"`
|
||||
SelectUp Keybind `toml:"select_up"`
|
||||
SelectDown Keybind `toml:"select_down"`
|
||||
SelectTop Keybind `toml:"select_top"`
|
||||
SelectBottom Keybind `toml:"select_bottom"`
|
||||
}
|
||||
|
||||
type PickerKeybinds struct {
|
||||
NavigationKeybinds
|
||||
Toggle string `toml:"toggle"`
|
||||
Cancel string `toml:"cancel"`
|
||||
Select string `toml:"select"`
|
||||
Select Keybind `toml:"select"`
|
||||
Cancel Keybind `toml:"cancel"`
|
||||
}
|
||||
|
||||
type GuildsTreeKeybinds struct {
|
||||
NavigationKeybinds
|
||||
SelectCurrent string `toml:"select_current"`
|
||||
YankID string `toml:"yank_id"`
|
||||
SelectCurrent Keybind `toml:"select_current"`
|
||||
YankID Keybind `toml:"yank_id"`
|
||||
|
||||
CollapseParentNode string `toml:"collapse_parent_node"`
|
||||
MoveToParentNode string `toml:"move_to_parent_node"`
|
||||
CollapseParentNode Keybind `toml:"collapse_parent_node"`
|
||||
MoveToParentNode Keybind `toml:"move_to_parent_node"`
|
||||
}
|
||||
|
||||
type MessagesListKeybinds struct {
|
||||
SelectionKeybinds
|
||||
ScrollKeybinds
|
||||
|
||||
SelectReply string `toml:"select_reply"`
|
||||
Reply string `toml:"reply"`
|
||||
ReplyMention string `toml:"reply_mention"`
|
||||
SelectReply Keybind `toml:"select_reply"`
|
||||
Reply Keybind `toml:"reply"`
|
||||
ReplyMention Keybind `toml:"reply_mention"`
|
||||
|
||||
Cancel string `toml:"cancel"`
|
||||
Edit string `toml:"edit"`
|
||||
Delete string `toml:"delete"`
|
||||
DeleteConfirm string `toml:"delete_confirm"`
|
||||
Open string `toml:"open"`
|
||||
Cancel Keybind `toml:"cancel"`
|
||||
Edit Keybind `toml:"edit"`
|
||||
Delete Keybind `toml:"delete"`
|
||||
DeleteConfirm Keybind `toml:"delete_confirm"`
|
||||
Open Keybind `toml:"open"`
|
||||
|
||||
YankContent string `toml:"yank_content"`
|
||||
YankURL string `toml:"yank_url"`
|
||||
YankID string `toml:"yank_id"`
|
||||
YankContent Keybind `toml:"yank_content"`
|
||||
YankURL Keybind `toml:"yank_url"`
|
||||
YankID Keybind `toml:"yank_id"`
|
||||
}
|
||||
|
||||
type MessageInputKeybinds struct {
|
||||
Paste string `toml:"paste"`
|
||||
Send string `toml:"send"`
|
||||
Cancel string `toml:"cancel"`
|
||||
TabComplete string `toml:"tab_complete"`
|
||||
Paste Keybind `toml:"paste"`
|
||||
Send Keybind `toml:"send"`
|
||||
Cancel Keybind `toml:"cancel"`
|
||||
TabComplete Keybind `toml:"tab_complete"`
|
||||
|
||||
OpenEditor string `toml:"open_editor"`
|
||||
OpenFilePicker string `toml:"open_file_picker"`
|
||||
OpenEditor Keybind `toml:"open_editor"`
|
||||
OpenFilePicker Keybind `toml:"open_file_picker"`
|
||||
}
|
||||
|
||||
type MentionsListKeybinds struct {
|
||||
|
|
@ -71,12 +106,16 @@ type MentionsListKeybinds struct {
|
|||
}
|
||||
|
||||
type Keybinds struct {
|
||||
FocusGuildsTree string `toml:"focus_guilds_tree"`
|
||||
FocusMessagesList string `toml:"focus_messages_list"`
|
||||
FocusMessageInput string `toml:"focus_message_input"`
|
||||
FocusPrevious string `toml:"focus_previous"`
|
||||
FocusNext string `toml:"focus_next"`
|
||||
ToggleGuildsTree string `toml:"toggle_guilds_tree"`
|
||||
ToggleGuildsTree Keybind `toml:"toggle_guilds_tree"`
|
||||
ToggleChannelsPicker Keybind `toml:"toggle_channels_picker"`
|
||||
ToggleHelp Keybind `toml:"toggle_help"`
|
||||
|
||||
FocusGuildsTree Keybind `toml:"focus_guilds_tree"`
|
||||
FocusMessagesList Keybind `toml:"focus_messages_list"`
|
||||
FocusMessageInput Keybind `toml:"focus_message_input"`
|
||||
|
||||
FocusPrevious Keybind `toml:"focus_previous"`
|
||||
FocusNext Keybind `toml:"focus_next"`
|
||||
|
||||
Picker PickerKeybinds `toml:"picker"`
|
||||
GuildsTree GuildsTreeKeybinds `toml:"guilds_tree"`
|
||||
|
|
@ -84,6 +123,91 @@ type Keybinds struct {
|
|||
MessageInput MessageInputKeybinds `toml:"message_input"`
|
||||
MentionsList MentionsListKeybinds `toml:"mentions_list"`
|
||||
|
||||
Logout string `toml:"logout"`
|
||||
Quit string `toml:"quit"`
|
||||
Logout Keybind `toml:"logout"`
|
||||
Quit Keybind `toml:"quit"`
|
||||
}
|
||||
|
||||
func defaultKeybinds() Keybinds {
|
||||
return Keybinds{
|
||||
ToggleGuildsTree: newKeybind("ctrl+b", "toggle guilds"),
|
||||
ToggleChannelsPicker: newKeybind("ctrl+k", "channels picker"),
|
||||
ToggleHelp: newKeybind("ctrl+.", "help"),
|
||||
|
||||
FocusGuildsTree: newKeybind("ctrl+g", "guilds"),
|
||||
FocusMessagesList: newKeybind("ctrl+t", "messages"),
|
||||
FocusMessageInput: newKeybind("ctrl+i", "input"),
|
||||
|
||||
FocusPrevious: newKeybind("ctrl+h", "focus prev"),
|
||||
FocusNext: newKeybind("ctrl+l", "focus next"),
|
||||
|
||||
Logout: newKeybind("ctrl+d", "logout"),
|
||||
Quit: newKeybind("ctrl+c", "quit"),
|
||||
|
||||
Picker: PickerKeybinds{
|
||||
NavigationKeybinds: NavigationKeybinds{
|
||||
Up: newKeybind("ctrl+p", "up"),
|
||||
Down: newKeybind("ctrl+n", "down"),
|
||||
Top: newKeybind("home", "top"),
|
||||
Bottom: newKeybind("end", "bottom"),
|
||||
},
|
||||
Cancel: newKeybind("esc", "cancel"),
|
||||
Select: newKeybind("enter", "sel"),
|
||||
},
|
||||
GuildsTree: GuildsTreeKeybinds{
|
||||
NavigationKeybinds: NavigationKeybinds{
|
||||
Up: newKeybind("k", "up"),
|
||||
Down: newKeybind("j", "down"),
|
||||
Top: newKeybind("g", "top"),
|
||||
Bottom: newKeybind("G", "bottom"),
|
||||
},
|
||||
SelectCurrent: newKeybind("enter", "sel"),
|
||||
YankID: newKeybind("i", "copy id"),
|
||||
CollapseParentNode: newKeybind("-", "collapse"),
|
||||
MoveToParentNode: newKeybind("p", "parent"),
|
||||
},
|
||||
MessagesList: MessagesListKeybinds{
|
||||
SelectionKeybinds: SelectionKeybinds{
|
||||
SelectUp: newKeybind("k", "up"),
|
||||
SelectDown: newKeybind("j", "down"),
|
||||
SelectTop: newKeybind("g", "top"),
|
||||
SelectBottom: newKeybind("G", "bottom"),
|
||||
},
|
||||
ScrollKeybinds: ScrollKeybinds{
|
||||
ScrollUp: newKeybind("K", "scroll up"),
|
||||
ScrollDown: newKeybind("J", "scroll down"),
|
||||
ScrollTop: newKeybind("home", "scroll top"),
|
||||
ScrollBottom: newKeybind("end", "scroll bottom"),
|
||||
},
|
||||
SelectReply: newKeybind("s", "sel reply"),
|
||||
Reply: newKeybind("R", "reply"),
|
||||
ReplyMention: newKeybind("r", "@reply"),
|
||||
Cancel: newKeybind("esc", "cancel"),
|
||||
Edit: newKeybind("e", "edit"),
|
||||
Delete: newKeybind("D", "force delete"),
|
||||
DeleteConfirm: newKeybind(
|
||||
"d",
|
||||
"delete",
|
||||
),
|
||||
Open: newKeybind("o", "open"),
|
||||
YankContent: newKeybind("y", "copy text"),
|
||||
YankURL: newKeybind("u", "copy url"),
|
||||
YankID: newKeybind("i", "copy id"),
|
||||
},
|
||||
MessageInput: MessageInputKeybinds{
|
||||
Paste: newKeybind("ctrl+v", "paste"),
|
||||
Send: newKeybind("enter", "send"),
|
||||
Cancel: newKeybind("esc", "cancel"),
|
||||
TabComplete: newKeybind("tab", "complete"),
|
||||
OpenEditor: newKeybind("ctrl+e", "editor"),
|
||||
OpenFilePicker: newKeybind("ctrl+\\", "attach"),
|
||||
},
|
||||
MentionsList: MentionsListKeybinds{
|
||||
NavigationKeybinds: NavigationKeybinds{
|
||||
Up: newKeybind("ctrl+p", "up"),
|
||||
Down: newKeybind("ctrl+n", "down"),
|
||||
Top: newKeybind("home", "top"),
|
||||
Bottom: newKeybind("end", "bottom"),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,6 +147,13 @@ func (vw *ScrollBarVisibilityWrapper) UnmarshalTOML(val any) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type HelpTheme struct {
|
||||
ShortKeyStyle StyleWrapper `toml:"short_key_style"`
|
||||
ShortDescStyle StyleWrapper `toml:"short_desc_style"`
|
||||
FullKeyStyle StyleWrapper `toml:"full_key_style"`
|
||||
FullDescStyle StyleWrapper `toml:"full_desc_style"`
|
||||
}
|
||||
|
||||
type (
|
||||
ThemeStyle struct {
|
||||
NormalStyle StyleWrapper `toml:"normal_style"`
|
||||
|
|
@ -217,6 +224,7 @@ type (
|
|||
MessagesList MessagesListTheme `toml:"messages_list"`
|
||||
MentionsList MentionsListTheme `toml:"mentions_list"`
|
||||
Dialog DialogTheme `toml:"dialog"`
|
||||
Help HelpTheme `toml:"help"`
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import (
|
|||
"github.com/ayn2op/discordo/internal/ui"
|
||||
"github.com/ayn2op/discordo/pkg/picker"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/help"
|
||||
"github.com/ayn2op/tview/keybind"
|
||||
"github.com/diamondburned/arikawa/v3/discord"
|
||||
)
|
||||
|
||||
|
|
@ -16,6 +18,8 @@ type channelsPicker struct {
|
|||
chatView *View
|
||||
}
|
||||
|
||||
var _ help.KeyMap = (*channelsPicker)(nil)
|
||||
|
||||
func newChannelsPicker(cfg *config.Config, chatView *View) *channelsPicker {
|
||||
cp := &channelsPicker{picker.New(), chatView}
|
||||
cp.Box = ui.ConfigureBox(tview.NewBox(), &cfg.Theme)
|
||||
|
|
@ -36,12 +40,12 @@ func newChannelsPicker(cfg *config.Config, chatView *View) *channelsPicker {
|
|||
SetThumbStyle(cfg.Theme.ScrollBar.ThumbStyle.Style).
|
||||
SetGlyphSet(cfg.Theme.ScrollBar.GlyphSet.GlyphSet))
|
||||
cp.SetKeyMap(&picker.KeyMap{
|
||||
Cancel: cfg.Keybinds.Picker.Cancel,
|
||||
Up: cfg.Keybinds.Picker.Up,
|
||||
Down: cfg.Keybinds.Picker.Down,
|
||||
Top: cfg.Keybinds.Picker.Top,
|
||||
Bottom: cfg.Keybinds.Picker.Bottom,
|
||||
Select: cfg.Keybinds.Picker.Select,
|
||||
Cancel: cfg.Keybinds.Picker.Cancel.Keybind,
|
||||
Up: cfg.Keybinds.Picker.Up.Keybind,
|
||||
Down: cfg.Keybinds.Picker.Down.Keybind,
|
||||
Top: cfg.Keybinds.Picker.Top.Keybind,
|
||||
Bottom: cfg.Keybinds.Picker.Bottom.Keybind,
|
||||
Select: cfg.Keybinds.Picker.Select.Keybind,
|
||||
})
|
||||
return cp
|
||||
}
|
||||
|
|
@ -121,3 +125,16 @@ func (cp *channelsPicker) addChannel(guild *discord.Guild, channel discord.Chann
|
|||
name := b.String()
|
||||
cp.AddItem(picker.Item{Text: name, FilterText: name, Reference: channel.ID})
|
||||
}
|
||||
|
||||
func (cp *channelsPicker) ShortHelp() []keybind.Keybind {
|
||||
cfg := cp.chatView.cfg.Keybinds.Picker
|
||||
return []keybind.Keybind{cfg.Up.Keybind, cfg.Down.Keybind, cfg.Select.Keybind, cfg.Cancel.Keybind}
|
||||
}
|
||||
|
||||
func (cp *channelsPicker) FullHelp() [][]keybind.Keybind {
|
||||
cfg := cp.chatView.cfg.Keybinds.Picker
|
||||
return [][]keybind.Keybind{
|
||||
{cfg.Up.Keybind, cfg.Down.Keybind, cfg.Top.Keybind, cfg.Bottom.Keybind},
|
||||
{cfg.Select.Keybind, cfg.Cancel.Keybind},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import (
|
|||
"github.com/ayn2op/discordo/internal/config"
|
||||
"github.com/ayn2op/discordo/internal/ui"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/help"
|
||||
"github.com/ayn2op/tview/keybind"
|
||||
"github.com/diamondburned/arikawa/v3/discord"
|
||||
"github.com/diamondburned/arikawa/v3/gateway"
|
||||
"github.com/diamondburned/ningen/v3"
|
||||
|
|
@ -29,6 +31,8 @@ type guildsTree struct {
|
|||
dmRootNode *tview.TreeNode
|
||||
}
|
||||
|
||||
var _ help.KeyMap = (*guildsTree)(nil)
|
||||
|
||||
func newGuildsTree(cfg *config.Config, chatView *View) *guildsTree {
|
||||
gt := &guildsTree{
|
||||
TreeView: tview.NewTreeView(),
|
||||
|
|
@ -52,6 +56,86 @@ func newGuildsTree(cfg *config.Config, chatView *View) *guildsTree {
|
|||
return gt
|
||||
}
|
||||
|
||||
func (gt *guildsTree) ShortHelp() []keybind.Keybind {
|
||||
cfg := gt.cfg.Keybinds.GuildsTree
|
||||
selectCurrent := cfg.SelectCurrent.Keybind
|
||||
collapseParent := cfg.CollapseParentNode.Keybind
|
||||
selectHelp := selectCurrent.Help()
|
||||
selectDesc := selectHelp.Desc
|
||||
if node := gt.GetCurrentNode(); node != nil {
|
||||
if len(node.GetChildren()) > 0 {
|
||||
if node.IsExpanded() {
|
||||
selectDesc = "collapse"
|
||||
} else {
|
||||
selectDesc = "expand"
|
||||
}
|
||||
} else {
|
||||
switch node.GetReference().(type) {
|
||||
case discord.GuildID, dmNode:
|
||||
selectDesc = "expand"
|
||||
}
|
||||
}
|
||||
}
|
||||
selectCurrent.SetHelp(selectHelp.Key, selectDesc)
|
||||
collapseHelp := collapseParent.Help()
|
||||
collapseParent.SetHelp(collapseHelp.Key, "collapse parent")
|
||||
|
||||
shortHelp := []keybind.Keybind{cfg.Up.Keybind, cfg.Down.Keybind, selectCurrent}
|
||||
if gt.canCollapseParent(gt.GetCurrentNode()) {
|
||||
shortHelp = append(shortHelp, collapseParent)
|
||||
}
|
||||
return shortHelp
|
||||
}
|
||||
|
||||
func (gt *guildsTree) FullHelp() [][]keybind.Keybind {
|
||||
cfg := gt.cfg.Keybinds.GuildsTree
|
||||
selectCurrent := cfg.SelectCurrent.Keybind
|
||||
collapseParent := cfg.CollapseParentNode.Keybind
|
||||
selectHelp := selectCurrent.Help()
|
||||
selectDesc := selectHelp.Desc
|
||||
if node := gt.GetCurrentNode(); node != nil {
|
||||
if len(node.GetChildren()) > 0 {
|
||||
if node.IsExpanded() {
|
||||
selectDesc = "collapse"
|
||||
} else {
|
||||
selectDesc = "expand"
|
||||
}
|
||||
} else {
|
||||
switch node.GetReference().(type) {
|
||||
case discord.GuildID, dmNode:
|
||||
selectDesc = "expand"
|
||||
}
|
||||
}
|
||||
}
|
||||
selectCurrent.SetHelp(selectHelp.Key, selectDesc)
|
||||
collapseHelp := collapseParent.Help()
|
||||
collapseParent.SetHelp(collapseHelp.Key, "collapse parent")
|
||||
|
||||
actions := []keybind.Keybind{selectCurrent, cfg.MoveToParentNode.Keybind}
|
||||
if gt.canCollapseParent(gt.GetCurrentNode()) {
|
||||
actions = append(actions, collapseParent)
|
||||
}
|
||||
|
||||
return [][]keybind.Keybind{
|
||||
{cfg.Up.Keybind, cfg.Down.Keybind, cfg.Top.Keybind, cfg.Bottom.Keybind},
|
||||
actions,
|
||||
{cfg.YankID.Keybind},
|
||||
}
|
||||
}
|
||||
|
||||
func (gt *guildsTree) canCollapseParent(node *tview.TreeNode) bool {
|
||||
if node == nil {
|
||||
return false
|
||||
}
|
||||
path := gt.GetPath(node)
|
||||
// Path layout is [root, ..., node]. A non-root parent means at least 3 nodes.
|
||||
if len(path) < 3 {
|
||||
return false
|
||||
}
|
||||
parent := path[len(path)-2]
|
||||
return parent != nil && parent.GetLevel() != 0
|
||||
}
|
||||
|
||||
func (gt *guildsTree) resetNodeIndex() {
|
||||
// Keep allocated map capacity; READY can rebuild often during reconnects.
|
||||
clear(gt.guildNodeByID)
|
||||
|
|
@ -284,28 +368,28 @@ func (gt *guildsTree) collapseParentNode(node *tview.TreeNode) {
|
|||
}
|
||||
|
||||
func (gt *guildsTree) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
||||
switch event.Name() {
|
||||
case gt.cfg.Keybinds.GuildsTree.CollapseParentNode:
|
||||
switch {
|
||||
case keybind.Matches(event, gt.cfg.Keybinds.GuildsTree.CollapseParentNode.Keybind):
|
||||
gt.collapseParentNode(gt.GetCurrentNode())
|
||||
return nil
|
||||
case gt.cfg.Keybinds.GuildsTree.MoveToParentNode:
|
||||
case keybind.Matches(event, gt.cfg.Keybinds.GuildsTree.MoveToParentNode.Keybind):
|
||||
return tcell.NewEventKey(tcell.KeyRune, "K", tcell.ModNone)
|
||||
|
||||
case gt.cfg.Keybinds.GuildsTree.Up:
|
||||
case keybind.Matches(event, gt.cfg.Keybinds.GuildsTree.Up.Keybind):
|
||||
return tcell.NewEventKey(tcell.KeyUp, "", tcell.ModNone)
|
||||
case gt.cfg.Keybinds.GuildsTree.Down:
|
||||
case keybind.Matches(event, gt.cfg.Keybinds.GuildsTree.Down.Keybind):
|
||||
return tcell.NewEventKey(tcell.KeyDown, "", tcell.ModNone)
|
||||
case gt.cfg.Keybinds.GuildsTree.Top:
|
||||
case keybind.Matches(event, gt.cfg.Keybinds.GuildsTree.Top.Keybind):
|
||||
gt.Move(gt.GetRowCount() * -1)
|
||||
// return tcell.NewEventKey(tcell.KeyHome, 0, tcell.ModNone)
|
||||
case gt.cfg.Keybinds.GuildsTree.Bottom:
|
||||
case keybind.Matches(event, gt.cfg.Keybinds.GuildsTree.Bottom.Keybind):
|
||||
gt.Move(gt.GetRowCount())
|
||||
// return tcell.NewEventKey(tcell.KeyEnd, 0, tcell.ModNone)
|
||||
|
||||
case gt.cfg.Keybinds.GuildsTree.SelectCurrent:
|
||||
case keybind.Matches(event, gt.cfg.Keybinds.GuildsTree.SelectCurrent.Keybind):
|
||||
return tcell.NewEventKey(tcell.KeyEnter, "", tcell.ModNone)
|
||||
|
||||
case gt.cfg.Keybinds.GuildsTree.YankID:
|
||||
case keybind.Matches(event, gt.cfg.Keybinds.GuildsTree.YankID.Keybind):
|
||||
gt.yankID()
|
||||
}
|
||||
|
||||
|
|
|
|||
71
internal/ui/chat/keybinds.go
Normal file
71
internal/ui/chat/keybinds.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package chat
|
||||
|
||||
import (
|
||||
"github.com/ayn2op/tview/help"
|
||||
"github.com/ayn2op/tview/keybind"
|
||||
)
|
||||
|
||||
var _ help.KeyMap = (*View)(nil)
|
||||
|
||||
func (v *View) ShortHelp() []keybind.Keybind {
|
||||
short := make([]keybind.Keybind, 0, 16)
|
||||
if active := v.activeKeyMap(); active != nil {
|
||||
short = append(short, active.ShortHelp()...)
|
||||
}
|
||||
short = append(short, v.baseShortHelp()...)
|
||||
return short
|
||||
}
|
||||
|
||||
func (v *View) FullHelp() [][]keybind.Keybind {
|
||||
full := make([][]keybind.Keybind, 0, 8)
|
||||
if active := v.activeKeyMap(); active != nil {
|
||||
full = append(full, active.FullHelp()...)
|
||||
}
|
||||
full = append(full, v.baseFullHelp()...)
|
||||
return full
|
||||
}
|
||||
|
||||
func (v *View) activeKeyMap() help.KeyMap {
|
||||
if v.GetVisible(channelsPickerLayerName) {
|
||||
return v.channelsPicker
|
||||
}
|
||||
|
||||
if v.app == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v.app.GetFocus() {
|
||||
case v.guildsTree:
|
||||
return v.guildsTree
|
||||
case v.messagesList:
|
||||
return v.messagesList
|
||||
case v.messageInput:
|
||||
return v.messageInput
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (v *View) baseShortHelp() []keybind.Keybind {
|
||||
cfg := v.cfg.Keybinds
|
||||
short := []keybind.Keybind{cfg.FocusGuildsTree.Keybind, cfg.FocusMessagesList.Keybind}
|
||||
if !v.messageInput.GetDisabled() {
|
||||
short = append(short, cfg.FocusMessageInput.Keybind)
|
||||
}
|
||||
short = append(short, cfg.ToggleGuildsTree.Keybind, cfg.ToggleChannelsPicker.Keybind, cfg.ToggleHelp.Keybind)
|
||||
return short
|
||||
}
|
||||
|
||||
func (v *View) baseFullHelp() [][]keybind.Keybind {
|
||||
cfg := v.cfg.Keybinds
|
||||
focus := []keybind.Keybind{cfg.FocusGuildsTree.Keybind, cfg.FocusMessagesList.Keybind}
|
||||
if !v.messageInput.GetDisabled() {
|
||||
focus = append(focus, cfg.FocusMessageInput.Keybind)
|
||||
}
|
||||
return [][]keybind.Keybind{
|
||||
focus,
|
||||
{cfg.FocusPrevious.Keybind, cfg.FocusNext.Keybind},
|
||||
{cfg.ToggleGuildsTree.Keybind, cfg.ToggleChannelsPicker.Keybind},
|
||||
{cfg.ToggleHelp.Keybind, cfg.Logout.Keybind, cfg.Quit.Keybind},
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,8 @@ import (
|
|||
"github.com/ayn2op/discordo/internal/consts"
|
||||
"github.com/ayn2op/discordo/internal/ui"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/help"
|
||||
"github.com/ayn2op/tview/keybind"
|
||||
"github.com/ayn2op/tview/layers"
|
||||
"github.com/diamondburned/arikawa/v3/api"
|
||||
"github.com/diamondburned/arikawa/v3/discord"
|
||||
|
|
@ -52,6 +54,8 @@ type messageInput struct {
|
|||
typingTimer *time.Timer
|
||||
}
|
||||
|
||||
var _ help.KeyMap = (*messageInput)(nil)
|
||||
|
||||
func newMessageInput(cfg *config.Config, chatView *View) *messageInput {
|
||||
mi := &messageInput{
|
||||
TextArea: tview.NewTextArea(),
|
||||
|
|
@ -90,12 +94,12 @@ func (mi *messageInput) stopTypingTimer() {
|
|||
}
|
||||
|
||||
func (mi *messageInput) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
||||
switch event.Name() {
|
||||
case mi.cfg.Keybinds.MessageInput.Paste:
|
||||
switch {
|
||||
case keybind.Matches(event, mi.cfg.Keybinds.MessageInput.Paste.Keybind):
|
||||
mi.paste()
|
||||
return tcell.NewEventKey(tcell.KeyCtrlV, "", tcell.ModNone)
|
||||
|
||||
case mi.cfg.Keybinds.MessageInput.Send:
|
||||
case keybind.Matches(event, mi.cfg.Keybinds.MessageInput.Send.Keybind):
|
||||
if mi.chatView.GetVisible(mentionsListLayerName) {
|
||||
mi.tabComplete()
|
||||
return nil
|
||||
|
|
@ -103,15 +107,15 @@ func (mi *messageInput) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
|||
|
||||
mi.send()
|
||||
return nil
|
||||
case mi.cfg.Keybinds.MessageInput.OpenEditor:
|
||||
case keybind.Matches(event, mi.cfg.Keybinds.MessageInput.OpenEditor.Keybind):
|
||||
mi.stopTabCompletion()
|
||||
mi.editor()
|
||||
return nil
|
||||
case mi.cfg.Keybinds.MessageInput.OpenFilePicker:
|
||||
case keybind.Matches(event, mi.cfg.Keybinds.MessageInput.OpenFilePicker.Keybind):
|
||||
mi.stopTabCompletion()
|
||||
mi.openFilePicker()
|
||||
return nil
|
||||
case mi.cfg.Keybinds.MessageInput.Cancel:
|
||||
case keybind.Matches(event, mi.cfg.Keybinds.MessageInput.Cancel.Keybind):
|
||||
if mi.chatView.GetVisible(mentionsListLayerName) {
|
||||
mi.stopTabCompletion()
|
||||
} else {
|
||||
|
|
@ -119,7 +123,7 @@ func (mi *messageInput) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
return nil
|
||||
case mi.cfg.Keybinds.MessageInput.TabComplete:
|
||||
case keybind.Matches(event, mi.cfg.Keybinds.MessageInput.TabComplete.Keybind):
|
||||
go mi.chatView.app.QueueUpdateDraw(func() { mi.tabComplete() })
|
||||
return nil
|
||||
default:
|
||||
|
|
@ -139,17 +143,17 @@ func (mi *messageInput) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
|||
if mi.cfg.AutocompleteLimit > 0 {
|
||||
if mi.chatView.GetVisible(mentionsListLayerName) {
|
||||
handler := mi.mentionsList.InputHandler()
|
||||
switch event.Name() {
|
||||
case mi.cfg.Keybinds.MentionsList.Up:
|
||||
switch {
|
||||
case keybind.Matches(event, mi.cfg.Keybinds.MentionsList.Up.Keybind):
|
||||
handler(tcell.NewEventKey(tcell.KeyUp, "", tcell.ModNone), nil)
|
||||
return nil
|
||||
case mi.cfg.Keybinds.MentionsList.Down:
|
||||
case keybind.Matches(event, mi.cfg.Keybinds.MentionsList.Down.Keybind):
|
||||
handler(tcell.NewEventKey(tcell.KeyDown, "", tcell.ModNone), nil)
|
||||
return nil
|
||||
case mi.cfg.Keybinds.MentionsList.Top:
|
||||
case keybind.Matches(event, mi.cfg.Keybinds.MentionsList.Top.Keybind):
|
||||
handler(tcell.NewEventKey(tcell.KeyHome, "", tcell.ModNone), nil)
|
||||
return nil
|
||||
case mi.cfg.Keybinds.MentionsList.Bottom:
|
||||
case keybind.Matches(event, mi.cfg.Keybinds.MentionsList.Bottom.Keybind):
|
||||
handler(tcell.NewEventKey(tcell.KeyEnd, "", tcell.ModNone), nil)
|
||||
return nil
|
||||
}
|
||||
|
|
@ -676,3 +680,44 @@ func (mi *messageInput) attach(name string, reader io.Reader) {
|
|||
}
|
||||
mi.SetFooter("Attached " + humanJoin(names))
|
||||
}
|
||||
|
||||
func (mi *messageInput) ShortHelp() []keybind.Keybind {
|
||||
if mi.chatView.GetVisible(mentionsListLayerName) {
|
||||
cfg := mi.cfg.Keybinds.MentionsList
|
||||
icfg := mi.cfg.Keybinds.MessageInput
|
||||
short := []keybind.Keybind{cfg.Up.Keybind, cfg.Down.Keybind, icfg.Cancel.Keybind}
|
||||
if selected := mi.chatView.SelectedChannel(); selected != nil && mi.chatView.state.HasPermissions(selected.ID, discord.PermissionAttachFiles) {
|
||||
short = append(short, icfg.OpenFilePicker.Keybind)
|
||||
}
|
||||
return short
|
||||
}
|
||||
|
||||
cfg := mi.cfg.Keybinds.MessageInput
|
||||
short := []keybind.Keybind{cfg.Send.Keybind, cfg.Cancel.Keybind, cfg.Paste.Keybind, cfg.OpenEditor.Keybind}
|
||||
if selected := mi.chatView.SelectedChannel(); selected != nil && mi.chatView.state.HasPermissions(selected.ID, discord.PermissionAttachFiles) {
|
||||
short = append(short, cfg.OpenFilePicker.Keybind)
|
||||
}
|
||||
return short
|
||||
}
|
||||
|
||||
func (mi *messageInput) FullHelp() [][]keybind.Keybind {
|
||||
if mi.chatView.GetVisible(mentionsListLayerName) {
|
||||
mcfg := mi.cfg.Keybinds.MentionsList
|
||||
icfg := mi.cfg.Keybinds.MessageInput
|
||||
return [][]keybind.Keybind{
|
||||
{mcfg.Up.Keybind, mcfg.Down.Keybind, mcfg.Top.Keybind, mcfg.Bottom.Keybind},
|
||||
{icfg.TabComplete.Keybind, icfg.Cancel.Keybind},
|
||||
}
|
||||
}
|
||||
|
||||
cfg := mi.cfg.Keybinds.MessageInput
|
||||
openEditor := []keybind.Keybind{cfg.Paste.Keybind, cfg.OpenEditor.Keybind}
|
||||
if selected := mi.chatView.SelectedChannel(); selected != nil && mi.chatView.state.HasPermissions(selected.ID, discord.PermissionAttachFiles) {
|
||||
openEditor = append(openEditor, cfg.OpenFilePicker.Keybind)
|
||||
}
|
||||
|
||||
return [][]keybind.Keybind{
|
||||
{cfg.Send.Keybind, cfg.Cancel.Keybind, cfg.TabComplete.Keybind},
|
||||
openEditor,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import (
|
|||
"github.com/ayn2op/discordo/internal/markdown"
|
||||
"github.com/ayn2op/discordo/internal/ui"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/help"
|
||||
"github.com/ayn2op/tview/keybind"
|
||||
"github.com/diamondburned/arikawa/v3/api"
|
||||
"github.com/diamondburned/arikawa/v3/discord"
|
||||
"github.com/diamondburned/arikawa/v3/gateway"
|
||||
|
|
@ -58,6 +60,8 @@ type messagesList struct {
|
|||
}
|
||||
}
|
||||
|
||||
var _ help.KeyMap = (*messagesList)(nil)
|
||||
|
||||
type messagesListRowKind uint8
|
||||
|
||||
const (
|
||||
|
|
@ -523,52 +527,64 @@ func (ml *messagesList) selectedMessage() (*discord.Message, error) {
|
|||
}
|
||||
|
||||
func (ml *messagesList) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
||||
switch event.Name() {
|
||||
case ml.cfg.Keybinds.MessagesList.ScrollUp:
|
||||
switch {
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.ScrollUp.Keybind):
|
||||
ml.ScrollUp()
|
||||
return nil
|
||||
case ml.cfg.Keybinds.MessagesList.ScrollDown:
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.ScrollDown.Keybind):
|
||||
ml.ScrollDown()
|
||||
return nil
|
||||
case ml.cfg.Keybinds.MessagesList.ScrollTop:
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.ScrollTop.Keybind):
|
||||
ml.ScrollToStart()
|
||||
return nil
|
||||
case ml.cfg.Keybinds.MessagesList.ScrollBottom:
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.ScrollBottom.Keybind):
|
||||
ml.ScrollToEnd()
|
||||
return nil
|
||||
|
||||
case ml.cfg.Keybinds.MessagesList.Cancel:
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.Cancel.Keybind):
|
||||
ml.clearSelection()
|
||||
return nil
|
||||
|
||||
case ml.cfg.Keybinds.MessagesList.SelectUp, ml.cfg.Keybinds.MessagesList.SelectDown, ml.cfg.Keybinds.MessagesList.SelectTop, ml.cfg.Keybinds.MessagesList.SelectBottom, ml.cfg.Keybinds.MessagesList.SelectReply:
|
||||
ml._select(event.Name())
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.SelectUp.Keybind):
|
||||
ml.selectUp()
|
||||
return nil
|
||||
case ml.cfg.Keybinds.MessagesList.YankID:
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.SelectDown.Keybind):
|
||||
ml.selectDown()
|
||||
return nil
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.SelectTop.Keybind):
|
||||
ml.selectTop()
|
||||
return nil
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.SelectBottom.Keybind):
|
||||
ml.selectBottom()
|
||||
return nil
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.SelectReply.Keybind):
|
||||
ml.selectReply()
|
||||
return nil
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.YankID.Keybind):
|
||||
ml.yankID()
|
||||
return nil
|
||||
case ml.cfg.Keybinds.MessagesList.YankContent:
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.YankContent.Keybind):
|
||||
ml.yankContent()
|
||||
return nil
|
||||
case ml.cfg.Keybinds.MessagesList.YankURL:
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.YankURL.Keybind):
|
||||
ml.yankURL()
|
||||
return nil
|
||||
case ml.cfg.Keybinds.MessagesList.Open:
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.Open.Keybind):
|
||||
ml.open()
|
||||
return nil
|
||||
case ml.cfg.Keybinds.MessagesList.Reply:
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.Reply.Keybind):
|
||||
ml.reply(false)
|
||||
return nil
|
||||
case ml.cfg.Keybinds.MessagesList.ReplyMention:
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.ReplyMention.Keybind):
|
||||
ml.reply(true)
|
||||
return nil
|
||||
case ml.cfg.Keybinds.MessagesList.Edit:
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.Edit.Keybind):
|
||||
ml.edit()
|
||||
return nil
|
||||
case ml.cfg.Keybinds.MessagesList.Delete:
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.Delete.Keybind):
|
||||
ml.delete()
|
||||
return nil
|
||||
case ml.cfg.Keybinds.MessagesList.DeleteConfirm:
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.DeleteConfirm.Keybind):
|
||||
ml.confirmDelete()
|
||||
return nil
|
||||
}
|
||||
|
|
@ -576,57 +592,81 @@ func (ml *messagesList) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
|||
return event
|
||||
}
|
||||
|
||||
func (ml *messagesList) _select(name string) {
|
||||
func (ml *messagesList) selectUp() {
|
||||
messages := ml.messages
|
||||
if len(messages) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
cursor := ml.Cursor()
|
||||
|
||||
switch name {
|
||||
case ml.cfg.Keybinds.MessagesList.SelectUp:
|
||||
switch {
|
||||
case cursor == -1:
|
||||
cursor = len(messages) - 1
|
||||
case cursor > 0:
|
||||
cursor--
|
||||
case cursor == 0:
|
||||
added := ml.prependOlderMessages()
|
||||
if added == 0 {
|
||||
return
|
||||
}
|
||||
cursor = added - 1
|
||||
}
|
||||
case ml.cfg.Keybinds.MessagesList.SelectDown:
|
||||
switch {
|
||||
case cursor == -1:
|
||||
cursor = len(messages) - 1
|
||||
case cursor < len(messages)-1:
|
||||
cursor++
|
||||
}
|
||||
case ml.cfg.Keybinds.MessagesList.SelectTop:
|
||||
cursor = 0
|
||||
case ml.cfg.Keybinds.MessagesList.SelectBottom:
|
||||
switch {
|
||||
case cursor == -1:
|
||||
cursor = len(messages) - 1
|
||||
case ml.cfg.Keybinds.MessagesList.SelectReply:
|
||||
if cursor == -1 || cursor >= len(messages) {
|
||||
case cursor > 0:
|
||||
cursor--
|
||||
case cursor == 0:
|
||||
added := ml.prependOlderMessages()
|
||||
if added == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if ref := messages[cursor].ReferencedMessage; ref != nil {
|
||||
refIdx := slices.IndexFunc(messages, func(m discord.Message) bool {
|
||||
return m.ID == ref.ID
|
||||
})
|
||||
if refIdx != -1 {
|
||||
cursor = refIdx
|
||||
}
|
||||
}
|
||||
cursor = added - 1
|
||||
}
|
||||
|
||||
ml.SetCursor(cursor)
|
||||
}
|
||||
|
||||
func (ml *messagesList) selectDown() {
|
||||
messages := ml.messages
|
||||
if len(messages) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
cursor := ml.Cursor()
|
||||
switch {
|
||||
case cursor == -1:
|
||||
cursor = len(messages) - 1
|
||||
case cursor < len(messages)-1:
|
||||
cursor++
|
||||
}
|
||||
|
||||
ml.SetCursor(cursor)
|
||||
}
|
||||
|
||||
func (ml *messagesList) selectTop() {
|
||||
if len(ml.messages) == 0 {
|
||||
return
|
||||
}
|
||||
ml.SetCursor(0)
|
||||
}
|
||||
|
||||
func (ml *messagesList) selectBottom() {
|
||||
if len(ml.messages) == 0 {
|
||||
return
|
||||
}
|
||||
ml.SetCursor(len(ml.messages) - 1)
|
||||
}
|
||||
|
||||
func (ml *messagesList) selectReply() {
|
||||
messages := ml.messages
|
||||
if len(messages) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
cursor := ml.Cursor()
|
||||
if cursor == -1 || cursor >= len(messages) {
|
||||
return
|
||||
}
|
||||
|
||||
if ref := messages[cursor].ReferencedMessage; ref != nil {
|
||||
refIdx := slices.IndexFunc(messages, func(m discord.Message) bool {
|
||||
return m.ID == ref.ID
|
||||
})
|
||||
if refIdx != -1 {
|
||||
ml.SetCursor(refIdx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ml *messagesList) prependOlderMessages() int {
|
||||
selectedChannel := ml.chatView.SelectedChannel()
|
||||
if selectedChannel == nil {
|
||||
|
|
@ -815,16 +855,16 @@ func (ml *messagesList) showAttachmentsList(urls []string, attachments []discord
|
|||
list.SetCursor(0)
|
||||
}
|
||||
list.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
switch event.Name() {
|
||||
case ml.cfg.Keybinds.MessagesList.SelectUp:
|
||||
switch {
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.SelectUp.Keybind):
|
||||
return tcell.NewEventKey(tcell.KeyUp, "", tcell.ModNone)
|
||||
case ml.cfg.Keybinds.MessagesList.SelectDown:
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.SelectDown.Keybind):
|
||||
return tcell.NewEventKey(tcell.KeyDown, "", tcell.ModNone)
|
||||
case ml.cfg.Keybinds.MessagesList.SelectTop:
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.SelectTop.Keybind):
|
||||
return tcell.NewEventKey(tcell.KeyHome, "", tcell.ModNone)
|
||||
case ml.cfg.Keybinds.MessagesList.SelectBottom:
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.SelectBottom.Keybind):
|
||||
return tcell.NewEventKey(tcell.KeyEnd, "", tcell.ModNone)
|
||||
case ml.cfg.Keybinds.MessagesList.Cancel:
|
||||
case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.Cancel.Keybind):
|
||||
closeList()
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1061,3 +1101,72 @@ func (ml *messagesList) waitForChunkEvent() uint {
|
|||
<-ml.fetchingMembers.done
|
||||
return ml.fetchingMembers.count
|
||||
}
|
||||
|
||||
func (ml *messagesList) ShortHelp() []keybind.Keybind {
|
||||
cfg := ml.cfg.Keybinds.MessagesList
|
||||
help := []keybind.Keybind{
|
||||
cfg.SelectUp.Keybind,
|
||||
cfg.SelectDown.Keybind,
|
||||
cfg.Cancel.Keybind,
|
||||
}
|
||||
|
||||
if msg, err := ml.selectedMessage(); err == nil {
|
||||
me, _ := ml.chatView.state.Cabinet.Me()
|
||||
if msg.Author.ID != me.ID {
|
||||
help = append(help, cfg.Reply.Keybind)
|
||||
}
|
||||
}
|
||||
|
||||
return help
|
||||
}
|
||||
|
||||
func (ml *messagesList) FullHelp() [][]keybind.Keybind {
|
||||
cfg := ml.cfg.Keybinds.MessagesList
|
||||
|
||||
canSelectReply := false
|
||||
canReply := false
|
||||
canEdit := false
|
||||
canDelete := false
|
||||
canOpen := false
|
||||
if msg, err := ml.selectedMessage(); err == nil {
|
||||
canSelectReply = msg.ReferencedMessage != nil
|
||||
canOpen = len(extractURLs(msg.Content)) != 0 || len(msg.Attachments) != 0
|
||||
|
||||
me, _ := ml.chatView.state.Cabinet.Me()
|
||||
canReply = msg.Author.ID != me.ID
|
||||
canEdit = msg.Author.ID == me.ID
|
||||
canDelete = canEdit
|
||||
if !canDelete {
|
||||
selected := ml.chatView.SelectedChannel()
|
||||
canDelete = selected != nil && ml.chatView.state.HasPermissions(selected.ID, discord.PermissionManageMessages)
|
||||
}
|
||||
}
|
||||
|
||||
actions := make([]keybind.Keybind, 0, 4)
|
||||
if canReply {
|
||||
actions = append(actions, cfg.Reply.Keybind, cfg.ReplyMention.Keybind)
|
||||
}
|
||||
if canSelectReply {
|
||||
actions = append(actions, cfg.SelectReply.Keybind)
|
||||
}
|
||||
actions = append(actions, cfg.Cancel.Keybind)
|
||||
|
||||
manage := make([]keybind.Keybind, 0, 4)
|
||||
if canEdit {
|
||||
manage = append(manage, cfg.Edit.Keybind)
|
||||
}
|
||||
if canDelete {
|
||||
manage = append(manage, cfg.DeleteConfirm.Keybind, cfg.Delete.Keybind)
|
||||
}
|
||||
if canOpen {
|
||||
manage = append(manage, cfg.Open.Keybind)
|
||||
}
|
||||
|
||||
return [][]keybind.Keybind{
|
||||
{cfg.SelectUp.Keybind, cfg.SelectDown.Keybind, cfg.SelectTop.Keybind, cfg.SelectBottom.Keybind},
|
||||
{cfg.ScrollUp.Keybind, cfg.ScrollDown.Keybind, cfg.ScrollTop.Keybind, cfg.ScrollBottom.Keybind},
|
||||
actions,
|
||||
manage,
|
||||
{cfg.YankContent.Keybind, cfg.YankURL.Keybind, cfg.YankID.Keybind},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package chat
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ayn2op/tview/layers"
|
||||
"log/slog"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -11,6 +10,9 @@ import (
|
|||
"github.com/ayn2op/discordo/internal/keyring"
|
||||
"github.com/ayn2op/discordo/internal/ui"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/help"
|
||||
"github.com/ayn2op/tview/keybind"
|
||||
"github.com/ayn2op/tview/layers"
|
||||
"github.com/diamondburned/arikawa/v3/discord"
|
||||
"github.com/diamondburned/ningen/v3"
|
||||
"github.com/diamondburned/ningen/v3/states/read"
|
||||
|
|
@ -30,6 +32,7 @@ const (
|
|||
type View struct {
|
||||
*layers.Layers
|
||||
|
||||
rootFlex *tview.Flex
|
||||
mainFlex *tview.Flex
|
||||
rightFlex *tview.Flex
|
||||
|
||||
|
|
@ -37,6 +40,7 @@ type View struct {
|
|||
messagesList *messagesList
|
||||
messageInput *messageInput
|
||||
channelsPicker *channelsPicker
|
||||
help *help.Help
|
||||
|
||||
selectedChannel *discord.Channel
|
||||
selectedChannelMu sync.RWMutex
|
||||
|
|
@ -55,6 +59,7 @@ func NewView(app *tview.Application, cfg *config.Config, onLogout func()) *View
|
|||
v := &View{
|
||||
Layers: layers.New(),
|
||||
|
||||
rootFlex: tview.NewFlex(),
|
||||
mainFlex: tview.NewFlex(),
|
||||
rightFlex: tview.NewFlex(),
|
||||
|
||||
|
|
@ -71,6 +76,20 @@ func NewView(app *tview.Application, cfg *config.Config, onLogout func()) *View
|
|||
v.channelsPicker = newChannelsPicker(cfg, v)
|
||||
v.channelsPicker.SetCancelFunc(v.closePicker)
|
||||
|
||||
v.help = help.New()
|
||||
|
||||
styles := help.DefaultStyles()
|
||||
styles.ShortKeyStyle = cfg.Theme.Help.ShortKeyStyle.Style
|
||||
styles.ShortDescStyle = cfg.Theme.Help.ShortDescStyle.Style
|
||||
styles.FullKeyStyle = cfg.Theme.Help.FullKeyStyle.Style
|
||||
styles.FullDescStyle = cfg.Theme.Help.FullDescStyle.Style
|
||||
v.help.SetStyles(styles)
|
||||
|
||||
v.help.SetKeyMap(v)
|
||||
v.help.SetCompactModifiers(cfg.Help.CompactModifiers)
|
||||
v.help.SetShortSeparator(cfg.Help.Separator)
|
||||
v.help.SetBorderPadding(0, 0, cfg.Help.Padding[0], cfg.Help.Padding[1])
|
||||
|
||||
v.SetBackgroundLayerStyle(v.cfg.Theme.Dialog.BackgroundStyle.Style)
|
||||
v.SetInputCapture(v.onInputCapture)
|
||||
v.buildLayout()
|
||||
|
|
@ -91,6 +110,7 @@ func (v *View) SetSelectedChannel(channel *discord.Channel) {
|
|||
|
||||
func (v *View) buildLayout() {
|
||||
v.Clear()
|
||||
v.rootFlex.Clear()
|
||||
v.rightFlex.Clear()
|
||||
v.mainFlex.Clear()
|
||||
|
||||
|
|
@ -103,7 +123,13 @@ func (v *View) buildLayout() {
|
|||
AddItem(v.guildsTree, 0, 1, true).
|
||||
AddItem(v.rightFlex, 0, 4, false)
|
||||
|
||||
v.AddLayer(v.mainFlex, layers.WithName(flexLayerName), layers.WithResize(true), layers.WithVisible(true))
|
||||
v.rootFlex.
|
||||
SetDirection(tview.FlexRow).
|
||||
AddItem(v.mainFlex, 0, 1, true).
|
||||
AddItem(v.help, 1, 0, false)
|
||||
|
||||
v.updateHelpHeight()
|
||||
v.AddLayer(v.rootFlex, layers.WithName(flexLayerName), layers.WithResize(true), layers.WithVisible(true))
|
||||
}
|
||||
|
||||
func (v *View) togglePicker() {
|
||||
|
|
@ -197,25 +223,29 @@ func (v *View) focusNext() {
|
|||
}
|
||||
|
||||
func (v *View) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
||||
switch event.Name() {
|
||||
case v.cfg.Keybinds.FocusGuildsTree:
|
||||
switch {
|
||||
case keybind.Matches(event, v.cfg.Keybinds.ToggleHelp.Keybind):
|
||||
v.help.SetShowAll(!v.help.ShowAll())
|
||||
v.updateHelpHeight()
|
||||
return nil
|
||||
case keybind.Matches(event, v.cfg.Keybinds.FocusGuildsTree.Keybind):
|
||||
v.messageInput.removeMentionsList()
|
||||
v.focusGuildsTree()
|
||||
return nil
|
||||
case v.cfg.Keybinds.FocusMessagesList:
|
||||
case keybind.Matches(event, v.cfg.Keybinds.FocusMessagesList.Keybind):
|
||||
v.messageInput.removeMentionsList()
|
||||
v.app.SetFocus(v.messagesList)
|
||||
return nil
|
||||
case v.cfg.Keybinds.FocusMessageInput:
|
||||
case keybind.Matches(event, v.cfg.Keybinds.FocusMessageInput.Keybind):
|
||||
v.focusMessageInput()
|
||||
return nil
|
||||
case v.cfg.Keybinds.FocusPrevious:
|
||||
case keybind.Matches(event, v.cfg.Keybinds.FocusPrevious.Keybind):
|
||||
v.focusPrevious()
|
||||
return nil
|
||||
case v.cfg.Keybinds.FocusNext:
|
||||
case keybind.Matches(event, v.cfg.Keybinds.FocusNext.Keybind):
|
||||
v.focusNext()
|
||||
return nil
|
||||
case v.cfg.Keybinds.Logout:
|
||||
case keybind.Matches(event, v.cfg.Keybinds.Logout.Keybind):
|
||||
if v.onLogout != nil {
|
||||
v.onLogout()
|
||||
}
|
||||
|
|
@ -226,10 +256,10 @@ func (v *View) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
return nil
|
||||
case v.cfg.Keybinds.ToggleGuildsTree:
|
||||
case keybind.Matches(event, v.cfg.Keybinds.ToggleGuildsTree.Keybind):
|
||||
v.toggleGuildsTree()
|
||||
return nil
|
||||
case v.cfg.Keybinds.Picker.Toggle:
|
||||
case keybind.Matches(event, v.cfg.Keybinds.ToggleChannelsPicker.Keybind):
|
||||
v.togglePicker()
|
||||
return nil
|
||||
}
|
||||
|
|
@ -237,6 +267,17 @@ func (v *View) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
|||
return event
|
||||
}
|
||||
|
||||
func (v *View) updateHelpHeight() {
|
||||
height := 1
|
||||
if v.help.ShowAll() {
|
||||
height = len(v.help.FullHelpLines(v.FullHelp(), 0))
|
||||
if height < 1 {
|
||||
height = 1
|
||||
}
|
||||
}
|
||||
v.rootFlex.ResizeItem(v.help, height, 0)
|
||||
}
|
||||
|
||||
func (v *View) showConfirmModal(prompt string, buttons []string, onDone func(label string)) {
|
||||
previousFocus := v.app.GetFocus()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package picker
|
||||
|
||||
type KeyMap struct {
|
||||
Cancel string
|
||||
import "github.com/ayn2op/tview/keybind"
|
||||
|
||||
Up string
|
||||
Down string
|
||||
Top string
|
||||
Bottom string
|
||||
Select string
|
||||
type KeyMap struct {
|
||||
Cancel keybind.Keybind
|
||||
|
||||
Up keybind.Keybind
|
||||
Down keybind.Keybind
|
||||
Top keybind.Keybind
|
||||
Bottom keybind.Keybind
|
||||
Select keybind.Keybind
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package picker
|
|||
|
||||
import (
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/keybind"
|
||||
"github.com/gdamore/tcell/v3"
|
||||
"github.com/sahilm/fuzzy"
|
||||
)
|
||||
|
|
@ -154,23 +155,23 @@ func (p *Picker) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
handler := p.list.InputHandler()
|
||||
switch event.Name() {
|
||||
case p.keyMap.Up:
|
||||
switch {
|
||||
case keybind.Matches(event, p.keyMap.Up):
|
||||
handler(tcell.NewEventKey(tcell.KeyUp, "", tcell.ModNone), nil)
|
||||
return nil
|
||||
case p.keyMap.Down:
|
||||
case keybind.Matches(event, p.keyMap.Down):
|
||||
handler(tcell.NewEventKey(tcell.KeyDown, "", tcell.ModNone), nil)
|
||||
return nil
|
||||
case p.keyMap.Top:
|
||||
case keybind.Matches(event, p.keyMap.Top):
|
||||
handler(tcell.NewEventKey(tcell.KeyHome, "", tcell.ModNone), nil)
|
||||
return nil
|
||||
case p.keyMap.Bottom:
|
||||
case keybind.Matches(event, p.keyMap.Bottom):
|
||||
handler(tcell.NewEventKey(tcell.KeyEnd, "", tcell.ModNone), nil)
|
||||
case p.keyMap.Select:
|
||||
case keybind.Matches(event, p.keyMap.Select):
|
||||
p.onListSelected(p.list.Cursor())
|
||||
return nil
|
||||
|
||||
case p.keyMap.Cancel:
|
||||
case keybind.Matches(event, p.keyMap.Cancel):
|
||||
if p.onCancel != nil {
|
||||
p.onCancel()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue