Browse Source

refactor: add comments to config.yml

ayn2op 2 years ago
parent
commit
dd416b3305
17 changed files with 242 additions and 269 deletions
  1. 7 8
      cmd/attachment_image.go
  2. 8 9
      cmd/guilds_tree.go
  3. 5 6
      cmd/main_flex.go
  4. 11 12
      cmd/message_input.go
  5. 21 22
      cmd/messages_text.go
  6. 11 11
      cmd/run.go
  7. 1 1
      cmd/state.go
  8. 0 66
      config/config.go
  9. 0 77
      config/keys.go
  10. 0 48
      config/theme.go
  11. 1 1
      go.mod
  12. 2 0
      go.sum
  13. 110 0
      internal/config/config.go
  14. 56 0
      internal/config/config.yml
  15. 1 0
      internal/config/config_test.go
  16. 2 2
      main.go
  17. 6 6
      ui/login_form.go

+ 7 - 8
cmd/run/attachment_image.go → cmd/attachment_image.go

@@ -1,4 +1,4 @@
-package run
+package cmd
 
 import (
 	"image"
@@ -6,7 +6,6 @@ import (
 	_ "image/png"
 	"net/http"
 
-	"github.com/ayn2op/discordo/config"
 	"github.com/diamondburned/arikawa/v3/discord"
 	"github.com/gdamore/tcell/v2"
 	"github.com/rivo/tview"
@@ -22,13 +21,13 @@ func newAttachmentImage(a discord.Attachment) (*AttachmentImage, error) {
 	}
 
 	ai.SetInputCapture(ai.onInputCapture)
-	ai.SetBackgroundColor(tcell.GetColor(config.Current.Theme.BackgroundColor))
-	ai.SetTitleColor(tcell.GetColor(config.Current.Theme.TitleColor))
+	ai.SetBackgroundColor(tcell.GetColor(cfg.Theme.BackgroundColor))
+	ai.SetTitleColor(tcell.GetColor(cfg.Theme.TitleColor))
 	ai.SetTitleAlign(tview.AlignLeft)
 
-	p := config.Current.Theme.BorderPadding
-	ai.SetBorder(config.Current.Theme.Border)
-	ai.SetBorderColor(tcell.GetColor(config.Current.Theme.BorderColor))
+	p := cfg.Theme.BorderPadding
+	ai.SetBorder(cfg.Theme.Border)
+	ai.SetBorderColor(tcell.GetColor(cfg.Theme.BorderColor))
 	ai.SetBorderPadding(p[0], p[1], p[2], p[3])
 
 	resp, err := http.Get(a.URL)
@@ -48,7 +47,7 @@ func newAttachmentImage(a discord.Attachment) (*AttachmentImage, error) {
 }
 
 func (ai *AttachmentImage) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
-	if event.Name() == config.Current.Keys.Cancel {
+	if event.Name() == cfg.Keys.Cancel {
 		app.SetRoot(mainFlex, true)
 		app.SetFocus(mainFlex.messagesText)
 		return nil

+ 8 - 9
cmd/run/guilds_tree.go → cmd/guilds_tree.go

@@ -1,4 +1,4 @@
-package run
+package cmd
 
 import (
 	"fmt"
@@ -6,7 +6,6 @@ import (
 	"sort"
 	"strings"
 
-	"github.com/ayn2op/discordo/config"
 	"github.com/diamondburned/arikawa/v3/discord"
 	"github.com/diamondburned/arikawa/v3/gateway"
 	"github.com/gdamore/tcell/v2"
@@ -29,17 +28,17 @@ func newGuildsTree() *GuildsTree {
 
 	gt.SetTopLevel(1)
 	gt.SetRoot(gt.root)
-	gt.SetGraphics(config.Current.Theme.GuildsTree.Graphics)
-	gt.SetBackgroundColor(tcell.GetColor(config.Current.Theme.BackgroundColor))
+	gt.SetGraphics(cfg.Theme.GuildsTree.Graphics)
+	gt.SetBackgroundColor(tcell.GetColor(cfg.Theme.BackgroundColor))
 	gt.SetSelectedFunc(gt.onSelected)
 
 	gt.SetTitle("Guilds")
-	gt.SetTitleColor(tcell.GetColor(config.Current.Theme.TitleColor))
+	gt.SetTitleColor(tcell.GetColor(cfg.Theme.TitleColor))
 	gt.SetTitleAlign(tview.AlignLeft)
 
-	p := config.Current.Theme.BorderPadding
-	gt.SetBorder(config.Current.Theme.Border)
-	gt.SetBorderColor(tcell.GetColor(config.Current.Theme.BorderColor))
+	p := cfg.Theme.BorderPadding
+	gt.SetBorder(cfg.Theme.Border)
+	gt.SetBorderColor(tcell.GetColor(cfg.Theme.BorderColor))
 	gt.SetBorderPadding(p[0], p[1], p[2], p[3])
 
 	return gt
@@ -188,7 +187,7 @@ func (gt *GuildsTree) onSelected(n *tview.TreeNode) {
 
 		gt.createChannelNodes(n, cs)
 	case discord.ChannelID:
-		ms, err := discordState.Messages(ref, config.Current.MessagesLimit)
+		ms, err := discordState.Messages(ref, uint(cfg.MessagesLimit))
 		if err != nil {
 			log.Println(err)
 			return

+ 5 - 6
cmd/run/main_flex.go → cmd/main_flex.go

@@ -1,7 +1,6 @@
-package run
+package cmd
 
 import (
-	"github.com/ayn2op/discordo/config"
 	"github.com/gdamore/tcell/v2"
 	"github.com/rivo/tview"
 )
@@ -43,7 +42,7 @@ func (mf *MainFlex) init() {
 
 func (mf *MainFlex) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
 	switch event.Name() {
-	case config.Current.Keys.GuildsTree.Toggle:
+	case cfg.Keys.GuildsTree.Toggle:
 		// The guilds tree is visible if the numbers of items is two.
 		if mf.GetItemCount() == 2 {
 			mf.RemoveItem(mf.guildsTree)
@@ -57,13 +56,13 @@ func (mf *MainFlex) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
 		}
 
 		return nil
-	case config.Current.Keys.GuildsTree.Focus:
+	case cfg.Keys.GuildsTree.Focus:
 		app.SetFocus(mf.guildsTree)
 		return nil
-	case config.Current.Keys.MessagesText.Focus:
+	case cfg.Keys.MessagesText.Focus:
 		app.SetFocus(mf.messagesText)
 		return nil
-	case config.Current.Keys.MessageInput.Focus:
+	case cfg.Keys.MessageInput.Focus:
 		app.SetFocus(mf.messageInput)
 		return nil
 	}

+ 11 - 12
cmd/run/message_input.go → cmd/message_input.go

@@ -1,4 +1,4 @@
-package run
+package cmd
 
 import (
 	"log"
@@ -7,7 +7,6 @@ import (
 	"strings"
 
 	"github.com/atotto/clipboard"
-	"github.com/ayn2op/discordo/config"
 	"github.com/ayn2op/discordo/internal/constants"
 	"github.com/diamondburned/arikawa/v3/api"
 	"github.com/diamondburned/arikawa/v3/discord"
@@ -25,7 +24,7 @@ func newMessageInput() *MessageInput {
 		TextArea: tview.NewTextArea(),
 	}
 
-	mi.SetTextStyle(tcell.StyleDefault.Background(tcell.GetColor(config.Current.Theme.BackgroundColor)))
+	mi.SetTextStyle(tcell.StyleDefault.Background(tcell.GetColor(cfg.Theme.BackgroundColor)))
 	mi.SetClipboard(func(s string) {
 		_ = clipboard.WriteAll(s)
 	}, func() string {
@@ -34,14 +33,14 @@ func newMessageInput() *MessageInput {
 	})
 
 	mi.SetInputCapture(mi.onInputCapture)
-	mi.SetBackgroundColor(tcell.GetColor(config.Current.Theme.BackgroundColor))
+	mi.SetBackgroundColor(tcell.GetColor(cfg.Theme.BackgroundColor))
 
-	mi.SetTitleColor(tcell.GetColor(config.Current.Theme.TitleColor))
+	mi.SetTitleColor(tcell.GetColor(cfg.Theme.TitleColor))
 	mi.SetTitleAlign(tview.AlignLeft)
 
-	p := config.Current.Theme.BorderPadding
-	mi.SetBorder(config.Current.Theme.Border)
-	mi.SetBorderColor(tcell.GetColor(config.Current.Theme.BorderColor))
+	p := cfg.Theme.BorderPadding
+	mi.SetBorder(cfg.Theme.Border)
+	mi.SetBorderColor(tcell.GetColor(cfg.Theme.BorderColor))
 	mi.SetBorderPadding(p[0], p[1], p[2], p[3])
 
 	return mi
@@ -54,15 +53,15 @@ func (mi *MessageInput) reset() {
 
 func (mi *MessageInput) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
 	switch event.Name() {
-	case config.Current.Keys.MessageInput.Send:
+	case cfg.Keys.MessageInput.Send:
 		mi.sendAction()
 		return nil
 	case "Alt+Enter":
 		return tcell.NewEventKey(tcell.KeyEnter, 0, tcell.ModNone)
-	case config.Current.Keys.MessageInput.LaunchEditor:
+	case cfg.Keys.MessageInput.LaunchEditor:
 		mainFlex.messageInput.launchEditorAction()
 		return nil
-	case config.Current.Keys.Cancel:
+	case cfg.Keys.Cancel:
 		mi.reset()
 		return nil
 	}
@@ -108,7 +107,7 @@ func (mi *MessageInput) sendAction() {
 }
 
 func (mi *MessageInput) launchEditorAction() {
-	e := config.Current.Editor
+	e := cfg.Editor
 	if e == "default" {
 		e = os.Getenv("EDITOR")
 	}

+ 21 - 22
cmd/run/messages_text.go → cmd/messages_text.go

@@ -1,4 +1,4 @@
-package run
+package cmd
 
 import (
 	"fmt"
@@ -7,7 +7,6 @@ import (
 	"time"
 
 	"github.com/atotto/clipboard"
-	"github.com/ayn2op/discordo/config"
 	"github.com/ayn2op/discordo/markdown"
 	"github.com/diamondburned/arikawa/v3/discord"
 	"github.com/gdamore/tcell/v2"
@@ -36,15 +35,15 @@ func newMessagesText() *MessagesText {
 		app.Draw()
 	})
 
-	mt.SetBackgroundColor(tcell.GetColor(config.Current.Theme.BackgroundColor))
+	mt.SetBackgroundColor(tcell.GetColor(cfg.Theme.BackgroundColor))
 
 	mt.SetTitle("Messages")
-	mt.SetTitleColor(tcell.GetColor(config.Current.Theme.TitleColor))
+	mt.SetTitleColor(tcell.GetColor(cfg.Theme.TitleColor))
 	mt.SetTitleAlign(tview.AlignLeft)
 
-	p := config.Current.Theme.BorderPadding
-	mt.SetBorder(config.Current.Theme.Border)
-	mt.SetBorderColor(tcell.GetColor(config.Current.Theme.BorderColor))
+	p := cfg.Theme.BorderPadding
+	mt.SetBorder(cfg.Theme.Border)
+	mt.SetBorderColor(tcell.GetColor(cfg.Theme.BorderColor))
 	mt.SetBorderPadding(p[0], p[1], p[2], p[3])
 
 	return mt
@@ -85,17 +84,17 @@ func (mt *MessagesText) createMessage(m discord.Message) {
 func (mt *MessagesText) createHeader(w io.Writer, m discord.Message, isReply bool) {
 	time := m.Timestamp.Format(time.Kitchen)
 
-	if config.Current.Timestamps && config.Current.TimestampsBeforeAuthor {
+	if cfg.Timestamps && cfg.TimestampsBeforeAuthor {
 		fmt.Fprintf(w, "[::d]%7s[::-] ", time)
 	}
 
 	if isReply {
-		fmt.Fprintf(mt, "[::d]%s", config.Current.Theme.MessagesText.ReplyIndicator)
+		fmt.Fprintf(mt, "[::d]%s", cfg.Theme.MessagesText.ReplyIndicator)
 	}
 
-	fmt.Fprintf(w, "[%s]%s[-:-:-] ", config.Current.Theme.MessagesText.AuthorColor, m.Author.Username)
+	fmt.Fprintf(w, "[%s]%s[-:-:-] ", cfg.Theme.MessagesText.AuthorColor, m.Author.Username)
 
-	if config.Current.Timestamps && !config.Current.TimestampsBeforeAuthor {
+	if cfg.Timestamps && !cfg.TimestampsBeforeAuthor {
 		fmt.Fprintf(w, "[::d]%s[::-] ", time)
 	}
 }
@@ -113,37 +112,37 @@ func (mt *MessagesText) createFooter(w io.Writer, m discord.Message) {
 
 func (mt *MessagesText) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
 	switch event.Name() {
-	case config.Current.Keys.MessagesText.CopyContent:
+	case cfg.Keys.MessagesText.CopyContent:
 		mt.copyContentAction()
 		return nil
-	case config.Current.Keys.MessagesText.Reply:
+	case cfg.Keys.MessagesText.Reply:
 		mt.replyAction(false)
 		return nil
-	case config.Current.Keys.MessagesText.ReplyMention:
+	case cfg.Keys.MessagesText.ReplyMention:
 		mt.replyAction(true)
 		return nil
-	case config.Current.Keys.MessagesText.SelectPrevious:
+	case cfg.Keys.MessagesText.SelectPrevious:
 		mt.selectPreviousAction()
 		return nil
-	case config.Current.Keys.MessagesText.SelectNext:
+	case cfg.Keys.MessagesText.SelectNext:
 		mt.selectNextAction()
 		return nil
-	case config.Current.Keys.MessagesText.SelectFirst:
+	case cfg.Keys.MessagesText.SelectFirst:
 		mt.selectFirstAction()
 		return nil
-	case config.Current.Keys.MessagesText.SelectLast:
+	case cfg.Keys.MessagesText.SelectLast:
 		mt.selectLastAction()
 		return nil
-	case config.Current.Keys.MessagesText.SelectReply:
+	case cfg.Keys.MessagesText.SelectReply:
 		mt.selectReplyAction()
 		return nil
-	case config.Current.Keys.MessagesText.ShowImage:
+	case cfg.Keys.MessagesText.ShowImage:
 		mt.showImageAction()
 		return nil
-	case config.Current.Keys.MessagesText.Delete:
+	case cfg.Keys.MessagesText.Delete:
 		mt.deleteAction()
 		return nil
-	case config.Current.Keys.Cancel:
+	case cfg.Keys.Cancel:
 		mainFlex.guildsTree.selectedChannelID = 0
 
 		mainFlex.messagesText.reset()

+ 11 - 11
cmd/run/run.go → cmd/run.go

@@ -1,11 +1,13 @@
-package run
+package cmd
 
 import (
+	"fmt"
 	"log"
 	"os"
 	"path/filepath"
 
-	"github.com/ayn2op/discordo/config"
+	oldConfig "github.com/ayn2op/discordo/config"
+	"github.com/ayn2op/discordo/internal/config"
 	"github.com/ayn2op/discordo/ui"
 	"github.com/rivo/tview"
 )
@@ -13,23 +15,21 @@ import (
 var (
 	discordState *State
 
+	cfg      *config.Config
 	app      = tview.NewApplication()
 	mainFlex *MainFlex
 )
 
 func Run(token string) error {
-	path := config.DefaultPath()
-	err := os.MkdirAll(filepath.Dir(path), os.ModePerm)
+	var err error
+	cfg, err = config.Load()
 	if err != nil {
 		return err
 	}
 
-	err = config.Load(path)
-	if err != nil {
-		return err
-	}
+	fmt.Printf("%+v\n", cfg)
 
-	path = config.DefaultLogPath()
+	path := oldConfig.DefaultLogPath()
 	err = os.MkdirAll(filepath.Dir(path), os.ModePerm)
 	if err != nil {
 		return err
@@ -44,7 +44,7 @@ func Run(token string) error {
 	log.SetFlags(log.LstdFlags | log.Llongfile)
 
 	if token == "" {
-		lf := ui.NewLoginForm()
+		lf := ui.NewLoginForm(cfg)
 
 		go func() {
 			mainFlex = newMainFlex()
@@ -73,6 +73,6 @@ func Run(token string) error {
 		app.SetRoot(mainFlex, true)
 	}
 
-	app.EnableMouse(config.Current.Mouse)
+	app.EnableMouse(cfg.Mouse)
 	return app.Run()
 }

+ 1 - 1
cmd/run/state.go → cmd/state.go

@@ -1,4 +1,4 @@
-package run
+package cmd
 
 import (
 	"context"

+ 0 - 66
config/config.go

@@ -5,74 +5,8 @@ import (
 	"path/filepath"
 
 	"github.com/ayn2op/discordo/internal/constants"
-	"gopkg.in/yaml.v3"
 )
 
-var Current = defConfig()
-
-type Config struct {
-	// Mouse indicates whether the mouse is usable or not.
-	Mouse bool `yaml:"mouse"`
-	// MessagesLimit is the number of messages to fetch when a text-based channel is selected.
-	MessagesLimit uint `yaml:"messages_limit"`
-	// TimestampsBeforeAuthor indicates whether to draw the timestamp before or after the author.
-	TimestampsBeforeAuthor bool `yaml:"timestamps_before_author"`
-	// Timestamps indicates whether to draw the timestamp in front of the message or not.
-	Timestamps bool `yaml:"timestamps"`
-	// Editor is the program to open when the `LaunchEditor` key is pressed. If the value of the field is "default", the `$EDITOR` environment variable is used instead.
-	Editor string `yaml:"editor"`
-
-	Keys  Keys  `yaml:"keys"`
-	Theme Theme `yaml:"theme"`
-}
-
-func defConfig() Config {
-	return Config{
-		Mouse:                  true,
-		TimestampsBeforeAuthor: false,
-		Timestamps:             false,
-		MessagesLimit:          50,
-		Editor:                 "default",
-
-		Keys:  defKeys(),
-		Theme: defTheme(),
-	}
-}
-
-func Load(path string) error {
-	_, err := os.Stat(path)
-	if os.IsNotExist(err) {
-		f, err := os.Create(path)
-		if err != nil {
-			return err
-		}
-		defer f.Close()
-
-		err = yaml.NewEncoder(f).Encode(Current)
-		if err != nil {
-			return err
-		}
-	} else {
-		f, err := os.Open(path)
-		if err != nil {
-			return err
-		}
-		defer f.Close()
-
-		err = yaml.NewDecoder(f).Decode(&Current)
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-func DefaultPath() string {
-	path, _ := os.UserConfigDir()
-	return filepath.Join(path, constants.Name, "config.yml")
-}
-
 func DefaultLogPath() string {
 	path, _ := os.UserCacheDir()
 	return filepath.Join(path, constants.Name, "logs.txt")

+ 0 - 77
config/keys.go

@@ -1,77 +0,0 @@
-package config
-
-type (
-	GuildsTreeKeys struct {
-		Focus  string `yaml:"focus"`
-		Toggle string `yaml:"toggle"`
-	}
-
-	MessagesTextKeys struct {
-		Focus string `yaml:"focus"`
-
-		ShowImage   string `yaml:"show_image"`
-		CopyContent string `yaml:"copy_content"`
-
-		Reply        string `yaml:"reply"`
-		ReplyMention string `yaml:"reply_mention"`
-		SelectReply  string `yaml:"select_reply"`
-
-		Delete string `yaml:"delete"`
-
-		SelectPrevious string `yaml:"select_previous"`
-		SelectNext     string `yaml:"select_next"`
-		SelectFirst    string `yaml:"select_first"`
-		SelectLast     string `yaml:"select_last"`
-	}
-
-	MessageInputKeys struct {
-		Focus string `yaml:"focus"`
-
-		Send         string `yaml:"send"`
-		LaunchEditor string `yaml:"launch_editor"`
-	}
-)
-
-type Keys struct {
-	Cancel string `yaml:"cancel"`
-
-	GuildsTree   GuildsTreeKeys   `yaml:"guilds_tree"`
-	MessagesText MessagesTextKeys `yaml:"messages_text"`
-	MessageInput MessageInputKeys `yaml:"message_input"`
-}
-
-func defKeys() Keys {
-	return Keys{
-		Cancel: "Esc",
-
-		GuildsTree: GuildsTreeKeys{
-			Focus:  "Alt+Rune[g]",
-			Toggle: "Alt+Rune[b]",
-		},
-
-		MessagesText: MessagesTextKeys{
-			Focus: "Alt+Rune[m]",
-
-			ShowImage:   "Rune[i]",
-			CopyContent: "Rune[c]",
-
-			Reply:        "Rune[r]",
-			ReplyMention: "Rune[R]",
-			SelectReply:  "Rune[s]",
-
-			Delete: "Rune[d]",
-
-			SelectPrevious: "Up",
-			SelectNext:     "Down",
-			SelectFirst:    "Home",
-			SelectLast:     "End",
-		},
-
-		MessageInput: MessageInputKeys{
-			Focus: "Alt+Rune[i]",
-
-			Send:         "Enter",
-			LaunchEditor: "Ctrl+E",
-		},
-	}
-}

+ 0 - 48
config/theme.go

@@ -1,48 +0,0 @@
-package config
-
-type (
-	GuildsTreeTheme struct {
-		// Graphics indicates whether to draw the line graphics to illustrate the hierarchy or not.
-		Graphics bool `yaml:"graphics"`
-	}
-
-	MessagesTextTheme struct {
-		AuthorColor string `yaml:"author_color"`
-		ReplyIndicator string `yaml:"reply_indicator"`
-	}
-
-	MessageInputTheme struct{}
-)
-
-type Theme struct {
-	Border        bool   `yaml:"border"`
-	BorderColor   string `yaml:"border_color"`
-	BorderPadding [4]int `yaml:"border_padding,flow"`
-
-	TitleColor      string `yaml:"title_color"`
-	BackgroundColor string `yaml:"background_color"`
-
-	GuildsTree   GuildsTreeTheme   `yaml:"guilds_tree"`
-	MessagesText MessagesTextTheme `yaml:"messages_text"`
-	MessageInput MessageInputTheme `yaml:"message_input"`
-}
-
-func defTheme() Theme {
-	return Theme{
-		Border:        true,
-		BorderColor:   "default",
-		BorderPadding: [...]int{0, 0, 1, 1},
-
-		TitleColor:      "default",
-		BackgroundColor: "default",
-
-		GuildsTree: GuildsTreeTheme{
-			Graphics: true,
-		},
-		MessagesText: MessagesTextTheme{
-			AuthorColor: "aqua",
-			ReplyIndicator: "╭",
-		},
-		MessageInput: MessageInputTheme{},
-	}
-}

+ 1 - 1
go.mod

@@ -6,7 +6,7 @@ require (
 	github.com/atotto/clipboard v0.1.4
 	github.com/diamondburned/arikawa/v3 v3.3.3
 	github.com/gdamore/tcell/v2 v2.6.0
-	github.com/rivo/tview v0.0.0-20231007183732-6c844bdc5f7a
+	github.com/rivo/tview v0.0.0-20231024211518-8b7bcf9883df
 	github.com/zalando/go-keyring v0.2.3
 	gopkg.in/yaml.v3 v3.0.1
 )

+ 2 - 0
go.sum

@@ -30,6 +30,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/rivo/tview v0.0.0-20231007183732-6c844bdc5f7a h1:368NWOLkcJirQFjVSUfB6LWbkas6fd8lQ3DbO66tw1w=
 github.com/rivo/tview v0.0.0-20231007183732-6c844bdc5f7a/go.mod h1:nVwGv4MP47T0jvlk7KuTTjjuSmrGO4JF0iaiNt4bufE=
+github.com/rivo/tview v0.0.0-20231024211518-8b7bcf9883df h1:G91TSQNNlR4hRz11lqKKp98ffxqPbEu2rUjxJSkUM4A=
+github.com/rivo/tview v0.0.0-20231024211518-8b7bcf9883df/go.mod h1:nVwGv4MP47T0jvlk7KuTTjjuSmrGO4JF0iaiNt4bufE=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=

+ 110 - 0
internal/config/config.go

@@ -0,0 +1,110 @@
+package config
+
+import (
+	"bytes"
+	_ "embed"
+	"io"
+	"os"
+	"path/filepath"
+
+	"github.com/ayn2op/discordo/internal/constants"
+	"gopkg.in/yaml.v3"
+)
+
+//go:embed config.yml
+var defaultConfig []byte
+
+type Config struct {
+	Mouse bool `yaml:"mouse"`
+
+	Timestamps             bool `yaml:"timestamps"`
+	TimestampsBeforeAuthor bool `yaml:"timestamps_before_author"`
+
+	MessagesLimit uint8 `yaml:"messages_limit"`
+
+	Editor string `yaml:"editor"`
+
+	Keys struct {
+		Cancel string `yaml:"cancel"`
+
+		GuildsTree struct {
+			Focus  string `yaml:"focus"`
+			Toggle string `yaml:"toggle"`
+		} `yaml:"guilds_tree"`
+
+		MessagesText struct {
+			Focus string `yaml:"focus"`
+
+			ShowImage   string `yaml:"show_image"`
+			CopyContent string `yaml:"copy_content"`
+
+			Reply        string `yaml:"reply"`
+			ReplyMention string `yaml:"reply_mention"`
+
+			Delete string `yaml:"delete"`
+
+			SelectPrevious string `yaml:"select_previous"`
+			SelectNext     string `yaml:"select_next"`
+			SelectFirst    string `yaml:"select_first"`
+			SelectLast     string `yaml:"select_last"`
+			SelectReply    string `yaml:"select_reply"`
+		} `yaml:"messages_text"`
+
+		MessageInput struct {
+			Focus string `yaml:"focus"`
+
+			Send         string `yaml:"send"`
+			LaunchEditor string `yaml:"launch_editor"`
+		} `yaml:"message_input"`
+	} `yaml:"keys"`
+	Theme struct {
+		Border        bool   `yaml:"border"`
+		BorderColor   string `yaml:"border_color"`
+		BorderPadding [4]int `yaml:"border_padding,flow"`
+
+		TitleColor      string `yaml:"title_color"`
+		BackgroundColor string `yaml:"background_color"`
+
+		GuildsTree struct {
+			Graphics bool `yaml:"graphics"`
+		} `yaml:"guilds_tree"`
+
+		MessagesText struct {
+			AuthorColor    string `yaml:"author_color"`
+			ReplyIndicator string `yaml:"reply_indicator"`
+		} `yaml:"messages_text"`
+	} `yaml:"theme"`
+}
+
+func Load() (*Config, error) {
+	path, err := os.UserConfigDir()
+	if err != nil {
+		return nil, err
+	}
+
+	path = filepath.Join(path, constants.Name)
+	if err := os.MkdirAll(path, os.ModePerm); err != nil {
+		return nil, err
+	}
+
+	path = filepath.Join(path, "config.yml")
+
+	f, err := os.Open(path)
+	reader := io.Reader(f)
+	if os.IsNotExist(err) {
+		err = os.WriteFile(path, defaultConfig, os.ModePerm)
+		reader = bytes.NewReader(defaultConfig)
+	}
+
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	var cfg Config
+	if err := yaml.NewDecoder(reader).Decode(&cfg); err != nil {
+		return nil, err
+	}
+
+	return &cfg, nil
+}

+ 56 - 0
internal/config/config.yml

@@ -0,0 +1,56 @@
+# Whether the mouse is usable or not.
+mouse: true
+
+# Whether to draw the timestamps of the corresponding message in front of it.
+timestamps: false
+# Whether to draw the timestamps before the name of author of the message or not. 
+timestamps_before_author: false
+
+# The number of messages to fetch when a text-based channel is selected. The value must be >0 and <=100.
+messages_limit: 50
+
+# The name of the program to launch when the launch_editor key is pressed. If the value of the field is set to "default", the `$EDITOR` environment variable is used instead.
+editor: default
+
+keys:
+  cancel: Esc
+  
+  guilds_tree:
+    focus: Alt+Rune[g]
+    toggle: Alt+Rune[b]
+  
+  messages_text:
+    focus: Alt+Rune[m]
+    show_image: Rune[i]
+    copy_content: Rune[c]
+    delete: Rune[d]
+
+    reply: Rune[r]
+    reply_mention: Rune[R]
+
+    select_previous: Up
+    select_next: Down
+    select_first: Home
+    select_last: End
+    select_reply: Rune[s]
+
+  message_input:
+    focus: Alt+Rune[i]
+
+    send: Enter
+    launch_editor: Ctrl+E
+
+theme:
+  border: true
+  border_color: default
+  border_padding: [0, 0, 1, 1]
+
+  title_color: default
+  background_color: default
+
+  guilds_tree:
+    graphics: true
+
+  messages_text:
+    author_color: aqua
+    reply_indicator: ╭

+ 1 - 0
internal/config/config_test.go

@@ -0,0 +1 @@
+package config

+ 2 - 2
main.go

@@ -4,7 +4,7 @@ import (
 	"flag"
 	"log"
 
-	"github.com/ayn2op/discordo/cmd/run"
+	"github.com/ayn2op/discordo/cmd"
 	"github.com/ayn2op/discordo/internal/constants"
 	"github.com/zalando/go-keyring"
 )
@@ -14,7 +14,7 @@ func main() {
 	token := flag.String("token", t, "The authentication token.")
 	flag.Parse()
 
-	if err := run.Run(*token); err != nil {
+	if err := cmd.Run(*token); err != nil {
 		log.Fatal(err)
 	}
 }

+ 6 - 6
ui/login_form.go

@@ -3,7 +3,7 @@ package ui
 import (
 	"errors"
 
-	"github.com/ayn2op/discordo/config"
+	"github.com/ayn2op/discordo/internal/config"
 	"github.com/ayn2op/discordo/internal/constants"
 	"github.com/diamondburned/arikawa/v3/api"
 	"github.com/gdamore/tcell/v2"
@@ -17,7 +17,7 @@ type LoginForm struct {
 	Error chan error
 }
 
-func NewLoginForm() *LoginForm {
+func NewLoginForm(cfg *config.Config) *LoginForm {
 	lf := &LoginForm{
 		Form:  tview.NewForm(),
 		Token: make(chan string, 1),
@@ -31,12 +31,12 @@ func NewLoginForm() *LoginForm {
 	lf.AddButton("Login", lf.onLoginButtonSelected)
 
 	lf.SetTitle("Login")
-	lf.SetTitleColor(tcell.GetColor(config.Current.Theme.TitleColor))
+	lf.SetTitleColor(tcell.GetColor(cfg.Theme.TitleColor))
 	lf.SetTitleAlign(tview.AlignLeft)
 
-	p := config.Current.Theme.BorderPadding
-	lf.SetBorder(config.Current.Theme.Border)
-	lf.SetBorderColor(tcell.GetColor(config.Current.Theme.BorderColor))
+	p := cfg.Theme.BorderPadding
+	lf.SetBorder(cfg.Theme.Border)
+	lf.SetBorderColor(tcell.GetColor(cfg.Theme.BorderColor))
 	lf.SetBorderPadding(p[0], p[1], p[2], p[3])
 
 	return lf