Skip to content

Commit

Permalink
Merge branch 'jk/ansi-color'
Browse files Browse the repository at this point in the history
The output coloring scheme learned two new attributes, italic and
strike, in addition to existing bold, reverse, etc.

* jk/ansi-color:
  color: support strike-through attribute
  color: support "italic" attribute
  color: allow "no-" for negating attributes
  color: refactor parse_attr
  add skip_prefix_mem helper
  doc: refactor description of color format
  color: fix max-size comment
  • Loading branch information
gitster committed Jul 11, 2016
2 parents bb2d8a8 + 9dc3515 commit 3c5de5c
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 39 deletions.
49 changes: 28 additions & 21 deletions Documentation/config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -150,27 +150,34 @@ integer::
1024", "by 1024x1024", etc.

color::
The value for a variables that takes a color is a list of
colors (at most two) and attributes (at most one), separated
by spaces. The colors accepted are `normal`, `black`,
`red`, `green`, `yellow`, `blue`, `magenta`, `cyan` and
`white`; the attributes are `bold`, `dim`, `ul`, `blink` and
`reverse`. The first color given is the foreground; the
second is the background. The position of the attribute, if
any, doesn't matter. Attributes may be turned off specifically
by prefixing them with `no` (e.g., `noreverse`, `noul`, etc).
+
Colors (foreground and background) may also be given as numbers between
0 and 255; these use ANSI 256-color mode (but note that not all
terminals may support this). If your terminal supports it, you may also
specify 24-bit RGB values as hex, like `#ff0ab3`.
+
The attributes are meant to be reset at the beginning of each item
in the colored output, so setting color.decorate.branch to `black`
will paint that branch name in a plain `black`, even if the previous
thing on the same output line (e.g. opening parenthesis before the
list of branch names in `log --decorate` output) is set to be
painted with `bold` or some other attribute.
The value for a variable that takes a color is a list of
colors (at most two, one for foreground and one for background)
and attributes (as many as you want), separated by spaces.
+
The basic colors accepted are `normal`, `black`, `red`, `green`, `yellow`,
`blue`, `magenta`, `cyan` and `white`. The first color given is the
foreground; the second is the background.
+
Colors may also be given as numbers between 0 and 255; these use ANSI
256-color mode (but note that not all terminals may support this). If
your terminal supports it, you may also specify 24-bit RGB values as
hex, like `#ff0ab3`.
+
The accepted attributes are `bold`, `dim`, `ul`, `blink`, `reverse`,
`italic`, and `strike` (for crossed-out or "strikethrough" letters).
The position of any attributes with respect to the colors
(before, after, or in between), doesn't matter. Specific attributes may
be turned off by prefixing them with `no` or `no-` (e.g., `noreverse`,
`no-ul`, etc).
+
For git's pre-defined color slots, the attributes are meant to be reset
at the beginning of each item in the colored output. So setting
`color.decorate.branch` to `black` will paint that branch name in a
plain `black`, even if the previous thing on the same output line (e.g.
opening parenthesis before the list of branch names in `log --decorate`
output) is set to be painted with `bold` or some other attribute.
However, custom log formats may do more complicated and layered
coloring, and the negated forms may be useful there.

pathname::
A variable that takes a pathname value can be given a
Expand Down
35 changes: 25 additions & 10 deletions color.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,19 +123,34 @@ static int parse_color(struct color *out, const char *name, int len)
return -1;
}

