Merge branch 'js/diff-color-words'

* js/diff-color-words:
  Change the spelling of "wordregex".
  color-words: Support diff.wordregex config option
  color-words: make regex configurable via attributes
  color-words: expand docs with precise semantics
  color-words: enable REG_NEWLINE to help user
  color-words: take an optional regular expression describing words
  color-words: change algorithm to allow for 0-character word boundaries
  color-words: refactor word splitting and use ALLOC_GROW()
  Add color_fwrite_lines(), a function coloring each line individually
This commit is contained in:
Junio C Hamano
2009-01-25 17:13:29 -08:00
10 changed files with 499 additions and 94 deletions

239
diff.c
View File

@ -23,6 +23,7 @@ static int diff_detect_rename_default;
static int diff_rename_limit_default = 200;
static int diff_suppress_blank_empty;
int diff_use_color_default = -1;
static const char *diff_word_regex_cfg;
static const char *external_diff_cmd_cfg;
int diff_auto_refresh_index = 1;
static int diff_mnemonic_prefix;
@ -92,6 +93,8 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
}
if (!strcmp(var, "diff.external"))
return git_config_string(&external_diff_cmd_cfg, var, value);
if (!strcmp(var, "diff.wordregex"))
return git_config_string(&diff_word_regex_cfg, var, value);
return git_diff_basic_config(var, value, cb);
}
@ -321,82 +324,138 @@ static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
struct diff_words_buffer {
mmfile_t text;
long alloc;
long current; /* output pointer */
int suppressed_newline;
struct diff_words_orig {
const char *begin, *end;
} *orig;
int orig_nr, orig_alloc;
};
static void diff_words_append(char *line, unsigned long len,
struct diff_words_buffer *buffer)
{
if (buffer->text.size + len > buffer->alloc) {
buffer->alloc = (buffer->text.size + len) * 3 / 2;
buffer->text.ptr = xrealloc(buffer->text.ptr, buffer->alloc);
}
ALLOC_GROW(buffer->text.ptr, buffer->text.size + len, buffer->alloc);
line++;
len--;
memcpy(buffer->text.ptr + buffer->text.size, line, len);
buffer->text.size += len;
buffer->text.ptr[buffer->text.size] = '\0';
}
struct diff_words_data {
struct diff_words_buffer minus, plus;
const char *current_plus;
FILE *file;
regex_t *word_regex;
};
static void print_word(FILE *file, struct diff_words_buffer *buffer, int len, int color,
int suppress_newline)
{
const char *ptr;
int eol = 0;
if (len == 0)
return;
ptr = buffer->text.ptr + buffer->current;
buffer->current += len;
if (ptr[len - 1] == '\n') {
eol = 1;
len--;
}
fputs(diff_get_color(1, color), file);
fwrite(ptr, len, 1, file);
fputs(diff_get_color(1, DIFF_RESET), file);
if (eol) {
if (suppress_newline)
buffer->suppressed_newline = 1;
else
putc('\n', file);
}
}
static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)
{
struct diff_words_data *diff_words = priv;
int minus_first, minus_len, plus_first, plus_len;
const char *minus_begin, *minus_end, *plus_begin, *plus_end;
if (diff_words->minus.suppressed_newline) {
if (line[0] != '+')
putc('\n', diff_words->file);
diff_words->minus.suppressed_newline = 0;
if (line[0] != '@' || parse_hunk_header(line, len,
&minus_first, &minus_len, &plus_first, &plus_len))
return;
/* POSIX requires that first be decremented by one if len == 0... */
if (minus_len) {
minus_begin = diff_words->minus.orig[minus_first].begin;
minus_end =
diff_words->minus.orig[minus_first + minus_len - 1].end;
} else
minus_begin = minus_end =
diff_words->minus.orig[minus_first].end;
if (plus_len) {
plus_begin = diff_words->plus.orig[plus_first].begin;
plus_end = diff_words->plus.orig[plus_first + plus_len - 1].end;
} else
plus_begin = plus_end = diff_words->plus.orig[plus_first].end;
if (diff_words->current_plus != plus_begin)
fwrite(diff_words->current_plus,
plus_begin - diff_words->current_plus, 1,
diff_words->file);
if (minus_begin != minus_end)
color_fwrite_lines(diff_words->file,
diff_get_color(1, DIFF_FILE_OLD),
minus_end - minus_begin, minus_begin);
if (plus_begin != plus_end)
color_fwrite_lines(diff_words->file,
diff_get_color(1, DIFF_FILE_NEW),
plus_end - plus_begin, plus_begin);
diff_words->current_plus = plus_end;
}
/* This function starts looking at *begin, and returns 0 iff a word was found. */
static int find_word_boundaries(mmfile_t *buffer, regex_t *word_regex,
int *begin, int *end)
{
if (word_regex && *begin < buffer->size) {
regmatch_t match[1];
if (!regexec(word_regex, buffer->ptr + *begin, 1, match, 0)) {
char *p = memchr(buffer->ptr + *begin + match[0].rm_so,
'\n', match[0].rm_eo - match[0].rm_so);
*end = p ? p - buffer->ptr : match[0].rm_eo + *begin;
*begin += match[0].rm_so;
return *begin >= *end;
}
return -1;
}
len--;
switch (line[0]) {
case '-':
print_word(diff_words->file,
&diff_words->minus, len, DIFF_FILE_OLD, 1);
break;
case '+':
print_word(diff_words->file,
&diff_words->plus, len, DIFF_FILE_NEW, 0);
break;
case ' ':
print_word(diff_words->file,
&diff_words->plus, len, DIFF_PLAIN, 0);
diff_words->minus.current += len;
break;
/* find the next word */
while (*begin < buffer->size && isspace(buffer->ptr[*begin]))
(*begin)++;
if (*begin >= buffer->size)
return -1;
/* find the end of the word */
*end = *begin + 1;
while (*end < buffer->size && !isspace(buffer->ptr[*end]))
(*end)++;
return 0;
}
/*
* This function splits the words in buffer->text, stores the list with
* newline separator into out, and saves the offsets of the original words
* in buffer->orig.
*/
static void diff_words_fill(struct diff_words_buffer *buffer, mmfile_t *out,
regex_t *word_regex)
{
int i, j;
long alloc = 0;
out->size = 0;
out->ptr = NULL;
/* fake an empty "0th" word */
ALLOC_GROW(buffer->orig, 1, buffer->orig_alloc);
buffer->orig[0].begin = buffer->orig[0].end = buffer->text.ptr;
buffer->orig_nr = 1;
for (i = 0; i < buffer->text.size; i++) {
if (find_word_boundaries(&buffer->text, word_regex, &i, &j))
return;
/* store original boundaries */
ALLOC_GROW(buffer->orig, buffer->orig_nr + 1,
buffer->orig_alloc);
buffer->orig[buffer->orig_nr].begin = buffer->text.ptr + i;
buffer->orig[buffer->orig_nr].end = buffer->text.ptr + j;
buffer->orig_nr++;
/* store one word */
ALLOC_GROW(out->ptr, out->size + j - i + 1, alloc);
memcpy(out->ptr + out->size, buffer->text.ptr + i, j - i);
out->ptr[out->size + j - i] = '\n';
out->size += j - i + 1;
i = j - 1;
}
}
@ -407,38 +466,36 @@ static void diff_words_show(struct diff_words_data *diff_words)
xdemitconf_t xecfg;
xdemitcb_t ecb;
mmfile_t minus, plus;
int i;
/* special case: only removal */
if (!diff_words->plus.text.size) {
color_fwrite_lines(diff_words->file,
diff_get_color(1, DIFF_FILE_OLD),
diff_words->minus.text.size, diff_words->minus.text.ptr);
diff_words->minus.text.size = 0;
return;
}
diff_words->current_plus = diff_words->plus.text.ptr;
memset(&xpp, 0, sizeof(xpp));
memset(&xecfg, 0, sizeof(xecfg));
minus.size = diff_words->minus.text.size;
minus.ptr = xmalloc(minus.size);
memcpy(minus.ptr, diff_words->minus.text.ptr, minus.size);
for (i = 0; i < minus.size; i++)
if (isspace(minus.ptr[i]))
minus.ptr[i] = '\n';
diff_words->minus.current = 0;
plus.size = diff_words->plus.text.size;
plus.ptr = xmalloc(plus.size);
memcpy(plus.ptr, diff_words->plus.text.ptr, plus.size);
for (i = 0; i < plus.size; i++)
if (isspace(plus.ptr[i]))
plus.ptr[i] = '\n';
diff_words->plus.current = 0;
diff_words_fill(&diff_words->minus, &minus, diff_words->word_regex);
diff_words_fill(&diff_words->plus, &plus, diff_words->word_regex);
xpp.flags = XDF_NEED_MINIMAL;
xecfg.ctxlen = diff_words->minus.alloc + diff_words->plus.alloc;
/* as only the hunk header will be parsed, we need a 0-context */
xecfg.ctxlen = 0;
xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words,
&xpp, &xecfg, &ecb);
free(minus.ptr);
free(plus.ptr);
if (diff_words->current_plus != diff_words->plus.text.ptr +
diff_words->plus.text.size)
fwrite(diff_words->current_plus,
diff_words->plus.text.ptr + diff_words->plus.text.size
- diff_words->current_plus, 1,
diff_words->file);
diff_words->minus.text.size = diff_words->plus.text.size = 0;
if (diff_words->minus.suppressed_newline) {
putc('\n', diff_words->file);
diff_words->minus.suppressed_newline = 0;
}
}
typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
@ -462,7 +519,10 @@ static void free_diff_words_data(struct emit_callback *ecbdata)
diff_words_show(ecbdata->diff_words);
free (ecbdata->diff_words->minus.text.ptr);
free (ecbdata->diff_words->minus.orig);
free (ecbdata->diff_words->plus.text.ptr);
free (ecbdata->diff_words->plus.orig);
free(ecbdata->diff_words->word_regex);
free(ecbdata->diff_words);
ecbdata->diff_words = NULL;
}
@ -1325,6 +1385,12 @@ static const struct userdiff_funcname *diff_funcname_pattern(struct diff_filespe
return one->driver->funcname.pattern ? &one->driver->funcname : NULL;
}
static const char *userdiff_word_regex(struct diff_filespec *one)
{
diff_filespec_load_driver(one);
return one->driver->word_regex;
}
void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b)
{
if (!options->a_prefix)
@ -1485,6 +1551,21 @@ static void builtin_diff(const char *name_a,
ecbdata.diff_words =
xcalloc(1, sizeof(struct diff_words_data));
ecbdata.diff_words->file = o->file;
if (!o->word_regex)
o->word_regex = userdiff_word_regex(one);
if (!o->word_regex)
o->word_regex = userdiff_word_regex(two);
if (!o->word_regex)
o->word_regex = diff_word_regex_cfg;
if (o->word_regex) {
ecbdata.diff_words->word_regex = (regex_t *)
xmalloc(sizeof(regex_t));
if (regcomp(ecbdata.diff_words->word_regex,
o->word_regex,
REG_EXTENDED | REG_NEWLINE))
die ("Invalid regular expression: %s",
o->word_regex);
}
}
xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
&xpp, &xecfg, &ecb);
@ -2498,6 +2579,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
DIFF_OPT_CLR(options, COLOR_DIFF);
else if (!strcmp(arg, "--color-words"))
options->flags |= DIFF_OPT_COLOR_DIFF | DIFF_OPT_COLOR_DIFF_WORDS;
else if (!prefixcmp(arg, "--color-words=")) {
options->flags |= DIFF_OPT_COLOR_DIFF | DIFF_OPT_COLOR_DIFF_WORDS;
options->word_regex = arg + 14;
}
else if (!strcmp(arg, "--exit-code"))
DIFF_OPT_SET(options, EXIT_WITH_STATUS);
else if (!strcmp(arg, "--quiet"))