mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 08:06:03 +02:00
color_config now accepts closures as color values (#7141)
# Description Closes #6909. You can now add closures to your `color_config` themes. Whenever a value would be printed with `table`, the closure is run with the value piped-in. The closure must return either a {fg,bg,attr} record or a color name (`'light_red'` etc.). This returned style is used to colour the value. This is entirely backwards-compatible with existing config.nu files. Example code excerpt: ``` let my_theme = { header: green_bold bool: { if $in { 'light_cyan' } else { 'light_red' } } int: purple_bold filesize: { |e| if $e == 0b { 'gray' } else if $e < 1mb { 'purple_bold' } else { 'cyan_bold' } } duration: purple_bold date: { (date now) - $in | if $in > 1wk { 'cyan_bold' } else if $in > 1day { 'green_bold' } else { 'yellow_bold' } } range: yellow_bold string: { if $in =~ '^#\w{6}$' { $in } else { 'white' } } nothing: white ``` Example output with this in effect:    Slightly important notes: * Some color_config names, namely "separator", "empty" and "hints", pipe in `null` instead of a value. * Currently, doing anything non-trivial inside a closure has an understandably big perf hit. I currently do not actually recommend something like `string: { if $in =~ '^#\w{6}$' { $in } else { 'white' } }` for serious work, mainly because of the abundance of string-type data in the world. Nevertheless, lesser-used types like "date" and "duration" work well with this. * I had to do some reorganisation in order to make it possible to call `eval_block()` that late in table rendering. I invented a new struct called "StyleComputer" which holds the engine_state and stack of the initial `table` command (implicit or explicit). * StyleComputer has a `compute()` method which takes a color_config name and a nu value, and always returns the correct Style, so you don't have to worry about A) the color_config value was set at all, B) whether it was set to a closure or not, or C) which default style to use in those cases. * Currently, errors encountered during execution of the closures are thrown in the garbage. Any other ideas are welcome. (Nonetheless, errors result in a huge perf hit when they are encountered. I think what should be done is to assume something terrible happened to the user's config and invalidate the StyleComputer for that `table` run, thus causing subsequent output to just be Style::default().) * More thorough tests are forthcoming - ran into some difficulty using `nu!` to take an alternative config, and for some reason `let-env config =` statements don't seem to work inside `nu!` pipelines(???) * The default config.nu has not been updated to make use of this yet. Do tell if you think I should incorporate that into this. # User-Facing Changes See above. # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace --features=extra` to check that all tests pass # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
This commit is contained in:
@ -1,11 +1,10 @@
|
||||
mod nu_protocol_table;
|
||||
mod table;
|
||||
mod table_theme;
|
||||
mod textstyle;
|
||||
mod util;
|
||||
|
||||
pub use nu_color_config::TextStyle;
|
||||
pub use nu_protocol_table::NuTable;
|
||||
pub use table::{Alignments, Table, TableConfig};
|
||||
pub use table_theme::TableTheme;
|
||||
pub use textstyle::{Alignment, TextStyle};
|
||||
pub use util::*;
|
||||
|
@ -1,16 +1,16 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{table::TrimStrategyModifier, Alignments, TableTheme};
|
||||
use nu_color_config::StyleComputer;
|
||||
use nu_protocol::{Config, Span, Value};
|
||||
use tabled::{
|
||||
color::Color, formatting::AlignmentStrategy, object::Segment, papergrid::records::Records,
|
||||
Alignment, Modify, Table,
|
||||
};
|
||||
|
||||
use crate::{table::TrimStrategyModifier, Alignments, TableTheme};
|
||||
|
||||
/// NuTable has a recursive table representation of nu_prorocol::Value.
|
||||
/// NuTable has a recursive table representation of nu_protocol::Value.
|
||||
///
|
||||
/// It doesn't support alignement and a proper width controll.
|
||||
/// It doesn't support alignement and a proper width control.
|
||||
pub struct NuTable {
|
||||
inner: tabled::Table,
|
||||
}
|
||||
@ -21,12 +21,12 @@ impl NuTable {
|
||||
collapse: bool,
|
||||
termwidth: usize,
|
||||
config: &Config,
|
||||
color_hm: &HashMap<String, nu_ansi_term::Style>,
|
||||
style_computer: &StyleComputer,
|
||||
theme: &TableTheme,
|
||||
with_footer: bool,
|
||||
) -> Self {
|
||||
let mut table = tabled::Table::new([""]);
|
||||
load_theme(&mut table, color_hm, theme);
|
||||
load_theme(&mut table, style_computer, theme);
|
||||
let cfg = table.get_config().clone();
|
||||
|
||||
let val = nu_protocol_value_to_json(value, config, with_footer);
|
||||
@ -200,11 +200,9 @@ fn connect_maps(map: &mut serde_json::Map<String, serde_json::Value>, value: ser
|
||||
}
|
||||
}
|
||||
|
||||
fn load_theme<R>(
|
||||
table: &mut tabled::Table<R>,
|
||||
color_hm: &HashMap<String, nu_ansi_term::Style>,
|
||||
theme: &TableTheme,
|
||||
) where
|
||||
//
|
||||
fn load_theme<R>(table: &mut tabled::Table<R>, style_computer: &StyleComputer, theme: &TableTheme)
|
||||
where
|
||||
R: Records,
|
||||
{
|
||||
let mut theme = theme.theme.clone();
|
||||
@ -212,11 +210,11 @@ fn load_theme<R>(
|
||||
|
||||
table.with(theme);
|
||||
|
||||
if let Some(color) = color_hm.get("separator") {
|
||||
let color = color.paint(" ").to_string();
|
||||
if let Ok(color) = Color::try_from(color) {
|
||||
table.with(color);
|
||||
}
|
||||
// color_config closures for "separator" are just given a null.
|
||||
let color = style_computer.compute("separator", &Value::nothing(Span::unknown()));
|
||||
let color = color.paint(" ").to_string();
|
||||
if let Ok(color) = Color::try_from(color) {
|
||||
table.with(color);
|
||||
}
|
||||
|
||||
table.with(
|
||||
|
@ -1,7 +1,8 @@
|
||||
use std::{cmp::min, collections::HashMap, fmt::Display};
|
||||
|
||||
use crate::table_theme::TableTheme;
|
||||
use nu_ansi_term::Style;
|
||||
use nu_color_config::TextStyle;
|
||||
use nu_protocol::TrimStrategy;
|
||||
use std::{cmp::min, collections::HashMap};
|
||||
use tabled::{
|
||||
alignment::AlignmentHorizontal,
|
||||
builder::Builder,
|
||||
@ -9,7 +10,6 @@ use tabled::{
|
||||
formatting::AlignmentStrategy,
|
||||
object::{Cell, Columns, Rows, Segment},
|
||||
papergrid::{
|
||||
self,
|
||||
records::{
|
||||
cell_info::CellInfo, tcell::TCell, vec_records::VecRecords, Records, RecordsMut,
|
||||
},
|
||||
@ -21,8 +21,6 @@ use tabled::{
|
||||
Alignment, Modify, ModifyObject, TableOption, Width,
|
||||
};
|
||||
|
||||
use crate::{table_theme::TableTheme, TextStyle};
|
||||
|
||||
/// Table represent a table view.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Table {
|
||||
@ -577,26 +575,6 @@ fn truncate_columns_by_columns(data: &mut Data, theme: &TableTheme, termwidth: u
|
||||
false
|
||||
}
|
||||
|
||||
impl papergrid::Color for TextStyle {
|
||||
fn fmt_prefix(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(color) = &self.color_style {
|
||||
color.prefix().fmt(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fmt_suffix(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(color) = &self.color_style {
|
||||
if !color.is_plain() {
|
||||
f.write_str("\u{1b}[0m")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The same as [`tabled::peaker::PriorityMax`] but prioritizes left columns first in case of equal width.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct PriorityMax;
|
||||
|
@ -1,241 +0,0 @@
|
||||
use nu_ansi_term::{Color, Style};
|
||||
|
||||
pub type Alignment = tabled::alignment::AlignmentHorizontal;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TextStyle {
|
||||
pub alignment: Alignment,
|
||||
pub color_style: Option<Style>,
|
||||
}
|
||||
|
||||
impl TextStyle {
|
||||
pub fn new() -> TextStyle {
|
||||
TextStyle {
|
||||
alignment: Alignment::Left,
|
||||
color_style: Some(Style::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bold(&self, bool_value: Option<bool>) -> TextStyle {
|
||||
let bv = bool_value.unwrap_or(false);
|
||||
|
||||
TextStyle {
|
||||
alignment: self.alignment,
|
||||
color_style: Some(Style {
|
||||
is_bold: bv,
|
||||
..self.color_style.unwrap_or_default()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_bold(&self) -> bool {
|
||||
self.color_style.unwrap_or_default().is_bold
|
||||
}
|
||||
|
||||
pub fn dimmed(&self) -> TextStyle {
|
||||
TextStyle {
|
||||
alignment: self.alignment,
|
||||
color_style: Some(Style {
|
||||
is_dimmed: true,
|
||||
..self.color_style.unwrap_or_default()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_dimmed(&self) -> bool {
|
||||
self.color_style.unwrap_or_default().is_dimmed
|
||||
}
|
||||
|
||||
pub fn italic(&self) -> TextStyle {
|
||||
TextStyle {
|
||||
alignment: self.alignment,
|
||||
color_style: Some(Style {
|
||||
is_italic: true,
|
||||
..self.color_style.unwrap_or_default()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_italic(&self) -> bool {
|
||||
self.color_style.unwrap_or_default().is_italic
|
||||
}
|
||||
|
||||
pub fn underline(&self) -> TextStyle {
|
||||
TextStyle {
|
||||
alignment: self.alignment,
|
||||
color_style: Some(Style {
|
||||
is_underline: true,
|
||||
..self.color_style.unwrap_or_default()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_underline(&self) -> bool {
|
||||
self.color_style.unwrap_or_default().is_underline
|
||||
}
|
||||
|
||||
pub fn blink(&self) -> TextStyle {
|
||||
TextStyle {
|
||||
alignment: self.alignment,
|
||||
color_style: Some(Style {
|
||||
is_blink: true,
|
||||
..self.color_style.unwrap_or_default()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_blink(&self) -> bool {
|
||||
self.color_style.unwrap_or_default().is_blink
|
||||
}
|
||||
|
||||
pub fn reverse(&self) -> TextStyle {
|
||||
TextStyle {
|
||||
alignment: self.alignment,
|
||||
color_style: Some(Style {
|
||||
is_reverse: true,
|
||||
..self.color_style.unwrap_or_default()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_reverse(&self) -> bool {
|
||||
self.color_style.unwrap_or_default().is_reverse
|
||||
}
|
||||
|
||||
pub fn hidden(&self) -> TextStyle {
|
||||
TextStyle {
|
||||
alignment: self.alignment,
|
||||
color_style: Some(Style {
|
||||
is_hidden: true,
|
||||
..self.color_style.unwrap_or_default()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_hidden(&self) -> bool {
|
||||
self.color_style.unwrap_or_default().is_hidden
|
||||
}
|
||||
|
||||
pub fn strikethrough(&self) -> TextStyle {
|
||||
TextStyle {
|
||||
alignment: self.alignment,
|
||||
color_style: Some(Style {
|
||||
is_strikethrough: true,
|
||||
..self.color_style.unwrap_or_default()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_strikethrough(&self) -> bool {
|
||||
self.color_style.unwrap_or_default().is_strikethrough
|
||||
}
|
||||
|
||||
pub fn fg(&self, foreground: Color) -> TextStyle {
|
||||
TextStyle {
|
||||
alignment: self.alignment,
|
||||
color_style: Some(Style {
|
||||
foreground: Some(foreground),
|
||||
..self.color_style.unwrap_or_default()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on(&self, background: Color) -> TextStyle {
|
||||
TextStyle {
|
||||
alignment: self.alignment,
|
||||
color_style: Some(Style {
|
||||
background: Some(background),
|
||||
..self.color_style.unwrap_or_default()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bg(&self, background: Color) -> TextStyle {
|
||||
TextStyle {
|
||||
alignment: self.alignment,
|
||||
color_style: Some(Style {
|
||||
background: Some(background),
|
||||
..self.color_style.unwrap_or_default()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alignment(&self, align: Alignment) -> TextStyle {
|
||||
TextStyle {
|
||||
alignment: align,
|
||||
color_style: self.color_style,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn style(&self, style: Style) -> TextStyle {
|
||||
TextStyle {
|
||||
alignment: self.alignment,
|
||||
color_style: Some(Style {
|
||||
foreground: style.foreground,
|
||||
background: style.background,
|
||||
is_bold: style.is_bold,
|
||||
is_dimmed: style.is_dimmed,
|
||||
is_italic: style.is_italic,
|
||||
is_underline: style.is_underline,
|
||||
is_blink: style.is_blink,
|
||||
is_reverse: style.is_reverse,
|
||||
is_hidden: style.is_hidden,
|
||||
is_strikethrough: style.is_strikethrough,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn basic_center() -> TextStyle {
|
||||
TextStyle::new()
|
||||
.alignment(Alignment::Center)
|
||||
.style(Style::default())
|
||||
}
|
||||
|
||||
pub fn basic_right() -> TextStyle {
|
||||
TextStyle::new()
|
||||
.alignment(Alignment::Right)
|
||||
.style(Style::default())
|
||||
}
|
||||
|
||||
pub fn basic_left() -> TextStyle {
|
||||
TextStyle::new()
|
||||
.alignment(Alignment::Left)
|
||||
.style(Style::default())
|
||||
}
|
||||
|
||||
pub fn default_header() -> TextStyle {
|
||||
TextStyle::new()
|
||||
.alignment(Alignment::Center)
|
||||
.fg(Color::Green)
|
||||
.bold(Some(true))
|
||||
}
|
||||
|
||||
pub fn default_field() -> TextStyle {
|
||||
TextStyle::new().fg(Color::Green).bold(Some(true))
|
||||
}
|
||||
|
||||
pub fn with_attributes(bo: bool, al: Alignment, co: Color) -> TextStyle {
|
||||
TextStyle::new().alignment(al).fg(co).bold(Some(bo))
|
||||
}
|
||||
|
||||
pub fn with_style(al: Alignment, style: Style) -> TextStyle {
|
||||
TextStyle::new().alignment(al).style(Style {
|
||||
foreground: style.foreground,
|
||||
background: style.background,
|
||||
is_bold: style.is_bold,
|
||||
is_dimmed: style.is_dimmed,
|
||||
is_italic: style.is_italic,
|
||||
is_underline: style.is_underline,
|
||||
is_blink: style.is_blink,
|
||||
is_reverse: style.is_reverse,
|
||||
is_hidden: style.is_hidden,
|
||||
is_strikethrough: style.is_strikethrough,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TextStyle {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user