vscode: let cSpell work on commit messages, too
[gitweb.git] / grep.c
diff --git a/grep.c b/grep.c
index b7012f40f556f47c0a5fa26df88bbafdcf04fe1c..cd7fc6f66cadfcb38ad73f171b97a419610aad37 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -1,12 +1,14 @@
 #include "cache.h"
 #include "config.h"
 #include "grep.h"
+#include "object-store.h"
 #include "userdiff.h"
 #include "xdiff-interface.h"
 #include "diff.h"
 #include "diffcore.h"
 #include "commit.h"
 #include "quote.h"
+#include "help.h"
 
 static int grep_source_load(struct grep_source *gs);
 static int grep_source_is_binary(struct grep_source *gs);
@@ -18,6 +20,7 @@ static const char *color_grep_slots[] = {
        [GREP_COLOR_FILENAME]       = "filename",
        [GREP_COLOR_FUNCTION]       = "function",
        [GREP_COLOR_LINENO]         = "lineNumber",
+       [GREP_COLOR_COLUMNNO]       = "column",
        [GREP_COLOR_MATCH_CONTEXT]  = "matchContext",
        [GREP_COLOR_MATCH_SELECTED] = "matchSelected",
        [GREP_COLOR_SELECTED]       = "selected",
@@ -57,6 +60,7 @@ void init_grep_defaults(void)
        color_set(opt->colors[GREP_COLOR_FILENAME], "");
        color_set(opt->colors[GREP_COLOR_FUNCTION], "");
        color_set(opt->colors[GREP_COLOR_LINENO], "");
+       color_set(opt->colors[GREP_COLOR_COLUMNNO], "");
        color_set(opt->colors[GREP_COLOR_MATCH_CONTEXT], GIT_COLOR_BOLD_RED);
        color_set(opt->colors[GREP_COLOR_MATCH_SELECTED], GIT_COLOR_BOLD_RED);
        color_set(opt->colors[GREP_COLOR_SELECTED], "");
@@ -80,6 +84,8 @@ static int parse_pattern_type_arg(const char *opt, const char *arg)
        die("bad %s argument: %s", opt, arg);
 }
 
