Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 761e0f2

Browse files
authored
Implement basic support for true colors (#271)
1 parent f35b2e4 commit 761e0f2

File tree

7 files changed

+120
-4
lines changed

7 files changed

+120
-4
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
target
22
Cargo.lock
3+
.idea

src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,9 @@ pub use crate::term::{
9494
#[cfg(feature = "std")]
9595
pub use crate::utils::{
9696
colors_enabled, colors_enabled_stderr, measure_text_width, pad_str, pad_str_with,
97-
set_colors_enabled, set_colors_enabled_stderr, style, truncate_str, Alignment, Attribute,
98-
Color, Emoji, Style, StyledObject,
97+
set_colors_enabled, set_colors_enabled_stderr, set_true_colors_enabled,
98+
set_true_colors_enabled_stderr, style, true_colors_enabled, true_colors_enabled_stderr,
99+
truncate_str, Alignment, Attribute, Color, Emoji, Style, StyledObject,
99100
};
100101

101102
#[cfg(all(feature = "ansi-parsing", feature = "alloc"))]

src/term.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ impl TermFeatures<'_> {
7878
is_a_color_terminal(self.0)
7979
}
8080

81+
/// Check if true colors are supported by this terminal.
82+
pub fn true_colors_supported(&self) -> bool {
83+
is_a_true_color_terminal(self.0)
84+
}
85+
8186
/// Check if this terminal is an msys terminal.
8287
///
8388
/// This is sometimes useful to disable features that are known to not

src/unix_term.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ pub(crate) fn is_a_color_terminal(out: &Term) -> bool {
3636
}
3737
}
3838

