From 33a68670c9bbea9f8c8d89bd4c6f8c5a147eb14c Mon Sep 17 00:00:00 2001
From: Sam <dxb@keybase.io>
Date: Fri, 25 Oct 2019 17:18:50 -0400
Subject: [PATCH 1/5] Clean up colors, clean up config, make config actually be
 used, add default config

---
 cmdConfig.go         |  84 ++++++++++++++++++++++++
 cmdDownload.go       |   6 +-
 cmdHelp.go           |   2 +-
 cmdJoin.go           |   6 +-
 cmdReply.go          |   2 +-
 cmdSet.go            | 125 -----------------------------------
 cmdStream.go         |   2 +-
 cmdUploadFile.go     |   4 +-
 cmdWall.go           |   2 +-
 colors.go            | 151 +++++++++++++++++++------------------------
 defaultConfig.go     |  70 ++++++++++++++++++++
 kbtui.toml           |  49 +++++++-------
 main.go              |  78 +++++++++++-----------
 tcmdShowReactions.go |   6 +-
 types.go             |  65 +++++++++++++++++++
 userConfigs.go       |  40 ------------
 16 files changed, 366 insertions(+), 326 deletions(-)
 create mode 100644 cmdConfig.go
 delete mode 100644 cmdSet.go
 create mode 100644 defaultConfig.go
 delete mode 100644 userConfigs.go

