diff(1): Add --color support

Adds a --color flag to diff(1) that supports the same options as GNU's
diff(1). The colors are customizable with the env var DIFFCOLORS in
a format similar to grep(1)'s GREPCOLORS. An example would be 04;36:41
for additions to be underlined light blue, and deletions have a red
background.

Differential Revision:	https://reviews.freebsd.org/D30545
This commit is contained in:
Cameron Katri 2021-09-05 02:10:41 +02:00 committed by Piotr Pawel Stefaniak
parent 7760b85414
commit f38702e5a5
4 changed files with 118 additions and 3 deletions

View File

@ -44,6 +44,7 @@
.Fl n | q | u | y
.Oc
.Op Fl -brief
.Op Fl -color Ns = Ns Ar when
.Op Fl -changed-group-format Ar GFMT
.Op Fl -ed
.Op Fl -expand-tabs
@ -71,6 +72,7 @@
.Op Fl I Ar pattern | Fl -ignore-matching-lines Ar pattern
.Op Fl L Ar label | Fl -label Ar label
.Op Fl -brief
.Op Fl -color Ns = Ns Ar when
.Op Fl -changed-group-format Ar GFMT
.Op Fl -ed
.Op Fl -expand-tabs
@ -96,6 +98,7 @@
.Op Fl aBbdiltw
.Op Fl I Ar pattern | Fl -ignore-matching-lines Ar pattern
.Op Fl -brief
.Op Fl -color Ns = Ns Ar when
.Op Fl -changed-group-format Ar GFMT
.Op Fl -ed
.Op Fl -expand-tabs
@ -122,6 +125,7 @@
.Op Fl I Ar pattern | Fl -ignore-matching-lines Ar pattern
.Op Fl L Ar label | Fl -label Ar label
.Op Fl -brief
.Op Fl -color Ns = Ns Ar when
.Op Fl -changed-group-format Ar GFMT
.Op Fl -ed
.Op Fl -expand-tabs
@ -150,6 +154,7 @@
.Fl n | q | u
.Oc
.Op Fl -brief
.Op Fl -color Ns = Ns Ar when
.Op Fl -changed-group-format Ar GFMT
.Op Fl -context
.Op Fl -ed
@ -184,6 +189,7 @@
.Ar dir1 dir2
.Nm diff
.Op Fl aBbditwW
.Op Fl -color Ns = Ns Ar when
.Op Fl -expand-tabs
.Op Fl -ignore-all-blanks
.Op Fl -ignore-blank-lines
@ -332,6 +338,21 @@ Causes chunks that include only blank lines to be ignored.
.It Fl b -ignore-space-change
Causes trailing blanks (spaces and tabs) to be ignored, and other
strings of blanks to compare equal.
.It Fl -color= Ns Oo Ar when Oc
Color the additions green, and removals red, or the value in the
.Ev DIFFCOLORS
environment variable.
The possible values of
.Ar when
are
.Dq Cm never ,
.Dq Cm always
and
.Dq Cm auto .
.Cm auto
will use color if the output is a tty and the
.Ev COLORTERM
environment variable is set to a non-empty string.
.It Fl d -minimal
Try very hard to produce a diff as small as possible.
This may consume a lot of processing power and memory when processing
@ -592,6 +613,20 @@ As in
identical
pairs (where num1 = num2) are abbreviated as a single
number.
.Sh ENVIRONMENT
.Bl -tag -width DIFFCOLORS
.It Ev DIFFCOLORS
The value of this variable is the form
.Ar add Ns : Ns Ar rm ,
where
.Ar add
is the ASCII escape sequence for additions and
.Ar rm
is the ASCII escape sequence for deletions.
If this is unset,
.Nm
uses green for additions and red for removals.
.El
.Sh FILES
.Bl -tag -width /tmp/diff.XXXXXXXX -compact
.It Pa /tmp/diff.XXXXXXXX

View File

