refactor: remove ansi_term dependency

This commit is contained in:
Amaan Qureshi 2024-05-25 19:59:50 -04:00
parent 35c0fad26f
commit c440f2a7c6
7 changed files with 164 additions and 181 deletions

View file

@ -1,8 +1,14 @@
use std::{
collections::HashMap, fmt::Write, fs, io, path, str, sync::atomic::AtomicUsize, time::Instant,
collections::HashMap,
fmt::Write,
fs,
io::{self, Write as _},
path, str,
sync::atomic::AtomicUsize,
time::Instant,
};
use ansi_term::Color;
use anstyle::{Ansi256Color, AnsiColor, Color, Effects, RgbColor};
use anyhow::Result;
use lazy_static::lazy_static;
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
@ -43,7 +49,7 @@ lazy_static! {
#[derive(Debug, Default)]
pub struct Style {
pub ansi: ansi_term::Style,
pub ansi: anstyle::Style,
pub css: Option<String>,
}
@ -103,30 +109,37 @@ impl Serialize for Theme {
let mut map = serializer.serialize_map(Some(self.styles.len()))?;
for (name, style) in self.highlight_names.iter().zip(&self.styles) {
let style = &style.ansi;
let color = style.foreground.map(|color| match color {
Color::Black => json!("black"),
Color::Blue => json!("blue"),
Color::Cyan => json!("cyan"),
Color::Green => json!("green"),
Color::Purple => json!("purple"),
Color::Red => json!("red"),
Color::White => json!("white"),
Color::Yellow => json!("yellow"),
Color::RGB(r, g, b) => json!(format!("#{:x?}{:x?}{:x?}", r, g, b)),
Color::Fixed(n) => json!(n),
let color = style.get_fg_color().map(|color| match color {
Color::Ansi(color) => match color {
AnsiColor::Black => json!("black"),
AnsiColor::Blue => json!("blue"),
AnsiColor::Cyan => json!("cyan"),
AnsiColor::Green => json!("green"),
AnsiColor::Magenta => json!("purple"),
AnsiColor::Red => json!("red"),
AnsiColor::White => json!("white"),
AnsiColor::Yellow => json!("yellow"),
_ => unreachable!(),
},
Color::Ansi256(Ansi256Color(n)) => json!(n),
Color::Rgb(RgbColor(r, g, b)) => json!(format!("#{r:x?}{g:x?}{b:x?}")),
});
if style.is_bold || style.is_italic || style.is_underline {
let effects = style.get_effects();
if effects.contains(Effects::BOLD)
|| effects.contains(Effects::ITALIC)
|| effects.contains(Effects::UNDERLINE)
{
let mut style_json = HashMap::new();
if let Some(color) = color {
style_json.insert("color", color);
}
if style.is_bold {
if effects.contains(Effects::BOLD) {
style_json.insert("bold", Value::Bool(true));
}
if style.is_italic {
if effects.contains(Effects::ITALIC) {
style_json.insert("italic", Value::Bool(true));
}
if style.is_underline {
if effects.contains(Effects::UNDERLINE) {
style_json.insert("underline", Value::Bool(true));
}
map.serialize_entry(&name, &style_json)?;
@ -191,7 +204,7 @@ fn parse_style(style: &mut Style, json: Value) {
}
"color" => {
if let Some(color) = parse_color(value) {
style.ansi = style.ansi.fg(color);
style.ansi = style.ansi.fg_color(Some(color));
}
}
_ => {}
@ -199,34 +212,36 @@ fn parse_style(style: &mut Style, json: Value) {
}
style.css = Some(style_to_css(style.ansi));
} else if let Some(color) = parse_color(json) {
style.ansi = style.ansi.fg(color);
style.ansi = style.ansi.fg_color(Some(color));
style.css = Some(style_to_css(style.ansi));
} else {
style.css = None;
}
if let Some(Color::RGB(red, green, blue)) = style.ansi.foreground {
if let Some(Color::Rgb(RgbColor(red, green, blue))) = style.ansi.get_fg_color() {
if !terminal_supports_truecolor() {
style.ansi = style.ansi.fg(closest_xterm_color(red, green, blue));
style.ansi = style
.ansi
.fg_color(Some(closest_xterm_color(red, green, blue)));
}
}
}
fn parse_color(json: Value) -> Option<Color> {
match json {
Value::Number(n) => n.as_u64().map(|n| Color::Fixed(n as u8)),
Value::Number(n) => n.as_u64().map(|n| Color::Ansi256(Ansi256Color(n as u8))),
Value::String(s) => match s.to_lowercase().as_str() {
"black" => Some(Color::Black),
"blue" => Some(Color::Blue),
"cyan" => Some(Color::Cyan),
"green" => Some(Color::Green),
"purple" => Some(Color::Purple),
"red" => Some(Color::Red),
"white" => Some(Color::White),
"yellow" => Some(Color::Yellow),
"black" => Some(Color::Ansi(AnsiColor::Black)),
"blue" => Some(Color::Ansi(AnsiColor::Blue)),
"cyan" => Some(Color::Ansi(AnsiColor::Cyan)),
"green" => Some(Color::Ansi(AnsiColor::Green)),
"purple" => Some(Color::Ansi(AnsiColor::Magenta)),
"red" => Some(Color::Ansi(AnsiColor::Red)),
"white" => Some(Color::Ansi(AnsiColor::White)),
"yellow" => Some(Color::Ansi(AnsiColor::Yellow)),
s => {
if let Some((red, green, blue)) = hex_string_to_rgb(s) {
Some(Color::RGB(red, green, blue))
Some(Color::Rgb(RgbColor(red, green, blue)))
} else {
None
}
@ -252,18 +267,19 @@ fn hex_string_to_rgb(s: &str) -> Option<(u8, u8, u8)> {
}
}
fn style_to_css(style: ansi_term::Style) -> String {
fn style_to_css(style: anstyle::Style) -> String {
let mut result = "style='".to_string();
if style.is_underline {
let effects = style.get_effects();
if effects.contains(Effects::UNDERLINE) {
write!(&mut result, "text-decoration: underline;").unwrap();
}
if style.is_bold {
if effects.contains(Effects::BOLD) {
write!(&mut result, "font-weight: bold;").unwrap();
}
if style.is_italic {
if effects.contains(Effects::ITALIC) {
write!(&mut result, "font-style: italic;").unwrap();
}
if let Some(color) = style.foreground {
if let Some(color) = style.get_fg_color() {
write_color(&mut result, color);
}
result.push('\'');
@ -271,26 +287,22 @@ fn style_to_css(style: ansi_term::Style) -> String {
}
fn write_color(buffer: &mut String, color: Color) {
if let Color::RGB(r, g, b) = &color {
write!(buffer, "color: #{r:02x}{g:02x}{b:02x}").unwrap();
} else {
write!(
buffer,
"color: {}",
match color {
Color::Black => "black",
Color::Blue => "blue",
Color::Red => "red",
Color::Green => "green",
Color::Yellow => "yellow",
Color::Cyan => "cyan",
Color::Purple => "purple",
Color::White => "white",
Color::Fixed(n) => CSS_STYLES_BY_COLOR_ID[n as usize].as_str(),
Color::RGB(_, _, _) => unreachable!(),
}
)
.unwrap();
match color {
Color::Ansi(color) => match color {
AnsiColor::Black => write!(buffer, "color: black").unwrap(),
AnsiColor::Red => write!(buffer, "color: red").unwrap(),
AnsiColor::Green => write!(buffer, "color: green").unwrap(),
AnsiColor::Yellow => write!(buffer, "color: yellow").unwrap(),
AnsiColor::Blue => write!(buffer, "color: blue").unwrap(),
AnsiColor::Magenta => write!(buffer, "color: purple").unwrap(),
AnsiColor::Cyan => write!(buffer, "color: cyan").unwrap(),
AnsiColor::White => write!(buffer, "color: white").unwrap(),
_ => unreachable!(),
},
Color::Ansi256(Ansi256Color(n)) => {
write!(buffer, "color: {}", CSS_STYLES_BY_COLOR_ID[n as usize]).unwrap()
}
Color::Rgb(RgbColor(r, g, b)) => write!(buffer, "color: #{r:02x}{g:02x}{b:02x}").unwrap(),
}
}
@ -319,7 +331,9 @@ fn closest_xterm_color(red: u8, green: u8, blue: u8) -> Color {
(color_id, distance)
});
Color::Fixed(distances.min_by(|(_, d1), (_, d2)| d1.cmp(d2)).unwrap().0)
Color::Ansi256(Ansi256Color(
distances.min_by(|(_, d1), (_, d2)| d1.cmp(d2)).unwrap().0,
))
}
pub fn ansi(
@ -349,11 +363,10 @@ pub fn ansi(
style_stack.pop();
}
HighlightEvent::Source { start, end } => {
style_stack
.last()
.unwrap()
.paint(&source[start..end])
.write_to(&mut stdout)?;
let style = style_stack.last().unwrap();
write!(&mut stdout, "{style}").unwrap();
stdout.write_all(&source[start..end])?;
write!(&mut stdout, "{style:#}").unwrap();
}
}
}
@ -427,25 +440,34 @@ mod tests {
let original_environment_variable = env::var("COLORTERM");
let mut style = Style::default();
assert_eq!(style.ansi.foreground, None);
assert_eq!(style.ansi.get_fg_color(), None);
assert_eq!(style.css, None);
// darkcyan is an ANSI color and is preserved
env::set_var("COLORTERM", "");
parse_style(&mut style, Value::String(DARK_CYAN.to_string()));
assert_eq!(style.ansi.foreground, Some(Color::Fixed(36)));
assert_eq!(
style.ansi.get_fg_color(),
Some(Color::Ansi256(Ansi256Color(36)))
);
assert_eq!(style.css, Some("style=\'color: #00af87\'".to_string()));
// junglegreen is not an ANSI color and is preserved when the terminal supports it
env::set_var("COLORTERM", "truecolor");
parse_style(&mut style, Value::String(JUNGLE_GREEN.to_string()));
assert_eq!(style.ansi.foreground, Some(Color::RGB(38, 166, 154)));
assert_eq!(
style.ansi.get_fg_color(),
Some(Color::Rgb(RgbColor(38, 166, 154)))
);
assert_eq!(style.css, Some("style=\'color: #26a69a\'".to_string()));
// junglegreen gets approximated as darkcyan when the terminal does not support it
env::set_var("COLORTERM", "");
parse_style(&mut style, Value::String(JUNGLE_GREEN.to_string()));
assert_eq!(style.ansi.foreground, Some(Color::Fixed(36)));
assert_eq!(
style.ansi.get_fg_color(),
Some(Color::Ansi256(Ansi256Color(36)))
);
assert_eq!(style.css, Some("style=\'color: #26a69a\'".to_string()));
if let Ok(environment_variable) = original_environment_variable {

View file

@ -7,7 +7,7 @@ use std::{
str,
};
use ansi_term::Colour;
use anstyle::{AnsiColor, Color, Style};
use anyhow::{anyhow, Context, Result};
use difference::{Changeset, Difference};
use indoc::indoc;
@ -240,7 +240,7 @@ pub fn get_tmp_test_file(target_test: u32, color: bool) -> Result<(PathBuf, Vec<
println!(
"{target_test}. {}\n",
opt_color(color, Colour::Green, test_name)
paint(color.then_some(AnsiColor::Green), test_name)
);
Ok((test_path, languages))
@ -270,8 +270,8 @@ pub fn check_queries_at_path(language: &Language, path: &Path) -> Result<()> {
pub fn print_diff_key() {
println!(
"\ncorrect / {} / {}",
Colour::Green.paint("expected"),
Colour::Red.paint("unexpected")
paint(Some(AnsiColor::Green), "expected"),
paint(Some(AnsiColor::Red), "unexpected")
);
}
@ -288,14 +288,14 @@ pub fn print_diff(actual: &str, expected: &str, use_color: bool) {
}
Difference::Add(part) => {
if use_color {
print!("{}{}", Colour::Green.paint(part), changeset.split);
println!("{}{}", paint(Some(AnsiColor::Green), part), changeset.split);
} else {
print!("expected:\n{part}{}", changeset.split);
}
}
Difference::Rem(part) => {
if use_color {
print!("{}{}", Colour::Red.paint(part), changeset.split);
println!("{}{}", paint(Some(AnsiColor::Red), part), changeset.split);
} else {
print!("unexpected:\n{part}{}", changeset.split);
}
@ -305,12 +305,9 @@ pub fn print_diff(actual: &str, expected: &str, use_color: bool) {
println!();
}
pub fn opt_color(use_color: bool, color: ansi_term::Colour, text: &str) -> String {
if use_color {
color.paint(text).to_string()
} else {
text.to_string()
}
pub fn paint(color: Option<AnsiColor>, text: &str) -> String {
let style = Style::new().fg_color(color.map(Color::Ansi));
format!("{style}{text}{style:#}")
}
/// This will return false if we want to "fail fast". It will bail and not parse any more tests.
@ -340,7 +337,7 @@ fn run_tests(
println!(
"{:>3}.  {}",
opts.test_num,
opt_color(opts.color, Colour::Yellow, &name),
paint(opts.color.then_some(AnsiColor::Yellow), &name),
);
return Ok(true);
}
@ -349,7 +346,7 @@ fn run_tests(
println!(
"{:>3}.  {}",
opts.test_num,
opt_color(opts.color, Colour::Purple, &name)
paint(opts.color.then_some(AnsiColor::Magenta), &name),
);
return Ok(true);
}
@ -369,13 +366,13 @@ fn run_tests(
println!(
"{:>3}.  {}",
opts.test_num,
opt_color(opts.color, Colour::Green, &name)
paint(opts.color.then_some(AnsiColor::Green), &name)
);
} else {
println!(
"{:>3}.  {}",
opts.test_num,
opt_color(opts.color, Colour::Red, &name)
paint(opts.color.then_some(AnsiColor::Red), &name)
);
}
@ -392,7 +389,7 @@ fn run_tests(
println!(
"{:>3}. ✓ {}",
opts.test_num,
opt_color(opts.color, Colour::Green, &name),
paint(opts.color.then_some(AnsiColor::Green), &name)
);
if opts.update {
let input = String::from_utf8(input.clone()).unwrap();
@ -438,14 +435,14 @@ fn run_tests(
println!(
"{:>3}. ✓ {}",
opts.test_num,
opt_color(opts.color, Colour::Blue, &name)
paint(opts.color.then_some(AnsiColor::Blue), &name),
);
}
} else {
println!(
"{:>3}. ✗ {}",
opts.test_num,
opt_color(opts.color, Colour::Red, &name)
paint(opts.color.then_some(AnsiColor::Red), &name),
);
}
failures.push((name.clone(), actual, output.clone()));

View file

@ -1,6 +1,6 @@
use std::{fs, path::Path};
use ansi_term::Colour;
use anstyle::AnsiColor;
use anyhow::{anyhow, Result};
use tree_sitter::Point;
use tree_sitter_highlight::{Highlight, HighlightConfiguration, HighlightEvent, Highlighter};
@ -8,7 +8,7 @@ use tree_sitter_loader::{Config, Loader};
use super::{
query_testing::{parse_position_comments, Assertion},
test::opt_color,
test::paint,
util,
};
@ -108,9 +108,8 @@ fn test_highlights_indented(
Ok(assertion_count) => {
println!(
"✓ {} ({assertion_count} assertions)",
opt_color(
use_color,
Colour::Green,
paint(
use_color.then_some(AnsiColor::Green),
test_file_name.to_string_lossy().as_ref()
),
);
@ -118,9 +117,8 @@ fn test_highlights_indented(
Err(e) => {
println!(
"✗ {}",
opt_color(
use_color,
Colour::Red,
paint(
use_color.then_some(AnsiColor::Red),
test_file_name.to_string_lossy().as_ref()
)
);

View file

@ -1,6 +1,6 @@
use std::{fs, path::Path};
use ansi_term::Colour;
use anstyle::AnsiColor;
use anyhow::{anyhow, Result};
use tree_sitter::Point;
use tree_sitter_loader::{Config, Loader};
@ -8,7 +8,7 @@ use tree_sitter_tags::{TagsConfiguration, TagsContext};
use super::{
query_testing::{parse_position_comments, Assertion},
test::opt_color,
test::paint,
util,
};
@ -75,9 +75,8 @@ pub fn test_tags(
Ok(assertion_count) => {
println!(
" ✓ {} ({assertion_count} assertions)",
opt_color(
use_color,
Colour::Green,
paint(
use_color.then_some(AnsiColor::Green),
test_file_name.to_string_lossy().as_ref()
),
);
@ -85,9 +84,8 @@ pub fn test_tags(
Err(e) => {
println!(
" ✗ {}",
opt_color(
use_color,
Colour::Red,
paint(
use_color.then_some(AnsiColor::Red),
test_file_name.to_string_lossy().as_ref()
)
);