Explorar o código

Remove vi interface (#376)

Ayyan %!s(int64=2) %!d(string=hai) anos
pai
achega
c87f3fffc6
Modificáronse 7 ficheiros con 322 adicións e 373 borrados
  1. 11 22
      README.md
  2. 16 20
      cmd/guilds_tree.go
  3. 20 60
      cmd/main_flex.go
  4. 87 78
      cmd/message_input.go
  5. 150 136
      cmd/messages_text.go
  6. 2 2
      internal/config/config.go
  7. 36 55
      internal/config/keys.go

+ 11 - 22
README.md

@@ -51,9 +51,9 @@ go build .
 
 
 The configuration file allows you to configure and customize the behavior, keybindings, and theme of the application.
 The configuration file allows you to configure and customize the behavior, keybindings, and theme of the application.
 
 
-- Unix: `$XDG_CONFIG_HOME/discordo/config.yml` or `$HOME/.config/discordo/config.yml`
-- Darwin: `$HOME/Library/Application Support/discordo/config.yml`
-- Windows: `%AppData%/discordo/config.yml`
+- Unix: `$XDG_CONFIG_HOME/discordo/config.toml` or `$HOME/.config/discordo/config.toml`
+- Darwin: `$HOME/Library/Application Support/discordo/config.toml`
+- Windows: `%AppData%/discordo/config.toml`
 
 
 ```toml
 ```toml
 mouse = true
 mouse = true
@@ -62,24 +62,19 @@ timestamps_before_author = false
 messages_limit = 50
 messages_limit = 50
 editor = "default"
 editor = "default"
 
 
-[keys.normal]
-insert_mode = "Rune[i]"
+[keys]
 focus_guilds_tree = "Ctrl+G"
 focus_guilds_tree = "Ctrl+G"
 focus_messages_text = "Ctrl+T"
 focus_messages_text = "Ctrl+T"
 toggle_guild_tree = "Ctrl+B"
 toggle_guild_tree = "Ctrl+B"
-
-[keys.normal.guilds_tree]
-select_current = "Enter"
 select_previous = "Rune[k]"
 select_previous = "Rune[k]"
 select_next = "Rune[j]"
 select_next = "Rune[j]"
 select_first = "Rune[g]"
 select_first = "Rune[g]"
 select_last = "Rune[G]"
 select_last = "Rune[G]"
 
 
-[keys.normal.messages_text]
-select_previous = "Rune[k]"
-select_next = "Rune[j]"
-select_first = "Rune[g]"
-select_last = "Rune[G]"
+[keys.guilds_tree]
+select_current = "Enter"
+
+[keys.messages_text]
 select_reply = "Rune[s]"
 select_reply = "Rune[s]"
 reply = "Rune[r]"
 reply = "Rune[r]"
 reply_mention = "Rune[R]"
 reply_mention = "Rune[R]"
@@ -87,12 +82,10 @@ delete = "Rune[d]"
 yank = "Rune[y]"
 yank = "Rune[y]"
 open = "Rune[o]"
 open = "Rune[o]"
 
 
-[keys.insert]
-normal_mode = "Esc"
-
-[keys.insert.message_input]
+[keys.message_input]
 send = "Enter"
 send = "Enter"
 editor = "Ctrl+E"
 editor = "Ctrl+E"
+cancel = "Esc"
 
 
 [theme]
 [theme]
 border = true
 border = true
@@ -104,16 +97,12 @@ background_color = "default"
 [theme.guilds_tree]
 [theme.guilds_tree]
 auto_expand_folders = true
 auto_expand_folders = true
 graphics = true
 graphics = true
-
+  
 [theme.messages_text]
 [theme.messages_text]
 author_color = "aqua"
 author_color = "aqua"
 reply_indicator = "╭ "
 reply_indicator = "╭ "
 ```
 ```
 
 
-## Documentation
-
-[Here.](./docs)
-
 ## Disclaimer
 ## Disclaimer
 
 
 Automated user accounts or "self-bots" are against Discord's Terms of Service. I am not responsible for any loss caused by using "self-bots" or Discordo.
 Automated user accounts or "self-bots" are against Discord's Terms of Service. I am not responsible for any loss caused by using "self-bots" or Discordo.

+ 16 - 20
cmd/guilds_tree.go

@@ -108,22 +108,23 @@ func (gt *GuildsTree) channelToString(c discord.Channel) string {
 	return s
 	return s
 }
 }
 
 
