A library to detect and handle terminal color/styling support.
Terminal environments can have several levels of color support:
- true color - sometimes referred to as RGB or 24bit, can set any valid RGB color.
- ANSI 256 - Indexed colors in the 256 color list.
- ANSI 16 - Only the first 16 colors in the ANSI color list, commonly seen in non-graphical environments like login shells.
- No Color - Text modifiers like bold and italics can be used, but no colors should be emitted. This is usually set by override variables.
- No TTY - The output is not a TTY and no escape sequences should be used.
All features are disabled by default.
-
terminfo- Enables checking against the terminfo database for color support. See terminfo. -
query-detect- Enables querying for true color support via DECRQSS. See querying the terminal. -
windows-version- Enables additional checks for color support based on the current version of Windows. See windows. -
convert- Enables converting incompatible colors based on the color support level. See conversions. -
color-cache- Adds an optional LRU cache for color conversion operations. See caching. -
ratatui- Enables direct conversion to Ratatui style and color objects. See Ratatui conversions. -
ratatui-underline-color- Enables Ratatui'sunderline-colorfeature and includes underline colors in Ratatui style conversions.
use std::io::stdout;
use termprofile::{TermProfile, DetectorSettings};
let profile = TermProfile::detect(&stdout(), DetectorSettings::default());
println!("Detected profile: {profile:?}");Variables can be overridden before detecting the color profile.
use std::io::stdout;
use termprofile::{TermProfile, TermVars, DetectorSettings};
let mut vars = TermVars::from_env(&stdout(), DetectorSettings::default());
vars.overrides.force_color = "1".into();
let profile = TermProfile::detect_with_vars(vars);
println!("Profile with override: {profile:?}");Environment variables can be sourced from an in-memory map instead of reading directly from the environment.
use std::collections::HashMap;
use std::io::stdout;
use termprofile::{TermProfile, TermVars, DetectorSettings};
let source = HashMap::from_iter([("TERM", "xterm-256color"), ("COLORTERM", "1")]);
let vars = TermVars::from_source(&source, &stdout(), DetectorSettings::default());
let profile = TermProfile::detect_with_vars(vars);
println!("Profile: {profile:?}");Colors and styles can be automatically adapted based on the current profile.
use termprofile::TermProfile;
use std::io::stdout;
use anstyle::{Color, RgbColor, Ansi256Color};
let profile = TermProfile::Ansi256;
let rgb_color: Color = RgbColor(209, 234, 213).into();
let adapted_color = profile.adapt_color(rgb_color);
assert_eq!(adapted_color, Some(Ansi256Color(253).into()));Automatic color conversion doesn't always produce optimal results. Use
ProfileColor to have more control over the conversion.
use termprofile::{TermProfile, ProfileColor};
use anstyle::{Color, RgbColor, Ansi256Color, AnsiColor};
let profile = TermProfile::Ansi256;
let color = ProfileColor::new(Color::Rgb(RgbColor(209, 234, 213)), profile)
.ansi_256(240)
.ansi_16(AnsiColor::White);
assert_eq!(color.adapt(), Some(Ansi256Color(240).into()));Text modifiers will be removed if the profile is set to NoTTY.
use termprofile::TermProfile;
use std::io::stdout;
use anstyle::{Color, RgbColor, AnsiColor, Style};
let profile = TermProfile::Ansi16;
let fg = RgbColor(106, 132, 92).into();
let bg = RgbColor(4, 35, 212).into();
let style = Style::new().fg_color(Some(fg)).bg_color(Some(bg));
let adapted_style = profile.adapt_style(style);
assert_eq!(
adapted_style,
Style::new()
.fg_color(Some(AnsiColor::Cyan.into()))
.bg_color(Some(AnsiColor::BrightBlue.into()))
);anstyle is used for color conversions due to its compatibility with other
terminal color crates, but it does not have support for a Color::Reset
variant, which can be important for TUI apps. If the ratatui feature is
enabled, all of the above conversions work with Ratatui types, allowing for full
compatibility without requiring anstyle as an intermediate layer.
use termprofile::TermProfile;
use std::io::stdout;
use ratatui::style::Color;
let profile = TermProfile::Ansi256;
let rgb_color = Color::Rgb(209, 234, 213);
let adapted_color = profile.adapt_color(rgb_color);
assert_eq!(adapted_color, Some(Color::Indexed(253)));Color conversion can be somewhat expensive if you're rendering at a high frame
rate. The color-cache feature enables an opt-in LRU cache that can be used to
cache a certain amount of calculations.
use termprofile::{set_color_cache_enabled, set_color_cache_size};
set_color_cache_enabled(true);
set_color_cache_size(256.try_into().expect("non-zero size"));See examples.
Sadly, there is no standard way to accurately detect color support in terminals. Instead, we must rely on a number of environment variables and other methods that have organically emerged as a pseudo-standard over time.
The most reliable way to detect true color support is to query for it. This is preferable over environment variables because it works over SSH and is not susceptible to ambiguity caused by terminal multiplexers. Unfortunately, this method isn't supported in many terminals yet.
COLORTERM- the terminal supports true color if this is set to24bitortruecolorTERM- the most common variable supplied by a terminal, this denotes the name of the terminal program. We maintain a list of terminals that are known to have true color support as well as some fuzzy matching logic for common suffixes (e.g.-256colorfor ANSI 256 support).TERM_PROGRAM- less common thanTERM, but can report more granular information for a few terminals.TERM_PROGRAM_VERSION- used sparingly, but some terminals only gain true color support after a certain version.
Several variables can be set manually by users in order to override the color detection behavior.
-
CLICOLOR_FORCE/FORCE_COLOR- two competing standards that seem to do the same thing. When either of these is set to a truthy value, the color support level will be at least ANSI 16, with other variables used to decide if further support is available.In addition to true/false values, chalk supports using numerical values to set a specific color level. Unfortunately, this creates ambiguity with
FORCE_COLOR=1which could be interpreted to mean either "color level 1" or "true". Instead, we support semantic values to force a specific color value.no_color- disables all colorsansioransi16- forces ANSI 16 coloransi256- forces ANSI 256 colorstruecolor,true_color, or24bit- forces true color
example:
CLICOLOR_FORCE="ansi256"This can be useful for testing how your program works with a specific color support level.
-
CLICOLOR- Will enable colors ifTERMis unset and the output is a terminal. -
NO_COLOR- When set to a truthy value, this forces colors to be disabled. -
TTY_FORCE- this can be set to a truthy value to treat the terminal like a TTY even if the call tois_terminalreturns false. May be useful when running a subprocess or in some nonstandard platforms such as webassembly.
If the terminfo feature is enabled, the
terminfo database is queried for
available properties:
colors- returns the number of available colors. Due to limitations with terminfo, true color terminals generally only report 256 colors with this property.TERMvalues ending in -direct (kitty-directoralacritty-direct, for example) are the exception and may report color values > 256 here.RGBandTc- nonstandard extensions to terminfo, this is a boolean that may be set in some newer terminals to indicate true color support.
If the windows-version feature is enabled, additional checks will be performed
to detect support for ANSI colors based on the active version of Windows. You
may want to enable this if support for older versions of Windows 10 (prior to
build
#14931,
released in 2016) is important to you.
Since CI platforms will render build output in an environment that's not a true TTY, color support detection likely won't work automatically. We try to account for some variables supplied by common CI providers, but we can't account for all of them.
If your CI provider isn't supported, you can use FORCE_COLOR or
CLICOLOR_FORCE to force color output.
Terminal multiplexers like GNU Screen and tmux affect the color support of the terminal. We attempt to detect these cases properly, but it's difficult to do so perfectly since they obscure some information from the host terminal.
Newer versions of Screen support true color, but there doesn't seem to be a way to see if it's enabled, so we cannot accurately detect this case.
Environment variables may not be passed into your SSH session depending on your
configuration, which can cause color support to be detected incorrectly. For
best results, enable the query-detect feature and use a terminal that supports
querying.
The MSRV is currently 1.88.0. Since Cargo's V3 resolver supports MSRV-aware dependencies, we do not treat an MSRV bump as a breaking change.
This library takes inspiration from many other implementations: