1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
|
// Copyright 2019-2022 Graham Clark. All rights reserved. Use of this source
// code is governed by the MIT license that can be found in the LICENSE
// file.
// package theme provides utilities for customizing the styling of termshark.
package theme
import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"github.com/gcla/gowid"
"github.com/rakyll/statik/fs"
"github.com/shibukawa/configdir"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
_ "github.com/gcla/termshark/v2/assets/statik"
)
//======================================================================
type Layer int
const (
Foreground Layer = 0
Background Layer = iota
)
var theme *viper.Viper
// MakeColorSafe extends gowid's MakeColorSafe function, preferring to interpret
// its string argument as a toml file config key lookup first; if this fails, then
// fall back to gowid.MakeColorSafe, which will then read colors as urwid color names,
// #-prefixed hex digits, grayscales, etc.
func MakeColorSafe(s string, l Layer) (gowid.Color, error) {
loops := 10
cur := s
if theme != nil {
for {
next := theme.GetString(cur)
if next != "" {
cur = next
} else {
next := theme.GetStringSlice(cur)
if next == nil || len(next) != 2 {
break
} else {
cur = next[l]
}
}
loops -= 1
if loops == 0 {
break
}
}
}
col, err := gowid.MakeColorSafe(cur)
if err == nil {
return gowid.Color{IColor: col, Id: s}, nil
}
return gowid.MakeColorSafe(s)
}
type Mode gowid.ColorMode
func (m Mode) String() string {
switch gowid.ColorMode(m) {
case gowid.Mode256Colors:
return "256"
case gowid.Mode88Colors:
return "88"
case gowid.Mode16Colors:
return "16"
case gowid.Mode8Colors:
return "8"
case gowid.ModeMonochrome:
return "mono"
case gowid.Mode24BitColors:
return "truecolor"
default:
return "unknown"
}
}
// Load will set the package-level theme object to a viper object representing the
// toml file either (a) read from disk, or failing that (b) built-in to termshark.
// Disk themes are preferred and take precedence.
func Load(name string, app gowid.IApp) error {
var err error
theme = viper.New()
defer func() {
if err != nil {
theme = nil
}
}()
theme.SetConfigType("toml")
stdConf := configdir.New("", "termshark")
dirs := stdConf.QueryFolders(configdir.Global)
mode := Mode(app.GetColorMode()).String()
log.Infof("Loading theme %s in terminal mode %v", name, app.GetColorMode())
// If there's not a truecolor theme, we assume the user wants the best alternative to be loaded,
// and if a terminal has truecolor support, it'll surely have 256-color support.
modes := []string{mode}
if mode == "truecolor" {
modes = append(modes, Mode(gowid.Mode256Colors).String())
}
for _, m := range modes {
// Prefer to load from disk
themeFileName := filepath.Join(dirs[0].Path, "themes", fmt.Sprintf("%s-%s.toml", name, m))
log.Infof("Trying to load user theme %s", themeFileName)
var file io.ReadCloser
file, err = os.Open(themeFileName)
if err == nil {
defer file.Close()
log.Infof("Loaded user theme %s", themeFileName)
return theme.ReadConfig(file)
}
}
// Fall back to built-in themes
statikFS, err := fs.New()
if err != nil {
return fmt.Errorf("in mode %v: %v", app.GetColorMode(), err)
}
for _, m := range modes {
themeFileName := path.Join("/themes", fmt.Sprintf("%s-%s.toml", name, m))
log.Infof("Trying to load built-in theme %s", themeFileName)
var file io.ReadCloser
file, err = statikFS.Open(themeFileName)
if err == nil {
defer file.Close()
log.Infof("Loaded built-in theme %s", themeFileName)
return theme.ReadConfig(file)
}
}
return err
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 110
// End:
|