Skip to content

Commit

Permalink
Merge branch 'tb/grep-column'
Browse files Browse the repository at this point in the history
"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
  • Loading branch information
gitster committed Jul 18, 2018
2 parents eb90563 + 240cf2a commit d036d66
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 34 deletions.
7 changes: 6 additions & 1 deletion Documentation/config.txt
Expand Up @@ -1182,8 +1182,10 @@ color.grep.<slot>::
filename prefix (when not using `-h`)
`function`;;
function name lines (when using `-p`)
`linenumber`;;
`lineNumber`;;
line number prefix (when using `-n`)
`column`;;
column number prefix (when using `--column`)
`match`;;
matching text (same as setting `matchContext` and `matchSelected`)
`matchContext`;;
Expand Down Expand Up @@ -1798,6 +1800,9 @@ gitweb.snapshot::
grep.lineNumber::
If set to true, enable `-n` option by default.

grep.column::
If set to true, enable the `--column` option by default.

grep.patternType::
Set the default matching behavior. Using a value of 'basic', 'extended',
'fixed', or 'perl' will enable the `--basic-regexp`, `--extended-regexp`,
Expand Down
9 changes: 8 additions & 1 deletion Documentation/git-grep.txt
Expand Up @@ -13,7 +13,7 @@ SYNOPSIS
[-v | --invert-match] [-h|-H] [--full-name]
[-E | --extended-regexp] [-G | --basic-regexp]
[-P | --perl-regexp]
[-F | --fixed-strings] [-n | --line-number]
[-F | --fixed-strings] [-n | --line-number] [--column]
[-l | --files-with-matches] [-L | --files-without-match]
[(-O | --open-files-in-pager) [<pager>]]
[-z | --null]
Expand Down Expand Up @@ -44,6 +44,9 @@ CONFIGURATION
grep.lineNumber::
If set to true, enable `-n` option by default.

grep.column::
If set to true, enable the `--column` option by default.

grep.patternType::
Set the default matching behavior. Using a value of 'basic', 'extended',
'fixed', or 'perl' will enable the `--basic-regexp`, `--extended-regexp`,
Expand Down Expand Up @@ -169,6 +172,10 @@ providing this option will cause it to die.
--line-number::
Prefix the line number to matching lines.

--column::
Prefix the 1-indexed byte-offset of the first match from the start of the
matching line.

-l::
--files-with-matches::
--name-only::
Expand Down
1 change: 1 addition & 0 deletions builtin/grep.c
Expand Up @@ -828,6 +828,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
GREP_PATTERN_TYPE_PCRE),
OPT_GROUP(""),
OPT_BOOL('n', "line-number", &opt.linenum, N_("show line numbers")),
OPT_BOOL(0, "column", &opt.columnnum, N_("show column number of first match")),
OPT_NEGBIT('h', NULL, &opt.pathname, N_("don't show filenames"), 1),
OPT_BIT('H', NULL, &opt.pathname, N_("show filenames"), 1),
OPT_NEGBIT(0, "full-name", &opt.relative,
Expand Down
12 changes: 10 additions & 2 deletions contrib/git-jump/README
Expand Up @@ -25,6 +25,13 @@ git-jump will feed this to the editor:
foo.c:2: printf("hello word!\n");
-----------------------------------

Or, when running 'git jump grep', column numbers will also be emitted,
e.g. `git jump grep "hello"` would return:

-----------------------------------
foo.c:2:9: printf("hello word!\n");
-----------------------------------

Obviously this trivial case isn't that interesting; you could just open
`foo.c` yourself. But when you have many changes scattered across a
project, you can use the editor's support to "jump" from point to point.
Expand All @@ -35,7 +42,8 @@ Git-jump can generate four types of interesting lists:

2. The beginning of any merge conflict markers.

3. Any grep matches.
3. Any grep matches, including the column of the first match on a
line.

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

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


Limitations
Expand Down
2 changes: 1 addition & 1 deletion contrib/git-jump/git-jump
Expand Up @@ -52,7 +52,7 @@ mode_merge() {
# editor shows them to us in the status bar.
mode_grep() {
cmd=$(git config jump.grepCmd)
test -n "$cmd" || cmd="git grep -n"
test -n "$cmd" || cmd="git grep -n --column"
$cmd "$@" |
perl -pe '
s/[ \t]+/ /g;
Expand Down
132 changes: 103 additions & 29 deletions grep.c
Expand Up @@ -20,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",
Expand Down Expand Up @@ -59,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], "");
Expand Down Expand Up @@ -110,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);
Expand Down Expand Up @@ -157,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;
Expand Down Expand Up @@ -1244,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");
Expand All @@ -1257,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);
Expand All @@ -1286,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,
Expand Down Expand Up @@ -1355,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;
Expand Down Expand Up @@ -1390,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;
Expand Down Expand Up @@ -1495,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;
}
}
Expand Down Expand Up @@ -1560,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++;
}
Expand Down Expand Up @@ -1759,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
Expand All @@ -1782,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)
Expand Down Expand Up @@ -1823,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;
Expand Down Expand Up @@ -1852,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:
Expand Down
2 changes: 2 additions & 0 deletions grep.h
Expand Up @@ -67,6 +67,7 @@ enum grep_color {
GREP_COLOR_FILENAME,
GREP_COLOR_FUNCTION,
GREP_COLOR_LINENO,
GREP_COLOR_COLUMNNO,
GREP_COLOR_MATCH_CONTEXT,
GREP_COLOR_MATCH_SELECTED,
GREP_COLOR_SELECTED,
Expand Down Expand Up @@ -139,6 +140,7 @@ struct grep_opt {
int prefix_length;
regex_t regexp;
int linenum;
int columnnum;
int invert;
int ignore_case;
int status_only;
Expand Down

0 comments on commit d036d66

Please sign in to comment.