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:
parent
7760b85414
commit
f38702e5a5
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user