theme

package
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 24, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package theme holds cdu's colour tokens: the Theme struct every style in the interface is built from, and the presets bundled into the binary.

A token names a role, not a hue. The charm theme's accent happens to be pink, midnight's is cyan and phosphor's is green — a renderer reaching for `pink` would be telling the truth in exactly one theme. So the tokens are Accent, Danger, Size and so on, and the render path never sees a colour literal.

Index

Constants

View Source
const Default = "charm"

Default is the theme cdu opens with when nothing says otherwise.

Variables

This section is empty.

Functions

func Dump

func Dump(name string) ([]byte, error)

Dump returns a theme's file, comments and all.

This is what makes shipping themes as files mean anything to someone holding only the binary: without it, "copy one and edit it" would mean going to GitHub. The comments are most of the value — they say what each token paints and why the palette is what it is.

func List

func List(w io.Writer, current, dir string) error

List writes every theme with a preview of each. The preview is the point: a name is not a colour, and the whole reason to run this is to see them.

dir is the user's theme directory, named so someone can go and put a theme in it; pass "" if it could not be worked out.

It degrades with the terminal exactly as the interface does — on a dumb terminal or under --no-color, Lipgloss emits nothing and the listing is a plain table, which is still useful, because it still says what to type.

func LoadUserThemes

func LoadUserThemes(dir string) []error

LoadUserThemes reads themes from dir, where a .yaml (or .yml) file is a theme named after itself. A theme of yours with a bundled name replaces it, so you can keep `charm` and mean your charm.

Every error is returned and that one file is skipped. This is the opposite of the bundled loader's panic, and deliberately: a bundled theme is part of the binary and a broken one is a corrupt build, while these are somebody's half-finished theme — not a reason to refuse to show them their disk.

A missing directory is not an error. Most people will never make one.

func Names

func Names() []string

Names lists every theme, yours included, sorted — for `cdu themes` and for the error message on an unknown name.

func TokenNames

func TokenNames() []string

TokenNames lists every yaml key a theme block accepts, sorted. `cdu themes` and the config writer use it, so the documentation cannot drift from the code.

Types

type Color

type Color string

Color is a #rrggbb hex string.

Hex only, deliberately. Lipgloss would also accept an ANSI index like "5", but the usage bar blends its endpoints in Luv, and an ANSI index carries no value to blend — it would come out black, on one theme, in one place. Constraining the token is cheaper than debugging that.

func (Color) Valid

func (c Color) Valid() bool

Valid reports whether c is a well-formed hex colour. The empty string is not valid but is not an error either: it means "not set", which is what lets a user override two tokens of a preset without restating the other nine.

type Config

type Config struct {
	Preset string `yaml:"preset,omitempty"`
	Theme  `yaml:",inline"`
}

Config is the `theme:` block of cdu's config file, and the shape --theme writes into. The tokens are inlined, so a user names a preset and then overrides the two colours they disagree with:

theme:
  preset: midnight
  accent: "#ff5fd1"

type Theme

