Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,32 @@ module's `_color` (and related `_active_color` / `_context_color` /
raw `#{E:@thm_<slot>}` — set `@themux_<name>_color` (or the per-slot variant) as
usual to override a single module without touching its role.

### Colour values

Every colour prop (`_color`, `_active_color`, the per-slot
`_{leading,text}_color` overrides, the raw channel pins
`_{icon,text}_{fg,bg}`, and the window/pane accents) accepts three spellings:

- **A role word** — `accent`, `info`, `notice`, `muted`, `alert`, `error`:
resolves through `@thm_role_<word>`.
- **A palette word** — any `@thm_*` name the current theme defines (`peach`,
`mauve`, `surface_0`, ...): resolves through `@thm_<word>`. The theme wins
over tmux's native colour names, so `red` is the palette red, not the
terminal's.
- **Raw tmux** — a hex (`#fab387`), a `colour255` name, `default`, or a
format (`#{E:@thm_peach}`, a draw-time `#{?...}` conditional): passes
through untouched.

Words resolve at render time, so they track a theme switch just like the
role/palette references. An unknown word passes through raw — tmux ignores an
invalid colour silently, so a typo shows up as a missing colour, not an error.

```sh
set -g @themux_git_color "mauve" # palette word
set -g @themux_session_color "accent" # role word
set -g @themux_time_color "#89dceb" # raw hex, same as always
```

> [!WARNING]
> **Breaking change:** module accent defaults changed in this release — see the
> full list below.
Expand Down Expand Up @@ -482,6 +508,21 @@ it there for you. Some modules ship their own per-module default: `git` sets
whose variant displays the accent, and `cpu`/`ram` do the same for their live
threshold colour (see below).

Visibility and escalation conditions share one grammar. A conditional
module's `@themux_<name>_when` (git/gitmux's in-repo gate, zoom's
`window_zoomed_flag`) and a stateful module's `@themux_<name>_active_when`
accept two preset words — `always` and `never` (alias `off`) — or any raw
tmux condition: a bare format variable (`client_prefix`), a `#{...}`
conditional, or a `#(...)` probe. tmux reads an unknown bare word as a format
variable that expands empty (false), so anything that is neither a preset nor
a real variable hides the module / never escalates.

```sh
set -g @themux_git_when "always" # render even outside a repo
set -g @themux_gitmux_when "never" # hide entirely
set -g @themux_git_active_when "never" # keep the resting accent on dirty trees
```

