diff --git a/config/config.go b/config/config.go index 792a334..3c50cbf 100644 --- a/config/config.go +++ b/config/config.go @@ -11,25 +11,28 @@ import ( const Name = "discordo" -type MessagesViewKeysConfig struct { - OpenActionsView string `yaml:"open_actions_view"` +type ( + MessagesTextKeysConfig struct { + LaunchActions string `yaml:"launch_actions"` - SelectPreviousMessage string `yaml:"select_previous_message"` - SelectNextMessage string `yaml:"select_next_message"` - SelectFirstMessage string `yaml:"select_first_message"` - SelectLastMessage string `yaml:"select_last_message"` -} + SelectPrevious string `yaml:"select_previous"` + SelectNext string `yaml:"select_next"` + SelectFirst string `yaml:"select_first"` + SelectLast string `yaml:"select_last"` + } -type InputViewKeysConfig struct { - SendMessage string `yaml:"send_message"` - OpenExternalEditor string `yaml:"open_external_editor"` - PasteClipboard string `yaml:"paste_clipboard"` -} + MessageInputKeysConfig struct { + Send string `yaml:"send"` + Paste string `yaml:"paste"` -type KeysConfig struct { - MessagesView MessagesViewKeysConfig `yaml:"messages_view"` - InputView InputViewKeysConfig `yaml:"input_view"` -} + LaunchEditor string `yaml:"launch_editor"` + } + + KeysConfig struct { + MessagesText MessagesTextKeysConfig `yaml:"messages_text"` + MessageInput MessageInputKeysConfig `yaml:"message_input"` + } +) type ThemeConfig struct { Background string `yaml:"background"` @@ -48,34 +51,77 @@ type Config struct { Timezone string `yaml:"timezone"` // A textual representation of the time value formatted according to the layout defined by its value. Learn more: https://pkg.go.dev/time#Layout TimeFormat string `yaml:"time_format"` - // Keybindings - Keys KeysConfig `yaml:"keys"` - // Theme + + Keys KeysConfig `yaml:"keys"` Theme ThemeConfig `yaml:"theme"` } -func New() *Config { - return &Config{ +func New() (*Config, error) { + path, err := os.UserConfigDir() + if err != nil { + return nil, err + } + + // Create the configuration directory if it does not exist already. + path = filepath.Join(path, Name) + err = os.MkdirAll(path, os.ModePerm) + if err != nil { + return nil, err + } + + c := def() + path = filepath.Join(path, "config.yml") + _, err = os.Stat(path) + // If the configuration file does not exist, create a new one and write the default configuration to it. + if os.IsNotExist(err) { + f, err := os.Create(path) + if err != nil { + return nil, err + } + defer f.Close() + + err = yaml.NewEncoder(f).Encode(c) + if err != nil { + return nil, err + } + } else { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + err = yaml.NewDecoder(f).Decode(&c) + if err != nil { + return nil, err + } + } + + return &c, nil +} + +func def() Config { + return Config{ Mouse: true, MessagesLimit: 50, - - Timestamps: false, - Timezone: "Local", - TimeFormat: time.Kitchen, + Timestamps: false, + Timezone: "Local", + TimeFormat: time.Kitchen, Keys: KeysConfig{ - MessagesView: MessagesViewKeysConfig{ - OpenActionsView: "Rune[a]", + MessagesText: MessagesTextKeysConfig{ + LaunchActions: "Rune[a]", - SelectPreviousMessage: "Up", - SelectNextMessage: "Down", - SelectFirstMessage: "Home", - SelectLastMessage: "End", + SelectPrevious: "Up", + SelectNext: "Down", + SelectFirst: "Home", + SelectLast: "End", }, - InputView: InputViewKeysConfig{ - SendMessage: "Enter", - OpenExternalEditor: "Ctrl+E", - PasteClipboard: "Ctrl+V", + MessageInput: MessageInputKeysConfig{ + Send: "Enter", + Paste: "Ctrl+V", + + LaunchEditor: "Ctrl+E", }, }, Theme: ThemeConfig{ @@ -86,44 +132,6 @@ func New() *Config { } } -func (cfg *Config) Load() error { - path := DirPath() - // Create the configuration directory if it does not exist already. - err := os.MkdirAll(path, os.ModePerm) - if err != nil { - return err - } - - // Open the configuration file with create and read-write flag. - f, err := os.OpenFile( - filepath.Join(path, "config.yml"), - os.O_CREATE|os.O_RDWR, - os.ModePerm, - ) - if err != nil { - return err - } - defer f.Close() - - fi, err := f.Stat() - if err != nil { - return err - } - - // If the configuration file is empty (the size of the file is zero; a new configuration file was created), write the default configuration to the file. - if fi.Size() == 0 { - return yaml.NewEncoder(f).Encode(cfg) - } - - return yaml.NewDecoder(f).Decode(&cfg) -} - -// DirPath returns the path of the configuration directory. -func DirPath() string { - path, _ := os.UserConfigDir() - return filepath.Join(path, Name) -} - // LogDirPath returns the path of the log directory. func LogDirPath() string { path, _ := os.UserCacheDir() diff --git a/main.go b/main.go index 7e7c5a8..49cda4c 100644 --- a/main.go +++ b/main.go @@ -38,8 +38,8 @@ func init() { func main() { flag.Parse() - cfg := config.New() - if err := cfg.Load(); err != nil { + cfg, err := config.New() + if err != nil { log.Fatal(err) } diff --git a/out.log b/out.log new file mode 100644 index 0000000..e69de29 diff --git a/ui/actions_view.go b/ui/actions_list.go similarity index 55% rename from ui/actions_view.go rename to ui/actions_list.go index 7cd69eb..a22f493 100644 --- a/ui/actions_view.go +++ b/ui/actions_list.go @@ -15,14 +15,14 @@ import ( var linkRegex = regexp.MustCompile("https?://.+") -type ActionsView struct { +type ActionsList struct { *tview.List app *Application message *discord.Message } -func newActionsView(app *Application, m *discord.Message) *ActionsView { - v := &ActionsView{ +func newActionsList(app *Application, m *discord.Message) *ActionsList { + v := &ActionsList{ List: tview.NewList(), app: app, message: m, @@ -31,13 +31,13 @@ func newActionsView(app *Application, m *discord.Message) *ActionsView { v.ShowSecondaryText(false) v.SetDoneFunc(func() { app.SetRoot(app.view, true) - app.SetFocus(app.view.MessagesView) + app.SetFocus(app.view.MessagesText) }) - isDM := channelIsInDMCategory(app.view.ChannelsView.selected) + isDM := channelIsInDMCategory(app.view.ChannelsTree.selected) // If the client user has the `SEND_MESSAGES` permission, add "Reply" and "Mention Reply" actions. - if isDM || !isDM && hasPermission(app.state, app.view.ChannelsView.selected.ID, discord.PermissionSendMessages) { + if isDM || !isDM && hasPermission(app.state, app.view.ChannelsTree.selected.ID, discord.PermissionSendMessages) { v.AddItem("Reply", "", 'r', v.replyAction) v.AddItem("Mention Reply", "", 'R', v.mentionReplyAction) } @@ -56,7 +56,7 @@ func newActionsView(app *Application, m *discord.Message) *ActionsView { } app.SetRoot(app.view, true) - app.SetFocus(app.view.MessagesView) + app.SetFocus(app.view.MessagesText) }) } @@ -69,7 +69,7 @@ func newActionsView(app *Application, m *discord.Message) *ActionsView { me, _ := app.state.MeStore.Me() // If the client user has the `MANAGE_MESSAGES` permission, add a new action to delete the message. - if (isDM && m.Author.ID == me.ID) || (!isDM && hasPermission(app.state, app.view.ChannelsView.selected.ID, discord.PermissionManageMessages)) { + if (isDM && m.Author.ID == me.ID) || (!isDM && hasPermission(app.state, app.view.ChannelsTree.selected.ID, discord.PermissionManageMessages)) { v.AddItem("Delete", "", 'd', v.deleteAction) } @@ -85,37 +85,37 @@ func newActionsView(app *Application, m *discord.Message) *ActionsView { return v } -func (v *ActionsView) replyAction() { - v.app.view.InputView.SetTitle("Replying to " + v.message.Author.Tag()) +func (al *ActionsList) replyAction() { + al.app.view.MessageInput.SetTitle("Replying to " + al.message.Author.Tag()) - v.app.SetRoot(v.app.view, true) - v.app.SetFocus(v.app.view.InputView) + al.app.SetRoot(al.app.view, true) + al.app.SetFocus(al.app.view.MessageInput) } -func (v *ActionsView) mentionReplyAction() { - v.app.view.InputView.SetTitle("[@] Replying to " + v.message.Author.Tag()) +func (al *ActionsList) mentionReplyAction() { + al.app.view.MessageInput.SetTitle("[@] Replying to " + al.message.Author.Tag()) - v.app.SetRoot(v.app.view, true) - v.app.SetFocus(v.app.view.InputView) + al.app.SetRoot(al.app.view, true) + al.app.SetFocus(al.app.view.MessageInput) } -func (v *ActionsView) selectReplyAction() { - ms, err := v.app.state.Cabinet.Messages(v.message.ChannelID) +func (al *ActionsList) selectReplyAction() { + ms, err := al.app.state.Cabinet.Messages(al.message.ChannelID) if err != nil { return } - v.app.view.MessagesView.selected, _ = findMessageByID(ms, v.message.ReferencedMessage.ID) - v.app.view.MessagesView. - Highlight(v.message.ReferencedMessage.ID.String()). + al.app.view.MessagesText.selected, _ = findMessageByID(ms, al.message.ReferencedMessage.ID) + al.app.view.MessagesText. + Highlight(al.message.ReferencedMessage.ID.String()). ScrollToHighlight() - v.app.SetRoot(v.app.view, true) - v.app.SetFocus(v.app.view.MessagesView) + al.app.SetRoot(al.app.view, true) + al.app.SetFocus(al.app.view.MessagesText) } -func (v *ActionsView) openAttachmentAction() { - for _, a := range v.message.Attachments { +func (al *ActionsList) openAttachmentAction() { + for _, a := range al.message.Attachments { cacheDirPath, _ := os.UserCacheDir() f, err := os.Create(filepath.Join(cacheDirPath, a.Filename)) if err != nil { @@ -137,12 +137,12 @@ func (v *ActionsView) openAttachmentAction() { go open.Run(f.Name()) } - v.app.SetRoot(v.app.view, true) - v.app.SetFocus(v.app.view.MessagesView) + al.app.SetRoot(al.app.view, true) + al.app.SetFocus(al.app.view.MessagesText) } -func (v *ActionsView) downloadAttachmentAction() { - for _, a := range v.message.Attachments { +func (al *ActionsList) downloadAttachmentAction() { + for _, a := range al.message.Attachments { path, err := os.UserHomeDir() if err != nil { path = os.TempDir() @@ -168,66 +168,66 @@ func (v *ActionsView) downloadAttachmentAction() { f.Write(d) } - v.app.SetRoot(v.app.view, true) - v.app.SetFocus(v.app.view.MessagesView) + al.app.SetRoot(al.app.view, true) + al.app.SetFocus(al.app.view.MessagesText) } -func (v *ActionsView) deleteAction() { - v.app.view.MessagesView.Clear() +func (al *ActionsList) deleteAction() { + al.app.view.MessagesText.Clear() - err := v.app.state.MessageRemove(v.message.ChannelID, v.message.ID) + err := al.app.state.MessageRemove(al.message.ChannelID, al.message.ID) if err != nil { return } - err = v.app.state.DeleteMessage(v.message.ChannelID, v.message.ID, "Unknown") + err = al.app.state.DeleteMessage(al.message.ChannelID, al.message.ID, "Unknown") if err != nil { return } // The returned slice will be sorted from latest to oldest. - ms, err := v.app.state.Cabinet.Messages(v.message.ChannelID) + ms, err := al.app.state.Cabinet.Messages(al.message.ChannelID) if err != nil { return } for i := len(ms) - 1; i >= 0; i-- { - _, err = v.app.view.MessagesView.Write(buildMessage(v.app, ms[i])) + _, err = al.app.view.MessagesText.Write(buildMessage(al.app, ms[i])) if err != nil { return } } - v.app.SetRoot(v.app.view, true) - v.app.SetFocus(v.app.view.MessagesView) + al.app.SetRoot(al.app.view, true) + al.app.SetFocus(al.app.view.MessagesText) } -func (v *ActionsView) copyContentAction() { - err := clipboard.WriteAll(v.message.Content) +func (al *ActionsList) copyContentAction() { + err := clipboard.WriteAll(al.message.Content) if err != nil { return } - v.app.SetRoot(v.app.view, true) - v.app.SetFocus(v.app.view.MessagesView) + al.app.SetRoot(al.app.view, true) + al.app.SetFocus(al.app.view.MessagesText) } -func (v *ActionsView) copyIDAction() { - err := clipboard.WriteAll(v.message.ID.String()) +func (al *ActionsList) copyIDAction() { + err := clipboard.WriteAll(al.message.ID.String()) if err != nil { return } - v.app.SetRoot(v.app.view, true) - v.app.SetFocus(v.app.view.MessagesView) + al.app.SetRoot(al.app.view, true) + al.app.SetFocus(al.app.view.MessagesText) } -func (v *ActionsView) copyLinkAction() { - err := clipboard.WriteAll(v.message.URL()) +func (al *ActionsList) copyLinkAction() { + err := clipboard.WriteAll(al.message.URL()) if err != nil { return } - v.app.SetRoot(v.app.view, true) - v.app.SetFocus(v.app.view.MessagesView) + al.app.SetRoot(al.app.view, true) + al.app.SetFocus(al.app.view.MessagesText) } diff --git a/ui/application.go b/ui/application.go index 489e3d7..805d8c3 100644 --- a/ui/application.go +++ b/ui/application.go @@ -73,9 +73,9 @@ func (app *Application) Run(token string) { } app.SetRoot(app.view, true) - app.SetFocus(app.view.GuildsView) + app.SetFocus(app.view.GuildsTree) } else { - loginView := newLoginView(app) + loginView := newLoginForm(app) app.SetRoot(loginView, true) } @@ -103,7 +103,7 @@ func (app *Application) onBeforeDraw(screen tcell.Screen) bool { } func (c *Application) onReady(r *gateway.ReadyEvent) { - root := c.view.GuildsView.GetRoot() + root := c.view.GuildsTree.GetRoot() for _, gf := range r.UserSettings.GuildFolders { if gf.ID == 0 { for _, gID := range gf.GuildIDs { @@ -154,24 +154,24 @@ func (c *Application) onReady(r *gateway.ReadyEvent) { } - c.view.GuildsView.SetCurrentNode(root) - c.SetFocus(c.view.GuildsView) + c.view.GuildsTree.SetCurrentNode(root) + c.SetFocus(c.view.GuildsTree) } func (c *Application) onGuildCreate(g *gateway.GuildCreateEvent) { guildNode := tview.NewTreeNode(g.Name) guildNode.SetReference(g.ID) - rootNode := c.view.GuildsView.GetRoot() + rootNode := c.view.GuildsTree.GetRoot() rootNode.AddChild(guildNode) - c.view.GuildsView.SetCurrentNode(rootNode) - c.SetFocus(c.view.GuildsView) + c.view.GuildsTree.SetCurrentNode(rootNode) + c.SetFocus(c.view.GuildsTree) c.Draw() } func (c *Application) onGuildDelete(g *gateway.GuildDeleteEvent) { - rootNode := c.view.GuildsView.GetRoot() + rootNode := c.view.GuildsTree.GetRoot() var parentNode *tview.TreeNode rootNode.Walk(func(node, _ *tview.TreeNode) bool { if node.GetReference() == g.ID { @@ -190,14 +190,14 @@ func (c *Application) onGuildDelete(g *gateway.GuildDeleteEvent) { } func (c *Application) onMessageCreate(m *gateway.MessageCreateEvent) { - if c.view.ChannelsView.selected != nil && m.ChannelID == c.view.ChannelsView.selected.ID { - _, err := c.view.MessagesView.Write(buildMessage(c, m.Message)) + if c.view.ChannelsTree.selected != nil && m.ChannelID == c.view.ChannelsTree.selected.ID { + _, err := c.view.MessagesText.Write(buildMessage(c, m.Message)) if err != nil { return } - if len(c.view.MessagesView.GetHighlights()) == 0 { - c.view.MessagesView.ScrollToEnd() + if len(c.view.MessagesText.GetHighlights()) == 0 { + c.view.MessagesText.ScrollToEnd() } } } diff --git a/ui/channels_view.go b/ui/channels_tree.go similarity index 78% rename from ui/channels_view.go rename to ui/channels_tree.go index c554f67..1598ecf 100644 --- a/ui/channels_view.go +++ b/ui/channels_tree.go @@ -8,15 +8,15 @@ import ( "github.com/rivo/tview" ) -type ChannelsView struct { +type ChannelsTree struct { *tview.TreeView app *Application selected *discord.Channel } -func newChannelsView(app *Application) *ChannelsView { - v := &ChannelsView{ +func newChannelsTree(app *Application) *ChannelsTree { + v := &ChannelsTree{ TreeView: tview.NewTreeView(), app: app, @@ -34,14 +34,14 @@ func newChannelsView(app *Application) *ChannelsView { return v } -func (v *ChannelsView) onSelected(node *tview.TreeNode) { +func (v *ChannelsTree) onSelected(node *tview.TreeNode) { v.selected = nil - v.app.view.MessagesView.selected = -1 - v.app.view.MessagesView. + v.app.view.MessagesText.selected = -1 + v.app.view.MessagesText. Highlight(). Clear(). SetTitle("") - v.app.view.InputView.SetText("") + v.app.view.MessageInput.SetText("") ref := node.GetReference() if ref == nil { @@ -64,21 +64,21 @@ func (v *ChannelsView) onSelected(node *tview.TreeNode) { default: v.selected = c - v.app.view.MessagesView.setTitle(c) - v.app.SetFocus(v.app.view.InputView) + v.app.view.MessagesText.setTitle(c) + v.app.SetFocus(v.app.view.MessageInput) - go v.app.view.MessagesView.loadMessages(c) + go v.app.view.MessagesText.loadMessages(c) } } -func (v *ChannelsView) createChannelNode(c discord.Channel) *tview.TreeNode { +func (v *ChannelsTree) createChannelNode(c discord.Channel) *tview.TreeNode { channelNode := tview.NewTreeNode(channelToString(c)) channelNode.SetReference(c.ID) return channelNode } -func (v *ChannelsView) createPrivateChannelNodes(root *tview.TreeNode) { +func (v *ChannelsTree) createPrivateChannelNodes(root *tview.TreeNode) { cs, err := v.app.state.Cabinet.PrivateChannels() if err != nil { log.Println(err) @@ -102,7 +102,7 @@ func (v *ChannelsView) createPrivateChannelNodes(root *tview.TreeNode) { } } -func (v *ChannelsView) createGuildChannelNodes(root *tview.TreeNode, gID discord.GuildID) { +func (v *ChannelsTree) createGuildChannelNodes(root *tview.TreeNode, gID discord.GuildID) { cs, err := v.app.state.Cabinet.Channels(gID) if err != nil { log.Println(err) @@ -118,7 +118,7 @@ func (v *ChannelsView) createGuildChannelNodes(root *tview.TreeNode, gID discord v.createChildrenChannelNodes(root, cs) } -func (v *ChannelsView) createOrphanChannelNodes(root *tview.TreeNode, cs []discord.Channel) { +func (v *ChannelsTree) createOrphanChannelNodes(root *tview.TreeNode, cs []discord.Channel) { for _, c := range cs { if (c.Type == discord.GuildText || c.Type == discord.GuildNews) && (!c.ParentID.IsValid()) { root.AddChild(v.createChannelNode(c)) @@ -126,7 +126,7 @@ func (v *ChannelsView) createOrphanChannelNodes(root *tview.TreeNode, cs []disco } } -func (v *ChannelsView) createCategoryChannelNodes(root *tview.TreeNode, cs []discord.Channel) { +func (v *ChannelsTree) createCategoryChannelNodes(root *tview.TreeNode, cs []discord.Channel) { CATEGORY: for _, c := range cs { if c.Type == discord.GuildCategory { @@ -142,7 +142,7 @@ CATEGORY: } } -func (v *ChannelsView) createChildrenChannelNodes(root *tview.TreeNode, cs []discord.Channel) { +func (v *ChannelsTree) createChildrenChannelNodes(root *tview.TreeNode, cs []discord.Channel) { for _, c := range cs { if (c.Type == discord.GuildText || c.Type == discord.GuildNews) && (c.ParentID.IsValid()) { var parentNode *tview.TreeNode diff --git a/ui/guilds_view.go b/ui/guilds_tree.go similarity index 62% rename from ui/guilds_view.go rename to ui/guilds_tree.go index a6fd95c..d032f15 100644 --- a/ui/guilds_view.go +++ b/ui/guilds_tree.go @@ -5,14 +5,14 @@ import ( "github.com/rivo/tview" ) -type GuildsView struct { +type GuildsTree struct { *tview.TreeView app *Application } -func newGuildsView(app *Application) *GuildsView { - v := &GuildsView{ +func newGuildsTree(app *Application) *GuildsTree { + v := &GuildsTree{ TreeView: tview.NewTreeView(), app: app, @@ -33,16 +33,16 @@ func newGuildsView(app *Application) *GuildsView { return v } -func (v *GuildsView) onSelected(node *tview.TreeNode) { - v.app.view.ChannelsView.selected = nil - v.app.view.MessagesView.selected = -1 - rootNode := v.app.view.ChannelsView.GetRoot() +func (v *GuildsTree) onSelected(node *tview.TreeNode) { + v.app.view.ChannelsTree.selected = nil + v.app.view.MessagesText.selected = -1 + rootNode := v.app.view.ChannelsTree.GetRoot() rootNode.ClearChildren() - v.app.view.MessagesView. + v.app.view.MessagesText. Highlight(). Clear(). SetTitle("") - v.app.view.InputView.SetText("") + v.app.view.MessageInput.SetText("") // If the selected node has children (guild folder), expand the selected node if it is collapsed, otherwise collapse. if len(node.GetChildren()) != 0 { @@ -53,11 +53,11 @@ func (v *GuildsView) onSelected(node *tview.TreeNode) { ref := node.GetReference() // If the reference of the selected node is nil, it must be the direct messages node. if ref == nil { - v.app.view.ChannelsView.createPrivateChannelNodes(rootNode) + v.app.view.ChannelsTree.createPrivateChannelNodes(rootNode) } else { // Guild - v.app.view.ChannelsView.createGuildChannelNodes(rootNode, ref.(discord.GuildID)) + v.app.view.ChannelsTree.createGuildChannelNodes(rootNode, ref.(discord.GuildID)) } - v.app.view.ChannelsView.SetCurrentNode(rootNode) - v.app.SetFocus(v.app.view.ChannelsView) + v.app.view.ChannelsTree.SetCurrentNode(rootNode) + v.app.SetFocus(v.app.view.ChannelsTree) } diff --git a/ui/login_view.go b/ui/login_form.go similarity index 69% rename from ui/login_view.go rename to ui/login_form.go index ce677aa..12fba3d 100644 --- a/ui/login_view.go +++ b/ui/login_form.go @@ -10,31 +10,31 @@ import ( "github.com/zalando/go-keyring" ) -type LoginView struct { +type LoginForm struct { *tview.Form app *Application } -func newLoginView(app *Application) *LoginView { - v := &LoginView{ +func newLoginForm(app *Application) *LoginForm { + lf := &LoginForm{ Form: tview.NewForm(), app: app, } - v.AddInputField("Email", "", 0, nil, nil) - v.AddPasswordField("Password", "", 0, 0, nil) - v.AddPasswordField("Code (optional)", "", 0, 0, nil) - v.AddButton("Login", v.onLoginButtonSelected) + lf.AddInputField("Email", "", 0, nil, nil) + lf.AddPasswordField("Password", "", 0, 0, nil) + lf.AddPasswordField("Code (optional)", "", 0, 0, nil) + lf.AddButton("Login", lf.onLoginButtonSelected) - v.SetTitle("Login") - v.SetTitleAlign(tview.AlignLeft) - v.SetBorder(true) - v.SetBorderPadding(1, 1, 1, 1) + lf.SetTitle("Login") + lf.SetTitleAlign(tview.AlignLeft) + lf.SetBorder(true) + lf.SetBorderPadding(1, 1, 1, 1) - return v + return lf } -func (v *LoginView) onLoginButtonSelected() { +func (v *LoginForm) onLoginButtonSelected() { email := v.GetFormItem(0).(*tview.InputField).GetText() password := v.GetFormItem(1).(*tview.InputField).GetText() if email == "" || password == "" { @@ -69,7 +69,7 @@ func (v *LoginView) onLoginButtonSelected() { } v.app.SetRoot(v.app.view, true) - v.app.SetFocus(v.app.view.GuildsView) + v.app.SetFocus(v.app.view.GuildsTree) go keyring.Set(config.Name, "token", l.Token) } diff --git a/ui/input_view.go b/ui/message_input.go similarity index 62% rename from ui/input_view.go rename to ui/message_input.go index 9f3a1eb..9accb66 100644 --- a/ui/input_view.go +++ b/ui/message_input.go @@ -15,53 +15,53 @@ import ( "github.com/rivo/tview" ) -type InputView struct { +type MessageInput struct { *tview.InputField app *Application } -func newInputView(app *Application) *InputView { - v := &InputView{ +func newMessageInput(app *Application) *MessageInput { + mi := &MessageInput{ InputField: tview.NewInputField(), app: app, } - v.SetFieldBackgroundColor(tview.Styles.PrimitiveBackgroundColor) - v.SetPlaceholder("Message...") - v.SetPlaceholderStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor)) - v.SetInputCapture(v.inputCapture) + mi.SetFieldBackgroundColor(tview.Styles.PrimitiveBackgroundColor) + mi.SetPlaceholder("Message...") + mi.SetPlaceholderStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor)) + mi.SetInputCapture(mi.inputCapture) - v.SetTitleAlign(tview.AlignLeft) - v.SetBorder(true) - v.SetBorderPadding(0, 0, 1, 1) + mi.SetTitleAlign(tview.AlignLeft) + mi.SetBorder(true) + mi.SetBorderPadding(0, 0, 1, 1) - return v + return mi } -func (v *InputView) inputCapture(event *tcell.EventKey) *tcell.EventKey { +func (v *MessageInput) inputCapture(event *tcell.EventKey) *tcell.EventKey { switch event.Name() { - case v.app.config.Keys.InputView.SendMessage: + case v.app.config.Keys.MessageInput.Send: return v.sendMessage() - case v.app.config.Keys.InputView.OpenExternalEditor: + case v.app.config.Keys.MessageInput.LaunchEditor: return v.openExternalEditor() - case v.app.config.Keys.InputView.PasteClipboard: + case v.app.config.Keys.MessageInput.Paste: return v.pasteClipboard() case "Esc": v. SetText(""). SetTitle("") - v.app.view.MessagesView.selected = -1 - v.app.view.MessagesView.Highlight() + v.app.view.MessagesText.selected = -1 + v.app.view.MessagesText.Highlight() return nil } return event } -func (v *InputView) sendMessage() *tcell.EventKey { - if v.app.view.ChannelsView.selected == nil { +func (v *MessageInput) sendMessage() *tcell.EventKey { + if v.app.view.ChannelsTree.selected == nil { return nil } @@ -70,14 +70,14 @@ func (v *InputView) sendMessage() *tcell.EventKey { return nil } - ms, err := v.app.state.Messages(v.app.view.ChannelsView.selected.ID, v.app.config.MessagesLimit) + ms, err := v.app.state.Messages(v.app.view.ChannelsTree.selected.ID, v.app.config.MessagesLimit) if err != nil { log.Println(err) return nil } - if len(v.app.view.MessagesView.GetHighlights()) != 0 { - mID, err := discord.ParseSnowflake(v.app.view.MessagesView.GetHighlights()[0]) + if len(v.app.view.MessagesText.GetHighlights()) != 0 { + mID, err := discord.ParseSnowflake(v.app.view.MessagesText.GetHighlights()[0]) if err != nil { log.Println(err) return nil @@ -99,19 +99,19 @@ func (v *InputView) sendMessage() *tcell.EventKey { go v.app.state.SendMessageComplex(m.ChannelID, d) - v.app.view.MessagesView.selected = -1 - v.app.view.MessagesView.Highlight() + v.app.view.MessagesText.selected = -1 + v.app.view.MessagesText.Highlight() v.SetTitle("") } else { - go v.app.state.SendMessage(v.app.view.ChannelsView.selected.ID, t) + go v.app.state.SendMessage(v.app.view.ChannelsTree.selected.ID, t) } v.SetText("") return nil } -func (v *InputView) pasteClipboard() *tcell.EventKey { +func (v *MessageInput) pasteClipboard() *tcell.EventKey { text, err := clipboard.ReadAll() if err != nil { log.Println(err) @@ -123,7 +123,7 @@ func (v *InputView) pasteClipboard() *tcell.EventKey { return nil } -func (v *InputView) openExternalEditor() *tcell.EventKey { +func (v *MessageInput) openExternalEditor() *tcell.EventKey { e := os.Getenv("EDITOR") if e == "" { log.Println("environment variable EDITOR is empty") diff --git a/ui/messages_view.go b/ui/messages_text.go similarity index 65% rename from ui/messages_view.go rename to ui/messages_text.go index 1dda71d..1adfd0f 100644 --- a/ui/messages_view.go +++ b/ui/messages_text.go @@ -9,7 +9,7 @@ import ( "github.com/rivo/tview" ) -type MessagesView struct { +type MessagesText struct { *tview.TextView // The index of the currently selected message. A negative index indicates that there is no currently selected message. @@ -17,31 +17,31 @@ type MessagesView struct { app *Application } -func newMessagesView(app *Application) *MessagesView { - v := &MessagesView{ +func newMessagesText(app *Application) *MessagesText { + mt := &MessagesText{ TextView: tview.NewTextView(), selected: -1, app: app, } - v.SetDynamicColors(true) - v.SetRegions(true) - v.SetWordWrap(true) - v.SetInputCapture(v.onInputCapture) - v.SetChangedFunc(func() { - v.app.Draw() + mt.SetDynamicColors(true) + mt.SetRegions(true) + mt.SetWordWrap(true) + mt.SetInputCapture(mt.onInputCapture) + mt.SetChangedFunc(func() { + mt.app.Draw() }) - v.SetTitle("Messages") - v.SetTitleAlign(tview.AlignLeft) - v.SetBorder(true) - v.SetBorderPadding(0, 0, 1, 1) + mt.SetTitle("Messages") + mt.SetTitleAlign(tview.AlignLeft) + mt.SetBorder(true) + mt.SetBorderPadding(0, 0, 1, 1) - return v + return mt } -func (v *MessagesView) setTitle(c *discord.Channel) { +func (v *MessagesText) setTitle(c *discord.Channel) { title := channelToString(*c) if c.Topic != "" { title += " - " + discordmd.Parse(c.Topic) @@ -50,7 +50,7 @@ func (v *MessagesView) setTitle(c *discord.Channel) { v.SetTitle(title) } -func (v *MessagesView) loadMessages(c *discord.Channel) { +func (v *MessagesText) loadMessages(c *discord.Channel) { // The returned slice will be sorted from latest to oldest. ms, err := v.app.state.Messages(c.ID, v.app.config.MessagesLimit) if err != nil { @@ -59,7 +59,7 @@ func (v *MessagesView) loadMessages(c *discord.Channel) { } for i := len(ms) - 1; i >= 0; i-- { - _, err = v.app.view.MessagesView.Write(buildMessage(v.app, ms[i])) + _, err = v.app.view.MessagesText.Write(buildMessage(v.app, ms[i])) if err != nil { log.Println(err) continue @@ -69,32 +69,32 @@ func (v *MessagesView) loadMessages(c *discord.Channel) { v.ScrollToEnd() } -func (v *MessagesView) onInputCapture(e *tcell.EventKey) *tcell.EventKey { - if v.app.view.ChannelsView.selected == nil { +func (v *MessagesText) onInputCapture(e *tcell.EventKey) *tcell.EventKey { + if v.app.view.ChannelsTree.selected == nil { return nil } // Messages should return messages ordered from latest to earliest. - ms, err := v.app.state.Cabinet.Messages(v.app.view.ChannelsView.selected.ID) + ms, err := v.app.state.Cabinet.Messages(v.app.view.ChannelsTree.selected.ID) if err != nil || len(ms) == 0 { return nil } switch e.Name() { - case v.app.config.Keys.MessagesView.OpenActionsView: + case v.app.config.Keys.MessagesText.LaunchActions: return v.openActionsView(ms) - case v.app.config.Keys.MessagesView.SelectPreviousMessage: + case v.app.config.Keys.MessagesText.SelectPrevious: return v.selectPreviousMessage(ms) - case v.app.config.Keys.MessagesView.SelectNextMessage: + case v.app.config.Keys.MessagesText.SelectNext: return v.selectNextMessage(ms) - case v.app.config.Keys.MessagesView.SelectFirstMessage: + case v.app.config.Keys.MessagesText.SelectFirst: return v.selectFirstMessage(ms) - case v.app.config.Keys.MessagesView.SelectLastMessage: + case v.app.config.Keys.MessagesText.SelectLast: return v.selectLastMessage(ms) case "Esc": v.selected = -1 - v.app.view.ChannelsView.selected = nil + v.app.view.ChannelsTree.selected = nil v.app.SetFocus(v.app.view) v. @@ -107,7 +107,7 @@ func (v *MessagesView) onInputCapture(e *tcell.EventKey) *tcell.EventKey { return e } -func (v *MessagesView) selectPreviousMessage(ms []discord.Message) *tcell.EventKey { +func (v *MessagesText) selectPreviousMessage(ms []discord.Message) *tcell.EventKey { // If there are no highlighted regions, select the latest (last) message. if len(v.GetHighlights()) == 0 { v.selected = 0 @@ -125,7 +125,7 @@ func (v *MessagesView) selectPreviousMessage(ms []discord.Message) *tcell.EventK return nil } -func (v *MessagesView) selectNextMessage(ms []discord.Message) *tcell.EventKey { +func (v *MessagesText) selectNextMessage(ms []discord.Message) *tcell.EventKey { // If there are no highlighted regions, select the latest (last) message. if len(v.GetHighlights()) == 0 { v.selected = 0 @@ -144,7 +144,7 @@ func (v *MessagesView) selectNextMessage(ms []discord.Message) *tcell.EventKey { return nil } -func (v *MessagesView) selectFirstMessage(ms []discord.Message) *tcell.EventKey { +func (v *MessagesText) selectFirstMessage(ms []discord.Message) *tcell.EventKey { v.selected = len(ms) - 1 v. Highlight(ms[v.selected].ID.String()). @@ -152,7 +152,7 @@ func (v *MessagesView) selectFirstMessage(ms []discord.Message) *tcell.EventKey return nil } -func (v *MessagesView) selectLastMessage(ms []discord.Message) *tcell.EventKey { +func (v *MessagesText) selectLastMessage(ms []discord.Message) *tcell.EventKey { v.selected = 0 v. Highlight(ms[v.selected].ID.String()). @@ -160,7 +160,7 @@ func (v *MessagesView) selectLastMessage(ms []discord.Message) *tcell.EventKey { return nil } -func (v *MessagesView) openActionsView(ms []discord.Message) *tcell.EventKey { +func (v *MessagesText) openActionsView(ms []discord.Message) *tcell.EventKey { hs := v.GetHighlights() if len(hs) == 0 { return nil @@ -176,7 +176,7 @@ func (v *MessagesView) openActionsView(ms []discord.Message) *tcell.EventKey { return nil } - actionsView := newActionsView(v.app, m) + actionsView := newActionsList(v.app, m) v.app.SetRoot(actionsView, true) return nil } diff --git a/ui/view.go b/ui/view.go index d0a9519..7d794e0 100644 --- a/ui/view.go +++ b/ui/view.go @@ -1,98 +1,42 @@ package ui import ( - "github.com/gdamore/tcell/v2" "github.com/rivo/tview" ) -type focusedId int - -const ( - focusedIdGuildsView focusedId = iota - focusedIdChannelsView - focusedIdMessagesView - focusedIdInputView -) - type View struct { *tview.Flex - GuildsView *GuildsView - ChannelsView *ChannelsView - MessagesView *MessagesView - InputView *InputView + GuildsTree *GuildsTree + ChannelsTree *ChannelsTree + MessagesText *MessagesText + MessageInput *MessageInput - app *Application - focused focusedId + app *Application } func newView(app *Application) *View { v := &View{ Flex: tview.NewFlex(), - GuildsView: newGuildsView(app), - ChannelsView: newChannelsView(app), - MessagesView: newMessagesView(app), - InputView: newInputView(app), + GuildsTree: newGuildsTree(app), + ChannelsTree: newChannelsTree(app), + MessagesText: newMessagesText(app), + MessageInput: newMessageInput(app), app: app, } left := tview.NewFlex(). SetDirection(tview.FlexRow). - AddItem(v.GuildsView, 10, 1, false). - AddItem(v.ChannelsView, 0, 1, false) + AddItem(v.GuildsTree, 10, 1, false). + AddItem(v.ChannelsTree, 0, 1, false) right := tview.NewFlex(). SetDirection(tview.FlexRow). - AddItem(v.MessagesView, 0, 1, false). - AddItem(v.InputView, 3, 1, false) + AddItem(v.MessagesText, 0, 1, false). + AddItem(v.MessageInput, 3, 1, false) v.AddItem(left, 0, 1, false) v.AddItem(right, 0, 4, false) - v.SetInputCapture(v.onInputCapture) - return v } - -func (v *View) onInputCapture(event *tcell.EventKey) *tcell.EventKey { - switch event.Key() { - case tcell.KeyEsc: - v.focused = 0 - case tcell.KeyBacktab: - // If the currently focused view is the guilds view (first), then focus the input view (last) - if v.focused == 0 { - v.focused = focusedIdInputView - } else { - v.focused-- - } - - v.setFocus() - case tcell.KeyTab: - // If the currently focused view is the input view (last), then focus the guilds view (first) - if v.focused == focusedIdInputView { - v.focused = focusedIdGuildsView - } else { - v.focused++ - } - - v.setFocus() - } - - return event -} - -func (v *View) setFocus() { - var p tview.Primitive - switch v.focused { - case focusedIdGuildsView: - p = v.GuildsView - case focusedIdChannelsView: - p = v.ChannelsView - case focusedIdMessagesView: - p = v.MessagesView - case focusedIdInputView: - p = v.InputView - } - - v.app.SetFocus(p) -}