diff --git a/cmdConfig.go b/cmdConfig.go
new file mode 100644
index 0000000..64a14ee
--- /dev/null
+++ b/cmdConfig.go
@@ -0,0 +1,84 @@
+// +build !rm_basic_commands allcommands setcmd
+
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+
+	"github.com/BurntSushi/toml"
+)
+
+func init() {
+	command := Command{
+		Cmd:         []string{"config"},
+		Description: "Change various settings",
+		Help:        "",
+		Exec:        cmdConfig,
+	}
+
+	RegisterCommand(command)
+}
+
+func cmdConfig(cmd []string) {
+	var err error
+	switch {
+	case len(cmd) == 2:
+		if cmd[1] == "load" {
+			config, err = readConfig()
+			if err != nil {
+				printError(err.Error())
+				return
+			}
+			printInfoF("Config file loaded: $TEXT", config.Colors.Message.Attachment.stylize(config.filepath))
+			return
+		}
+	case len(cmd) > 2:
+		if cmd[1] == "load" {
+			config, err = readConfig(cmd[3])
+			if err != nil {
+				printError(err.Error())
+				return
+			}
+			printInfoF("Config file loaded: $TEXT", config.Colors.Message.Attachment.stylize(config.filepath))
+			return
+		}
+	}
+	printError("Must pass a valid command")
+}
+
+func readConfig(filepath ...string) (*Config, error) {
+	var result = new(Config)
+	var configFile string
+	var env bool
+
+	switch len(filepath) {
+	case 0:
+		configFile, env = os.LookupEnv("KBTUI_CFG")
+		if !env {
+			configFile = "~/.config/kbtui.toml"
+			if _, err := os.Stat(configFile); os.IsNotExist(err) {
+				configFile = "kbtui.toml"
+			}
+		}
+	default:
+		configFile = filepath[0]
+		if _, err := os.Stat(configFile); os.IsNotExist(err) {
+			return result, fmt.Errorf("Unable to load config: %s not found", configFile)
+		}
+	}
+
+	f, err := ioutil.ReadFile(configFile)
+	if err != nil {
+		f = []byte(defaultConfig)
+	}
+
+	err = toml.Unmarshal(f, result)
+	if err != nil {
+		return result, err
+	}
+
+	result.filepath = configFile
+	return result, nil
+}
diff --git a/cmdDownload.go b/cmdDownload.go
index bda6233..c78df27 100644
--- a/cmdDownload.go
+++ b/cmdDownload.go
@@ -21,7 +21,7 @@ func init() {
 func cmdDownloadFile(cmd []string) {
 
 	if len(cmd) < 2 {
-		printInfo(fmt.Sprintf("%s%s $messageId $fileName - Download a file to user's downloadpath", cmdPrefix, cmd[0]))
+		printInfo(fmt.Sprintf("%s%s $messageId $fileName - Download a file to user's downloadpath", config.Basics.CmdPrefix, cmd[0]))
 		return
 	}
 	messageID, err := strconv.Atoi(cmd[1])
@@ -46,8 +46,8 @@ func cmdDownloadFile(cmd []string) {
 		fileName = api.Result.Messages[0].Msg.Content.Attachment.Object.Filename
 	}
 
-	_, err = chat.Download(messageID, fmt.Sprintf("%s/%s", downloadPath, fileName))
-	channelName := messageLinkKeybaseColor.stylize(channel.Name)
+	_, err = chat.Download(messageID, fmt.Sprintf("%s/%s", config.Basics.DownloadPath, fileName))
+	channelName := config.Colors.Message.LinkKeybase.stylize(channel.Name)
 	if err != nil {
 		printErrorF(fmt.Sprintf("There was an error downloading %s from $TEXT", fileName), channelName)
 	} else {
diff --git a/cmdHelp.go b/cmdHelp.go
index bbf71de..f7574bb 100644
--- a/cmdHelp.go
+++ b/cmdHelp.go
@@ -25,7 +25,7 @@ func cmdHelp(cmd []string) {
 	if len(cmd) == 1 {
 		sort.Strings(baseCommands)
 		for _, c := range baseCommands {
-			helpText = fmt.Sprintf("%s%s%s\t\t%s\n", helpText, cmdPrefix, c, commands[c].Description)
+			helpText = fmt.Sprintf("%s%s%s\t\t%s\n", helpText, config.Basics.CmdPrefix, c, commands[c].Description)
 		}
 		if len(typeCommands) > 0 {
 			for c := range typeCommands {
diff --git a/cmdJoin.go b/cmdJoin.go
index 05858c7..a2800d1 100644
--- a/cmdJoin.go
+++ b/cmdJoin.go
@@ -41,12 +41,12 @@ func cmdJoin(cmd []string) {
 			channel.TopicName = ""
 			channel.MembersType = keybase.USER
 		}
-		printInfoF("You are joining: $TEXT", messageLinkKeybaseColor.stylize(joinedName))
+		printInfoF("You are joining: $TEXT", config.Colors.Message.LinkKeybase.stylize(joinedName))
 		clearView("Chat")
 		setViewTitle("Input", fmt.Sprintf(" %s ", joinedName))
 		go populateChat()
 	default:
-		printInfo(fmt.Sprintf("To join a team use %sjoin <team> <channel>", cmdPrefix))
-		printInfo(fmt.Sprintf("To join a PM use %sjoin <user>", cmdPrefix))
+		printInfo(fmt.Sprintf("To join a team use %sjoin <team> <channel>", config.Basics.CmdPrefix))
+		printInfo(fmt.Sprintf("To join a PM use %sjoin <user>", config.Basics.CmdPrefix))
 	}
 }
diff --git a/cmdReply.go b/cmdReply.go
index 9a21aa3..b47f60b 100644
--- a/cmdReply.go
+++ b/cmdReply.go
@@ -22,7 +22,7 @@ func init() {
 func cmdReply(cmd []string) {
 	chat := k.NewChat(channel)
 	if len(cmd) < 2 {
-		printInfo(fmt.Sprintf("%s%s $ID - Reply to message $ID", cmdPrefix, cmd[0]))
+		printInfo(fmt.Sprintf("%s%s $ID - Reply to message $ID", config.Basics.CmdPrefix, cmd[0]))
 		return
 	}
 	messageID, err := strconv.Atoi(cmd[1])
diff --git a/cmdSet.go b/cmdSet.go
deleted file mode 100644
index 812fb48..0000000
--- a/cmdSet.go
+++ /dev/null
@@ -1,125 +0,0 @@
-// +build !rm_basic_commands allcommands setcmd
-
-package main
-
-import (
-	"fmt"
-	"os"
-	"strings"
-
-	"github.com/pelletier/go-toml"
-)
-
-func init() {
-	command := Command{
-		Cmd:         []string{"set", "config"},
-		Description: "Change various settings",
-		Help:        "",
-		Exec:        cmdSet,
-	}
-
-	RegisterCommand(command)
-}
-func printSetting(cmd []string) {
-	switch cmd[1] {
-	case "load":
-		loadFromToml()
-	case "downloadPath":
-		printInfo(fmt.Sprintf("Setting for %s -> %s", cmd[1], downloadPath))
-	case "outputFormat":
-		printInfo(fmt.Sprintf("Setting for %s -> %s", cmd[1], outputFormat))
-	case "dateFormat":
-		printInfo(fmt.Sprintf("Setting for %s -> %s", cmd[1], dateFormat))
-	case "timeFormat":
-		printInfo(fmt.Sprintf("Setting for %s -> %s", cmd[1], timeFormat))
-	case "cmdPrefix":
-		printInfo(fmt.Sprintf("Setting for %s -> %s", cmd[1], cmdPrefix))
-	default:
-		printError(fmt.Sprintf("Unknown config value %s", cmd[1]))
-	}
-}
-func cmdSet(cmd []string) {
-	if len(cmd) < 2 {
-		printError("No config value specified")
-		return
-	}
-	if len(cmd) < 3 {
-		printSetting(cmd)
-		return
-	}
-	switch cmd[1] {
-	case "downloadPath":
-		if len(cmd) != 3 {
-			printError("Invalid download path.")
-		}
-		downloadPath = cmd[2]
-	case "outputFormat":
-		outputFormat = strings.Join(cmd[1:], " ")
-	case "dateFormat":
-		dateFormat = strings.Join(cmd[1:], " ")
-	case "timeFormat":
-		timeFormat = strings.Join(cmd[1:], " ")
-	case "cmdPrefix":
-		cmdPrefix = cmd[2]
-	default:
-		printError(fmt.Sprintf("Unknown config value %s", cmd[1]))
-	}
-
-}
-func loadFromToml() {
-	configFile, env := os.LookupEnv("KBTUI_CFG")
-	if !env {
-		configFile = "~/.config/kbtui.toml"
-		if _, err := os.Stat(configFile); os.IsNotExist(err) {
-			configFile = "kbtui.toml"
-		}
-
-	}
-	printInfoF("Loading config from toml: $TEXT", messageAttachmentColor.stylize(configFile))
-	config, err := toml.LoadFile(configFile)
-	if err != nil {
-		printError(fmt.Sprintf("Could not read config file: %+v", err))
-		return
-	}
-	colorless = config.GetDefault("Basics.colorless", false).(bool)
-	if config.Has("Basics.colorless") {
-		colorless = config.Get("Basics.colorless").(bool)
-	}
-	if config.Has("Basics.downloadPath") {
-		downloadPath = config.Get("Basics.downloadPath").(string)
-	}
-	if config.Has("Basics.cmdPrefix") {
-		cmdPrefix = config.Get("Basics.cmdPrefix").(string)
-	}
-	if config.Has("Formatting.outputFormat") {
-		outputFormat = config.Get("Formatting.outputFormat").(string)
-	}
-	if config.Has("Formatting.dateFormat") {
-		dateFormat = config.Get("Formatting.dateFormat").(string)
-	}
-	if config.Has("Formatting.timeFormat") {
-		timeFormat = config.Get("Formatting.timeFormat").(string)
-	}
-	channelsColor = styleFromConfig(config, "channels.basic")
-
-	channelsHeaderColor = styleFromConfig(config, "channels.header")
-	channelUnreadColor = styleFromConfig(config, "channels.unread")
-
-	mentionColor = styleFromConfig(config, "message.mention")
-	messageHeaderColor = styleFromConfig(config, "message.header")
-	messageIDColor = styleFromConfig(config, "message.id")
-	messageTimeColor = styleFromConfig(config, "message.time")
-	messageSenderDefaultColor = styleFromConfig(config, "message.sender_default")
-	messageSenderDeviceColor = styleFromConfig(config, "message.sender_device")
-	messageBodyColor = styleFromConfig(config, "message.body")
-	messageAttachmentColor = styleFromConfig(config, "message.attachment")
-	messageLinkURLColor = styleFromConfig(config, "message.link_url")
-	messageLinkKeybaseColor = styleFromConfig(config, "message.link_keybase")
-	messageReactionColor = styleFromConfig(config, "message.reaction")
-	messageCodeColor = styleFromConfig(config, "message.code")
-
-	feedColor = styleFromConfig(config, "feed.basic")
-	errorColor = styleFromConfig(config, "feed.error")
-
-	RunCommand("clean")
-}
diff --git a/cmdStream.go b/cmdStream.go
index 4e8b848..b5a57ae 100644
--- a/cmdStream.go
+++ b/cmdStream.go
@@ -22,6 +22,6 @@ func cmdStream(cmd []string) {
 	channel.Name = ""
 
 	printInfo("You are now viewing the formatted stream")
-	setViewTitle("Input", fmt.Sprintf(" Stream - Not in a chat. %sj to join ", cmdPrefix))
+	setViewTitle("Input", fmt.Sprintf(" Stream - Not in a chat. %sj to join ", config.Basics.CmdPrefix))
 	clearView("Chat")
 }
diff --git a/cmdUploadFile.go b/cmdUploadFile.go
index e6f61ec..07d05a8 100644
--- a/cmdUploadFile.go
+++ b/cmdUploadFile.go
@@ -21,7 +21,7 @@ func init() {
 
 func cmdUploadFile(cmd []string) {
 	if len(cmd) < 2 {
-		printInfo(fmt.Sprintf("%s%s $filePath $fileName - Upload file from absolute path with optional name", cmdPrefix, cmd[0]))
+		printInfo(fmt.Sprintf("%s%s $filePath $fileName - Upload file from absolute path with optional name", config.Basics.CmdPrefix, cmd[0]))
 		return
 	}
 	filePath := cmd[1]
@@ -40,7 +40,7 @@ func cmdUploadFile(cmd []string) {
 	}
 	chat := k.NewChat(channel)
 	_, err := chat.Upload(fileName, filePath)
-	channelName := messageLinkKeybaseColor.stylize(channel.Name).string()
+	channelName := config.Colors.Message.LinkKeybase.stylize(channel.Name).string()
 	if err != nil {
 		printError(fmt.Sprintf("There was an error uploading %s to %s\n%+v", filePath, channelName, err))
 	} else {
diff --git a/cmdWall.go b/cmdWall.go
index 7fe0904..51f27b4 100644
--- a/cmdWall.go
+++ b/cmdWall.go
@@ -65,7 +65,7 @@ func cmdPopulateWall(cmd []string) {
 		return
 	}
 
-	printInfoF("Displaying public messages for user $TEXT", messageLinkKeybaseColor.stylize(requestedUsers))
+	printInfoF("Displaying public messages for user $TEXT", config.Colors.Message.LinkKeybase.stylize(requestedUsers))
 	for _, chann := range users {
 		chat := k.NewChat(chann)
 		api, err := chat.Read()
diff --git a/colors.go b/colors.go
index 1c1dc44..8ff9ad0 100644
--- a/colors.go
+++ b/colors.go
@@ -2,16 +2,12 @@ package main
 
 import (
 	"fmt"
-	"github.com/pelletier/go-toml"
 	"regexp"
 	"strings"
 )
 
-// Begin Colors
-type color int
-
 const (
-	black color = iota
+	black int = iota
 	red
 	green
 	yellow
@@ -19,103 +15,88 @@ const (
 	magenta
 	cyan
 	grey
-	normal color = -1
+	normal int = -1
 )
 
-func colorFromString(s string) color {
-	s = strings.ToLower(s)
-	switch s {
-	case "black":
-		return black
-	case "red":
-		return red
-	case "green":
-		return green
-	case "yellow":
-		return yellow
-	case "purple":
-		return purple
-	case "magenta":
-		return magenta
-	case "cyan":
-		return cyan
-	case "grey":
-		return grey
-	case "normal":
+var colorMapString = map[string]int{
+	"black":   black,
+	"red":     red,
+	"green":   green,
+	"yellow":  yellow,
+	"purple":  purple,
+	"magenta": magenta,
+	"cyan":    cyan,
+	"grey":    grey,
+	"normal":  normal,
+}
+
+var colorMapInt = map[int]string{
+	black:   "black",
+	red:     "red",
+	green:   "green",
+	yellow:  "yellow",
+	purple:  "purple",
+	magenta: "magenta",
+	cyan:    "cyan",
+	grey:    "grey",
+	normal:  "normal",
+}
+
+func colorFromString(color string) int {
+	var result int
+	color = strings.ToLower(color)
+	result, ok := colorMapString[color]
+	if !ok {
 		return normal
-	default:
-		printError(fmt.Sprintf("color `%s` cannot be parsed.", s))
 	}
-	return normal
-}
-
-// Style struct for specializing the style/color of a stylize
-type Style struct {
-	foregroundColor color
-	backgroundColor color
-	bold            bool
-	italic          bool // Currently not supported by the UI library
-	underline       bool
-	strikethrough   bool // Currently not supported by the UI library
-	inverse         bool
+	return result
 }
 
-var basicStyle = Style{normal, normal, false, false, false, false, false}
-
-func styleFromConfig(config *toml.Tree, key string) Style {
-	key = "Colors." + key + "."
-	style := basicStyle
-	if config.Has(key + "foreground") {
-		style = style.withForeground(colorFromString(config.Get(key + "foreground").(string)))
-	}
-	if config.Has(key + "background") {
-		style = style.withForeground(colorFromString(config.Get(key + "background").(string)))
-	}
-	if config.GetDefault(key+"bold", false).(bool) {
-		style = style.withBold()
-	}
-	if config.GetDefault(key+"italic", false).(bool) {
-		style = style.withItalic()
-	}
-	if config.GetDefault(key+"underline", false).(bool) {
-		style = style.withUnderline()
-	}
-	if config.GetDefault(key+"strikethrough", false).(bool) {
-		style = style.withStrikethrough()
-	}
-	if config.GetDefault(key+"inverse", false).(bool) {
-		style = style.withInverse()
+func colorFromInt(color int) string {
+	var result string
+	result, ok := colorMapInt[color]
+	if !ok {
+		return "normal"
 	}
+	return result
+}
 
-	return style
+var basicStyle = Style{
+	Foreground:    colorMapInt[normal],
+	Background:    colorMapInt[normal],
+	Italic:        false,
+	Bold:          false,
+	Underline:     false,
+	Strikethrough: false,
+	Inverse:       false,
 }
 
-func (s Style) withForeground(f color) Style {
-	s.foregroundColor = f
+func (s Style) withForeground(color int) Style {
+	s.Foreground = colorFromInt(color)
 	return s
 }
-func (s Style) withBackground(f color) Style {
-	s.backgroundColor = f
+func (s Style) withBackground(color int) Style {
+	s.Background = colorFromInt(color)
 	return s
 }
 func (s Style) withBold() Style {
-	s.bold = true
+	s.Bold = true
 	return s
 }
 func (s Style) withInverse() Style {
-	s.inverse = true
+	s.Inverse = true
 	return s
 }
 func (s Style) withItalic() Style {
-	s.italic = true
+	s.Italic = true
 	return s
 }
 func (s Style) withStrikethrough() Style {
-	s.strikethrough = true
+	s.Strikethrough = true
 	return s
 }
 func (s Style) withUnderline() Style {
-	s.underline = true
+	s.Underline = true
 	return s
 }
 
@@ -123,29 +104,29 @@ func (s Style) withUnderline() Style {
 //  which essentially just adds on top. that is relevant in the case of
 //  bold/italic etc - it should add style - not clear.
 func (s Style) toANSI() string {
-	if colorless {
+	if config.Basics.Colorless {
 		return ""
 	}
 	output := "\x1b[0m\x1b[0"
-	if s.foregroundColor != normal {
-		output += fmt.Sprintf(";%d", 30+s.foregroundColor)
+	if colorFromString(s.Foreground) != normal {
+		output += fmt.Sprintf(";%d", 30+colorFromString(s.Foreground))
 	}
-	if s.backgroundColor != normal {
-		output += fmt.Sprintf(";%d", 40+s.backgroundColor)
+	if colorFromString(s.Background) != normal {
+		output += fmt.Sprintf(";%d", 40+colorFromString(s.Background))
 	}
-	if s.bold {
+	if s.Bold {
 		output += ";1"
 	}
-	if s.italic {
+	if s.Italic {
 		output += ";3"
 	}
-	if s.underline {
+	if s.Underline {
 		output += ";4"
 	}
-	if s.inverse {
+	if s.Inverse {
 		output += ";7"
 	}
-	if s.strikethrough {
+	if s.Strikethrough {
 		output += ";9"
 	}
 
diff --git a/defaultConfig.go b/defaultConfig.go
new file mode 100644
index 0000000..4d406a5
--- /dev/null
+++ b/defaultConfig.go
@@ -0,0 +1,70 @@
+package main
+
+var defaultConfig = `
+[basics]
+downloadPath = "/tmp/"
+colorless = false
+# The prefix before evaluating a command
+cmdPrefix = "/"
+
+[formatting]
+# BASH-like PS1 variable equivalent
+outputFormat = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
+outputStreamFormat = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
+outputMentionFormat = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
+pmFormat = "PM from $USER@$DEVICE: $MSG"
+
+# 02 = Day, Jan = Month, 06 = Year
+dateFormat = "02Jan06"
+
+# 15 = hours, 04 = minutes, 05 = seconds
+timeFormat = "15:04"
+
+
+[colors]
+	 [colors.channels]
+		  [colors.channels.basic]
+		  foreground = "normal"
+		  [colors.channels.header]
+		  foreground = "magenta"
+		  bold = true
+		  [colors.channels.unread]
+		  foreground = "green"
+		  italic = true
+
+	 [colors.message]
+		  [colors.message.body]
+		  foreground = "normal"
+		  [colors.message.header]
+		  foreground = "grey"
+		  [colors.message.mention]
+		  foreground = "green"
+		  italic = true
+		  bold = true
+		  [colors.message.id]
+		  foreground = "yellow"
+		  [colors.message.time]
+		  foreground = "magenta"
+		  [colors.message.sender_default]
+		  foreground = "cyan"
+		  bold = true
+		  [colors.message.sender_device]
+		  foreground = "cyan"
+		  [colors.message.attachment]
+		  foreground = "red"
+		  [colors.message.link_url]
+		  foreground = "yellow"
+		  [colors.message.link_keybase]
+		  foreground = "yellow"
+		  [colors.message.reaction]
+		  foreground = "magenta"
+		  bold = true
+		  [colors.message.code]
+		  foreground = "cyan"
+		  background = "grey"
+	 [colors.feed]
+		  [colors.feed.basic]
+		  foreground = "grey"
+		  [colors.feed.error]
+		  foreground = "red"
+`
diff --git a/kbtui.toml b/kbtui.toml
index 37a5af4..290b742 100644
--- a/kbtui.toml
+++ b/kbtui.toml
@@ -1,12 +1,15 @@
-[Basics]
+[basics]
 downloadPath = "/tmp/"
 colorless = false
 # The prefix before evaluating a command
 cmdPrefix = "/"
 
-[Formatting]
+[formatting]
 # BASH-like PS1 variable equivalent
 outputFormat = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
+outputStreamFormat = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
+outputMentionFormat = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
+pmFormat = "PM from $USER@$DEVICE: $MSG"
 
 # 02 = Day, Jan = Month, 06 = Year
 dateFormat = "02Jan06"
@@ -15,49 +18,49 @@ dateFormat = "02Jan06"
 timeFormat = "15:04"
 
 
-[Colors]
-	 [Colors.channels]
-		  [Colors.channels.basic]
+[colors]
+	 [colors.channels]
+		  [colors.channels.basic]
 		  foreground = "normal"
-		  [Colors.channels.header]
+		  [colors.channels.header]
 		  foreground = "magenta"
 		  bold = true
-		  [Colors.channels.unread]
+		  [colors.channels.unread]
 		  foreground = "green"
 		  italic = true
 
-	 [Colors.message]
-		  [Colors.message.body]
+	 [colors.message]
+		  [colors.message.body]
 		  foreground = "normal"
-		  [Colors.message.header]
+		  [colors.message.header]
 		  foreground = "grey"
-		  [Colors.message.mention]
+		  [colors.message.mention]
 		  foreground = "green"
 		  italic = true
 		  bold = true
-		  [Colors.message.id]
+		  [colors.message.id]
 		  foreground = "yellow"
-		  [Colors.message.time]
+		  [colors.message.time]
 		  foreground = "magenta"
-		  [Colors.message.sender_default]
+		  [colors.message.sender_default]
 		  foreground = "cyan"
 		  bold = true
-		  [Colors.message.sender_device]
+		  [colors.message.sender_device]
 		  foreground = "cyan"
-		  [Colors.message.attachment]
+		  [colors.message.attachment]
 		  foreground = "red"
-		  [Colors.message.link_url]
+		  [colors.message.link_url]
 		  foreground = "yellow"
-		  [Colors.message.link_keybase]
+		  [colors.message.link_keybase]
 		  foreground = "yellow"
-		  [Colors.message.reaction]
+		  [colors.message.reaction]
 		  foreground = "magenta"
 		  bold = true
-		  [Colors.message.code]
+		  [colors.message.code]
 		  foreground = "cyan"
 		  background = "grey"
-	 [Colors.feed]
-		  [Colors.feed.basic]
+	 [colors.feed]
+		  [colors.feed.basic]
 		  foreground = "grey"
-		  [Colors.feed.error]
+		  [colors.feed.error]
 		  foreground = "red"
\ No newline at end of file
diff --git a/main.go b/main.go
index d83ecbc..16e1dec 100644
--- a/main.go
+++ b/main.go
@@ -25,6 +25,8 @@ var (
 	g           *gocui.Gui
 )
 
+var config *Config
+
 func main() {
 	if !k.LoggedIn {
 		fmt.Println("You are not logged in.")
@@ -37,7 +39,7 @@ func main() {
 	}
 	defer g.Close()
 	g.SetManagerFunc(layout)
-	go RunCommand("config", "load")
+	RunCommand("config", "load")
 	go populateList()
 	go updateChatWindow()
 	if len(os.Args) > 1 {
@@ -82,7 +84,7 @@ func layout(g *gocui.Gui) error {
 		chatView.Autoscroll = true
 		chatView.Wrap = true
 		welcomeText := basicStyle.stylize("Welcome $USER!\n\nYour chats will appear here.\nSupported commands are as follows:\n")
-		welcomeText = welcomeText.replace("$USER", mentionColor.stylize(k.Username))
+		welcomeText = welcomeText.replace("$USER", config.Colors.Message.Mention.stylize(k.Username))
 		fmt.Fprintln(chatView, welcomeText.string())
 		RunCommand("help")
 	}
@@ -95,7 +97,7 @@ func layout(g *gocui.Gui) error {
 		}
 		inputView.Editable = true
 		inputView.Wrap = true
-		inputView.Title = fmt.Sprintf(" Not in a chat - write `%sj` to join", cmdPrefix)
+		inputView.Title = fmt.Sprintf(" Not in a chat - write `%sj` to join", config.Basics.CmdPrefix)
 		g.Cursor = true
 	}
 	if listView, err4 := g.SetView("List", 0, 0, maxX/2-maxX/3-1, maxY-1, 0); err4 != nil {
@@ -255,7 +257,7 @@ func printError(message string) {
 	printErrorF(message)
 }
 func printErrorF(message string, parts ...StyledString) {
-	printToView("Feed", errorColor.sprintf(removeFormatting(message), parts...).string())
+	printToView("Feed", config.Colors.Feed.Error.sprintf(removeFormatting(message), parts...).string())
 }
 
 // this removes formatting
@@ -265,7 +267,7 @@ func printInfo(message string) {
 
 // this removes formatting
 func printInfoF(message string, parts ...StyledString) {
-	printToView("Feed", feedColor.sprintf(removeFormatting(message), parts...).string())
+	printToView("Feed", config.Colors.Feed.Basic.sprintf(removeFormatting(message), parts...).string())
 }
 func printToView(viewName string, message string) {
 	g.Update(func(g *gocui.Gui) error {
@@ -345,10 +347,10 @@ func populateList() {
 		log.Printf("%+v", err)
 	} else {
 		clearView("List")
-		var textBase = channelsColor.stylize("")
-		var recentPMs = textBase.append(channelsHeaderColor.stylize("---[PMs]---\n"))
+		var textBase = config.Colors.Channels.Basic.stylize("")
+		var recentPMs = textBase.append(config.Colors.Channels.Header.stylize("---[PMs]---\n"))
 		var recentPMsCount = 0
-		var recentChannels = textBase.append(channelsHeaderColor.stylize("---[Teams]---\n"))
+		var recentChannels = textBase.append(config.Colors.Channels.Header.stylize("---[Teams]---\n"))
 		var recentChannelsCount = 0
 		for _, s := range testVar.Result.Conversations {
 			channels = append(channels, s.Channel)
@@ -357,7 +359,7 @@ func populateList() {
 				if recentChannelsCount <= ((maxY - 2) / 3) {
 					channel := fmt.Sprintf("%s\n\t#%s\n", s.Channel.Name, s.Channel.TopicName)
 					if s.Unread {
-						recentChannels = recentChannels.append(channelUnreadColor.stylize("*" + channel))
+						recentChannels = recentChannels.append(config.Colors.Channels.Unread.stylize("*" + channel))
 					} else {
 						recentChannels = recentChannels.appendString(channel)
 					}
@@ -367,7 +369,7 @@ func populateList() {
 				if recentPMsCount <= ((maxY - 2) / 3) {
 					pmName := fmt.Sprintf("%s\n", cleanChannelName(s.Channel.Name))
 					if s.Unread {
-						recentPMs = recentPMs.append(channelUnreadColor.stylize("*" + pmName))
+						recentPMs = recentPMs.append(config.Colors.Channels.Unread.stylize("*" + pmName))
 					} else {
 						recentPMs = recentPMs.appendString(pmName)
 					}
@@ -384,35 +386,35 @@ func populateList() {
 
 // Formatting
 func formatMessageBody(body string) StyledString {
-	output := messageBodyColor.stylize(body)
+	output := config.Colors.Message.Body.stylize(body)
 
 	output = colorReplaceMentionMe(output)
-	output = output.colorRegex(`_[^_]*_`, messageBodyColor.withItalic())
-	output = output.colorRegex(`~[^~]*~`, messageBodyColor.withStrikethrough())
-	output = output.colorRegex(`@[\w_]*(\.[\w_]+)*`, messageLinkKeybaseColor)
+	output = output.colorRegex(`_[^_]*_`, config.Colors.Message.Body.withItalic())
+	output = output.colorRegex(`~[^~]*~`, config.Colors.Message.Body.withStrikethrough())
+	output = output.colorRegex(`@[\w_]*(\.[\w_]+)*`, config.Colors.Message.LinkKeybase)
 	// TODO change how bold, italic etc works, so it uses boldOn boldOff ([1m and [22m)
-	output = output.colorRegex(`\*[^\*]*\*`, messageBodyColor.withBold())
+	output = output.colorRegex(`\*[^\*]*\*`, config.Colors.Message.Body.withBold())
 	output = output.replaceString("```", "<code>")
 	// TODO make background color cover whole line
-	output = output.colorRegex("<code>(.*\n)*<code>", messageCodeColor)
-	output = output.colorRegex("`[^`]*`", messageCodeColor)
+	output = output.colorRegex("<code>(.*\n)*<code>", config.Colors.Message.Code)
+	output = output.colorRegex("`[^`]*`", config.Colors.Message.Code)
 	// mention URL
-	output = output.colorRegex(`(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*))`, messageLinkURLColor)
+	output = output.colorRegex(`(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*))`, config.Colors.Message.LinkURL)
 	return output
 }
 
 // TODO use this more
 func formatChannel(ch keybase.Channel) StyledString {
-	return messageLinkKeybaseColor.stylize(fmt.Sprintf("@%s#%s", ch.Name, ch.TopicName))
+	return config.Colors.Message.LinkKeybase.stylize(fmt.Sprintf("@%s#%s", ch.Name, ch.TopicName))
 }
 
 func colorReplaceMentionMe(msg StyledString) StyledString {
-	return msg.colorRegex("(@?"+k.Username+")", mentionColor)
+	return msg.colorRegex("(@?"+k.Username+")", config.Colors.Message.Mention)
 }
 func colorUsername(username string) StyledString {
-	var color = messageSenderDefaultColor
+	var color = config.Colors.Message.SenderDefault
 	if username == k.Username {
-		color = mentionColor
+		color = config.Colors.Message.Mention
 	}
 	return color.stylize(username)
 }
@@ -423,27 +425,27 @@ func cleanChannelName(c string) string {
 }
 
 func formatMessage(api keybase.ChatAPI, formatString string) string {
-	ret := messageHeaderColor.stylize("")
+	ret := config.Colors.Message.Header.stylize("")
 	msgType := api.Msg.Content.Type
 	switch msgType {
 	case "text", "attachment":
-		ret = messageHeaderColor.stylize(formatString)
+		ret = config.Colors.Message.Header.stylize(formatString)
 		tm := time.Unix(int64(api.Msg.SentAt), 0)
 		var msg = formatMessageBody(api.Msg.Content.Text.Body)
 		if msgType == "attachment" {
-			msg = messageBodyColor.stylize("$TITLE\n$FILE")
+			msg = config.Colors.Message.Body.stylize("$TITLE\n$FILE")
 			attachment := api.Msg.Content.Attachment
 			msg = msg.replaceString("$TITLE", attachment.Object.Title)
-			msg = msg.replace("$FILE", messageAttachmentColor.stylize(fmt.Sprintf("[Attachment: %s]", attachment.Object.Filename)))
+			msg = msg.replace("$FILE", config.Colors.Message.Attachment.stylize(fmt.Sprintf("[Attachment: %s]", attachment.Object.Filename)))
 		}
 
 		user := colorUsername(api.Msg.Sender.Username)
-		device := messageSenderDeviceColor.stylize(api.Msg.Sender.DeviceName)
-		msgID := messageIDColor.stylize(fmt.Sprintf("%d", api.Msg.ID))
-		date := messageTimeColor.stylize(tm.Format(dateFormat))
-		msgTime := messageTimeColor.stylize(tm.Format(timeFormat))
+		device := config.Colors.Message.SenderDevice.stylize(api.Msg.Sender.DeviceName)
+		msgID := config.Colors.Message.ID.stylize(fmt.Sprintf("%d", api.Msg.ID))
+		date := config.Colors.Message.Time.stylize(tm.Format(config.Formatting.DateFormat))
+		msgTime := config.Colors.Message.Time.stylize(tm.Format(config.Formatting.TimeFormat))
 
-		channelName := messageIDColor.stylize(fmt.Sprintf("@%s#%s", api.Msg.Channel.Name, api.Msg.Channel.TopicName))
+		channelName := config.Colors.Message.ID.stylize(fmt.Sprintf("@%s#%s", api.Msg.Channel.Name, api.Msg.Channel.TopicName))
 		ret = ret.replace("$MSG", msg)
 		ret = ret.replace("$USER", user)
 		ret = ret.replace("$DEVICE", device)
@@ -455,9 +457,9 @@ func formatMessage(api keybase.ChatAPI, formatString string) string {
 	return ret.string()
 }
 func formatOutput(api keybase.ChatAPI) string {
-	format := outputFormat
+	format := config.Formatting.OutputFormat
 	if stream {
-		format = outputStreamFormat
+		format = config.Formatting.OutputStreamFormat
 	}
 	return formatMessage(api, format)
 }
@@ -485,7 +487,7 @@ func handleMessage(api keybase.ChatAPI) {
 						if m.Text == k.Username {
 							// We are in a team
 							if topicName != channel.TopicName {
-								printInfo(formatMessage(api, mentionFormat))
+								printInfo(formatMessage(api, config.Formatting.OutputMentionFormat))
 								fmt.Print("\a")
 							}
 
@@ -494,7 +496,7 @@ func handleMessage(api keybase.ChatAPI) {
 					}
 				} else {
 					if msgSender != channel.Name {
-						printInfo(formatMessage(api, pmFormat))
+						printInfo(formatMessage(api, config.Formatting.PMFormat))
 						fmt.Print("\a")
 					}
 
@@ -512,7 +514,7 @@ func handleMessage(api keybase.ChatAPI) {
 			if api.Msg.Channel.MembersType == keybase.TEAM {
 				printToView("Chat", formatOutput(api))
 			} else {
-				printToView("Chat", formatMessage(api, pmFormat))
+				printToView("Chat", formatMessage(api, config.Formatting.PMFormat))
 			}
 		}
 	} else {
@@ -546,8 +548,8 @@ func handleInput(viewName string) error {
 	if inputString == "" {
 		return nil
 	}
-	if strings.HasPrefix(inputString, cmdPrefix) {
-		cmd := deleteEmpty(strings.Split(inputString[len(cmdPrefix):], " "))
+	if strings.HasPrefix(inputString, config.Basics.CmdPrefix) {
+		cmd := deleteEmpty(strings.Split(inputString[len(config.Basics.CmdPrefix):], " "))
 		if len(cmd) < 1 {
 			return nil
 		}
diff --git a/tcmdShowReactions.go b/tcmdShowReactions.go
index 5fa278f..ac3f2f6 100644
--- a/tcmdShowReactions.go
+++ b/tcmdShowReactions.go
@@ -22,9 +22,9 @@ func init() {
 func tcmdShowReactions(m keybase.ChatAPI) {
 	team := false
 	user := colorUsername(m.Msg.Sender.Username)
-	id := messageIDColor.stylize(fmt.Sprintf("%d", m.Msg.Content.Reaction.M))
-	reaction := messageReactionColor.stylize(m.Msg.Content.Reaction.B)
-	where := messageLinkKeybaseColor.stylize("a PM")
+	id := config.Colors.Message.ID.stylize(fmt.Sprintf("%d", m.Msg.Content.Reaction.M))
+	reaction := config.Colors.Message.Reaction.stylize(m.Msg.Content.Reaction.B)
+	where := config.Colors.Message.LinkKeybase.stylize("a PM")
 	if m.Msg.Channel.MembersType == keybase.TEAM {
 		team = true
 		where = formatChannel(m.Msg.Channel)
diff --git a/types.go b/types.go
index 77784e3..b8fd72f 100644
--- a/types.go
+++ b/types.go
@@ -17,3 +17,68 @@ type TypeCommand struct {
 	Description string                // A short description of the command
 	Exec        func(keybase.ChatAPI) // A function that takes a raw chat message as input
 }
+
+// Config holds user-configurable values
+type Config struct {
+	filepath   string     `toml:"-"`
+	Basics     Basics     `toml:"basics"`
+	Formatting Formatting `toml:"formatting"`
+	Colors     Colors     `toml:"colors"`
+}
+
+type Basics struct {
+	DownloadPath string `toml:"downloadPath"`
+	Colorless    bool   `toml:"colorless"`
+	CmdPrefix    string `toml:"cmdPrefix"`
+}
+
+type Formatting struct {
+	OutputFormat        string `toml:"outputFormat"`
+	OutputStreamFormat  string `toml:"outputStreamFormat"`
+	OutputMentionFormat string `toml:"outputMentionFormat"`
+	PMFormat            string `toml:"pmFormat"`
+	DateFormat          string `toml:"dateFormat"`
+	TimeFormat          string `toml:"timeFormat"`
+}
+
+type Style struct {
+	Foreground    string `toml:"foreground"`
+	Background    string `toml:"background"`
+	Italic        bool   `toml:"italic"`
+	Bold          bool   `toml:"bold"`
+	Underline     bool   `toml:"underline"`
+	Strikethrough bool   `toml:"strikethrough"`
+	Inverse       bool   `toml:"inverse"`
+}
+
+type Channels struct {
+	Basic  Style `toml:"basic"`
+	Header Style `toml:"header"`
+	Unread Style `toml:"unread"`
+}
+
+type Message struct {
+	Body          Style `toml:"body"`
+	Header        Style `toml:"header"`
+	Mention       Style `toml:"mention"`
+	ID            Style `toml:"id"`
+	Time          Style `toml:"time"`
+	SenderDefault Style `toml:"sender_default"`
+	SenderDevice  Style `toml:"sender_device"`
+	Attachment    Style `toml:"attachment"`
+	LinkURL       Style `toml:"link_url"`
+	LinkKeybase   Style `toml:"link_keybase"`
+	Reaction      Style `toml:"reaction"`
+	Code          Style `toml:"code"`
+}
+
+type Feed struct {
+	Basic Style `toml:"basic"`
+	Error Style `toml:"error"`
+}
+
+type Colors struct {
+	Channels Channels `toml:"channels"`
+	Message  Message  `toml:"message"`
+	Feed     Feed     `toml:"feed"`
+}
diff --git a/userConfigs.go b/userConfigs.go
deleted file mode 100644
index d0c7fd7..0000000
--- a/userConfigs.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package main
-
-// Path where Downloaded files will default to
-var downloadPath = "/tmp/"
-
-var colorless bool = false
-var channelsColor = basicStyle
-var channelUnreadColor = channelsColor.withForeground(green).withItalic()
-var channelsHeaderColor = channelsColor.withForeground(magenta).withBold()
-
-var mentionColor = basicStyle.withForeground(green)
-var messageHeaderColor = basicStyle.withForeground(grey)
-var messageIDColor = basicStyle.withForeground(yellow)
-var messageTimeColor = basicStyle.withForeground(magenta)
-var messageSenderDefaultColor = basicStyle.withForeground(cyan)
-var messageSenderDeviceColor = messageSenderDefaultColor
-var messageBodyColor = basicStyle
-var messageAttachmentColor = basicStyle.withForeground(red)
-var messageLinkURLColor = basicStyle.withForeground(yellow)
-var messageLinkKeybaseColor = basicStyle.withForeground(yellow)
-var messageReactionColor = basicStyle.withForeground(magenta)
-var messageCodeColor = basicStyle.withBackground(grey).withForeground(cyan)
-
-var feedColor = basicStyle.withForeground(grey)
-var errorColor = basicStyle.withForeground(red)
-
-// BASH-like PS1 variable equivalent
-var outputFormat = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
-var outputStreamFormat = "┌──[$TEAM] [$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
-var mentionFormat = outputStreamFormat
-var pmFormat = "PM from $USER@$DEVICE: $MSG"
-
-// 02 = Day, Jan = Month, 06 = Year
-var dateFormat = "02Jan06"
-
-// 15 = hours, 04 = minutes, 05 = seconds
-var timeFormat = "15:04"
-
-// The prefix before evaluating a command
-var cmdPrefix = "/"

From 99922c258252d8daddeb03199e599a29dc032e7c Mon Sep 17 00:00:00 2001
From: Sam <dxb@keybase.io>
Date: Sat, 26 Oct 2019 21:24:41 -0400
Subject: [PATCH 2/5] Update config to be more uniform

---
 defaultConfig.go | 17 +++++++++--------
 kbtui.toml       | 19 ++++++++++---------
 types.go         | 37 ++++++++++++++++++++++---------------
 3 files changed, 41 insertions(+), 32 deletions(-)

diff --git a/defaultConfig.go b/defaultConfig.go
index 4d406a5..88d1b3e 100644
--- a/defaultConfig.go
+++ b/defaultConfig.go
@@ -2,23 +2,24 @@ package main
 
 var defaultConfig = `
 [basics]
-downloadPath = "/tmp/"
+download_path = "/tmp/"
 colorless = false
+
 # The prefix before evaluating a command
-cmdPrefix = "/"
+cmd_prefix = "/"
 
 [formatting]
 # BASH-like PS1 variable equivalent
-outputFormat = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
-outputStreamFormat = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
-outputMentionFormat = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
-pmFormat = "PM from $USER@$DEVICE: $MSG"
+output_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
+output_stream_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
+output_mention_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
+pm_format = "PM from $USER@$DEVICE: $MSG"
 
 # 02 = Day, Jan = Month, 06 = Year
-dateFormat = "02Jan06"
+date_format = "02Jan06"
 
 # 15 = hours, 04 = minutes, 05 = seconds
-timeFormat = "15:04"
+time_format = "15:04"
 
 
 [colors]
diff --git a/kbtui.toml b/kbtui.toml
index 290b742..6196e2e 100644
--- a/kbtui.toml
+++ b/kbtui.toml
@@ -1,21 +1,22 @@
 [basics]
-downloadPath = "/tmp/"
+download_path = "/tmp/"
 colorless = false
+
 # The prefix before evaluating a command
-cmdPrefix = "/"
+cmd_prefix = "/"
 
 [formatting]
 # BASH-like PS1 variable equivalent
-outputFormat = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
-outputStreamFormat = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
-outputMentionFormat = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
-pmFormat = "PM from $USER@$DEVICE: $MSG"
+output_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
+output_stream_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
+output_mention_format = "┌──[$USER@$DEVICE] [$ID] [$DATE - $TIME]\n└╼ $MSG"
+pm_format = "PM from $USER@$DEVICE: $MSG"
 
 # 02 = Day, Jan = Month, 06 = Year
-dateFormat = "02Jan06"
+date_format = "02Jan06"
 
 # 15 = hours, 04 = minutes, 05 = seconds
-timeFormat = "15:04"
+time_format = "15:04"
 
 
 [colors]
@@ -63,4 +64,4 @@ timeFormat = "15:04"
 		  [colors.feed.basic]
 		  foreground = "grey"
 		  [colors.feed.error]
-		  foreground = "red"
\ No newline at end of file
+		  foreground = "red"
diff --git a/types.go b/types.go
index b8fd72f..744a14a 100644
--- a/types.go
+++ b/types.go
@@ -20,27 +20,37 @@ type TypeCommand struct {
 
 // Config holds user-configurable values
 type Config struct {
-	filepath   string     `toml:"-"`
+	filepath   string     `toml:"-"` // filepath is not stored in the config file, but is written to the Config struct so it's known where the config was loaded from
 	Basics     Basics     `toml:"basics"`
 	Formatting Formatting `toml:"formatting"`
 	Colors     Colors     `toml:"colors"`
 }
 
+// Basics holds the 'basics' section of the config file
 type Basics struct {
-	DownloadPath string `toml:"downloadPath"`
+	DownloadPath string `toml:"download_path"`
 	Colorless    bool   `toml:"colorless"`
-	CmdPrefix    string `toml:"cmdPrefix"`
+	CmdPrefix    string `toml:"cmd_prefix"`
 }
 
+// Formatting holds the 'formatting' section of the config file
 type Formatting struct {
-	OutputFormat        string `toml:"outputFormat"`
-	OutputStreamFormat  string `toml:"outputStreamFormat"`
-	OutputMentionFormat string `toml:"outputMentionFormat"`
-	PMFormat            string `toml:"pmFormat"`
-	DateFormat          string `toml:"dateFormat"`
-	TimeFormat          string `toml:"timeFormat"`
+	OutputFormat        string `toml:"output_format"`
+	OutputStreamFormat  string `toml:"output_stream_format"`
+	OutputMentionFormat string `toml:"output_mention_format"`
+	PMFormat            string `toml:"pm_format"`
+	DateFormat          string `toml:"date_format"`
+	TimeFormat          string `toml:"time_format"`
 }
 
+// Colors holds the 'colors' section of the config file
+type Colors struct {
+	Channels Channels `toml:"channels"`
+	Message  Message  `toml:"message"`
+	Feed     Feed     `toml:"feed"`
+}
+
+// Style holds basic style information
 type Style struct {
 	Foreground    string `toml:"foreground"`
 	Background    string `toml:"background"`
@@ -51,12 +61,14 @@ type Style struct {
 	Inverse       bool   `toml:"inverse"`
 }
 
+// Channels holds the style information for various elements of a channel
 type Channels struct {
 	Basic  Style `toml:"basic"`
 	Header Style `toml:"header"`
 	Unread Style `toml:"unread"`
 }
 
+// Message holds the style information for various elements of a message
 type Message struct {
 	Body          Style `toml:"body"`
 	Header        Style `toml:"header"`
@@ -72,13 +84,8 @@ type Message struct {
 	Code          Style `toml:"code"`
 }
 
+// Feed holds the style information for various elements of the feed window
 type Feed struct {
 	Basic Style `toml:"basic"`
 	Error Style `toml:"error"`
 }
-
-type Colors struct {
-	Channels Channels `toml:"channels"`
-	Message  Message  `toml:"message"`
-	Feed     Feed     `toml:"feed"`
-}

From a8e0ed17b931763b7227030545b93ad82db4bd79 Mon Sep 17 00:00:00 2001
From: Sam <dxb@keybase.io>
Date: Sat, 26 Oct 2019 21:38:01 -0400
Subject: [PATCH 3/5] Add unicode_emojis flag to config

---
 defaultConfig.go | 1 +
 kbtui.toml       | 1 +
 types.go         | 7 ++++---
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/defaultConfig.go b/defaultConfig.go
index 88d1b3e..5859e69 100644
--- a/defaultConfig.go
+++ b/defaultConfig.go
@@ -4,6 +4,7 @@ var defaultConfig = `
 [basics]
 download_path = "/tmp/"
 colorless = false
+unicode_emojis = true
 
 # The prefix before evaluating a command
 cmd_prefix = "/"
diff --git a/kbtui.toml b/kbtui.toml
index 6196e2e..a72723f 100644
--- a/kbtui.toml
+++ b/kbtui.toml
@@ -1,6 +1,7 @@
 [basics]
 download_path = "/tmp/"
 colorless = false
+unicode_emojis = true
 
 # The prefix before evaluating a command
 cmd_prefix = "/"
diff --git a/types.go b/types.go
index 744a14a..9ce3de3 100644
--- a/types.go
+++ b/types.go
@@ -28,9 +28,10 @@ type Config struct {
 
 // Basics holds the 'basics' section of the config file
 type Basics struct {
-	DownloadPath string `toml:"download_path"`
-	Colorless    bool   `toml:"colorless"`
-	CmdPrefix    string `toml:"cmd_prefix"`
+	DownloadPath  string `toml:"download_path"`
+	Colorless     bool   `toml:"colorless"`
+	CmdPrefix     string `toml:"cmd_prefix"`
+	UnicodeEmojis bool   `toml:"unicode_emojis"`
 }
 
 // Formatting holds the 'formatting' section of the config file

From 05afd348d0fdfea7221e050a9a3ff05128948fc9 Mon Sep 17 00:00:00 2001
From: Sam <dxb@keybase.io>
Date: Sat, 26 Oct 2019 22:16:37 -0400
Subject: [PATCH 4/5] Load default config first, so missing values remain
 default

---
 cmdConfig.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/cmdConfig.go b/cmdConfig.go
index 64a14ee..8ed3098 100644
--- a/cmdConfig.go
+++ b/cmdConfig.go
@@ -53,6 +53,10 @@ func readConfig(filepath ...string) (*Config, error) {
 	var configFile string
 	var env bool
 
+	// Load default config first, this way any values missing from the provided config file will remain the default value
+	d := []byte(defaultConfig)
+	toml.Unmarshal(d, result)
+
 	switch len(filepath) {
 	case 0:
 		configFile, env = os.LookupEnv("KBTUI_CFG")

From 876668d3aa1ca1a14b9159fa3a00425bc70da353 Mon Sep 17 00:00:00 2001
From: Sam <dxb@keybase.io>
Date: Mon, 28 Oct 2019 15:23:41 -0400
Subject: [PATCH 5/5] Use github.com/pelletier/go-toml instead of
 github.com/BurntSushi/toml

---
 cmdConfig.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/cmdConfig.go b/cmdConfig.go
index 8ed3098..5d7355b 100644
--- a/cmdConfig.go
+++ b/cmdConfig.go
@@ -7,7 +7,7 @@ import (
 	"io/ioutil"
 	"os"
 
-	"github.com/BurntSushi/toml"
+	"github.com/pelletier/go-toml"
 )
 
 func init() {