forked from extern/nushell
added ansi gradient in the spirit of fun (#3570)
This commit is contained in:
parent
aa1cd7eba6
commit
94fc8a1334
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -3272,6 +3272,8 @@ name = "nu-ansi-term"
|
|||||||
version = "0.32.1"
|
version = "0.32.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"doc-comment",
|
"doc-comment",
|
||||||
|
"itertools",
|
||||||
|
"overload",
|
||||||
"regex 1.5.4",
|
"regex 1.5.4",
|
||||||
"serde 1.0.126",
|
"serde 1.0.126",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -4294,6 +4296,12 @@ dependencies = [
|
|||||||
"num-traits 0.2.14",
|
"num-traits 0.2.14",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "overload"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking"
|
name = "parking"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
|
@ -18,10 +18,15 @@ doctest = false
|
|||||||
[features]
|
[features]
|
||||||
derive_serde_style = ["serde"]
|
derive_serde_style = ["serde"]
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies]
|
||||||
version = "1.0.90"
|
overload = "0.1.1"
|
||||||
features = ["derive"]
|
serde = { version = "1.0.90", features = ["derive"], optional = true }
|
||||||
optional = true
|
itertools = "0.10.0"
|
||||||
|
|
||||||
|
# [dependencies.serde]
|
||||||
|
# version = "1.0.90"
|
||||||
|
# features = ["derive"]
|
||||||
|
# optional = true
|
||||||
|
|
||||||
[target.'cfg(target_os="windows")'.dependencies.winapi]
|
[target.'cfg(target_os="windows")'.dependencies.winapi]
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
|
37
crates/nu-ansi-term/examples/gradient_colors.rs
Normal file
37
crates/nu-ansi-term/examples/gradient_colors.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
use nu_ansi_term::{build_all_gradient_text, Color, Gradient, Rgb, TargetGround};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let text = "lorem ipsum quia dolor sit amet, consectetur, adipisci velit";
|
||||||
|
|
||||||
|
// a gradient from hex colors
|
||||||
|
let start = Rgb::from_hex(0x40c9ff);
|
||||||
|
let end = Rgb::from_hex(0xe81cff);
|
||||||
|
let grad0 = Gradient::new(start, end);
|
||||||
|
|
||||||
|
// a gradient from color::rgb()
|
||||||
|
let start = Color::Rgb(64, 201, 255);
|
||||||
|
let end = Color::Rgb(232, 28, 255);
|
||||||
|
let gradient = Gradient::from_color_rgb(start, end);
|
||||||
|
|
||||||
|
// a slightly different gradient
|
||||||
|
let start2 = Color::Rgb(128, 64, 255);
|
||||||
|
let end2 = Color::Rgb(0, 28, 255);
|
||||||
|
let gradient2 = Gradient::from_color_rgb(start2, end2);
|
||||||
|
|
||||||
|
// reverse the gradient
|
||||||
|
let gradient3 = gradient.reverse();
|
||||||
|
|
||||||
|
let build_fg = gradient.build(text, TargetGround::Foreground);
|
||||||
|
println!("{}", build_fg);
|
||||||
|
let build_bg = gradient.build(text, TargetGround::Background);
|
||||||
|
println!("{}", build_bg);
|
||||||
|
let bgt = build_all_gradient_text(text, gradient, gradient2);
|
||||||
|
println!("{}", bgt);
|
||||||
|
let bgt2 = build_all_gradient_text(text, gradient, gradient3);
|
||||||
|
println!("{}", bgt2);
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
grad0.build("nushell is awesome", TargetGround::Foreground)
|
||||||
|
);
|
||||||
|
}
|
105
crates/nu-ansi-term/src/gradient.rs
Normal file
105
crates/nu-ansi-term/src/gradient.rs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
use crate::{rgb::Rgb, Color};
|
||||||
|
|
||||||
|
/// Linear color gradient between two color stops
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Gradient {
|
||||||
|
/// Start Color of Gradient
|
||||||
|
pub start: Rgb,
|
||||||
|
|
||||||
|
/// End Color of Gradient
|
||||||
|
pub end: Rgb,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gradient {
|
||||||
|
/// Creates a new [Gradient] with two [Rgb] colors, `start` and `end`
|
||||||
|
#[inline]
|
||||||
|
pub const fn new(start: Rgb, end: Rgb) -> Self {
|
||||||
|
Self { start, end }
|
||||||
|
}
|
||||||
|
pub const fn from_color_rgb(start: Color, end: Color) -> Self {
|
||||||
|
let start_grad = match start {
|
||||||
|
Color::Rgb(r, g, b) => Rgb { r, g, b },
|
||||||
|
_ => Rgb { r: 0, g: 0, b: 0 },
|
||||||
|
};
|
||||||
|
let end_grad = match end {
|
||||||
|
Color::Rgb(r, g, b) => Rgb { r, g, b },
|
||||||
|
_ => Rgb { r: 0, g: 0, b: 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
start: start_grad,
|
||||||
|
end: end_grad,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the [Rgb] color between `start` and `end` for `t`
|
||||||
|
pub fn at(&self, t: f32) -> Rgb {
|
||||||
|
self.start.lerp(self.end, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the reverse of `self`
|
||||||
|
#[inline]
|
||||||
|
pub const fn reverse(&self) -> Self {
|
||||||
|
Self::new(self.end, self.start)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn build(&self, text: &str, target: TargetGround) -> String {
|
||||||
|
let delta = 1.0 / text.len() as f32;
|
||||||
|
let mut result = text.char_indices().fold(String::new(), |mut acc, (i, c)| {
|
||||||
|
let temp = format!(
|
||||||
|
"\x1B[{}m{}",
|
||||||
|
self.at(i as f32 * delta).ansi_color_code(target),
|
||||||
|
c
|
||||||
|
);
|
||||||
|
acc.push_str(&temp);
|
||||||
|
acc
|
||||||
|
});
|
||||||
|
|
||||||
|
result.push_str("\x1B[0m");
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn build_all_gradient_text(text: &str, foreground: Gradient, background: Gradient) -> String {
|
||||||
|
let delta = 1.0 / text.len() as f32;
|
||||||
|
let mut result = text.char_indices().fold(String::new(), |mut acc, (i, c)| {
|
||||||
|
let step = i as f32 * delta;
|
||||||
|
let temp = format!(
|
||||||
|
"\x1B[{};{}m{}",
|
||||||
|
foreground
|
||||||
|
.at(step)
|
||||||
|
.ansi_color_code(TargetGround::Foreground),
|
||||||
|
background
|
||||||
|
.at(step)
|
||||||
|
.ansi_color_code(TargetGround::Background),
|
||||||
|
c
|
||||||
|
);
|
||||||
|
acc.push_str(&temp);
|
||||||
|
acc
|
||||||
|
});
|
||||||
|
|
||||||
|
result.push_str("\x1B[0m");
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum TargetGround {
|
||||||
|
Foreground,
|
||||||
|
Background,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TargetGround {
|
||||||
|
#[inline]
|
||||||
|
pub const fn code(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Self::Foreground => 30,
|
||||||
|
Self::Background => 40,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ANSIColorCode {
|
||||||
|
fn ansi_color_code(&self, target: TargetGround) -> String;
|
||||||
|
}
|
@ -121,13 +121,13 @@
|
|||||||
//! `Fixed` colors instead, but there’s nothing to be gained by doing so
|
//! `Fixed` colors instead, but there’s nothing to be gained by doing so
|
||||||
//! either.
|
//! either.
|
||||||
//!
|
//!
|
||||||
//! You can also access full 24-bit color by using the `Color::RGB` variant,
|
//! You can also access full 24-bit color by using the `Color::Rgb` variant,
|
||||||
//! which takes separate `u8` arguments for red, green, and blue:
|
//! which takes separate `u8` arguments for red, green, and blue:
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use nu_ansi_term::Color::RGB;
|
//! use nu_ansi_term::Color::Rgb;
|
||||||
//!
|
//!
|
||||||
//! RGB(70, 130, 180).paint("Steel blue");
|
//! Rgb(70, 130, 180).paint("Steel blue");
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Combining successive colored strings
|
//! ## Combining successive colored strings
|
||||||
@ -233,7 +233,7 @@
|
|||||||
#![crate_type = "rlib"]
|
#![crate_type = "rlib"]
|
||||||
#![crate_type = "dylib"]
|
#![crate_type = "dylib"]
|
||||||
#![warn(missing_copy_implementations)]
|
#![warn(missing_copy_implementations)]
|
||||||
#![warn(missing_docs)]
|
// #![warn(missing_docs)]
|
||||||
#![warn(trivial_casts, trivial_numeric_casts)]
|
#![warn(trivial_casts, trivial_numeric_casts)]
|
||||||
// #![warn(unused_extern_crates, unused_qualifications)]
|
// #![warn(unused_extern_crates, unused_qualifications)]
|
||||||
|
|
||||||
@ -265,3 +265,9 @@ mod util;
|
|||||||
pub use util::*;
|
pub use util::*;
|
||||||
|
|
||||||
mod debug;
|
mod debug;
|
||||||
|
|
||||||
|
pub mod gradient;
|
||||||
|
pub use gradient::*;
|
||||||
|
|
||||||
|
mod rgb;
|
||||||
|
pub use rgb::*;
|
||||||
|
173
crates/nu-ansi-term/src/rgb.rs
Normal file
173
crates/nu-ansi-term/src/rgb.rs
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
// Code liberally borrowed from here
|
||||||
|
// https://github.com/navierr/coloriz
|
||||||
|
use std::ops;
|
||||||
|
use std::u32;
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Rgb {
|
||||||
|
/// Red
|
||||||
|
pub r: u8,
|
||||||
|
/// Green
|
||||||
|
pub g: u8,
|
||||||
|
/// Blue
|
||||||
|
pub b: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rgb {
|
||||||
|
/// Creates a new [Rgb] color
|
||||||
|
#[inline]
|
||||||
|
pub const fn new(r: u8, g: u8, b: u8) -> Self {
|
||||||
|
Self { r, g, b }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new [Rgb] color with a hex code
|
||||||
|
#[inline]
|
||||||
|
pub const fn from_hex(hex: u32) -> Self {
|
||||||
|
Self::new((hex >> 16) as u8, (hex >> 8) as u8, hex as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_hex_string(hex: String) -> Self {
|
||||||
|
if hex.chars().count() == 8 && hex.starts_with("0x") {
|
||||||
|
// eprintln!("hex:{:?}", hex);
|
||||||
|
let (_, value_string) = hex.split_at(2);
|
||||||
|
// eprintln!("value_string:{:?}", value_string);
|
||||||
|
let int_val = u64::from_str_radix(value_string, 16);
|
||||||
|
match int_val {
|
||||||
|
Ok(num) => Self::new(
|
||||||
|
((num & 0xff0000) >> 16) as u8,
|
||||||
|
((num & 0xff00) >> 8) as u8,
|
||||||
|
(num & 0xff) as u8,
|
||||||
|
),
|
||||||
|
// Don't fail, just make the color black
|
||||||
|
// Should we fail?
|
||||||
|
_ => Self::new(0, 0, 0),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Don't fail, just make the color black.
|
||||||
|
// Should we fail?
|
||||||
|
Self::new(0, 0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new [Rgb] color with three [f32] values
|
||||||
|
pub fn from_f32(r: f32, g: f32, b: f32) -> Self {
|
||||||
|
Self::new(
|
||||||
|
(r.clamp(0.0, 1.0) * 255.0) as u8,
|
||||||
|
(g.clamp(0.0, 1.0) * 255.0) as u8,
|
||||||
|
(b.clamp(0.0, 1.0) * 255.0) as u8,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a grayscale [Rgb] color
|
||||||
|
#[inline]
|
||||||
|
pub const fn gray(x: u8) -> Self {
|
||||||
|
Self::new(x, x, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a grayscale [Rgb] color with a [f32] value
|
||||||
|
pub fn gray_f32(x: f32) -> Self {
|
||||||
|
Self::from_f32(x, x, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new [Rgb] color from a [HSL] color
|
||||||
|
// pub fn from_hsl(hsl: HSL) -> Self {
|
||||||
|
// if hsl.s == 0.0 {
|
||||||
|
// return Self::gray_f32(hsl.l);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let q = if hsl.l < 0.5 {
|
||||||
|
// hsl.l * (1.0 + hsl.s)
|
||||||
|
// } else {
|
||||||
|
// hsl.l + hsl.s - hsl.l * hsl.s
|
||||||
|
// };
|
||||||
|
// let p = 2.0 * hsl.l - q;
|
||||||
|
// let h2c = |t: f32| {
|
||||||
|
// let t = t.clamp(0.0, 1.0);
|
||||||
|
// if 6.0 * t < 1.0 {
|
||||||
|
// p + 6.0 * (q - p) * t
|
||||||
|
// } else if t < 0.5 {
|
||||||
|
// q
|
||||||
|
// } else if 1.0 < 1.5 * t {
|
||||||
|
// p + 6.0 * (q - p) * (1.0 / 1.5 - t)
|
||||||
|
// } else {
|
||||||
|
// p
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// Self::from_f32(h2c(hsl.h + 1.0 / 3.0), h2c(hsl.h), h2c(hsl.h - 1.0 / 3.0))
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// Computes the linear interpolation between `self` and `other` for `t`
|
||||||
|
pub fn lerp(&self, other: Self, t: f32) -> Self {
|
||||||
|
let t = t.clamp(0.0, 1.0);
|
||||||
|
self * (1.0 - t) + other * t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(u8, u8, u8)> for Rgb {
|
||||||
|
fn from((r, g, b): (u8, u8, u8)) -> Self {
|
||||||
|
Self::new(r, g, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(f32, f32, f32)> for Rgb {
|
||||||
|
fn from((r, g, b): (f32, f32, f32)) -> Self {
|
||||||
|
Self::from_f32(r, g, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use crate::ANSIColorCode;
|
||||||
|
use crate::TargetGround;
|
||||||
|
impl ANSIColorCode for Rgb {
|
||||||
|
fn ansi_color_code(&self, target: TargetGround) -> String {
|
||||||
|
format!("{};2;{};{};{}", target.code() + 8, self.r, self.g, self.b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
overload::overload!(
|
||||||
|
(lhs: ?Rgb) + (rhs: ?Rgb) -> Rgb {
|
||||||
|
Rgb::new(
|
||||||
|
lhs.r.saturating_add(rhs.r),
|
||||||
|
lhs.g.saturating_add(rhs.g),
|
||||||
|
lhs.b.saturating_add(rhs.b)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
overload::overload!(
|
||||||
|
(lhs: ?Rgb) - (rhs: ?Rgb) -> Rgb {
|
||||||
|
Rgb::new(
|
||||||
|
lhs.r.saturating_sub(rhs.r),
|
||||||
|
lhs.g.saturating_sub(rhs.g),
|
||||||
|
lhs.b.saturating_sub(rhs.b)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
overload::overload!(
|
||||||
|
(lhs: ?Rgb) * (rhs: ?f32) -> Rgb {
|
||||||
|
Rgb::new(
|
||||||
|
(lhs.r as f32 * rhs.clamp(0.0, 1.0)) as u8,
|
||||||
|
(lhs.g as f32 * rhs.clamp(0.0, 1.0)) as u8,
|
||||||
|
(lhs.b as f32 * rhs.clamp(0.0, 1.0)) as u8
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
overload::overload!(
|
||||||
|
(lhs: ?f32) * (rhs: ?Rgb) -> Rgb {
|
||||||
|
Rgb::new(
|
||||||
|
(rhs.r as f32 * lhs.clamp(0.0, 1.0)) as u8,
|
||||||
|
(rhs.g as f32 * lhs.clamp(0.0, 1.0)) as u8,
|
||||||
|
(rhs.b as f32 * lhs.clamp(0.0, 1.0)) as u8
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
overload::overload!(
|
||||||
|
-(rgb: ?Rgb) -> Rgb {
|
||||||
|
Rgb::new(
|
||||||
|
255 - rgb.r,
|
||||||
|
255 - rgb.g,
|
||||||
|
255 - rgb.b)
|
||||||
|
}
|
||||||
|
);
|
@ -364,7 +364,7 @@ pub enum Color {
|
|||||||
/// [cc]: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg
|
/// [cc]: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg
|
||||||
Fixed(u8),
|
Fixed(u8),
|
||||||
|
|
||||||
/// A 24-bit RGB color, as specified by ISO-8613-3.
|
/// A 24-bit Rgb color, as specified by ISO-8613-3.
|
||||||
Rgb(u8, u8, u8),
|
Rgb(u8, u8, u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -546,7 +546,7 @@ impl Color {
|
|||||||
/// ```
|
/// ```
|
||||||
/// use nu_ansi_term::Color;
|
/// use nu_ansi_term::Color;
|
||||||
///
|
///
|
||||||
/// let style = Color::RGB(31, 31, 31).on(Color::White);
|
/// let style = Color::Rgb(31, 31, 31).on(Color::White);
|
||||||
/// println!("{}", style.paint("eyyyy"));
|
/// println!("{}", style.paint("eyyyy"));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn on(self, background: Color) -> Style {
|
pub fn on(self, background: Color) -> Style {
|
||||||
@ -584,13 +584,13 @@ mod serde_json_tests {
|
|||||||
let colors = &[
|
let colors = &[
|
||||||
Color::Red,
|
Color::Red,
|
||||||
Color::Blue,
|
Color::Blue,
|
||||||
Color::RGB(123, 123, 123),
|
Color::Rgb(123, 123, 123),
|
||||||
Color::Fixed(255),
|
Color::Fixed(255),
|
||||||
];
|
];
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serde_json::to_string(&colors).unwrap(),
|
serde_json::to_string(&colors).unwrap(),
|
||||||
String::from("[\"Red\",\"Blue\",{\"RGB\":[123,123,123]},{\"Fixed\":255}]")
|
String::from("[\"Red\",\"Blue\",{\"Rgb\":[123,123,123]},{\"Fixed\":255}]")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -599,7 +599,7 @@ mod serde_json_tests {
|
|||||||
let colors = &[
|
let colors = &[
|
||||||
Color::Red,
|
Color::Red,
|
||||||
Color::Blue,
|
Color::Blue,
|
||||||
Color::RGB(123, 123, 123),
|
Color::Rgb(123, 123, 123),
|
||||||
Color::Fixed(255),
|
Color::Fixed(255),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -146,6 +146,7 @@ pub(crate) use autoview::Autoview;
|
|||||||
pub(crate) use cd::Cd;
|
pub(crate) use cd::Cd;
|
||||||
|
|
||||||
pub(crate) use ansi::Ansi;
|
pub(crate) use ansi::Ansi;
|
||||||
|
pub(crate) use ansi::AnsiGradient;
|
||||||
pub(crate) use ansi::AnsiStrip;
|
pub(crate) use ansi::AnsiStrip;
|
||||||
pub(crate) use append::Command as Append;
|
pub(crate) use append::Command as Append;
|
||||||
pub(crate) use autoenv::Autoenv;
|
pub(crate) use autoenv::Autoenv;
|
||||||
|
@ -331,7 +331,7 @@ pub fn str_to_ansi(s: &str) -> Option<String> {
|
|||||||
// OSC escape (Operating system command)
|
// OSC escape (Operating system command)
|
||||||
"osc" | "escape_right" => Some("\x1b]".to_string()),
|
"osc" | "escape_right" => Some("\x1b]".to_string()),
|
||||||
|
|
||||||
// Ansi RGB - Needs to be 32;2;r;g;b or 48;2;r;g;b
|
// Ansi Rgb - Needs to be 32;2;r;g;b or 48;2;r;g;b
|
||||||
// assuming the rgb will be passed via command and no here
|
// assuming the rgb will be passed via command and no here
|
||||||
"rgb_fg" => Some("\x1b[38;2;".to_string()),
|
"rgb_fg" => Some("\x1b[38;2;".to_string()),
|
||||||
"rgb_bg" => Some("\x1b[48;2;".to_string()),
|
"rgb_bg" => Some("\x1b[48;2;".to_string()),
|
||||||
|
326
crates/nu-command/src/commands/ansi/gradient.rs
Normal file
326
crates/nu-command/src/commands/ansi/gradient.rs
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
use nu_ansi_term::{build_all_gradient_text, gradient::TargetGround, Gradient, Rgb};
|
||||||
|
use nu_engine::WholeStreamCommand;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::ShellTypeName;
|
||||||
|
use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
use nu_source::Tag;
|
||||||
|
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl WholeStreamCommand for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"ansi gradient"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("ansi gradient")
|
||||||
|
.named(
|
||||||
|
"fgstart",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"foreground gradient start color in hex (0x123456)",
|
||||||
|
Some('a'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"fgend",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"foreground gradient end color in hex",
|
||||||
|
Some('b'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"bgstart",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"background gradient start color in hex",
|
||||||
|
Some('c'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"bgend",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"background gradient end color in hex",
|
||||||
|
Some('d'),
|
||||||
|
)
|
||||||
|
.rest(
|
||||||
|
SyntaxShape::ColumnPath,
|
||||||
|
"optionally, draw gradients using text from column paths",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"draw text with a provided start and end code making a gradient"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
operate(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "draw text in a gradient with foreground start and end colors",
|
||||||
|
example:
|
||||||
|
"echo 'Hello, Nushell! This is a gradient.' | ansi gradient --fgstart 0x40c9ff --fgend 0xe81cff",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "draw text in a gradient with foreground start and end colors and background start and end colors",
|
||||||
|
example:
|
||||||
|
"echo 'Hello, Nushell! This is a gradient.' | ansi gradient --fgstart 0x40c9ff --fgend 0xe81cff --bgstart 0xe81cff --bgend 0x40c9ff",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "draw text in a gradient by specifying foreground start color - end color is assumed to be black",
|
||||||
|
example:
|
||||||
|
"echo 'Hello, Nushell! This is a gradient.' | ansi gradient --fgstart 0x40c9ff",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "draw text in a gradient by specifying foreground end color - start color is assumed to be black",
|
||||||
|
example:
|
||||||
|
"echo 'Hello, Nushell! This is a gradient.' | ansi gradient --fgend 0xe81cff",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
|
let args = args.evaluate_once()?;
|
||||||
|
let fgstart: Option<Value> = args.get_flag("fgstart")?;
|
||||||
|
let fgend: Option<Value> = args.get_flag("fgend")?;
|
||||||
|
let bgstart: Option<Value> = args.get_flag("bgstart")?;
|
||||||
|
let bgend: Option<Value> = args.get_flag("bgend")?;
|
||||||
|
let column_paths: Vec<_> = args.rest(0)?;
|
||||||
|
|
||||||
|
let fgs_hex = fgstart.map(|color| Rgb::from_hex_string(color.convert_to_string()));
|
||||||
|
let fge_hex = fgend.map(|color| Rgb::from_hex_string(color.convert_to_string()));
|
||||||
|
let bgs_hex = bgstart.map(|color| Rgb::from_hex_string(color.convert_to_string()));
|
||||||
|
let bge_hex = bgend.map(|color| Rgb::from_hex_string(color.convert_to_string()));
|
||||||
|
|
||||||
|
let result: Vec<Value> = args
|
||||||
|
.input
|
||||||
|
.map(move |v| {
|
||||||
|
if column_paths.is_empty() {
|
||||||
|
action(&v, v.tag(), fgs_hex, fge_hex, bgs_hex, bge_hex)
|
||||||
|
} else {
|
||||||
|
let mut ret = v;
|
||||||
|
|
||||||
|
for path in &column_paths {
|
||||||
|
ret = ret.swap_data_by_column_path(
|
||||||
|
path,
|
||||||
|
Box::new(move |old| {
|
||||||
|
action(old, old.tag(), fgs_hex, fge_hex, bgs_hex, bge_hex)
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<Value>, _>>()?;
|
||||||
|
|
||||||
|
Ok(OutputStream::from_stream(result.into_iter()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action(
|
||||||
|
input: &Value,
|
||||||
|
tag: impl Into<Tag>,
|
||||||
|
fg_start: Option<Rgb>,
|
||||||
|
fg_end: Option<Rgb>,
|
||||||
|
bg_start: Option<Rgb>,
|
||||||
|
bg_end: Option<Rgb>,
|
||||||
|
) -> Result<Value, ShellError> {
|
||||||
|
let tag = tag.into();
|
||||||
|
|
||||||
|
match &input.value {
|
||||||
|
UntaggedValue::Primitive(Primitive::String(astring)) => {
|
||||||
|
match (fg_start, fg_end, bg_start, bg_end) {
|
||||||
|
(None, None, None, None) => {
|
||||||
|
// Error - no colors
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"please supply color parameters",
|
||||||
|
"please supply foreground and/or background color parameters",
|
||||||
|
Tag::unknown(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
(None, None, None, Some(bg_end)) => {
|
||||||
|
// Error - missing bg_start, so assume black
|
||||||
|
let bg_start = Rgb::new(0, 0, 0);
|
||||||
|
let gradient = Gradient::new(bg_start, bg_end);
|
||||||
|
let gradient_string = gradient.build(astring, TargetGround::Background);
|
||||||
|
Ok(UntaggedValue::string(gradient_string).into_value(tag))
|
||||||
|
}
|
||||||
|
(None, None, Some(bg_start), None) => {
|
||||||
|
// Error - missing bg_end, so assume black
|
||||||
|
let bg_end = Rgb::new(0, 0, 0);
|
||||||
|
let gradient = Gradient::new(bg_start, bg_end);
|
||||||
|
let gradient_string = gradient.build(astring, TargetGround::Background);
|
||||||
|
Ok(UntaggedValue::string(gradient_string).into_value(tag))
|
||||||
|
}
|
||||||
|
(None, None, Some(bg_start), Some(bg_end)) => {
|
||||||
|
// Background Only
|
||||||
|
let gradient = Gradient::new(bg_start, bg_end);
|
||||||
|
let gradient_string = gradient.build(astring, TargetGround::Background);
|
||||||
|
Ok(UntaggedValue::string(gradient_string).into_value(tag))
|
||||||
|
}
|
||||||
|
(None, Some(fg_end), None, None) => {
|
||||||
|
// Error - missing fg_start, so assume black
|
||||||
|
let fg_start = Rgb::new(0, 0, 0);
|
||||||
|
let gradient = Gradient::new(fg_start, fg_end);
|
||||||
|
let gradient_string = gradient.build(astring, TargetGround::Foreground);
|
||||||
|
Ok(UntaggedValue::string(gradient_string).into_value(tag))
|
||||||
|
}
|
||||||
|
(None, Some(fg_end), None, Some(bg_end)) => {
|
||||||
|
// missin fg_start and bg_start, so assume black
|
||||||
|
let fg_start = Rgb::new(0, 0, 0);
|
||||||
|
let bg_start = Rgb::new(0, 0, 0);
|
||||||
|
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||||
|
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||||
|
let gradient_string =
|
||||||
|
build_all_gradient_text(astring, fg_gradient, bg_gradient);
|
||||||
|
Ok(UntaggedValue::string(gradient_string).into_value(tag))
|
||||||
|
}
|
||||||
|
(None, Some(fg_end), Some(bg_start), None) => {
|
||||||
|
// Error - missing fg_start and bg_end
|
||||||
|
let fg_start = Rgb::new(0, 0, 0);
|
||||||
|
let bg_end = Rgb::new(0, 0, 0);
|
||||||
|
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||||
|
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||||
|
let gradient_string =
|
||||||
|
build_all_gradient_text(astring, fg_gradient, bg_gradient);
|
||||||
|
Ok(UntaggedValue::string(gradient_string).into_value(tag))
|
||||||
|
}
|
||||||
|
(None, Some(fg_end), Some(bg_start), Some(bg_end)) => {
|
||||||
|
// Error - missing fg_start, so assume black
|
||||||
|
let fg_start = Rgb::new(0, 0, 0);
|
||||||
|
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||||
|
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||||
|
let gradient_string =
|
||||||
|
build_all_gradient_text(astring, fg_gradient, bg_gradient);
|
||||||
|
Ok(UntaggedValue::string(gradient_string).into_value(tag))
|
||||||
|
}
|
||||||
|
(Some(fg_start), None, None, None) => {
|
||||||
|
// Error - missing fg_end, so assume black
|
||||||
|
let fg_end = Rgb::new(0, 0, 0);
|
||||||
|
let gradient = Gradient::new(fg_start, fg_end);
|
||||||
|
let gradient_string = gradient.build(astring, TargetGround::Foreground);
|
||||||
|
Ok(UntaggedValue::string(gradient_string).into_value(tag))
|
||||||
|
}
|
||||||
|
(Some(fg_start), None, None, Some(bg_end)) => {
|
||||||
|
// Error - missing fg_end, bg_start, so assume black
|
||||||
|
let fg_end = Rgb::new(0, 0, 0);
|
||||||
|
let bg_start = Rgb::new(0, 0, 0);
|
||||||
|
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||||
|
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||||
|
let gradient_string =
|
||||||
|
build_all_gradient_text(astring, fg_gradient, bg_gradient);
|
||||||
|
Ok(UntaggedValue::string(gradient_string).into_value(tag))
|
||||||
|
}
|
||||||
|
(Some(fg_start), None, Some(bg_start), None) => {
|
||||||
|
// Error - missing fg_end, bg_end, so assume black
|
||||||
|
let fg_end = Rgb::new(0, 0, 0);
|
||||||
|
let bg_end = Rgb::new(0, 0, 0);
|
||||||
|
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||||
|
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||||
|
let gradient_string =
|
||||||
|
build_all_gradient_text(astring, fg_gradient, bg_gradient);
|
||||||
|
Ok(UntaggedValue::string(gradient_string).into_value(tag))
|
||||||
|
}
|
||||||
|
(Some(fg_start), None, Some(bg_start), Some(bg_end)) => {
|
||||||
|
// Error - missing fg_end, so assume black
|
||||||
|
let fg_end = Rgb::new(0, 0, 0);
|
||||||
|
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||||
|
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||||
|
let gradient_string =
|
||||||
|
build_all_gradient_text(astring, fg_gradient, bg_gradient);
|
||||||
|
Ok(UntaggedValue::string(gradient_string).into_value(tag))
|
||||||
|
}
|
||||||
|
(Some(fg_start), Some(fg_end), None, None) => {
|
||||||
|
// Foreground Only
|
||||||
|
let gradient = Gradient::new(fg_start, fg_end);
|
||||||
|
let gradient_string = gradient.build(astring, TargetGround::Foreground);
|
||||||
|
Ok(UntaggedValue::string(gradient_string).into_value(tag))
|
||||||
|
}
|
||||||
|
(Some(fg_start), Some(fg_end), None, Some(bg_end)) => {
|
||||||
|
// Error - missing bg_start, so assume black
|
||||||
|
let bg_start = Rgb::new(0, 0, 0);
|
||||||
|
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||||
|
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||||
|
let gradient_string =
|
||||||
|
build_all_gradient_text(astring, fg_gradient, bg_gradient);
|
||||||
|
Ok(UntaggedValue::string(gradient_string).into_value(tag))
|
||||||
|
}
|
||||||
|
(Some(fg_start), Some(fg_end), Some(bg_start), None) => {
|
||||||
|
// Error - missing bg_end, so assume black
|
||||||
|
let bg_end = Rgb::new(0, 0, 0);
|
||||||
|
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||||
|
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||||
|
let gradient_string =
|
||||||
|
build_all_gradient_text(astring, fg_gradient, bg_gradient);
|
||||||
|
Ok(UntaggedValue::string(gradient_string).into_value(tag))
|
||||||
|
}
|
||||||
|
(Some(fg_start), Some(fg_end), Some(bg_start), Some(bg_end)) => {
|
||||||
|
// Foreground and Background Gradient
|
||||||
|
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||||
|
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||||
|
let gradient_string =
|
||||||
|
build_all_gradient_text(astring, fg_gradient, bg_gradient);
|
||||||
|
Ok(UntaggedValue::string(gradient_string).into_value(tag))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
let got = format!("got {}", other.type_name());
|
||||||
|
|
||||||
|
Err(ShellError::labeled_error(
|
||||||
|
"value is not string",
|
||||||
|
got,
|
||||||
|
tag.span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::ShellError;
|
||||||
|
use super::{action, SubCommand};
|
||||||
|
use nu_ansi_term::Rgb;
|
||||||
|
use nu_protocol::UntaggedValue;
|
||||||
|
use nu_source::Tag;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn test_stripping() {
|
||||||
|
// let input_string =
|
||||||
|
// UntaggedValue::string("\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld")
|
||||||
|
// .into_untagged_value();
|
||||||
|
// let expected = UntaggedValue::string("Hello Nu World").into_untagged_value();
|
||||||
|
|
||||||
|
// let actual = action(&input_string, Tag::unknown()).unwrap();
|
||||||
|
// assert_eq!(actual, expected);
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fg_gradient() {
|
||||||
|
let input_string = UntaggedValue::string("Hello, World!").into_untagged_value();
|
||||||
|
let expected = UntaggedValue::string("\u{1b}[38;2;64;201;255mH\u{1b}[38;2;76;187;254me\u{1b}[38;2;89;174;254ml\u{1b}[38;2;102;160;254ml\u{1b}[38;2;115;147;254mo\u{1b}[38;2;128;133;254m,\u{1b}[38;2;141;120;254m \u{1b}[38;2;153;107;254mW\u{1b}[38;2;166;94;254mo\u{1b}[38;2;179;80;254mr\u{1b}[38;2;192;67;254ml\u{1b}[38;2;205;53;254md\u{1b}[38;2;218;40;254m!\u{1b}[0m").into_untagged_value();
|
||||||
|
let fg_start = Rgb::from_hex_string("0x40c9ff".to_string());
|
||||||
|
let fg_end = Rgb::from_hex_string("0xe81cff".to_string());
|
||||||
|
let actual = action(
|
||||||
|
&input_string,
|
||||||
|
Tag::unknown(),
|
||||||
|
Some(fg_start),
|
||||||
|
Some(fg_end),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
mod command;
|
mod command;
|
||||||
|
mod gradient;
|
||||||
mod strip;
|
mod strip;
|
||||||
|
|
||||||
pub use command::Command as Ansi;
|
pub use command::Command as Ansi;
|
||||||
|
pub use gradient::SubCommand as AnsiGradient;
|
||||||
pub use strip::SubCommand as AnsiStrip;
|
pub use strip::SubCommand as AnsiStrip;
|
||||||
|
@ -111,6 +111,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
|||||||
whole_stream_command(BuildString),
|
whole_stream_command(BuildString),
|
||||||
whole_stream_command(Ansi),
|
whole_stream_command(Ansi),
|
||||||
whole_stream_command(AnsiStrip),
|
whole_stream_command(AnsiStrip),
|
||||||
|
whole_stream_command(AnsiGradient),
|
||||||
whole_stream_command(Char),
|
whole_stream_command(Char),
|
||||||
// Column manipulation
|
// Column manipulation
|
||||||
whole_stream_command(DropColumn),
|
whole_stream_command(DropColumn),
|
||||||
|
Loading…
Reference in New Issue
Block a user