diff --git a/usr.bin/diff/diff.h b/usr.bin/diff/diff.h --- a/usr.bin/diff/diff.h +++ b/usr.bin/diff/diff.h @@ -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; diff --git a/usr.bin/diff/diff.1 b/usr.bin/diff/diff.1 --- a/usr.bin/diff/diff.1 +++ b/usr.bin/diff/diff.1 @@ -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 @@ .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 @@ 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 diff --git a/usr.bin/diff/diff.c b/usr.bin/diff/diff.c --- a/usr.bin/diff/diff.c +++ b/usr.bin/diff/diff.c @@ -39,11 +39,13 @@ #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 @@ OPT_HORIZON_LINES, OPT_CHANGED_GROUP_FORMAT, OPT_SUPPRESS_COMMON, + OPT_COLOR, }; static struct option longopts[] = { @@ -98,6 +101,7 @@ { "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 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 @@ 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 @@ 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 @@ 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) { diff --git a/usr.bin/diff/diffreg.c b/usr.bin/diff/diffreg.c --- a/usr.bin/diff/diffreg.c +++ b/usr.bin/diff/diffreg.c @@ -1140,13 +1140,23 @@ } } 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 @@ 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 @@ } /* 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); }