mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-11 22:21:14 -03:00
New type for colors + text styling attributes
Our "Color" type also includes text styling (bold, italics, ...). This doesn't seem like the perfect way to model this, because the background text styles are always unused (this is noticeable with some fish_color_* variables). Introduce a type that represents attributes to apply to text, and nothing else. Prefer to pass this instead of two Color values with redundant text style is redundant.
This commit is contained in:
@@ -3,60 +3,11 @@
|
||||
use super::prelude::*;
|
||||
use crate::color::Color;
|
||||
use crate::common::str2wcstring;
|
||||
use crate::terminal::TerminalCommand::{
|
||||
EnterBoldMode, EnterDimMode, EnterItalicsMode, EnterReverseMode, EnterStandoutMode,
|
||||
EnterUnderlineMode, ExitAttributeMode,
|
||||
};
|
||||
use crate::terminal::TerminalCommand::ExitAttributeMode;
|
||||
use crate::terminal::{best_color, get_color_support, Output, Outputter};
|
||||
use crate::text_face::{TextFace, TextStyling};
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn print_modifiers(
|
||||
outp: &mut Outputter,
|
||||
bold: bool,
|
||||
underline: bool,
|
||||
italics: bool,
|
||||
dim: bool,
|
||||
reverse: bool,
|
||||
bg: Color,
|
||||
) {
|
||||
if bold {
|
||||
outp.write_command(EnterBoldMode);
|
||||
}
|
||||
|
||||
if underline {
|
||||
outp.write_command(EnterUnderlineMode);
|
||||
}
|
||||
|
||||
if italics {
|
||||
outp.write_command(EnterItalicsMode);
|
||||
}
|
||||
|
||||
if dim {
|
||||
outp.write_command(EnterDimMode);
|
||||
}
|
||||
|
||||
#[allow(clippy::collapsible_if)]
|
||||
if reverse {
|
||||
if !outp.write_command(EnterReverseMode) {
|
||||
outp.write_command(EnterStandoutMode);
|
||||
}
|
||||
}
|
||||
if !bg.is_none() && bg.is_normal() {
|
||||
outp.write_command(ExitAttributeMode);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn print_colors(
|
||||
streams: &mut IoStreams,
|
||||
args: &[&wstr],
|
||||
bold: bool,
|
||||
underline: bool,
|
||||
italics: bool,
|
||||
dim: bool,
|
||||
reverse: bool,
|
||||
bg: Color,
|
||||
) {
|
||||
fn print_colors(streams: &mut IoStreams, args: &[&wstr], style: TextStyling, bg: Color) {
|
||||
let outp = &mut Outputter::new_buffering();
|
||||
|
||||
// Rebind args to named_colors if there are no args.
|
||||
@@ -70,18 +21,14 @@ fn print_colors(
|
||||
|
||||
for color_name in args {
|
||||
if streams.out_is_terminal() {
|
||||
print_modifiers(outp, bold, underline, italics, dim, reverse, bg);
|
||||
let color = Color::from_wstr(color_name).unwrap_or(Color::NONE);
|
||||
outp.set_color(color, Color::NONE);
|
||||
if !bg.is_none() {
|
||||
outp.write_color(bg, false /* not is_fg */);
|
||||
}
|
||||
let fg = Color::from_wstr(color_name).unwrap_or(Color::None);
|
||||
outp.set_text_face(TextFace::new(fg, bg, style));
|
||||
}
|
||||
outp.write_wstr(color_name);
|
||||
if streams.out_is_terminal() && !bg.is_none() {
|
||||
// If we have a background, stop it after the color
|
||||
// or it goes to the end of the line and looks ugly.
|
||||
outp.write_command(ExitAttributeMode);
|
||||
outp.reset_text_face(false);
|
||||
}
|
||||
outp.writech('\n');
|
||||
} // conveniently, 'normal' is always the last color so we don't need to reset here
|
||||
@@ -114,11 +61,7 @@ pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
|
||||
}
|
||||
|
||||
let mut bgcolor = None;
|
||||
let mut bold = false;
|
||||
let mut underline = false;
|
||||
let mut italics = false;
|
||||
let mut dim = false;
|
||||
let mut reverse = false;
|
||||
let mut style = TextStyling::empty();
|
||||
let mut print = false;
|
||||
|
||||
let mut w = WGetopter::new(SHORT_OPTIONS, LONG_OPTIONS, argv);
|
||||
@@ -132,11 +75,11 @@ pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
|
||||
builtin_print_help(parser, streams, argv[0]);
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
'o' => bold = true,
|
||||
'i' => italics = true,
|
||||
'd' => dim = true,
|
||||
'r' => reverse = true,
|
||||
'u' => underline = true,
|
||||
'o' => style |= TextStyling::BOLD,
|
||||
'i' => style |= TextStyling::ITALICS,
|
||||
'd' => style |= TextStyling::DIM,
|
||||
'r' => style |= TextStyling::REVERSE,
|
||||
'u' => style |= TextStyling::UNDERLINE,
|
||||
'c' => print = true,
|
||||
':' => {
|
||||
// We don't error here because "-b" is the only option that requires an argument,
|
||||
@@ -159,7 +102,7 @@ pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
|
||||
// We want to reclaim argv so grab wopt_index now.
|
||||
let mut wopt_index = w.wopt_index;
|
||||
|
||||
let mut bg = Color::from_wstr(bgcolor.unwrap_or(L!(""))).unwrap_or(Color::NONE);
|
||||
let mut bg = bgcolor.and_then(Color::from_wstr).unwrap_or(Color::None);
|
||||
if bgcolor.is_some() && bg.is_none() {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
"%ls: Unknown color '%ls'\n",
|
||||
@@ -174,17 +117,17 @@ pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
|
||||
// for --print-colors. Because it's not interesting in terms of display,
|
||||
// just skip it.
|
||||
if bgcolor.is_some() && bg.is_special() {
|
||||
bg = Color::NONE;
|
||||
bg = Color::None;
|
||||
}
|
||||
let args = &argv[wopt_index..argc];
|
||||
print_colors(streams, args, bold, underline, italics, dim, reverse, bg);
|
||||
print_colors(streams, args, style, bg);
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
|
||||
// Remaining arguments are foreground color.
|
||||
let mut fgcolors = Vec::new();
|
||||
while wopt_index < argc {
|
||||
let fg = Color::from_wstr(argv[wopt_index]).unwrap_or(Color::NONE);
|
||||
let fg = Color::from_wstr(argv[wopt_index]).unwrap_or(Color::None);
|
||||
if fg.is_none() {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
"%ls: Unknown color '%ls'\n",
|
||||
@@ -203,7 +146,7 @@ pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
|
||||
assert!(fgcolors.is_empty() || !fg.is_none());
|
||||
|
||||
let outp = &mut Outputter::new_buffering();
|
||||
print_modifiers(outp, bold, underline, italics, dim, reverse, bg);
|
||||
outp.set_text_face(TextFace::new(Color::None, Color::None, style));
|
||||
if bgcolor.is_some() && bg.is_normal() {
|
||||
outp.write_command(ExitAttributeMode);
|
||||
}
|
||||
@@ -215,7 +158,7 @@ pub fn set_color(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -
|
||||
// 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
|
||||
outp.set_color(Color::RESET, Color::NONE);
|
||||
outp.reset_text_face(true);
|
||||
}
|
||||
}
|
||||
if bgcolor.is_some() && !bg.is_normal() && !bg.is_reset() {
|
||||
|
||||
171
src/color.rs
171
src/color.rs
@@ -1,4 +1,3 @@
|
||||
use bitflags::bitflags;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use crate::wchar::prelude::*;
|
||||
@@ -22,8 +21,9 @@ fn from_bits(bits: u32) -> Self {
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that represents a color.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Type {
|
||||
pub enum Color {
|
||||
// TODO: remove this? Users should probably use `Option<RgbColor>` instead
|
||||
None,
|
||||
Named { idx: u8 },
|
||||
@@ -32,55 +32,12 @@ pub enum Type {
|
||||
Reset,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct Flags: u8 {
|
||||
const DEFAULT = 0;
|
||||
const BOLD = 1<<0;
|
||||
const UNDERLINE = 1<<1;
|
||||
const ITALICS = 1<<2;
|
||||
const DIM = 1<<3;
|
||||
const REVERSE = 1<<4;
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that represents a color.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct Color {
|
||||
pub typ: Type,
|
||||
pub flags: Flags,
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// The color white
|
||||
pub const WHITE: Self = Self {
|
||||
typ: Type::Named { idx: 7 },
|
||||
flags: Flags::DEFAULT,
|
||||
};
|
||||
pub const WHITE: Self = Self::Named { idx: 7 };
|
||||
|
||||
/// The color black
|
||||
pub const BLACK: Self = Self {
|
||||
typ: Type::Named { idx: 0 },
|
||||
flags: Flags::DEFAULT,
|
||||
};
|
||||
|
||||
/// The reset special color.
|
||||
pub const RESET: Self = Self {
|
||||
typ: Type::Reset,
|
||||
flags: Flags::DEFAULT,
|
||||
};
|
||||
|
||||
/// The normal special color.
|
||||
pub const NORMAL: Self = Self {
|
||||
typ: Type::Normal,
|
||||
flags: Flags::DEFAULT,
|
||||
};
|
||||
|
||||
/// The none special color.
|
||||
pub const NONE: Self = Self {
|
||||
typ: Type::None,
|
||||
flags: Flags::DEFAULT,
|
||||
};
|
||||
pub const BLACK: Self = Self::Named { idx: 0 };
|
||||
|
||||
/// Parse a color from a string.
|
||||
pub fn from_wstr(s: &wstr) -> Option<Self> {
|
||||
@@ -91,35 +48,32 @@ pub fn from_wstr(s: &wstr) -> Option<Self> {
|
||||
|
||||
/// Create an RGB color.
|
||||
pub fn from_rgb(r: u8, g: u8, b: u8) -> Self {
|
||||
Self {
|
||||
typ: Type::Rgb(Color24 { r, g, b }),
|
||||
flags: Flags::DEFAULT,
|
||||
}
|
||||
Self::Rgb(Color24 { r, g, b })
|
||||
}
|
||||
|
||||
/// Returns whether the color is the normal special color.
|
||||
pub const fn is_normal(self) -> bool {
|
||||
matches!(self.typ, Type::Normal)
|
||||
matches!(self, Self::Normal)
|
||||
}
|
||||
|
||||
/// Returns whether the color is the reset special color.
|
||||
pub const fn is_reset(self) -> bool {
|
||||
matches!(self.typ, Type::Reset)
|
||||
matches!(self, Self::Reset)
|
||||
}
|
||||
|
||||
/// Returns whether the color is the none special color.
|
||||
pub const fn is_none(self) -> bool {
|
||||
matches!(self.typ, Type::None)
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
|
||||
/// Returns whether the color is a named color (like "magenta").
|
||||
pub const fn is_named(self) -> bool {
|
||||
matches!(self.typ, Type::Named { .. })
|
||||
matches!(self, Self::Named { .. })
|
||||
}
|
||||
|
||||
/// Returns whether the color is specified via RGB components.
|
||||
pub const fn is_rgb(self) -> bool {
|
||||
matches!(self.typ, Type::Rgb(_))
|
||||
matches!(self, Self::Rgb(_))
|
||||
}
|
||||
|
||||
/// Returns whether the color is special, that is, not rgb or named.
|
||||
@@ -127,73 +81,23 @@ pub const fn is_special(self) -> bool {
|
||||
!self.is_named() && !self.is_rgb()
|
||||
}
|
||||
|
||||
/// Returns whether the color is bold.
|
||||
pub const fn is_bold(self) -> bool {
|
||||
self.flags.contains(Flags::BOLD)
|
||||
}
|
||||
|
||||
/// Set whether the color is bold.
|
||||
pub fn set_bold(&mut self, bold: bool) {
|
||||
self.flags.set(Flags::BOLD, bold)
|
||||
}
|
||||
|
||||
/// Returns whether the color is underlined.
|
||||
pub const fn is_underline(self) -> bool {
|
||||
self.flags.contains(Flags::UNDERLINE)
|
||||
}
|
||||
|
||||
/// Set whether the color is underline.
|
||||
pub fn set_underline(&mut self, underline: bool) {
|
||||
self.flags.set(Flags::UNDERLINE, underline)
|
||||
}
|
||||
|
||||
/// Returns whether the color is italics.
|
||||
pub const fn is_italics(self) -> bool {
|
||||
self.flags.contains(Flags::ITALICS)
|
||||
}
|
||||
|
||||
/// Set whether the color is italics.
|
||||
pub fn set_italics(&mut self, italics: bool) {
|
||||
self.flags.set(Flags::ITALICS, italics)
|
||||
}
|
||||
|
||||
/// Returns whether the color is dim.
|
||||
pub const fn is_dim(self) -> bool {
|
||||
self.flags.contains(Flags::DIM)
|
||||
}
|
||||
|
||||
/// Set whether the color is dim.
|
||||
pub fn set_dim(&mut self, dim: bool) {
|
||||
self.flags.set(Flags::DIM, dim)
|
||||
}
|
||||
|
||||
/// Returns whether the color is reverse.
|
||||
pub const fn is_reverse(self) -> bool {
|
||||
self.flags.contains(Flags::REVERSE)
|
||||
}
|
||||
|
||||
/// Set whether the color is reverse.
|
||||
pub fn set_reverse(&mut self, reverse: bool) {
|
||||
self.flags.set(Flags::REVERSE, reverse)
|
||||
}
|
||||
|
||||
pub fn is_grayscale(&self) -> bool {
|
||||
match self.typ {
|
||||
Type::None => true,
|
||||
Type::Named { idx } => [0, 7, 8, 15, 16].contains(&idx) || (232..=255).contains(&idx),
|
||||
Type::Rgb(rgb) => rgb.r == rgb.g && rgb.r == rgb.b,
|
||||
Type::Normal => true,
|
||||
Type::Reset => true,
|
||||
match self {
|
||||
Self::None => true,
|
||||
Self::Named { idx } => [0, 7, 8, 15, 16].contains(idx) || (232..=255).contains(idx),
|
||||
Self::Rgb(rgb) => rgb.r == rgb.g && rgb.r == rgb.b,
|
||||
Self::Normal => true,
|
||||
Self::Reset => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the name index for the given color. Requires that the color be named or RGB.
|
||||
pub fn to_name_index(self) -> u8 {
|
||||
// TODO: This should look for the nearest color.
|
||||
match self.typ {
|
||||
Type::Named { idx } => idx,
|
||||
Type::Rgb(c) => term16_color_for_rgb(c),
|
||||
Type::None | Type::Normal | Type::Reset => {
|
||||
match self {
|
||||
Self::Named { idx } => idx,
|
||||
Self::Rgb(c) => term16_color_for_rgb(c),
|
||||
Self::None | Self::Normal | Self::Reset => {
|
||||
panic!("to_name_index() called on Color that's not named or RGB")
|
||||
}
|
||||
}
|
||||
@@ -201,7 +105,7 @@ pub fn to_name_index(self) -> u8 {
|
||||
|
||||
/// Returns the term256 index for the given color. Requires that the color be RGB.
|
||||
pub fn to_term256_index(self) -> u8 {
|
||||
let Type::Rgb(c) = self.typ else {
|
||||
let Self::Rgb(c) = self else {
|
||||
panic!("Tried to get term256 index of non-RGB color");
|
||||
};
|
||||
|
||||
@@ -210,7 +114,7 @@ pub fn to_term256_index(self) -> u8 {
|
||||
|
||||
/// Returns the 24 bit color for the given color. Requires that the color be RGB.
|
||||
pub const fn to_color24(self) -> Color24 {
|
||||
let Type::Rgb(c) = self.typ else {
|
||||
let Self::Rgb(c) = self else {
|
||||
panic!("Tried to get color24 of non-RGB color");
|
||||
};
|
||||
|
||||
@@ -238,20 +142,13 @@ pub fn named_color_names() -> Vec<&'static wstr> {
|
||||
|
||||
/// Try parsing a special color name like "normal".
|
||||
fn try_parse_special(special: &wstr) -> Option<Self> {
|
||||
// TODO: this is a very hot function, may need optimization by e.g. comparing length first,
|
||||
// depending on how well inlining of `simple_icase_compare` works
|
||||
let typ = if simple_icase_compare(special, L!("normal")) == Ordering::Equal {
|
||||
Type::Normal
|
||||
if simple_icase_compare(special, L!("normal")) == Ordering::Equal {
|
||||
Some(Self::Normal)
|
||||
} else if simple_icase_compare(special, L!("reset")) == Ordering::Equal {
|
||||
Type::Reset
|
||||
Some(Self::Reset)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(Self {
|
||||
typ,
|
||||
flags: Flags::default(),
|
||||
})
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Try parsing an rgb color like "#F0A030".
|
||||
@@ -302,11 +199,8 @@ fn try_parse_named(name: &wstr) -> Option<Self> {
|
||||
.binary_search_by(|c| simple_icase_compare(c.name, name))
|
||||
.ok()?;
|
||||
|
||||
Some(Self {
|
||||
typ: Type::Named {
|
||||
idx: NAMED_COLORS[i].idx,
|
||||
},
|
||||
flags: Flags::default(),
|
||||
Some(Self::Named {
|
||||
idx: NAMED_COLORS[i].idx,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -436,7 +330,7 @@ fn term256_color_for_rgb(color: Color24) -> u8 {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::color::{Color, Color24, Flags, Type};
|
||||
use crate::color::{Color, Color24};
|
||||
use crate::wchar::prelude::*;
|
||||
|
||||
#[test]
|
||||
@@ -466,10 +360,7 @@ fn parse_rgb() {
|
||||
#[test]
|
||||
fn test_term16_color_for_rgb() {
|
||||
for c in 0..=u8::MAX {
|
||||
let color = Color {
|
||||
typ: Type::Rgb(Color24 { r: c, g: c, b: c }),
|
||||
flags: Flags::DEFAULT,
|
||||
};
|
||||
let color = Color::Rgb(Color24 { r: c, g: c, b: c });
|
||||
let _ = color.to_name_index();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
valid_var_name, valid_var_name_char, ASCII_MAX, EXPAND_RESERVED_BASE, EXPAND_RESERVED_END,
|
||||
};
|
||||
use crate::complete::complete_wrap_map;
|
||||
use crate::env::Environment;
|
||||
use crate::env::{EnvVar, Environment};
|
||||
use crate::expand::{
|
||||
expand_one, expand_to_command_and_args, ExpandFlags, ExpandResultCode, PROCESS_EXPAND_SELF_STR,
|
||||
};
|
||||
@@ -27,7 +27,8 @@
|
||||
parse_util_locate_cmdsubst_range, parse_util_slice_length, MaybeParentheses,
|
||||
};
|
||||
use crate::path::{path_as_implicit_cd, path_get_cdpath, path_get_path, paths_are_same_file};
|
||||
use crate::terminal::{parse_color, Outputter};
|
||||
use crate::terminal::{parse_text_face, Outputter};
|
||||
use crate::text_face::{TextFace, TextStyling};
|
||||
use crate::threads::assert_is_background_thread;
|
||||
use crate::tokenizer::{variable_assignment_equals_pos, PipeOrRedir};
|
||||
use crate::wchar::{wstr, WString, L};
|
||||
@@ -71,12 +72,14 @@ pub fn colorize(text: &wstr, colors: &[HighlightSpec], vars: &dyn Environment) -
|
||||
for (i, c) in text.chars().enumerate() {
|
||||
let color = colors[i];
|
||||
if color != last_color {
|
||||
outp.set_color(rv.resolve_spec(&color, false, vars), Color::NORMAL);
|
||||
let mut face = rv.resolve_spec(&color, vars);
|
||||
face.bg = Color::Normal; // Historical behavior.
|
||||
outp.set_text_face(face);
|
||||
last_color = color;
|
||||
}
|
||||
outp.writech(c);
|
||||
}
|
||||
outp.set_color(Color::NORMAL, Color::NORMAL);
|
||||
outp.set_text_face(TextFace::default());
|
||||
outp.contents().to_owned()
|
||||
}
|
||||
|
||||
@@ -107,8 +110,7 @@ pub fn highlight_shell(
|
||||
/// one screen redraw.
|
||||
#[derive(Default)]
|
||||
pub struct HighlightColorResolver {
|
||||
fg_cache: HashMap<HighlightSpec, Color>,
|
||||
bg_cache: HashMap<HighlightSpec, Color>,
|
||||
cache: HashMap<HighlightSpec, TextFace>,
|
||||
}
|
||||
|
||||
/// highlight_color_resolver_t resolves highlight specs (like "a command") to actual RGB colors.
|
||||
@@ -119,82 +121,62 @@ pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
/// Return an RGB color for a given highlight spec.
|
||||
pub fn resolve_spec(
|
||||
pub(crate) fn resolve_spec(
|
||||
&mut self,
|
||||
highlight: &HighlightSpec,
|
||||
is_background: bool,
|
||||
vars: &dyn Environment,
|
||||
) -> Color {
|
||||
let cache = if is_background {
|
||||
&mut self.bg_cache
|
||||
} else {
|
||||
&mut self.fg_cache
|
||||
};
|
||||
match cache.entry(*highlight) {
|
||||
) -> TextFace {
|
||||
match self.cache.entry(*highlight) {
|
||||
Entry::Occupied(e) => *e.get(),
|
||||
Entry::Vacant(e) => {
|
||||
let color = Self::resolve_spec_uncached(highlight, is_background, vars);
|
||||
e.insert(color);
|
||||
color
|
||||
let face = Self::resolve_spec_uncached(highlight, vars);
|
||||
e.insert(face);
|
||||
face
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn resolve_spec_uncached(
|
||||
pub(crate) fn resolve_spec_uncached(
|
||||
highlight: &HighlightSpec,
|
||||
is_background: bool,
|
||||
vars: &dyn Environment,
|
||||
) -> Color {
|
||||
let mut result = Color::NORMAL;
|
||||
let role = if is_background {
|
||||
highlight.background
|
||||
} else {
|
||||
highlight.foreground
|
||||
) -> TextFace {
|
||||
let resolve_role = |role| {
|
||||
vars.get_unless_empty(get_highlight_var_name(role))
|
||||
.or_else(|| vars.get_unless_empty(get_highlight_var_name(get_fallback(role))))
|
||||
.or_else(|| vars.get(get_highlight_var_name(HighlightRole::normal)))
|
||||
};
|
||||
|
||||
let var = vars
|
||||
.get_unless_empty(get_highlight_var_name(role))
|
||||
.or_else(|| vars.get_unless_empty(get_highlight_var_name(get_fallback(role))))
|
||||
.or_else(|| vars.get(get_highlight_var_name(HighlightRole::normal)));
|
||||
if let Some(var) = var {
|
||||
result = parse_color(&var, is_background);
|
||||
}
|
||||
let fg_var = resolve_role(highlight.foreground);
|
||||
let bg_var = resolve_role(highlight.background);
|
||||
let mut result = parse_text_face_for_highlight(fg_var.as_ref(), bg_var.as_ref());
|
||||
|
||||
// Handle modifiers.
|
||||
if !is_background && highlight.valid_path {
|
||||
if let Some(var2) = vars.get(L!("fish_color_valid_path")) {
|
||||
let result2 = parse_color(&var2, is_background);
|
||||
if result.is_normal() {
|
||||
result = result2;
|
||||
} else if !result2.is_normal() {
|
||||
// Valid path has an actual color, use it and merge the modifiers.
|
||||
let mut rescol = result2;
|
||||
rescol.set_bold(result.is_bold() || result2.is_bold());
|
||||
rescol.set_underline(result.is_underline() || result2.is_underline());
|
||||
rescol.set_italics(result.is_italics() || result2.is_italics());
|
||||
rescol.set_dim(result.is_dim() || result2.is_dim());
|
||||
rescol.set_reverse(result.is_reverse() || result2.is_reverse());
|
||||
result = rescol;
|
||||
} else {
|
||||
if result2.is_bold() {
|
||||
result.set_bold(true)
|
||||
};
|
||||
if result2.is_underline() {
|
||||
result.set_underline(true)
|
||||
};
|
||||
if result2.is_italics() {
|
||||
result.set_italics(true)
|
||||
};
|
||||
if result2.is_dim() {
|
||||
result.set_dim(true)
|
||||
};
|
||||
if result2.is_reverse() {
|
||||
result.set_reverse(true)
|
||||
};
|
||||
if highlight.valid_path {
|
||||
if let Some(valid_path_var) = vars.get(L!("fish_color_valid_path")) {
|
||||
// Historical behavior is to not apply background.
|
||||
let valid_path_face = parse_text_face_for_highlight(Some(&valid_path_var), None);
|
||||
// Apply the foreground, except if it's normal. The intention here is likely
|
||||
// to only override foreground if the valid path color has an explicit foreground.
|
||||
if !valid_path_face.fg.is_normal() {
|
||||
result.fg = valid_path_face.fg;
|
||||
}
|
||||
if valid_path_face.is_bold() {
|
||||
result.set_bold(true);
|
||||
}
|
||||
if valid_path_face.is_underline() {
|
||||
result.set_underline(true);
|
||||
}
|
||||
if valid_path_face.is_italics() {
|
||||
result.set_italics(true);
|
||||
}
|
||||
if valid_path_face.is_dim() {
|
||||
result.set_dim(true);
|
||||
}
|
||||
if valid_path_face.is_reverse() {
|
||||
result.set_reverse(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !is_background && highlight.force_underline {
|
||||
if highlight.force_underline {
|
||||
result.set_underline(true);
|
||||
}
|
||||
|
||||
@@ -202,6 +184,32 @@ pub fn resolve_spec_uncached(
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the internal color code representing the specified color.
|
||||
/// TODO: This code should be refactored to enable sharing with builtin_set_color.
|
||||
/// In particular, the argument parsing still isn't fully capable.
|
||||
pub(crate) fn parse_text_face_for_highlight(
|
||||
fg_var: Option<&EnvVar>,
|
||||
bg_var: Option<&EnvVar>,
|
||||
) -> TextFace {
|
||||
let parse_var = |maybe_var: Option<&EnvVar>, is_background| {
|
||||
let Some(var) = maybe_var else {
|
||||
return (Color::Normal, TextStyling::empty());
|
||||
};
|
||||
let (mut color, style) = parse_text_face(var, is_background);
|
||||
if color.is_none() {
|
||||
color = Color::Normal;
|
||||
}
|
||||
(color, style)
|
||||
};
|
||||
let (fg, fg_style) = parse_var(fg_var, false);
|
||||
let (bg, bg_style) = parse_var(bg_var, true);
|
||||
TextFace {
|
||||
fg,
|
||||
bg,
|
||||
style: fg_style | bg_style,
|
||||
}
|
||||
}
|
||||
|
||||
fn command_is_valid(
|
||||
cmd: &wstr,
|
||||
decoration: StatementDecoration,
|
||||
@@ -1276,7 +1284,7 @@ pub enum HighlightRole {
|
||||
pager_selected_description,
|
||||
}
|
||||
|
||||
/// Simply value type describing how a character should be highlighted..
|
||||
/// Simple value type describing how a character should be highlighted.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct HighlightSpec {
|
||||
pub foreground: HighlightRole,
|
||||
|
||||
@@ -695,9 +695,9 @@ fn test_trailing_spaces_after_command() {
|
||||
|
||||
// Check that 'echo' is underlined
|
||||
for i in 0..4 {
|
||||
let rgb = resolver.resolve_spec(&colors[i], false, vars);
|
||||
let face = resolver.resolve_spec(&colors[i], vars);
|
||||
assert!(
|
||||
rgb.is_underline(),
|
||||
face.is_underline(),
|
||||
"Character at position {} of 'echo' should be underlined",
|
||||
i
|
||||
);
|
||||
@@ -705,9 +705,9 @@ fn test_trailing_spaces_after_command() {
|
||||
|
||||
// Check that trailing spaces are NOT underlined
|
||||
for i in 4..text.len() {
|
||||
let rgb = resolver.resolve_spec(&colors[i], false, vars);
|
||||
let face = resolver.resolve_spec(&colors[i], vars);
|
||||
assert!(
|
||||
!rgb.is_underline(),
|
||||
!face.is_underline(),
|
||||
"Trailing space at position {} should NOT be underlined",
|
||||
i
|
||||
);
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
pub mod signal;
|
||||
pub mod terminal;
|
||||
pub mod termsize;
|
||||
pub mod text_face;
|
||||
pub mod threads;
|
||||
pub mod timer;
|
||||
pub mod tinyexpr;
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
use crate::builtins::shared::ErrorCode;
|
||||
use crate::builtins::shared::STATUS_CMD_ERROR;
|
||||
use crate::builtins::shared::STATUS_CMD_OK;
|
||||
use crate::color::Color;
|
||||
use crate::common::restore_term_foreground_process_group_for_exit;
|
||||
use crate::common::{
|
||||
escape, escape_string, exit_without_destructors, get_ellipsis_char, get_obfuscation_read_char,
|
||||
@@ -71,7 +70,8 @@
|
||||
use crate::future::IsSomeAnd;
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::highlight::{
|
||||
autosuggest_validate_from_history, highlight_shell, HighlightRole, HighlightSpec,
|
||||
autosuggest_validate_from_history, highlight_shell, parse_text_face_for_highlight,
|
||||
HighlightRole, HighlightSpec,
|
||||
};
|
||||
use crate::history::{
|
||||
history_session_id, in_private_mode, History, HistorySearch, PersistenceMode, SearchDirection,
|
||||
@@ -123,8 +123,7 @@
|
||||
signal_check_cancel, signal_clear_cancel, signal_reset_handlers, signal_set_handlers,
|
||||
signal_set_handlers_once,
|
||||
};
|
||||
use crate::terminal::parse_color;
|
||||
use crate::terminal::parse_color_maybe_none;
|
||||
use crate::terminal::parse_text_face;
|
||||
use crate::terminal::BufferedOutputter;
|
||||
use crate::terminal::Output;
|
||||
use crate::terminal::Outputter;
|
||||
@@ -144,6 +143,7 @@
|
||||
SYNCHRONIZED_OUTPUT_SUPPORTED,
|
||||
};
|
||||
use crate::termsize::{termsize_invalidate_tty, termsize_last, termsize_update};
|
||||
use crate::text_face::TextFace;
|
||||
use crate::threads::{
|
||||
assert_is_background_thread, assert_is_main_thread, iothread_service_main_with_timeout,
|
||||
Debounce,
|
||||
@@ -1660,7 +1660,10 @@ fn paint_layout(&mut self, reason: &wstr, is_final_rendering: bool) {
|
||||
let explicit_foreground = self
|
||||
.vars()
|
||||
.get_unless_empty(L!("fish_color_search_match"))
|
||||
.is_some_and(|var| !parse_color_maybe_none(&var, false).is_none());
|
||||
.is_some_and(|var| {
|
||||
let (color, _flags) = parse_text_face(&var, false);
|
||||
!color.is_none()
|
||||
});
|
||||
|
||||
for color in &mut colors[range] {
|
||||
if explicit_foreground {
|
||||
@@ -2290,9 +2293,7 @@ fn readline(&mut self, nchars: Option<NonZeroUsize>) -> Option<WString> {
|
||||
}
|
||||
perror("tcsetattr"); // return to previous mode
|
||||
}
|
||||
Outputter::stdoutput()
|
||||
.borrow_mut()
|
||||
.set_color(Color::RESET, Color::RESET);
|
||||
Outputter::stdoutput().borrow_mut().reset_text_face(true);
|
||||
}
|
||||
let result = self
|
||||
.rls()
|
||||
@@ -2694,13 +2695,13 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) {
|
||||
|
||||
let mut outp = Outputter::stdoutput().borrow_mut();
|
||||
if let Some(fish_color_cancel) = self.vars().get(L!("fish_color_cancel")) {
|
||||
outp.set_color(
|
||||
parse_color(&fish_color_cancel, false),
|
||||
parse_color(&fish_color_cancel, true),
|
||||
);
|
||||
outp.set_text_face(parse_text_face_for_highlight(
|
||||
Some(&fish_color_cancel),
|
||||
Some(&fish_color_cancel),
|
||||
));
|
||||
}
|
||||
outp.write_wstr(L!("^C"));
|
||||
outp.set_color(Color::RESET, Color::RESET);
|
||||
outp.reset_text_face(true);
|
||||
|
||||
// We print a newline last so the prompt_sp hack doesn't get us.
|
||||
outp.push(b'\n');
|
||||
@@ -4464,9 +4465,7 @@ fn reader_interactive_init(parser: &Parser) {
|
||||
|
||||
/// Destroy data for interactive use.
|
||||
fn reader_interactive_destroy() {
|
||||
Outputter::stdoutput()
|
||||
.borrow_mut()
|
||||
.set_color(Color::RESET, Color::RESET);
|
||||
Outputter::stdoutput().borrow_mut().reset_text_face(true);
|
||||
}
|
||||
|
||||
/// Return whether fish is currently unwinding the stack in preparation to exit.
|
||||
@@ -4530,7 +4529,7 @@ pub fn reader_write_title(
|
||||
out.write_command(Osc0WindowTitle(&lst));
|
||||
}
|
||||
|
||||
out.set_color(Color::RESET, Color::RESET);
|
||||
out.reset_text_face(true);
|
||||
if reset_cursor_position && !lst.is_empty() {
|
||||
// Put the cursor back at the beginning of the line (issue #2453).
|
||||
out.write_bytes(b"\r");
|
||||
@@ -5659,7 +5658,7 @@ fn reader_run_command(parser: &Parser, cmd: &wstr) -> EvalRes {
|
||||
reader_write_title(cmd, parser, true);
|
||||
Outputter::stdoutput()
|
||||
.borrow_mut()
|
||||
.set_color(Color::NORMAL, Color::NORMAL);
|
||||
.set_text_face(TextFace::default());
|
||||
term_donate(false);
|
||||
|
||||
let time_before = Instant::now();
|
||||
|
||||
@@ -1038,9 +1038,9 @@ fn update(
|
||||
// Helper function to set a resolved color, using the caching resolver.
|
||||
let mut color_resolver = HighlightColorResolver::new();
|
||||
let mut set_color = |zelf: &mut Self, c| {
|
||||
let fg = color_resolver.resolve_spec(&c, false, vars);
|
||||
let bg = color_resolver.resolve_spec(&c, true, vars);
|
||||
zelf.outp.borrow_mut().set_color(fg, bg);
|
||||
zelf.outp
|
||||
.borrow_mut()
|
||||
.set_text_face(color_resolver.resolve_spec(&c, vars));
|
||||
};
|
||||
|
||||
let mut cached_layouts = LAYOUT_CACHE_SHARED.lock().unwrap();
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// Generic output functions.
|
||||
use crate::color::{self, Color, Color24};
|
||||
use crate::color::{Color, Color24};
|
||||
use crate::common::ToCString;
|
||||
use crate::common::{self, escape_string, wcs2string, wcs2string_appending, EscapeStringStyle};
|
||||
use crate::env::EnvVar;
|
||||
use crate::future_feature_flags::{self, FeatureFlag};
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::screen::{is_dumb, only_grayscale};
|
||||
use crate::text_face::{TextFace, TextStyling};
|
||||
use crate::threads::MainThread;
|
||||
use crate::wchar::prelude::*;
|
||||
use crate::FLOGF;
|
||||
@@ -231,13 +232,7 @@ pub(crate) fn use_terminfo() -> bool {
|
||||
}
|
||||
|
||||
fn palette_color(out: &mut impl Output, foreground: bool, mut idx: u8) -> bool {
|
||||
if only_grayscale()
|
||||
&& !(Color {
|
||||
typ: color::Type::Named { idx },
|
||||
flags: color::Flags::DEFAULT,
|
||||
})
|
||||
.is_grayscale()
|
||||
{
|
||||
if only_grayscale() && !(Color::Named { idx }).is_grayscale() {
|
||||
return false;
|
||||
}
|
||||
if use_terminfo() {
|
||||
@@ -441,8 +436,8 @@ const fn new_from_fd(fd: RawFd) -> Self {
|
||||
contents: Vec::new(),
|
||||
buffer_count: 0,
|
||||
fd,
|
||||
last_fg: Color::NORMAL,
|
||||
last_bg: Color::NORMAL,
|
||||
last_fg: Color::Normal,
|
||||
last_bg: Color::Normal,
|
||||
was_bold: false,
|
||||
was_underline: false,
|
||||
was_italics: false,
|
||||
@@ -492,7 +487,8 @@ pub fn write_color(&mut self, color: Color, is_fg: bool) -> bool {
|
||||
self.write_command(TerminalCommand::SelectRgbColor(is_fg, color.to_color24()))
|
||||
}
|
||||
|
||||
pub(crate) fn reset_color(&mut self, weird_workaround: bool) {
|
||||
/// Unconditionally resets colors and text style.
|
||||
pub(crate) fn reset_text_face(&mut self, weird_workaround: bool) {
|
||||
if weird_workaround {
|
||||
// If we exit attribute mode, we must first set a color, or previously colored text might
|
||||
// lose its color. Terminals are weird...
|
||||
@@ -500,8 +496,8 @@ pub(crate) fn reset_color(&mut self, weird_workaround: bool) {
|
||||
}
|
||||
use TerminalCommand::ExitAttributeMode;
|
||||
self.write_command(ExitAttributeMode);
|
||||
self.last_fg = Color::NORMAL;
|
||||
self.last_bg = Color::NORMAL;
|
||||
self.last_fg = Color::Normal;
|
||||
self.last_bg = Color::Normal;
|
||||
self.reset_modes();
|
||||
}
|
||||
|
||||
@@ -522,17 +518,18 @@ pub(crate) fn reset_color(&mut self, weird_workaround: bool) {
|
||||
/// - Lastly we may need to write set_a_background or set_a_foreground to set the other half of the
|
||||
/// color pair to what it should be.
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
pub fn set_color(&mut self, mut fg: Color, bg: Color) {
|
||||
pub(crate) fn set_text_face(&mut self, face: TextFace) {
|
||||
let TextFace { mut fg, bg, .. } = face;
|
||||
let mut bg_set = false;
|
||||
let mut last_bg_set = false;
|
||||
let is_bold = fg.is_bold() || bg.is_bold();
|
||||
let is_underline = fg.is_underline() || bg.is_underline();
|
||||
let is_italics = fg.is_italics() || bg.is_italics();
|
||||
let is_dim = fg.is_dim() || bg.is_dim();
|
||||
let is_reverse = fg.is_reverse() || bg.is_reverse();
|
||||
let is_bold = face.is_bold();
|
||||
let is_underline = face.is_underline();
|
||||
let is_italics = face.is_italics();
|
||||
let is_dim = face.is_dim();
|
||||
let is_reverse = face.is_reverse();
|
||||
|
||||
if fg.is_reset() || bg.is_reset() {
|
||||
self.reset_color(true);
|
||||
self.reset_text_face(true);
|
||||
return;
|
||||
}
|
||||
use TerminalCommand::{
|
||||
@@ -544,7 +541,7 @@ pub fn set_color(&mut self, mut fg: Color, bg: Color) {
|
||||
|| (self.was_reverse && !is_reverse)
|
||||
{
|
||||
// Only way to exit bold/dim/reverse mode is a reset of all attributes.
|
||||
self.reset_color(false);
|
||||
self.reset_text_face(false);
|
||||
}
|
||||
if !self.last_bg.is_special() {
|
||||
// Background was set.
|
||||
@@ -571,7 +568,7 @@ pub fn set_color(&mut self, mut fg: Color, bg: Color) {
|
||||
}
|
||||
if !bg_set && last_bg_set {
|
||||
// Background color changed and is no longer set, so we exit bold mode.
|
||||
self.reset_color(false);
|
||||
self.reset_text_face(false);
|
||||
}
|
||||
|
||||
if !fg.is_none() && self.last_fg != fg {
|
||||
@@ -579,7 +576,7 @@ pub fn set_color(&mut self, mut fg: Color, bg: Color) {
|
||||
write_foreground_color(self, 0);
|
||||
self.write_command(ExitAttributeMode);
|
||||
|
||||
self.last_bg = Color::NORMAL;
|
||||
self.last_bg = Color::Normal;
|
||||
self.reset_modes();
|
||||
} else {
|
||||
assert!(!fg.is_special());
|
||||
@@ -735,11 +732,11 @@ fn write_bytes(&mut self, buf: &[u8]) {
|
||||
/// RgbColor::NONE if empty.
|
||||
pub fn best_color(candidates: &[Color], support: ColorSupport) -> Color {
|
||||
if candidates.is_empty() {
|
||||
return Color::NONE;
|
||||
return Color::None;
|
||||
}
|
||||
|
||||
let mut first_rgb = Color::NONE;
|
||||
let mut first_named = Color::NONE;
|
||||
let mut first_rgb = Color::None;
|
||||
let mut first_named = Color::None;
|
||||
for color in candidates {
|
||||
if first_rgb.is_none() && color.is_rgb() {
|
||||
first_rgb = *color;
|
||||
@@ -763,24 +760,8 @@ pub fn best_color(candidates: &[Color], support: ColorSupport) -> Color {
|
||||
result
|
||||
}
|
||||
|
||||
/// Return the internal color code representing the specified color.
|
||||
/// TODO: This code should be refactored to enable sharing with builtin_set_color.
|
||||
/// In particular, the argument parsing still isn't fully capable.
|
||||
pub fn parse_color(var: &EnvVar, is_background: bool) -> Color {
|
||||
let mut result = parse_color_maybe_none(var, is_background);
|
||||
if result.is_none() {
|
||||
result.typ = color::Type::Normal;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn parse_color_maybe_none(var: &EnvVar, is_background: bool) -> Color {
|
||||
let mut is_bold = false;
|
||||
let mut is_underline = false;
|
||||
let mut is_italics = false;
|
||||
let mut is_dim = false;
|
||||
let mut is_reverse = false;
|
||||
|
||||
pub fn parse_text_face(var: &EnvVar, is_background: bool) -> (Color, TextStyling) {
|
||||
let mut style = TextStyling::empty();
|
||||
let mut candidates: Vec<Color> = Vec::new();
|
||||
|
||||
let prefix = L!("--background=");
|
||||
@@ -802,7 +783,7 @@ pub fn parse_color_maybe_none(var: &EnvVar, is_background: bool) -> Color {
|
||||
next_is_background = true;
|
||||
} else if next == "--reverse" || next == "-r" {
|
||||
// Reverse should be meaningful in either context
|
||||
is_reverse = true;
|
||||
style |= TextStyling::REVERSE;
|
||||
} else if next.starts_with("-b") {
|
||||
// Look for something like "-bred".
|
||||
// Yes, that length is hardcoded.
|
||||
@@ -810,15 +791,15 @@ pub fn parse_color_maybe_none(var: &EnvVar, is_background: bool) -> Color {
|
||||
}
|
||||
} else {
|
||||
if next == "--bold" || next == "-o" {
|
||||
is_bold = true;
|
||||
style |= TextStyling::BOLD;
|
||||
} else if next == "--underline" || next == "-u" {
|
||||
is_underline = true;
|
||||
style |= TextStyling::UNDERLINE;
|
||||
} else if next == "--italics" || next == "-i" {
|
||||
is_italics = true;
|
||||
style |= TextStyling::ITALICS;
|
||||
} else if next == "--dim" || next == "-d" {
|
||||
is_dim = true;
|
||||
style |= TextStyling::DIM;
|
||||
} else if next == "--reverse" || next == "-r" {
|
||||
is_reverse = true;
|
||||
style |= TextStyling::REVERSE;
|
||||
} else {
|
||||
color_name = Some(next.as_utfstr());
|
||||
}
|
||||
@@ -829,13 +810,8 @@ pub fn parse_color_maybe_none(var: &EnvVar, is_background: bool) -> Color {
|
||||
}
|
||||
}
|
||||
|
||||
let mut result = best_color(&candidates, get_color_support());
|
||||
result.set_bold(is_bold);
|
||||
result.set_underline(is_underline);
|
||||
result.set_italics(is_italics);
|
||||
result.set_dim(is_dim);
|
||||
result.set_reverse(is_reverse);
|
||||
result
|
||||
let color = best_color(&candidates, get_color_support());
|
||||
(color, style)
|
||||
}
|
||||
|
||||
/// The [`Term`] singleton. Initialized via a call to [`setup()`] and surfaced to the outside world via [`term()`].
|
||||
|
||||
86
src/text_face.rs
Normal file
86
src/text_face.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use bitflags::bitflags;
|
||||
|
||||
use crate::color::Color;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct TextStyling: u8 {
|
||||
const BOLD = 1<<0;
|
||||
const UNDERLINE = 1<<1;
|
||||
const ITALICS = 1<<2;
|
||||
const DIM = 1<<3;
|
||||
const REVERSE = 1<<4;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct TextFace {
|
||||
pub(crate) fg: Color,
|
||||
pub(crate) bg: Color,
|
||||
pub(crate) style: TextStyling,
|
||||
}
|
||||
|
||||
impl Default for TextFace {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
fg: Color::Normal,
|
||||
bg: Color::Normal,
|
||||
style: TextStyling::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextFace {
|
||||
pub fn new(fg: Color, bg: Color, style: TextStyling) -> Self {
|
||||
Self { fg, bg, style }
|
||||
}
|
||||
/// Returns whether the text face is bold.
|
||||
pub const fn is_bold(self) -> bool {
|
||||
self.style.contains(TextStyling::BOLD)
|
||||
}
|
||||
|
||||
/// Set whether the text face is bold.
|
||||
pub fn set_bold(&mut self, bold: bool) {
|
||||
self.style.set(TextStyling::BOLD, bold)
|
||||
}
|
||||
|
||||
/// Returns whether the text face is underlined.
|
||||
pub const fn is_underline(self) -> bool {
|
||||
self.style.contains(TextStyling::UNDERLINE)
|
||||
}
|
||||
|
||||
/// Set whether the text face is underline.
|
||||
pub fn set_underline(&mut self, underline: bool) {
|
||||
self.style.set(TextStyling::UNDERLINE, underline)
|
||||
}
|
||||
|
||||
/// Returns whether the text face is italics.
|
||||
pub const fn is_italics(self) -> bool {
|
||||
self.style.contains(TextStyling::ITALICS)
|
||||
}
|
||||
|
||||
/// Set whether the text face is italics.
|
||||
pub fn set_italics(&mut self, italics: bool) {
|
||||
self.style.set(TextStyling::ITALICS, italics)
|
||||
}
|
||||
|
||||
/// Returns whether the text face is dim.
|
||||
pub const fn is_dim(self) -> bool {
|
||||
self.style.contains(TextStyling::DIM)
|
||||
}
|
||||
|
||||
/// Set whether the text face is dim.
|
||||
pub fn set_dim(&mut self, dim: bool) {
|
||||
self.style.set(TextStyling::DIM, dim)
|
||||
}
|
||||
|
||||
/// Returns whether the text face has reverse foreground/background colors.
|
||||
pub const fn is_reverse(self) -> bool {
|
||||
self.style.contains(TextStyling::REVERSE)
|
||||
}
|
||||
|
||||
/// Set whether the text face has reverse foreground/background colors.
|
||||
pub fn set_reverse(&mut self, reverse: bool) {
|
||||
self.style.set(TextStyling::REVERSE, reverse)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user