static int parse_attr(const char *name, int len)
static int parse_attr(const char *name, size_t len)
{
static const int attr_values[] = { 1, 2, 4, 5, 7,
22, 22, 24, 25, 27 };
static const char * const attr_names[] = {
"bold", "dim", "ul", "blink", "reverse",
"nobold", "nodim", "noul", "noblink", "noreverse"
static const struct {
const char *name;
size_t len;
int val, neg;
} attrs[] = {
#define ATTR(x, val, neg) { (x), sizeof(x)-1, (val), (neg) }
ATTR("bold", 1, 22),
ATTR("dim", 2, 22),
ATTR("italic", 3, 23),
ATTR("ul", 4, 24),
ATTR("blink", 5, 25),
ATTR("reverse", 7, 27),
ATTR("strike", 9, 29)
#undef ATTR
};
int negate = 0;
int i;
for (i = 0; i < ARRAY_SIZE(attr_names); i++) {
const char *str = attr_names[i];
if (!strncasecmp(name, str, len) && !str[len])
return attr_values[i];

if (skip_prefix_mem(name, len, "no", &name, &len)) {
skip_prefix_mem(name, len, "-", &name, &len);
negate = 1;
}

for (i = 0; i < ARRAY_SIZE(attrs); i++) {
if (attrs[i].len == len && !memcmp(attrs[i].name, name, len))
return negate ? attrs[i].neg : attrs[i].val;
}
return -1;
}
Expand Down
15 changes: 9 additions & 6 deletions color.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,23 @@

struct strbuf;

/* 2 + (2 * num_attrs) + 8 + 1 + 8 + 'm' + NUL */
/* "\033[1;2;4;5;7;38;5;2xx;48;5;2xxm\0" */
/*
* The maximum length of ANSI color sequence we would generate:
* - leading ESC '[' 2
* - attr + ';' 3 * 10 (e.g. "1;")
* - attr + ';' 2 * num_attr (e.g. "1;")
* - no-attr + ';' 3 * num_attr (e.g. "22;")
* - fg color + ';' 17 (e.g. "38;2;255;255;255;")
* - bg color + ';' 17 (e.g. "48;2;255;255;255;")
* - terminating 'm' NUL 2
*
* The above overcounts attr (we only use 5 not 8) and one semicolon
* but it is close enough.
* The above overcounts by one semicolon but it is close enough.
*
* The space for attributes is also slightly overallocated, as
* the negation for some attributes is the same (e.g., nobold and nodim).
*
* We allocate space for 7 attributes.
*/
#define COLOR_MAXLEN 70
#define COLOR_MAXLEN 75

/*
* IMPORTANT: Due to the way these color codes are emulated on Windows,
Expand Down
17 changes: 17 additions & 0 deletions git-compat-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,23 @@ static inline int skip_prefix(const char *str, const char *prefix,
return 0;
}

/*
* Like skip_prefix, but promises never to read past "len" bytes of the input
* buffer, and returns the remaining number of bytes in "out" via "outlen".
*/
static inline int skip_prefix_mem(const char *buf, size_t len,
const char *prefix,
const char **out, size_t *outlen)
{
size_t prefix_len = strlen(prefix);
if (prefix_len <= len && !memcmp(buf, prefix, prefix_len)) {
*out = buf + prefix_len;
*outlen = len - prefix_len;
return 1;
}
return 0;
}

/*
* If buf ends with suffix, return 1 and subtract the length of the suffix
* from *len. Otherwise, return 0 and leave *len untouched.
Expand Down
9 changes: 7 additions & 2 deletions t/t4026-color.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,19 @@ test_expect_success 'attr negation' '
color "nobold nodim noul noblink noreverse" "[22;24;25;27m"
'

test_expect_success '"no-" variant of negation' '
color "no-bold no-blink" "[22;25m"
'

test_expect_success 'long color specification' '
color "254 255 bold dim ul blink reverse" "[1;2;4;5;7;38;5;254;48;5;255m"
'

test_expect_success 'absurdly long color specification' '
color \
"#ffffff #ffffff bold nobold dim nodim ul noul blink noblink reverse noreverse" \
"[1;2;4;5;7;22;24;25;27;38;2;255;255;255;48;2;255;255;255m"
"#ffffff #ffffff bold nobold dim nodim italic noitalic
ul noul blink noblink reverse noreverse strike nostrike" \
"[1;2;3;4;5;7;9;22;23;24;25;27;29;38;2;255;255;255;48;2;255;255;255m"
'

test_expect_success '0-7 are aliases for basic ANSI color names' '
Expand Down

0 comments on commit 3c5de5c

Please sign in to comment.