2018-01-10 01:18:19 +01:00
|
|
|
package config
|
|
|
|
|
|
|
|
import (
|
2019-03-21 22:36:42 +01:00
|
|
|
"errors"
|
2018-01-10 03:24:50 +01:00
|
|
|
"fmt"
|
2019-05-22 18:35:44 +02:00
|
|
|
"io/ioutil"
|
2022-01-24 11:45:35 +01:00
|
|
|
"log"
|
2019-05-18 21:29:26 +02:00
|
|
|
"net/url"
|
2019-05-16 23:26:08 +02:00
|
|
|
"os"
|
2019-05-18 21:29:26 +02:00
|
|
|
"os/exec"
|
2018-01-10 01:18:19 +01:00
|
|
|
"path"
|
2019-03-31 20:42:18 +02:00
|
|
|
"regexp"
|
2019-06-05 19:48:00 +02:00
|
|
|
"sort"
|
2021-11-13 09:10:09 +01:00
|
|
|
"strconv"
|
2018-01-10 03:24:50 +01:00
|
|
|
"strings"
|
2019-12-20 19:21:33 +01:00
|
|
|
"time"
|
2018-01-10 01:18:19 +01:00
|
|
|
"unicode"
|
2018-01-10 17:19:45 +01:00
|
|
|
|
2020-11-30 23:07:03 +01:00
|
|
|
"github.com/gdamore/tcell/v2"
|
2018-01-10 17:19:45 +01:00
|
|
|
"github.com/go-ini/ini"
|
2020-01-23 13:56:48 +01:00
|
|
|
"github.com/imdario/mergo"
|
2018-01-10 17:19:45 +01:00
|
|
|
"github.com/kyoh86/xdg"
|
2019-11-03 13:51:14 +01:00
|
|
|
|
2021-11-05 10:19:46 +01:00
|
|
|
"git.sr.ht/~rjarry/aerc/lib/templates"
|
2018-01-10 01:18:19 +01:00
|
|
|
)
|
|
|
|
|
2019-06-25 09:23:51 +02:00
|
|
|
type GeneralConfig struct {
|
|
|
|
DefaultSavePath string `ini:"default-save-path"`
|
|
|
|
}
|
|
|
|
|
2018-01-10 01:18:19 +01:00
|
|
|
type UIConfig struct {
|
2019-12-20 19:21:33 +01:00
|
|
|
IndexFormat string `ini:"index-format"`
|
|
|
|
TimestampFormat string `ini:"timestamp-format"`
|
2021-10-26 17:24:45 +02:00
|
|
|
ThisDayTimeFormat string `ini:"this-day-time-format"`
|
2021-11-06 17:32:38 +01:00
|
|
|
ThisWeekTimeFormat string `ini:"this-week-time-format"`
|
2021-10-26 17:24:45 +02:00
|
|
|
ThisYearTimeFormat string `ini:"this-year-time-format"`
|
2019-12-20 19:21:33 +01:00
|
|
|
ShowHeaders []string `delim:","`
|
|
|
|
RenderAccountTabs string `ini:"render-account-tabs"`
|
2020-03-07 17:42:41 +01:00
|
|
|
PinnedTabMarker string `ini:"pinned-tab-marker"`
|
2019-12-20 19:21:33 +01:00
|
|
|
SidebarWidth int `ini:"sidebar-width"`
|
|
|
|
PreviewHeight int `ini:"preview-height"`
|
|
|
|
EmptyMessage string `ini:"empty-message"`
|
|
|
|
EmptyDirlist string `ini:"empty-dirlist"`
|
|
|
|
MouseEnabled bool `ini:"mouse-enabled"`
|
2021-11-12 18:12:02 +01:00
|
|
|
ThreadingEnabled bool `ini:"threading-enabled"`
|
2019-12-20 19:21:33 +01:00
|
|
|
NewMessageBell bool `ini:"new-message-bell"`
|
|
|
|
Spinner string `ini:"spinner"`
|
|
|
|
SpinnerDelimiter string `ini:"spinner-delimiter"`
|
|
|
|
DirListFormat string `ini:"dirlist-format"`
|
2022-01-28 10:17:13 +01:00
|
|
|
DirListDelay time.Duration `ini:"dirlist-delay"`
|
2019-12-20 19:21:33 +01:00
|
|
|
Sort []string `delim:" "`
|
|
|
|
NextMessageOnDelete bool `ini:"next-message-on-delete"`
|
|
|
|
CompletionDelay time.Duration `ini:"completion-delay"`
|
|
|
|
CompletionPopovers bool `ini:"completion-popovers"`
|
2020-07-27 10:03:55 +02:00
|
|
|
StyleSetDirs []string `ini:"stylesets-dirs" delim:":"`
|
|
|
|
StyleSetName string `ini:"styleset-name"`
|
|
|
|
style StyleSet
|
2021-11-29 23:48:35 +01:00
|
|
|
// customize border appearance
|
|
|
|
BorderCharVertical rune `ini:"-"`
|
|
|
|
BorderCharHorizontal rune `ini:"-"`
|
2018-01-10 01:18:19 +01:00
|
|
|
}
|
|
|
|
|
2020-01-24 18:18:49 +01:00
|
|
|
type ContextType int
|
|
|
|
|
2020-01-23 13:56:48 +01:00
|
|
|
const (
|
2020-01-24 18:18:49 +01:00
|
|
|
UI_CONTEXT_FOLDER ContextType = iota
|
2020-01-23 13:56:48 +01:00
|
|
|
UI_CONTEXT_ACCOUNT
|
|
|
|
UI_CONTEXT_SUBJECT
|
2021-12-10 22:27:29 +01:00
|
|
|
BIND_CONTEXT_ACCOUNT
|
2020-01-23 13:56:48 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type UIConfigContext struct {
|
2020-01-24 18:18:49 +01:00
|
|
|
ContextType ContextType
|
2020-01-23 13:56:48 +01:00
|
|
|
Regex *regexp.Regexp
|
|
|
|
UiConfig UIConfig
|
|
|
|
}
|
|
|
|
|
2019-03-31 20:24:53 +02:00
|
|
|
const (
|
|
|
|
FILTER_MIMETYPE = iota
|
|
|
|
FILTER_HEADER
|
|
|
|
)
|
|
|
|
|
2018-01-10 01:18:19 +01:00
|
|
|
type AccountConfig struct {
|
2021-11-13 09:10:09 +01:00
|
|
|
Archive string
|
|
|
|
CopyTo string
|
|
|
|
Default string
|
|
|
|
Postpone string
|
|
|
|
From string
|
|
|
|
Aliases string
|
|
|
|
Name string
|
|
|
|
Source string
|
|
|
|
SourceCredCmd string
|
|
|
|
Folders []string
|
|
|
|
FoldersExclude []string
|
|
|
|
Params map[string]string
|
|
|
|
Outgoing string
|
|
|
|
OutgoingCredCmd string
|
|
|
|
SignatureFile string
|
|
|
|
SignatureCmd string
|
|
|
|
EnableFoldersSort bool `ini:"enable-folders-sort"`
|
|
|
|
FoldersSort []string `ini:"folders-sort" delim:","`
|
2018-01-10 01:18:19 +01:00
|
|
|
}
|
|
|
|
|
2019-03-21 22:36:42 +01:00
|
|
|
type BindingConfig struct {
|
2019-05-14 20:27:28 +02:00
|
|
|
Global *KeyBindings
|
2019-05-21 22:31:04 +02:00
|
|
|
AccountWizard *KeyBindings
|
2019-05-14 20:27:28 +02:00
|
|
|
Compose *KeyBindings
|
|
|
|
ComposeEditor *KeyBindings
|
|
|
|
ComposeReview *KeyBindings
|
|
|
|
MessageList *KeyBindings
|
|
|
|
MessageView *KeyBindings
|
|
|
|
Terminal *KeyBindings
|
2019-03-21 22:36:42 +01:00
|
|
|
}
|
|
|
|
|
2021-12-10 22:27:29 +01:00
|
|
|
type BindingConfigContext struct {
|
|
|
|
ContextType ContextType
|
|
|
|
Regex *regexp.Regexp
|
|
|
|
Bindings *KeyBindings
|
|
|
|
BindContext string
|
|
|
|
}
|
|
|
|
|
2019-05-14 21:25:30 +02:00
|
|
|
type ComposeConfig struct {
|
2019-12-20 19:21:35 +01:00
|
|
|
Editor string `ini:"editor"`
|
|
|
|
HeaderLayout [][]string `ini:"-"`
|
|
|
|
AddressBookCmd string `ini:"address-book-cmd"`
|
2019-05-14 21:25:30 +02:00
|
|
|
}
|
|
|
|
|
2019-03-31 20:24:53 +02:00
|
|
|
type FilterConfig struct {
|
|
|
|
FilterType int
|
|
|
|
Filter string
|
|
|
|
Command string
|
2019-03-31 20:42:18 +02:00
|
|
|
Header string
|
|
|
|
Regex *regexp.Regexp
|
2019-03-31 20:24:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type ViewerConfig struct {
|
2019-07-17 22:51:02 +02:00
|
|
|
Pager string
|
|
|
|
Alternatives []string
|
2019-07-18 00:42:03 +02:00
|
|
|
ShowHeaders bool `ini:"show-headers"`
|
|
|
|
AlwaysShowMime bool `ini:"always-show-mime"`
|
2019-07-17 22:51:02 +02:00
|
|
|
HeaderLayout [][]string `ini:"-"`
|
2019-03-31 20:24:53 +02:00
|
|
|
}
|
|
|
|
|
2019-07-21 22:01:51 +02:00
|
|
|
type TriggersConfig struct {
|
|
|
|
NewEmail string `ini:"new-email"`
|
|
|
|
ExecuteCommand func(command []string) error
|
|
|
|
}
|
|
|
|
|
2019-11-03 13:51:14 +01:00
|
|
|
type TemplateConfig struct {
|
2020-01-23 18:18:40 +01:00
|
|
|
TemplateDirs []string `ini:"template-dirs", delim:":"`
|
2022-01-19 21:28:06 +01:00
|
|
|
NewMessage string `ini:"new-message"`
|
2020-01-23 18:18:40 +01:00
|
|
|
QuotedReply string `ini:"quoted-reply"`
|
|
|
|
Forwards string `ini:"forwards"`
|
2019-11-03 13:51:14 +01:00
|
|
|
}
|
|
|
|
|
2018-01-10 01:18:19 +01:00
|
|
|
type AercConfig struct {
|
2021-12-10 22:27:29 +01:00
|
|
|
Bindings BindingConfig
|
|
|
|
ContextualBinds []BindingConfigContext
|
|
|
|
Compose ComposeConfig
|
|
|
|
Ini *ini.File `ini:"-"`
|
|
|
|
Accounts []AccountConfig `ini:"-"`
|
|
|
|
Filters []FilterConfig `ini:"-"`
|
|
|
|
Viewer ViewerConfig `ini:"-"`
|
|
|
|
Triggers TriggersConfig `ini:"-"`
|
|
|
|
Ui UIConfig
|
|
|
|
ContextualUis []UIConfigContext
|
|
|
|
General GeneralConfig
|
|
|
|
Templates TemplateConfig
|
2018-01-10 01:18:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Input: TimestampFormat
|
|
|
|
// Output: timestamp-format
|
|
|
|
func mapName(raw string) string {
|
|
|
|
newstr := make([]rune, 0, len(raw))
|
|
|
|
for i, chr := range raw {
|
|
|
|
if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
|
|
|
|
if i > 0 {
|
|
|
|
newstr = append(newstr, '-')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
newstr = append(newstr, unicode.ToLower(chr))
|
|
|
|
}
|
|
|
|
return string(newstr)
|
|
|
|
}
|
|
|
|
|
2018-01-10 03:24:50 +01:00
|
|
|
func loadAccountConfig(path string) ([]AccountConfig, error) {
|
2018-01-10 17:19:45 +01:00
|
|
|
file, err := ini.Load(path)
|
|
|
|
if err != nil {
|
2019-05-22 17:35:55 +02:00
|
|
|
// No config triggers account configuration wizard
|
|
|
|
return nil, nil
|
2018-01-10 03:24:50 +01:00
|
|
|
}
|
|
|
|
file.NameMapper = mapName
|
2018-01-10 17:19:45 +01:00
|
|
|
|
|
|
|
var accounts []AccountConfig
|
2018-01-10 03:24:50 +01:00
|
|
|
for _, _sec := range file.SectionStrings() {
|
|
|
|
if _sec == "DEFAULT" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
sec := file.Section(_sec)
|
2019-03-16 02:33:08 +01:00
|
|
|
account := AccountConfig{
|
2021-11-13 09:10:09 +01:00
|
|
|
Archive: "Archive",
|
|
|
|
Default: "INBOX",
|
|
|
|
Postpone: "Drafts",
|
|
|
|
Name: _sec,
|
|
|
|
Params: make(map[string]string),
|
|
|
|
EnableFoldersSort: true,
|
2019-03-16 02:33:08 +01:00
|
|
|
}
|
2018-01-10 03:24:50 +01:00
|
|
|
if err = sec.MapTo(&account); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for key, val := range sec.KeysHash() {
|
2019-03-16 02:33:08 +01:00
|
|
|
if key == "folders" {
|
2019-06-05 19:48:00 +02:00
|
|
|
folders := strings.Split(val, ",")
|
|
|
|
sort.Strings(folders)
|
|
|
|
account.Folders = folders
|
2020-07-01 09:52:14 +02:00
|
|
|
} else if key == "folders-exclude" {
|
|
|
|
folders := strings.Split(val, ",")
|
|
|
|
sort.Strings(folders)
|
|
|
|
account.FoldersExclude = folders
|
2019-05-21 20:31:14 +02:00
|
|
|
} else if key == "source-cred-cmd" {
|
2019-05-18 21:29:26 +02:00
|
|
|
account.SourceCredCmd = val
|
2019-05-13 05:35:36 +02:00
|
|
|
} else if key == "outgoing" {
|
|
|
|
account.Outgoing = val
|
2019-05-21 20:31:14 +02:00
|
|
|
} else if key == "outgoing-cred-cmd" {
|
2019-05-18 21:29:26 +02:00
|
|
|
account.OutgoingCredCmd = val
|
2019-05-13 22:04:01 +02:00
|
|
|
} else if key == "from" {
|
|
|
|
account.From = val
|
2020-08-20 19:22:50 +02:00
|
|
|
} else if key == "aliases" {
|
|
|
|
account.Aliases = val
|
2019-05-16 01:41:21 +02:00
|
|
|
} else if key == "copy-to" {
|
|
|
|
account.CopyTo = val
|
2019-06-08 19:41:56 +02:00
|
|
|
} else if key == "archive" {
|
|
|
|
account.Archive = val
|
2021-11-13 09:10:09 +01:00
|
|
|
} else if key == "enable-folders-sort" {
|
|
|
|
account.EnableFoldersSort, _ = strconv.ParseBool(val)
|
2018-01-10 03:24:50 +01:00
|
|
|
} else if key != "name" {
|
|
|
|
account.Params[key] = val
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if account.Source == "" {
|
|
|
|
return nil, fmt.Errorf("Expected source for account %s", _sec)
|
|
|
|
}
|
2020-11-12 22:58:02 +01:00
|
|
|
if account.From == "" {
|
|
|
|
return nil, fmt.Errorf("Expected from for account %s", _sec)
|
|
|
|
}
|
2019-05-18 21:29:26 +02:00
|
|
|
|
|
|
|
source, err := parseCredential(account.Source, account.SourceCredCmd)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Invalid source credentials for %s: %s", _sec, err)
|
|
|
|
}
|
|
|
|
account.Source = source
|
|
|
|
|
|
|
|
outgoing, err := parseCredential(account.Outgoing, account.OutgoingCredCmd)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Invalid outgoing credentials for %s: %s", _sec, err)
|
|
|
|
}
|
|
|
|
account.Outgoing = outgoing
|
|
|
|
|
2018-01-10 03:24:50 +01:00
|
|
|
accounts = append(accounts, account)
|
|
|
|
}
|
|
|
|
return accounts, nil
|
|
|
|
}
|
|
|
|
|
2019-05-18 21:29:26 +02:00
|
|
|
func parseCredential(cred, command string) (string, error) {
|
|
|
|
if cred == "" || command == "" {
|
|
|
|
return cred, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
u, err := url.Parse(cred)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
// ignore the command if a password is specified
|
|
|
|
if _, exists := u.User.Password(); exists {
|
|
|
|
return cred, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// don't attempt to parse the command if the url is a path (ie /usr/bin/sendmail)
|
|
|
|
if !u.IsAbs() {
|
|
|
|
return cred, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd := exec.Command("sh", "-c", command)
|
2019-08-07 17:27:04 +02:00
|
|
|
cmd.Stdin = os.Stdin
|
2019-05-18 21:29:26 +02:00
|
|
|
output, err := cmd.Output()
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("failed to read password: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
pw := strings.TrimSpace(string(output))
|
|
|
|
u.User = url.UserPassword(u.User.Username(), pw)
|
|
|
|
|
|
|
|
return u.String(), nil
|
|
|
|
}
|
|
|
|
|
2019-05-22 18:35:44 +02:00
|
|
|
func installTemplate(root, sharedir, name string) error {
|
|
|
|
if _, err := os.Stat(root); os.IsNotExist(err) {
|
|
|
|
err := os.MkdirAll(root, 0755)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
data, err := ioutil.ReadFile(path.Join(sharedir, name))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = ioutil.WriteFile(path.Join(root, name), data, 0644)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-07-13 19:42:22 +02:00
|
|
|
func (config *AercConfig) LoadConfig(file *ini.File) error {
|
|
|
|
if filters, err := file.GetSection("filters"); err == nil {
|
|
|
|
// TODO: Parse the filter more finely, e.g. parse the regex
|
|
|
|
for _, match := range filters.KeyStrings() {
|
|
|
|
cmd := filters.KeysHash()[match]
|
|
|
|
filter := FilterConfig{
|
|
|
|
Command: cmd,
|
|
|
|
Filter: match,
|
|
|
|
}
|
|
|
|
if strings.Contains(match, ",~") {
|
|
|
|
filter.FilterType = FILTER_HEADER
|
|
|
|
header := filter.Filter[:strings.Index(filter.Filter, ",")]
|
|
|
|
regex := filter.Filter[strings.Index(filter.Filter, "~")+1:]
|
|
|
|
filter.Header = strings.ToLower(header)
|
|
|
|
filter.Regex, err = regexp.Compile(regex)
|
|
|
|
if err != nil {
|
2021-04-09 21:26:09 +02:00
|
|
|
return err
|
2019-07-13 19:42:22 +02:00
|
|
|
}
|
|
|
|
} else if strings.ContainsRune(match, ',') {
|
|
|
|
filter.FilterType = FILTER_HEADER
|
|
|
|
header := filter.Filter[:strings.Index(filter.Filter, ",")]
|
|
|
|
value := filter.Filter[strings.Index(filter.Filter, ",")+1:]
|
|
|
|
filter.Header = strings.ToLower(header)
|
|
|
|
filter.Regex, err = regexp.Compile(regexp.QuoteMeta(value))
|
2021-04-09 21:26:09 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-07-13 19:42:22 +02:00
|
|
|
} else {
|
|
|
|
filter.FilterType = FILTER_MIMETYPE
|
|
|
|
}
|
|
|
|
config.Filters = append(config.Filters, filter)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if viewer, err := file.GetSection("viewer"); err == nil {
|
|
|
|
if err := viewer.MapTo(&config.Viewer); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for key, val := range viewer.KeysHash() {
|
|
|
|
switch key {
|
|
|
|
case "alternatives":
|
|
|
|
config.Viewer.Alternatives = strings.Split(val, ",")
|
2019-07-15 20:56:44 +02:00
|
|
|
case "header-layout":
|
|
|
|
config.Viewer.HeaderLayout = parseLayout(val)
|
2019-07-13 19:42:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if compose, err := file.GetSection("compose"); err == nil {
|
|
|
|
if err := compose.MapTo(&config.Compose); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-07-23 01:29:07 +02:00
|
|
|
for key, val := range compose.KeysHash() {
|
|
|
|
switch key {
|
|
|
|
case "header-layout":
|
|
|
|
config.Compose.HeaderLayout = parseLayout(val)
|
|
|
|
}
|
|
|
|
}
|
2019-07-13 19:42:22 +02:00
|
|
|
}
|
2021-12-10 22:27:29 +01:00
|
|
|
|
2019-07-13 19:42:22 +02:00
|
|
|
if ui, err := file.GetSection("ui"); err == nil {
|
|
|
|
if err := ui.MapTo(&config.Ui); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-11-29 23:48:35 +01:00
|
|
|
if err := validateBorderChars(ui, &config.Ui); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-07-13 19:42:22 +02:00
|
|
|
}
|
2021-12-10 22:27:29 +01:00
|
|
|
|
2020-01-23 13:56:48 +01:00
|
|
|
for _, sectionName := range file.SectionStrings() {
|
|
|
|
if !strings.Contains(sectionName, "ui:") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
uiSection, err := file.GetSection(sectionName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
uiSubConfig := UIConfig{}
|
|
|
|
if err := uiSection.MapTo(&uiSubConfig); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-11-29 23:48:35 +01:00
|
|
|
if err := validateBorderChars(uiSection, &uiSubConfig); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-01-23 13:56:48 +01:00
|
|
|
contextualUi :=
|
|
|
|
UIConfigContext{
|
|
|
|
UiConfig: uiSubConfig,
|
|
|
|
}
|
|
|
|
|
|
|
|
var index int
|
|
|
|
if strings.Contains(sectionName, "~") {
|
|
|
|
index = strings.Index(sectionName, "~")
|
|
|
|
regex := string(sectionName[index+1:])
|
|
|
|
contextualUi.Regex, err = regexp.Compile(regex)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else if strings.Contains(sectionName, "=") {
|
|
|
|
index = strings.Index(sectionName, "=")
|
|
|
|
value := string(sectionName[index+1:])
|
|
|
|
contextualUi.Regex, err = regexp.Compile(regexp.QuoteMeta(value))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("Invalid Ui Context regex in %s", sectionName)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch sectionName[3:index] {
|
|
|
|
case "account":
|
|
|
|
contextualUi.ContextType = UI_CONTEXT_ACCOUNT
|
|
|
|
case "folder":
|
|
|
|
contextualUi.ContextType = UI_CONTEXT_FOLDER
|
|
|
|
case "subject":
|
|
|
|
contextualUi.ContextType = UI_CONTEXT_SUBJECT
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("Unknown Contextual Ui Section: %s", sectionName)
|
|
|
|
}
|
|
|
|
config.ContextualUis = append(config.ContextualUis, contextualUi)
|
|
|
|
}
|
2019-07-21 22:01:51 +02:00
|
|
|
if triggers, err := file.GetSection("triggers"); err == nil {
|
|
|
|
if err := triggers.MapTo(&config.Triggers); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2019-11-03 13:51:14 +01:00
|
|
|
if templatesSec, err := file.GetSection("templates"); err == nil {
|
|
|
|
if err := templatesSec.MapTo(&config.Templates); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
templateDirs := templatesSec.Key("template-dirs").String()
|
2019-11-10 17:00:21 +01:00
|
|
|
if templateDirs != "" {
|
|
|
|
config.Templates.TemplateDirs = strings.Split(templateDirs, ":")
|
|
|
|
}
|
2019-11-03 13:51:14 +01:00
|
|
|
for key, val := range templatesSec.KeysHash() {
|
|
|
|
if key == "template-dirs" {
|
|
|
|
continue
|
|
|
|
}
|
2020-11-10 20:27:30 +01:00
|
|
|
// we want to fail during startup if the templates are not ok
|
|
|
|
// hence we do a dummy execute here
|
2019-11-03 13:51:14 +01:00
|
|
|
_, err := templates.ParseTemplateFromFile(
|
2020-11-10 20:27:30 +01:00
|
|
|
val, config.Templates.TemplateDirs, templates.DummyData())
|
2019-11-03 13:51:14 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-07-27 10:03:55 +02:00
|
|
|
|
|
|
|
if err := config.Ui.loadStyleSet(
|
|
|
|
config.Ui.StyleSetDirs); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for idx, contextualUi := range config.ContextualUis {
|
|
|
|
if contextualUi.UiConfig.StyleSetName == "" &&
|
|
|
|
len(contextualUi.UiConfig.StyleSetDirs) == 0 {
|
|
|
|
continue // no need to do anything if nothing is overridden
|
|
|
|
}
|
|
|
|
// fill in the missing part from the base
|
|
|
|
if contextualUi.UiConfig.StyleSetName == "" {
|
|
|
|
config.ContextualUis[idx].UiConfig.StyleSetName = config.Ui.StyleSetName
|
|
|
|
} else if len(contextualUi.UiConfig.StyleSetDirs) == 0 {
|
|
|
|
config.ContextualUis[idx].UiConfig.StyleSetDirs = config.Ui.StyleSetDirs
|
|
|
|
}
|
|
|
|
// since at least one of them has changed, load the styleset
|
|
|
|
if err := config.ContextualUis[idx].UiConfig.loadStyleSet(
|
|
|
|
config.ContextualUis[idx].UiConfig.StyleSetDirs); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-13 19:42:22 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-11-29 23:48:35 +01:00
|
|
|
func validateBorderChars(section *ini.Section, config *UIConfig) error {
|
|
|
|
for key, val := range section.KeysHash() {
|
|
|
|
switch key {
|
|
|
|
case "border-char-vertical":
|
|
|
|
char := []rune(val)
|
|
|
|
if len(char) != 1 {
|
|
|
|
return fmt.Errorf("%v must be one and only one character", key)
|
|
|
|
}
|
|
|
|
config.BorderCharVertical = char[0]
|
|
|
|
case "border-char-horizontal":
|
|
|
|
char := []rune(val)
|
|
|
|
if len(char) != 1 {
|
|
|
|
return fmt.Errorf("%v must be one and only one character", key)
|
|
|
|
}
|
|
|
|
config.BorderCharHorizontal = char[0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-01-24 11:45:35 +01:00
|
|
|
func LoadConfigFromFile(root *string, sharedir string, logger *log.Logger) (*AercConfig, error) {
|
2018-01-10 01:18:19 +01:00
|
|
|
if root == nil {
|
|
|
|
_root := path.Join(xdg.ConfigHome(), "aerc")
|
|
|
|
root = &_root
|
|
|
|
}
|
2019-05-16 23:26:08 +02:00
|
|
|
filename := path.Join(*root, "accounts.conf")
|
|
|
|
if err := checkConfigPerms(filename); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
filename = path.Join(*root, "aerc.conf")
|
2021-04-17 18:50:35 +02:00
|
|
|
|
|
|
|
// if it doesn't exist copy over the template, then load
|
|
|
|
if _, err := os.Stat(filename); errors.Is(err, os.ErrNotExist) {
|
|
|
|
if err := installTemplate(*root, sharedir, "aerc.conf"); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-28 16:06:38 +02:00
|
|
|
file, err := ini.LoadSources(ini.LoadOptions{
|
|
|
|
KeyValueDelimiters: "=",
|
|
|
|
}, filename)
|
2018-01-10 17:19:45 +01:00
|
|
|
if err != nil {
|
2021-04-17 18:50:35 +02:00
|
|
|
return nil, err
|
2018-01-10 01:18:19 +01:00
|
|
|
}
|
|
|
|
file.NameMapper = mapName
|
|
|
|
config := &AercConfig{
|
2019-03-21 22:36:42 +01:00
|
|
|
Bindings: BindingConfig{
|
2019-05-14 20:27:28 +02:00
|
|
|
Global: NewKeyBindings(),
|
2019-05-21 22:31:04 +02:00
|
|
|
AccountWizard: NewKeyBindings(),
|
2019-05-14 20:27:28 +02:00
|
|
|
Compose: NewKeyBindings(),
|
|
|
|
ComposeEditor: NewKeyBindings(),
|
|
|
|
ComposeReview: NewKeyBindings(),
|
|
|
|
MessageList: NewKeyBindings(),
|
|
|
|
MessageView: NewKeyBindings(),
|
|
|
|
Terminal: NewKeyBindings(),
|
2019-03-21 22:36:42 +01:00
|
|
|
},
|
2021-12-10 22:27:29 +01:00
|
|
|
|
|
|
|
ContextualBinds: []BindingConfigContext{},
|
|
|
|
|
2019-03-21 22:36:42 +01:00
|
|
|
Ini: file,
|
2019-03-15 06:12:06 +01:00
|
|
|
|
2018-01-10 01:18:19 +01:00
|
|
|
Ui: UIConfig{
|
2021-10-26 17:24:45 +02:00
|
|
|
IndexFormat: "%D %-17.17n %s",
|
|
|
|
TimestampFormat: "2006-01-02 03:04 PM",
|
|
|
|
ThisDayTimeFormat: "",
|
2021-11-06 17:32:38 +01:00
|
|
|
ThisWeekTimeFormat: "",
|
2021-10-26 17:24:45 +02:00
|
|
|
ThisYearTimeFormat: "",
|
2018-01-10 01:18:19 +01:00
|
|
|
ShowHeaders: []string{
|
|
|
|
"From", "To", "Cc", "Bcc", "Subject", "Date",
|
|
|
|
},
|
2019-09-20 21:22:09 +02:00
|
|
|
RenderAccountTabs: "auto",
|
2020-03-07 17:42:41 +01:00
|
|
|
PinnedTabMarker: "`",
|
2019-09-20 21:22:09 +02:00
|
|
|
SidebarWidth: 20,
|
|
|
|
PreviewHeight: 12,
|
|
|
|
EmptyMessage: "(no messages)",
|
|
|
|
EmptyDirlist: "(no folders)",
|
|
|
|
MouseEnabled: false,
|
|
|
|
NewMessageBell: true,
|
|
|
|
Spinner: "[..] , [..] , [..] , [..] , [..], [..] , [..] , [..] ",
|
|
|
|
SpinnerDelimiter: ",",
|
|
|
|
DirListFormat: "%n %>r",
|
2022-01-28 10:17:13 +01:00
|
|
|
DirListDelay: 200 * time.Millisecond,
|
2019-09-20 21:22:09 +02:00
|
|
|
NextMessageOnDelete: true,
|
2019-12-20 19:21:33 +01:00
|
|
|
CompletionDelay: 250 * time.Millisecond,
|
|
|
|
CompletionPopovers: true,
|
2020-07-27 10:03:55 +02:00
|
|
|
StyleSetDirs: []string{path.Join(sharedir, "stylesets")},
|
|
|
|
StyleSetName: "default",
|
2021-11-29 23:48:35 +01:00
|
|
|
// border defaults
|
|
|
|
BorderCharVertical: ' ',
|
|
|
|
BorderCharHorizontal: ' ',
|
2018-01-10 01:18:19 +01:00
|
|
|
},
|
2019-07-15 20:56:44 +02:00
|
|
|
|
2020-01-23 13:56:48 +01:00
|
|
|
ContextualUis: []UIConfigContext{},
|
|
|
|
|
2019-07-15 20:56:44 +02:00
|
|
|
Viewer: ViewerConfig{
|
|
|
|
Pager: "less -R",
|
|
|
|
Alternatives: []string{"text/plain", "text/html"},
|
|
|
|
ShowHeaders: false,
|
|
|
|
HeaderLayout: [][]string{
|
|
|
|
{"From", "To"},
|
|
|
|
{"Cc", "Bcc"},
|
|
|
|
{"Date"},
|
|
|
|
{"Subject"},
|
|
|
|
},
|
|
|
|
},
|
2019-07-23 01:29:07 +02:00
|
|
|
|
|
|
|
Compose: ComposeConfig{
|
|
|
|
HeaderLayout: [][]string{
|
|
|
|
{"To", "From"},
|
|
|
|
{"Subject"},
|
|
|
|
},
|
|
|
|
},
|
2019-11-10 17:00:21 +01:00
|
|
|
|
|
|
|
Templates: TemplateConfig{
|
|
|
|
TemplateDirs: []string{path.Join(sharedir, "templates")},
|
2022-01-19 21:28:06 +01:00
|
|
|
NewMessage: "new_message",
|
2019-11-10 17:00:21 +01:00
|
|
|
QuotedReply: "quoted_reply",
|
|
|
|
Forwards: "forward_as_body",
|
|
|
|
},
|
2018-01-10 01:18:19 +01:00
|
|
|
}
|
2020-07-27 10:03:55 +02:00
|
|
|
|
2019-05-21 22:31:04 +02:00
|
|
|
// These bindings are not configurable
|
|
|
|
config.Bindings.AccountWizard.ExKey = KeyStroke{
|
|
|
|
Key: tcell.KeyCtrlE,
|
|
|
|
}
|
|
|
|
quit, _ := ParseBinding("<C-q>", ":quit<Enter>")
|
|
|
|
config.Bindings.AccountWizard.Add(quit)
|
2019-07-13 19:42:22 +02:00
|
|
|
|
|
|
|
if err = config.LoadConfig(file); err != nil {
|
|
|
|
return nil, err
|
2018-01-10 01:18:19 +01:00
|
|
|
}
|
2020-07-27 10:03:55 +02:00
|
|
|
|
2019-06-25 09:23:51 +02:00
|
|
|
if ui, err := file.GetSection("general"); err == nil {
|
|
|
|
if err := ui.MapTo(&config.General); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2019-07-13 19:42:22 +02:00
|
|
|
|
2018-01-10 03:24:50 +01:00
|
|
|
accountsPath := path.Join(*root, "accounts.conf")
|
|
|
|
if accounts, err := loadAccountConfig(accountsPath); err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else {
|
|
|
|
config.Accounts = accounts
|
|
|
|
}
|
2021-12-10 22:27:29 +01:00
|
|
|
|
2019-05-22 18:35:44 +02:00
|
|
|
filename = path.Join(*root, "binds.conf")
|
|
|
|
binds, err := ini.Load(filename)
|
2019-03-21 22:36:42 +01:00
|
|
|
if err != nil {
|
2019-05-22 18:35:44 +02:00
|
|
|
if err := installTemplate(*root, sharedir, "binds.conf"); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if binds, err = ini.Load(filename); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-03-21 22:36:42 +01:00
|
|
|
}
|
2019-05-14 20:27:28 +02:00
|
|
|
|
2021-12-10 22:27:29 +01:00
|
|
|
baseGroups := map[string]**KeyBindings{
|
|
|
|
"default": &config.Bindings.Global,
|
|
|
|
"compose": &config.Bindings.Compose,
|
|
|
|
"messages": &config.Bindings.MessageList,
|
|
|
|
"terminal": &config.Bindings.Terminal,
|
|
|
|
"view": &config.Bindings.MessageView,
|
2019-05-14 20:27:28 +02:00
|
|
|
"compose::editor": &config.Bindings.ComposeEditor,
|
|
|
|
"compose::review": &config.Bindings.ComposeReview,
|
2019-03-21 22:36:42 +01:00
|
|
|
}
|
2021-12-10 22:27:29 +01:00
|
|
|
|
|
|
|
// Base Bindings
|
|
|
|
for _, sectionName := range binds.SectionStrings() {
|
|
|
|
// Handle :: delimeter
|
|
|
|
baseSectionName := strings.Replace(sectionName, "::", "////", -1)
|
|
|
|
sections := strings.Split(baseSectionName, ":")
|
|
|
|
baseOnly := len(sections) == 1
|
|
|
|
baseSectionName = strings.Replace(sections[0], "////", "::", -1)
|
|
|
|
|
|
|
|
group, ok := baseGroups[strings.ToLower(baseSectionName)]
|
2019-03-21 22:36:42 +01:00
|
|
|
if !ok {
|
2021-12-10 22:27:29 +01:00
|
|
|
return nil, errors.New("Unknown keybinding group " + sectionName)
|
2019-03-21 22:36:42 +01:00
|
|
|
}
|
2021-12-10 22:27:29 +01:00
|
|
|
|
|
|
|
if baseOnly {
|
2022-01-24 11:45:35 +01:00
|
|
|
err = config.LoadBinds(binds, baseSectionName, group, logger)
|
2019-03-21 22:36:42 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-12-10 22:27:29 +01:00
|
|
|
|
2019-03-21 22:36:42 +01:00
|
|
|
config.Bindings.Global.Globals = false
|
2021-12-10 22:27:29 +01:00
|
|
|
for _, contextBind := range config.ContextualBinds {
|
|
|
|
if contextBind.BindContext == "default" {
|
|
|
|
contextBind.Bindings.Globals = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-10 01:18:19 +01:00
|
|
|
return config, nil
|
|
|
|
}
|
2019-05-16 23:26:08 +02:00
|
|
|
|
2021-12-10 22:27:29 +01:00
|
|
|
func LoadBindingSection(sec *ini.Section) (*KeyBindings, error) {
|
|
|
|
bindings := NewKeyBindings()
|
|
|
|
for key, value := range sec.KeysHash() {
|
|
|
|
if key == "$ex" {
|
|
|
|
strokes, err := ParseKeyStrokes(value)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(strokes) != 1 {
|
|
|
|
return nil, errors.New("Invalid binding")
|
|
|
|
}
|
|
|
|
bindings.ExKey = strokes[0]
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if key == "$noinherit" {
|
|
|
|
if value == "false" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if value != "true" {
|
|
|
|
return nil, errors.New("Invalid binding")
|
|
|
|
}
|
|
|
|
bindings.Globals = false
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
binding, err := ParseBinding(key, value)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
bindings.Add(binding)
|
|
|
|
}
|
|
|
|
return bindings, nil
|
|
|
|
}
|
|
|
|
|
2022-01-24 11:45:35 +01:00
|
|
|
func (config *AercConfig) LoadBinds(binds *ini.File, baseName string, baseGroup **KeyBindings, logger *log.Logger) error {
|
2021-12-10 22:27:29 +01:00
|
|
|
|
|
|
|
if sec, err := binds.GetSection(baseName); err == nil {
|
|
|
|
binds, err := LoadBindingSection(sec)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
*baseGroup = MergeBindings(binds, *baseGroup)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, sectionName := range binds.SectionStrings() {
|
|
|
|
if !strings.Contains(sectionName, baseName+":") ||
|
|
|
|
strings.Contains(sectionName, baseName+"::") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
bindSection, err := binds.GetSection(sectionName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
binds, err := LoadBindingSection(bindSection)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
contextualBind :=
|
|
|
|
BindingConfigContext{
|
|
|
|
Bindings: binds,
|
|
|
|
BindContext: baseName,
|
|
|
|
}
|
|
|
|
|
|
|
|
var index int
|
|
|
|
if strings.Contains(sectionName, "=") {
|
|
|
|
index = strings.Index(sectionName, "=")
|
|
|
|
value := string(sectionName[index+1:])
|
|
|
|
contextualBind.Regex, err = regexp.Compile(value)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("Invalid Bind Context regex in %s", sectionName)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch sectionName[len(baseName)+1 : index] {
|
|
|
|
case "account":
|
2021-12-11 23:12:13 +01:00
|
|
|
acctName := sectionName[index+1:]
|
|
|
|
valid := false
|
|
|
|
for _, acctConf := range config.Accounts {
|
|
|
|
matches := contextualBind.Regex.FindString(acctConf.Name)
|
|
|
|
if matches != "" {
|
|
|
|
valid = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !valid {
|
2022-01-24 11:45:35 +01:00
|
|
|
logger.Printf("Tried to define binds for unexistent account: %v\n", acctName)
|
|
|
|
continue
|
2021-12-11 23:12:13 +01:00
|
|
|
}
|
2021-12-10 22:27:29 +01:00
|
|
|
contextualBind.ContextType = BIND_CONTEXT_ACCOUNT
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("Unknown Context Bind Section: %s", sectionName)
|
|
|
|
}
|
|
|
|
config.ContextualBinds = append(config.ContextualBinds, contextualBind)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-05-16 23:26:08 +02:00
|
|
|
// checkConfigPerms checks for too open permissions
|
|
|
|
// printing the fix on stdout and returning an error
|
|
|
|
func checkConfigPerms(filename string) error {
|
|
|
|
info, err := os.Stat(filename)
|
|
|
|
if err != nil {
|
2019-05-22 17:35:55 +02:00
|
|
|
return nil // disregard absent files
|
2019-05-16 23:26:08 +02:00
|
|
|
}
|
|
|
|
perms := info.Mode().Perm()
|
|
|
|
// group or others have read access
|
2021-01-13 03:46:56 +01:00
|
|
|
if perms&044 != 0 {
|
2019-07-28 15:02:09 +02:00
|
|
|
fmt.Fprintf(os.Stderr, "The file %v has too open permissions.\n", filename)
|
|
|
|
fmt.Fprintln(os.Stderr, "This is a security issue (it contains passwords).")
|
|
|
|
fmt.Fprintf(os.Stderr, "To fix it, run `chmod 600 %v`\n", filename)
|
2019-05-16 23:26:08 +02:00
|
|
|
return errors.New("account.conf permissions too lax")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2019-07-15 20:56:44 +02:00
|
|
|
|
|
|
|
func parseLayout(layout string) [][]string {
|
|
|
|
rows := strings.Split(layout, ",")
|
|
|
|
l := make([][]string, len(rows))
|
|
|
|
for i, r := range rows {
|
|
|
|
l[i] = strings.Split(r, "|")
|
|
|
|
}
|
|
|
|
return l
|
|
|
|
}
|
2020-01-23 13:56:48 +01:00
|
|
|
|
2020-07-27 10:03:55 +02:00
|
|
|
func (ui *UIConfig) loadStyleSet(styleSetDirs []string) error {
|
|
|
|
ui.style = NewStyleSet()
|
|
|
|
err := ui.style.LoadStyleSet(ui.StyleSetName, styleSetDirs)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Unable to load default styleset: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (config AercConfig) mergeContextualUi(baseUi UIConfig,
|
|
|
|
contextType ContextType, s string) UIConfig {
|
2020-01-23 13:56:48 +01:00
|
|
|
for _, contextualUi := range config.ContextualUis {
|
|
|
|
if contextualUi.ContextType != contextType {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if !contextualUi.Regex.Match([]byte(s)) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-07-27 10:03:55 +02:00
|
|
|
mergo.Merge(&baseUi, contextualUi.UiConfig, mergo.WithOverride)
|
|
|
|
if contextualUi.UiConfig.StyleSetName != "" {
|
|
|
|
baseUi.style = contextualUi.UiConfig.style
|
|
|
|
}
|
|
|
|
return baseUi
|
2020-01-23 13:56:48 +01:00
|
|
|
}
|
2020-07-27 10:03:55 +02:00
|
|
|
|
|
|
|
return baseUi
|
2020-01-23 13:56:48 +01:00
|
|
|
}
|
|
|
|
|
2020-07-27 10:03:55 +02:00
|
|
|
func (config AercConfig) GetUiConfig(params map[ContextType]string) UIConfig {
|
2020-01-23 13:56:48 +01:00
|
|
|
baseUi := config.Ui
|
|
|
|
|
|
|
|
for k, v := range params {
|
2020-07-27 10:03:55 +02:00
|
|
|
baseUi = config.mergeContextualUi(baseUi, k, v)
|
2020-01-23 13:56:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return baseUi
|
|
|
|
}
|
2020-07-27 10:03:55 +02:00
|
|
|
|
|
|
|
func (uiConfig UIConfig) GetStyle(so StyleObject) tcell.Style {
|
|
|
|
return uiConfig.style.Get(so)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (uiConfig UIConfig) GetStyleSelected(so StyleObject) tcell.Style {
|
|
|
|
return uiConfig.style.Selected(so)
|
|
|
|
}
|
2020-10-27 19:56:44 +01:00
|
|
|
|
|
|
|
func (uiConfig UIConfig) GetComposedStyle(base StyleObject,
|
|
|
|
styles []StyleObject) tcell.Style {
|
|
|
|
return uiConfig.style.Compose(base, styles)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (uiConfig UIConfig) GetComposedStyleSelected(base StyleObject, styles []StyleObject) tcell.Style {
|
|
|
|
return uiConfig.style.ComposeSelected(base, styles)
|
|
|
|
}
|