diff options
Diffstat (limited to 'grep.c')
-rw-r--r-- | grep.c | 311 |
1 files changed, 203 insertions, 108 deletions
@@ -1,18 +1,32 @@ #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); static struct grep_opt grep_defaults; +static const char *color_grep_slots[] = { + [GREP_COLOR_CONTEXT] = "context", + [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", + [GREP_COLOR_SEP] = "separator", +}; + static void std_output(struct grep_opt *opt, const void *buf, size_t size) { fwrite(buf, size, 1, stdout); @@ -42,14 +56,16 @@ void init_grep_defaults(void) opt->pathname = 1; opt->max_depth = -1; opt->pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED; - color_set(opt->color_context, ""); - color_set(opt->color_filename, ""); - color_set(opt->color_function, ""); - color_set(opt->color_lineno, ""); - color_set(opt->color_match_context, GIT_COLOR_BOLD_RED); - color_set(opt->color_match_selected, GIT_COLOR_BOLD_RED); - color_set(opt->color_selected, ""); - color_set(opt->color_sep, GIT_COLOR_CYAN); + color_set(opt->colors[GREP_COLOR_CONTEXT], ""); + 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; } @@ -69,6 +85,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. @@ -76,7 +94,7 @@ static int parse_pattern_type_arg(const char *opt, const char *arg) int grep_config(const char *var, const char *value, void *cb) { struct grep_opt *opt = &grep_defaults; - char *color = NULL; + const char *slot; if (userdiff_config(var, value) < 0) return -1; @@ -95,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); @@ -103,32 +125,18 @@ int grep_config(const char *var, const char *value, void *cb) if (!strcmp(var, "color.grep")) opt->color = git_config_colorbool(var, value); - else if (!strcmp(var, "color.grep.context")) - color = opt->color_context; - else if (!strcmp(var, "color.grep.filename")) - color = opt->color_filename; - else if (!strcmp(var, "color.grep.function")) - color = opt->color_function; - else if (!strcmp(var, "color.grep.linenumber")) - color = opt->color_lineno; - else if (!strcmp(var, "color.grep.matchcontext")) - color = opt->color_match_context; - else if (!strcmp(var, "color.grep.matchselected")) - color = opt->color_match_selected; - else if (!strcmp(var, "color.grep.selected")) - color = opt->color_selected; - else if (!strcmp(var, "color.grep.separator")) - color = opt->color_sep; - else if (!strcmp(var, "color.grep.match")) { - int rc = 0; - if (!value) - return config_error_nonbool(var); - rc |= color_parse(value, opt->color_match_context); - rc |= color_parse(value, opt->color_match_selected); - return rc; - } - - if (color) { + if (!strcmp(var, "color.grep.match")) { + if (grep_config("color.grep.matchcontext", value, cb) < 0) + return -1; + if (grep_config("color.grep.matchselected", value, cb) < 0) + return -1; + } else if (skip_prefix(var, "color.grep.", &slot)) { + int i = LOOKUP_CONFIG(color_grep_slots, slot); + char *color; + + if (i < 0) + return -1; + color = opt->colors[i]; if (!value) return config_error_nonbool(var); return color_parse(value, color); @@ -144,6 +152,7 @@ int grep_config(const char *var, const char *value, void *cb) void grep_init(struct grep_opt *opt, const char *prefix) { struct grep_opt *def = &grep_defaults; + int i; memset(opt, 0, sizeof(*opt)); opt->prefix = prefix; @@ -151,23 +160,19 @@ 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; opt->output = def->output; - color_set(opt->color_context, def->color_context); - color_set(opt->color_filename, def->color_filename); - color_set(opt->color_function, def->color_function); - color_set(opt->color_lineno, def->color_lineno); - color_set(opt->color_match_context, def->color_match_context); - color_set(opt->color_match_selected, def->color_match_selected); - color_set(opt->color_selected, def->color_selected); - color_set(opt->color_sep, def->color_sep); + for (i = 0; i < NR_GREP_COLORS; i++) + color_set(opt->colors[i], def->colors[i]); } static void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct grep_opt *opt) @@ -1098,12 +1103,12 @@ static void output_sep(struct grep_opt *opt, char sign) if (opt->null_following_name) opt->output(opt, "\0", 1); else - output_color(opt, &sign, 1, opt->color_sep); + output_color(opt, &sign, 1, opt->colors[GREP_COLOR_SEP]); } static void show_name(struct grep_opt *opt, const char *name) { - output_color(opt, name, strlen(name), opt->color_filename); + output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]); opt->output(opt, opt->null_following_name ? "\0" : "\n", 1); } @@ -1248,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"); @@ -1261,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); @@ -1290,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, @@ -1358,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) @@ -1370,62 +1451,63 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol, } else if (opt->pre_context || opt->post_context || opt->funcbody) { if (opt->last_shown == 0) { if (opt->show_hunk_mark) { - output_color(opt, "--", 2, opt->color_sep); + output_color(opt, "--", 2, opt->colors[GREP_COLOR_SEP]); opt->output(opt, "\n", 1); } } else if (lno > opt->last_shown + 1) { - output_color(opt, "--", 2, opt->color_sep); + output_color(opt, "--", 2, opt->colors[GREP_COLOR_SEP]); opt->output(opt, "\n", 1); } } - if (opt->heading && opt->last_shown == 0) { - output_color(opt, name, strlen(name), opt->color_filename); - opt->output(opt, "\n", 1); - } - opt->last_shown = lno; - - if (!opt->heading && opt->pathname) { - output_color(opt, name, strlen(name), opt->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->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->color_match_selected; - else - match_color = opt->color_match_context; - if (sign == ':') - line_color = opt->color_selected; - else if (sign == '-') - line_color = opt->color_context; - else if (sign == '=') - line_color = opt->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 @@ -1499,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; } } @@ -1564,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++; } @@ -1763,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 @@ -1786,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) @@ -1816,7 +1900,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle if (binary_match_only) { opt->output(opt, "Binary file ", 12); output_color(opt, gs->name, strlen(gs->name), - opt->color_filename); + opt->colors[GREP_COLOR_FILENAME]); opt->output(opt, " matches\n", 9); return 1; } @@ -1827,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; @@ -1856,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: @@ -1890,7 +1985,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle char buf[32]; if (opt->pathname) { output_color(opt, gs->name, strlen(gs->name), - opt->color_filename); + opt->colors[GREP_COLOR_FILENAME]); output_sep(opt, ':'); } xsnprintf(buf, sizeof(buf), "%u\n", count); |