-func (gt *GuildsTree) createChannelNode(n *tview.TreeNode, c discord.Channel) {
+func (gt *GuildsTree) createChannelNode(n *tview.TreeNode, c discord.Channel) *tview.TreeNode {
 	if c.Type != discord.DirectMessage && c.Type != discord.GroupDM {
 	if c.Type != discord.DirectMessage && c.Type != discord.GroupDM {
 		ps, err := discordState.Permissions(c.ID, discordState.Ready().User.ID)
 		ps, err := discordState.Permissions(c.ID, discordState.Ready().User.ID)
 		if err != nil {
 		if err != nil {
 			log.Println(err)
 			log.Println(err)
-			return
+			return nil
 		}
 		}
 
 
 		if !ps.Has(discord.PermissionViewChannel) {
 		if !ps.Has(discord.PermissionViewChannel) {
-			return
+			return nil
 		}
 		}
 	}
 	}
 
 
 	cn := tview.NewTreeNode(gt.channelToString(c))
 	cn := tview.NewTreeNode(gt.channelToString(c))
 	cn.SetReference(c.ID)
 	cn.SetReference(c.ID)
 	n.AddChild(cn)
 	n.AddChild(cn)
+	return cn
 }
 }
 
 
 func (gt *GuildsTree) createChannelNodes(n *tview.TreeNode, cs []discord.Channel) {
 func (gt *GuildsTree) createChannelNodes(n *tview.TreeNode, cs []discord.Channel) {
@@ -219,23 +220,18 @@ func (gt *GuildsTree) onSelected(n *tview.TreeNode) {
 }
 }
 
 
 func (gt *GuildsTree) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
 func (gt *GuildsTree) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
-	switch mainFlex.mode {
-	case ModeNormal:
-		switch event.Name() {
-		case cfg.Keys.Normal.GuildsTree.SelectCurrent:
-			return tcell.NewEventKey(tcell.KeyEnter, 0, tcell.ModNone)
-		case cfg.Keys.Normal.GuildsTree.SelectPrevious:
-			return tcell.NewEventKey(tcell.KeyUp, 0, tcell.ModNone)
-		case cfg.Keys.Normal.GuildsTree.SelectNext:
-			return tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone)
-		case cfg.Keys.Normal.GuildsTree.SelectFirst:
-			return tcell.NewEventKey(tcell.KeyHome, 0, tcell.ModNone)
-		case cfg.Keys.Normal.GuildsTree.SelectLast:
-			return tcell.NewEventKey(tcell.KeyEnd, 0, tcell.ModNone)
-		}
-
-		// do not propagate event to the children in normal mode.
-		return nil
+	switch event.Name() {
+	case cfg.Keys.SelectPrevious:
+		return tcell.NewEventKey(tcell.KeyUp, 0, tcell.ModNone)
+	case cfg.Keys.SelectNext:
+		return tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone)
+	case cfg.Keys.SelectFirst:
+		return tcell.NewEventKey(tcell.KeyHome, 0, tcell.ModNone)
+	case cfg.Keys.SelectLast:
+		return tcell.NewEventKey(tcell.KeyEnd, 0, tcell.ModNone)
+
+	case cfg.Keys.GuildsTree.SelectCurrent:
+		return tcell.NewEventKey(tcell.KeyEnter, 0, tcell.ModNone)
 	}
 	}
 
 
 	return event
 	return event

+ 20 - 60
cmd/main_flex.go

@@ -5,17 +5,9 @@ import (
 	"github.com/rivo/tview"
 	"github.com/rivo/tview"
 )
 )
 
 
