Pārlūkot izejas kodu

Use yaml for configuration instead of Lua (#215)

ayntgl 3 gadi atpakaļ
vecāks
revīzija
40e2e7e6a5
12 mainītis faili ar 148 papildinājumiem un 540 dzēšanām
  1. 1 1
      README.md
  2. 78 42
      config/config.go
  3. 0 107
      config/config.lua
  4. 1 2
      go.mod
  5. 1 9
      go.sum
  6. 5 21
      main.go
  7. 3 9
      ui/builder.go
  8. 1 3
      ui/channels_tree.go
  9. 18 201
      ui/core.go
  10. 16 55
      ui/message_input.go
  11. 24 84
      ui/messages_panel.go
  12. 0 6
      ui/util.go

+ 1 - 1
README.md

@@ -73,7 +73,7 @@ sudo mv ./discordo /usr/local/bin
 
 ### Configuration
 
-A default configuration file is created on first start-up at `$HOME/.config/discordo/config.lua` on Unix, `$HOME/Library/Application Support/discordo/config.lua` on Darwin, and `%AppData%/discordo/config.lua` on Windows. You can configure the default configuration directory path using the `config` command-line flag (eg: `discordo --config $HOME/my-custom-dir).
+A default configuration file is created on first start-up at `$HOME/.config/discordo/config.yml` on Unix, `$HOME/Library/Application Support/discordo/config.yml` on Darwin, and `%AppData%/discordo/config.yml` on Windows. You can configure the default configuration directory path using the `config` command-line flag (eg: `discordo --config $HOME/my-custom-dir).
 
 ## Disclaimer
 

+ 78 - 42
config/config.go

@@ -2,78 +2,114 @@ package config
 
 import (
 	_ "embed"
-	"io"
 	"os"
 	"path/filepath"
+	"time"
 
-	lua "github.com/yuin/gopher-lua"
+	"gopkg.in/yaml.v3"
 )
 
 const Name = "discordo"
 
-//go:embed config.lua
-var LuaConfig []byte
+type MessagesPanelKeysConfig struct {
+	OpenActionsList string `yaml:"open_actions_list"`
+
+	SelectPreviousMessage string `yaml:"select_previous_message"`
+	SelectNextMessage     string `yaml:"select_next_message"`
+	SelectFirstMessage    string `yaml:"select_first_message"`
+	SelectLastMessage     string `yaml:"select_last_message"`
+}
+
+type MessageInputKeysConfig struct {
+	OpenExternalEditor string `yaml:"open_external_editor"`
+	PasteClipboard     string `yaml:"paste_clipboard"`
+}
+
+type KeysConfig struct {
+	MessagesPanel MessagesPanelKeysConfig `yaml:"messages_panel"`
+	MessageInput  MessageInputKeysConfig  `yaml:"message_input"`
+}
+
+type ThemeConfig struct {
+	Background string `yaml:"background"`
+	Border     string `yaml:"border"`
+	Title      string `yaml:"title"`
+}
 
-// Config initializes a new Lua state, loads a configuration file, and defines essential micellaneous fields.
 type Config struct {
-	// Path is the path of the configuration file. Its value is the configuration directory until Load() is called.
-	Path  string
-	State *lua.LState
+	// Whether the mouse is usable or not.
+	Mouse bool `yaml:"mouse"`
+	// The maximum number of messages to fetch and display on the messages panel. Its value must not be lesser than 1 and greater than 100.
+	MessagesLimit uint `yaml:"messages_limit"`
+	// Whether to display the timestamps of the messages beside the displayed message or not.
+	Timestamps bool `yaml:"timestamps"`
+	// The timezone of the timestamps. Learn more: https://pkg.go.dev/time#LoadLocation
+	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
+	Theme ThemeConfig `yaml:"theme"`
 }
 
-func New(path string) *Config {
+func New() *Config {
 	return &Config{
-		Path:  path,
-		State: lua.NewState(),
+		Mouse:         true,
+		MessagesLimit: 50,
+
+		Timestamps: false,
+		Timezone:   "Local",
+		TimeFormat: time.Kitchen,
+
+		Keys: KeysConfig{
+			MessagesPanel: MessagesPanelKeysConfig{
+				OpenActionsList: "Rune[a]",
+
+				SelectPreviousMessage: "Up",
+				SelectNextMessage:     "Down",
+				SelectFirstMessage:    "Home",
+				SelectLastMessage:     "End",
+			},
+		},
+		Theme: ThemeConfig{
+			Background: "default",
+			Border:     "white",
+			Title:      "white",
+		},
 	}
 }
 
 func (c *Config) Load() error {
-	// Create directories that do not exist and are mentioned in the path recursively.
-	err := os.MkdirAll(c.Path, os.ModePerm)
+	configPath, err := os.UserConfigDir()
 	if err != nil {
 		return err
 	}
 
-	c.Path = filepath.Join(c.Path, "config.lua")
-	// Open the existing configuration file with read-only flag.
-	f, err := os.Open(c.Path)
-	// If the configuration file does not exist, create a new configuration file with the read-write flag.
-	if os.IsNotExist(err) {
-		f, err = os.Create(c.Path)
-		if err != nil {
-			return err
-		}
-		defer f.Close()
-
-		_, err = f.Write(LuaConfig)
-		if err != nil {
-			return err
-		}
-
-		return f.Sync()
+	configPath = filepath.Join(configPath, Name)
+	// Create directories that do not exist and are mentioned in the path recursively.
+	err = os.MkdirAll(configPath, os.ModePerm)
+	if err != nil {
+		return err
 	}
 
+	configPath = filepath.Join(configPath, "config.yml")
+	// Open the existing configuration file with read-only flag.
+	f, err := os.OpenFile(configPath, os.O_CREATE|os.O_RDWR, os.ModePerm)
 	if err != nil {
 		return err
 	}
 	defer f.Close()
 
-	b, err := io.ReadAll(f)
+	fi, err := f.Stat()
 	if err != nil {
 		return err
 	}
 
-	LuaConfig = b
-	return nil
-}
-
-func (c *Config) KeyLua(s *lua.LState) int {
-	keyTable := s.NewTable()
-	keyTable.RawSetString("name", s.Get(1))
-	keyTable.RawSetString("description", s.Get(2))
-	keyTable.RawSetString("action", s.Get(3))
+	// 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(c)
+	}
 
-	s.Push(keyTable) // Push the result
-	return 1         // Number of results
+	return yaml.NewDecoder(f).Decode(&c)
 }

+ 0 - 107
config/config.lua

@@ -1,107 +0,0 @@
-local string = require "string"
-
--- Whether the mouse is usable or not.
-mouse = true
-
--- The maximum number of messages to fetch and display on the messages panel.
--- Its value must not be lesser than 1 and greater than 100.
-messagesLimit = 50
-
--- Whether to display the timestamp of the message beside the displayed message or not.
-timestamps = false
-
--- The timezone of the timestamps.
--- Learn more: https://pkg.go.dev/time#LoadLocation
-timezone = "Local"
-
--- 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 = "3:04PM"
-
-browser = "Chrome"
-browserVersion = "104.0.5112.102"
-oss = "Linux"
-
--- Identify properties are connection properties that are dispatched in the IDENTIFY gateway event to trigger the initial handshake with the gateway.
--- Learn more: https://discord.com/developers/docs/topics/gateway#identify
-identifyProperties = {
-    userAgent = string.format(
-        "Mozilla/5.0 (X11; %s x86_64) AppleWebKit/537.36 (KHTML, like Gecko) %s/%s Safari/537.36",
-        oss,
-        browser,
-        browserVersion
-    ),
-    browser = browser,
-    browserVersion = browserVersion,
-    os = oss
-}
-
--- Keybindings
-keys = {
-    -- application = {
-    --     key(
-    --         "Ctrl+R",
-    --         "Refresh the screen.",
-    --         function(core, event)
-    --             core.Application:Sync()
-    --             return nil
-    --         end
-    --     )
-    -- },
-    messagesPanel = {
-        key(
-            "Rune[a]",
-            "Open the message actions list widget.",
-            function(core, event)
-                return openMessageActionsList()
-            end
-        ),
-        key(
-            "Up",
-            "Select the previous message.",
-            function(core, event)
-                return selectPreviousMessage()
-            end
-        ),
-        key(
-            "Down",
-            "Select the next message.",
-            function(core, event)
-                return selectNextMessage()
-            end
-        ),
-        key(
-            "Home",
-            "Select the first message.",
-            function(core, event)
-                return selectFirstMessage()
-            end
-        ),
-        key(
-            "End",
-            "Select the last message.",
-            function(core, event)
-                return selectLastMessage()
-            end
-        )
-    },
-    messageInput = {
-        key(
-            "Ctrl+E",
-            "Open the external editor.",
-            function()
-                return openExternalEditor()
-            end
-        ),
-        key(
-            "Ctrl+V",
-            "Paste the clipboard content.",
-            function()
-                return pasteClipboardContent()
-            end
-        )
-    }
-}
-
--- Theme
-theme = {background = "default", border = "white", title = "white"}

+ 1 - 2
go.mod

@@ -8,9 +8,8 @@ require (
 	github.com/gdamore/tcell/v2 v2.5.3
 	github.com/rivo/tview v0.0.0-20220906194528-4664d8bf22d9
 	github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
-	github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64
 	github.com/zalando/go-keyring v0.2.1
-	layeh.com/gopher-luar v1.0.10
+	gopkg.in/yaml.v3 v3.0.1
 )
 
 require (

+ 1 - 9
go.sum

@@ -2,9 +2,6 @@ github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVK
 github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
 github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
 github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
-github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
 github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
 github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
@@ -46,14 +43,10 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
 github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
-github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
-github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
-github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
 github.com/zalando/go-keyring v0.2.1 h1:MBRN/Z8H4U5wEKXiD67YbDAr5cj/DOStmSga70/2qKc=
 github.com/zalando/go-keyring v0.2.1/go.mod h1:g63M2PPn0w5vjmEbwAX3ib5I+41zdm4esSETOn9Y6Dw=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -73,11 +66,10 @@ golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxb
 golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
 golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-layeh.com/gopher-luar v1.0.10 h1:55b0mpBhN9XSshEd2Nz6WsbYXctyBT35azk4POQNSXo=
-layeh.com/gopher-luar v1.0.10/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=

+ 5 - 21
main.go

@@ -3,8 +3,6 @@ package main
 import (
 	"flag"
 	"log"
-	"os"
-	"path/filepath"
 
 	"github.com/ayntgl/discordo/config"
 	"github.com/ayntgl/discordo/ui"
@@ -12,38 +10,24 @@ import (
 	"github.com/zalando/go-keyring"
 )
 
-const (
-	name = "discordo"
-)
-
-var (
-	token      string
-	configPath string
-)
+var token string
 
 func init() {
 	flag.StringVar(&token, "token", "", "The client authentication token.")
 	// If the token is provided via a command-line flag, store it in the default keyring.
 	if token != "" {
-		go keyring.Set(name, "token", token)
+		go keyring.Set(config.Name, "token", token)
 	}
 
 	if token == "" {
-		token, _ = keyring.Get(name, "token")
-	}
-
-	configDirPath, err := os.UserConfigDir()
-	if err != nil {
-		log.Fatal(err)
+		token, _ = keyring.Get(config.Name, "token")
 	}
-
-	flag.StringVar(&configPath, "config", filepath.Join(configDirPath, name), "The path to the configuration directory.")
 }
 
 func main() {
 	flag.Parse()
 
-	cfg := config.New(configPath)
+	cfg := config.New()
 	err := cfg.Load()
 	if err != nil {
 		log.Fatal(err)
@@ -80,7 +64,7 @@ func main() {
 				log.Fatal(err)
 			}
 
-			go keyring.Set(name, "token", tkn)
+			go keyring.Set(config.Name, "token", tkn)
 
 			c.DrawMainFlex()
 			c.Application.SetRoot(c.MainFlex, true)

+ 3 - 9
ui/builder.go

@@ -6,7 +6,6 @@ import (
 	"time"
 
 	"github.com/diamondburned/arikawa/v3/discord"
-	lua "github.com/yuin/gopher-lua"
 )
 
 func buildMessage(c *Core, m discord.Message) []byte {
@@ -23,19 +22,14 @@ func buildMessage(c *Core, m discord.Message) []byte {
 		// Build the message associated with crosspost, channel follow add, pin, or a reply.
 		buildReferencedMessage(&b, m.ReferencedMessage, c.State.Ready().User.ID)
 
-		timestamps := c.Config.State.GetGlobal("timestamps")
-
-		if lua.LVAsBool(timestamps) {
-			timezone := c.Config.State.GetGlobal("timezone")
-			loc, err := time.LoadLocation(lua.LVAsString(timezone))
+		if c.Config.Timestamps {
+			loc, err := time.LoadLocation(c.Config.Timezone)
 			if err != nil {
 				return nil
 			}
 
-			timeFormat := c.Config.State.GetGlobal("timeFormat")
-
 			b.WriteString("[::d]")
-			b.WriteString(m.Timestamp.Time().In(loc).Format(lua.LVAsString(timeFormat)))
+			b.WriteString(m.Timestamp.Time().In(loc).Format(c.Config.TimeFormat))
 			b.WriteString("[::-]")
 			b.WriteByte(' ')
 		}

+ 1 - 3
ui/channels_tree.go

@@ -5,7 +5,6 @@ import (
 
 	"github.com/diamondburned/arikawa/v3/discord"
 	"github.com/rivo/tview"
-	lua "github.com/yuin/gopher-lua"
 )
 
 type ChannelsTree struct {
@@ -64,9 +63,8 @@ func (ct *ChannelsTree) onSelected(node *tview.TreeNode) {
 	ct.core.MessagesPanel.SetTitle(title)
 
 	go func() {
-		messagesLimit := ct.core.Config.State.GetGlobal("messagesLimit")
 		// The returned slice will be sorted from latest to oldest.
-		ms, err := ct.core.State.Messages(c.ID, uint(lua.LVAsNumber(messagesLimit)))
+		ms, err := ct.core.State.Messages(c.ID, ct.core.Config.MessagesLimit)
 		if err != nil {
 			return
 		}

+ 18 - 201
ui/core.go

@@ -1,15 +1,9 @@
 package ui
 
 import (
-	"strings"
-
 	"github.com/ayntgl/discordo/config"
-	"github.com/diamondburned/arikawa/v3/discord"
-	"github.com/diamondburned/arikawa/v3/gateway"
 	"github.com/gdamore/tcell/v2"
 	"github.com/rivo/tview"
-	lua "github.com/yuin/gopher-lua"
-	luar "layeh.com/gopher-luar"
 )
 
 type focused int
@@ -47,7 +41,14 @@ func NewCore(cfg *config.Config) *Core {
 		Config: cfg,
 	}
 
+	tview.Styles.PrimitiveBackgroundColor = tcell.GetColor(cfg.Theme.Background)
+	tview.Styles.BorderColor = tcell.GetColor(cfg.Theme.Border)
+	tview.Styles.TitleColor = tcell.GetColor(cfg.Theme.Title)
+
+	c.Application.EnableMouse(c.Config.Mouse)
 	c.Application.SetInputCapture(c.onInputCapture)
+	c.Application.SetBeforeDrawFunc(c.beforeDraw)
+
 	c.GuildsTree = NewGuildsTree(c)
 	c.ChannelsTree = NewChannelsTree(c)
 	c.MessagesPanel = NewMessagesPanel(c)
@@ -56,65 +57,10 @@ func NewCore(cfg *config.Config) *Core {
 }
 
 func (c *Core) Run(token string) error {
-	c.register()
-	err := c.Config.State.DoString(string(config.LuaConfig))
-	if err != nil {
-		return err
-	}
-
-	themeTable, ok := c.Config.State.GetGlobal("theme").(*lua.LTable)
-	if !ok {
-		themeTable = c.Config.State.NewTable()
-	}
-
-	backgroundColor := tcell.GetColor(lua.LVAsString(themeTable.RawGetString("background")))
-	borderColor := tcell.GetColor(lua.LVAsString(themeTable.RawGetString("border")))
-	titleColor := tcell.GetColor(lua.LVAsString(themeTable.RawGetString("title")))
-
-	c.GuildsTree.SetBackgroundColor(backgroundColor)
-	c.GuildsTree.SetBorderColor(borderColor)
-	c.GuildsTree.SetTitleColor(titleColor)
-
-	c.ChannelsTree.SetBackgroundColor(backgroundColor)
-	c.ChannelsTree.SetBorderColor(borderColor)
-	c.ChannelsTree.SetTitleColor(titleColor)
-
-	c.MessagesPanel.SetBackgroundColor(backgroundColor)
-	c.MessagesPanel.SetBorderColor(borderColor)
-	c.MessagesPanel.SetTitleColor(titleColor)
-
-	c.MessageInput.SetBackgroundColor(backgroundColor)
-	c.MessageInput.SetBorderColor(borderColor)
-	c.MessageInput.SetTitleColor(titleColor)
-	c.MessageInput.SetPlaceholderStyle(tcell.StyleDefault.Background(backgroundColor))
-
-	c.Application.SetBeforeDrawFunc(func(s tcell.Screen) bool {
-		if backgroundColor == 0 {
-			s.Clear()
-		}
-
-		return false
-	})
-
-	c.Application.EnableMouse(lua.LVAsBool(c.Config.State.GetGlobal("mouse")))
-
 	c.State = NewState(token, c)
 	return c.State.Run()
 }
 
-func (c *Core) register() {
-	c.Config.State.SetGlobal("key", c.Config.State.NewFunction(c.Config.KeyLua))
-	// Messages panel
-	c.Config.State.SetGlobal("openMessageActionsList", c.Config.State.NewFunction(c.MessagesPanel.openMessageActionsListLua))
-	c.Config.State.SetGlobal("selectPreviousMessage", c.Config.State.NewFunction(c.MessagesPanel.selectPreviousMessageLua))
-	c.Config.State.SetGlobal("selectNextMessage", c.Config.State.NewFunction(c.MessagesPanel.selectNextMessageLua))
-	c.Config.State.SetGlobal("selectFirstMessage", c.Config.State.NewFunction(c.MessagesPanel.selectFirstMessageLua))
-	c.Config.State.SetGlobal("selectLastMessage", c.Config.State.NewFunction(c.MessagesPanel.selectLastMessageLua))
-	// Message input
-	c.Config.State.SetGlobal("openExternalEditor", c.Config.State.NewFunction(c.MessageInput.openExternalEditorLua))
-	c.Config.State.SetGlobal("pasteClipboardContent", c.Config.State.NewFunction(c.MessageInput.pasteClipboardContentLua))
-}
-
 func (c *Core) DrawMainFlex() {
 	leftFlex := tview.NewFlex().
 		SetDirection(tview.FlexRow).
@@ -129,52 +75,21 @@ func (c *Core) DrawMainFlex() {
 		AddItem(rightFlex, 0, 4, false)
 }
 
-func (c *Core) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
-	// If the main flex is nil, that is, it is not initialized yet, then the login form is currently focused.
-	if c.MainFlex == nil {
-		return e
-	}
-
-	keysTable, ok := c.Config.State.GetGlobal("keys").(*lua.LTable)
-	if !ok {
-		keysTable = c.Config.State.NewTable()
-	}
-
-	applicationTable, ok := keysTable.RawGetString("application").(*lua.LTable)
-	if !ok {
-		applicationTable = c.Config.State.NewTable()
+func (c *Core) beforeDraw(screen tcell.Screen) bool {
+	if c.Config.Theme.Background == "default" {
+		screen.Clear()
 	}
 
-	var fn lua.LValue
-	applicationTable.ForEach(func(k, v lua.LValue) {
-		keyTable := v.(*lua.LTable)
-		if e.Name() == lua.LVAsString(keyTable.RawGetString("name")) {
-			fn = keyTable.RawGetString("action")
-		}
-	})
-
-	if fn != nil {
-		c.Config.State.CallByParam(lua.P{
-			Fn:      fn,
-			NRet:    1,
-			Protect: true,
-		}, luar.New(c.Config.State, c), luar.New(c.Config.State, e))
-		// Returned value
-		ret, ok := c.Config.State.Get(-1).(*lua.LUserData)
-		if !ok {
-			return e
-		}
+	return false
+}
 
-		// Remove returned value
-		c.Config.State.Pop(1)
-		ev, ok := ret.Value.(*tcell.EventKey)
-		if ok {
-			return ev
-		}
+func (c *Core) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
+	// If the main flex is nil, that is, it is not initialized yet, then the login form is currently focused.
+	if c.MainFlex == nil {
+		return event
 	}
 
-	// Default
-	switch e.Key() {
+	switch event.Key() {
 	case tcell.KeyEsc:
 		c.focused = 0
 	case tcell.KeyBacktab:
@@ -197,7 +112,7 @@ func (c *Core) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
 		c.setFocus()
 	}
 
-	return e
+	return event
 }
 
 func (c *Core) setFocus() {
@@ -215,101 +130,3 @@ func (c *Core) setFocus() {
 
 	c.Application.SetFocus(p)
 }
-
-func (c *Core) onStateReady(r *gateway.ReadyEvent) {
-	rootNode := c.GuildsTree.GetRoot()
-	for _, gf := range r.UserSettings.GuildFolders {
-		if gf.ID == 0 {
-			for _, gID := range gf.GuildIDs {
-				g, err := c.State.Cabinet.Guild(gID)
-				if err != nil {
-					return
-				}
-
-				guildNode := tview.NewTreeNode(g.Name)
-				guildNode.SetReference(g.ID)
-				rootNode.AddChild(guildNode)
-			}
-		} else {
-			var b strings.Builder
-
-			if gf.Color != discord.NullColor {
-				b.WriteByte('[')
-				b.WriteString(gf.Color.String())
-				b.WriteByte(']')
-			} else {
-				b.WriteString("[#ED4245]")
-			}
-
-			if gf.Name != "" {
-				b.WriteString(gf.Name)
-			} else {
-				b.WriteString("Folder")
-			}
-
-			b.WriteString("[-]")
-
-			folderNode := tview.NewTreeNode(b.String())
-			rootNode.AddChild(folderNode)
-
-			for _, gID := range gf.GuildIDs {
-				g, err := c.State.Cabinet.Guild(gID)
-				if err != nil {
-					return
-				}
-
-				guildNode := tview.NewTreeNode(g.Name)
-				guildNode.SetReference(g.ID)
-				folderNode.AddChild(guildNode)
-			}
-		}
-
-	}
-
-	c.GuildsTree.SetCurrentNode(rootNode)
-	c.Application.SetFocus(c.GuildsTree)
-}
-
-func (c *Core) onStateGuildCreate(g *gateway.GuildCreateEvent) {
-	guildNode := tview.NewTreeNode(g.Name)
-	guildNode.SetReference(g.ID)
-
-	rootNode := c.GuildsTree.GetRoot()
-	rootNode.AddChild(guildNode)
-
-	c.GuildsTree.SetCurrentNode(rootNode)
-	c.Application.SetFocus(c.GuildsTree)
-	c.Application.Draw()
-}
-
-func (c *Core) onStateGuildDelete(g *gateway.GuildDeleteEvent) {
-	rootNode := c.GuildsTree.GetRoot()
-	var parentNode *tview.TreeNode
-	rootNode.Walk(func(node, _ *tview.TreeNode) bool {
-		if node.GetReference() == g.ID {
-			parentNode = node
-			return false
-		}
-
-		return true
-	})
-
-	if parentNode != nil {
-		rootNode.RemoveChild(parentNode)
-	}
-
-	c.Application.Draw()
-}
-
-func (c *Core) onStateMessageCreate(m *gateway.MessageCreateEvent) {
-	if c.ChannelsTree.SelectedChannel != nil && c.ChannelsTree.SelectedChannel.ID == m.ChannelID {
-		_, err := c.MessagesPanel.Write(buildMessage(c, m.Message))
-		if err != nil {
-			return
-		}
-
-		if len(c.MessagesPanel.GetHighlights()) == 0 {
-			c.MessagesPanel.ScrollToEnd()
-		}
-	}
-}

+ 16 - 55
ui/message_input.go

@@ -12,8 +12,6 @@ import (
 	"github.com/diamondburned/arikawa/v3/utils/json/option"
 	"github.com/gdamore/tcell/v2"
 	"github.com/rivo/tview"
-	lua "github.com/yuin/gopher-lua"
-	luar "layeh.com/gopher-luar"
 )
 
 type MessageInput struct {
@@ -30,7 +28,7 @@ func NewMessageInput(c *Core) *MessageInput {
 	mi.SetFieldBackgroundColor(tview.Styles.PrimitiveBackgroundColor)
 	mi.SetPlaceholder("Message...")
 	mi.SetPlaceholderStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor))
-	mi.SetInputCapture(mi.onInputCapture)
+	mi.SetInputCapture(mi.inputCapture)
 
 	mi.SetTitleAlign(tview.AlignLeft)
 	mi.SetBorder(true)
@@ -39,50 +37,14 @@ func NewMessageInput(c *Core) *MessageInput {
 	return mi
 }
 
-func (mi *MessageInput) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
-	keysTable, ok := mi.core.Config.State.GetGlobal("keys").(*lua.LTable)
-	if !ok {
-		keysTable = mi.core.Config.State.NewTable()
-	}
-
-	messageInputTable, ok := keysTable.RawGetString("messageInput").(*lua.LTable)
-	if !ok {
-		messageInputTable = mi.core.Config.State.NewTable()
-	}
-
-	var fn lua.LValue
-	messageInputTable.ForEach(func(k, v lua.LValue) {
-		keyTable := v.(*lua.LTable)
-		if e.Name() == lua.LVAsString(keyTable.RawGetString("name")) {
-			fn = keyTable.RawGetString("action")
-		}
-	})
-
-	if fn != nil {
-		mi.core.Config.State.CallByParam(lua.P{
-			Fn:      fn,
-			NRet:    1,
-			Protect: true,
-		}, luar.New(mi.core.Config.State, mi.core), luar.New(mi.core.Config.State, e))
-		// Returned value
-		ret, ok := mi.core.Config.State.Get(-1).(*lua.LUserData)
-		if !ok {
-			return e
-		}
-
-		// Remove returned value
-		mi.core.Config.State.Pop(1)
-
-		ev, ok := ret.Value.(*tcell.EventKey)
-		if ok {
-			return ev
-		}
-	}
-
-	// Defaults
-	switch e.Name() {
+func (mi *MessageInput) inputCapture(event *tcell.EventKey) *tcell.EventKey {
+	switch event.Name() {
 	case "Enter":
 		return mi.sendMessage()
+	case mi.core.Config.Keys.MessageInput.OpenExternalEditor:
+		return mi.openExternalEditor()
+	case mi.core.Config.Keys.MessageInput.PasteClipboard:
+		return mi.pasteClipboard()
 	case "Esc":
 		mi.
 			SetText("").
@@ -92,7 +54,7 @@ func (mi *MessageInput) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
 		return nil
 	}
 
-	return e
+	return event
 }
 
 func (mi *MessageInput) sendMessage() *tcell.EventKey {
@@ -105,8 +67,7 @@ func (mi *MessageInput) sendMessage() *tcell.EventKey {
 		return nil
 	}
 
-	messagesLimit := mi.core.Config.State.GetGlobal("messagesLimit")
-	ms, err := mi.core.State.Messages(mi.core.ChannelsTree.SelectedChannel.ID, uint(lua.LVAsNumber(messagesLimit)))
+	ms, err := mi.core.State.Messages(mi.core.ChannelsTree.SelectedChannel.ID, mi.core.Config.MessagesLimit)
 	if err != nil {
 		return nil
 	}
@@ -143,22 +104,22 @@ func (mi *MessageInput) sendMessage() *tcell.EventKey {
 	return nil
 }
 
-func (mi *MessageInput) pasteClipboardContentLua(s *lua.LState) int {
+func (mi *MessageInput) pasteClipboard() *tcell.EventKey {
 	text, _ := clipboard.ReadAll()
 	text = mi.GetText() + text
 	mi.SetText(text)
-	return returnNilLua(s)
+	return nil
 }
 
-func (mi *MessageInput) openExternalEditorLua(s *lua.LState) int {
+func (mi *MessageInput) openExternalEditor() *tcell.EventKey {
 	e := os.Getenv("EDITOR")
 	if e == "" {
-		return returnNilLua(s)
+		return nil
 	}
 
 	f, err := os.CreateTemp(os.TempDir(), "discordo-*.md")
 	if err != nil {
-		return returnNilLua(s)
+		return nil
 	}
 	defer os.Remove(f.Name())
 
@@ -175,9 +136,9 @@ func (mi *MessageInput) openExternalEditorLua(s *lua.LState) int {
 
 	b, err := io.ReadAll(f)
 	if err != nil {
-		return returnNilLua(s)
+		return nil
 	}
 
 	mi.SetText(string(b))
-	return returnNilLua(s)
+	return nil
 }

+ 24 - 84
ui/messages_panel.go

@@ -4,8 +4,6 @@ import (
 	"github.com/diamondburned/arikawa/v3/discord"
 	"github.com/gdamore/tcell/v2"
 	"github.com/rivo/tview"
-	lua "github.com/yuin/gopher-lua"
-	luar "layeh.com/gopher-luar"
 )
 
 type MessagesPanel struct {
@@ -51,47 +49,19 @@ func (mp *MessagesPanel) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
 		return nil
 	}
 
-	keysTable, ok := mp.core.Config.State.GetGlobal("keys").(*lua.LTable)
-	if !ok {
-		keysTable = mp.core.Config.State.NewTable()
-	}
-
-	messagesPanel, ok := keysTable.RawGetString("messagesPanel").(*lua.LTable)
-	if !ok {
-		messagesPanel = mp.core.Config.State.NewTable()
-	}
-
-	var fn lua.LValue
-	messagesPanel.ForEach(func(k, v lua.LValue) {
-		keyTable := v.(*lua.LTable)
-		if e.Name() == lua.LVAsString(keyTable.RawGetString("name")) {
-			fn = keyTable.RawGetString("action")
-		}
-	})
-
-	if fn != nil {
-		mp.core.Config.State.CallByParam(lua.P{
-			Fn:      fn,
-			NRet:    1,
-			Protect: true,
-		}, luar.New(mp.core.Config.State, mp.core), luar.New(mp.core.Config.State, e))
-		// Returned value
-		ret, ok := mp.core.Config.State.Get(-1).(*lua.LUserData)
-		if !ok {
-			return e
-		}
-
-		// Remove returned value
-		mp.core.Config.State.Pop(1)
-
-		ev, ok := ret.Value.(*tcell.EventKey)
-		if ok {
-			return ev
-		}
-	}
-
 	// Defaults
 	switch e.Name() {
+	case mp.core.Config.Keys.MessagesPanel.OpenActionsList:
+		return mp.openMessageActionsList(ms)
+
+	case mp.core.Config.Keys.MessagesPanel.SelectPreviousMessage:
+		return mp.selectPreviousMessage(ms)
+	case mp.core.Config.Keys.MessagesPanel.SelectNextMessage:
+		return mp.selectNextMessage(ms)
+	case mp.core.Config.Keys.MessagesPanel.SelectFirstMessage:
+		return mp.selectFirstMessage(ms)
+	case mp.core.Config.Keys.MessagesPanel.SelectLastMessage:
+		return mp.selectLastMessage(ms)
 	case "Esc":
 		mp.SelectedMessage = -1
 		mp.core.Application.SetFocus(mp.core.MainFlex)
@@ -105,13 +75,7 @@ func (mp *MessagesPanel) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
 	return e
 }
 
-func (mp *MessagesPanel) selectPreviousMessageLua(s *lua.LState) int {
-	// Messages should return messages ordered from latest to earliest.
-	ms, err := mp.core.State.Cabinet.Messages(mp.core.ChannelsTree.SelectedChannel.ID)
-	if err != nil || len(ms) == 0 {
-		return returnNilLua(s)
-	}
-
+func (mp *MessagesPanel) selectPreviousMessage(ms []discord.Message) *tcell.EventKey {
 	// If there are no highlighted regions, select the latest (last) message in the messages panel.
 	if len(mp.GetHighlights()) == 0 {
 		mp.SelectedMessage = 0
@@ -126,16 +90,10 @@ func (mp *MessagesPanel) selectPreviousMessageLua(s *lua.LState) int {
 
 	mp.Highlight(ms[mp.SelectedMessage].ID.String())
 	mp.ScrollToHighlight()
-	return returnNilLua(s)
+	return nil
 }
 
-func (mp *MessagesPanel) selectNextMessageLua(s *lua.LState) int {
-	// Messages should return messages ordered from latest to earliest.
-	ms, err := mp.core.State.Cabinet.Messages(mp.core.ChannelsTree.SelectedChannel.ID)
-	if err != nil || len(ms) == 0 {
-		return returnNilLua(s)
-	}
-
+func (mp *MessagesPanel) selectNextMessage(ms []discord.Message) *tcell.EventKey {
 	// If there are no highlighted regions, select the latest (last) message in the messages panel.
 	if len(mp.GetHighlights()) == 0 {
 		mp.SelectedMessage = 0
@@ -151,60 +109,42 @@ func (mp *MessagesPanel) selectNextMessageLua(s *lua.LState) int {
 	mp.
 		Highlight(ms[mp.SelectedMessage].ID.String()).
 		ScrollToHighlight()
-	return returnNilLua(s)
+	return nil
 }
 
-func (mp *MessagesPanel) selectFirstMessageLua(s *lua.LState) int {
-	// Messages should return messages ordered from latest to earliest.
-	ms, err := mp.core.State.Cabinet.Messages(mp.core.ChannelsTree.SelectedChannel.ID)
-	if err != nil || len(ms) == 0 {
-		return returnNilLua(s)
-	}
-
+func (mp *MessagesPanel) selectFirstMessage(ms []discord.Message) *tcell.EventKey {
 	mp.SelectedMessage = len(ms) - 1
 	mp.
 		Highlight(ms[mp.SelectedMessage].ID.String()).
 		ScrollToHighlight()
-	return returnNilLua(s)
+	return nil
 }
 
-func (mp *MessagesPanel) selectLastMessageLua(s *lua.LState) int {
-	// Messages should return messages ordered from latest to earliest.
-	ms, err := mp.core.State.Cabinet.Messages(mp.core.ChannelsTree.SelectedChannel.ID)
-	if err != nil || len(ms) == 0 {
-		return returnNilLua(s)
-	}
-
+func (mp *MessagesPanel) selectLastMessage(ms []discord.Message) *tcell.EventKey {
 	mp.SelectedMessage = 0
 	mp.
 		Highlight(ms[mp.SelectedMessage].ID.String()).
 		ScrollToHighlight()
-	return returnNilLua(s)
+	return nil
 }
 
-func (mp *MessagesPanel) openMessageActionsListLua(s *lua.LState) int {
-	// Messages should return messages ordered from latest to earliest.
-	ms, err := mp.core.State.Cabinet.Messages(mp.core.ChannelsTree.SelectedChannel.ID)
-	if err != nil || len(ms) == 0 {
-		return returnNilLua(s)
-	}
-
+func (mp *MessagesPanel) openMessageActionsList(ms []discord.Message) *tcell.EventKey {
 	hs := mp.GetHighlights()
 	if len(hs) == 0 {
-		return returnNilLua(s)
+		return nil
 	}
 
 	mID, err := discord.ParseSnowflake(hs[0])
 	if err != nil {
-		return returnNilLua(s)
+		return nil
 	}
 
 	_, m := findMessageByID(ms, discord.MessageID(mID))
 	if m == nil {
-		return returnNilLua(s)
+		return nil
 	}
 
 	actionsList := NewMessageActionsList(mp.core, m)
 	mp.core.Application.SetRoot(actionsList, true)
-	return returnNilLua(s)
+	return nil
 }

+ 0 - 6
ui/util.go

@@ -5,7 +5,6 @@ import (
 	"strings"
 
 	"github.com/diamondburned/arikawa/v3/discord"
-	lua "github.com/yuin/gopher-lua"
 )
 
 var (
@@ -66,8 +65,3 @@ func hasPermission(s *State, cID discord.ChannelID, p discord.Permissions) bool
 
 	return perm&p == p
 }
-
-func returnNilLua(s *lua.LState) int {
-	s.Push(lua.LNil) // Push the result
-	return 1         // Number of results
-}