+define_list_config_array_extra(color_grep_slots, {"match"});
+
 /*
  * Read the configuration file once and store it in
  * the grep_defaults template.
@@ -106,6 +112,10 @@ int grep_config(const char *var, const char *value, void *cb)
                opt->linenum = git_config_bool(var, value);
                return 0;
        }
+       if (!strcmp(var, "grep.column")) {
+               opt->columnnum = git_config_bool(var, value);
+               return 0;
+       }
 
        if (!strcmp(var, "grep.fullname")) {
                opt->relative = !git_config_bool(var, value);
@@ -153,6 +163,7 @@ void grep_init(struct grep_opt *opt, const char *prefix)
        opt->extended_regexp_option = def->extended_regexp_option;
        opt->pattern_type_option = def->pattern_type_option;
        opt->linenum = def->linenum;
+       opt->columnnum = def->columnnum;
        opt->max_depth = def->max_depth;
        opt->pathname = def->pathname;
        opt->relative = def->relative;
@@ -396,7 +407,7 @@ static void compile_pcre1_regexp(struct grep_pat *p, const struct grep_opt *opt)
                        die("Couldn't allocate PCRE JIT stack");
                pcre_assign_jit_stack(p->pcre1_extra_info, NULL, p->pcre1_jit_stack);
        } else if (p->pcre1_jit_on != 0) {
-               die("BUG: The pcre1_jit_on variable should be 0 or 1, not %d",
+               BUG("The pcre1_jit_on variable should be 0 or 1, not %d",
                    p->pcre1_jit_on);
        }
 #endif
@@ -542,7 +553,7 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt
                        die("Couldn't allocate PCRE2 match context");
                pcre2_jit_stack_assign(p->pcre2_match_context, NULL, p->pcre2_jit_stack);
        } else if (p->pcre2_jit_on != 0) {
-               die("BUG: The pcre2_jit_on variable should be 0 or 1, not %d",
+               BUG("The pcre2_jit_on variable should be 0 or 1, not %d",
                    p->pcre1_jit_on);
        }
 }
@@ -628,7 +639,6 @@ static void compile_fixed_regexp(struct grep_pat *p, struct grep_opt *opt)
        if (err) {
                char errbuf[1024];
                regerror(err, &p->regexp, errbuf, sizeof(errbuf));
-               regfree(&p->regexp);
                compile_regexp_failed(p, errbuf);
        }
 }
@@ -693,7 +703,6 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
        if (err) {
                char errbuf[1024];
                regerror(err, &p->regexp, errbuf, 1024);
-               regfree(&p->regexp);
                compile_regexp_failed(p, errbuf);
        }
 }
@@ -909,10 +918,10 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
 
        for (p = opt->header_list; p; p = p->next) {
                if (p->token != GREP_PATTERN_HEAD)
-                       die("BUG: a non-header pattern in grep header list.");
+                       BUG("a non-header pattern in grep header list.");
                if (p->field < GREP_HEADER_FIELD_MIN ||
                    GREP_HEADER_FIELD_MAX <= p->field)
-                       die("BUG: unknown header field %d", p->field);
+                       BUG("unknown header field %d", p->field);
                compile_regexp(p, opt);
        }
 
@@ -925,7 +934,7 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt)
 
                h = compile_pattern_atom(&pp);
                if (!h || pp != p->next)
-                       die("BUG: malformed header expr");
+                       BUG("malformed header expr");
                if (!header_group[p->field]) {
                        header_group[p->field] = h;
                        continue;
@@ -1242,11 +1251,11 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
        return hit;
 }
 
-static int match_expr_eval(struct grep_expr *x, char *bol, char *eol,
-                          enum grep_context ctx, int collect_hits)
+static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x, char *bol,
+                          char *eol, enum grep_context ctx, ssize_t *col,
+                          ssize_t *icol, int collect_hits)
 {
        int h = 0;
-       regmatch_t match;
 
        if (!x)
                die("Not a valid grep expression");
@@ -1255,25 +1264,52 @@ static int match_expr_eval(struct grep_expr *x, char *bol, char *eol,
                h = 1;
                break;
        case GREP_NODE_ATOM:
-               h = match_one_pattern(x->u.atom, bol, eol, ctx, &match, 0);
+               {
+                       regmatch_t tmp;
+                       h = match_one_pattern(x->u.atom, bol, eol, ctx,
+                                             &tmp, 0);
+                       if (h && (*col < 0 || tmp.rm_so < *col))
+                               *col = tmp.rm_so;
+               }
                break;
        case GREP_NODE_NOT:
-               h = !match_expr_eval(x->u.unary, bol, eol, ctx, 0);
+               /*
+                * Upon visiting a GREP_NODE_NOT, col and icol become swapped.
+                */
+               h = !match_expr_eval(opt, x->u.unary, bol, eol, ctx, icol, col,
+                                    0);
                break;
        case GREP_NODE_AND:
-               if (!match_expr_eval(x->u.binary.left, bol, eol, ctx, 0))
-                       return 0;
-               h = match_expr_eval(x->u.binary.right, bol, eol, ctx, 0);
+               h = match_expr_eval(opt, x->u.binary.left, bol, eol, ctx, col,
+                                   icol, 0);
+               if (h || opt->columnnum) {
+                       /*
+                        * Don't short-circuit AND when given --column, since a
+                        * NOT earlier in the tree may turn this into an OR. In
+                        * this case, see the below comment.
+                        */
+                       h &= match_expr_eval(opt, x->u.binary.right, bol, eol,
+                                            ctx, col, icol, 0);
+               }
                break;
        case GREP_NODE_OR:
