From ce631fd2fb1f5b63f5f0f1b4041a30dfad823d22 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Mon, 14 Apr 2025 15:36:50 +0200 Subject: [PATCH] Colored underlines in set_color and fish_color_* Add a new underline-color option to set_color (instead of adding an optional color argument to --underline); this allows to set the underline color independently of underline style (line, curly, etc.). I don't think this flexibility is very important but this approach is probably the least hacky. Note that there are two variants: 1. \e[58:5:1m 2. \e[58;5;1m Variant 1 breaks: breakage from colon-variant for colored underlines - cool-retro-term makes text blink - GNU screen (goes into bold mode) - terminology (goes into bold mode) Variant 2 would break: - mintty (Cygwin terminal) -- it enables bold font instead. - Windows Terminal (where it paints the foreground yellow) - JetBrains terminals echo the colons instead of consuming them - putty - GNU screen (goes into bold mode) - st - urxvt - xterm - etc. So choose variant 1. Closes #11388 Closes #7619 --- CHANGELOG.rst | 1 + doc_src/cmds/set_color.rst | 3 + doc_src/interactive.rst | 9 ++- doc_src/terminal-compatibility.rst | 12 +++ share/completions/set.fish | 1 + share/completions/set_color.fish | 1 + share/tools/web_config/webconfig.py | 57 ++++++++------- src/builtins/set_color.rs | 39 ++++++++-- src/highlight/highlight.rs | 13 +++- src/terminal.rs | 109 +++++++++++++++++----------- src/text_face.rs | 29 +++++++- tests/checks/set_color.fish | 5 ++ 12 files changed, 196 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3b92a5ff8..d72a304da 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -66,6 +66,7 @@ Completions Improved terminal support ^^^^^^^^^^^^^^^^^^^^^^^^^ - Support for curly underlines in `fish_color_*` variables and :doc:`set_color ` (:issue:`10957`). +- Underlines can now be colored independent of text (:issue:`7619`). - New documentation page `Terminal Compatibility `_ (also accessible via ``man fish-terminal-compatibility``) lists required and optional terminal control sequences used by fish. Other improvements diff --git a/doc_src/cmds/set_color.rst b/doc_src/cmds/set_color.rst index 685af432e..578ff897b 100644 --- a/doc_src/cmds/set_color.rst +++ b/doc_src/cmds/set_color.rst @@ -37,6 +37,9 @@ The following options are available: **-b** or **--background** *COLOR* Sets the background color. +**--underline-color** *COLOR* + Set the underline color. + **-c** or **--print-colors** Prints the given colors or a colored list of the 16 named colors. diff --git a/doc_src/interactive.rst b/doc_src/interactive.rst index 3cc05593b..3b4e2002a 100644 --- a/doc_src/interactive.rst +++ b/doc_src/interactive.rst @@ -90,12 +90,15 @@ Syntax highlighting variables ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The colors used by fish for syntax highlighting can be configured by changing the values of various variables. The value of these variables can be one of the colors accepted by the :doc:`set_color ` command. -The modifier switches accepted by ``set_color`` like +Options accepted by ``set_color`` like +``--background=``, ``--bold``, ``--dim``, ``--italics``, -``--reverse`` and -``--underline`` are also accepted. +``--reverse``, +``--underline`` and +``--underline-color=`` +are also accepted. Example: to make errors highlighted and red, use:: diff --git a/doc_src/terminal-compatibility.rst b/doc_src/terminal-compatibility.rst index 9de7268db..239bb21f9 100644 --- a/doc_src/terminal-compatibility.rst +++ b/doc_src/terminal-compatibility.rst @@ -144,6 +144,10 @@ Optional Commands - setab - Select background color Ps from the 256-color-palette. - + * - ``\e[58:5: Ps m`` (note: colons not semicolons) + - Su + - Select underline color Ps from the 256-color-palette. + - kitty * - ``\e[ Ps m`` - setaf setab @@ -161,6 +165,14 @@ Optional Commands - - Select background color from 24-bit RGB colors. - + * - ``\e[58:2:: Ps : Ps : Ps m`` (note: colons not semicolons) + - Su + - Select underline color from 24-bit RGB colors. + - kitty + * - ``\e[59m`` + - Su + - Reset underline color (follow foreground color). + - kitty * - ``\e[ Ps S`` - indn - Scroll forward Ps lines. diff --git a/share/completions/set.fish b/share/completions/set.fish index 2fb6066a8..68b0c6811 100644 --- a/share/completions/set.fish +++ b/share/completions/set.fish @@ -127,6 +127,7 @@ complete -c set -n '__fish_seen_argument -s e -l erase; and __fish_seen_argument # Color completions complete -c set -n '__fish_set_is_color true false' -x -a '(set_color --print-colors)' -d 'text color' complete -c set -n '__fish_set_is_color false true' -a '--background=(set_color --print-colors)' +complete -c set -n '__fish_set_is_color false true' -a '--underline-color=(set_color --print-colors)' complete -c set -n '__fish_set_is_color true false' -a --bold -x complete -c set -n '__fish_set_is_color true false' -a --dim -x complete -c set -n '__fish_set_is_color true false' -a --italics -x diff --git a/share/completions/set_color.fish b/share/completions/set_color.fish index 74a1e46b7..2fa24f148 100644 --- a/share/completions/set_color.fish +++ b/share/completions/set_color.fish @@ -1,5 +1,6 @@ complete -c set_color -x -d Color -a '(set_color --print-colors)' complete -c set_color -s b -l background -x -a '(set_color --print-colors)' -d "Change background color" +complete -c set_color -s b -l underline-color -x -a '(set_color --print-colors)' -d "Change underline color" complete -c set_color -s o -l bold -d 'Make font bold' complete -c set_color -s i -l italics -d Italicise complete -c set_color -s d -l dim -d 'Dim text' diff --git a/share/tools/web_config/webconfig.py b/share/tools/web_config/webconfig.py index a2f8e8742..14aa7f76b 100755 --- a/share/tools/web_config/webconfig.py +++ b/share/tools/web_config/webconfig.py @@ -227,6 +227,7 @@ def parse_color(color_str): comps = color_str.split(" ") color = "" background_color = "" + underline_color = "" bold = False underline = False italics = False @@ -251,37 +252,41 @@ def parse_color(color_str): dim = True elif comp == "--reverse" or comp == "-r": reverse = True - elif comp.startswith("--background"): - # Background color - c = comp[len("--background=") :] - parsed_c = parse_one_color(c) - # We prefer the unparsed version - if it says "brgreen", we use brgreen, - # instead of 00ff00 - if better_color(background_color, parsed_c) == parsed_c: - background_color = c - elif comp.startswith("-b"): - # Background color in short. - if comp == "-b": - if i + 1 == len(comps): - c = "" - else: - c = comps[i + 1] - i += 1 - else: - c = comp[len("-b"):] - parsed_c = parse_one_color(c) - if better_color(background_color, parsed_c) == parsed_c: - background_color = c else: - # Regular color - parsed_c = parse_one_color(comp) - if better_color(color, parsed_c) == parsed_c: - color = comp + def parse_opt(current_best: str, i: int, long_opt: str, short_opt: str | None) -> str: + if comp.startswith(long_opt): + c = comp[len(long_opt) :] + parsed_c = parse_one_color(c) + # We prefer the unparsed version - if it says "brgreen", we use brgreen, + # instead of 00ff00 + if better_color(current_best, parsed_c) == parsed_c: + return True, c, i + elif short_opt is not None and comp.startswith(short_opt): + if comp == short_opt: + if i + 1 == len(comps): + c = "" + else: + c = comps[i + 1] + i += 1 + else: + c = comp[len(short_opt):] + parsed_c = parse_one_color(c) + if better_color(current_best, parsed_c) == parsed_c: + return True, c, i + return False, current_best, i + is_bg, background_color, i = parse_opt(background_color, i, "--background", "-b") + is_ul, underline_color, i = parse_opt(underline_color, i, "--underline-color", None) + if not (is_bg or is_ul): + # Regular color + parsed_c = parse_one_color(comp) + if better_color(color, parsed_c) == parsed_c: + color = comp i += 1 return { "color": color, "background": background_color, + "underline-color": underline_color, "bold": bold, "underline": underline, "italics": italics, @@ -309,6 +314,8 @@ def unparse_color(col): ret += " --reverse" if col["background"]: ret += " --background=" + col["background"] + if col["underline-color"]: + ret += " --underline-color=" + col["underline-color"] return ret diff --git a/src/builtins/set_color.rs b/src/builtins/set_color.rs index f266e7225..61eecca99 100644 --- a/src/builtins/set_color.rs +++ b/src/builtins/set_color.rs @@ -3,13 +3,20 @@ use super::prelude::*; use crate::color::Color; use crate::common::str2wcstring; -use crate::terminal::{best_color, get_color_support, Outputter}; +use crate::terminal::TerminalCommand::DefaultUnderlineColor; +use crate::terminal::{best_color, get_color_support, Output, Outputter, Paintable}; use crate::text_face::{ parse_text_face_and_options, TextFace, TextFaceArgsAndOptions, TextFaceArgsAndOptionsResult, TextStyling, }; -fn print_colors(streams: &mut IoStreams, args: &[&wstr], style: TextStyling, bg: Option) { +fn print_colors( + streams: &mut IoStreams, + args: &[&wstr], + style: TextStyling, + bg: Option, + underline_color: Option, +) { let outp = &mut Outputter::new_buffering(); // Rebind args to named_colors if there are no args. @@ -24,7 +31,12 @@ fn print_colors(streams: &mut IoStreams, args: &[&wstr], style: TextStyling, bg: for color_name in args { if streams.out_is_terminal() { let fg = Color::from_wstr(color_name).unwrap_or(Color::None); - outp.set_text_face(TextFace::new(fg, bg.unwrap_or(Color::None), style)); + outp.set_text_face(TextFace::new( + fg, + bg.unwrap_or(Color::None), + underline_color.unwrap_or(Color::None), + style, + )); } outp.write_wstr(color_name); if streams.out_is_terminal() && bg.is_some() { @@ -53,6 +65,7 @@ pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) - let TextFaceArgsAndOptions { wopt_index, bgcolor, + underline_color, style, print_color_mode, } = match parse_text_face_and_options(argv, /*is_builtin=*/ true) { @@ -102,9 +115,14 @@ pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) - None => None, }; + let underline_color = match underline_color { + Some(s) => Some(parse_color(s)?), + None => None, + }; + if print_color_mode { let args = &argv[wopt_index..argc]; - print_colors(streams, args, style, bg); + print_colors(streams, args, style, bg, underline_color); return Ok(SUCCESS); } @@ -139,9 +157,9 @@ pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) - // - if fg and bg are equal, it makes one of them white // - if bg is not normal, it makes the foreground bold // The first one seems fine but the second one not really. - outp.set_text_face(TextFace::new(Color::None, Color::None, style)); + outp.set_text_face(TextFace::new(Color::None, Color::None, Color::None, style)); if let Some(fg) = fg { - if !outp.write_color(fg, true /* is_fg */) { + if !outp.write_color(Paintable::Foreground, fg) { // We need to do *something* or the lack of any output messes up // when the cartesian product here would make "foo" disappear: // $ echo (set_color foo)bar @@ -149,7 +167,14 @@ pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) - } } if let Some(bg) = bg { - outp.write_color(bg, false /* is_fg */); + outp.write_color(Paintable::Background, bg); + } + if let Some(underline_color) = underline_color { + if underline_color.is_normal() { + outp.write_command(DefaultUnderlineColor); + } else { + outp.write_color(Paintable::Underline, underline_color); + } } } diff --git a/src/highlight/highlight.rs b/src/highlight/highlight.rs index 14ee3b450..10d799775 100644 --- a/src/highlight/highlight.rs +++ b/src/highlight/highlight.rs @@ -183,10 +183,17 @@ pub(crate) fn resolve_spec_uncached( /// Return the internal color code representing the specified color. pub(crate) fn parse_text_face_for_highlight(var: &EnvVar) -> TextFace { let face = parse_text_face(var.as_list()); - let fg = face.fg.unwrap_or(Color::Normal); - let bg = face.bg.unwrap_or(Color::Normal); + let default = TextFace::default(); + let fg = face.fg.unwrap_or(default.fg); + let bg = face.bg.unwrap_or(default.bg); + let underline_color = face.underline_color.unwrap_or(default.underline_color); let style = face.style; - TextFace { fg, bg, style } + TextFace { + fg, + bg, + underline_color, + style, + } } fn command_is_valid( diff --git a/src/terminal.rs b/src/terminal.rs index 4ce1f3b4b..054fc9551 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -41,6 +41,13 @@ pub fn set_color_support(val: ColorSupport) { COLOR_SUPPORT.store(val.bits(), Ordering::Relaxed); } +#[derive(Clone, Copy)] +pub(crate) enum Paintable { + Foreground, + Background, + Underline, +} + #[derive(Clone)] pub(crate) enum TerminalCommand<'a> { // Text attributes @@ -61,8 +68,9 @@ pub(crate) enum TerminalCommand<'a> { ClearToEndOfScreen, // Colors - SelectPaletteColor(/*is_foreground=*/ bool, u8), - SelectRgbColor(/*is_foreground=*/ bool, Color24), + SelectPaletteColor(Paintable, u8), + SelectRgbColor(Paintable, Color24), + DefaultUnderlineColor, // Cursor Movement CursorUp, @@ -153,8 +161,9 @@ fn write(out: &mut impl Output, sequence: &'static [u8]) -> bool { ClearScreen => ti(self, b"\x1b[H\x1b[2J", |term| &term.clear_screen), ClearToEndOfLine => ti(self, b"\x1b[K", |term| &term.clr_eol), ClearToEndOfScreen => ti(self, b"\x1b[J", |term| &term.clr_eos), - SelectPaletteColor(is_foreground, idx) => palette_color(self, is_foreground, idx), - SelectRgbColor(is_foreground, rgb) => rgb_color(self, is_foreground, rgb), + SelectPaletteColor(paintable, idx) => palette_color(self, paintable, idx), + SelectRgbColor(paintable, rgb) => rgb_color(self, paintable, rgb), + DefaultUnderlineColor => write(self, b"\x1b[59m"), CursorUp => ti(self, b"\x1b[A", |term| &term.cursor_up), CursorDown => ti(self, b"\n", |term| &term.cursor_down), CursorLeft => ti(self, b"\x08", |term| &term.cursor_left), @@ -232,20 +241,22 @@ pub(crate) fn use_terminfo() -> bool { !future_feature_flags::test(FeatureFlag::ignore_terminfo) && TERM.lock().unwrap().is_some() } -fn palette_color(out: &mut impl Output, foreground: bool, mut idx: u8) -> bool { +fn palette_color(out: &mut impl Output, paintable: Paintable, mut idx: u8) -> bool { if only_grayscale() && !(Color::Named { idx }).is_grayscale() { return false; } if use_terminfo() { let term = crate::terminal::term(); - let Some(command) = (if foreground { - term.set_a_foreground + let Some(command) = (match paintable { + Paintable::Foreground => term + .set_a_foreground .as_ref() - .or(term.set_foreground.as_ref()) - } else { - term.set_a_background + .or(term.set_foreground.as_ref()), + Paintable::Background => term + .set_a_background .as_ref() - .or(term.set_background.as_ref()) + .or(term.set_background.as_ref()), + Paintable::Underline => None, }) else { return false; }; @@ -260,7 +271,14 @@ fn palette_color(out: &mut impl Output, foreground: bool, mut idx: u8) -> bool { idx -= 8; } } - let bg = if foreground { 0 } else { 10 }; + let bg = match paintable { + Paintable::Foreground => 0, + Paintable::Background => 10, + Paintable::Underline => { + write_to_output!(out, "\x1b[58:5:{}m", idx); + return true; + } + }; match idx { 0..=7 => write_to_output!(out, "\x1b[{}m", 30 + bg + idx), 8..=15 => write_to_output!(out, "\x1b[{}m", 90 + bg + (idx - 8)), @@ -279,17 +297,19 @@ fn term_supports_color_natively(term: &Term, c: u8) -> bool { } } -fn rgb_color(out: &mut impl Output, foreground: bool, rgb: Color24) -> bool { +fn rgb_color(out: &mut impl Output, paintable: Paintable, rgb: Color24) -> bool { // Foreground: ^[38;2;;;m // Background: ^[48;2;;;m - write_to_output!( - out, - "\x1b[{};2;{};{};{}m", - if foreground { 38 } else { 48 }, - rgb.r, - rgb.g, - rgb.b - ); + // Underline: ^[58:2::::m + let code = match paintable { + Paintable::Foreground => 38, + Paintable::Background => 48, + Paintable::Underline => { + write_to_output!(out, "\x1b[58:2::{}:{}:{}m", rgb.r, rgb.g, rgb.b); + return true; + } + }; + write_to_output!(out, "\x1b[{code};2;{};{};{}m", rgb.r, rgb.g, rgb.b); true } @@ -398,14 +418,6 @@ fn index_for_color(c: Color) -> u8 { c.to_term256_index() } -fn write_foreground_color(outp: &mut Outputter, idx: u8) -> bool { - outp.write_command(TerminalCommand::SelectPaletteColor(true, idx)) -} - -fn write_background_color(outp: &mut Outputter, idx: u8) -> bool { - outp.write_command(TerminalCommand::SelectPaletteColor(false, idx)) -} - pub struct Outputter { /// Storage for buffered contents. contents: Vec, @@ -447,16 +459,12 @@ fn maybe_flush(&mut self) { /// Unconditionally write the color string to the output. /// Exported for builtin_set_color's usage only. - pub fn write_color(&mut self, color: Color, is_fg: bool) -> bool { + pub(crate) fn write_color(&mut self, paintable: Paintable, color: Color) -> bool { let supports_term24bit = get_color_support().contains(ColorSupport::TERM_24BIT); if !supports_term24bit || !color.is_rgb() { // Indexed or non-24 bit color. let idx = index_for_color(color); - if is_fg { - return write_foreground_color(self, idx); - } else { - return write_background_color(self, idx); - }; + return self.write_command(TerminalCommand::SelectPaletteColor(paintable, idx)); } if only_grayscale() && color.is_grayscale() { @@ -464,7 +472,10 @@ pub fn write_color(&mut self, color: Color, is_fg: bool) -> bool { } // 24 bit! - self.write_command(TerminalCommand::SelectRgbColor(is_fg, color.to_color24())) + self.write_command(TerminalCommand::SelectRgbColor( + paintable, + color.to_color24(), + )) } /// Unconditionally resets colors and text style. @@ -494,14 +505,15 @@ pub(crate) fn reset_text_face(&mut self) { pub(crate) fn set_text_face(&mut self, face: TextFace) { let mut fg = face.fg; let bg = face.bg; + let underline_color = face.underline_color; let style = face.style; let mut bg_set = false; let mut last_bg_set = false; use TerminalCommand::{ - EnterBoldMode, EnterCurlyUnderlineMode, EnterDimMode, EnterItalicsMode, - EnterReverseMode, EnterStandoutMode, EnterUnderlineMode, ExitAttributeMode, - ExitItalicsMode, ExitUnderlineMode, + DefaultUnderlineColor, EnterBoldMode, EnterCurlyUnderlineMode, EnterDimMode, + EnterItalicsMode, EnterReverseMode, EnterStandoutMode, EnterUnderlineMode, + ExitAttributeMode, ExitItalicsMode, ExitUnderlineMode, }; // Removes all styles that are individually resettable. @@ -549,10 +561,11 @@ pub(crate) fn set_text_face(&mut self, face: TextFace) { self.write_command(ExitAttributeMode); self.last.bg = Color::Normal; + self.last.underline_color = Color::Normal; self.last.style = TextStyling::default(); } else { assert!(!fg.is_special()); - self.write_color(fg, true /* foreground */); + self.write_color(Paintable::Foreground, fg); } self.last.fg = fg; } @@ -561,16 +574,28 @@ pub(crate) fn set_text_face(&mut self, face: TextFace) { if bg.is_normal() { self.write_command(ExitAttributeMode); if !self.last.fg.is_normal() { - self.write_color(self.last.fg, true /* foreground */); + self.write_color(Paintable::Foreground, self.last.fg); + } + if !self.last.underline_color.is_normal() && !self.last.underline_color.is_none() { + self.write_color(Paintable::Underline, self.last.underline_color); } self.last.style = TextStyling::default(); } else { assert!(!bg.is_special()); - self.write_color(bg, false /* not foreground */); + self.write_color(Paintable::Background, bg); } self.last.bg = bg; } + if !underline_color.is_none() && underline_color != self.last.underline_color { + if underline_color.is_normal() { + self.write_command(DefaultUnderlineColor); + } else { + self.write_color(Paintable::Underline, underline_color); + } + self.last.underline_color = underline_color; + } + // Lastly, we set bold, underline, italics, dim, and reverse modes correctly. if style.is_bold() && !self.last.style.is_bold() diff --git a/src/text_face.rs b/src/text_face.rs index 9a65e5363..e4e15f31b 100644 --- a/src/text_face.rs +++ b/src/text_face.rs @@ -107,6 +107,7 @@ pub const fn is_reverse(self) -> bool { pub(crate) struct TextFace { pub(crate) fg: Color, pub(crate) bg: Color, + pub(crate) underline_color: Color, pub(crate) style: TextStyling, } @@ -115,18 +116,25 @@ pub const fn default() -> Self { Self { fg: Color::Normal, bg: Color::Normal, + underline_color: Color::None, style: TextStyling::default(), } } - pub fn new(fg: Color, bg: Color, style: TextStyling) -> Self { - Self { fg, bg, style } + pub fn new(fg: Color, bg: Color, underline_color: Color, style: TextStyling) -> Self { + Self { + fg, + bg, + underline_color, + style, + } } } pub(crate) struct SpecifiedTextFace { pub(crate) fg: Option, pub(crate) bg: Option, + pub(crate) underline_color: Option, pub(crate) style: TextStyling, } @@ -138,6 +146,7 @@ pub(crate) fn parse_text_face(arguments: &[WString]) -> SpecifiedTextFace { let TextFaceArgsAndOptions { wopt_index, bgcolor, + underline_color, style, print_color_mode, } = match parse_text_face_and_options(&mut argv, /*is_builtin=*/ false) { @@ -155,14 +164,21 @@ pub(crate) fn parse_text_face(arguments: &[WString]) -> SpecifiedTextFace { get_color_support(), ); let bg = bgcolor.and_then(Color::from_wstr); + let underline_color = underline_color.and_then(Color::from_wstr); assert!(fg.map_or(true, |fg| !fg.is_none())); assert!(bg.map_or(true, |bg| !bg.is_none())); - SpecifiedTextFace { fg, bg, style } + SpecifiedTextFace { + fg, + bg, + underline_color, + style, + } } pub(crate) struct TextFaceArgsAndOptions<'a> { pub(crate) wopt_index: usize, pub(crate) bgcolor: Option<&'a wstr>, + pub(crate) underline_color: Option<&'a wstr>, pub(crate) style: TextStyling, pub(crate) print_color_mode: bool, } @@ -184,6 +200,7 @@ pub(crate) fn parse_text_face_and_options<'a>( let short_options = &short_options[..short_options.len() - builtin_extra_args]; let long_options: &[WOption] = &[ wopt(L!("background"), ArgType::RequiredArgument, 'b'), + wopt(L!("underline-color"), ArgType::RequiredArgument, '\x02'), wopt(L!("bold"), ArgType::NoArgument, 'o'), wopt(L!("underline"), ArgType::OptionalArgument, 'u'), wopt(L!("italics"), ArgType::NoArgument, 'i'), @@ -195,6 +212,7 @@ pub(crate) fn parse_text_face_and_options<'a>( let long_options = &long_options[..long_options.len() - builtin_extra_args]; let mut bgcolor = None; + let mut underline_color = None; let mut style = TextStyling::default(); let mut print_color_mode = false; @@ -205,6 +223,10 @@ pub(crate) fn parse_text_face_and_options<'a>( assert!(w.woptarg.is_some(), "Arg should have been set"); bgcolor = w.woptarg; } + '\x02' => { + assert!(w.woptarg.is_some(), "Arg should have been set"); + underline_color = w.woptarg; + } 'h' => { if is_builtin { return TextFaceArgsAndOptionsResult::PrintHelp; @@ -241,6 +263,7 @@ pub(crate) fn parse_text_face_and_options<'a>( TextFaceArgsAndOptionsResult::Ok(TextFaceArgsAndOptions { wopt_index: w.wopt_index, bgcolor, + underline_color, style, print_color_mode, }) diff --git a/tests/checks/set_color.fish b/tests/checks/set_color.fish index ba6becd92..ccdf92988 100644 --- a/tests/checks/set_color.fish +++ b/tests/checks/set_color.fish @@ -28,3 +28,8 @@ set_color --underline=asdf # CHECKERR: set_color: invalid underline style: asdf set_color -ushort # CHECKERR: set_color: invalid underline style: short + +string escape (set_color --underline-color=red) +# CHECK: \e\[58:5:1m +string escape (set_color --underline-color=normal) +# CHECK: \e\[59m