fix conf management

This commit is contained in:
2026-10-13 11:49:40 +02:00
parent 6bfc2804ab
commit daa2ee755c
4 changed files with 192 additions and 86 deletions
+15 -13
View File
@@ -94,8 +94,8 @@ A bar-style prompt showing current time, execution time of the last command
| `busy` | fun | Monitor /dev/urandom for a hex pattern — look busy | | `busy` | fun | Monitor /dev/urandom for a hex pattern — look busy |
| `check_updates` | updates | Check whether a newer profile version is available online; when called with `-q` at startup a 3-second network timeout is applied so a slow or absent network never delays the prompt | | `check_updates` | updates | Check whether a newer profile version is available online; when called with `-q` at startup a 3-second network timeout is applied so a slow or absent network never delays the prompt |
| `clean` | filefct | Erase backup files in given directories, optionally recursive | | `clean` | filefct | Erase backup files in given directories, optionally recursive |
| `conf_dump` | conf | Display the profile configuration file; `-s/--section NAME` restricts output to one section; an optional pattern argument filters keys by substring match | | `conf_dump` | conf | Display the profile configuration file; `-s/--section NAME` restricts output to one section; `-a/--all` also lists commented-out keys (documented defaults, skipped when the key is set); an optional pattern argument filters keys by substring match; honours `NO_COLOR` |
| `conf_save` | conf | Save or update a key=value pair in a configuration section: `conf_save <section> <key> <value>`; creates the section header automatically if absent | | `conf_save` | conf | Save or update a key=value pair in a configuration section: `conf_save <section> <key> <value>`; replaces an existing key in place or appends it to the section (creating the section if absent); works through a symlinked file and keeps its permissions; the value must be single-line and the running shell is not updated (applies at next load) |
| `disp` | disp | Display formatted info / warning / error / debug messages; long messages are word-wrapped and continuation lines are indented to align with the message text | | `disp` | disp | Display formatted info / warning / error / debug messages; long messages are word-wrapped and continuation lines are indented to align with the message text |
| `dwl` | net | Download a URL using curl, wget, or fetch transparently; supports `-t <seconds>` / `--timeout <seconds>` to cap the transfer time | | `dwl` | net | Download a URL using curl, wget, or fetch transparently; supports `-t <seconds>` / `--timeout <seconds>` to cap the transfer time |
| `expandlist` | filefct | Expand glob expressions into a quoted, separated list | | `expandlist` | filefct | Expand glob expressions into a quoted, separated list |
@@ -188,21 +188,23 @@ automatically at startup:
| *(absent or `smart`)* | Auto-detect the best available `terminfo` entry (default) | | *(absent or `smart`)* | Auto-detect the best available `terminfo` entry (default) |
| any other string | Exported as-is — standard Unix behaviour | | any other string | Exported as-is — standard Unix behaviour |
When auto-detection runs, `COLORTERM=truecolor|24bit` is first evaluated as a When auto-detection runs, `COLORTERM=truecolor|24bit` is evaluated as a global
global truecolor flag. Each emulator hint is then checked in order; within each truecolor flag: it does not pick an entry by itself, it only enables the
branch a `*-direct` terminfo entry (24-bit colour) is tried before the `*-direct` (24-bit colour) terminfo entries below, each of which falls back to its
`*-256color` fallback. Only entries confirmed to exist via `terminfo` are used. `*-256color` counterpart. Hints are checked in the following order and the first
entry confirmed to exist via `terminfo` is used. Terminal multiplexers come first
because they inherit the outer emulator's variables (`TERM_PROGRAM`,
`VTE_VERSION`, …) but need their own `TERM`.
1. **`$COLORTERM`** — `truecolor` or `24bit` sets the truecolor flag for all subsequent probes 1. **`$TMUX`** — tmux session → `tmux-direct`¹ → `screen-direct`¹, then `tmux-256color`, then `screen-256color`
2. **`$TERM_PROGRAM`** — emulator name hints: 2. **`$STY`** — GNU screen session → `screen-direct`¹, then `screen-256color`, then `screen`
3. **`$TERM_PROGRAM`** — emulator name hints:
- `iTerm.app` → `xterm-direct`¹, then `xterm-256color` - `iTerm.app` → `xterm-direct`¹, then `xterm-256color`
- `WezTerm` → `xterm-direct`¹, then `wezterm`, then `xterm-256color` - `WezTerm` → `xterm-direct`¹, then `wezterm`, then `xterm-256color`
- `Hyper`, `vscode` → `xterm-direct`¹, then `xterm-256color` - `Hyper`, `vscode` → `xterm-direct`¹, then `xterm-256color`
3. **`$VTE_VERSION`** — GNOME Terminal, Tilix, … → `vte-direct`¹, then `vte-256color`, then `xterm-256color` 4. **`$VTE_VERSION`** — GNOME Terminal, Tilix, … → `vte-direct`¹, then `vte-256color`, then `xterm-256color`
4. **`$WT_SESSION`** — Windows Terminal → `xterm-direct`¹, then `xterm-256color` 5. **`$WT_SESSION`** — Windows Terminal → `xterm-direct`¹, then `xterm-256color`
5. **`$TMUX`** — tmux session → `tmux-direct`¹ → `screen-direct`¹, then `tmux-256color`, then `screen-256color` 6. **Generic probe** — `xterm-256color` → `xterm-color` → `xterm` → `vt100`
6. **`$STY`** — GNU screen session → `screen-direct`¹, then `screen-256color`, then `screen`
7. **Generic probe** — `xterm-256color` → `xterm-color` → `xterm` → `vt100`
¹ Only attempted when the truecolor flag is set (`COLORTERM=truecolor` or `24bit`). ¹ Only attempted when the truecolor flag is set (`COLORTERM=truecolor` or `24bit`).
+11
View File
@@ -25,6 +25,17 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
fallback (no `pigz`) works again. fallback (no `pigz`) works again.
- `utaz`: single top-level directory flattening, `--no-dir` target location, - `utaz`: single top-level directory flattening, `--no-dir` target location,
`foo.tar.gz` host directory name and `.tar.lz` extraction. `foo.tar.gz` host directory name and `.tar.lz` extraction.
- `conf_save`: no longer replaces a symlinked configuration file by a regular
one, keeps the file permissions, rejects multi-line values, and inserts a new
key before the trailing comments/banner of its section instead of under the
next section's banner; leading blanks before a `[section]` are recognised.
- `conf_dump -a`: a commented default is no longer listed when the key is set
later in the same section; `NO_COLOR` is honoured; names with newlines or
backslashes in arguments are handled.
- `term_set`: `COLORTERM=truecolor` no longer short-circuits every other probe
(it selected `xterm-direct` even inside tmux/screen or VTE terminals);
multiplexers (tmux, screen) are now checked before emulator hints, since they
inherit the emulator's variables.
- `compress.sh`: option flags and loop variables no longer leak into the - `compress.sh`: option flags and loop variables no longer leak into the
interactive shell. interactive shell.
+3 -2
View File
@@ -157,8 +157,9 @@ Set `TERM` in the `[system]` section of `profile.conf`:
When `TERM` is absent or set to `smart`, `term_set` probes your terminal When `TERM` is absent or set to `smart`, `term_set` probes your terminal
emulator's environment variables (`COLORTERM`, `TERM_PROGRAM`, `VTE_VERSION`, emulator's environment variables (`COLORTERM`, `TERM_PROGRAM`, `VTE_VERSION`,
`WT_SESSION`, `TMUX`, `STY`) and then tests terminfo entries in preference `WT_SESSION`, `TMUX`, `STY`) and then tests terminfo entries in preference
order. If you are experiencing display issues, run `term_set` interactively order (multiplexers first). If you are experiencing display issues, run
and check the result with `echo $TERM`. See *Prompt & theming* below for `TERM=smart; term_set` interactively (`term_set` leaves an already-set `TERM`
untouched) and check the result with `echo $TERM`. See *Prompt & theming* below for
symptoms caused by a wrong `TERM` value. symptoms caused by a wrong `TERM` value.
--- ---
Executable → Regular
+158 -66
View File
@@ -34,16 +34,44 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Print the path of the configuration file the conf_* functions work on:
# the user file ($HOME/.profile.conf) when it exists, otherwise the installation
# file ($PROFILE_CONF, defaulting to $MYPATH/profile.conf). The returned path
# may not exist yet.
# Usage: _conf_path
_conf_path()
{
if [[ -f "$HOME/.profile.conf" ]]; then
printf '%s\n' "$HOME/.profile.conf"
else
printf '%s\n' "${PROFILE_CONF:-${MYPATH}/profile.conf}"
fi
}
export -f _conf_path
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Save or update a key=value pair in a section of the profile configuration file. # Save or update a key=value pair in a section of the profile configuration file.
# The user configuration ($HOME/.profile.conf) is updated when it exists, # The user configuration ($HOME/.profile.conf) is updated when it exists,
# otherwise the installation configuration ($PROFILE_CONF or $MYPATH/profile.conf) # otherwise the installation configuration ($PROFILE_CONF or $MYPATH/profile.conf)
# is used. The section header is created automatically when absent. # is used. The section header is created automatically when absent.
# #
# An existing key is replaced in place (duplicates in the same section are
# dropped); a new key is added at the end of the section's active lines, before
# its trailing blank/comment lines, so it never ends up under the banner of the
# next section. The running shell is NOT updated: the value is read again by
# parse_conf/load_conf the next time the profile is sourced.
#
# The value is stored verbatim, but parse_conf trims surrounding whitespace and
# quotes, drops backticks and "$(", and expands "$VAR" references when it reads
# the file back; values relying on those are therefore not preserved.
#
# Usage: conf_save <section> <key> <value> # Usage: conf_save <section> <key> <value>
# section : INI section name without brackets, e.g. "prompt" for [prompt] # section : INI section name without brackets, e.g. "prompt" for [prompt]
# key : variable name to set (alphanumeric and underscore only) # key : variable name to set (alphanumeric and underscore only)
# value : value to assign (may be empty) # value : value to assign (may be empty, must be a single line)
conf_save() conf_save()
{ {
if [[ $# -ne 3 ]]; then if [[ $# -ne 3 ]]; then
@@ -61,15 +89,21 @@ conf_save()
disp E "conf_save: invalid key name '${key}' (alphanumeric and underscore only)." disp E "conf_save: invalid key name '${key}' (alphanumeric and underscore only)."
return 1 return 1
fi fi
# A line break would split the entry and corrupt the file.
local conf_file if [[ "$value" == *$'\n'* || "$value" == *$'\r'* ]]; then
if [[ -f "$HOME/.profile.conf" ]]; then disp E "conf_save: the value must not contain line breaks."
conf_file="$HOME/.profile.conf" return 1
else
conf_file="${PROFILE_CONF:-${MYPATH}/profile.conf}"
fi fi
local conf_dir="${conf_file%/*}" local conf_file
conf_file=$(_conf_path)
# Follow a symlink (e.g. dotfiles manager) so the link itself is not
# replaced by a regular file when the temporary file is moved over it.
[[ -L "$conf_file" ]] && conf_file=$(readlink -f -- "$conf_file")
# Directory part of the path; a bare file name lives in the current directory.
local conf_dir=.
[[ "$conf_file" == */* ]] && conf_dir="${conf_file%/*}"
[[ -d "$conf_dir" ]] || mkdir -p "$conf_dir" || { [[ -d "$conf_dir" ]] || mkdir -p "$conf_dir" || {
disp E "conf_save: unable to create configuration directory: ${conf_dir}" disp E "conf_save: unable to create configuration directory: ${conf_dir}"
return 1 return 1
@@ -86,26 +120,40 @@ conf_save()
return 0 return 0
fi fi
# Temporary file next to the target so the final mv is an atomic rename.
local tmp_file="${conf_file}.tmp.$$" local tmp_file="${conf_file}.tmp.$$"
# The value is passed via ENVIRON (not awk -v) so backslashes are preserved # The value is passed via ENVIRON (not awk -v) so backslashes are preserved
# verbatim; awk -v would interpret C escape sequences (\t, \n, \e, ...). # verbatim; awk -v would interpret C escape sequences (\t, \n, \e, ...).
# awk state machine: in_sec is true while inside the target section, wrote
# once the key has been emitted, saw_sec once the section has been seen.
_CONF_SAVE_VAL="$value" \ _CONF_SAVE_VAL="$value" \
awk -v sec="$section" -v key="$key" ' awk -v sec="$section" -v key="$key" '
# Emit the held trailing blank/comment lines
function flush_pending( i) {
for (i = 1; i <= np; i++) print pend[i]
np = 0
}
BEGIN { BEGIN {
val = ENVIRON["_CONF_SAVE_VAL"] val = ENVIRON["_CONF_SAVE_VAL"]
in_sec = 0 in_sec = 0
saw_sec = 0 saw_sec = 0
wrote = 0 wrote = 0
np = 0
} }
{ {
if ($0 ~ /^\[[^]]+\][[:space:]]*$/) { # Section header (leading blanks allowed, as parse_conf does)
if ($0 ~ /^[[:space:]]*\[[^]]+\][[:space:]]*$/) {
# Leaving the target section without having found the key:
# add it now, before the section trailing comments.
if (in_sec && !wrote) { if (in_sec && !wrote) {
print key "=" val print key "=" val
wrote = 1 wrote = 1
} }
flush_pending()
if ($0 ~ ("^\\[" sec "\\][[:space:]]*$")) { if ($0 ~ ("^[[:space:]]*\\[" sec "\\][[:space:]]*$")) {
in_sec = 1 in_sec = 1
saw_sec = 1 saw_sec = 1
} else { } else {
@@ -116,6 +164,15 @@ conf_save()
next next
} }
# Until the key is written, hold blank and comment lines: if the
# section ends without the key, it must go before them.
if (in_sec && !wrote && $0 ~ /^[[:space:]]*([#;].*)?$/) {
pend[++np] = $0
next
}
flush_pending()
# Existing assignment of the key: replace the first, drop the others
if (in_sec && $0 ~ ("^[[:space:]]*" key "[[:space:]]*=")) { if (in_sec && $0 ~ ("^[[:space:]]*" key "[[:space:]]*=")) {
if (!wrote) { if (!wrote) {
print key "=" val print key "=" val
@@ -130,9 +187,11 @@ conf_save()
END { END {
if (in_sec && !wrote) { if (in_sec && !wrote) {
print key "=" val print key "=" val
wrote = 1
} }
flush_pending()
if (!saw_sec) { if (!saw_sec) {
print "" if (NR > 0) print ""
print "[" sec "]" print "[" sec "]"
print key "=" val print key "=" val
} }
@@ -143,7 +202,11 @@ conf_save()
return 1 return 1
} }
mv "$tmp_file" "$conf_file" || { # mktemp-style default permissions must not replace the original ones
# (the file may hold private values); best effort, GNU chmod only.
chmod --reference="$conf_file" -- "$tmp_file" 2>/dev/null
mv -- "$tmp_file" "$conf_file" || {
rm -f "$tmp_file" rm -f "$tmp_file"
disp E "conf_save: unable to replace configuration file: ${conf_file}" disp E "conf_save: unable to replace configuration file: ${conf_file}"
return 1 return 1
@@ -177,9 +240,8 @@ conf_dump()
disp E "Invalid options, use \"conf_dump --help\" to display usage." disp E "Invalid options, use \"conf_dump --help\" to display usage."
return 1 return 1
fi fi
local -a _pargs=() # Restore the getopt-normalised (shell-quoted) argument list, "--" ends options
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0') eval set -- "$PARSED"
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
@@ -219,28 +281,37 @@ conf_dump()
[[ $# -gt 0 ]] && key_pattern="$1" [[ $# -gt 0 ]] && key_pattern="$1"
local conf_file local conf_file
if [[ -f "$HOME/.profile.conf" ]]; then conf_file=$(_conf_path)
conf_file="$HOME/.profile.conf"
else
conf_file="${PROFILE_CONF:-${MYPATH}/profile.conf}"
fi
if [[ ! -f "$conf_file" ]]; then if [[ ! -f "$conf_file" ]]; then
disp E "conf_dump: configuration file not found: ${conf_file}" disp E "conf_dump: configuration file not found: ${conf_file}"
return 1 return 1
fi fi
# Colours are passed via ENVIRON to avoid awk -v escape interpretation. # Honour NO_COLOR like disp does: empty colour strings give plain output.
_CONF_DUMP_SEC="${Blue:-}" \ local c_sec="${Blue:-}" c_active="${BGreen:-}" c_default="${BIWhite:-}" c_rst="${RESETCOL:-}"
_CONF_DUMP_KEY_ACTIVE="${BGreen:-}" \ if [[ -n ${NO_COLOR:-} ]]; then
_CONF_DUMP_KEY_DEFAULT="${BIWhite:-}" \ c_sec="" c_active="" c_default="" c_rst=""
_CONF_DUMP_RST="${RESETCOL:-}" \ fi
awk -v sec_filter="$section" -v key_filter="$key_pattern" -v show_all="$show_all" '
# Everything is passed via ENVIRON, not awk -v, to avoid awk interpreting
# backslash escapes (colours hold literal \e sequences; a filter could
# contain a backslash). The file is given twice: the first pass only
# collects the active keys, the second one prints.
_CONF_DUMP_SEC="$c_sec" \
_CONF_DUMP_KEY_ACTIVE="$c_active" \
_CONF_DUMP_KEY_DEFAULT="$c_default" \
_CONF_DUMP_RST="$c_rst" \
_CONF_DUMP_SECF="$section" \
_CONF_DUMP_KEYF="$key_pattern" \
awk -v show_all="$show_all" '
BEGIN { BEGIN {
c_sec = ENVIRON["_CONF_DUMP_SEC"] c_sec = ENVIRON["_CONF_DUMP_SEC"]
c_active = ENVIRON["_CONF_DUMP_KEY_ACTIVE"] c_active = ENVIRON["_CONF_DUMP_KEY_ACTIVE"]
c_default = ENVIRON["_CONF_DUMP_KEY_DEFAULT"] c_default = ENVIRON["_CONF_DUMP_KEY_DEFAULT"]
c_rst = ENVIRON["_CONF_DUMP_RST"] c_rst = ENVIRON["_CONF_DUMP_RST"]
sec_filter = ENVIRON["_CONF_DUMP_SECF"]
key_filter = ENVIRON["_CONF_DUMP_KEYF"]
# Shell colour vars contain literal \e[…m strings; convert to # Shell colour vars contain literal \e[…m strings; convert to
# the actual ESC byte so awk print emits real ANSI sequences. # the actual ESC byte so awk print emits real ANSI sequences.
gsub(/\\e/, "\033", c_sec) gsub(/\\e/, "\033", c_sec)
@@ -251,17 +322,35 @@ conf_dump()
current_sec = "" current_sec = ""
hdr_printed = 0 hdr_printed = 0
found = 0 found = 0
# seen[sec:key] — tracks every key already printed to deduplicate # active[sec:key] — keys set (uncommented) anywhere in the file,
# commented entries and avoid re-showing an active key in white. # filled by the first pass.
# seen[sec:key] — keys already printed, to show each commented
# default at most once.
}
# First pass (FNR == NR): record the active keys, print nothing.
# An active key suppresses its commented default wherever the latter
# appears in the section (before or after it).
FNR == NR {
line = $0; sub(/\r$/, "", line)
if (line ~ /^[[:space:]]*\[[^]]+\][[:space:]]*$/) {
pass1_sec = line
sub(/^[[:space:]]*\[/, "", pass1_sec)
sub(/\][[:space:]]*$/, "", pass1_sec)
} else if (line ~ /^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*[[:space:]]*=/) {
k = line; sub(/[[:space:]]*=.*$/, "", k); sub(/^[[:space:]]*/, "", k)
active[pass1_sec ":" k] = 1
}
next
} }
{ {
sub(/\r$/, "") sub(/\r$/, "")
# Section header # Section header (leading blanks allowed, as parse_conf does)
if ($0 ~ /^\[[^]]+\][[:space:]]*$/) { if ($0 ~ /^[[:space:]]*\[[^]]+\][[:space:]]*$/) {
current_sec = $0 current_sec = $0
sub(/^\[/, "", current_sec) sub(/^[[:space:]]*\[/, "", current_sec)
sub(/\][[:space:]]*$/, "", current_sec) sub(/\][[:space:]]*$/, "", current_sec)
in_target = (sec_filter == "" || current_sec == sec_filter) in_target = (sec_filter == "" || current_sec == sec_filter)
hdr_printed = 0 hdr_printed = 0
@@ -285,7 +374,8 @@ conf_dump()
next next
} }
# Commented-out key=value — shown only with show_all. # Commented-out key=value — shown only with show_all, and only when
# the key is not set for real anywhere in the same section.
# The value displayed is the live environment value (what load_conf # The value displayed is the live environment value (what load_conf
# actually exported); when the key is not set in the environment we # actually exported); when the key is not set in the environment we
# fall back to the value written in the comment, i.e. the documented # fall back to the value written in the comment, i.e. the documented
@@ -293,7 +383,7 @@ conf_dump()
if (show_all && $0 ~ /^[[:space:]]*#[[:space:]]*[A-Za-z_][A-Za-z0-9_]*[[:space:]]*=/) { if (show_all && $0 ~ /^[[:space:]]*#[[:space:]]*[A-Za-z_][A-Za-z0-9_]*[[:space:]]*=/) {
line = $0; sub(/^[[:space:]]*#[[:space:]]*/, "", line) line = $0; sub(/^[[:space:]]*#[[:space:]]*/, "", line)
key = line; sub(/[[:space:]]*=.*$/, "", key); sub(/^[[:space:]]*/, "", key) key = line; sub(/[[:space:]]*=.*$/, "", key); sub(/^[[:space:]]*/, "", key)
if (seen[current_sec ":" key]) next if (active[current_sec ":" key] || seen[current_sec ":" key]) next
seen[current_sec ":" key] = 1 seen[current_sec ":" key] = 1
if (key_filter != "" && index(key, key_filter) == 0) next if (key_filter != "" && index(key, key_filter) == 0) next
val = ENVIRON[key] val = ENVIRON[key]
@@ -306,7 +396,7 @@ conf_dump()
print " " c_default key c_rst "=" val print " " c_default key c_rst "=" val
} }
} }
' "$conf_file" ' "$conf_file" "$conf_file"
} }
export -f conf_dump export -f conf_dump
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -318,9 +408,11 @@ export -f conf_dump
# #
# If TERM is already set to a specific value (not empty, not the sentinel # If TERM is already set to a specific value (not empty, not the sentinel
# "smart"), it is honoured as-is — standard Unix behaviour. # "smart"), it is honoured as-is — standard Unix behaviour.
# If TERM is empty or set to "smart", terminal emulator hints are checked first # If TERM is empty or set to "smart", multiplexer hints are checked first
# (COLORTERM, TERM_PROGRAM, VTE_VERSION, WT_SESSION, TMUX, STY), then terminfo # (TMUX, STY), then terminal emulator hints (TERM_PROGRAM, VTE_VERSION,
# is probed in preference order: xterm-256color → xterm-color → xterm → vt100. # WT_SESSION), then terminfo is probed in preference order:
# xterm-256color → xterm-color → xterm → vt100. COLORTERM=truecolor|24bit
# enables the *-direct (24-bit) entries in those probes.
# #
# Usage: term_set # Usage: term_set
term_set() term_set()
@@ -334,27 +426,46 @@ term_set()
fi fi
# Return true when terminfo has an entry for the given terminal type. # Return true when terminfo has an entry for the given terminal type.
# (Nested functions are global in bash; it is removed with unset -f below.)
local _term_has local _term_has
_term_has() _term_has()
{ {
tput -T "$1" longname >/dev/null 2>&1 tput -T "$1" longname >/dev/null 2>&1
} }
# True-color hint: COLORTERM=truecolor|24bit is set by the emulator. # True-color flag: COLORTERM=truecolor|24bit is set by the emulator. It does
# not select an entry by itself; it only enables the *-direct entries in the
# probes below, each of which falls back to its *-256color counterpart.
local _truecolor=0 local _truecolor=0
[[ "${COLORTERM:-}" == "truecolor" || "${COLORTERM:-}" == "24bit" ]] && _truecolor=1 [[ "${COLORTERM:-}" == "truecolor" || "${COLORTERM:-}" == "24bit" ]] && _truecolor=1
local _candidate="" local _candidate=""
# 1. Explicit truecolor hint set by modern terminal emulators. # Probes run in priority order and stop at the first terminfo entry found
# (_candidate non-empty). Multiplexers come first: they provide their own
# terminal and inherit the emulator's variables (TERM_PROGRAM, VTE_VERSION,
# ...), which would otherwise wrongly select the outer emulator's entry.
# 1. tmux — prefer *-direct when truecolor, then *-256color, then screen-256color.
if [[ -n "${TMUX:-}" ]]; then
if (( _truecolor )); then if (( _truecolor )); then
local _t _term_has "tmux-direct" && _candidate="tmux-direct"
for _t in xterm-direct xterm-256color; do [[ -z "$_candidate" ]] && _term_has "screen-direct" && _candidate="screen-direct"
_term_has "$_t" && { _candidate="$_t"; break; } fi
done [[ -z "$_candidate" ]] && _term_has "tmux-256color" && _candidate="tmux-256color"
[[ -z "$_candidate" ]] && _term_has "screen-256color" && _candidate="screen-256color"
fi fi
# 2. Terminal programme name hints. # 2. GNU screen — prefer screen-direct when truecolor, then screen-256color.
if [[ -z "$_candidate" && -n "${STY:-}" ]]; then
if (( _truecolor )); then
_term_has "screen-direct" && _candidate="screen-direct"
fi
[[ -z "$_candidate" ]] && _term_has "screen-256color" && _candidate="screen-256color"
[[ -z "$_candidate" ]] && _term_has "screen" && _candidate="screen"
fi
# 3. Terminal programme name hints.
if [[ -z "$_candidate" ]]; then if [[ -z "$_candidate" ]]; then
case "${TERM_PROGRAM:-}" in case "${TERM_PROGRAM:-}" in
iTerm.app) iTerm.app)
@@ -381,7 +492,7 @@ term_set()
esac esac
fi fi
# 3. VTE-based terminals (GNOME Terminal, Tilix, Xfce Terminal, …). # 4. VTE-based terminals (GNOME Terminal, Tilix, Xfce Terminal, …).
if [[ -z "$_candidate" && -n "${VTE_VERSION:-}" ]]; then if [[ -z "$_candidate" && -n "${VTE_VERSION:-}" ]]; then
if (( _truecolor )); then if (( _truecolor )); then
_term_has "vte-direct" && _candidate="vte-direct" _term_has "vte-direct" && _candidate="vte-direct"
@@ -390,7 +501,7 @@ term_set()
[[ -z "$_candidate" ]] && _term_has "xterm-256color" && _candidate="xterm-256color" [[ -z "$_candidate" ]] && _term_has "xterm-256color" && _candidate="xterm-256color"
fi fi
# 4. Windows Terminal. # 5. Windows Terminal.
if [[ -z "$_candidate" && -n "${WT_SESSION:-}" ]]; then if [[ -z "$_candidate" && -n "${WT_SESSION:-}" ]]; then
if (( _truecolor )); then if (( _truecolor )); then
_term_has "xterm-direct" && _candidate="xterm-direct" _term_has "xterm-direct" && _candidate="xterm-direct"
@@ -398,26 +509,7 @@ term_set()
[[ -z "$_candidate" ]] && _term_has "xterm-256color" && _candidate="xterm-256color" [[ -z "$_candidate" ]] && _term_has "xterm-256color" && _candidate="xterm-256color"
fi fi
# 5. tmux — prefer *-direct when truecolor, then *-256color, then screen-256color. # 6. Generic terminfo probe in preference order.
if [[ -z "$_candidate" && -n "${TMUX:-}" ]]; then
if (( _truecolor )); then
_term_has "tmux-direct" && _candidate="tmux-direct"
[[ -z "$_candidate" ]] && _term_has "screen-direct" && _candidate="screen-direct"
fi
[[ -z "$_candidate" ]] && _term_has "tmux-256color" && _candidate="tmux-256color"
[[ -z "$_candidate" ]] && _term_has "screen-256color" && _candidate="screen-256color"
fi
# 6. GNU screen — prefer screen-direct when truecolor, then screen-256color.
if [[ -z "$_candidate" && -n "${STY:-}" ]]; then
if (( _truecolor )); then
_term_has "screen-direct" && _candidate="screen-direct"
fi
[[ -z "$_candidate" ]] && _term_has "screen-256color" && _candidate="screen-256color"
[[ -z "$_candidate" ]] && _term_has "screen" && _candidate="screen"
fi
# 7. Generic terminfo probe in preference order.
if [[ -z "$_candidate" ]]; then if [[ -z "$_candidate" ]]; then
local _t local _t
for _t in xterm-256color xterm-color xterm vt100; do for _t in xterm-256color xterm-color xterm vt100; do