-               if (!collect_hits)
-                       return (match_expr_eval(x->u.binary.left,
-                                               bol, eol, ctx, 0) ||
-                               match_expr_eval(x->u.binary.right,
-                                               bol, eol, ctx, 0));
-               h = match_expr_eval(x->u.binary.left, bol, eol, ctx, 0);
-               x->u.binary.left->hit |= h;
-               h |= match_expr_eval(x->u.binary.right, bol, eol, ctx, 1);
+               if (!(collect_hits || opt->columnnum)) {
+                       /*
+                        * Don't short-circuit OR when given --column (or
+                        * collecting hits) to ensure we don't skip a later
+                        * child that would produce an earlier match.
+                        */
+                       return (match_expr_eval(opt, x->u.binary.left, bol, eol,
+                                               ctx, col, icol, 0) ||
+                               match_expr_eval(opt, x->u.binary.right, bol,
+                                               eol, ctx, col, icol, 0));
+               }
+               h = match_expr_eval(opt, x->u.binary.left, bol, eol, ctx, col,
+                                   icol, 0);
+               if (collect_hits)
+                       x->u.binary.left->hit |= h;
+               h |= match_expr_eval(opt, x->u.binary.right, bol, eol, ctx, col,
+                                    icol, collect_hits);
                break;
        default:
                die("Unexpected node type (internal error) %d", x->node);
@@ -1284,27 +1320,43 @@ static int match_expr_eval(struct grep_expr *x, char *bol, char *eol,
 }
 
 static int match_expr(struct grep_opt *opt, char *bol, char *eol,
-                     enum grep_context ctx, int collect_hits)
+                     enum grep_context ctx, ssize_t *col,
+                     ssize_t *icol, int collect_hits)
 {
        struct grep_expr *x = opt->pattern_expression;
-       return match_expr_eval(x, bol, eol, ctx, collect_hits);
+       return match_expr_eval(opt, x, bol, eol, ctx, col, icol, collect_hits);
 }
 
 static int match_line(struct grep_opt *opt, char *bol, char *eol,
+                     ssize_t *col, ssize_t *icol,
                      enum grep_context ctx, int collect_hits)
 {
        struct grep_pat *p;
-       regmatch_t match;
+       int hit = 0;
 
        if (opt->extended)
-               return match_expr(opt, bol, eol, ctx, collect_hits);
+               return match_expr(opt, bol, eol, ctx, col, icol,
+                                 collect_hits);
 
        /* we do not call with collect_hits without being extended */
        for (p = opt->pattern_list; p; p = p->next) {
-               if (match_one_pattern(p, bol, eol, ctx, &match, 0))
-                       return 1;
+               regmatch_t tmp;
+               if (match_one_pattern(p, bol, eol, ctx, &tmp, 0)) {
+                       hit |= 1;
+                       if (!opt->columnnum) {
+                               /*
+                                * Without --column, any single match on a line
+                                * is enough to know that it needs to be
+                                * printed. With --column, scan _all_ patterns
+                                * to find the earliest.
+                                */
+                               break;
+                       }
+                       if (*col < 0 || tmp.rm_so < *col)
+                               *col = tmp.rm_so;
+               }
        }
-       return 0;
+       return hit;
 }
 
 static int match_next_pattern(struct grep_pat *p, char *bol, char *eol,
@@ -1353,7 +1405,7 @@ static int next_match(struct grep_opt *opt, char *bol, char *eol,
 }
 
 static void show_line(struct grep_opt *opt, char *bol, char *eol,
-                     const char *name, unsigned lno, char sign)
+                     const char *name, unsigned lno, ssize_t cno, char sign)
 {
        int rest = eol - bol;
        const char *match_color, *line_color = NULL;
@@ -1388,6 +1440,17 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
                output_color(opt, buf, strlen(buf), opt->colors[GREP_COLOR_LINENO]);
                output_sep(opt, sign);
        }