-type Mode uint
-
-const (
-	ModeNormal Mode = iota
-	ModeInsert
-)
-
 type MainFlex struct {
 type MainFlex struct {
 	*tview.Flex
 	*tview.Flex
 
 
-	mode         Mode
 	guildsTree   *GuildsTree
 	guildsTree   *GuildsTree
 	messagesText *MessagesText
 	messagesText *MessagesText
 	messageInput *MessageInput
 	messageInput *MessageInput
@@ -25,23 +17,11 @@ func newMainFlex() *MainFlex {
 	mf := &MainFlex{
 	mf := &MainFlex{
 		Flex: tview.NewFlex(),
 		Flex: tview.NewFlex(),
 
 
-		mode:         ModeNormal,
 		guildsTree:   newGuildsTree(),
 		guildsTree:   newGuildsTree(),
 		messagesText: newMessagesText(),
 		messagesText: newMessagesText(),
 		messageInput: newMessageInput(),
 		messageInput: newMessageInput(),
 	}
 	}
 
 
-	app.SetBeforeDrawFunc(func(screen tcell.Screen) bool {
-		switch mf.mode {
-		case ModeNormal:
-			mf.messageInput.SetBorderAttributes(tcell.AttrNone)
-		case ModeInsert:
-			mf.messageInput.SetBorderAttributes(tcell.AttrDim)
-		}
-
-		return false
-	})
-
 	mf.init()
 	mf.init()
 	mf.SetInputCapture(mf.onInputCapture)
 	mf.SetInputCapture(mf.onInputCapture)
 	return mf
 	return mf
@@ -60,49 +40,29 @@ func (mf *MainFlex) init() {
 }
 }
 
 
 func (mf *MainFlex) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
 func (mf *MainFlex) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
-	switch mf.mode {
-	case ModeNormal:
-		switch event.Name() {
-		case cfg.Keys.Normal.InsertMode:
-			mf.mode = ModeInsert
-			app.SetFocus(mf.messageInput)
-			return nil
-
-		case cfg.Keys.Normal.FocusGuildsTree:
-			app.SetFocus(mf.guildsTree)
-			return nil
-		case cfg.Keys.Normal.FocusMessagesText:
-			app.SetFocus(mf.messagesText)
-			return nil
-		case cfg.Keys.Normal.ToggleGuildsTree:
-			// The guilds tree is visible if the numbers of items is two.
-			if mf.GetItemCount() == 2 {
-				mf.RemoveItem(mf.guildsTree)
-				if mf.guildsTree.HasFocus() {
-					app.SetFocus(mf)
-				}
-			} else {
-				mf.init()
-				app.SetFocus(mf.guildsTree)
+	switch event.Name() {
+	case cfg.Keys.FocusGuildsTree:
+		app.SetFocus(mf.guildsTree)
+		return nil
+	case cfg.Keys.FocusMessagesText:
+		app.SetFocus(mf.messagesText)
+		return nil
+	case cfg.Keys.FocusMessageInput:
+		app.SetFocus(mf.messageInput)
+		return nil
+	case cfg.Keys.ToggleGuildsTree:
+		// The guilds tree is visible if the numbers of items is two.
+		if mf.GetItemCount() == 2 {
+			mf.RemoveItem(mf.guildsTree)
+			if mf.guildsTree.HasFocus() {
+				app.SetFocus(mf)
 			}
 			}
-
-			return nil
-		}
-
-		// do not propagate event to the children if the message input is focused in normal mode.
-		if mf.messageInput.HasFocus() {
-			return nil
-		}
-	case ModeInsert:
-		switch event.Name() {
-		case cfg.Keys.Insert.NormalMode:
-			mf.mode = ModeNormal
-			return nil
+		} else {
+			mf.init()
+			app.SetFocus(mf.guildsTree)
 		}
 		}
 
 
-		if !mf.messageInput.HasFocus() {
-			return nil
-		}
+		return nil
 	}
 	}
 
 
 	return event
 	return event

+ 87 - 78
cmd/message_input.go

@@ -49,97 +49,106 @@ func newMessageInput() *MessageInput {
 }
 }
 
 
 func (mi *MessageInput) reset() {
 func (mi *MessageInput) reset() {
+	mi.replyMessageIdx = -1
 	mi.SetTitle("")
 	mi.SetTitle("")
 	mi.SetText("", true)
 	mi.SetText("", true)
 }
 }
 
 
 func (mi *MessageInput) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
 func (mi *MessageInput) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
-	switch mainFlex.mode {
-	case ModeInsert:
-		switch event.Name() {
-		case cfg.Keys.Insert.MessageInput.Send:
-			if !mainFlex.guildsTree.selectedChannelID.IsValid() {
-				return nil
-			}
+	switch event.Name() {
+	case cfg.Keys.MessageInput.Send:
+		mi.send()
+		return nil
+	case cfg.Keys.MessageInput.Editor:
+		mi.editor()
+		return nil
+	case cfg.Keys.MessageInput.Cancel:
+		mi.reset()
+		return nil
+	}
 
 
-			text := strings.TrimSpace(mi.GetText())
-			if text == "" {
-				return nil
-			}
+	return event
+}
 
 
-			if mi.replyMessageIdx != -1 {
-				ms, err := discordState.Cabinet.Messages(mainFlex.guildsTree.selectedChannelID)
-				if err != nil {
-					log.Println(err)
-					return nil
-				}
-
-				data := api.SendMessageData{
-					Content:         text,
-					Reference:       &discord.MessageReference{MessageID: ms[mi.replyMessageIdx].ID},
-					AllowedMentions: &api.AllowedMentions{RepliedUser: option.False},
-				}
-
-				if strings.HasPrefix(mi.GetTitle(), "[@]") {
-					data.AllowedMentions.RepliedUser = option.True
-				}
-
-				go func() {
-					if _, err := discordState.SendMessageComplex(mainFlex.guildsTree.selectedChannelID, data); err != nil {
-						log.Println("failed to send message:", err)
-					}
-				}()
-			} else {
-				go func() {
-					if _, err := discordState.SendMessage(mainFlex.guildsTree.selectedChannelID, text); err != nil {
-						log.Println("failed to send message:", err)
-					}
-				}()
-			}
+func (mi *MessageInput) send() {
+	if !mainFlex.guildsTree.selectedChannelID.IsValid() {
+		return
+	}
 
 
-			mi.replyMessageIdx = -1
-			mainFlex.messagesText.Highlight()
-			mi.reset()
-			return nil
-		case cfg.Keys.Insert.MessageInput.Editor:
-			e := cfg.Editor
-			if e == "default" {
-				e = os.Getenv("EDITOR")
-			}
+	text := strings.TrimSpace(mi.GetText())
+	if text == "" {
+		return
+	}
+
+	if mi.replyMessageIdx != -1 {
+		ms, err := discordState.Cabinet.Messages(mainFlex.guildsTree.selectedChannelID)
+		if err != nil {
+			log.Println(err)
+			return
+		}
 
 
-			f, err := os.CreateTemp("", constants.TmpFilePattern)
-			if err != nil {
-				log.Println(err)
-				return nil
+		data := api.SendMessageData{
+			Content:         text,
+			Reference:       &discord.MessageReference{MessageID: ms[mi.replyMessageIdx].ID},
+			AllowedMentions: &api.AllowedMentions{RepliedUser: option.False},
+		}
+
+		if strings.HasPrefix(mi.GetTitle(), "[@]") {
+			data.AllowedMentions.RepliedUser = option.True
+		}
+
+		go func() {
+			if _, err := discordState.SendMessageComplex(mainFlex.guildsTree.selectedChannelID, data); err != nil {
+				log.Println("failed to send message:", err)
 			}
 			}
-			_, _ = f.WriteString(mi.GetText())
-			f.Close()
-
-			defer os.Remove(f.Name())
-
-			cmd := exec.Command(e, f.Name())
-			cmd.Stdin = os.Stdin
-			cmd.Stdout = os.Stdout
-			cmd.Stderr = os.Stderr
-
-			app.Suspend(func() {
-				err := cmd.Run()
-				if err != nil {
-					log.Println(err)
-					return
-				}
-			})
-
-			msg, err := os.ReadFile(f.Name())
-			if err != nil {
-				log.Println(err)
-				return nil
+		}()
+	} else {
+		go func() {
+			if _, err := discordState.SendMessage(mainFlex.guildsTree.selectedChannelID, text); err != nil {
+				log.Println("failed to send message:", err)
 			}
 			}
+		}()
+	}
+
+	mi.replyMessageIdx = -1
+	mainFlex.messagesText.Highlight()
+	mi.reset()
+}
 
 
-			mi.SetText(strings.TrimSpace(string(msg)), true)
-			return nil
+func (mi *MessageInput) editor() {
+	e := cfg.Editor
+	if e == "default" {
+		e = os.Getenv("EDITOR")
+	}
+
+	f, err := os.CreateTemp("", constants.TmpFilePattern)
+	if err != nil {
+		log.Println(err)
+		return
+	}
+	_, _ = f.WriteString(mi.GetText())
+	f.Close()
+
+	defer os.Remove(f.Name())
+
+	cmd := exec.Command(e, f.Name())
+	cmd.Stdin = os.Stdin
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+
+	app.Suspend(func() {
+		err := cmd.Run()
+		if err != nil {
+			log.Println(err)
+			return
 		}
 		}
+	})
+
+	msg, err := os.ReadFile(f.Name())
+	if err != nil {
+		log.Println(err)
+		return
 	}
 	}
 
 
-	return event
+	mi.SetText(strings.TrimSpace(string(msg)), true)
 }
 }

