Skip to content

Commit d036d66

Browse files
committed
Merge branch 'tb/grep-column'
"git grep" learned the "--column" option that gives not just the line number but the column number of the hit. * tb/grep-column: contrib/git-jump/git-jump: jump to exact location grep.c: add configuration variables to show matched option builtin/grep.c: add '--column' option to 'git-grep(1)' grep.c: display column number of first match grep.[ch]: extend grep_opt to allow showing matched column grep.c: expose {,inverted} match column in match_line() Documentation/config.txt: camel-case lineNumber for consistency
2 parents eb90563 + 240cf2a commit d036d66

File tree

8 files changed

+226
-34
lines changed

8 files changed

+226
-34
lines changed

Documentation/config.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1182,8 +1182,10 @@ color.grep.<slot>::
11821182
filename prefix (when not using `-h`)
11831183
`function`;;
11841184
function name lines (when using `-p`)
1185-
`linenumber`;;
1185+
`lineNumber`;;
11861186
line number prefix (when using `-n`)
1187+
`column`;;
1188+
column number prefix (when using `--column`)
11871189
`match`;;
11881190
matching text (same as setting `matchContext` and `matchSelected`)
11891191
`matchContext`;;
@@ -1798,6 +1800,9 @@ gitweb.snapshot::
17981800
grep.lineNumber::
17991801
If set to true, enable `-n` option by default.
18001802

1803+
grep.column::
1804+
If set to true, enable the `--column` option by default.
1805+
18011806
grep.patternType::
18021807
Set the default matching behavior. Using a value of 'basic', 'extended',
18031808
'fixed', or 'perl' will enable the `--basic-regexp`, `--extended-regexp`,

Documentation/git-grep.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ SYNOPSIS
1313
[-v | --invert-match] [-h|-H] [--full-name]
1414
[-E | --extended-regexp] [-G | --basic-regexp]
1515
[-P | --perl-regexp]
16-
[-F | --fixed-strings] [-n | --line-number]
16+
[-F | --fixed-strings] [-n | --line-number] [--column]
1717
[-l | --files-with-matches] [-L | --files-without-match]
1818
[(-O | --open-files-in-pager) [<pager>]]
1919
[-z | --null]
@@ -44,6 +44,9 @@ CONFIGURATION
4444
grep.lineNumber::
4545
If set to true, enable `-n` option by default.
4646

47+
grep.column::
48+
If set to true, enable the `--column` option by default.
49+
4750
grep.patternType::
4851
Set the default matching behavior. Using a value of 'basic', 'extended',
4952
'fixed', or 'perl' will enable the `--basic-regexp`, `--extended-regexp`,
@@ -169,6 +172,10 @@ providing this option will cause it to die.
169172
--line-number::
170173
Prefix the line number to matching lines.
171174

175+
--column::
176+
Prefix the 1-indexed byte-offset of the first match from the start of the
177+
matching line.
178+
172179
-l::
173180
--files-with-matches::
174181
--name-only::

