Skip to content

Commit b332368

Browse files
committed
Merge branch 'rs/color-grep'
* rs/color-grep: grep: prefer builtin over external one when coloring results grep: cast printf %.*s "precision" argument explicitly to int grep: add support for coloring with external greps grep: color patterns in output grep: add pmatch and eflags arguments to match_one_pattern() grep: remove grep_opt argument from match_expr_eval() grep: micro-optimize hit collection for AND nodes
2 parents ca8a36e + 6e89ec0 commit b332368

File tree

5 files changed

+205
-42
lines changed

5 files changed

+205
-42
lines changed

Documentation/config.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,25 @@ color.diff.<slot>::
553553
whitespace errors). The values of these variables may be specified as
554554
in color.branch.<slot>.
555555

556+
color.grep::
557+
When set to `always`, always highlight matches. When `false` (or
558+
`never`), never. When set to `true` or `auto`, use color only
559+
when the output is written to the terminal. Defaults to `false`.
560+
561+
color.grep.external::
562+
The string value of this variable is passed to an external 'grep'
563+
command as a command line option if match highlighting is turned
564+
on. If set to an empty string, no option is passed at all,
565+
turning off coloring for external 'grep' calls; this is the default.
566+
For GNU grep, set it to `--color=always` to highlight matches even
567+
when a pager is used.
568+
569+
color.grep.match::
570+
Use customized color for matches. The value of this variable
571+
may be specified as in color.branch.<slot>. It is passed using
572+
the environment variables 'GREP_COLOR' and 'GREP_COLORS' when
573+
calling an external 'grep'.
574+
556575
color.interactive::
557576
When set to `always`, always use colors for interactive prompts
558577
and displays (such as those used by "git-add --interactive").

Documentation/git-grep.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ SYNOPSIS
1717
[-l | --files-with-matches] [-L | --files-without-match]
1818
[-z | --null]
1919
[-c | --count] [--all-match]
20+
[--color | --no-color]
2021
[-A <post-context>] [-B <pre-context>] [-C <context>]
2122
[-f <file>] [-e] <pattern>
2223
[--and|--or|--not|(|)|-e <pattern>...] [<tree>...]
@@ -105,6 +106,13 @@ OPTIONS
105106
Instead of showing every matched line, show the number of
106107
lines that match.
107108

109+
--color::
110+
Show colored matches.
111+
112+
--no-color::
113+
Turn off match highlighting, even when the configuration file
114+
gives the default to color output.
115+
108116
-[ABC] <context>::
109117
Show `context` trailing (`A` -- after), or leading (`B`
110118
-- before), or both (`C` -- context) lines, and place a

builtin-grep.c

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,28 @@
2222

2323
static int builtin_grep;
2424

