From efb983406cba85a92573f50cfc94c455422e7d3a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 3 Jul 2025 13:40:43 -0400 Subject: color: Match the BSD $LSCOLORS behaviour more closely BSD/macOS ls(1) don't reject overlong or odd-length $LSCOLORS values (although they can warn, which we don't). They also don't use the "intense" background colors for capital letters; instead, that enables underline on FreeBSD, or (foreground) bold on macOS. We copy FreeBSD here. --- src/color.c | 67 +++++++++++++++++++++++++++++++-------------- tests/bfs/color_bsd.out | 54 ++++++++++++++++++------------------ tests/bfs/color_bsd.sh | 2 +- tests/bfs/color_bsd_fail.sh | 2 -- 4 files changed, 74 insertions(+), 51 deletions(-) delete mode 100644 tests/bfs/color_bsd_fail.sh diff --git a/src/color.c b/src/color.c index 11dff2a..926cf2b 100644 --- a/src/color.c +++ b/src/color.c @@ -618,28 +618,43 @@ fail: /** Parse the FreeBSD $LSCOLORS format. */ static int parse_bsd_ls_colors(struct colors *colors, const char *lscolors) { static const char *fg_codes[256] = { - ['a'] = "30", ['b'] = "31", ['c'] = "32", ['d'] = "33", - ['e'] = "34", ['f'] = "35", ['g'] = "36", ['h'] = "37", ['x'] = "39", + // 0-7: deprecated aliases for a-h + ['0'] = "30", ['1'] = "31", ['2'] = "32", ['3'] = "33", + ['4'] = "34", ['5'] = "35", ['6'] = "36", ['7'] = "37", + // a-h: first 8 ANSI foreground colors + ['a'] = "30", ['b'] = "31", ['c'] = "32", ['d'] = "33", + ['e'] = "34", ['f'] = "35", ['g'] = "36", ['h'] = "37", + // x: default foreground + ['x'] = "39", + // A-H: bold foreground colors ['A'] = "1;30", ['B'] = "1;31", ['C'] = "1;32", ['D'] = "1;33", - ['E'] = "1;34", ['F'] = "1;35", ['G'] = "1;36", ['H'] = "1;37", ['X'] = "1" + ['E'] = "1;34", ['F'] = "1;35", ['G'] = "1;36", ['H'] = "1;37", + // X: bold default foreground + ['X'] = "1;39", }; static const char *bg_codes[256] = { - ['a'] = "40", ['b'] = "41", ['c'] = "42", ['d'] = "43", - ['e'] = "44", ['f'] = "45", ['g'] = "46", ['h'] = "47", ['x'] = "49", - ['A'] = "4;100", ['B'] = "4;101", ['C'] = "4;102", ['D'] = "4;103", - ['E'] = "4;104", ['F'] = "4;105", ['G'] = "4;106", ['H'] = "4;107", ['X'] = "4;49" + // 0-7: deprecated aliases for a-h + ['0'] = "40", ['1'] = "41", ['2'] = "42", ['3'] = "43", + ['4'] = "44", ['5'] = "45", ['6'] = "46", ['7'] = "47", + // a-h: first 8 ANSI background colors + ['a'] = "40", ['b'] = "41", ['c'] = "42", ['d'] = "43", + ['e'] = "44", ['f'] = "45", ['g'] = "46", ['h'] = "47", + // x: default background + ['x'] = "49", + // A-H: background colors + underline + ['A'] = "4;40", ['B'] = "4;41", ['C'] = "4;42", ['D'] = "4;43", + ['E'] = "4;44", ['F'] = "4;45", ['G'] = "4;46", ['H'] = "4;47", + // X: default background + underline + ['X'] = "4;49", }; // Please refer to https://man.freebsd.org/cgi/man.cgi?ls(1)#ENVIRONMENT char complete_colors[] = "exfxcxdxbxegedabagacadah"; + // For short $LSCOLORS, use the default colors for the rest size_t max = strlen(complete_colors); - size_t len = strnlen(lscolors, max + 1); - if (len == 0 || len % 2 != 0 || len > max) { - errno = EINVAL; - return -1; - } + size_t len = strnlen(lscolors, max); memcpy(complete_colors, lscolors, len); struct esc_seq **keys[] = { @@ -657,8 +672,8 @@ static int parse_bsd_ls_colors(struct colors *colors, const char *lscolors) { &colors->dataless, }; - dchar *value = dstralloc(0); - if (!value) { + dchar *buf = dstralloc(8); + if (!buf) { return -1; } @@ -669,15 +684,25 @@ static int parse_bsd_ls_colors(struct colors *colors, const char *lscolors) { const char *fg_code = fg_codes[fg]; const char *bg_code = bg_codes[bg]; - if (!fg_code || !bg_code) { - continue; - } - dstrshrink(value, 0); - if (dstrcatf(&value, "%s;%s", fg_code, bg_code) != 0) { - goto fail; + dstrshrink(buf, 0); + if (fg_code) { + if (dstrcat(&buf, fg_code) != 0) { + goto fail; + } + } + if (fg_code && bg_code) { + if (dstrcat(&buf, ";") != 0) { + goto fail; + } + } + if (bg_code) { + if (dstrcat(&buf, bg_code) != 0) { + goto fail; + } } + const dchar *value = dstrlen(buf) > 0 ? buf : NULL; if (set_esc_field(colors, keys[i], value) != 0) { goto fail; } @@ -685,7 +710,7 @@ static int parse_bsd_ls_colors(struct colors *colors, const char *lscolors) { ret = 0; fail: - dstrfree(value); + dstrfree(buf); return ret; } diff --git a/tests/bfs/color_bsd.out b/tests/bfs/color_bsd.out index f7c577c..2ad656f 100644 --- a/tests/bfs/color_bsd.out +++ b/tests/bfs/color_bsd.out @@ -1,27 +1,27 @@ -$'rainbow/\e[1m' -$'rainbow/\e[1m/'$'\e[0m' -rainbow -rainbow/sugid -rainbow/suid -rainbow/sticky_ow -rainbow/ow -rainbow/sgid -rainbow/exec.sh -rainbow/socket -rainbow/pipe -rainbow/broken -rainbow/chardev_link -rainbow/link.txt -rainbow/sticky -rainbow/file.dat -rainbow/file.txt -rainbow/lower.gz -rainbow/lower.tar -rainbow/lower.tar.gz -rainbow/lu.tar.GZ -rainbow/mh1 -rainbow/mh2 -rainbow/ul.TAR.gz -rainbow/upper.GZ -rainbow/upper.TAR -rainbow/upper.TAR.GZ +$'rainbow/\e[1m' +$'rainbow/\e[1m/'$'\e[0m' +rainbow +rainbow/pipe +rainbow/broken +rainbow/chardev_link +rainbow/link.txt +rainbow/sugid +rainbow/suid +rainbow/sticky_ow +rainbow/ow +rainbow/sgid +rainbow/exec.sh +rainbow/socket +rainbow/sticky +rainbow/file.dat +rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ +rainbow/mh1 +rainbow/mh2 +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_bsd.sh b/tests/bfs/color_bsd.sh index f8a777f..2e99f0b 100644 --- a/tests/bfs/color_bsd.sh +++ b/tests/bfs/color_bsd.sh @@ -1 +1 @@ -LSCOLORS="eB" bfs_diff rainbow -color +LSCOLORS="exFxcXDXbxeGxdXb" bfs_diff rainbow -color diff --git a/tests/bfs/color_bsd_fail.sh b/tests/bfs/color_bsd_fail.sh deleted file mode 100644 index 541190c..0000000 --- a/tests/bfs/color_bsd_fail.sh +++ /dev/null @@ -1,2 +0,0 @@ -# LSCOLORS can be at most 24 characters long (12 color pairs); this one has 25. -! LSCOLORS="exfxcxdxbxegedabagacadeahB" invoke_bfs rainbow -color -- cgit v1.2.3