builtin/grep.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
828828
GREP_PATTERN_TYPE_PCRE),
829829
OPT_GROUP(""),
830830
OPT_BOOL('n', "line-number", &opt.linenum, N_("show line numbers")),
831+
OPT_BOOL(0, "column", &opt.columnnum, N_("show column number of first match")),
831832
OPT_NEGBIT('h', NULL, &opt.pathname, N_("don't show filenames"), 1),
832833
OPT_BIT('H', NULL, &opt.pathname, N_("show filenames"), 1),
833834
OPT_NEGBIT(0, "full-name", &opt.relative,

contrib/git-jump/README

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ git-jump will feed this to the editor:
2525
foo.c:2: printf("hello word!\n");
2626
-----------------------------------
2727

28+
Or, when running 'git jump grep', column numbers will also be emitted,
29+
e.g. `git jump grep "hello"` would return:
30+
31+
-----------------------------------
32+
foo.c:2:9: printf("hello word!\n");
33+
-----------------------------------
34+
2835
Obviously this trivial case isn't that interesting; you could just open
2936
`foo.c` yourself. But when you have many changes scattered across a
3037
project, you can use the editor's support to "jump" from point to point.
@@ -35,7 +42,8 @@ Git-jump can generate four types of interesting lists:
3542

3643
2. The beginning of any merge conflict markers.
3744

38-
3. Any grep matches.
45+
3. Any grep matches, including the column of the first match on a
46+
line.
3947

4048
4. Any whitespace errors detected by `git diff --check`.
4149

@@ -82,7 +90,7 @@ which does something similar to `git jump grep`. However, it is limited
8290
to positioning the cursor to the correct line in only the first file,
8391
leaving you to locate subsequent hits in that file or other files using
8492
the editor or pager. By contrast, git-jump provides the editor with a
85-
complete list of files and line numbers for each match.
93+
complete list of files, lines, and a column number for each match.
8694

8795

8896
Limitations

contrib/git-jump/git-jump

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ mode_merge() {
5252
# editor shows them to us in the status bar.
5353
mode_grep() {
5454
cmd=$(git config jump.grepCmd)
55-
test -n "$cmd" || cmd="git grep -n"
55+
test -n "$cmd" || cmd="git grep -n --column"
5656
$cmd "$@" |
5757
perl -pe '
5858
s/[ \t]+/ /g;

grep.c

Lines changed: 103 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ static const char *color_grep_slots[] = {
2020
[GREP_COLOR_FILENAME] = "filename",
2121
[GREP_COLOR_FUNCTION] = "function",
2222
[GREP_COLOR_LINENO] = "lineNumber",
23+
[GREP_COLOR_COLUMNNO] = "column",
2324
[GREP_COLOR_MATCH_CONTEXT] = "matchContext",
2425
[GREP_COLOR_MATCH_SELECTED] = "matchSelected",
2526
[GREP_COLOR_SELECTED] = "selected",
@@ -59,6 +60,7 @@ void init_grep_defaults(void)
5960
color_set(opt->colors[GREP_COLOR_FILENAME], "");
6061
color_set(opt->colors[GREP_COLOR_FUNCTION], "");
6162
color_set(opt->colors[GREP_COLOR_LINENO], "");
63+
color_set(opt->colors[GREP_COLOR_COLUMNNO], "");
6264
color_set(opt->colors[GREP_COLOR_MATCH_CONTEXT], GIT_COLOR_BOLD_RED);
6365
color_set(opt->colors[GREP_COLOR_MATCH_SELECTED], GIT_COLOR_BOLD_RED);
6466
color_set(opt->colors[GREP_COLOR_SELECTED], "");
@@ -110,6 +112,10 @@ int grep_config(const char *var, const char *value, void *cb)
110112
opt->linenum = git_config_bool(var, value);
111113
return 0;
112114
}
115+
if (!strcmp(var, "grep.column")) {
116+
opt->columnnum = git_config_bool(var, value);
117+
return 0;
118+
}
113119

114120
if (!strcmp(var, "grep.fullname")) {
115121
opt->relative = !git_config_bool(var, value);
@@ -157,6 +163,7 @@ void grep_init(struct grep_opt *opt, const char *prefix)
157163
opt->extended_regexp_option = def->extended_regexp_option;
158164
opt->pattern_type_option = def->pattern_type_option;
159165
opt->linenum = def->linenum;
166+
opt->columnnum = def->columnnum;
160167
opt->max_depth = def->max_depth;
161168
opt->pathname = def->pathname;
162169
opt->relative = def->relative;
@@ -1244,11 +1251,11 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
12441251
return hit;
12451252
}
12461253

1247-
static int match_expr_eval(struct grep_expr *x, char *bol, char *eol,
1248-
enum grep_context ctx, int collect_hits)
1254+
static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x, char *bol,
1255+
char *eol, enum grep_context ctx, ssize_t *col,
1256+
ssize_t *icol, int collect_hits)
12491257
{
12501258
int h = 0;
1251-
regmatch_t match;
12521259

12531260
if (!x)
12541261
die("Not a valid grep expression");
@@ -1257,25 +1264,52 @@ static int match_expr_eval(struct grep_expr *x, char *bol, char *eol,
12571264
h = 1;
12581265
break;
12591266
case GREP_NODE_ATOM:
1260-
h = match_one_pattern(x->u.atom, bol, eol, ctx, &match, 0);
1267+
{
1268+
regmatch_t tmp;
1269+
h = match_one_pattern(x->u.atom, bol, eol, ctx,
1270+
&tmp, 0);
1271+
if (h && (*col < 0 || tmp.rm_so < *col))
1272+
*col = tmp.rm_so;
1273+
}
12611274
break;
12621275
case GREP_NODE_NOT:
1263-
h = !match_expr_eval(x->u.unary, bol, eol, ctx, 0);
1276+
/*
1277+
* Upon visiting a GREP_NODE_NOT, col and icol become swapped.
1278+
*/
1279+
h = !match_expr_eval(opt, x->u.unary, bol, eol, ctx, icol, col,
1280+
0);
12641281
break;
12651282
case GREP_NODE_AND:
1266-
if (!match_expr_eval(x->u.binary.left, bol, eol, ctx, 0))
1267-
return 0;
1268-
h = match_expr_eval(x->u.binary.right, bol, eol, ctx, 0);
1283+
h = match_expr_eval(opt, x->u.binary.left, bol, eol, ctx, col,
1284+
icol, 0);
1285+
if (h || opt->columnnum) {
1286+
/*
1287+
* Don't short-circuit AND when given --column, since a
1288+
* NOT earlier in the tree may turn this into an OR. In
1289+
* this case, see the below comment.
1290+
*/
1291+
h &= match_expr_eval(opt, x->u.binary.right, bol, eol,
1292+
ctx, col, icol, 0);
1293+
}
12691294
break;
12701295
case GREP_NODE_OR:
1271-
if (!collect_hits)
1272-
return (match_expr_eval(x->u.binary.left,
1273-
bol, eol, ctx, 0) ||
1274-
match_expr_eval(x->u.binary.right,
1275-
bol, eol, ctx, 0));
1276-
h = match_expr_eval(x->u.binary.left, bol, eol, ctx, 0);
1277-
x->u.binary.left->hit |= h;
1278-
h |= match_expr_eval(x->u.binary.right, bol, eol, ctx, 1);
1296+
if (!(collect_hits || opt->columnnum)) {
1297+
/*
1298+
* Don't short-circuit OR when given --column (or
1299+
* collecting hits) to ensure we don't skip a later
1300+
* child that would produce an earlier match.
1301+
*/
1302+
return (match_expr_eval(opt, x->u.binary.left, bol, eol,
1303+
ctx, col, icol, 0) ||
1304+
match_expr_eval(opt, x->u.binary.right, bol,
1305+
eol, ctx, col, icol, 0));
1306+
}
1307+
h = match_expr_eval(opt, x->u.binary.left, bol, eol, ctx, col,
1308+
icol, 0);
1309+
if (collect_hits)
1310+
x->u.binary.left->hit |= h;
1311+
h |= match_expr_eval(opt, x->u.binary.right, bol, eol, ctx, col,
1312+
icol, collect_hits);
12791313
break;
12801314
default:
12811315
die("Unexpected node type (internal error) %d", x->node);
@@ -1286,27 +1320,43 @@ static int match_expr_eval(struct grep_expr *x, char *bol, char *eol,
12861320
}
12871321

12881322
static int match_expr(struct grep_opt *opt, char *bol, char *eol,
1289-
enum grep_context ctx, int collect_hits)
1323+
enum grep_context ctx, ssize_t *col,
1324+
ssize_t *icol, int collect_hits)
12901325
{
12911326
struct grep_expr *x = opt->pattern_expression;
1292-
return match_expr_eval(x, bol, eol, ctx, collect_hits);
1327+
return match_expr_eval(opt, x, bol, eol, ctx, col, icol, collect_hits);
12931328
}
12941329

12951330
static int match_line(struct grep_opt *opt, char *bol, char *eol,
1331+
ssize_t *col, ssize_t *icol,
12961332
enum grep_context ctx, int collect_hits)
12971333
{
12981334
struct grep_pat *p;
1299-
regmatch_t match;
1335+
int hit = 0;
13001336

13011337
if (opt->extended)
1302-
return match_expr(opt, bol, eol, ctx, collect_hits);
1338+
return match_expr(opt, bol, eol, ctx, col, icol,
1339+
collect_hits);
13031340

13041341
/* we do not call with collect_hits without being extended */
13051342
for (p = opt->pattern_list; p; p = p->next) {
1306-
if (match_one_pattern(p, bol, eol, ctx, &match, 0))
1307-
return 1;
1343+
regmatch_t tmp;
1344+
if (match_one_pattern(p, bol, eol, ctx, &tmp, 0)) {
1345+
hit |= 1;
1346+
if (!opt->columnnum) {
1347+
/*
1348+
* Without --column, any single match on a line
1349+
* is enough to know that it needs to be
1350+
* printed. With --column, scan _all_ patterns
1351+
* to find the earliest.
1352+
*/
1353+
break;
1354+
}
1355+
if (*col < 0 || tmp.rm_so < *col)
1356+
*col = tmp.rm_so;
1357+
}
13081358
}
1309-
return 0;
1359+
return hit;
13101360
}
13111361

13121362
static int match_next_pattern(struct grep_pat *p, char *bol, char *eol,
@@ -1355,7 +1405,7 @@ static int next_match(struct grep_opt *opt, char *bol, char *eol,
13551405
}
13561406

13571407
static void show_line(struct grep_opt *opt, char *bol, char *eol,
1358-
const char *name, unsigned lno, char sign)
1408+
const char *name, unsigned lno, ssize_t cno, char sign)
13591409
{
13601410
int rest = eol - bol;
13611411
const char *match_color, *line_color = NULL;
@@ -1390,6 +1440,17 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
13901440
output_color(opt, buf, strlen(buf), opt->colors[GREP_COLOR_LINENO]);
13911441
output_sep(opt, sign);
13921442
}
1443+
/*
1444+
* Treat 'cno' as the 1-indexed offset from the start of a non-context
1445+
* line to its first match. Otherwise, 'cno' is 0 indicating that we are
1446+
* being called with a context line.
1447+
*/
1448+
if (opt->columnnum && cno) {
1449+
char buf[32];
1450+
xsnprintf(buf, sizeof(buf), "%"PRIuMAX, (uintmax_t)cno);
1451+
output_color(opt, buf, strlen(buf), opt->colors[GREP_COLOR_COLUMNNO]);
1452+
output_sep(opt, sign);
1453+
}
13931454
if (opt->color) {
13941455
regmatch_t match;
13951456
enum grep_context ctx = GREP_CONTEXT_BODY;
@@ -1495,7 +1556,7 @@ static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs,
14951556
break;
14961557

14971558
if (match_funcname(opt, gs, bol, eol)) {
1498-
show_line(opt, bol, eol, gs->name, lno, '=');
1559+
show_line(opt, bol, eol, gs->name, lno, 0, '=');
14991560
break;
15001561
}
15011562
}
@@ -1560,7 +1621,7 @@ static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
15601621

15611622
while (*eol != '\n')
15621623
eol++;
1563-
show_line(opt, bol, eol, gs->name, cur, sign);
1624+
show_line(opt, bol, eol, gs->name, cur, 0, sign);
15641625
bol = eol + 1;
15651626
cur++;
15661627
}
@@ -1759,6 +1820,8 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
17591820
while (left) {
17601821
char *eol, ch;
17611822
int hit;
1823+
ssize_t cno;
1824+
ssize_t col = -1, icol = -1;
17621825

17631826
/*
17641827
* look_ahead() skips quickly to the line that possibly
@@ -1782,7 +1845,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
17821845
if ((ctx == GREP_CONTEXT_HEAD) && (eol == bol))
17831846
ctx = GREP_CONTEXT_BODY;
17841847

1785-
hit = match_line(opt, bol, eol, ctx, collect_hits);
1848+
hit = match_line(opt, bol, eol, &col, &icol, ctx, collect_hits);
17861849
*eol = ch;
17871850

17881851
if (collect_hits)
@@ -1823,7 +1886,18 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
18231886
show_pre_context(opt, gs, bol, eol, lno);
18241887
else if (opt->funcname)
18251888
show_funcname_line(opt, gs, bol, lno);
1826-
show_line(opt, bol, eol, gs->name, lno, ':');
1889+
cno = opt->invert ? icol : col;
1890+
if (cno < 0) {
1891+
/*
1892+
* A negative cno indicates that there was no
1893+
* match on the line. We are thus inverted and
1894+
* being asked to show all lines that _don't_
1895+
* match a given expression. Therefore, set cno
1896+
* to 0 to suggest the whole line matches.
1897+
*/
1898+
cno = 0;
1899+
}
1900+
show_line(opt, bol, eol, gs->name, lno, cno + 1, ':');
18271901
last_hit = lno;
18281902
if (opt->funcbody)
18291903
show_function = 1;
@@ -1852,7 +1926,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
18521926
/* If the last hit is within the post context,
18531927
* we need to show this line.
18541928
*/
1855-
show_line(opt, bol, eol, gs->name, lno, '-');
1929+
show_line(opt, bol, eol, gs->name, lno, col + 1, '-');
18561930
}
18571931

18581932
next_line:

grep.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ enum grep_color {
6767
GREP_COLOR_FILENAME,
6868
GREP_COLOR_FUNCTION,
6969
GREP_COLOR_LINENO,
70+
GREP_COLOR_COLUMNNO,
7071
GREP_COLOR_MATCH_CONTEXT,
7172
GREP_COLOR_MATCH_SELECTED,
7273
GREP_COLOR_SELECTED,
@@ -139,6 +140,7 @@ struct grep_opt {
139140
int prefix_length;
140141
regex_t regexp;
141142
int linenum;
143+
int columnnum;
142144
int invert;
143145
int ignore_case;
144146
int status_only;

0 commit comments

Comments
 (0)