@ -39,11 +39,13 @@ __FBSDID("$FreeBSD$");
#include "xmalloc.h"
bool lflag, Nflag, Pflag, rflag, sflag, Tflag, cflag;
bool ignore_file_case, suppress_common;
bool ignore_file_case, suppress_common, color;
int diff_format, diff_context, status;
int tabsize = 8, width = 130;
static int colorflag = COLORFLAG_NEVER;
char *start, *ifdefname, *diffargs, *label[2], *ignore_pats;
char *group_format = NULL;
const char *add_code, *del_code;
struct stat stb1, stb2;
struct excludes *excludes_list;
regex_t ignore_re;
@ -58,6 +60,7 @@ enum {
OPT_HORIZON_LINES,
OPT_CHANGED_GROUP_FORMAT,
OPT_SUPPRESS_COMMON,
OPT_COLOR,
};
static struct option longopts[] = {
@ -98,6 +101,7 @@ static struct option longopts[] = {
{ "tabsize", required_argument, NULL, OPT_TSIZE },
{ "changed-group-format", required_argument, NULL, OPT_CHANGED_GROUP_FORMAT},
{ "suppress-common-lines", no_argument, NULL, OPT_SUPPRESS_COMMON },
{ "color", optional_argument, NULL, OPT_COLOR },
{ NULL, 0, 0, '\0'}
};
@ -108,6 +112,7 @@ static void push_ignore_pats(char *);
static void read_excludes_file(char *file);
static void set_argstr(char **, char **);
static char *splice(char *, char *);
static bool do_color(void);
int
main(int argc, char **argv)
@ -301,6 +306,17 @@ main(int argc, char **argv)
case OPT_SUPPRESS_COMMON:
suppress_common = 1;
break;
case OPT_COLOR:
if (optarg == NULL || strncmp(optarg, "auto", 4) == 0)
colorflag = COLORFLAG_AUTO;
else if (strncmp(optarg, "always", 6) == 0)
colorflag = COLORFLAG_ALWAYS;
else if (strncmp(optarg, "never", 5) == 0)
colorflag = COLORFLAG_NEVER;
else
errx(2, "unsupported --color value '%s' (must be always, auto, or never)",
optarg);
break;
default:
usage();
break;
@ -316,6 +332,22 @@ main(int argc, char **argv)
argc -= optind;
argv += optind;
if (do_color()) {
char *p;
const char *env;
color = true;
add_code = "32";
del_code = "31";
env = getenv("DIFFCOLORS");
if (env != NULL && *env != '\0' && (p = strdup(env))) {
add_code = p;
strsep(&p, ":");
if (p != NULL)
del_code = p;
}
}
#ifdef __OpenBSD__
if (pledge("stdio rpath tmppath", NULL) == -1)
err(2, "pledge");
@ -545,6 +577,27 @@ conflicting_format(void)
usage();
}
static bool
do_color(void)
{
const char *p, *p2;
switch (colorflag) {
case COLORFLAG_AUTO:
p = getenv("CLICOLOR");
p2 = getenv("COLORTERM");
if ((p != NULL && *p != '\0') || (p2 != NULL && *p2 != '\0'))
return isatty(STDOUT_FILENO);
break;
case COLORFLAG_ALWAYS:
return (true);
case COLORFLAG_NEVER:
return (false);
}
return (false);
}
static char *
splice(char *dir, char *path)
{

View File

@ -87,17 +87,25 @@
#define D_SKIPPED2 6 /* path2 was a special file */
#define D_ERROR 7 /* A file access error occurred */
/*
* Color options
*/
#define COLORFLAG_NEVER 0
#define COLORFLAG_AUTO 1
#define COLORFLAG_ALWAYS 2
struct excludes {
char *pattern;
struct excludes *next;
};
extern bool lflag, Nflag, Pflag, rflag, sflag, Tflag, cflag;
extern bool ignore_file_case, suppress_common;
extern bool ignore_file_case, suppress_common, color;
extern int diff_format, diff_context, status;
extern int tabsize, width;
extern char *start, *ifdefname, *diffargs, *label[2], *ignore_pats;
extern char *group_format;
extern const char *add_code, *del_code;
extern struct stat stb1, stb2;
extern struct excludes *excludes_list;
extern regex_t ignore_re;

View File

@ -1140,13 +1140,23 @@ change(char *file1, FILE *f1, char *file2, FILE *f2, int a, int b, int c, int d,
}
}
if (diff_format == D_SIDEBYSIDE) {
if (color && a > b)
printf("\033[%sm", add_code);
else if (color && c > d)
printf("\033[%sm", del_code);
if (a > b) {
print_space(0, hw + padding , *pflags);
} else {
nc = fetch(ixold, a, b, f1, '\0', 1, *pflags);
print_space(nc, hw - nc + padding, *pflags);
}
if (color && a > b)
printf("\033[%sm", add_code);
else if (color && c > d)
printf("\033[%sm", del_code);
printf("%c", (a > b) ? '>' : ((c > d) ? '<' : '|'));
if (color && c > d)
printf("\033[m");
print_space(hw + padding + 1 , padding, *pflags);
fetch(ixnew, c, d, f2, '\0', 0, *pflags);
printf("\n");
@ -1220,6 +1230,10 @@ fetch(long *f, int a, int b, FILE *lb, int ch, int oldfile, int flags)
nc = hw;
if (diff_format != D_IFDEF && diff_format != D_GFORMAT &&
ch != '\0') {
if (color && (ch == '>' || ch == '+'))
printf("\033[%sm", add_code);
else if (color && (ch == '<' || ch == '-'))
printf("\033[%sm", del_code);
printf("%c", ch);
if (Tflag && (diff_format == D_NORMAL ||
diff_format == D_CONTEXT ||
@ -1290,12 +1304,17 @@ fetch(long *f, int a, int b, FILE *lb, int ch, int oldfile, int flags)
}
/* when side-by-side, do not print a newline */
if (diff_format != D_SIDEBYSIDE || c != '\n') {
printf("%c", c);
if (color && c == '\n')
printf("\033[m%c", c);
else
printf("%c", c);
col++;
}
}
}
}
if (color && diff_format == D_SIDEBYSIDE)
printf("\033[m");
return (col);
}