type Theme struct {
	// Name is the preset this came from, for `cdu themes` and error messages. It
	// is not a colour and is never read from the config.
	Name string `yaml:"-"`

	// Light marks a theme drawn for a light terminal.
	//
	// cdu never paints the field: there is no background token, so the terminal's
	// own background shows through, which is what keeps transparency and blur
	// working for the people most likely to care about themes at all. The price is
	// that a light theme on a dark terminal is unreadable, so `cdu themes` says
	// which is which.
	Light bool `yaml:"-"`

	// User marks a theme that came from the user's theme directory rather than
	// from the binary. `cdu themes` says so, which is how you find out whether the
	// file you just wrote actually loaded.
	User bool `yaml:"-"`

	// Plain means the theme uses no colour, rendering through the same
	// bold/reverse/underline path as --no-color.
	//
	// `mono` is defined this way rather than as a set of greys because no fixed
	// grey is legible on both a light and a dark terminal, while the no-colour
	// path is — it conveys state through attributes instead, and it is the path
	// nocolor_test.go already audits. A Plain theme therefore has no tokens, and
	// Missing does not apply to it.
	Plain bool `yaml:"-"`

	// Panel backs the modal and the cursor row.
	Panel Color `yaml:"panel,omitempty"`
	// Text is an ordinary file name and the modal's body.
	Text Color `yaml:"text,omitempty"`
	// Selected is the cursor row's name, drawn on Panel. It is a token of its own
	// rather than "white" because on a light theme it is dark.
	Selected Color `yaml:"selected,omitempty"`
	// Ink is the foreground on a filled chip: the focused button (on Dim) and the
	// destructive one (on Danger).
	//
	// It looks like Selected, and in charm it is the same white, which is exactly
	// why it was worth separating. Selected sits on Panel — a *surface*, dark in a
	// dark theme. Ink sits on Danger and Dim — *colours*, which can be light even
	// when the theme is dark. ember's danger is #ff4d4d and its ink is near-black
	// for that reason, while its selected row stays white. Fusing the two put
	// white on a light red at about 3:1, and on a pastel palette it went to 1.3:1
	// — an unreadable button, in a modal, on one theme. contrast_test.go is what
	// caught it and what keeps it caught.
	Ink Color `yaml:"ink,omitempty"`
	// Dim is percentages, key hints, and disabled buttons.
	Dim Color `yaml:"dim,omitempty"`
	// Accent is the cursor marker, the wordmark, the modal border and matched
	// filter runes.
	Accent Color `yaml:"accent,omitempty"`
	// Size is the size column.
	Size Color `yaml:"size,omitempty"`
	// Danger is anything destructive.
	Danger Color `yaml:"danger,omitempty"`

	// BarFrom and BarTo are the usage bar's gradient endpoints. BarFrom doubles as
	// the solid fill below truecolor, where the gradient is not drawn. They are
	// separate from Accent so a theme can run the bar through colours it would not
	// use for a marker.
	BarFrom Color `yaml:"bar-from,omitempty"`
	BarTo   Color `yaml:"bar-to,omitempty"`
	// BarTrack is the unlit part of the bar.
	BarTrack Color `yaml:"bar-track,omitempty"`
	// contains filtered or unexported fields
}

Theme is the complete set of colours the interface may use. A bundled preset sets every token; a user's config may set any subset, and the rest are inherited from the preset it names.

func Charm

func Charm() Theme

Charm is the default theme. It is the one theme the rest of the program may ask for without handling "not found" — loadBundled guarantees it exists.

func Preset

func Preset(name string) (Theme, bool)

Preset returns a theme by name. The bool is false for an unknown name; the caller warns and falls back rather than exiting, because a typo in a config should not stop a disk usage tool from opening.

func Resolve

func Resolve(cfg *Config, flag string) (Theme, error)

Resolve produces the theme to render with. It always produces one.

A bad preset name or a malformed hex is reported and the offending part falls back — never an exit. Someone who typo'd a colour in a config file opened cdu because a disk is full; refusing to start would be answering a question they did not ask. The caller prints the error to stderr and renders anyway.

--no-color and NO_COLOR are not handled here: they already drive the plain render path, which is precisely what mono is, so forcing the theme as well would be a second way of saying the same thing.

func (*Theme) Missing

func (t *Theme) Missing() []string

Missing lists the tokens with no value, sorted. A preset with a missing token would render that element black-on-black, so the preset test uses this; the config loader uses it after merging to prove nothing was left unresolved.

func (*Theme) Overlay

func (t *Theme) Overlay(other *Theme)

Overlay copies every token set on other over the receiver, leaving the rest alone. This is how a user's partial theme block lands on top of a preset.

func (*Theme) Validate

func (t *Theme) Validate() error

Validate reports every token that is set but malformed. An unset token is not an error — it inherits.

The caller warns and falls back rather than exiting: someone with a typo in their config wants their disk usage tool to open, not to argue.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL