Skip to content

Commit

Permalink
cli: allow colors in form #rrggbb
Browse files Browse the repository at this point in the history
  • Loading branch information
tomafro committed Mar 17, 2024
1 parent 8600750 commit 77276c6
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 21 deletions.
17 changes: 10 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### New features

* cli now supports rgb hex colors (in the form `#rrggbb`) wherev
er existing color names are supported.

* `ui.default-command` now accepts multiple string arguments, for more complex
default `jj` commands.

Expand Down Expand Up @@ -133,7 +136,7 @@ No code changes (fixing Rust `Cargo.toml` stuff).
When symlink support is unavailable, they will be materialized as regular files in the
working copy (instead of resulting in a crash).
[#2](https://github.com/martinvonz/jj/issues/2)

* On Windows, the `:builtin` pager is now used by default, rather than being
disabled entirely.

Expand Down Expand Up @@ -180,7 +183,7 @@ Thanks to the people who made this release happen!
copy commit on top of a single specified revision, i.e. with one parent.
`merge` creates a new working copy commit on top of *at least* two specified
revisions, i.e. with two or more parents.

The only difference between these commands and `jj new`, which *also* creates
a new working copy commit, is that `new` can create a working copy commit on
top of any arbitrary number of revisions, so it can handle both the previous
Expand Down Expand Up @@ -337,7 +340,7 @@ Thanks to the people who made this release happen!

* `jj branch set` no longer creates a new branch. Use `jj branch create`
instead.

* `jj init --git` in an existing Git repository now errors and exits rather than
creating a second Git store.

Expand Down Expand Up @@ -501,8 +504,8 @@ Thanks to the people who made this release happen!

### New features

* The `ancestors()` revset function now takes an optional `depth` argument
to limit the depth of the ancestor set. For example, use `jj log -r
* The `ancestors()` revset function now takes an optional `depth` argument
to limit the depth of the ancestor set. For example, use `jj log -r
'ancestors(@, 5)` to view the last 5 commits.

* Support for the Watchman filesystem monitor is now bundled by default. Set
Expand Down Expand Up @@ -667,13 +670,13 @@ Thanks to the people who made this release happen!
respectively.

* `jj log` timestamp format now accepts `.utc()` to convert a timestamp to UTC.

* templates now support additional string methods `.starts_with(x)`, `.ends_with(x)`
`.remove_prefix(x)`, `.remove_suffix(x)`, and `.substr(start, end)`.

* `jj next` and `jj prev` are added, these allow you to traverse the history
in a linear style. For people coming from Sapling and `git-branchles`
see [#2126](https://github.com/martinvonz/jj/issues/2126) for
see [#2126](https://github.com/martinvonz/jj/issues/2126) for
further pending improvements.

* `jj diff --stat` has been implemented. It shows a histogram of the changes,
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"
}
]
},
"basicFormatterLabels": {
"enum": [
"description",
Expand Down
92 changes: 84 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,36 @@ 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}"
))),
_ => {
if name_or_hex.len() == 7 && name_or_hex.starts_with('#') {
color_for_hex(name_or_hex)
} else {
Err(config::ConfigError::Message(format!(
"invalid color: {}",
name_or_hex
)))
}
}
}
}

fn color_for_hex(color: &str) -> Result<Color, config::ConfigError> {
if color.len() == 7 && color.starts_with('#') {
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)) => Ok(Color::Rgb { r, g, b }),
_ => Err(config::ConfigError::Message(format!(
"invalid color: {}",
color
))),
}
} else {
Err(config::ConfigError::Message(format!(
"invalid color: {}",
color
)))
}
}

Expand Down Expand Up @@ -676,6 +703,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.replace(' ', "-")), 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 +938,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
16 changes: 11 additions & 5 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 Expand Up @@ -524,7 +530,7 @@ conflict is considered fully resolved when there are no conflict markers left.

## Commit Signing

`jj` can be configured to sign and verify the commits it creates using either
`jj` can be configured to sign and verify the commits it creates using either
GnuPG or SSH signing keys.

To do this you need to configure a signing backend.
Expand Down Expand Up @@ -575,8 +581,8 @@ signing.backends.ssh.program = "/path/to/ssh-keygen"
When verifying commit signatures the ssh backend needs to be provided with an allowed-signers
file containing the public keys of authors whose signatures you want to be able to verify.

You can find the format for this file in the
[ssh-keygen man page](https://man.openbsd.org/ssh-keygen#ALLOWED_SIGNERS). This can be provided
You can find the format for this file in the
[ssh-keygen man page](https://man.openbsd.org/ssh-keygen#ALLOWED_SIGNERS). This can be provided
as follows:

```toml
Expand Down

0 comments on commit 77276c6

Please sign in to comment.