+ 150 - 136
cmd/messages_text.go

@@ -1,6 +1,7 @@
 package cmd
 package cmd
 
 
 import (
 import (
+	"errors"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"log"
 	"log"
@@ -149,179 +150,192 @@ func (mt *MessagesText) createFooter(w io.Writer, m discord.Message) {
 	}
 	}
 }
 }
 
 
-func (mt *MessagesText) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
-	switch mainFlex.mode {
-	case ModeNormal:
-		switch event.Name() {
-		case cfg.Keys.Normal.MessagesText.Yank:
-			if mt.selectedMessage == -1 {
-				return nil
-			}
+func (mt *MessagesText) getSelectedMessage() (*discord.Message, error) {
+	if mt.selectedMessage == -1 {
+		return nil, errors.New("no message is currently selected")
+	}
 
 
-			ms, err := discordState.Cabinet.Messages(mainFlex.guildsTree.selectedChannelID)
-			if err != nil {
-				log.Println(err)
-				return nil
-			}
+	ms, err := discordState.Cabinet.Messages(mainFlex.guildsTree.selectedChannelID)
+	if err != nil {
+		return nil, err
+	}
 
 
-			err = clipboard.WriteAll(ms[mt.selectedMessage].Content)
-			if err != nil {
-				log.Println("failed to write to clipboard:", err)
-				return nil
-			}
+	return &ms[mt.selectedMessage], nil
+}
 
 
+func (mt *MessagesText) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
+	switch event.Name() {
+	case cfg.Keys.SelectPrevious, cfg.Keys.SelectNext, cfg.Keys.SelectFirst, cfg.Keys.SelectLast, cfg.Keys.MessagesText.SelectReply:
+		ms, err := discordState.Cabinet.Messages(mainFlex.guildsTree.selectedChannelID)
+		if err != nil {
+			log.Println(err)
 			return nil
 			return nil
+		}
 
 
-		case cfg.Keys.Normal.MessagesText.SelectFirst, cfg.Keys.Normal.MessagesText.SelectLast, cfg.Keys.Normal.MessagesText.SelectPrevious, cfg.Keys.Normal.MessagesText.SelectNext, cfg.Keys.Normal.MessagesText.SelectReply:
-			ms, err := discordState.Cabinet.Messages(mainFlex.guildsTree.selectedChannelID)
-			if err != nil {
-				log.Println(err)
-				return nil
-			}
-
-			switch event.Name() {
-			case cfg.Keys.Normal.MessagesText.SelectPrevious:
-				// If no message is currently selected, select the latest message.
-				if len(mt.GetHighlights()) == 0 {
-					mt.selectedMessage = 0
-				} else {
-					if mt.selectedMessage < len(ms)-1 {
-						mt.selectedMessage++
-					} else {
-						return nil
-					}
-				}
-			case cfg.Keys.Normal.MessagesText.SelectNext:
-				// If no message is currently selected, select the latest message.
-				if len(mt.GetHighlights()) == 0 {
-					mt.selectedMessage = 0
+		switch event.Name() {
+		case cfg.Keys.SelectPrevious:
+			// If no message is currently selected, select the latest message.
+			if len(mt.GetHighlights()) == 0 {
+				mt.selectedMessage = 0
+			} else {
+				if mt.selectedMessage < len(ms)-1 {
+					mt.selectedMessage++
 				} else {
 				} else {
-					if mt.selectedMessage > 0 {
-						mt.selectedMessage--
-					} else {
-						return nil
-					}
+					return nil
 				}
 				}
-			case cfg.Keys.Normal.MessagesText.SelectFirst:
-				mt.selectedMessage = len(ms) - 1
-			case cfg.Keys.Normal.MessagesText.SelectLast:
+			}
+		case cfg.Keys.SelectNext:
+			// If no message is currently selected, select the latest message.
+			if len(mt.GetHighlights()) == 0 {
 				mt.selectedMessage = 0
 				mt.selectedMessage = 0
-			case cfg.Keys.Normal.MessagesText.SelectReply:
-				if mt.selectedMessage == -1 {
+			} else {
+				if mt.selectedMessage > 0 {
+					mt.selectedMessage--
+				} else {
 					return nil
 					return nil
 				}
 				}
-
-				if ref := ms[mt.selectedMessage].ReferencedMessage; ref != nil {
-					for i, m := range ms {
-						if ref.ID == m.ID {
-							mt.selectedMessage = i
-						}
-					}
-				}
 			}
 			}
-
-			mt.Highlight(ms[mt.selectedMessage].ID.String())
-			mt.ScrollToHighlight()
-			return nil
-		case cfg.Keys.Normal.MessagesText.Open:
+		case cfg.Keys.SelectFirst:
+			mt.selectedMessage = len(ms) - 1
+		case cfg.Keys.SelectLast:
+			mt.selectedMessage = 0
+		case cfg.Keys.MessagesText.SelectReply:
 			if mt.selectedMessage == -1 {
 			if mt.selectedMessage == -1 {
 				return nil
 				return nil
 			}
 			}
 
 
-			ms, err := discordState.Cabinet.Messages(mainFlex.guildsTree.selectedChannelID)
-			if err != nil {
-				log.Println(err)
-				return nil
+			if ref := ms[mt.selectedMessage].ReferencedMessage; ref != nil {
+				for i, m := range ms {
+					if ref.ID == m.ID {
+						mt.selectedMessage = i
+					}
+				}
 			}
 			}
+		}
 
 
-			attachments := ms[mt.selectedMessage].Attachments
-			if len(attachments) == 0 {
-				return nil
-			}
+		mt.Highlight(ms[mt.selectedMessage].ID.String())
+		mt.ScrollToHighlight()
+		return nil
 
 
-			for _, a := range attachments {
-				go func() {
-					if err := open.Start(a.URL); err != nil {
-						log.Println(err)
-					}
-				}()
-			}
+	case cfg.Keys.MessagesText.Yank:
+		mt.yank()
+		return nil
+	case cfg.Keys.MessagesText.Open:
+		mt.open()
+		return nil
+	case cfg.Keys.MessagesText.Reply:
+		mt.reply(false)
+		return nil
+	case cfg.Keys.MessagesText.ReplyMention:
+		mt.reply(true)
+		return nil
+	case cfg.Keys.MessagesText.Delete:
+		mt.delete()
+		return nil
+	}
 
 
-			return nil
-		case cfg.Keys.Normal.MessagesText.Reply, cfg.Keys.Normal.MessagesText.ReplyMention:
-			if mt.selectedMessage == -1 {
-				return nil
-			}
+	return event
+}
 
 
-			var title string
-			if event.Name() == cfg.Keys.Normal.MessagesText.ReplyMention {
-				title += "[@] Replying to "
-			} else {
-				title += "Replying to "
-			}
+func (mt *MessagesText) yank() {
+	msg, err := mt.getSelectedMessage()
+	if err != nil {
+		log.Println(err)
+		return
+	}
 
 
-			ms, err := discordState.Cabinet.Messages(mainFlex.guildsTree.selectedChannelID)
-			if err != nil {
-				log.Println(err)
-				return nil
-			}
+	err = clipboard.WriteAll(msg.Content)
+	if err != nil {
+		log.Println("failed to write to clipboard:", err)
+		return
+	}
+}
 
 
-			title += ms[mt.selectedMessage].Author.Tag()
-			mainFlex.messageInput.SetTitle(title)
-			mainFlex.messageInput.replyMessageIdx = mt.selectedMessage
+func (mt *MessagesText) open() {
+	msg, err := mt.getSelectedMessage()
+	if err != nil {
+		log.Println(err)
+		return
+	}
 
 
-			app.SetFocus(mainFlex.messageInput)
-			return nil
-		case cfg.Keys.Normal.MessagesText.Delete:
-			if mt.selectedMessage == -1 {
-				return nil
-			}
+	attachments := msg.Attachments
+	if len(attachments) == 0 {
+		return
+	}
 
 
-			ms, err := discordState.Cabinet.Messages(mainFlex.guildsTree.selectedChannelID)
-			if err != nil {
+	for _, a := range attachments {
+		go func() {
+			if err := open.Start(a.URL); err != nil {
 				log.Println(err)
 				log.Println(err)
-				return nil
 			}
 			}
+		}()
+	}
 
 
-			m := ms[mt.selectedMessage]
-			clientID := discordState.Ready().User.ID
-
-			ps, err := discordState.Permissions(mainFlex.guildsTree.selectedChannelID, discordState.Ready().User.ID)
-			if err != nil {
-				return nil
-			}
+}
 
 
-			if m.Author.ID != clientID && !ps.Has(discord.PermissionManageMessages) {
-				return nil
-			}
+func (mt *MessagesText) reply(mention bool) {
+	var title string
+	if mention {
+		title += "[@] Replying to "
+	} else {
+		title += "Replying to "
+	}
 
 
-			if err := discordState.DeleteMessage(mainFlex.guildsTree.selectedChannelID, m.ID, ""); err != nil {
-				log.Println(err)
-			}
+	msg, err := mt.getSelectedMessage()
+	if err != nil {
+		log.Println(err)
+		return
+	}
 
 
-			if err := discordState.MessageRemove(mainFlex.guildsTree.selectedChannelID, m.ID); err != nil {
-				log.Println(err)
-			}
+	title += msg.Author.Tag()
+	mainFlex.messageInput.SetTitle(title)
+	mainFlex.messageInput.replyMessageIdx = mt.selectedMessage
+	app.SetFocus(mainFlex.messageInput)
+}
 
 
-			ms, err = discordState.Cabinet.Messages(mainFlex.guildsTree.selectedChannelID)
-			if err != nil {
-				log.Println(err)
-				return nil
-			}
+func (mt *MessagesText) delete() {
 
 
-			mt.Clear()
+	msg, err := mt.getSelectedMessage()
+	if err != nil {
+		log.Println(err)
+		return
+	}
 
 
-			for i := len(ms) - 1; i >= 0; i-- {
-				mainFlex.messagesText.createMessage(ms[i])
-			}
+	clientID := discordState.Ready().User.ID
+	if msg.GuildID.IsValid() {
+		ps, err := discordState.Permissions(mainFlex.guildsTree.selectedChannelID, discordState.Ready().User.ID)
+		if err != nil {
+			return
+		}
 
 
-			return nil
+		if msg.Author.ID != clientID && !ps.Has(discord.PermissionManageMessages) {
+			return
 		}
 		}
+	} else {
+		if msg.Author.ID != clientID {
+			return
+		}
+	}
 
 
-		// do not propagate event to the children in normal mode.
-		return nil
+	if err := discordState.DeleteMessage(mainFlex.guildsTree.selectedChannelID, msg.ID, ""); err != nil {
+		log.Println(err)
+		return
+	}
 
 
+	if err := discordState.MessageRemove(mainFlex.guildsTree.selectedChannelID, msg.ID); err != nil {
+		log.Println(err)
+	}
+
+	ms, err := discordState.Cabinet.Messages(mainFlex.guildsTree.selectedChannelID)
+	if err != nil {
+		log.Println(err)
+		return
+	}
+
+	mt.Clear()
+
+	for i := len(ms) - 1; i >= 0; i-- {
+		mainFlex.messagesText.createMessage(ms[i])
 	}
 	}
 
 
