git: f38702e5a52e - main - diff(1): Add --color support

Piotr Pawel Stefaniak pstef at FreeBSD.org
Wed Sep 15 23:59:56 UTC 2021


The branch main has been updated by pstef:

URL: https://cgit.FreeBSD.org/src/commit/?id=f38702e5a52e1350b9349bc297ccd0b50b1941c3

commit f38702e5a52e1350b9349bc297ccd0b50b1941c3
Author:     Cameron Katri <me at cameronkatri.com>
AuthorDate: 2021-09-05 00:10:41 +0000
Commit:     Piotr Pawel Stefaniak <pstef at FreeBSD.org>
CommitDate: 2021-09-15 23:46:44 +0000

    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
---
 usr.bin/diff/diff.1    | 35 ++++++++++++++++++++++++++++++++
 usr.bin/diff/diff.c    | 55 +++++++++++++++++++++++++++++++++++++++++++++++++-
 usr.bin/diff/diff.h    | 10 ++++++++-
 usr.bin/diff/diffreg.c | 21 ++++++++++++++++++-
 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);
 }
 


More information about the dev-commits-src-all mailing list