aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCameron Katri <me@cameronkatri.com>2021-09-05 00:10:41 +0000
committerPiotr Pawel Stefaniak <pstef@FreeBSD.org>2021-09-15 23:46:44 +0000
commitf38702e5a52e1350b9349bc297ccd0b50b1941c3 (patch)
treeb355a90e59d4eee5a9ef2759107d6eaf26013e58
parent7760b85414189789c792c92c66bb3ddb877deae1 (diff)
downloadsrc-f38702e5a52e.tar.gz
src-f38702e5a52e.zip
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
-rw-r--r--usr.bin/diff/diff.135
-rw-r--r--usr.bin/diff/diff.c55
-rw-r--r--usr.bin/diff/diff.h10
-rw-r--r--usr.bin/diff/diffreg.c21
4 files changed, 118 insertions, 3 deletions
diff --git a/usr.bin/diff/diff.1 b/usr.bin/diff/diff.1
index e0a790f6efb6..6056ddd3ac76 100644
--- 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 @@ 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
diff --git a/usr.bin/diff/diff.c b/usr.bin/diff/diff.c
index a5966e74dbcc..4fc3094035d9 100644
--- a/usr.bin/diff/diff.c
+++ b/usr.bin/diff/diff.c
@@ -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)
{
diff --git a/usr.bin/diff/diff.h b/usr.bin/diff/diff.h
index 7ae700810fc6..5164fe22ace4 100644
--- 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/diffreg.c b/usr.bin/diff/diffreg.c
index c9095ec46e88..47c1934a6a65 100644
--- a/usr.bin/diff/diffreg.c
+++ b/usr.bin/diff/diffreg.c
@@ -1140,13 +1140,23 @@ proceed:
}
}
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);
}