Subject: [PATCH 16/16] ls: color output

From: Nguyễn Thái Ngọc Duy <[email-masked]>
Date: Wed, 9 Feb 2011 19:24:44 +0700

The rules are currently hardcoded (the first item has highest priority)

 - If it's a modified entry, it's bold red.
 - If it's an executable, it's bold green.
 - If it's a directory, it's bold blue.

Personally I'm happy with just that. But people might want to separate
other entries from cached ones in "ls -co", anyone?

Signed-off-by: Nguyễn Thái Ngọc Duy <[email-masked]>
---
 Bad bad bad hard coding.

 builtin/ls-files.c |   59 ++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 50 insertions(+), 9 deletions(-)

diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index bc438b2..9c8179a 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -14,6 +14,7 @@
 #include "resolve-undo.h"
 #include "string-list.h"
 #include "column.h"
+#include "color.h"
 
 static int abbrev;
 static int show_deleted;
@@ -37,6 +38,7 @@ static int error_unmatch;
 static char *ps_matched;
 static const char *with_tree;
 static int exc_given;
+static int use_color;
 
 static const char *tag_cached = "";
 static const char *tag_unmerged = "";
@@ -50,12 +52,18 @@ static const char *tag_resolve_undo = "";
 static struct string_list output;
 static int column_mode;
 
-static void write_name(const char* name, size_t len)
+static void write_name(const char* name, size_t len, const char *color)
 {
 	/* No quoting in column layout. It will be done by the end. */
 	if (column_mode & COL_MODE) {
 		struct strbuf sb = STRBUF_INIT;
-		strbuf_add(&sb, name, len);
+		if (color && use_color) {
+			strbuf_addstr(&sb, color);
+			strbuf_add(&sb, name, len);
+			strbuf_addstr(&sb, GIT_COLOR_RESET);
+		}
+		else
+			strbuf_add(&sb, name, len);
 		string_list_append(&output, strbuf_detach(&sb, NULL));
 		return;
 	}
@@ -81,16 +89,29 @@ static const char *path_too_deep(const char *name)
 	return strchr(name + common, '/');
 }
 
+static int ansi_length(const char *s)
+{
+	const char *p = s;
+	while (p[0] == '\033')
+		p += strspn(p + 2, "0123456789;") + 3;
+	return p - s;
+}
+
 static int already_shown(const char *name)
 {
 	const char *last_item;
-	int len;
+	int len, ansi_len;
 
 	if (!output.nr)
 		return 0;
 
 	last_item = output.items[output.nr-1].string;
 	len = strlen(last_item);
+	ansi_len = ansi_length(last_item);
+	if (ansi_len) {
+		last_item += ansi_len;
+		len -= ansi_len + strlen(GIT_COLOR_RESET);
+	}
 	return !strncmp(last_item, name, len) && name[len] == '/';
 }
 
@@ -98,6 +119,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)
 {
 	int len = max_prefix_len;
 	const char *too_deep;
+	struct stat st;
 
 	if (len >= ent->len)
 		die("git ls-files: internal error - directory entry not superset of prefix");
@@ -111,7 +133,9 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)
 
 	len = too_deep ? too_deep - ent->name : ent->len;
 	fputs(tag, stdout);
-	write_name(ent->name, len);
+	write_name(ent->name, len,
+		   len < ent->len ? GIT_COLOR_BOLD_BLUE :
+		   (!stat(ent->name, &st) && st.st_mode & S_IXUSR ? GIT_COLOR_BOLD_GREEN : NULL));
 }
 
 static void show_other_files(struct dir_struct *dir)
@@ -179,6 +203,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
 {
 	int len = max_prefix_len;
 	const char *too_deep;
+	struct stat st;
 	int namelen;
 
 	if (len >= ce_namelen(ce))
@@ -218,7 +243,10 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
 		       find_unique_abbrev(ce->sha1,abbrev),
 		       ce_stage(ce));
 	}
-	write_name(ce->name, namelen);
+	write_name(ce->name, namelen,
+		   namelen < ce_namelen(ce) ? GIT_COLOR_BOLD_BLUE :
+		   (!stat(ce->name, &st) && ce_modified(ce, &st, 0) ? GIT_COLOR_BOLD_RED :
+		    (ce_permissions(ce->ce_mode) == 0755 ? GIT_COLOR_BOLD_GREEN : NULL)));
 	if (debug_mode) {
 		printf("  ctime: %d:%d\n", ce->ce_ctime.sec, ce->ce_ctime.nsec);
 		printf("  mtime: %d:%d\n", ce->ce_mtime.sec, ce->ce_mtime.nsec);
@@ -251,7 +279,7 @@ static void show_ru_info(void)
 			printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i],
 			       find_unique_abbrev(ui->sha1[i], abbrev),
 			       i + 1);
-			write_name(path, len);
+			write_name(path, len, NULL);
 		}
 	}
 }
@@ -676,6 +704,16 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
 	return 0;
 }
 
+static int ls_config(const char *var, const char *value, void *cb)
+{
+	if (!strcmp(var, "color.ls"))
+		use_color = git_config_colorbool(var, value, -1);
+	else
+		return git_color_default_config(var, value, cb);
+
+	return 0;
+}
+
 static int one_match_pathspec(const char *pathspec)
 {
 	struct stat st;
@@ -724,11 +762,13 @@ static void remove_common_prefix()
 	 */
 
 	s1 = output.items[output.nr-1].string;
-	len = strlen(s1);
+	len = strlen(s1) - ansi_length(s1);
 	for (i = 1; i < output.nr; i++) {
 		int j = 0;
 		s1 = output.items[i-1].string;
+		s1 += ansi_length(s1);
 		s2 = output.items[i].string;
+		s2 += ansi_length(s2);
 		while (j < len && s1[j] == s2[j])
 			j++;
 		len = j;
@@ -740,6 +780,7 @@ static void remove_common_prefix()
 	if (len) {
 		for (i = 0; i < output.nr; i++) {
 			char *s = output.items[i].string;
+			s += ansi_length(s);
 			memcpy(s, s+len, strlen(s) - len + 1);
 		}
 	}
@@ -778,12 +819,12 @@ int cmd_ls(int argc, const char **argv, const char *cmd_prefix)
 	prefix = cmd_prefix;
 	if (prefix)
 		prefix_len = strlen(prefix);
-	git_config(git_default_config, NULL);
+	git_config(ls_config, NULL);
 
 	if (read_cache() < 0)
 		die("index file corrupt");
 
-	column_mode = core_column;
+	column_mode = core_column | COL_ANSI;
 
 	argc = parse_options(argc, argv, prefix, builtin_ls_files_options,
 			ls_files_usage, 0);
@@ -793,7 +834,7 @@ int cmd_ls(int argc, const char **argv, const char *cmd_prefix)
 
 	pathspecs = get_pathspec(prefix, argv);
 
-	if (show_modified)
+	if (show_modified || use_color)
 		refresh_index(&the_index, REFRESH_QUIET, pathspecs, NULL, NULL);
 
 	if (!pathspecs) {