feat: implement selectable messages (#38)

* feat: implement selectable messages

* style: shorten long lines

* feat: handle key down event for message select functionality

* feat: add handlers for top and bottom navigation

* style: shorten long lines

* docs: add 's' to singular verb

* docs: add missing period

* fix: if selectedChannel is not initialized, do not handle key event

* fix: prepend message to .Messages slice

* fix: if selectedChannel is nil, do not handle events

* feat: better default keybindings

* fix: use != comparison operator
This commit is contained in:
rigormorrtiss 2021-08-31 15:18:02 +04:00 • committed by GitHub
parent 102a45b66a
commit 5d19c768fe
3 changed files with 140 additions and 22 deletions

View file

@ -43,9 +43,18 @@ By default, Discordo utilizes OS-specific keyring to store credentials such as c
### Default Keybindings
- `Alt` + `g`: Sets the focus on the guilds TreeView.
- `Alt` + `m`: Sets the focus on the messages TextView.
- `Alt` + `i`: Sets the focus on the message InputField.
Global:
- `Alt` + `1`: Sets the focus on the guilds TreeView.
- `Alt` + `2`: Sets the focus on the messages TextView.
- `Alt` + `3`: Sets the focus on the message InputField.
TextView:
- `k` or `Up`: Selects the message just before the currently selected message.
- `j` or `Down`: Selects the message just after the currently selected message.
- `g` or `Home`: Selects the first message rendered in the TextView.
- `G` or `End`: Selects the last message rendered in the TextView.
### Clipboard

View file

@ -37,7 +37,10 @@ func main() {
EnableMouse(config.Mouse).
SetInputCapture(onAppInputCapture)
guildsTreeView = ui.NewGuildsTreeView(onGuildsTreeViewSelected)
messagesTextView = ui.NewMessagesTextView(app)
messagesTextView = ui.NewMessagesTextView(
app,
onMessagesTextViewInputCapture,
)
messageInputField = ui.NewMessageInputField(onMessageInputFieldInputCapture)
mainFlex = ui.NewMainFlex(
guildsTreeView,
@ -73,29 +76,123 @@ func main() {
func onAppInputCapture(e *tcell.EventKey) *tcell.EventKey {
switch e.Name() {
case "Alt+Rune[g]":
case "Alt+Rune[1]":
app.SetFocus(guildsTreeView)
case "Alt+Rune[m]":
case "Alt+Rune[2]":
app.SetFocus(messagesTextView)
case "Alt+Rune[i]":
case "Alt+Rune[3]":
app.SetFocus(messageInputField)
}
return e
}
func onMessagesTextViewInputCapture(e *tcell.EventKey) *tcell.EventKey {
if selectedChannel == nil {
return nil
}
switch {
case e.Key() == tcell.KeyUp || e.Rune() == 'k': // Up
ms := selectedChannel.Messages
hs := messagesTextView.GetHighlights()
// Initially, no message is highlighted/selected; highlight the last
// message in the TextView.
if len(hs) == 0 {
messagesTextView.
Highlight(selectedChannel.LastMessageID).
ScrollToHighlight()
} else {
// Find the index of the highlighted message in the
// *discordgo.Channel.Messages slice.
var idx int
for i, v := range ms {
if hs[0] == v.ID {
idx = i
}
}
// If the length of the *discordgo.Channel.Messages slice is
// equal to the index of the message just after highlighted
// message in the slice (this is the first-rendered message in
// the TextView), do not handle the event.
if len(ms) == idx+1 {
return nil
}
// Highlight the message just before the currently highlighted
// message.
messagesTextView.
Highlight(ms[idx+1].ID).
ScrollToHighlight()
}
return nil
case e.Key() == tcell.KeyDown || e.Rune() == 'j': // Down
ms := selectedChannel.Messages
hs := messagesTextView.GetHighlights()
// Initially, no message is highlighted/selected; highlight the last
// message in the TextView.
if len(hs) == 0 {
messagesTextView.
Highlight(ms[0].ID).
ScrollToHighlight()
} else {
// Find the index of the highlighted message in the
// *discordgo.Channel.Messages slice.
var idx int
for i, v := range selectedChannel.Messages {
if v.Type == discordgo.MessageTypeDefault || v.Type == discordgo.MessageTypeReply {
if hs[0] == v.ID {
idx = i
}
}
}
// If the index of the highlighted message in the slice is equal
// to zero (this is the last-rendered message in the TextView),
// do not handle the event.
if idx == 0 {
return nil
}
// Highlight the message just after the currently highlighted
// message.
messagesTextView.
Highlight(ms[idx-1].ID).
ScrollToHighlight()
}
return nil
case e.Key() == tcell.KeyHome || e.Rune() == 'g': // Top
ms := selectedChannel.Messages
// Highlight the last message in the selectedChannel.Messages slice
// (the first message rendered in the TextView).
messagesTextView.
Highlight(ms[len(ms)-1].ID).
ScrollToHighlight()
case e.Key() == tcell.KeyEnd || e.Rune() == 'G': // Bottom
ms := selectedChannel.Messages
// Highlight the first message in the selectedChannel.Messages slice
// (the last message rendered in the TextView).
messagesTextView.
Highlight(ms[0].ID).
ScrollToHighlight()
}
return e
}
func onMessageInputFieldInputCapture(e *tcell.EventKey) *tcell.EventKey {
switch e.Key() {
case tcell.KeyEnter:
if selectedChannel != nil {
t := strings.TrimSpace(messageInputField.GetText())
if t == "" {
return nil
}
messageInputField.SetText("")
go session.ChannelMessageSend(selectedChannel.ID, t)
if selectedChannel == nil {
return nil
}
t := strings.TrimSpace(messageInputField.GetText())
if t == "" {
return nil
}
messageInputField.SetText("")
go session.ChannelMessageSend(selectedChannel.ID, t)
case tcell.KeyCtrlV:
text, _ := clipboard.ReadAll()
text = messageInputField.GetText() + text
@ -157,13 +254,19 @@ func onSessionReady(_ *discordgo.Session, r *discordgo.Ready) {
}
func onSessionMessageCreate(_ *discordgo.Session, m *discordgo.MessageCreate) {
if selectedChannel != nil && selectedChannel.ID == m.ChannelID {
util.WriteMessage(
messagesTextView,
m.Message,
session.State.Ready.User.ID,
)
if selectedChannel == nil || selectedChannel.ID != m.ChannelID {
return
}
selectedChannel.Messages = append(
[]*discordgo.Message{m.Message},
selectedChannel.Messages...)
util.WriteMessage(
messagesTextView,
m.Message,
session.State.Ready.User.ID,
)
}
func onGuildsTreeViewSelected(n *tview.TreeNode) {
@ -219,6 +322,7 @@ func onGuildsTreeViewSelected(n *tview.TreeNode) {
func writeMessages(cID string) {
msgs, _ := session.ChannelMessages(cID, config.GetMessagesLimit, "", "", "")
selectedChannel.Messages = msgs
for i := len(msgs) - 1; i >= 0; i-- {
util.WriteMessage(
messagesTextView,

View file

@ -1,11 +1,15 @@
package ui
import (
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
// NewMessagesTextView creates and returns a new messages textview.
func NewMessagesTextView(app *tview.Application) *tview.TextView {
func NewMessagesTextView(
app *tview.Application,
onMessagesTextViewInputCapture func(*tcell.EventKey) *tcell.EventKey,
) *tview.TextView {
v := tview.NewTextView()
v.
SetRegions(true).
@ -15,6 +19,7 @@ func NewMessagesTextView(app *tview.Application) *tview.TextView {
SetChangedFunc(func() {
app.Draw()
}).
SetInputCapture(onMessagesTextViewInputCapture).
SetBorder(true).
SetBorderPadding(0, 0, 1, 0).
SetTitleAlign(tview.AlignLeft)