Skip to content

Commit 91c69dd

Browse files
authored
Improve color support (#548)
1 parent f558388 commit 91c69dd

File tree

9 files changed

+76
-79
lines changed

9 files changed

+76
-79
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ jobs:
5252

5353
- name: Test
5454
run: cargo test --verbose ${{ matrix.feature-flags }}
55+
env:
56+
COLORTERM: truecolor
5557

5658
coverage:
5759
name: Coverage ${{ matrix.feature-flags }} (${{ matrix.os }})

Cargo.lock

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ members = ["gengo", "gengo-bin", "samples-test"]
33
resolver = "2"
44

55
[workspace.dependencies]
6+
chromaterm = "0.1"
67
indexmap = "2"
78
owo-colors = ">=3, <=4"
89
serde_json = "1"

gengo-bin/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ name = "gengo"
1818

1919
[features]
2020
default = ["color", "gengo/max-performance"]
21-
color = ["gengo/owo-colors", "owo-colors", "relative-luminance"]
21+
color = ["gengo/chromaterm", "chromaterm", "relative-luminance"]
2222

2323
[dependencies]
24+
chromaterm = { workspace = true, optional = true }
2425
clap = { version = "4", features = ["derive", "wrap_help"] }
2526
gengo = { path = "../gengo", version = "0.13", default-features = false }
2627
indexmap.workspace = true
27-
owo-colors = { workspace = true, optional = true }
2828
relative-luminance = { version = "1", optional = true }
2929
serde_json.workspace = true
3030

gengo-bin/src/cli.rs

Lines changed: 41 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
#[cfg(feature = "color")]
2+
use chromaterm::{colors, prelude::*};
13
use clap::Error as ClapError;
24
use clap::{Parser, Subcommand, ValueEnum};
35
use gengo::{Analysis, Builder, Directory, Git, analysis::SummaryOpts};
46
use indexmap::IndexMap;
57
#[cfg(feature = "color")]
6-
use owo_colors::Rgb;
7-
#[cfg(feature = "color")]
88
use relative_luminance::Luminance;
99
use std::error::Error as BaseError;
1010
use std::io::{self, Write};
@@ -45,7 +45,7 @@ pub struct CLI {
4545
breakdown: bool,
4646
/// Control when colors are displayed.
4747
#[cfg(feature = "color")]
48-
#[arg(long, default_value = "always")]
48+
#[arg(long, default_value = "auto")]
4949
color: ColorControl,
5050
/// The format to use for output.
5151
#[arg(short = 'F', long, default_value = "pretty")]
@@ -74,6 +74,8 @@ enum Commands {
7474
#[cfg(feature = "color")]
7575
#[derive(ValueEnum, Debug, Clone)]
7676
enum ColorControl {
77+
/// Automatically detect if colors are supported.
78+
Auto,
7779
/// Always use colors.
7880
Always,
7981
/// Use only the 8 ANSI colors.
@@ -92,6 +94,19 @@ enum Format {
9294

9395
impl CLI {
9496
pub fn run(&self, mut out: impl Write, mut err: impl Write) -> Result<(), io::Error> {
97+
#[cfg(feature = "color")]
98+
{
99+
use ColorControl::*;
100+
use chromaterm::ColorSupport;
101+
102+
chromaterm::config::convert_to_supported(true);
103+
match self.color {
104+
Auto => chromaterm::config::use_default_color_support(),
105+
Always => chromaterm::config::use_color_support(ColorSupport::True),
106+
Ansi => chromaterm::config::use_color_support(ColorSupport::Simple),
107+
Never => chromaterm::config::use_color_support(ColorSupport::None),
108+
}
109+
}
95110
let results = self.command.analyze(self.read_limit);
96111
let results = match results {
97112
Ok(results) => results,
@@ -121,13 +136,13 @@ impl CLI {
121136
for (language, size) in summary.iter() {
122137
let percentage = (*size * 100) as f64 / total;
123138
#[cfg(feature = "color")]
124-
let color = language.owo_color();
139+
let color = language.chromaterm_color();
125140
#[cfg(not(feature = "color"))]
126141
let color = ();
127142

128143
let stats = format!("{:>6.2}% {}", percentage, size);
129144
let line = format!("{:<15} {}", stats, language.name());
130-
let line = self.colorize(&line, color);
145+
let line = self.colorize(&line, &color);
131146
writeln!(out, "{}", line)?;
132147
}
133148

@@ -183,11 +198,11 @@ impl CLI {
183198

184199
for (language, files) in files_per_language.into_iter() {
185200
#[cfg(feature = "color")]
186-
let color = language.owo_color();
201+
let color = language.chromaterm_color();
187202
#[cfg(not(feature = "color"))]
188203
let color = ();
189204

190-
writeln!(out, "{}", self.colorize(language.name(), color))?;
205+
writeln!(out, "{}", self.colorize(language.name(), &color))?;
191206

192207
let files = {
193208
let mut files = files;
@@ -199,7 +214,7 @@ impl CLI {
199214
writeln!(
200215
out,
201216
" {}",
202-
self.colorize(&file.display().to_string(), color)
217+
self.colorize(&file.display().to_string(), &color)
203218
)?;
204219
}
205220
writeln!(out)?;
@@ -208,61 +223,21 @@ impl CLI {
208223
}
209224

210225
#[cfg(feature = "color")]
211-
fn colorize(&self, s: &str, color: owo_colors::Rgb) -> String {
212-
use ColorControl::*;
213-
use owo_colors::{AnsiColors::*, OwoColorize, Rgb};
214-
215-
match self.color {
216-
Never => String::from(s),
217-
Always => {
218-
let fg = if Self::is_bright(color) {
219-
Rgb(0, 0, 0)
220-
} else {
221-
Rgb(0xFF, 0xFF, 0xFF)
222-
};
223-
s.on_color(color).color(fg).to_string()
224-
}
225-
Ansi => {
226-
let (bg, (r, g, b)) = Self::closest_color(color);
227-
let fg = if Self::is_bright(Rgb(r, g, b)) {
228-
Black
229-
} else {
230-
White
231-
};
232-
s.on_color(bg).color(fg).to_string()
233-
}
234-
}
235-
}
226+
fn colorize(&self, s: &str, color: &colors::True) -> String {
227+
use chromaterm::{Color, colors::True};
236228

237-
#[cfg(feature = "color")]
238-
fn is_bright<T: Into<RgbWrapper>>(color: T) -> bool {
239-
color.into().relative_luminance() > 0.5
229+
let fg = if Self::is_bright(color) {
230+
True::from_rgb(0, 0, 0)
231+
} else {
232+
True::from_rgb(0xFF, 0xFF, 0xFF)
233+
};
234+
let (r, g, b) = color.rgb_u8();
235+
s.on_rgb(r, g, b).color(fg).to_string()
240236
}
241237

242238
#[cfg(feature = "color")]
243-
fn closest_color(rgb: owo_colors::Rgb) -> (owo_colors::AnsiColors, (u8, u8, u8)) {
244-
use owo_colors::AnsiColors::*;
245-
// NOTE Gets the closest color by Euclidean distance
246-
[
247-
(Black, (0u8, 0u8, 0u8)),
248-
(Red, (0xFF, 0, 0)),
249-
(Green, (0, 0xFF, 0)),
250-
(Yellow, (0xFF, 0xFF, 0)),
251-
(Blue, (0, 0, 0xFF)),
252-
(Magenta, (0xFF, 0, 0xFF)),
253-
(Cyan, (0, 0xFF, 0xFF)),
254-
(White, (0xFF, 0xFF, 0xFF)),
255-
]
256-
.into_iter()
257-
.min_by_key(|(_, (r, g, b))| {
258-
// NOTE As a shortcut we'll just skip the square root step
259-
[(r, rgb.0), (g, rgb.1), (b, rgb.2)]
260-
.into_iter()
261-
.map(|(p1, p2)| u32::from(p1.abs_diff(p2)))
262-
.map(|diff| diff * diff)
263-
.sum::<u32>()
264-
})
265-
.unwrap()
239+
fn is_bright<'a, T: Into<RgbWrapper<'a>>>(color: T) -> bool {
240+
color.into().relative_luminance() > 0.5
266241
}
267242

268243
#[cfg(not(feature = "color"))]
@@ -293,13 +268,15 @@ impl Commands {
293268

294269
#[cfg(feature = "color")]
295270
mod color_support {
271+
use chromaterm::Color;
272+
296273
use super::*;
297274

298-
pub(super) struct RgbWrapper(Rgb);
275+
pub(super) struct RgbWrapper<'a>(&'a colors::True);
299276

300-
impl Luminance<f32> for RgbWrapper {
277+
impl Luminance<f32> for RgbWrapper<'_> {
301278
fn luminance_rgb(&self) -> relative_luminance::Rgb<f32> {
302-
let Rgb(r, g, b) = self.0;
279+
let (r, g, b) = self.0.rgb_u8();
303280
// NOTE Normalize to the range [0.0, 1.0]
304281
relative_luminance::Rgb::new(
305282
f32::from(r) / 255.0,
@@ -309,8 +286,8 @@ mod color_support {
309286
}
310287
}
311288

312-
impl From<Rgb> for RgbWrapper {
313-
fn from(rgb: Rgb) -> Self {
289+
impl<'a> From<&'a colors::True> for RgbWrapper<'a> {
290+
fn from(rgb: &'a colors::True) -> Self {
314291
Self(rgb)
315292
}
316293
}

gengo-bin/tests/snapshots/test_cli__color_breakdown_javascript_repo.snap

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
source: gengo-bin/tests/test_cli.rs
33
expression: stdout
44
---
5-
[38;2;255;255;255;48;2;0;0;0m 40.79% 62 Plain Text[0m
6-
[38;2;255;255;255;48;2;47;116;192m 40.79% 62 TypeScript[0m
7-
[38;2;0;0;0;48;2;240;220;78m 18.42% 28 JavaScript[0m
5+
[38;2;255;255;255m[48;2;0;0;0m 40.79% 62 Plain Text[0m[0m
6+
[38;2;255;255;255m[48;2;47;116;192m 40.79% 62 TypeScript[0m[0m
7+
[38;2;0;0;0m[48;2;240;220;78m 18.42% 28 JavaScript[0m[0m
88

9-
[38;2;0;0;0;48;2;240;220;78mJavaScript[0m
10-
[38;2;0;0;0;48;2;240;220;78mbin.js[0m
9+
[38;2;0;0;0m[48;2;240;220;78mJavaScript[0m[0m
10+
[38;2;0;0;0m[48;2;240;220;78mbin.js[0m[0m
1111

12-
[38;2;255;255;255;48;2;0;0;0mPlain Text[0m
13-
[38;2;255;255;255;48;2;0;0;0mdist/bin.js[0m
12+
[38;2;255;255;255m[48;2;0;0;0mPlain Text[0m[0m
13+
[38;2;255;255;255m[48;2;0;0;0mdist/bin.js[0m[0m
1414

15-
[38;2;255;255;255;48;2;47;116;192mTypeScript[0m
16-
[38;2;255;255;255;48;2;47;116;192msrc/bin.ts[0m
15+
[38;2;255;255;255m[48;2;47;116;192mTypeScript[0m[0m
16+
[38;2;255;255;255m[48;2;47;116;192msrc/bin.ts[0m[0m

gengo-bin/tests/snapshots/test_cli__color_javascript_repo.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
source: gengo-bin/tests/test_cli.rs
33
expression: stdout
44
---
5-
[38;2;255;255;255;48;2;0;0;0m 40.79% 62 Plain Text[0m
6-
[38;2;255;255;255;48;2;47;116;192m 40.79% 62 TypeScript[0m
7-
[38;2;0;0;0;48;2;240;220;78m 18.42% 28 JavaScript[0m
5+
[38;2;255;255;255m[48;2;0;0;0m 40.79% 62 Plain Text[0m[0m
6+
[38;2;255;255;255m[48;2;47;116;192m 40.79% 62 TypeScript[0m[0m
7+
[38;2;0;0;0m[48;2;240;220;78m 18.42% 28 JavaScript[0m[0m

gengo/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ max-performance = ["gix/max-performance"]
1818
max-performance-safe = ["gix/max-performance-safe"]
1919

2020
[dependencies]
21+
chromaterm = { workspace = true, optional = true }
2122
gix = { version = ">= 0.56, <= 0.71", default-features = false, features = [
2223
"attributes",
2324
"index",

gengo/src/language/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,15 @@ impl serde::Serialize for Language {
210210
}
211211
}
212212

213+
#[cfg(feature = "chromaterm")]
214+
impl Language {
215+
/// Converts the color to RGB true color.
216+
pub const fn chromaterm_color(&self) -> chromaterm::colors::True {
217+
let (r, g, b) = self.color_rgb();
218+
chromaterm::colors::True::from_rgb(r, g, b)
219+
}
220+
}
221+
213222
#[cfg(feature = "owo-colors")]
214223
impl Language {
215224
/// Converts the color to RGB.

0 commit comments

Comments
 (0)