set_color: add --foreground and --reset options

`--foreground` has two purposes:
- allow resetting the foreground color to its default, without also
resetting the other colors and modes
- improve readibility and unify the `set_color` arguments

`--reset` also has two purposes:
- provide a more intuitive way to reset the text formatting
- allow setting the colors and modes from a clean state without
requiring two calls to `set_color`

Part 3/3 of #12495

Closes #12507
This commit is contained in:
Nahor
2026-03-03 17:28:50 -08:00
committed by Johannes Altmanninger
parent 4e41d142fd
commit eb7ea0ef9b
19 changed files with 215 additions and 94 deletions

View File

@@ -5,6 +5,7 @@ Notable improvements and fixes
------------------------------
- New Spanish translations (:issue:`12489`).
- ``set_color`` is able to turn off italics, reverse mode, strikethrough and underline individually (e.g. ``--italics=off``).
- ``set_color`` learned the foreground (``--foreground`` or ``-f``) and reset (``--reset``) modifiers.
For distributors and developers
-------------------------------

View File

@@ -6,7 +6,7 @@ Synopsis
.. synopsis::
set_color [OPTIONS] VALUE
set_color [OPTIONS] [VALUE]
Description
-----------
@@ -33,6 +33,11 @@ However if :envvar:`fish_term256` is set to 0, fish prefers the first named colo
The following options are available:
**-f** or **--foreground** *COLOR*
Sets the foreground color.
This is equivalent to calling ``set_color COLOR`` with the exception that the keyword **normal** will only reset the foreground color to its default, instead of all colors and modes.
It cannot be used with *VALUE* or **--print-colors**.
**-b** or **--background** *COLOR*
Sets the background color.
@@ -41,6 +46,7 @@ The following options are available:
**-c** or **--print-colors**
Prints the given colors or a colored list of the 16 named colors.
It cannot be used with **--foreground**.
**-o** or **--bold**
Sets bold mode.
@@ -60,6 +66,10 @@ The following options are available:
**-u** or **--underline**, or **-uSTYLE** or **--underline=STYLE**
Set the underline mode; supported styles are **single** (default), **double**, **curly**, **dotted**, **dashed** and **off**.
**--reset**
Reset the text formatting to the terminal defaults before applying the new colors and modes.
This is equivalent to calling ``set_color normal`` except that it is possible to set the foreground color in the same call (e.g. ``set_color --reset green``)
**--theme=THEME**
Ignored.
:ref:`Color variables <variables-color>` that contain only this option are treated like missing / empty color variables,
@@ -75,10 +85,11 @@ The following options are available:
Notes
-----
1. Using **set_color normal** will reset all colors and modes to the terminal's default.
2. Setting the background color only affects subsequently written characters. Fish provides no way to set the background color for the entire terminal window. Configuring the window background color (and other attributes such as its opacity) has to be done using whatever mechanisms the terminal provides. Look for a config option.
3. Some terminals use the ``--bold`` escape sequence to switch to a brighter color set rather than increasing the weight of text.
4. ``set_color`` works by printing sequences of characters to standard output. If used in command substitution or a pipe, these characters will also be captured. This may or may not be desirable. Checking the exit status of ``isatty stdout`` before using ``set_color`` can be useful to decide not to colorize output in a script.
1. Using ``set_color normal`` will reset all colors and modes to the terminal's default.
2. In contrast, ``set_color --foreground normal`` will only reset the foreground color and leave all the other colors and modes unchanged.
3. Setting the background color only affects subsequently written characters. Fish provides no way to set the background color for the entire terminal window. Configuring the window background color (and other attributes such as its opacity) has to be done using whatever mechanisms the terminal provides. Look for a config option.
4. Some terminals use the ``--bold`` escape sequence to switch to a brighter color set rather than increasing the weight of text.
5. ``set_color`` works by printing sequences of characters to standard output. If used in command substitution or a pipe, these characters will also be captured. This may or may not be desirable. Checking the exit status of ``isatty stdout`` before using ``set_color`` can be useful to decide not to colorize output in a script.
Examples
--------

View File

