Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: allow colors in form '#rrggbb' #3310

Merged
merged 1 commit into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
16 changes: 15 additions & 1 deletion cli/src/config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@
"type": "object",
"description": "Mapping from jj formatter labels to colors",
"definitions": {
"colors": {
"colorNames": {
"enum": [
"default",
"black",
Expand All @@ -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"
}
]
},
tomafro marked this conversation as resolved.
Show resolved Hide resolved
"basicFormatterLabels": {
"enum": [
"description",
Expand Down
81 changes: 73 additions & 8 deletions cli/src/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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") {
Expand All @@ -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),
Expand All @@ -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) {
tomafro marked this conversation as resolved.
Show resolved Hide resolved
(Ok(r), Ok(g), Ok(b)) => Some(Color::Rgb { r, g, b }),
_ => None,
}
} else {
None
}
}

Expand Down Expand Up @@ -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###"
 black 
 white 
 pastel-blue 
"###);
}

#[test]
fn test_color_formatter_single_label() {
// Test that a single label can be colored and that the color is reset
Expand Down Expand Up @@ -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
Expand Down
10 changes: 8 additions & 2 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down