diff options
Diffstat (limited to 'grep.c')
-rw-r--r-- | grep.c | 218 |
1 files changed, 159 insertions, 59 deletions
@@ -1,6 +1,7 @@ #include "cache.h" #include "config.h" #include "grep.h" +#include "object-store.h" #include "userdiff.h" #include "xdiff-interface.h" #include "diff.h" @@ -19,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", @@ -58,10 +60,12 @@ 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], ""); color_set(opt->colors[GREP_COLOR_SEP], GIT_COLOR_CYAN); + opt->only_matching = 0; opt->color = -1; opt->output = std_output; } @@ -109,6 +113,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); @@ -152,10 +160,12 @@ void grep_init(struct grep_opt *opt, const char *prefix) opt->pattern_tail = &opt->pattern_list; opt->header_tail = &opt->header_list; + opt->only_matching = def->only_matching; opt->color = def->color; 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; @@ -1243,11 +1253,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"); @@ -1256,25 +1266,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); @@ -1285,27 +1322,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,11 +1406,44 @@ static int next_match(struct grep_opt *opt, char *bol, char *eol, return hit; } +static void show_line_header(struct grep_opt *opt, const char *name, + unsigned lno, ssize_t cno, char sign) +{ + if (opt->heading && opt->last_shown == 0) { + output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]); + opt->output(opt, "\n", 1); + } + opt->last_shown = lno; + + if (!opt->heading && opt->pathname) { + output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]); + output_sep(opt, sign); + } + if (opt->linenum) { + char buf[32]; + xsnprintf(buf, sizeof(buf), "%d", lno); + 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); + } +} + 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; + const char *match_color = NULL; + const char *line_color = NULL; if (opt->file_break && opt->last_shown == 0) { if (opt->show_hunk_mark) @@ -1373,54 +1459,55 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol, opt->output(opt, "\n", 1); } } - if (opt->heading && opt->last_shown == 0) { - output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]); - opt->output(opt, "\n", 1); - } - opt->last_shown = lno; - - if (!opt->heading && opt->pathname) { - output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]); - output_sep(opt, sign); - } - if (opt->linenum) { - char buf[32]; - xsnprintf(buf, sizeof(buf), "%d", lno); - output_color(opt, buf, strlen(buf), opt->colors[GREP_COLOR_LINENO]); - output_sep(opt, sign); + if (!opt->only_matching) { + /* + * In case the line we're being called with contains more than + * one match, leave printing each header to the loop below. + */ + show_line_header(opt, name, lno, cno, sign); } - if (opt->color) { + if (opt->color || opt->only_matching) { regmatch_t match; enum grep_context ctx = GREP_CONTEXT_BODY; int ch = *eol; int eflags = 0; - if (sign == ':') - match_color = opt->colors[GREP_COLOR_MATCH_SELECTED]; - else - match_color = opt->colors[GREP_COLOR_MATCH_CONTEXT]; - if (sign == ':') - line_color = opt->colors[GREP_COLOR_SELECTED]; - else if (sign == '-') - line_color = opt->colors[GREP_COLOR_CONTEXT]; - else if (sign == '=') - line_color = opt->colors[GREP_COLOR_FUNCTION]; + if (opt->color) { + if (sign == ':') + match_color = opt->colors[GREP_COLOR_MATCH_SELECTED]; + else + match_color = opt->colors[GREP_COLOR_MATCH_CONTEXT]; + if (sign == ':') + line_color = opt->colors[GREP_COLOR_SELECTED]; + else if (sign == '-') + line_color = opt->colors[GREP_COLOR_CONTEXT]; + else if (sign == '=') + line_color = opt->colors[GREP_COLOR_FUNCTION]; + } *eol = '\0'; while (next_match(opt, bol, eol, ctx, &match, eflags)) { if (match.rm_so == match.rm_eo) break; - output_color(opt, bol, match.rm_so, line_color); + if (opt->only_matching) + show_line_header(opt, name, lno, cno, sign); + else + output_color(opt, bol, match.rm_so, line_color); output_color(opt, bol + match.rm_so, match.rm_eo - match.rm_so, match_color); + if (opt->only_matching) + opt->output(opt, "\n", 1); bol += match.rm_eo; + cno += match.rm_eo; rest -= match.rm_eo; eflags = REG_NOTBOL; } *eol = ch; } - output_color(opt, bol, rest, line_color); - opt->output(opt, "\n", 1); + if (!opt->only_matching) { + output_color(opt, bol, rest, line_color); + opt->output(opt, "\n", 1); + } } #ifndef NO_PTHREADS @@ -1494,7 +1581,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; } } @@ -1559,7 +1646,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++; } @@ -1758,6 +1845,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 @@ -1781,7 +1870,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) @@ -1822,7 +1911,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; @@ -1851,7 +1951,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: |