@@ -155,6 +155,9 @@ Optional Commands
* - ``\e[48;2; Ps ; Ps ; Ps m``
-
- Select background color from 24-bit RGB colors.
* - ``\e[39m``
-
- Reset foreground color to the terminal's default.
* - ``\e[49m``
-
- Reset background color to the terminal's default.

View File

@@ -215,6 +215,10 @@ msgstr "%s: %s: ungültiger Unterbefehl"
msgid "%s: %s: invalid variable name. See `help %s`"
msgstr ""
#, c-format
msgid "%s: %s: option cannot be used with a non-option argument"
msgstr ""
#, c-format
msgid "%s: %s: option does not take an argument"
msgstr ""

View File

@@ -215,6 +215,10 @@ msgstr ""
msgid "%s: %s: invalid variable name. See `help %s`"
msgstr ""
#, c-format
msgid "%s: %s: option cannot be used with a non-option argument"
msgstr ""
#, c-format
msgid "%s: %s: option does not take an argument"
msgstr ""

View File

@@ -215,6 +215,10 @@ msgstr "%s: %s: subcomando no válido"
msgid "%s: %s: invalid variable name. See `help %s`"
msgstr "%s: %s: nombre de variable no válido. Consulte `help %s`"
#, c-format
msgid "%s: %s: option cannot be used with a non-option argument"
msgstr ""
#, c-format
msgid "%s: %s: option does not take an argument"
msgstr "%s: %s: la opción no acepta un argumento"

View File

@@ -344,6 +344,10 @@ msgstr "%s : %s : sous-commande invalide"
msgid "%s: %s: invalid variable name. See `help %s`"
msgstr "%s : %s : nom de variable invalide. Voir « help %s »"
#, c-format
msgid "%s: %s: option cannot be used with a non-option argument"
msgstr ""
#, c-format
msgid "%s: %s: option does not take an argument"
msgstr "%s : %s : cette option ne prend pas dargument"

View File

@@ -211,6 +211,10 @@ msgstr ""
msgid "%s: %s: invalid variable name. See `help %s`"
msgstr ""
#, c-format
msgid "%s: %s: option cannot be used with a non-option argument"
msgstr ""
#, c-format
msgid "%s: %s: option does not take an argument"
msgstr ""

View File

@@ -216,6 +216,10 @@ msgstr ""
msgid "%s: %s: invalid variable name. See `help %s`"
msgstr ""
#, c-format
msgid "%s: %s: option cannot be used with a non-option argument"
msgstr ""
#, c-format
msgid "%s: %s: option does not take an argument"
msgstr ""

View File

@@ -212,6 +212,10 @@ msgstr ""
msgid "%s: %s: invalid variable name. See `help %s`"
msgstr ""
#, c-format
msgid "%s: %s: option cannot be used with a non-option argument"
msgstr ""
#, c-format
msgid "%s: %s: option does not take an argument"
msgstr ""

View File

@@ -236,6 +236,10 @@ msgstr "%s: %s: 无效的子命令"
msgid "%s: %s: invalid variable name. See `help %s`"
msgstr "%s: %s: 无效的变量名。参见 `help %s`"
#, c-format
msgid "%s: %s: option cannot be used with a non-option argument"
msgstr ""
#, c-format
msgid "%s: %s: option does not take an argument"
msgstr "%s: %s: 选项不接受参数"

View File

@@ -209,6 +209,10 @@ msgstr "%s%s無效的子命令"
msgid "%s: %s: invalid variable name. See `help %s`"
msgstr "%s%s無效的變數名稱。參見「help %s」"
#, c-format
msgid "%s: %s: option cannot be used with a non-option argument"
msgstr ""
#, c-format
msgid "%s: %s: option does not take an argument"
msgstr "%s%s選項不需要引數"

View File

@@ -4,9 +4,7 @@
use crate::common::bytes2wcstring;
use crate::screen::{is_dumb, only_grayscale};
use crate::terminal::Outputter;
use crate::text_face::{
self, PrintColorsArgs, SpecifiedTextFace, TextFace, TextStyling, parse_text_face_and_options,
};
use crate::text_face::{self, PrintColorsArgs, TextFace, TextStyling, parse_text_face_and_options};
use fish_color::Color;
fn print_colors(
@@ -63,73 +61,88 @@ pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
use text_face::ParseError::*;
use text_face::ParsedArgs::*;
let (specified_face, with_reset) =
match parse_text_face_and_options(argv, /*is_builtin=*/ true) {
Ok(SetFace(face)) => (face, false),
Ok(ResetFace) => (SpecifiedTextFace::default(), true),
Ok(PrintColors(PrintColorsArgs {
fg_args,
bg,
underline_color,
style,
})) => {
print_colors(streams, fg_args, style, bg, underline_color);
return Ok(SUCCESS);
}
Ok(PrintHelp) => {
builtin_print_help(parser, streams, argv[0]);
return Ok(SUCCESS);
}
Err(MissingOptArg) => {
// Either "--background" or "--underline-color" are missing an argument.
// Don't print an error, for consistency with "set_color".
// In future we change both to actually print an error.
return Err(STATUS_INVALID_ARGS);
}
Err(UnexpectedOptArg(option_index)) => {
builtin_unexpected_argument(
parser,
streams,
L!("set_color"),
argv[option_index],
true, /* print_hints */
);
return Err(STATUS_INVALID_ARGS);
}
Err(InvalidOptArg(name, value)) => {
streams.err.appendln(&wgettext_fmt!(
"%s: %s: invalid option argument: %s",
argv[0],
name,
value
));
return Err(STATUS_INVALID_ARGS);
}
Err(UnknownColor(arg)) => {
streams
.err
.appendln(&wgettext_fmt!("%s: Unknown color '%s'", argv[0], arg));
return Err(STATUS_INVALID_ARGS);
}
Err(UnknownUnderlineStyle(arg)) => {
streams.err.appendln(&wgettext_fmt!(
"%s: invalid underline style: %s",
argv[0],
arg
));
return Err(STATUS_INVALID_ARGS);
}
Err(UnknownOption(unknown_option_index)) => {
builtin_unknown_option(
parser,
streams,
L!("set_color"),
argv[unknown_option_index],
true, /* print_hints */
);
return Err(STATUS_INVALID_ARGS);
}
};
let specified_face = match parse_text_face_and_options(argv, /*is_builtin=*/ true) {
Ok(SetFace(face)) => face,
Ok(PrintColors(PrintColorsArgs {
fg_args,
bg,
underline_color,
style,
})) => {
print_colors(streams, fg_args, style, bg, underline_color);
return Ok(SUCCESS);
}
Ok(PrintHelp) => {
builtin_print_help(parser, streams, argv[0]);
return Ok(SUCCESS);
}
Err(MissingOptArg) => {
// Either "--background" or "--underline-color" are missing an argument.
// Don't print an error, for consistency with "set_color".
// In future we change both to actually print an error.
return Err(STATUS_INVALID_ARGS);
}
Err(UnexpectedOptArg(option_index)) => {
builtin_unexpected_argument(
parser,
streams,
L!("set_color"),
argv[option_index],
true, /* print_hints */
);
return Err(STATUS_INVALID_ARGS);
}
Err(InvalidOptArg(name, value)) => {
streams.err.appendln(&wgettext_fmt!(
"%s: %s: invalid option argument: %s",
argv[0],
name,
value
));
return Err(STATUS_INVALID_ARGS);
}
Err(UnknownColor(arg)) => {
streams
.err
.appendln(&wgettext_fmt!("%s: Unknown color '%s'", argv[0], arg));
return Err(STATUS_INVALID_ARGS);
}
Err(UnknownUnderlineStyle(arg)) => {
streams.err.appendln(&wgettext_fmt!(
"%s: invalid underline style: %s",
argv[0],
arg
));
return Err(STATUS_INVALID_ARGS);
}
Err(UnknownOption(unknown_option_index)) => {
builtin_unknown_option(
parser,
streams,
L!("set_color"),
argv[unknown_option_index],
true, /* print_hints */
);
return Err(STATUS_INVALID_ARGS);
}
Err(InvalidFgArgCombination) => {
streams.err.appendln(&wgettext_fmt!(
"%s: %s: option cannot be used with a non-option argument",
argv[0],
"--foreground",
));
return Err(STATUS_INVALID_ARGS);
}
Err(InvalidFgPrintColorCombination) => {
streams.err.appendln(&wgettext_fmt!(
BUILTIN_ERR_COMBO2_EXCLUSIVE,
argv[0],
"--foreground",
"--print-colors",
));
return Err(STATUS_INVALID_ARGS);
}
};
let mut outp = Outputter::new_buffering_no_assume_normal();
@@ -142,7 +155,7 @@ pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
specified_face.underline_color.unwrap_or(Color::None),
specified_face.style,
),
with_reset,
specified_face.reset,
);
if specified_face.fg.is_some() && outp.contents().is_empty() {

View File

@@ -1911,8 +1911,11 @@ fn test_parse_text_face_for_highlight_fully_specified() {
);
};
assert_all_set(vec![L!("--reset").into()]);
assert_all_set(vec![L!("normal").into()]);
assert_all_set(vec![L!("green").into()]);
assert_all_set(vec![L!("--foreground=normal").into()]);
assert_all_set(vec![L!("--foreground=green").into()]);
assert_all_set(vec![L!("--background=normal").into()]);
assert_all_set(vec![L!("--background=green").into()]);
assert_all_set(vec![L!("--underline-color=normal").into()]);

View File

@@ -60,6 +60,7 @@ pub(crate) enum SgrTerminalCommand {
// Colors
SelectPaletteColor(Paintable, u8),
SelectRgbColor(Paintable, Color24),
DefaultForegroundColor,
DefaultBackgroundColor,
DefaultUnderlineColor,
}
@@ -376,9 +377,10 @@ fn set_text_face_internal(
let style = face.style;
use SgrTerminalCommand::{
DefaultBackgroundColor, DefaultUnderlineColor, EnterBoldMode, EnterDimMode,
EnterItalicsMode, EnterReverseMode, EnterStrikethroughMode, EnterUnderlineMode,
ExitItalicsMode, ExitReverseMode, ExitStrikethroughMode, ExitUnderlineMode,
DefaultBackgroundColor, DefaultForegroundColor, DefaultUnderlineColor, EnterBoldMode,
EnterDimMode, EnterItalicsMode, EnterReverseMode, EnterStrikethroughMode,
EnterUnderlineMode, ExitItalicsMode, ExitReverseMode, ExitStrikethroughMode,
ExitUnderlineMode,
};
let mut style_writer = self.style_writer();
@@ -411,12 +413,12 @@ fn set_text_face_internal(
if !fg.is_none() && fg != style_writer.last().fg {
if fg.is_normal() {
style_writer.reset_text_face();
style_writer.write_command(DefaultForegroundColor);
} else {
assert!(!fg.is_special());
style_writer.write_color(Paintable::Foreground, fg);
style_writer.last().fg = fg;
}
style_writer.last().fg = fg;
}
if !bg.is_none() && bg != style_writer.last().bg {
@@ -680,6 +682,7 @@ pub(crate) fn write_command(&mut self, cmd: SgrTerminalCommand) -> bool {
ExitUnderlineMode => self.write_param_str(1, b"24"),
SelectPaletteColor(paintable, idx) => self.write_palette_color(paintable, idx),
SelectRgbColor(paintable, rgb) => self.write_rgb_color(paintable, rgb),
DefaultForegroundColor => self.write_param_str(1, b"39"),
DefaultBackgroundColor => self.write_param_str(1, b"49"),
DefaultUnderlineColor => self.write_param_str(1, b"59"),
}

View File

@@ -191,6 +191,7 @@ pub(crate) struct SpecifiedTextFace {
pub(crate) bg: Option<Color>,
pub(crate) underline_color: Option<Color>,
pub(crate) style: TextStyling,
pub(crate) reset: bool,
}
impl Default for SpecifiedTextFace {
@@ -200,6 +201,7 @@ fn default() -> Self {
bg: Default::default(),
underline_color: Default::default(),
style: TextStyling::unknown(),
reset: false,
}
}
}
@@ -213,7 +215,7 @@ pub(crate) fn parse_text_face(arguments: &[WString]) -> SpecifiedTextFace {
match parse_text_face_and_options(&mut argv, /*is_builtin=*/ false) {
Ok(SetFace(specified_face)) => specified_face,
Err(_) => Default::default(),
Ok(ResetFace) | Ok(PrintColors(_)) | Ok(PrintHelp) => unreachable!(),
Ok(PrintColors(_)) | Ok(PrintHelp) => unreachable!(),
}
}
@@ -226,7 +228,6 @@ pub(crate) struct PrintColorsArgs<'argarray, 'args> {
pub(crate) enum ParsedArgs<'argarray, 'args> {
SetFace(SpecifiedTextFace),
ResetFace,
PrintHelp,
PrintColors(PrintColorsArgs<'argarray, 'args>),
}
@@ -238,6 +239,8 @@ pub(crate) enum ParseError<'args> {
UnknownColor(&'args wstr),
UnknownUnderlineStyle(&'args wstr),
UnknownOption(usize),
InvalidFgArgCombination,
InvalidFgPrintColorCombination,
}
fn parse_resettable_style<'a>(w: &WGetopter<'_, 'a, '_>) -> Result<ResettableStyle, &'a wstr> {
@@ -258,9 +261,10 @@ pub(crate) fn parse_text_face_and_options<'argarray, 'args>(
is_builtin: bool,
) -> Result<ParsedArgs<'argarray, 'args>, ParseError<'args>> {
let builtin_extra_args = if is_builtin { 0 } else { "hc".len() };
let short_options = L!("b:oi::dr::s::u::ch");
let short_options = L!("f:b:oi::dr::s::u::ch");
let short_options = &short_options[..short_options.len() - builtin_extra_args];
let long_options: &[WOption] = &[
wopt(L!("foreground"), ArgType::RequiredArgument, 'f'),
wopt(L!("background"), ArgType::RequiredArgument, 'b'),
wopt(L!("underline-color"), ArgType::RequiredArgument, '\x02'),
wopt(L!("bold"), ArgType::NoArgument, 'o'),
@@ -270,6 +274,7 @@ pub(crate) fn parse_text_face_and_options<'argarray, 'args>(
wopt(L!("reverse"), ArgType::OptionalArgument, 'r'),
wopt(L!("strikethrough"), ArgType::OptionalArgument, 's'),
wopt(L!("theme"), ArgType::RequiredArgument, '\x01'),
wopt(L!("reset"), ArgType::NoArgument, '\x03'),
wopt(L!("help"), ArgType::NoArgument, 'h'),
wopt(L!("print-colors"), ArgType::NoArgument, 'c'),
];
@@ -289,14 +294,21 @@ pub(crate) fn parse_text_face_and_options<'argarray, 'args>(
}
};
let mut fg_colors = vec![];
let mut bg_colors = vec![];
let mut underline_colors = vec![];
let mut style = TextStyling::unknown();
let mut print_color_mode = false;
let mut reset = false;
let mut w = WGetopter::new(short_options, long_options, argv);
while let Some(c) = w.next_opt() {
match c {
'f' => {
if let Some(fg) = parse_color(w.woptarg.unwrap())? {
fg_colors.push(fg);
}
}
'b' => {
if let Some(bg) = parse_color(w.woptarg.unwrap())? {
bg_colors.push(bg);
@@ -308,6 +320,9 @@ pub(crate) fn parse_text_face_and_options<'argarray, 'args>(
underline_colors.push(underline_color);
}
}
'\x03' => {
reset = true;
}
'h' => {
assert!(is_builtin);
return Ok(PrintHelp);
@@ -362,6 +377,9 @@ pub(crate) fn parse_text_face_and_options<'argarray, 'args>(
}
let fg_args = &w.argv[w.wopt_index..];
if !fg_args.is_empty() && !fg_colors.is_empty() {
return Err(InvalidFgArgCombination);
}
let best_color =
|colors: Vec<Color>| terminal::best_color(colors.into_iter(), get_color_support());
@@ -370,6 +388,9 @@ pub(crate) fn parse_text_face_and_options<'argarray, 'args>(
let underline_color = best_color(underline_colors);
if print_color_mode {
if !fg_colors.is_empty() {
return Err(InvalidFgPrintColorCombination);
}
return Ok(PrintColors(PrintColorsArgs {
fg_args,
bg,
@@ -380,15 +401,21 @@ pub(crate) fn parse_text_face_and_options<'argarray, 'args>(
// Historical behavior: reset only applies if it's the first argument.
if is_builtin && fg_args.first().is_some_and(|fg| fg == "reset") {
return Ok(ResetFace);
return Ok(SetFace(SpecifiedTextFace {
reset: true,
..Default::default()
}));
}
let mut fg_colors = Vec::with_capacity(fg_args.len());
fg_colors.reserve(fg_args.len());
for fg in fg_args {
if is_builtin && fg == "reset" {
continue;
}
if let Some(fg) = parse_color(fg)? {
if fg == Color::Normal {
reset = true;
}
fg_colors.push(fg);
}
}
@@ -399,6 +426,7 @@ pub(crate) fn parse_text_face_and_options<'argarray, 'args>(
bg,
underline_color,
style,
reset,
}))
}
@@ -420,6 +448,7 @@ fn test_parse_text_face() {
bg: None,
underline_color: None,
style: TextStyling::unknown(),
reset: false
}
);
}

View File

@@ -6,11 +6,11 @@ end
# ==========
# Verify that `functions --details` works as expected when given too many args.
functions --details f1 f2
functions --details f1 f2
#CHECKERR: functions: --details: expected 1 arguments; got 2
# Verify that it still mentions "--details" even if it isn't the last option.
functions --details --verbose f1 f2
functions --details --verbose f1 f2
#CHECKERR: functions: --details: expected 1 arguments; got 2
# ==========
@@ -210,7 +210,8 @@ functions --description ""
# CHECKERR: ^
# CHECKERR: (Type 'help functions' for related documentation)
function foo --on-variable foo; end
function foo --on-variable foo
end
# This should print *everything*
functions --handlers-type "" | string match 'Event *'
# CHECK: Event signal
@@ -250,5 +251,5 @@ functions --no-details --color=never test_color_option
string escape (functions --no-details --color=always test_color_option)
# CHECK: function\ \e\[36mtest_color_option\e\[32m
# CHECK: \e\[m\ \ \ \ echo\ \e\[36mhello\e\[32m
# CHECK: \e\[mend\e\[32m\e\[m
# CHECK: \e\[39m\ \ \ \ echo\ \e\[36mhello\e\[32m
# CHECK: \e\[39mend\e\[32m\e\[39m

View File

@@ -550,7 +550,7 @@ echo 'PATH={$PATH[echo " "' | $fish_indent --ansi
fish_config theme choose "ayu Dark"
echo -n 'echo hello' | builtin fish_indent --ansi
echo end
# CHECK: {{\x1b\[38;2;57;186;230mecho\x1b\[38;2;179;177;173m hello\x1b\[38;2;242;150;104m\x1b\[m}}
# CHECK: {{\x1b\[38;2;57;186;230mecho\x1b\[38;2;179;177;173m hello\x1b\[38;2;242;150;104m\x1b\[39m}}
# CHECK: end
echo a\> | $fish_indent

View File

@@ -4,12 +4,33 @@ string escape (set_color normal)
# CHECK: \e\[m
string escape (set_color reset)
# CHECK: \e\[m
string escape (set_color --reset)
# CHECK: \e\[m
string escape (set_color red yellow)
# CHECK: \e\[31m
string escape (set_color red reset yellow)
# CHECK: \e\[31m
string escape (set_color -fred)
# CHECK: \e\[31m
string escape (set_color --foreground red)
# CHECK: \e\[31m
string escape (set_color --foreground normal)
# CHECK: \e\[39m
string escape (set_color --foreground reset)
# CHECKERR: set_color: Unknown color 'reset'
string escape (set_color --foreground=f00 --foreground=green --foreground=00f)
# CHECK: \e\[38\;2\;255\;0\;0m
fish_term256=0 string escape (set_color --foreground=f00 --foreground=green --foreground=00f)
# CHECK: \e\[32m
string escape (set_color --foreground red red)
# CHECKERR: set_color: --foreground: option cannot be used with a non-option argument
string escape (set_color --foreground red --print-colors)
# CHECKERR: set_color: --foreground --print-colors: options cannot be used together
string escape (set_color --background=reset)
# CHECKERR: set_color: Unknown color 'reset'
@@ -139,5 +160,5 @@ string escape (set_color --underline=dashed)
string escape (set_color --underline=off)
# CHECK: \e\[24m
string escape (set_color f00 --background=00f --underline-color=0f0 --bold --dim --italics --reverse --strikethrough --underline=curly)
# CHECK: \e\[38\;2\;255\;0\;0\;48\;2\;0\;0\;255\;58:2::0:255:0\;1\;4:3\;2\;3\;7m\e\[9m
string escape (set_color --reset f00 --background=00f --underline-color=0f0 --bold --dim --italics --reverse --strikethrough --underline=curly)
# CHECK: \e\[\;38\;2\;255\;0\;0\;48\;2\;0\;0\;255\;58:2::0:255:0\;1\;4:3\;2\;3m\e\[7\;9m