forked from mirrors/jj
cli: allow colors in form #rrggbb
Changes the formatter to accept not only existing color names (such as "red" or "green") but also those in the form #rrggbb, where rr, gg, and bb are two-digit hexadecimal numbers. This allows much finer control over colors used.
This commit is contained in:
parent
3ffe3d9ed8
commit
933150d819
4 changed files with 98 additions and 11 deletions
|
@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### New features
|
||||
|
||||
* Config now supports rgb hex colors (in the form `#rrggbb`) wherever existing color names are supported.
|
||||
|
||||
* `ui.default-command` now accepts multiple string arguments, for more complex
|
||||
default `jj` commands.
|
||||
|
||||
|
|
|
@ -162,7 +162,7 @@
|
|||
"type": "object",
|
||||
"description": "Mapping from jj formatter labels to colors",
|
||||
"definitions": {
|
||||
"colors": {
|
||||
"colorNames": {
|
||||
"enum": [
|
||||
"default",
|
||||
"black",
|
||||
|
@ -183,6 +183,20 @@
|
|||
"bright white"
|
||||
]
|
||||
},
|
||||
"hexColor": {
|
||||
"type": "string",
|
||||
"pattern": "^#[0-9a-fA-F]{6}$"
|
||||
},
|
||||
"colors": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/properties/colors/definitions/colorNames"
|
||||
},
|
||||
{
|
||||
"$ref": "#/properties/colors/definitions/hexColor"
|
||||
}
|
||||
]
|
||||
},
|
||||
"basicFormatterLabels": {
|
||||
"enum": [
|
||||
"description",
|
||||
|
|
|
@ -333,7 +333,7 @@ fn rules_from_config(config: &config::Config) -> Result<Rules, config::ConfigErr
|
|||
match value.kind {
|
||||
config::ValueKind::String(color_name) => {
|
||||
let style = Style {
|
||||
fg_color: Some(color_for_name(&color_name)?),
|
||||
fg_color: Some(color_for_name_or_hex(&color_name)?),
|
||||
bg_color: None,
|
||||
bold: None,
|
||||
underlined: None,
|
||||
|
@ -344,12 +344,12 @@ fn rules_from_config(config: &config::Config) -> Result<Rules, config::ConfigErr
|
|||
let mut style = Style::default();
|
||||
if let Some(value) = style_table.get("fg") {
|
||||
if let config::ValueKind::String(color_name) = &value.kind {
|
||||
style.fg_color = Some(color_for_name(color_name)?);
|
||||
style.fg_color = Some(color_for_name_or_hex(color_name)?);
|
||||
}
|
||||
}
|
||||
if let Some(value) = style_table.get("bg") {
|
||||
if let config::ValueKind::String(color_name) = &value.kind {
|
||||
style.bg_color = Some(color_for_name(color_name)?);
|
||||
style.bg_color = Some(color_for_name_or_hex(color_name)?);
|
||||
}
|
||||
}
|
||||
if let Some(value) = style_table.get("bold") {
|
||||
|
@ -370,8 +370,8 @@ fn rules_from_config(config: &config::Config) -> Result<Rules, config::ConfigErr
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
fn color_for_name(color_name: &str) -> Result<Color, config::ConfigError> {
|
||||
match color_name {
|
||||
fn color_for_name_or_hex(name_or_hex: &str) -> Result<Color, config::ConfigError> {
|
||||
match name_or_hex {
|
||||
"default" => Ok(Color::Reset),
|
||||
"black" => Ok(Color::Black),
|
||||
"red" => Ok(Color::DarkRed),
|
||||
|
@ -389,9 +389,25 @@ fn color_for_name(color_name: &str) -> Result<Color, config::ConfigError> {
|
|||
"bright magenta" => Ok(Color::Magenta),
|
||||
"bright cyan" => Ok(Color::Cyan),
|
||||
"bright white" => Ok(Color::White),
|
||||
_ => Err(config::ConfigError::Message(format!(
|
||||
"invalid color: {color_name}"
|
||||
))),
|
||||
_ => color_for_hex(name_or_hex)
|
||||
.ok_or_else(|| config::ConfigError::Message(format!("invalid color: {}", name_or_hex))),
|
||||
}
|
||||
}
|
||||
|
||||
fn color_for_hex(color: &str) -> Option<Color> {
|
||||
if color.len() == 7
|
||||
&& color.starts_with('#')
|
||||
&& color[1..].chars().all(|c| c.is_ascii_hexdigit())
|
||||
{
|
||||
let r = u8::from_str_radix(&color[1..3], 16);
|
||||
let g = u8::from_str_radix(&color[3..5], 16);
|
||||
let b = u8::from_str_radix(&color[5..7], 16);
|
||||
match (r, g, b) {
|
||||
(Ok(r), Ok(g), Ok(b)) => Some(Color::Rgb { r, g, b }),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -676,6 +692,38 @@ mod tests {
|
|||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_color_formatter_hex_colors() {
|
||||
// Test the color code for each color.
|
||||
let labels_and_colors = [
|
||||
["black", "#000000"],
|
||||
["white", "#ffffff"],
|
||||
["pastel-blue", "#AFE0D9"],
|
||||
];
|
||||
let mut config_builder = config::Config::builder();
|
||||
for [label, color] in labels_and_colors {
|
||||
// Use the color name as the label.
|
||||
config_builder = config_builder
|
||||
.set_override(format!("colors.{}", label), color)
|
||||
.unwrap();
|
||||
}
|
||||
let mut output: Vec<u8> = vec![];
|
||||
let mut formatter =
|
||||
ColorFormatter::for_config(&mut output, &config_builder.build().unwrap()).unwrap();
|
||||
for [label, _] in labels_and_colors {
|
||||
formatter.push_label(&label.replace(' ', "-")).unwrap();
|
||||
formatter.write_str(&format!(" {label} ")).unwrap();
|
||||
formatter.pop_label().unwrap();
|
||||
formatter.write_str("\n").unwrap();
|
||||
}
|
||||
drop(formatter);
|
||||
insta::assert_snapshot!(String::from_utf8(output).unwrap(), @r###"
|
||||
[38;2;0;0;0m black [39m
|
||||
[38;2;255;255;255m white [39m
|
||||
[38;2;175;224;217m pastel-blue [39m
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_color_formatter_single_label() {
|
||||
// Test that a single label can be colored and that the color is reset
|
||||
|
@ -879,6 +927,23 @@ mod tests {
|
|||
@"invalid color: bloo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_color_formatter_unrecognized_hex_color() {
|
||||
// An unrecognized hex color causes an error.
|
||||
let config = config_from_string(
|
||||
r##"
|
||||
colors."outer" = "red"
|
||||
colors."outer inner" = "#ffgggg"
|
||||
"##,
|
||||
);
|
||||
let mut output: Vec<u8> = vec![];
|
||||
let err = ColorFormatter::for_config(&mut output, &config)
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
insta::assert_snapshot!(err,
|
||||
@"invalid color: #ffgggg");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_color_formatter_normal_color() {
|
||||
// The "default" color resets the color. It is possible to reset only the
|
||||
|
|
|
@ -113,12 +113,18 @@ All of them but "default" come in a bright version too, e.g. "bright red". The
|
|||
"default" color can be used to override a color defined by a parent style
|
||||
(explained below).
|
||||
|
||||
If you use a string value for a color, as in the example above, it will be used
|
||||
You can also use a 6-digit hex code for more control over the exact color used:
|
||||
|
||||
```toml
|
||||
colors.change_id = "#ff1525"
|
||||
```
|
||||
|
||||
If you use a string value for a color, as in the examples above, it will be used
|
||||
for the foreground color. You can also set the background color, or make the
|
||||
text bold or underlined. For that, you need to use a table:
|
||||
|
||||
```toml
|
||||
colors.commit_id = { fg = "green", bg = "red", bold = true, underline = true }
|
||||
colors.commit_id = { fg = "green", bg = "#ff1525", bold = true, underline = true }
|
||||
```
|
||||
|
||||
The key names are called "labels". The above used `commit_id` as label. You can
|
||||
|
|
Loading…
Reference in a new issue