25+
static int grep_config(const char *var, const char *value, void *cb)
26+
{
27+
struct grep_opt *opt = cb;
28+
29+
if (!strcmp(var, "grep.color") || !strcmp(var, "color.grep")) {
30+
opt->color = git_config_colorbool(var, value, -1);
31+
return 0;
32+
}
33+
if (!strcmp(var, "grep.color.external") ||
34+
!strcmp(var, "color.grep.external")) {
35+
return git_config_string(&(opt->color_external), var, value);
36+
}
37+
if (!strcmp(var, "grep.color.match") ||
38+
!strcmp(var, "color.grep.match")) {
39+
if (!value)
40+
return config_error_nonbool(var);
41+
color_parse(value, var, opt->color_match);
42+
return 0;
43+
}
44+
return git_color_default_config(var, value, cb);
45+
}
46+
2547
/*
2648
* git grep pathspecs are somewhat different from diff-tree pathspecs;
2749
* pathname wildcards are allowed.
@@ -269,6 +291,21 @@ static int flush_grep(struct grep_opt *opt,
269291
return status;
270292
}
271293

294+
static void grep_add_color(struct strbuf *sb, const char *escape_seq)
295+
{
296+
size_t orig_len = sb->len;
297+
298+
while (*escape_seq) {
299+
if (*escape_seq == 'm')
300+
strbuf_addch(sb, ';');
301+
else if (*escape_seq != '\033' && *escape_seq != '[')
302+
strbuf_addch(sb, *escape_seq);
303+
escape_seq++;
304+
}
305+
if (sb->len > orig_len && sb->buf[sb->len - 1] == ';')
306+
strbuf_setlen(sb, sb->len - 1);
307+
}
308+
272309
static int external_grep(struct grep_opt *opt, const char **paths, int cached)
273310
{
274311
int i, nr, argc, hit, len, status;
@@ -339,6 +376,23 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
339376
push_arg("-e");
340377
push_arg(p->pattern);
341378
}
379+
if (opt->color) {
380+
struct strbuf sb = STRBUF_INIT;
381+
382+
grep_add_color(&sb, opt->color_match);
383+
setenv("GREP_COLOR", sb.buf, 1);
384+
385+
strbuf_reset(&sb);
386+
strbuf_addstr(&sb, "mt=");
387+
grep_add_color(&sb, opt->color_match);
388+
strbuf_addstr(&sb, ":sl=:cx=:fn=:ln=:bn=:se=");
389+
setenv("GREP_COLORS", sb.buf, 1);
390+
391+
strbuf_release(&sb);
392+
393+
if (opt->color_external && strlen(opt->color_external) > 0)
394+
push_arg(opt->color_external);
395+
}
342396

343397
hit = 0;
344398
argc = nr;
@@ -536,6 +590,12 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
536590
opt.pattern_tail = &opt.pattern_list;
537591
opt.regflags = REG_NEWLINE;
538592

593+
strcpy(opt.color_match, GIT_COLOR_RED GIT_COLOR_BOLD);
594+
opt.color = -1;
595+
git_config(grep_config, &opt);
596+
if (opt.color == -1)
597+
opt.color = git_use_color_default;
598+
539599
/*
540600
* If there is no -- then the paths must exist in the working
541601
* tree. If there is no explicit pattern specified with -e or
@@ -732,6 +792,14 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
732792
opt.relative = 0;
733793
continue;
734794
}
795+
if (!strcmp("--color", arg)) {
796+
opt.color = 1;
797+
continue;
798+
}
799+
if (!strcmp("--no-color", arg)) {
800+
opt.color = 0;
801+
continue;
802+
}
735803
if (!strcmp("--", arg)) {
736804
/* later processing wants to have this at argv[1] */
737805
argv--;
@@ -757,6 +825,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
757825
}
758826
}
759827

828+
if (opt.color && !opt.color_external)
829+
builtin_grep = 1;
760830
if (!opt.pattern_list)
761831
die("no pattern given.");
762832
if ((opt.regflags != REG_NEWLINE) && opt.fixed)

grep.c

Lines changed: 103 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
3939
{
4040
int err;
4141

42+
p->word_regexp = opt->word_regexp;
43+
4244
if (opt->fixed || is_fixed(p->pattern))
4345
p->fixed = 1;
4446
if (opt->regflags & REG_ICASE)
@@ -251,18 +253,6 @@ static int word_char(char ch)
251253
return isalnum(ch) || ch == '_';
252254
}
253255

254-
static void show_line(struct grep_opt *opt, const char *bol, const char *eol,
255-
const char *name, unsigned lno, char sign)
256-
{
257-
if (opt->null_following_name)
258-
sign = '\0';
259-
if (opt->pathname)
260-
printf("%s%c", name, sign);
261-
if (opt->linenum)
262-
printf("%d%c", lno, sign);
263-
printf("%.*s\n", (int)(eol-bol), bol);
264-
}
265-
266256
static void show_name(struct grep_opt *opt, const char *name)
267257
{
268258
printf("%s%c", name, opt->null_following_name ? '\0' : '\n');
@@ -306,11 +296,12 @@ static struct {
306296
{ "committer ", 10 },
307297
};
308298

309-
static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol, enum grep_context ctx)
299+
static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
300+
enum grep_context ctx,
301+
regmatch_t *pmatch, int eflags)
310302
{
311303
int hit = 0;
312304
int saved_ch = 0;
313-
regmatch_t pmatch[10];
314305

315306
if ((p->token != GREP_PATTERN) &&
316307
((p->token == GREP_PATTERN_HEAD) != (ctx == GREP_CONTEXT_HEAD)))
@@ -329,16 +320,12 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol
329320
}
330321