The `cpu`/`ram` threshold modules carry tmux-cpu's live level colour
`#{<name>_bg_color}` as their accent — green at rest, warming yellow → red as the
value climbs. The default variants place it on both slots: the **icon** is `solid`
Expand Down
5 changes: 5 additions & 0 deletions docs/reference/status-line.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,11 @@ set -g @themux_git_color "#{E:@thm_role_accent}" # resting (clean)
set -g @themux_git_active_color "#{E:@thm_role_notice}" # dirty
```

**Visibility:** the module renders only inside a git work tree by default.
`@themux_git_when` accepts `always` (render everywhere), `never` (hide), or a
raw tmux condition — see
[colour values & conditions](./configuration.md#colour-values).

**Symbols:** each symbol has a glyph default and a plain-ASCII default,
picked through the module's [leading content](./configuration.md#status-modules)
cascade (`@themux_git_leading_show` > `@themux_module_leading_show` >
Expand Down
4 changes: 3 additions & 1 deletion modules/git.conf
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,10 @@ set -ogq "@themux_${MODULE_NAME}_active_when" "#{E:@_tmx_${MODULE_NAME}_dirty}"
set -ogq "@themux_${MODULE_NAME}_state_target" "both"

# Only render inside a git work tree (a cheap rev-parse, evaluated per refresh).
# -ogq so the gate is a user option: the when grammar (always/never presets or
# a raw tmux condition) can replace the in-repo default.
set -gq "@_tmx_${MODULE_NAME}_in_repo" '#(git -C "#{pane_current_path}" rev-parse --is-inside-work-tree 2>/dev/null)'
set -gq "@themux_${MODULE_NAME}_when" "#{E:@_tmx_${MODULE_NAME}_in_repo}"
set -ogq "@themux_${MODULE_NAME}_when" "#{E:@_tmx_${MODULE_NAME}_in_repo}"

# module_block.conf matches modules by substring over the joined status lines,
# so the token "git" also matches inside "gitmux": a status line configured
Expand Down
4 changes: 3 additions & 1 deletion modules/gitmux.conf
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ set -ogq "@themux_${MODULE_NAME}_text_bg" "#{E:@thm_crust}"
# whole segment when outside a repo and, in a powerline run, lets neighbours
# connect straight through instead of around an empty gitmux.
set -gq "@_tmx_${MODULE_NAME}_in_repo" '#(git -C "#{pane_current_path}" rev-parse --is-inside-work-tree 2>/dev/null)'
set -gq "@themux_${MODULE_NAME}_when" "#{E:@_tmx_${MODULE_NAME}_in_repo}"
# -ogq so the gate is a user option (when grammar: always/never or a raw
# tmux condition), same as git.conf.
set -ogq "@themux_${MODULE_NAME}_when" "#{E:@_tmx_${MODULE_NAME}_in_repo}"

source -F "#{d:current_file}/../utils/module_block.conf"
1 change: 1 addition & 0 deletions run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P)
"${script_dir}"/tests/harness.sh --test "${script_dir}"/tests/ram_module.sh --expected "${script_dir}"/tests/ram_module_expected.txt "$@"
"${script_dir}"/tests/harness.sh --test "${script_dir}"/tests/module_variant.sh --expected "${script_dir}"/tests/module_variant_expected.txt "$@"
"${script_dir}"/tests/harness.sh --test "${script_dir}"/tests/notch_direction.sh --expected "${script_dir}"/tests/notch_direction_expected.txt "$@"
"${script_dir}"/tests/harness.sh --test "${script_dir}"/tests/prop_grammar.sh --expected "${script_dir}"/tests/prop_grammar_expected.txt "$@"
98 changes: 98 additions & 0 deletions tests/prop_grammar.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env bash

script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P)
# shellcheck disable=SC1091
source "${script_dir}/helpers.sh"

src() {
tmux source "${script_dir}/../themux_options.conf"
tmux source "${script_dir}/../themux.conf"
}

# The friendly prop grammar (utils/render_style.sh): colour props accept a
# theme role or palette word, when/active_when accept always/never presets —
# and every prop still accepts raw tmux semantics untouched. Assertions read
# the baked core/seg (deterministic under the default catppuccin_mocha
# palette: accent #a6e3a1, notice/peach #fab387, mauve #cba6f7, red #f38ba8).
tmux set -g @themux_status_line_1 "git"

core_bg1() { tmux show -gqv @_tmx_module_git_core | grep -o 'bg=[^]]*' | head -1; }

# A palette word resolves through @thm_<word>.
tmux set -g @themux_git_color "mauve"
src
printf 'colour_palette_word [%s]\n' "$(core_bg1)"

# A role word resolves through @thm_role_<word> (checked before the palette).
tmux set -g @themux_git_color "accent"
src
printf 'colour_role_word [%s]\n' "$(core_bg1)"

# The theme wins over tmux's native colour names: red is @thm_red, not the
# terminal's red.
tmux set -g @themux_git_color "red"
src
printf 'colour_theme_wins [%s]\n' "$(core_bg1)"

# Raw tmux values pass through untouched: a colour255 name and a hex.
tmux set -g @themux_git_color "colour123"
src
printf 'colour_raw_name [%s]\n' "$(core_bg1)"
tmux set -g @themux_git_color "#94e2d5"
src
printf 'colour_raw_hex [%s]\n' "$(core_bg1)"
tmux set -gu @themux_git_color

# The active accent takes the same words.
tmux set -g @themux_git_active_color "mauve"
src
printf 'colour_active_word [%s]\n' "$(core_bg1)"
tmux set -gu @themux_git_active_color

# when presets gate the standalone pill (@themux_module_git): `always` drops
# the gate entirely, `never` bakes a constant-false one, and any other value
# passes through raw (zoom's bare window_zoomed_flag idiom keeps working).
# git.conf sets its in-repo default with -ogq, so a user value set before the
# source survives.
tmux set -g @themux_git_when "always"
src
printf 'when_always_ungated '
{ [ -z "$(tmux show -gqv @themux_module_git | { grep -oF '#{?#{E:@_tmx_git_in_repo},' || true; })" ]; } && printf "Y\n" || printf "n\n"

tmux set -g @themux_git_when "never"
src
printf 'when_never_const_false '
tmux show -gqv @themux_module_git | { grep -coF '#{?0,' || true; }

tmux set -g @themux_git_when "window_zoomed_flag"
src
printf 'when_raw_variable '
tmux show -gqv @themux_module_git | { grep -coF '#{?window_zoomed_flag,' || true; }

tmux set -gu @themux_git_when
src
printf 'when_default_in_repo '
tmux show -gqv @themux_module_git | { grep -coF '#{?#{E:@_tmx_git_in_repo},' || true; }

# active_when presets: `never` pins the resting accent (the collapsed core
# carries no dirty switch), `always` pins the active one behind a
# constant-true format (a bare 1 would be read as an unknown format variable
# and stay false).
tmux set -g @themux_git_active_when "never"
src
printf 'active_never_resting [%s]\n' "$(core_bg1)"

tmux set -g @themux_git_active_when "always"
src
printf 'active_always_pinned [%s]\n' "$(core_bg1)"
tmux set -gu @themux_git_active_when
src

# The window renderer routes its accents through the same helper (one line,
# so grep -c is a presence flag: 1 resolved, 0 not).
tmux set -g @themux_window_leading_color "mauve"
src
printf 'window_colour_word '
tmux show -gqv window-status-format | { grep -coF '#cba6f7' || true; }
tmux set -gu @themux_window_leading_color
src
13 changes: 13 additions & 0 deletions tests/prop_grammar_expected.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
colour_palette_word [bg=#{?#{E:@_tmx_git_dirty},#fab387,#cba6f7}]
colour_role_word [bg=#{?#{E:@_tmx_git_dirty},#fab387,#a6e3a1}]
colour_theme_wins [bg=#{?#{E:@_tmx_git_dirty},#fab387,#f38ba8}]
colour_raw_name [bg=#{?#{E:@_tmx_git_dirty},#fab387,colour123}]
colour_raw_hex [bg=#{?#{E:@_tmx_git_dirty},#fab387,#94e2d5}]
colour_active_word [bg=#{?#{E:@_tmx_git_dirty},#cba6f7,#a6e3a1}]
when_always_ungated Y
when_never_const_false 1
when_raw_variable 1
when_default_in_repo 1
active_never_resting [bg=#a6e3a1]
active_always_pinned [bg=#{?#{==:1,1},#fab387,#a6e3a1}]
window_colour_word 1
8 changes: 6 additions & 2 deletions utils/layout.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ interp() { # $1 module, $2 string -> the string with the module's plugin literal
"$fn" "$2"
}

# Shared prop grammar (themux_when_cond for the visibility presets).
# shellcheck source=render_style.sh
. "$(dirname "$0")/render_style.sh"

windows_block=$(tmux show -gqv @_tmx_fmt_windows)

# Control-byte marker for a zone-aware notch (auto), matching utils/module_render.sh
Expand Down Expand Up @@ -197,7 +201,7 @@ powerline_run() {
local shown="0" shown_fixed=1 # shown: anything visible yet? (fixed while constant)
for i in "${!mods[@]}"; do
m="${mods[i]}"; c="${conns[i]}"
cond=$(tmux show -gqv "@themux_${m}_when")
cond=$(themux_when_cond "$(tmux show -gqv "@themux_${m}_when")")
lcol=$(mod_field "$m" lcol); lbg=$(mod_field "$m" lbg)
rcol=$(mod_field "$m" rcol); rbg=$(mod_field "$m" rbg)
core=$(mod_core "$m" "$align")
Expand Down Expand Up @@ -298,7 +302,7 @@ expand_zone() {
G)
read -ra mm <<<"${it_mods[$1]}"
for m in "${mm[@]}"; do
cond=$(tmux show -gqv "@themux_${m}_when")
cond=$(themux_when_cond "$(tmux show -gqv "@themux_${m}_when")")
[ -z "$cond" ] && { printf '1'; return; }
if [ -z "$vis" ]; then vis="$cond"; else vis="#{||:${vis},${cond}}"; fi
done
Expand Down
14 changes: 14 additions & 0 deletions utils/module_render.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,20 @@ text_acc=$(bake "$text_acc" "$text_acc_E")
lead_aacc=$(bake "$lead_aacc" "$lead_aacc_E")
text_aacc=$(bake "$text_aacc" "$text_aacc_E")

# Friendly prop grammar (render_style.sh): accents and raw channel pins accept
# a theme role/palette word (notice, peach), active_when accepts always/never.
# Raw tmux values — hex, colour255, formats, bare variables — pass through.
lead_acc=$(themux_colour "$lead_acc")
text_acc=$(themux_colour "$text_acc")
lead_aacc=$(themux_colour "$lead_aacc")
text_aacc=$(themux_colour "$text_aacc")
icon_bg_ov=$(themux_colour "$icon_bg_ov") icon_fg_ov=$(themux_colour "$icon_fg_ov")
text_bg_ov=$(themux_colour "$text_bg_ov") text_fg_ov=$(themux_colour "$text_fg_ov")
active_when=$(themux_active_cond "$active_when")
# The standalone-pill gate below and layout.sh's powerline splice both read
# the same normalized vocabulary (always -> unconditional, never -> hidden).
when=$(themux_when_cond "$when")

# Select leading content before routing state. `auto` state needs to know if the
# leading slot exists: visible leading keeps state on leading; hidden leading
# moves state to text. Invalid leading values intentionally fall back to icons.
Expand Down
13 changes: 7 additions & 6 deletions utils/pane_render.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,13 @@ case "$shape" in
esac

# Per-part accents: a resting accent (inactive pane) and an active accent (active
# pane). surface/crust/fg are static.
lead_base=$(expand "#{E:@themux_pane_leading_color}")
lead_act=$(expand "#{E:@themux_pane_leading_active_color}")
txt_base=$(expand "#{E:@themux_pane_text_color}")
txt_act=$(expand "#{E:@themux_pane_text_active_color}")
surface=$(expand "#{E:@themux_pane_background_color}")
# pane). surface/crust/fg are static. themux_colour resolves a friendly theme
# role/palette word (notice, peach); raw tmux values pass through.
lead_base=$(themux_colour "$(expand "#{E:@themux_pane_leading_color}")")
lead_act=$(themux_colour "$(expand "#{E:@themux_pane_leading_active_color}")")
txt_base=$(themux_colour "$(expand "#{E:@themux_pane_text_color}")")
txt_act=$(themux_colour "$(expand "#{E:@themux_pane_text_active_color}")")
surface=$(themux_colour "$(expand "#{E:@themux_pane_background_color}")")
crust=$(expand "#{@thm_crust}")
fg=$(expand "#{@thm_fg}")

Expand Down
47 changes: 47 additions & 0 deletions utils/render_style.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,53 @@ themux_notch_mode() { # $1 raw -> gt | lt | auto | off
case "$1" in '>') echo gt ;; '<') echo lt ;; auto|on) echo auto ;; *) echo off ;; esac
}

# Map a friendly colour word to the current theme. A bare lowercase word
# resolves through @thm_role_<word> (accent, notice, ...) then @thm_<word>
# (peach, surface_0, ...) — the theme wins over tmux's native colour names,
# so `red` is the palette red, not the terminal's. Anything else — a hex, a
# colour255 name, `default`, a #{...} format, an unknown word — passes
# through untouched, keeping raw tmux semantics assignable from outside.
# One display round-trip per call, at render time only (bake tracks theme
# switches because a theme change re-renders).
themux_colour() { # $1 raw colour value -> theme colour or the value itself
case "$1" in
'' | *[!a-z0-9_]*) printf '%s' "$1"; return ;;
esac
local v
v=$(tmux display -p "#{?#{!=:#{@thm_role_$1},},#{E:@thm_role_$1},#{?#{!=:#{@thm_$1},},#{E:@thm_$1},}}")
printf '%s' "${v:-$1}"
}

# Normalize a visibility condition (@themux_<name>_when). Two preset words:
# `always` joins the module unconditionally (empty condition) and `never`
# (alias `off`) hides it (a constant-false condition). Anything else passes
# through raw — a bare format variable (window_zoomed_flag), a #{...}
# conditional, or a #(...) probe. tmux looks a bare unknown word up as a
# format variable (empty -> false), so an unmapped word silently hides the
# module; the presets make the two useful constants sayable without knowing
# that.
themux_when_cond() { # $1 raw when value -> normalized condition
case "$1" in
always) printf '' ;;
never | off) printf '0' ;;
*) printf '%s' "$1" ;;
esac
}

# Normalize an escalation condition (@themux_<name>_active_when): `always`
# pins the active appearance (a constant-true format — a bare 1 would be
# looked up as a format variable and read false), `never` (alias `off`) pins
# the resting one (the empty condition collapses the per-channel switch in
# module_render.sh's chan). Anything else passes through raw, e.g. session's
# bare `client_prefix`.
themux_active_cond() { # $1 raw active_when value -> normalized condition
case "$1" in
always) printf '#{==:1,1}' ;;
never | off) printf '' ;;
*) printf '%s' "$1" ;;
esac
}

# Parse a @themux_*_padding value into four side pads:
# "N" -> "N N|N N"
# "<leading-left> [leading-right]|<text-left> [text-right]"
Expand Down
10 changes: 5 additions & 5 deletions utils/window_render.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ read -r pleft pright tleft tright <<<"$(pad_parse "$(themux_prop window padding)
crust=$(expand "#{@thm_crust}")
fg=$(expand "#{@thm_fg}")
flags=$(expand "#{@_tmx_w_flags}")
surface=$(expand "#{E:@themux_window_background_color}")
surface=$(themux_colour "$(expand "#{E:@themux_window_background_color}")")

# Shape glyphs (octal UTF-8). squared has none — the block padding is its edge.
case "$shape" in
Expand Down Expand Up @@ -105,12 +105,12 @@ render_side() {
local fbg sbg fcap scap gt_txt lt_txt
if [ "$p" = w ]; then
lvar="$leading" tvar="$name_style"
lead_acc=$(expand "#{E:@themux_window_leading_color}")
txt_acc=$(expand "#{E:@themux_window_text_color}")
lead_acc=$(themux_colour "$(expand "#{E:@themux_window_leading_color}")")
txt_acc=$(themux_colour "$(expand "#{E:@themux_window_text_color}")")
else
lvar="$leading_active" tvar="$name_active"
lead_acc=$(expand "#{E:@themux_window_leading_active_color}")
txt_acc=$(expand "#{E:@themux_window_text_active_color}")
lead_acc=$(themux_colour "$(expand "#{E:@themux_window_leading_active_color}")")
txt_acc=$(themux_colour "$(expand "#{E:@themux_window_text_active_color}")")
fi
resolve_style "$lvar" "$lead_acc" "$surface" "$crust" "$fg"
ibg="$RS_BG" ifg="$RS_FG"
Expand Down