39+
pub(crate) fn is_a_true_color_terminal(out: &Term) -> bool {
40+
if !is_a_color_terminal(out) {
41+
return false;
42+
}
43+
env::var("COLORTERM").map_or(false, |term| term == "truecolor" || term == "24bit")
44+
}
45+
3946
fn c_result<F: FnOnce() -> libc::c_int>(f: F) -> io::Result<()> {
4047
let res = f();
4148
if res != 0 {

src/utils.rs

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,18 @@ fn default_colors_enabled(out: &Term) -> bool {
1818
|| &env::var("CLICOLOR_FORCE").unwrap_or_else(|_| "0".into()) != "0"
1919
}
2020

21+
fn default_true_colors_enabled(out: &Term) -> bool {
22+
out.features().true_colors_supported()
23+
}
24+
2125
static STDOUT_COLORS: Lazy<AtomicBool> =
2226
Lazy::new(|| AtomicBool::new(default_colors_enabled(&Term::stdout())));
27+
static STDOUT_TRUE_COLORS: Lazy<AtomicBool> =
28+
Lazy::new(|| AtomicBool::new(default_true_colors_enabled(&Term::stdout())));
2329
static STDERR_COLORS: Lazy<AtomicBool> =
2430
Lazy::new(|| AtomicBool::new(default_colors_enabled(&Term::stderr())));
31+
static STDERR_TRUE_COLORS: Lazy<AtomicBool> =
32+
Lazy::new(|| AtomicBool::new(default_true_colors_enabled(&Term::stderr())));
2533

2634
/// Returns `true` if colors should be enabled for stdout.
2735
///
@@ -35,6 +43,12 @@ pub fn colors_enabled() -> bool {
3543
STDOUT_COLORS.load(Ordering::Relaxed)
3644
}
3745

46+
/// Returns `true` if true colors should be enabled for stdout.
47+
#[inline]
48+
pub fn true_colors_enabled() -> bool {
49+
STDERR_TRUE_COLORS.load(Ordering::Relaxed)
50+
}
51+
3852
/// Forces colorization on or off for stdout.
3953
///
4054
/// This overrides the default for the current process and changes the return value of the
@@ -44,6 +58,15 @@ pub fn set_colors_enabled(val: bool) {
4458
STDOUT_COLORS.store(val, Ordering::Relaxed)
4559
}
4660

61+
/// Forces true colorization on or off for stdout.
62+
///
63+
/// This overrides the default for the current process and changes the return value of the
64+
/// `true_colors_enabled` function.
65+
#[inline]
66+
pub fn set_true_colors_enabled(val: bool) {
67+
STDOUT_TRUE_COLORS.store(val, Ordering::Relaxed)
68+
}
69+
4770
/// Returns `true` if colors should be enabled for stderr.
4871
///
4972
/// This honors the [clicolors spec](http://bixense.com/clicolors/).
@@ -56,6 +79,12 @@ pub fn colors_enabled_stderr() -> bool {
5679
STDERR_COLORS.load(Ordering::Relaxed)
5780
}
5881

82+
/// Returns `true` if true colors should be enabled for stderr.
83+
#[inline]
84+
pub fn true_colors_enabled_stderr() -> bool {
85+
STDERR_TRUE_COLORS.load(Ordering::Relaxed)
86+
}
87+
5988
/// Forces colorization on or off for stderr.
6089
///
6190
/// This overrides the default for the current process and changes the return value of the
@@ -65,6 +94,15 @@ pub fn set_colors_enabled_stderr(val: bool) {
6594
STDERR_COLORS.store(val, Ordering::Relaxed)
6695
}
6796

97+
/// Forces true colorization on or off for stderr.
98+
///
99+
/// This overrides the default for the current process and changes the return value of the
100+
/// `true_colors_enabled_stderr` function.
101+
#[inline]
102+
pub fn set_true_colors_enabled_stderr(val: bool) {
103+
STDERR_TRUE_COLORS.store(val, Ordering::Relaxed)
104+
}
105+
68106
/// Measure the width of a string in terminal characters.
69107
pub fn measure_text_width(s: &str) -> usize {
70108
#[cfg(feature = "ansi-parsing")]
@@ -94,6 +132,7 @@ pub enum Color {
94132
Cyan,
95133
White,
96134
Color256(u8),
135+
TrueColor(u8, u8, u8),
97136
}
98137

99138
impl Color {
@@ -109,6 +148,7 @@ impl Color {
109148
Color::Cyan => 6,
110149
Color::White => 7,
111150
Color::Color256(x) => x as usize,
151+
Color::TrueColor(_, _, _) => panic!("RGB colors must be handled separately"),
112152
}
113153
}
114154

@@ -293,6 +333,28 @@ impl Style {
293333
"reverse" => rv.reverse(),
294334
"hidden" => rv.hidden(),
295335
"strikethrough" => rv.strikethrough(),
336+
on_true_color if on_true_color.starts_with("on_#") && on_true_color.len() == 10 => {
337+
if let (Ok(r), Ok(g), Ok(b)) = (
338+
u8::from_str_radix(&on_true_color[4..6], 16),
339+
u8::from_str_radix(&on_true_color[6..8], 16),
340+
u8::from_str_radix(&on_true_color[8..10], 16),
341+
) {
342+
rv.on_true_color(r, g, b)
343+
} else {
344+
continue;
345+
}
346+
}
347+
true_color if true_color.starts_with('#') && true_color.len() == 7 => {
348+
if let (Ok(r), Ok(g), Ok(b)) = (
349+
u8::from_str_radix(&true_color[1..3], 16),
350+
u8::from_str_radix(&true_color[3..5], 16),
351+
u8::from_str_radix(&true_color[5..7], 16),
352+
) {
353+
rv.true_color(r, g, b)
354+
} else {
355+
continue;
356+
}
357+
}
296358
on_c if on_c.starts_with("on_") => {
297359
if let Ok(n) = on_c[3..].parse::<u8>() {
298360
rv.on_color256(n)
@@ -402,6 +464,10 @@ impl Style {
402464
pub const fn color256(self, color: u8) -> Self {
403465
self.fg(Color::Color256(color))
404466
}
467+
#[inline]
468+
pub const fn true_color(self, r: u8, g: u8, b: u8) -> Self {
469+
self.fg(Color::TrueColor(r, g, b))
470+
}
405471

406472
#[inline]
407473
pub const fn bright(mut self) -> Self {
@@ -445,6 +511,10 @@ impl Style {
445511
pub const fn on_color256(self, color: u8) -> Self {
446512
self.bg(Color::Color256(color))
447513
}
514+
#[inline]
515+
pub const fn on_true_color(self, r: u8, g: u8, b: u8) -> Self {
516+
self.bg(Color::TrueColor(r, g, b))
517+
}
448518

449519
#[inline]
450520
pub const fn on_bright(mut self) -> Self {
@@ -600,6 +670,10 @@ impl<D> StyledObject<D> {
600670
pub const fn color256(self, color: u8) -> StyledObject<D> {
601671
self.fg(Color::Color256(color))
602672
}
673+
#[inline]
674+
pub const fn true_color(self, r: u8, g: u8, b: u8) -> StyledObject<D> {
675+
self.fg(Color::TrueColor(r, g, b))
676+
}
603677

604678
#[inline]
605679
pub const fn bright(mut self) -> StyledObject<D> {
@@ -643,6 +717,10 @@ impl<D> StyledObject<D> {
643717
pub const fn on_color256(self, color: u8) -> StyledObject<D> {
644718
self.bg(Color::Color256(color))
645719
}
720+
#[inline]
721+
pub const fn on_true_color(self, r: u8, g: u8, b: u8) -> StyledObject<D> {
722+
self.bg(Color::TrueColor(r, g, b))
723+
}
646724

647725
#[inline]
648726
pub const fn on_bright(mut self) -> StyledObject<D> {
@@ -702,7 +780,9 @@ macro_rules! impl_fmt {
702780
})
703781
{
704782
if let Some(fg) = self.style.fg {
705-
if fg.is_color256() {
783+
if let Color::TrueColor(r, g, b) = fg {
784+
write!(f, "\x1b[38;2;{};{};{}m", r, g, b)?;
785+
} else if fg.is_color256() {
706786
write!(f, "\x1b[38;5;{}m", fg.ansi_num())?;
707787
} else if self.style.fg_bright {
708788
write!(f, "\x1b[38;5;{}m", fg.ansi_num() + 8)?;
@@ -712,7 +792,9 @@ macro_rules! impl_fmt {
712792
reset = true;
713793
}
714794
if let Some(bg) = self.style.bg {
715-
if bg.is_color256() {
795+
if let Color::TrueColor(r, g, b) = bg {
796+
write!(f, "\x1b[48;2;{};{};{}m", r, g, b)?;
797+
} else if bg.is_color256() {
716798
write!(f, "\x1b[48;5;{}m", bg.ansi_num())?;
717799
} else if self.style.bg_bright {
718800
write!(f, "\x1b[48;5;{}m", bg.ansi_num() + 8)?;

src/wasm_term.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ pub(crate) fn is_a_color_terminal(_out: &Term) -> bool {
2828
false
2929
}
3030

31+
#[inline]
32+
pub(crate) fn is_a_true_color_terminal(_out: &Term) -> bool {
33+
false
34+
}
35+
3136
#[inline]
3237
pub(crate) fn terminal_size(_out: &Term) -> Option<(u16, u16)> {
3338
None

src/windows_term/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,21 @@ pub(crate) fn is_a_color_terminal(out: &Term) -> bool {
8080
enable_ansi_on(out)
8181
}
8282

83+
pub(crate) fn is_a_true_color_terminal(out: &Term) -> bool {
84+
if !is_a_color_terminal(out) {
85+
return false;
86+
}
87+
// Powershell does not respect the COLORTERM var despite supporting true colors
88+
// but other shells may respect it
89+
if msys_tty_on(out) {
90+
return match env::var("COLORTERM") {
91+
Ok(term) => term == "truecolor" || term == "24bit",
92+
Err(_) => true,
93+
};
94+
}
95+
false
96+
}
97+
8398
/// Enables or disables the `mode` flag on the given `HANDLE` and yields the previous mode.
8499
fn set_console_mode(handle: HANDLE, mode: CONSOLE_MODE, enable: bool) -> Option<CONSOLE_MODE> {
85100
unsafe {

0 commit comments

Comments
 (0)