Subject: [PATCH] Add color to git-add--interactive diffs (Take 2: now without spurious line break!)

From: Tom Tobin <[email-masked]>
Date: Sun, 14 Oct 2007 03:44:54 -0500

(This is repost; my damned mail client wrapped a line in the patch last
time, and now I've got that under control.  My apologies!)  :(

Seeing the recent discussion and code regarding adding color to
git-add--interactive, I thought I'd throw in my recent attempt at
colorizing the diffs.  (This doesn't handle anything else, such as the
prompts.)

After banging my head against parsing colorized output of git-add-files,
I gave up and implemented internal colorization keying off of the
color.diff configuration.

Hopefully this can be of some use towards fully colorizing
git-add--interactive; I'll admit up front that Perl isn't my primary
language, so I apologize in advance for whatever stupidities I've
introduced.  ;) 

Signed-off-by: Tom Tobin <[email-masked]>
---
 git-add--interactive.perl |  111 ++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 109 insertions(+), 2 deletions(-)

diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index be68814..eeb38e6 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -1,5 +1,6 @@
 #!/usr/bin/perl -w
 
+use List::Util qw(first);
 use strict;
 
 sub run_cmd_pipe {
@@ -22,6 +23,112 @@ if (!defined $GIT_DIR) {
 }
 chomp($GIT_DIR);
 
+my ($use_color) = 0;
+my (%term_color_codes) = (
+	"normal", "", "black", "0", "red", "1",
+	"green", "2", "yellow", "3", "blue", "4",
+	"magenta", "5", "cyan", "6", "white", "7"
+);
+my (%term_attr_codes) = (
+	"bold", "1", "dim", "2", "ul", "4", "blink", "5", "reverse", "7"
+);
+my %colorconfig = (
+	'color.diff' => 'never',
+	'color.diff.plain' => '',
+	'color.diff.meta' => 'bold',
+	'color.diff.frag' => 'cyan',
+	'color.diff.old' => 'red',
+	'color.diff.new' => 'green',
+	'color.diff.commit' => 'yellow',
+	'color.diff.whitespace' => 'normal red'
+	);
+for (split("\n", `git-config --get-regexp '^color\.diff'`)) {
+	my ($var, $val) = $_ =~ /^([^\s]+)\s(.*)$/;
+	$colorconfig{$var} = $val;
+}
+if (first { $_ eq $colorconfig{'color.diff'} } ("true", "always", "auto")) {
+	$use_color = 1;
+}
+
+sub parse_color {
+	my ($fg, $bg, $attr, $lookup);
+	my ($fg_code, $bg_code, $attr_code, $output_code) = ("", "", "", "");
+	my (@color) = @_;
+	my (@colorvals) = defined($color[0]) ? split(" ", $color[0]) : ();
+
+	for (@colorvals) {
+		$lookup = $term_color_codes{$_};
+		if (defined($lookup)) {
+			if (!defined($fg)) {
+				$fg = 1;
+				$fg_code = "3$lookup";
+			} elsif (!defined($bg)) {
+				$bg = 1;
+				$bg_code = "4$lookup";
+			} else {
+				die("Color slots only take up to two colors!");
+			}
+			next;
+		}
+		$lookup = $term_attr_codes{$_};
+		if (defined($lookup)) {
+			if (!defined($attr)) {
+				$attr = 1;
+				$attr_code = $lookup;
+			} else {
+				die("Color slots only take a single attribute!");
+			}
+		} else {
+			die("Unrecognized value for color slot!");
+		}
+	}
+	for ($fg_code, $bg_code, $attr_code) {
+		if ($_ eq "") {
+			next;
+		}
+		if ($output_code ne "") {
+			$output_code = $output_code . ";";
+		}
+		$output_code = $output_code . $_;
+	}
+	if (length($output_code)) {
+		return "\e[${output_code}m";
+	} else {
+		return "";
+	}
+}
+
+sub colorize_head_line {
+	my $line = shift @_;
+	if ($use_color) {
+		# git doesn't colorize these by default, soooo
+		# if ($line =~ /^\+/) {
+		#	 return parse_color($colorconfig{'color.diff.new'}) . "$line\e[m";
+		# }
+		# if ($line =~ /^-/) {
+		#	 return parse_color($colorconfig{'color.diff.old'}) . "$line\e[m";
+		# }
+		return parse_color($colorconfig{'color.diff.meta'}) . "$line\e[m";
+	}
+	return $line;
+}
+
+sub colorize_hunk_line {
+	my $line = shift @_;
+	if ($use_color) {
+		if ($line =~ /^\+/) {
+			return parse_color($colorconfig{'color.diff.new'}) . "$line\e[m";
+		}
+		if ($line =~ /^-/) {
+			return parse_color($colorconfig{'color.diff.old'}) . "$line\e[m";
+		}
+		if ($line =~ /^@@ /) {
+			return parse_color($colorconfig{'color.diff.frag'}) . "$line\e[m";
+		}
+	}
+	return $line;
+}
+
 sub refresh {
 	my $fh;
 	open $fh, 'git update-index --refresh |'
@@ -573,7 +680,7 @@ sub patch_update_cmd {
 	my $path = $it->{VALUE};
 	my ($head, @hunk) = parse_diff($path);
 	for (@{$head->{TEXT}}) {
-		print;
+		print colorize_head_line($_);
 	}
 	$num = scalar @hunk;
 	$ix = 0;
@@ -617,7 +724,7 @@ sub patch_update_cmd {
 			$other .= '/s';
 		}
 		for (@{$hunk[$ix]{TEXT}}) {
-			print;
+			print colorize_hunk_line($_);
 		}
 		print "Stage this hunk [y/n/a/d$other/?]? ";
 		my $line = <STDIN>;
-- 
1.5.3.4