-	return event
 }
 }

+ 2 - 2
internal/config/config.go

@@ -22,7 +22,7 @@ type Config struct {
 	Theme Theme `toml:"theme"`
 	Theme Theme `toml:"theme"`
 }
 }
 
 
-func defaultConfig() Config {
+func DefaultConfig() Config {
 	return Config{
 	return Config{
 		Mouse: true,
 		Mouse: true,
 
 
@@ -44,7 +44,7 @@ func Load() (*Config, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	cfg := defaultConfig()
+	cfg := DefaultConfig()
 	path = filepath.Join(path, constants.Name, "config.toml")
 	path = filepath.Join(path, constants.Name, "config.toml")
 	f, err := os.Open(path)
 	f, err := os.Open(path)
 	if os.IsNotExist(err) {
 	if os.IsNotExist(err) {

+ 36 - 55
internal/config/keys.go

@@ -2,35 +2,27 @@ package config
 
 
 type (
 type (
 	Keys struct {
 	Keys struct {
-		Normal NormalModeKeys `toml:"normal"`
-		Insert InsertModeKeys `toml:"insert"`
-	}
-
-	NormalModeKeys struct {
-		InsertMode        string `toml:"insert_mode"`
 		FocusGuildsTree   string `toml:"focus_guilds_tree"`
 		FocusGuildsTree   string `toml:"focus_guilds_tree"`
 		FocusMessagesText string `toml:"focus_messages_text"`
 		FocusMessagesText string `toml:"focus_messages_text"`
+		FocusMessageInput string `toml:"focus_message_input"`
 		ToggleGuildsTree  string `toml:"toggle_guild_tree"`
 		ToggleGuildsTree  string `toml:"toggle_guild_tree"`
 
 
-		GuildsTree   GuildsTreeNormalModeKeys   `toml:"guilds_tree"`
-		MessagesText MessagesTextNormalModeKeys `toml:"messages_text"`
-	}
-
-	GuildsTreeNormalModeKeys struct {
-		SelectCurrent  string `toml:"select_current"`
 		SelectPrevious string `toml:"select_previous"`
 		SelectPrevious string `toml:"select_previous"`
 		SelectNext     string `toml:"select_next"`
 		SelectNext     string `toml:"select_next"`
 		SelectFirst    string `toml:"select_first"`
 		SelectFirst    string `toml:"select_first"`
 		SelectLast     string `toml:"select_last"`
 		SelectLast     string `toml:"select_last"`
+
+		GuildsTree   GuildsTreeKeys   `toml:"guilds_tree"`
+		MessagesText MessagesTextKeys `toml:"messages_text"`
+		MessageInput MessageInputKeys `toml:"message_input"`
 	}
 	}
 
 
-	MessagesTextNormalModeKeys struct {
-		SelectPrevious string `toml:"select_previous"`
-		SelectNext     string `toml:"select_next"`
-		SelectFirst    string `toml:"select_first"`
-		SelectLast     string `toml:"select_last"`
-		SelectReply    string `toml:"select_reply"`
+	GuildsTreeKeys struct {
+		SelectCurrent string `toml:"select_current"`
+	}
 
 
+	MessagesTextKeys struct {
+		SelectReply  string `toml:"select_reply"`
 		Reply        string `toml:"reply"`
 		Reply        string `toml:"reply"`
 		ReplyMention string `toml:"reply_mention"`
 		ReplyMention string `toml:"reply_mention"`
 
 
@@ -39,55 +31,44 @@ type (
 		Open   string `toml:"open"`
 		Open   string `toml:"open"`
 	}
 	}
 
 
-	InsertModeKeys struct {
-		NormalMode string `toml:"normal_mode"`
-
-		MessageInput MessageInputInsertModeKeys `toml:"message_input"`
-	}
-
-	MessageInputInsertModeKeys struct {
+	MessageInputKeys struct {
 		Send   string `toml:"send"`
 		Send   string `toml:"send"`
 		Editor string `toml:"editor"`
 		Editor string `toml:"editor"`
+		Cancel string `toml:"cancel"`
 	}
 	}
 )
 )
 
 
 func defaultKeys() Keys {
 func defaultKeys() Keys {
 	return Keys{
 	return Keys{
-		Normal: NormalModeKeys{
-			InsertMode: "Rune[i]",
+		FocusGuildsTree:   "Ctrl+G",
+		FocusMessagesText: "Ctrl+T",
+		FocusMessageInput: "Ctrl+P",
+		ToggleGuildsTree:  "Ctrl+B",
+
+		SelectPrevious: "Rune[k]",
+		SelectNext:     "Rune[j]",
+		SelectFirst:    "Rune[g]",
+		SelectLast:     "Rune[G]",
 
 
-			FocusGuildsTree:   "Ctrl+G",
-			FocusMessagesText: "Ctrl+T",
-			ToggleGuildsTree:  "Ctrl+B",
+		GuildsTree: GuildsTreeKeys{
+			SelectCurrent: "Enter",
+		},
 
 
-			GuildsTree: GuildsTreeNormalModeKeys{
-				SelectCurrent:  "Enter",
-				SelectPrevious: "Rune[k]",
-				SelectNext:     "Rune[j]",
-				SelectFirst:    "Rune[g]",
-				SelectLast:     "Rune[G]",
-			},
-			MessagesText: MessagesTextNormalModeKeys{
-				SelectPrevious: "Rune[k]",
-				SelectNext:     "Rune[j]",
-				SelectFirst:    "Rune[g]",
-				SelectLast:     "Rune[G]",
-				SelectReply:    "Rune[s]",
+		MessagesText: MessagesTextKeys{
+			SelectReply: "Rune[s]",
 
 
-				Reply:        "Rune[r]",
-				ReplyMention: "Rune[R]",
+			Reply:        "Rune[r]",
+			ReplyMention: "Rune[R]",
 
 
-				Delete: "Rune[d]",
-				Yank:   "Rune[y]",
-				Open:   "Rune[o]",
-			},
+			Delete: "Rune[d]",
+			Yank:   "Rune[y]",
+			Open:   "Rune[o]",
 		},
 		},
-		Insert: InsertModeKeys{
-			NormalMode: "Esc",
-			MessageInput: MessageInputInsertModeKeys{
-				Send:   "Enter",
-				Editor: "Ctrl+E",
-			},
+
+		MessageInput: MessageInputKeys{
+			Send:   "Enter",
+			Editor: "Ctrl+E",
+			Cancel: "Esc",
 		},
 		},
 	}
 	}
 }
 }