{"thread":{"id":"14873","subject":"[PATCH] git grep colorized","startedAt":"2008-08-07T11:53:45Z","lastAt":"2008-08-07T11:53:45Z","messageCount":1,"participants":["Thiago Alves"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"86402","messageId":"ecd9a7b10808070453o3e0b34e5w27564d2e4ee73191@mail.gmail.com","threadId":"14873","inReplyTo":null,"subject":"[PATCH] git grep colorized","fromName":"Thiago Alves","fromEmail":"thiago.salves@gmail.com","sentAt":"2008-08-07T11:53:45Z","receivedAt":"2008-08-07T11:53:45Z","isPatch":true,"sender":{"key":"thiago.salves@gmail.com","avatar":null},"body":"Hi guys,\n\nrecently I try to put some colors on my git-grep and found a patch\nfrom Nguyễn Thái Ngọc Duy <pclouds@gmail.com> to do it!\nThe problem with this patch is that it doesn't support the config file\nto persist the color option on grep.\n\nI took the liberty to change his patch and add support to config files\nand bash completion.\n\nAttached is the resultant patch!\n\nI hope this could be useful to someone.\n\nThiago dos Santos Alves\nComputer Scientist\n\nthiago.salves@gmail.com\n-----------------------------------------------------\n\"Man's mind, once stretched by a new idea, never regains it's original\ndimensions.\"\n\n\ndiff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\nindex fa4d133..8554068 100644\n--- a/Documentation/git-grep.txt\n+++ b/Documentation/git-grep.txt\n@@ -15,7 +15,7 @@ SYNOPSIS\n \t   [-E | --extended-regexp] [-G | --basic-regexp]\n \t   [-F | --fixed-strings] [-n]\n \t   [-l | --files-with-matches] [-L | --files-without-match]\n-\t   [-c | --count] [--all-match]\n+\t   [-c | --count] [--all-match] [--color | --no-color]\n \t   [-A <post-context>] [-B <pre-context>] [-C <context>]\n \t   [-f <file>] [-e] <pattern>\n \t   [--and|--or|--not|(|)|-e <pattern>...] [<tree>...]\n@@ -99,6 +99,13 @@ OPTIONS\n \tInstead of showing every matched line, show the number of\n \tlines that match.\n \n+--color::\n+\tColorize grep output highlighting matches, file names and line numbers.\n+\n+--no-color::\n+\tTurn off grep colors, even when the configuration file gives the\n+\tdefault to color output.\n+\n -[ABC] <context>::\n \tShow `context` trailing (`A` -- after), or leading (`B`\n \t-- before), or both (`C` -- context) lines, and place a\ndiff --git a/builtin-grep.c b/builtin-grep.c\nindex 631129d..29e7d5a 100644\n--- a/builtin-grep.c\n+++ b/builtin-grep.c\n@@ -11,6 +11,7 @@\n #include \"tree-walk.h\"\n #include \"builtin.h\"\n #include \"grep.h\"\n+#include \"color.h\"\n \n #ifndef NO_EXTERNAL_GREP\n #ifdef __unix__\n@@ -20,6 +21,7 @@\n #endif\n #endif\n \n+static int grep_use_color = -1;\n /*\n  * git grep pathspecs are somewhat different from diff-tree pathspecs;\n  * pathname wildcards are allowed.\n@@ -386,7 +388,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)\n \t * we grep through the checked-out files. It tends to\n \t * be a lot more optimized\n \t */\n-\tif (!cached) {\n+\tif (!cached && !opt->color) {\n \t\thit = external_grep(opt, paths, cached);\n \t\tif (hit >= 0)\n \t\t\treturn hit;\n@@ -507,6 +509,22 @@ static const char emsg_missing_context_len[] =\n static const char emsg_missing_argument[] =\n \"option requires an argument -%s\";\n \n+static int git_grep_config(const char *var, const char *value, void *cb)\n+{\n+\tif (!strcmp(var, \"color.grep\")) {\n+\t\tgrep_use_color = git_config_colorbool(var, value, -1);\n+\t\treturn 0;\n+\t}\n+    if (!prefixcmp(var, \"color.grep.\")) {\n+\t\tint slot = parse_grep_color_slot(var, 11);\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tcolor_parse(value, var, grep_get_color(slot));\n+\t\treturn 0;\n+\t}\n+\treturn git_color_default_config(var, value, cb);\n+}\n+\n int cmd_grep(int argc, const char **argv, const char *prefix)\n {\n \tint hit = 0;\n@@ -524,6 +542,11 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \topt.pattern_tail = &opt.pattern_list;\n \topt.regflags = REG_NEWLINE;\n \n+\tgit_config(git_grep_config, NULL);\n+\tif (grep_use_color == -1)\n+\t\tgrep_use_color = git_use_color_default;\n+    opt.color = grep_use_color;\n+\n \t/*\n \t * If there is no -- then the paths must exist in the working\n \t * tree.  If there is no explicit pattern specified with -e or\n@@ -711,6 +734,14 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\t\topt.relative = 0;\n \t\t\tcontinue;\n \t\t}\n+\t\tif (!strcmp(\"--color\", arg)) {\n+\t\t\topt.color = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(\"--no-color\", arg)) {\n+\t\t\topt.color = 0;\n+\t\t\tcontinue;\n+\t\t}\n \t\tif (!strcmp(\"--\", arg)) {\n \t\t\t/* later processing wants to have this at argv[1] */\n \t\t\targv--;\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 3396e35..ee4d509 100755\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1157,7 +1157,7 @@ _git_config ()\n \t\t__gitcomp \"$(__git_merge_strategies)\"\n \t\treturn\n \t\t;;\n-\tcolor.branch|color.diff|color.status)\n+\tcolor.branch|color.diff|color.status|color.grep)\n \t\t__gitcomp \"always never auto\"\n \t\treturn\n \t\t;;\n@@ -1240,6 +1240,11 @@ _git_config ()\n \t\tcolor.diff.new\n \t\tcolor.diff.commit\n \t\tcolor.diff.whitespace\n+\t\tcolor.grep\n+\t\tcolor.grep.filepath\n+\t\tcolor.grep.lineno\n+\t\tcolor.grep.separator\n+\t\tcolor.grep.match\n \t\tcolor.pager\n \t\tcolor.status\n \t\tcolor.status.header\n@@ -1390,6 +1395,22 @@ _git_shortlog ()\n \t__git_complete_revlist\n }\n \n+_git_grep ()\n+{\n+    local cur=\"${COMP_WORDS[COMP_CWORD]}\"\n+    case \"$cur\" in\n+    --*)\n+        __gitcomp \"\n+        --cached --text --ignore-case --word-regexp --invert-match\n+        --full-name --extend-regexp --fixed-strings --files-with-matches\n+        --files-without-match --count --and --or --not --all-match\n+        --color\n+        \"\n+        return\n+        ;;\n+    esac\n+}\n+\n _git_show ()\n {\n \tlocal cur=\"${COMP_WORDS[COMP_CWORD]}\"\n@@ -1642,7 +1663,7 @@ _git ()\n \tfetch)       _git_fetch ;;\n \tformat-patch) _git_format_patch ;;\n \tgc)          _git_gc ;;\n-\tgrep)        _git_grep ;;\n+    grep)        _git_grep ;;\n \thelp)        _git_help ;;\n \tinit)        _git_init ;;\n \tlog)         _git_log ;;\ndiff --git a/grep.c b/grep.c\nindex f67d671..5c3e6cd 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -1,7 +1,39 @@\n #include \"cache.h\"\n #include \"grep.h\"\n+#include \"color.h\"\n #include \"xdiff-interface.h\"\n \n+static char grep_colors[][COLOR_MAXLEN] = {\n+\t\"\\033[m\",          /* reset */\n+\t\"\",\t               /* PLAIN (normal) */\n+\t\"\\033[35m\",        /* FILEPATH (magenta) */\n+\t\"\\033[32m\",        /* LINENO (green) */\n+\t\"\\033[36m\",        /* SEPARATOR (cyan) */\n+    \"\\033[1m\\033[31m\", /* MATCH (red)*/\n+};\n+\n+const char *grep_get_color(enum color_grep ix)\n+{\n+\treturn grep_colors[ix];\n+}\n+\n+int parse_grep_color_slot(const char *var, int ofs)\n+{\n+\tif (!strcasecmp(var+ofs, \"plain\"))\n+\t\treturn COLOR_GREP_PLAIN;\n+\tif (!strcasecmp(var+ofs, \"reset\"))\n+\t\treturn COLOR_GREP_RESET;\n+\tif (!strcasecmp(var+ofs, \"filepath\"))\n+\t\treturn COLOR_GREP_FILEPATH;\n+\tif (!strcasecmp(var+ofs, \"lineno\"))\n+\t\treturn COLOR_GREP_LINENO;\n+\tif (!strcasecmp(var+ofs, \"separator\"))\n+\t\treturn COLOR_GREP_SEPARATOR;\n+\tif (!strcasecmp(var+ofs, \"match\"))\n+\t\treturn COLOR_GREP_MATCH;\n+\tdie(\"bad config variable '%s'\", var);\n+}\n+\n void append_grep_pattern(struct grep_opt *opt, const char *pat,\n \t\t\t const char *origin, int no, enum grep_pat_token t)\n {\n@@ -247,7 +279,10 @@ static int fixmatch(const char *pattern, char *line, regmatch_t *match)\n \t}\n }\n \n-static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol, enum grep_context ctx)\n+static int match_one_pattern_1(struct grep_opt *opt, struct grep_pat *p,\n+\t\t\t       char *bol, char *eol,\n+\t\t\t       enum grep_context ctx, int eflags,\n+\t\t\t       int *rm_so, int *rm_eo)\n {\n \tint hit = 0;\n \tint at_true_bol = 1;\n@@ -261,7 +296,7 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol\n \tif (!opt->fixed) {\n \t\tregex_t *exp = &p->regexp;\n \t\thit = !regexec(exp, bol, ARRAY_SIZE(pmatch),\n-\t\t\t       pmatch, 0);\n+\t\t\t       pmatch, eflags);\n \t}\n \telse {\n \t\thit = !fixmatch(p->pattern, bol, pmatch);\n@@ -298,9 +333,20 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol\n \t\t\tgoto again;\n \t\t}\n \t}\n+\tif (hit) {\n+\t\tif (rm_so)\n+\t\t\t*rm_so = pmatch[0].rm_so;\n+\t\tif (rm_eo)\n+\t\t\t*rm_eo = pmatch[0].rm_eo;\n+\t}\n \treturn hit;\n }\n \n+static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol, enum grep_context ctx)\n+{\n+\treturn match_one_pattern_1(opt, p, bol, eol, ctx, 0, NULL, NULL);\n+}\n+\n static int match_expr_eval(struct grep_opt *o,\n \t\t\t   struct grep_expr *x,\n \t\t\t   char *bol, char *eol,\n@@ -365,6 +411,54 @@ static int match_line(struct grep_opt *opt, char *bol, char *eol,\n \treturn 0;\n }\n \n+static void show_line_colored(struct grep_opt *opt, char *bol, char *eol,\n+\t\t\t      const char *name, unsigned lno, char sign)\n+{\n+\tstruct grep_pat *p;\n+\tint rm_so, rm_eo, eflags = 0;\n+\tint ch;\n+\n+\tif (opt->pathname)\n+\t\tprintf(\"%s%s%s%c%s\", \n+               grep_get_color(COLOR_GREP_FILEPATH), \n+               name, \n+               grep_get_color(COLOR_GREP_SEPARATOR), \n+               sign, \n+               grep_get_color(COLOR_GREP_RESET));\n+\tif (opt->linenum)\n+\t\tprintf(\"%s%d%s%c%s\", \n+               grep_get_color(COLOR_GREP_LINENO), \n+               lno, \n+               grep_get_color(COLOR_GREP_SEPARATOR), \n+               sign, \n+               grep_get_color(COLOR_GREP_RESET));\n+\n+\tch = *eol;\n+\t*eol = 0;\n+\twhile (bol < eol) {\n+\t\tfor (p = opt->pattern_list; p; p = p->next) {\n+\t\t\tif (match_one_pattern_1(opt, p, bol, eol, GREP_CONTEXT_BODY, eflags, &rm_so, &rm_eo))\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\t/* No match, break the loop */\n+\t\tif (!p ||\n+\t\t    (rm_so < 0) || (eol - bol) <= rm_so ||\n+\t\t    (rm_eo < 0) || (eol - bol) < rm_eo)\n+\t\t\tbreak;\n+\n+\t\tprintf(\"%.*s%s%.*s%s\",\n+\t\t       rm_so, bol,\n+               grep_get_color(COLOR_GREP_MATCH), \n+\t\t       rm_eo-rm_so, bol+rm_so,\n+               grep_get_color(COLOR_GREP_RESET));\n+\t\tbol += rm_eo;\n+\t\teflags = REG_NOTBOL;\n+\t}\n+\tprintf(\"%s\\n\", bol);\n+\t*eol = ch;\n+}\n+\n static int grep_buffer_1(struct grep_opt *opt, const char *name,\n \t\t\t char *buf, unsigned long size, int collect_hits)\n {\n@@ -466,8 +560,12 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,\n \t\t\t}\n \t\t\tif (last_shown && lno != last_shown + 1)\n \t\t\t\tprintf(hunk_mark);\n-\t\t\tif (!opt->count)\n-\t\t\t\tshow_line(opt, bol, eol, name, lno, ':');\n+\t\t\tif (!opt->count) {\n+\t\t\t\tif (opt->color)\n+\t\t\t\t\tshow_line_colored(opt, bol, eol, name, lno, ':');\n+\t\t\t\telse\n+\t\t\t\t\tshow_line(opt, bol, eol, name, lno, ':');\n+\t\t\t}\n \t\t\tlast_shown = last_hit = lno;\n \t\t}\n \t\telse if (last_hit &&\ndiff --git a/grep.h b/grep.h\nindex d252dd2..9073114 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -33,6 +33,15 @@ enum grep_expr_node {\n \tGREP_NODE_OR,\n };\n \n+enum color_grep {\n+\tCOLOR_GREP_RESET = 0,\n+\tCOLOR_GREP_PLAIN = 1,\n+\tCOLOR_GREP_FILEPATH = 2,\n+\tCOLOR_GREP_LINENO = 3,\n+\tCOLOR_GREP_SEPARATOR = 4,\n+    COLOR_GREP_MATCH = 5,\n+};\n+\n struct grep_expr {\n \tenum grep_expr_node node;\n \tunsigned hit;\n@@ -68,11 +77,14 @@ struct grep_opt {\n \tunsigned extended:1;\n \tunsigned relative:1;\n \tunsigned pathname:1;\n+\tunsigned color:1;\n \tint regflags;\n \tunsigned pre_context;\n \tunsigned post_context;\n };\n \n+extern const char *grep_get_color(enum color_grep ix);\n+extern int parse_grep_color_slot(const char *var, int ofs);\n extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t);\n extern void compile_grep_patterns(struct grep_opt *opt);\n extern void free_grep_patterns(struct grep_opt *opt);\n"}]}