+       /*
+        * Treat 'cno' as the 1-indexed offset from the start of a non-context
+        * line to its first match. Otherwise, 'cno' is 0 indicating that we are
+        * being called with a context line.
+        */
+       if (opt->columnnum && cno) {
+               char buf[32];
+               xsnprintf(buf, sizeof(buf), "%"PRIuMAX, (uintmax_t)cno);
+               output_color(opt, buf, strlen(buf), opt->colors[GREP_COLOR_COLUMNNO]);
+               output_sep(opt, sign);
+       }
        if (opt->color) {
                regmatch_t match;
                enum grep_context ctx = GREP_CONTEXT_BODY;
@@ -1493,7 +1556,7 @@ static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs,
                        break;
 
                if (match_funcname(opt, gs, bol, eol)) {
-                       show_line(opt, bol, eol, gs->name, lno, '=');
+                       show_line(opt, bol, eol, gs->name, lno, 0, '=');
                        break;
                }
        }
@@ -1558,7 +1621,7 @@ static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
 
                while (*eol != '\n')
                        eol++;
-               show_line(opt, bol, eol, gs->name, cur, sign);
+               show_line(opt, bol, eol, gs->name, cur, 0, sign);
                bol = eol + 1;
                cur++;
        }
@@ -1644,7 +1707,7 @@ static int fill_textconv_grep(struct userdiff_driver *driver,
                fill_filespec(df, &null_oid, 0, 0100644);
                break;
        default:
-               die("BUG: attempt to textconv something without a path?");
+               BUG("attempt to textconv something without a path?");
        }
 
        /*
@@ -1740,7 +1803,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
                case GREP_BINARY_TEXT:
                        break;
                default:
-                       die("BUG: unknown binary handling mode");
+                       BUG("unknown binary handling mode");
                }
        }
 
@@ -1757,6 +1820,8 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
        while (left) {
                char *eol, ch;
                int hit;
+               ssize_t cno;
+               ssize_t col = -1, icol = -1;
 
                /*
                 * look_ahead() skips quickly to the line that possibly
@@ -1780,7 +1845,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
                if ((ctx == GREP_CONTEXT_HEAD) && (eol == bol))
                        ctx = GREP_CONTEXT_BODY;
 
-               hit = match_line(opt, bol, eol, ctx, collect_hits);
+               hit = match_line(opt, bol, eol, &col, &icol, ctx, collect_hits);
                *eol = ch;
 
                if (collect_hits)
@@ -1821,7 +1886,18 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
                                show_pre_context(opt, gs, bol, eol, lno);
                        else if (opt->funcname)
                                show_funcname_line(opt, gs, bol, lno);
-                       show_line(opt, bol, eol, gs->name, lno, ':');
+                       cno = opt->invert ? icol : col;
+                       if (cno < 0) {
+                               /*
+                                * A negative cno indicates that there was no
+                                * match on the line. We are thus inverted and
+                                * being asked to show all lines that _don't_
+                                * match a given expression. Therefore, set cno
+                                * to 0 to suggest the whole line matches.
+                                */
+                               cno = 0;
+                       }
+                       show_line(opt, bol, eol, gs->name, lno, cno + 1, ':');
                        last_hit = lno;
                        if (opt->funcbody)
                                show_function = 1;
@@ -1850,7 +1926,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
                        /* If the last hit is within the post context,
                         * we need to show this line.
                         */
-                       show_line(opt, bol, eol, gs->name, lno, '-');
+                       show_line(opt, bol, eol, gs->name, lno, col + 1, '-');
                }
 
        next_line:
@@ -2064,7 +2140,7 @@ static int grep_source_load(struct grep_source *gs)
        case GREP_SOURCE_BUF:
                return gs->buf ? 0 : -1;
        }
-       die("BUG: invalid grep_source type to load");
+       BUG("invalid grep_source type to load");
 }
 
 void grep_source_load_driver(struct grep_source *gs)