331322
again:
332-
if (!p->fixed) {
333-
regex_t *exp = &p->regexp;
334-
hit = !regexec(exp, bol, ARRAY_SIZE(pmatch),
335-
pmatch, 0);
336-
}
337-
else {
323+
if (p->fixed)
338324
hit = !fixmatch(p->pattern, bol, pmatch);
339-
}
325+
else
326+
hit = !regexec(&p->regexp, bol, 1, pmatch, eflags);
340327

341-
if (hit && opt->word_regexp) {
328+
if (hit && p->word_regexp) {
342329
if ((pmatch[0].rm_so < 0) ||
343330
(eol - bol) <= pmatch[0].rm_so ||
344331
(pmatch[0].rm_eo < 0) ||
@@ -378,39 +365,33 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol
378365
return hit;
379366
}
380367

381-
static int match_expr_eval(struct grep_opt *o,
382-
struct grep_expr *x,
383-
char *bol, char *eol,
384-
enum grep_context ctx,
385-
int collect_hits)
368+
static int match_expr_eval(struct grep_expr *x, char *bol, char *eol,
369+
enum grep_context ctx, int collect_hits)
386370
{
387371
int h = 0;
372+
regmatch_t match;
388373

389374
switch (x->node) {
390375
case GREP_NODE_ATOM:
391-
h = match_one_pattern(o, x->u.atom, bol, eol, ctx);
376+
h = match_one_pattern(x->u.atom, bol, eol, ctx, &match, 0);
392377
break;
393378
case GREP_NODE_NOT:
394-
h = !match_expr_eval(o, x->u.unary, bol, eol, ctx, 0);
379+
h = !match_expr_eval(x->u.unary, bol, eol, ctx, 0);
395380
break;
396381
case GREP_NODE_AND:
397-
if (!collect_hits)
398-
return (match_expr_eval(o, x->u.binary.left,
399-
bol, eol, ctx, 0) &&
400-
match_expr_eval(o, x->u.binary.right,
401-
bol, eol, ctx, 0));
402-
h = match_expr_eval(o, x->u.binary.left, bol, eol, ctx, 0);
403-
h &= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 0);
382+
if (!match_expr_eval(x->u.binary.left, bol, eol, ctx, 0))
383+
return 0;
384+
h = match_expr_eval(x->u.binary.right, bol, eol, ctx, 0);
404385
break;
405386
case GREP_NODE_OR:
406387
if (!collect_hits)
407-
return (match_expr_eval(o, x->u.binary.left,
388+
return (match_expr_eval(x->u.binary.left,
408389
bol, eol, ctx, 0) ||
409-
match_expr_eval(o, x->u.binary.right,
390+
match_expr_eval(x->u.binary.right,
410391
bol, eol, ctx, 0));
411-
h = match_expr_eval(o, x->u.binary.left, bol, eol, ctx, 0);
392+
h = match_expr_eval(x->u.binary.left, bol, eol, ctx, 0);
412393
x->u.binary.left->hit |= h;
413-
h |= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 1);
394+
h |= match_expr_eval(x->u.binary.right, bol, eol, ctx, 1);
414395
break;
415396
default:
416397
die("Unexpected node type (internal error) %d", x->node);
@@ -424,24 +405,104 @@ static int match_expr(struct grep_opt *opt, char *bol, char *eol,
424405
enum grep_context ctx, int collect_hits)
425406
{
426407
struct grep_expr *x = opt->pattern_expression;
427-
return match_expr_eval(opt, x, bol, eol, ctx, collect_hits);
408+
return match_expr_eval(x, bol, eol, ctx, collect_hits);
428409
}
429410

430411
static int match_line(struct grep_opt *opt, char *bol, char *eol,
431412
enum grep_context ctx, int collect_hits)
432413
{
433414
struct grep_pat *p;
415+
regmatch_t match;
416+
434417
if (opt->extended)
435418
return match_expr(opt, bol, eol, ctx, collect_hits);
436419

437420
/* we do not call with collect_hits without being extended */
438421
for (p = opt->pattern_list; p; p = p->next) {
439-
if (match_one_pattern(opt, p, bol, eol, ctx))
422+
if (match_one_pattern(p, bol, eol, ctx, &match, 0))
440423
return 1;
441424
}
442425
return 0;
443426
}
444427

428+
static int match_next_pattern(struct grep_pat *p, char *bol, char *eol,
429+
enum grep_context ctx,
430+
regmatch_t *pmatch, int eflags)
431+
{
432+
regmatch_t match;
433+
434+
if (!match_one_pattern(p, bol, eol, ctx, &match, eflags))
435+
return 0;
436+
if (match.rm_so < 0 || match.rm_eo < 0)
437+
return 0;
438+
if (pmatch->rm_so >= 0 && pmatch->rm_eo >= 0) {
439+
if (match.rm_so > pmatch->rm_so)
440+
return 1;
441+
if (match.rm_so == pmatch->rm_so && match.rm_eo < pmatch->rm_eo)
442+
return 1;
443+
}
444+
pmatch->rm_so = match.rm_so;
445+
pmatch->rm_eo = match.rm_eo;
446+
return 1;
447+
}
448+
449+
static int next_match(struct grep_opt *opt, char *bol, char *eol,
450+
enum grep_context ctx, regmatch_t *pmatch, int eflags)
451+
{
452+
struct grep_pat *p;
453+
int hit = 0;
454+
455+
pmatch->rm_so = pmatch->rm_eo = -1;
456+
if (bol < eol) {
457+
for (p = opt->pattern_list; p; p = p->next) {
458+
switch (p->token) {
459+
case GREP_PATTERN: /* atom */
460+
case GREP_PATTERN_HEAD:
461+
case GREP_PATTERN_BODY:
462+
hit |= match_next_pattern(p, bol, eol, ctx,
463+
pmatch, eflags);
464+
break;
465+
default:
466+
break;
467+
}
468+
}
469+
}
470+
return hit;
471+
}
472+
473+
static void show_line(struct grep_opt *opt, char *bol, char *eol,
474+
const char *name, unsigned lno, char sign)
475+
{
476+
int rest = eol - bol;
477+
478+
if (opt->null_following_name)
479+
sign = '\0';
480+
if (opt->pathname)
481+
printf("%s%c", name, sign);
482+
if (opt->linenum)
483+
printf("%d%c", lno, sign);
484+
if (opt->color) {
485+
regmatch_t match;
486+
enum grep_context ctx = GREP_CONTEXT_BODY;
487+
int ch = *eol;
488+
int eflags = 0;
489+
490+
*eol = '\0';
491+
while (next_match(opt, bol, eol, ctx, &match, eflags)) {
492+
printf("%.*s%s%.*s%s",
493+
(int)match.rm_so, bol,
494+
opt->color_match,
495+
(int)(match.rm_eo - match.rm_so), bol + match.rm_so,
496+
GIT_COLOR_RESET);
497+
bol += match.rm_eo;
498+
rest -= match.rm_eo;
499+
eflags = REG_NOTBOL;
500+
}
501+
*eol = ch;
502+
}
503+
printf("%.*s\n", rest, bol);
504+
}
505+
445506
static int grep_buffer_1(struct grep_opt *opt, const char *name,
446507
char *buf, unsigned long size, int collect_hits)
447508
{

0 commit comments

Comments
 (0)