1package theme
2
3import (
4 "fmt"
5 "os"
6 "path/filepath"
7 "strings"
8)
9
10// terminal describes where one terminal keeps its config.
11type terminal struct {
12 name string
13 format string
14 rel string // path under the config directory
15}
16
17// terminals are the terminals auto knows how to read, by the name $TERMINAL
18// gives them.
19var terminals = map[string]terminal{
20 "kitty": {"kitty", "kitty", "kitty/kitty.conf"},
21 "ghostty": {"ghostty", "ghostty", "ghostty/config"},
22 "foot": {"foot", "foot", "foot/foot.ini"},
23 "alacritty": {"alacritty", "alacritty", "alacritty/alacritty.toml"},
24}
25
26// Env is how Auto reads its surroundings, so tests can fake them.
27type Env struct {
28 Getenv func(string) string
29 ConfigDir string // $XDG_CONFIG_HOME or ~/.config
30}
31
32// SystemEnv is the real environment.
33func SystemEnv() Env {
34 dir := os.Getenv("XDG_CONFIG_HOME")
35 if dir == "" {
36 home, _ := os.UserHomeDir()
37 dir = filepath.Join(home, ".config")
38 }
39 return Env{Getenv: os.Getenv, ConfigDir: dir}
40}
41
42// Auto reads the theme of the terminal named by $TERMINAL: a bare name such as
43// "kitty" or a path such as "/usr/bin/foot". It is deliberately the only rule,
44// so there is one place to look when the colors are not the ones you expect.
45func Auto(e Env) (Theme, error) {
46 value := strings.TrimSpace(e.Getenv("TERMINAL"))
47 if value == "" {
48 return Theme{}, fmt.Errorf(`theme = "auto" needs $TERMINAL (for example TERMINAL=kitty). ` +
49 `Put it in ~/.config/environment.d/10-terminal.conf and start a new session`)
50 }
51 // Only the program name counts: "/usr/bin/foot --server" is foot.
52 name := filepath.Base(strings.Fields(value)[0])
53 term, ok := terminals[name]
54 if !ok {
55 return Theme{}, fmt.Errorf("$TERMINAL is %q, but neferbar reads the themes of kitty, ghostty, foot and alacritty only; "+
56 `set bar.theme to the path of a theme file instead`, name)
57 }
58 path := filepath.Join(e.ConfigDir, term.rel)
59 if !fileExists(path) {
60 return Theme{}, fmt.Errorf("$TERMINAL is %s, but %s does not exist", name, path)
61 }
62 th, err := formats[term.format].load(path)
63 if err != nil {
64 return Theme{}, fmt.Errorf("cannot read the %s config %s: %w", name, path, err)
65 }
66 if th.Defines() == 0 {
67 return Theme{}, fmt.Errorf("the %s config %s sets no colors", name, path)
68 }
69 th.Source = fmt.Sprintf("%s (%s), from $TERMINAL", name, path)
70 return th, nil
71}