{"thread":{"id":"41103","subject":"[PATCH 1/7] diff.c: keep all word diff structs together","startedAt":"2015-12-31T12:37:30Z","lastAt":"2015-12-31T12:37:37Z","messageCount":8,"participants":["Nguyễn Thái Ngọc Duy"],"isPatch":true,"patchVersion":1,"patchTotal":7},"messages":[{"id":"275238","messageId":"1451565457-18756-1-git-send-email-pclouds@gmail.com","threadId":"41103","inReplyTo":null,"subject":"A failed attempt to integrate diff-highlight to the core","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2015-12-31T12:37:30Z","receivedAt":"2015-12-31T12:37:30Z","isPatch":false,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is mostly for the record. The patches are not as important as the\nresult in this mail. But if you want to try out, you can.\n\nIf you don't know, the script diff-highlight in contrib can highlight\nchanges between two nearly identical lines, pointing out what\ncharacters are different. It can improve readability a lot in certain\ncases (e.g. when you wrap _() around strings).\n\nI approached it differently. I took --color-words and reformatted its\noutput in unified diff format. So you'll see +/- lines like a normal\ndiff, but deleted and added words are also highlighted.\n\nThe idea seems to fit well into diff-words machinery. But the output\ndoes not, especially in cases where there are many deleted/added words\nin a chunk. It could look.. patchy, like somebody throwing colors on a\nwall (especially when you do --highlight-words=.). You can try out and\nsee.  git-log works so you can see lots of diff.\n\nI don't know, maybe the idea can be improved to have something closer\nto diff-highlight. I suppose we can select similar lines and diff\nthem, instead of diff the entire chunk in one go. I might revisit it\nat some point in future, but for now, it's a failure.\n\nNguyễn Thái Ngọc Duy (7):\n  diff.c: keep all word diff structs together\n  diff.c: refactor diff_words_append()\n  diff --color-words: another special diff case\n  diff.c: refactor fn_out_diff_words_write_helper()\n  diff: unified diff with colored words, step 1, unified diff only\n  diff.c: add new arguments to emit_line_0()\n  diff --highlight-words: actually highlight words\n\n diff.c | 400 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------\n diff.h |   3 +-\n 2 files changed, 363 insertions(+), 40 deletions(-)\n\n-- \n2.3.0.rc1.137.g477eb31\n"},{"id":"275237","messageId":"1451565457-18756-2-git-send-email-pclouds@gmail.com","threadId":"41103","inReplyTo":"1451565457-18756-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 1/7] diff.c: keep all word diff structs together","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2015-12-31T12:37:31Z","receivedAt":"2015-12-31T12:37:31Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n diff.c | 22 +++++++++++-----------\n 1 file changed, 11 insertions(+), 11 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 80eb0c2..dfbed41 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -759,17 +759,6 @@ struct diff_words_buffer {\n \tint orig_nr, orig_alloc;\n };\n \n-static void diff_words_append(char *line, unsigned long len,\n-\t\tstruct diff_words_buffer *buffer)\n-{\n-\tALLOC_GROW(buffer->text.ptr, buffer->text.size + len, buffer->alloc);\n-\tline++;\n-\tlen--;\n-\tmemcpy(buffer->text.ptr + buffer->text.size, line, len);\n-\tbuffer->text.size += len;\n-\tbuffer->text.ptr[buffer->text.size] = '\\0';\n-}\n-\n struct diff_words_style_elem {\n \tconst char *prefix;\n \tconst char *suffix;\n@@ -799,6 +788,17 @@ struct diff_words_data {\n \tstruct diff_words_style *style;\n };\n \n+static void diff_words_append(char *line, unsigned long len,\n+\t\tstruct diff_words_buffer *buffer)\n+{\n+\tALLOC_GROW(buffer->text.ptr, buffer->text.size + len, buffer->alloc);\n+\tline++;\n+\tlen--;\n+\tmemcpy(buffer->text.ptr + buffer->text.size, line, len);\n+\tbuffer->text.size += len;\n+\tbuffer->text.ptr[buffer->text.size] = '\\0';\n+}\n+\n static int fn_out_diff_words_write_helper(FILE *fp,\n \t\t\t\t\t  struct diff_words_style_elem *st_el,\n \t\t\t\t\t  const char *newline,\n-- \n2.3.0.rc1.137.g477eb31\n"},{"id":"275239","messageId":"1451565457-18756-3-git-send-email-pclouds@gmail.com","threadId":"41103","inReplyTo":"1451565457-18756-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 2/7] diff.c: refactor diff_words_append()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2015-12-31T12:37:32Z","receivedAt":"2015-12-31T12:37:32Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n diff.c | 21 ++++++++++++---------\n 1 file changed, 12 insertions(+), 9 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex dfbed41..8af1df1 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -788,9 +788,17 @@ struct diff_words_data {\n \tstruct diff_words_style *style;\n };\n \n-static void diff_words_append(char *line, unsigned long len,\n-\t\tstruct diff_words_buffer *buffer)\n+static void diff_words_append(struct diff_words_data *diff_words,\n+\t\t\t      char *line, unsigned long len)\n {\n+\tstruct diff_words_buffer *buffer;\n+\n+\tif (line[0] == '-')\n+\t\tbuffer = &diff_words->minus;\n+\telse {\n+\t\tassert(line[0] == '+');\n+\t\tbuffer = &diff_words->plus;\n+\t}\n \tALLOC_GROW(buffer->text.ptr, buffer->text.size + len, buffer->alloc);\n \tline++;\n \tlen--;\n@@ -1252,13 +1260,8 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)\n \t}\n \n \tif (ecbdata->diff_words) {\n-\t\tif (line[0] == '-') {\n-\t\t\tdiff_words_append(line, len,\n-\t\t\t\t\t  &ecbdata->diff_words->minus);\n-\t\t\treturn;\n-\t\t} else if (line[0] == '+') {\n-\t\t\tdiff_words_append(line, len,\n-\t\t\t\t\t  &ecbdata->diff_words->plus);\n+\t\tif (line[0] == '-' || line[0] == '+') {\n+\t\t\tdiff_words_append(ecbdata->diff_words, line, len);\n \t\t\treturn;\n \t\t} else if (starts_with(line, \"\\\\ \")) {\n \t\t\t/*\n-- \n2.3.0.rc1.137.g477eb31\n"},{"id":"275240","messageId":"1451565457-18756-4-git-send-email-pclouds@gmail.com","threadId":"41103","inReplyTo":"1451565457-18756-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 3/7] diff --color-words: another special diff case","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2015-12-31T12:37:33Z","receivedAt":"2015-12-31T12:37:33Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n diff.c | 10 ++++++++++\n 1 file changed, 10 insertions(+)\n\ndiff --git a/diff.c b/diff.c\nindex 8af1df1..1354368 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -1037,6 +1037,16 @@ static void diff_words_show(struct diff_words_data *diff_words)\n \t\tdiff_words->minus.text.size = 0;\n \t\treturn;\n \t}\n+\t/* special case: only addition */\n+\tif (!diff_words->minus.text.size) {\n+\t\tfputs(line_prefix, diff_words->opt->file);\n+\t\tfn_out_diff_words_write_helper(diff_words->opt->file,\n+\t\t\t&style->new, style->newline,\n+\t\t\tdiff_words->plus.text.size,\n+\t\t\tdiff_words->plus.text.ptr, line_prefix);\n+\t\tdiff_words->plus.text.size = 0;\n+\t\treturn;\n+\t}\n \n \tdiff_words->current_plus = diff_words->plus.text.ptr;\n \tdiff_words->last_minus = 0;\n-- \n2.3.0.rc1.137.g477eb31\n"},{"id":"275241","messageId":"1451565457-18756-5-git-send-email-pclouds@gmail.com","threadId":"41103","inReplyTo":"1451565457-18756-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 4/7] diff.c: refactor fn_out_diff_words_write_helper()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2015-12-31T12:37:34Z","receivedAt":"2015-12-31T12:37:34Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n diff.c | 15 ++++++++-------\n 1 file changed, 8 insertions(+), 7 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 1354368..8a9e42f 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -807,12 +807,13 @@ static void diff_words_append(struct diff_words_data *diff_words,\n \tbuffer->text.ptr[buffer->text.size] = '\\0';\n }\n \n-static int fn_out_diff_words_write_helper(FILE *fp,\n+static int fn_out_diff_words_write_helper(struct diff_words_data *dw,\n \t\t\t\t\t  struct diff_words_style_elem *st_el,\n \t\t\t\t\t  const char *newline,\n \t\t\t\t\t  size_t count, const char *buf,\n \t\t\t\t\t  const char *line_prefix)\n {\n+\tFILE *fp = dw->opt->file;\n \tint print = 0;\n \n \twhile (count) {\n@@ -919,7 +920,7 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)\n \t\tfputs(line_prefix, diff_words->opt->file);\n \t}\n \tif (diff_words->current_plus != plus_begin) {\n-\t\tfn_out_diff_words_write_helper(diff_words->opt->file,\n+\t\tfn_out_diff_words_write_helper(diff_words,\n \t\t\t\t&style->ctx, style->newline,\n \t\t\t\tplus_begin - diff_words->current_plus,\n \t\t\t\tdiff_words->current_plus, line_prefix);\n@@ -927,13 +928,13 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)\n \t\t\tfputs(line_prefix, diff_words->opt->file);\n \t}\n \tif (minus_begin != minus_end) {\n-\t\tfn_out_diff_words_write_helper(diff_words->opt->file,\n+\t\tfn_out_diff_words_write_helper(diff_words,\n \t\t\t\t&style->old, style->newline,\n \t\t\t\tminus_end - minus_begin, minus_begin,\n \t\t\t\tline_prefix);\n \t}\n \tif (plus_begin != plus_end) {\n-\t\tfn_out_diff_words_write_helper(diff_words->opt->file,\n+\t\tfn_out_diff_words_write_helper(diff_words,\n \t\t\t\t&style->new, style->newline,\n \t\t\t\tplus_end - plus_begin, plus_begin,\n \t\t\t\tline_prefix);\n@@ -1030,7 +1031,7 @@ static void diff_words_show(struct diff_words_data *diff_words)\n \t/* special case: only removal */\n \tif (!diff_words->plus.text.size) {\n \t\tfputs(line_prefix, diff_words->opt->file);\n-\t\tfn_out_diff_words_write_helper(diff_words->opt->file,\n+\t\tfn_out_diff_words_write_helper(diff_words,\n \t\t\t&style->old, style->newline,\n \t\t\tdiff_words->minus.text.size,\n \t\t\tdiff_words->minus.text.ptr, line_prefix);\n@@ -1040,7 +1041,7 @@ static void diff_words_show(struct diff_words_data *diff_words)\n \t/* special case: only addition */\n \tif (!diff_words->minus.text.size) {\n \t\tfputs(line_prefix, diff_words->opt->file);\n-\t\tfn_out_diff_words_write_helper(diff_words->opt->file,\n+\t\tfn_out_diff_words_write_helper(diff_words,\n \t\t\t&style->new, style->newline,\n \t\t\tdiff_words->plus.text.size,\n \t\t\tdiff_words->plus.text.ptr, line_prefix);\n@@ -1067,7 +1068,7 @@ static void diff_words_show(struct diff_words_data *diff_words)\n \t\t\tdiff_words->plus.text.size) {\n \t\tif (color_words_output_graph_prefix(diff_words))\n \t\t\tfputs(line_prefix, diff_words->opt->file);\n-\t\tfn_out_diff_words_write_helper(diff_words->opt->file,\n+\t\tfn_out_diff_words_write_helper(diff_words,\n \t\t\t&style->ctx, style->newline,\n \t\t\tdiff_words->plus.text.ptr + diff_words->plus.text.size\n \t\t\t- diff_words->current_plus, diff_words->current_plus,\n-- \n2.3.0.rc1.137.g477eb31\n"},{"id":"275242","messageId":"1451565457-18756-6-git-send-email-pclouds@gmail.com","threadId":"41103","inReplyTo":"1451565457-18756-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 5/7] diff: unified diff with colored words, step 1, unified diff only","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2015-12-31T12:37:35Z","receivedAt":"2015-12-31T12:37:35Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The goal is to produce a unified diff, but with changed words colored\ndifferently. A new diff-words mode is added that can keep track of both\nlines and words of each chunk. The marks then are post processed and\neach line is output in unified format. The actual word coloring comes in\nthe next patch.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n diff.c | 256 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n diff.h |   3 +-\n 2 files changed, 255 insertions(+), 4 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 8a9e42f..3b7317e 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -440,6 +440,23 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,\n \tecbdata->blank_at_eof_in_postimage = (at - l2) + 1;\n }\n \n+#define TAG_BEGIN_WORD(tag) \\\n+\t((tag) == TAG_BEGIN_OLD_WORD || \\\n+\t (tag) == TAG_BEGIN_NEW_WORD)\n+\n+enum pointer_tag { /* order is important because it's used in sorting */\n+\tTAG_END_WORD,\n+\tTAG_END_LINE,\n+\tTAG_BEGIN_LINE,\n+\tTAG_BEGIN_OLD_WORD,\n+\tTAG_BEGIN_NEW_WORD\n+};\n+\n+struct tagged_pointer {\n+\tconst char *str;\n+\tenum pointer_tag tag;\n+};\n+\n static void emit_line_0(struct diff_options *o, const char *set, const char *reset,\n \t\t\tint first, const char *line, int len)\n {\n@@ -757,6 +774,10 @@ struct diff_words_buffer {\n \t\tconst char *begin, *end;\n \t} *orig;\n \tint orig_nr, orig_alloc;\n+\tunsigned long *line;\n+\tint line_nr, line_alloc;\n+\tstruct tagged_pointer *mark;\n+\tint mark_nr, mark_alloc;\n };\n \n struct diff_words_style_elem {\n@@ -772,6 +793,8 @@ struct diff_words_style {\n \tconst char *newline;\n };\n \n+static struct diff_words_style diff_words_unified_style;\n+\n static struct diff_words_style diff_words_styles[] = {\n \t{ DIFF_WORDS_PORCELAIN, {\"+\", \"\\n\"}, {\"-\", \"\\n\"}, {\" \", \"\\n\"}, \"~\\n\" },\n \t{ DIFF_WORDS_PLAIN, {\"{+\", \"+}\"}, {\"[-\", \"-]\"}, {\"\", \"\"}, \"\\n\" },\n@@ -803,6 +826,13 @@ static void diff_words_append(struct diff_words_data *diff_words,\n \tline++;\n \tlen--;\n \tmemcpy(buffer->text.ptr + buffer->text.size, line, len);\n+\tif (diff_words->type == DIFF_WORDS_UNIFIED) {\n+\t\tunsigned long *l;\n+\t\tALLOC_GROW(buffer->line, (buffer->line_nr + 1) * 2, buffer->line_alloc);\n+\t\tl = buffer->line + (buffer->line_nr++) * 2;\n+\t\tl[0] = buffer->text.size;\n+\t\tl[1] = l[0] + len;\n+\t}\n \tbuffer->text.size += len;\n \tbuffer->text.ptr[buffer->text.size] = '\\0';\n }\n@@ -816,6 +846,40 @@ static int fn_out_diff_words_write_helper(struct diff_words_data *dw,\n \tFILE *fp = dw->opt->file;\n \tint print = 0;\n \n+\tif (dw->type == DIFF_WORDS_UNIFIED) {\n+\t\tstruct diff_words_style *st = &diff_words_unified_style;\n+\t\tstruct diff_words_buffer *b;\n+\t\tenum pointer_tag tag;\n+\t\tstruct tagged_pointer *tp;\n+\n+\t\tif (st_el == &st->ctx)\n+\t\t\treturn 0;\n+\t\telse if (st_el == &st->old)\n+\t\t\ttag = TAG_BEGIN_OLD_WORD;\n+\t\telse if (st_el == &st->new)\n+\t\t\ttag = TAG_BEGIN_NEW_WORD;\n+\t\telse\n+\t\t\treturn -1;\n+\n+\t\tif (buf >= dw->minus.text.ptr &&\n+\t\t    buf < dw->minus.text.ptr + dw->minus.text.size)\n+\t\t\tb = &dw->minus;\n+\t\telse if (buf >= dw->plus.text.ptr &&\n+\t\t\t buf < dw->plus.text.ptr + dw->plus.text.size)\n+\t\t\tb = &dw->plus;\n+\t\telse\n+\t\t\treturn -1;\n+\n+\t\tALLOC_GROW(b->mark, b->mark_nr + 2, b->mark_alloc);\n+\t\ttp = b->mark + b->mark_nr;\n+\t\ttp[0].str = buf;\n+\t\ttp[0].tag = tag;\n+\t\ttp[1].str = buf + count;\n+\t\ttp[1].tag = TAG_END_WORD;\n+\t\tb->mark_nr += 2;\n+\t\treturn 0;\n+\t}\n+\n \twhile (count) {\n \t\tchar *p = memchr(buf, '\\n', count);\n \t\tif (print)\n@@ -1014,6 +1078,23 @@ static void diff_words_fill(struct diff_words_buffer *buffer, mmfile_t *out,\n \t}\n }\n \n+static void diff_words_add_line(struct diff_words_buffer *buffer)\n+{\n+\tint i;\n+\n+\tALLOC_GROW(buffer->mark, buffer->line_nr * 2, buffer->mark_alloc);\n+\tfor (i = 0; i < buffer->line_nr; i++) {\n+\t\tstruct tagged_pointer *tp = buffer->mark + buffer->mark_nr;\n+\t\ttp->str = buffer->text.ptr + buffer->line[i * 2];\n+\t\ttp->tag = TAG_BEGIN_LINE;\n+\t\ttp++;\n+\t\ttp->str = buffer->text.ptr + buffer->line[i * 2 + 1];\n+\t\ttp->tag = TAG_END_LINE;\n+\t\tbuffer->mark_nr += 2;\n+\t}\n+\tbuffer->line_nr = 0;\n+}\n+\n /* this executes the word diff on the accumulated buffers */\n static void diff_words_show(struct diff_words_data *diff_words)\n {\n@@ -1025,6 +1106,16 @@ static void diff_words_show(struct diff_words_data *diff_words)\n \tstruct diff_options *opt = diff_words->opt;\n \tconst char *line_prefix;\n \n+\tif (diff_words->type == DIFF_WORDS_UNIFIED) {\n+\t\t/*\n+\t\t * line marks are collected in line[] array as offsets\n+\t\t * because the \"text\" buffer can be reallocated. Now\n+\t\t * it's safe to convert line[] to mark[].\n+\t\t */\n+\t\tdiff_words_add_line(&diff_words->minus);\n+\t\tdiff_words_add_line(&diff_words->plus);\n+\t}\n+\n \tassert(opt);\n \tline_prefix = diff_line_prefix(opt);\n \n@@ -1077,12 +1168,152 @@ static void diff_words_show(struct diff_words_data *diff_words)\n \tdiff_words->minus.text.size = diff_words->plus.text.size = 0;\n }\n \n+static int tagptrcmp(const void *a_, const void *b_)\n+{\n+\tconst struct tagged_pointer *a = a_;\n+\tconst struct tagged_pointer *b = b_;\n+\treturn a->str == b->str ? a->tag > b->tag : a->str > b->str;\n+}\n+\n+static void diff_words_buffer_finalize_marks(struct diff_words_buffer *b)\n+{\n+\tstruct tagged_pointer *dst, *src, *next, *end, *cur_word;\n+\tint i, dst_nr, dst_alloc;\n+\n+\t/* Join consecutive same-type words */\n+\tend = b->mark + b->mark_nr;\n+\tfor (src = b->mark; src < end; src++) {\n+\t\tnext = src + 2;\n+\t\twhile (next + 2 <= end &&\n+\t\t       TAG_BEGIN_WORD(src[0].tag) &&\n+\t\t       TAG_BEGIN_WORD(next[0].tag) &&\n+\t\t       src[0].tag == next[0].tag &&\n+\t\t       src[1].tag == TAG_END_WORD &&\n+\t\t       next[1].tag == TAG_END_WORD &&\n+\t\t       src[1].str == next[0].str) {\n+\t\t\tsrc[1] = next[1];\n+\t\t\tmemcpy(next, next + 2, sizeof(*next) * (end - next - 2));\n+\t\t\tend -= 2;\n+\t\t}\n+\t}\n+\tb->mark_nr = end - b->mark;\n+\n+\t/*\n+\t * Simplify one-word chunks. Not that at this point we have\n+\t * all line marks (in correct order), then all word marks.\n+\t */\n+\tif (b->mark_nr >= 4 &&\n+\t    b->mark[0].tag == TAG_BEGIN_LINE &&\n+\t    TAG_BEGIN_WORD(b->mark[b->mark_nr - 2].tag) &&\n+\t    b->mark[0].str == b->mark[b->mark_nr - 2].str &&\n+\t    b->mark[b->mark_nr - 1].tag == TAG_END_WORD &&\n+\t    b->mark[b->mark_nr - 3].tag == TAG_END_LINE &&\n+\t    b->mark[b->mark_nr - 1].str == b->mark[b->mark_nr - 3].str) {\n+\t\tb->mark_nr -= 2;\n+\t\treturn;\n+\t}\n+\n+\t/* Move words into lines */\n+\tqsort(b->mark, b->mark_nr, sizeof(*b->mark), tagptrcmp);\n+\n+\t/* Split words that span across lines */\n+\tcur_word = NULL;\n+\tdst = NULL;\n+\tdst_nr = 0;\n+\tdst_alloc = 0;\n+\tALLOC_GROW(dst, b->mark_nr, dst_alloc);\n+\tfor (i = 0; i < b->mark_nr; i++) {\n+\t\tstruct tagged_pointer *src = b->mark + i;\n+\n+\t\tswitch (src->tag) {\n+\t\tcase TAG_BEGIN_OLD_WORD:\n+\t\tcase TAG_BEGIN_NEW_WORD:\n+\t\t\tALLOC_GROW(dst, dst_nr + 1, dst_alloc);\n+\t\t\tdst[dst_nr++] = *src;\n+\t\t\tcur_word = src;\n+\t\t\tbreak;\n+\n+\t\tcase TAG_END_WORD:\n+\t\t\tALLOC_GROW(dst, dst_nr + 1, dst_alloc);\n+\t\t\tdst[dst_nr++] = *src;\n+\t\t\tcur_word = NULL;\n+\t\t\tbreak;\n+\n+\t\tcase TAG_BEGIN_LINE:\n+\t\t\tALLOC_GROW(dst, dst_nr + 2, dst_alloc);\n+\t\t\tdst[dst_nr++] = *src;\n+\t\t\tif (cur_word) {\n+\t\t\t\tdst[dst_nr].tag = cur_word->tag;\n+\t\t\t\tdst[dst_nr].str = src->str;\n+\t\t\t\tdst_nr++;\n+\t\t\t}\n+\t\t\tbreak;\n+\n+\t\tcase TAG_END_LINE:\n+\t\t\tALLOC_GROW(dst, dst_nr + 2, dst_alloc);\n+\t\t\tif (cur_word) {\n+\t\t\t\tdst[dst_nr].tag = TAG_END_WORD;\n+\t\t\t\tdst[dst_nr].str = src->str;\n+\t\t\t\tdst_nr++;\n+\t\t\t}\n+\t\t\tdst[dst_nr++] = *src;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tfree(b->mark);\n+\tb->mark = dst;\n+\tb->mark_nr = dst_nr;\n+\tb->mark_alloc = dst_alloc;\n+}\n+\n+static void diff_words_flush_unified(struct emit_callback *ecb,\n+\t\t\t\t     enum color_diff color,\n+\t\t\t\t     unsigned ws_error_highlight,\n+\t\t\t\t     char sign)\n+{\n+\tconst char *reset = diff_get_color(ecb->color_diff, DIFF_RESET);\n+\tstruct diff_words_data *dw = ecb->diff_words;\n+\tstruct diff_words_buffer *b;\n+\tstruct tagged_pointer *begin_line, *end, *end_line;\n+\n+\tswitch (sign) {\n+\tcase '-':\n+\t\tb = &dw->minus;\n+\t\tbreak;\n+\tcase '+':\n+\t\tb = &dw->plus;\n+\t\tbreak;\n+\tdefault:\n+\t\treturn;\n+\t}\n+\n+\tif (!b->mark_nr)\n+\t\treturn;\n+\n+\tdiff_words_buffer_finalize_marks(b);\n+\tend = b->mark + b->mark_nr;\n+\tfor (begin_line = b->mark; begin_line < end; begin_line = end_line + 1) {\n+\t\tassert(begin_line->tag == TAG_BEGIN_LINE);\n+\t\tend_line = begin_line;\n+\t\twhile (end_line < end && end_line->tag != TAG_END_LINE)\n+\t\t\tend_line++;\n+\t\tassert(end_line->tag == TAG_END_LINE);\n+\t\temit_line_checked(reset, ecb, begin_line->str,\n+\t\t\t\t  end_line->str - begin_line->str,\n+\t\t\t\t  color, ws_error_highlight, sign);\n+\t}\n+\tb->mark_nr = 0;\n+}\n+\n /* In \"color-words\" mode, show word-diff of words accumulated in the buffer */\n static void diff_words_flush(struct emit_callback *ecbdata)\n {\n \tif (ecbdata->diff_words->minus.text.size ||\n \t    ecbdata->diff_words->plus.text.size)\n \t\tdiff_words_show(ecbdata->diff_words);\n+\tdiff_words_flush_unified(ecbdata, DIFF_FILE_OLD, WSEH_OLD, '-');\n+\tdiff_words_flush_unified(ecbdata, DIFF_FILE_NEW, WSEH_NEW, '+');\n }\n \n static void diff_filespec_load_driver(struct diff_filespec *one)\n@@ -1133,6 +1364,10 @@ static void init_diff_words_data(struct emit_callback *ecbdata,\n \t\t\tdie (\"Invalid regular expression: %s\",\n \t\t\t     o->word_regex);\n \t}\n+\tif (o->word_diff == DIFF_WORDS_UNIFIED) {\n+\t\tecbdata->diff_words->style = &diff_words_unified_style;\n+\t\treturn;\n+\t}\n \tfor (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) {\n \t\tif (o->word_diff == diff_words_styles[i].type) {\n \t\t\tecbdata->diff_words->style =\n@@ -1155,8 +1390,12 @@ static void free_diff_words_data(struct emit_callback *ecbdata)\n \t\tfree (ecbdata->diff_words->opt);\n \t\tfree (ecbdata->diff_words->minus.text.ptr);\n \t\tfree (ecbdata->diff_words->minus.orig);\n+\t\tfree (ecbdata->diff_words->minus.line);\n+\t\tfree (ecbdata->diff_words->minus.mark);\n \t\tfree (ecbdata->diff_words->plus.text.ptr);\n \t\tfree (ecbdata->diff_words->plus.orig);\n+\t\tfree (ecbdata->diff_words->plus.line);\n+\t\tfree (ecbdata->diff_words->plus.mark);\n \t\tif (ecbdata->diff_words->word_regex) {\n \t\t\tregfree(ecbdata->diff_words->word_regex);\n \t\t\tfree(ecbdata->diff_words->word_regex);\n@@ -1274,7 +1513,8 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)\n \t\tif (line[0] == '-' || line[0] == '+') {\n \t\t\tdiff_words_append(ecbdata->diff_words, line, len);\n \t\t\treturn;\n-\t\t} else if (starts_with(line, \"\\\\ \")) {\n+\t\t} else if (ecbdata->diff_words->type != DIFF_WORDS_UNIFIED &&\n+\t\t\t   starts_with(line, \"\\\\ \")) {\n \t\t\t/*\n \t\t\t * Eat the \"no newline at eof\" marker as if we\n \t\t\t * saw a \"+\" or \"-\" line with nothing on it,\n@@ -1288,7 +1528,8 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)\n \t\tif (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) {\n \t\t\temit_line(ecbdata->opt, context, reset, line, len);\n \t\t\tfputs(\"~\\n\", ecbdata->opt->file);\n-\t\t} else {\n+\t\t\treturn;\n+\t\t} else if (ecbdata->diff_words->type != DIFF_WORDS_UNIFIED) {\n \t\t\t/*\n \t\t\t * Skip the prefix character, if any.  With\n \t\t\t * diff_suppress_blank_empty, there may be\n@@ -1299,8 +1540,8 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)\n \t\t\t      len--;\n \t\t\t}\n \t\t\temit_line(ecbdata->opt, context, reset, line, len);\n+\t\t\treturn;\n \t\t}\n-\t\treturn;\n \t}\n \n \tswitch (line[0]) {\n@@ -3859,6 +4100,15 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)\n \t\toptions->word_diff = DIFF_WORDS_COLOR;\n \t\toptions->word_regex = arg;\n \t}\n+\telse if (!strcmp(arg, \"--highlight-words\")) {\n+\t\toptions->use_color = 1;\n+\t\toptions->word_diff = DIFF_WORDS_UNIFIED;\n+\t}\n+\telse if (skip_prefix(arg, \"--highlight-words=\", &arg)) {\n+\t\toptions->use_color = 1;\n+\t\toptions->word_diff = DIFF_WORDS_UNIFIED;\n+\t\toptions->word_regex = arg;\n+\t}\n \telse if (!strcmp(arg, \"--word-diff\")) {\n \t\tif (options->word_diff == DIFF_WORDS_NONE)\n \t\t\toptions->word_diff = DIFF_WORDS_PLAIN;\ndiff --git a/diff.h b/diff.h\nindex f7208ad..85c469b 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -107,7 +107,8 @@ enum diff_words_type {\n \tDIFF_WORDS_NONE = 0,\n \tDIFF_WORDS_PORCELAIN,\n \tDIFF_WORDS_PLAIN,\n-\tDIFF_WORDS_COLOR\n+\tDIFF_WORDS_COLOR,\n+\tDIFF_WORDS_UNIFIED\n };\n \n struct diff_options {\n-- \n2.3.0.rc1.137.g477eb31\n"},{"id":"275243","messageId":"1451565457-18756-7-git-send-email-pclouds@gmail.com","threadId":"41103","inReplyTo":"1451565457-18756-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 6/7] diff.c: add new arguments to emit_line_0()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2015-12-31T12:37:36Z","receivedAt":"2015-12-31T12:37:36Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This patch is no-op, to reduce noise in the next one.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n diff.c | 33 ++++++++++++++++++++++-----------\n 1 file changed, 22 insertions(+), 11 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 3b7317e..47d22e3 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -458,7 +458,9 @@ struct tagged_pointer {\n };\n \n static void emit_line_0(struct diff_options *o, const char *set, const char *reset,\n-\t\t\tint first, const char *line, int len)\n+\t\t\tint first, const char *line, int len,\n+\t\t\tstruct tagged_pointer *begin,\n+\t\t\tstruct tagged_pointer *end)\n {\n \tint has_trailing_newline, has_trailing_carriage_return;\n \tint nofirst;\n@@ -497,7 +499,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res\n static void emit_line(struct diff_options *o, const char *set, const char *reset,\n \t\t      const char *line, int len)\n {\n-\temit_line_0(o, set, reset, line[0], line+1, len-1);\n+\temit_line_0(o, set, reset, line[0], line+1, len-1, NULL, NULL);\n }\n \n static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len)\n@@ -516,7 +518,9 @@ static void emit_line_checked(const char *reset,\n \t\t\t      const char *line, int len,\n \t\t\t      enum color_diff color,\n \t\t\t      unsigned ws_error_highlight,\n-\t\t\t      char sign)\n+\t\t\t      char sign,\n+\t\t\t      struct tagged_pointer *begin,\n+\t\t\t      struct tagged_pointer *end)\n {\n \tconst char *set = diff_get_color(ecbdata->color_diff, color);\n \tconst char *ws = NULL;\n@@ -528,13 +532,16 @@ static void emit_line_checked(const char *reset,\n \t}\n \n \tif (!ws)\n-\t\temit_line_0(ecbdata->opt, set, reset, sign, line, len);\n+\t\temit_line_0(ecbdata->opt, set, reset, sign,\n+\t\t\t    line, len, begin, end);\n \telse if (sign == '+' && new_blank_line_at_eof(ecbdata, line, len))\n \t\t/* Blank line at EOF - paint '+' as well */\n-\t\temit_line_0(ecbdata->opt, ws, reset, sign, line, len);\n+\t\temit_line_0(ecbdata->opt, ws, reset, sign,\n+\t\t\t    line, len, begin, end);\n \telse {\n \t\t/* Emit just the prefix, then the rest. */\n-\t\temit_line_0(ecbdata->opt, set, reset, sign, \"\", 0);\n+\t\temit_line_0(ecbdata->opt, set, reset, sign,\n+\t\t\t    \"\", 0, NULL, NULL);\n \t\tws_check_emit(line, len, ecbdata->ws_rule,\n \t\t\t      ecbdata->opt->file, set, reset, ws);\n \t}\n@@ -545,7 +552,8 @@ static void emit_add_line(const char *reset,\n \t\t\t  const char *line, int len)\n {\n \temit_line_checked(reset, ecbdata, line, len,\n-\t\t\t  DIFF_FILE_NEW, WSEH_NEW, '+');\n+\t\t\t  DIFF_FILE_NEW, WSEH_NEW, '+',\n+\t\t\t  NULL, NULL);\n }\n \n static void emit_del_line(const char *reset,\n@@ -553,7 +561,8 @@ static void emit_del_line(const char *reset,\n \t\t\t  const char *line, int len)\n {\n \temit_line_checked(reset, ecbdata, line, len,\n-\t\t\t  DIFF_FILE_OLD, WSEH_OLD, '-');\n+\t\t\t  DIFF_FILE_OLD, WSEH_OLD, '-',\n+\t\t\t  NULL, NULL);\n }\n \n static void emit_context_line(const char *reset,\n@@ -561,7 +570,8 @@ static void emit_context_line(const char *reset,\n \t\t\t      const char *line, int len)\n {\n \temit_line_checked(reset, ecbdata, line, len,\n-\t\t\t  DIFF_CONTEXT, WSEH_CONTEXT, ' ');\n+\t\t\t  DIFF_CONTEXT, WSEH_CONTEXT, ' ',\n+\t\t\t  NULL, NULL);\n }\n \n static void emit_hunk_header(struct emit_callback *ecbdata,\n@@ -682,7 +692,7 @@ static void emit_rewrite_lines(struct emit_callback *ecb,\n \t\t\t\t\t\t     DIFF_CONTEXT);\n \t\tputc('\\n', ecb->opt->file);\n \t\temit_line_0(ecb->opt, context, reset, '\\\\',\n-\t\t\t    nneof, strlen(nneof));\n+\t\t\t    nneof, strlen(nneof), NULL, NULL);\n \t}\n }\n \n@@ -1301,7 +1311,8 @@ static void diff_words_flush_unified(struct emit_callback *ecb,\n \t\tassert(end_line->tag == TAG_END_LINE);\n \t\temit_line_checked(reset, ecb, begin_line->str,\n \t\t\t\t  end_line->str - begin_line->str,\n-\t\t\t\t  color, ws_error_highlight, sign);\n+\t\t\t\t  color, ws_error_highlight, sign,\n+\t\t\t\t  NULL, NULL);\n \t}\n \tb->mark_nr = 0;\n }\n-- \n2.3.0.rc1.137.g477eb31\n"},{"id":"275244","messageId":"1451565457-18756-8-git-send-email-pclouds@gmail.com","threadId":"41103","inReplyTo":"1451565457-18756-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 7/7] diff --highlight-words: actually highlight words","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2015-12-31T12:37:37Z","receivedAt":"2015-12-31T12:37:37Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n diff.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++---\n 1 file changed, 50 insertions(+), 3 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 47d22e3..68a847d 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -457,6 +457,50 @@ struct tagged_pointer {\n \tenum pointer_tag tag;\n };\n \n+static void emit_tagged_line(struct diff_options *o, const char *set,\n+\t\t\t     const char *line, int len,\n+\t\t\t     struct tagged_pointer *begin,\n+\t\t\t     struct tagged_pointer *end)\n+{\n+\tstruct tagged_pointer *current;\n+\tFILE *file = o->file;\n+\tconst char *last_color = set;\n+\n+\tfor (current = begin; current < end; current++) {\n+\t\tstruct tagged_pointer *next = current + 1;\n+\t\tconst char *start = current->str;\n+\t\tconst char *end = next->str;\n+\t\tconst char *color = NULL;\n+\n+\t\tswitch (current->tag) {\n+\t\tcase TAG_END_WORD:\n+\t\t\tcolor = set;\n+\t\t\tbreak;\n+\n+\t\tcase TAG_BEGIN_OLD_WORD:\n+\t\t\tcolor = GIT_COLOR_BG_RED GIT_COLOR_YELLOW;\n+\t\t\tbreak;\n+\n+\t\tcase TAG_BEGIN_NEW_WORD:\n+\t\t\tcolor = GIT_COLOR_BG_GREEN GIT_COLOR_BLUE;\n+\t\t\tbreak;\n+\n+\t\tdefault:\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (color && color != last_color) {\n+\t\t\tif (color == set)\n+\t\t\t\tfputs(GIT_COLOR_RESET, file);\n+\t\t\tfputs(color, file);\n+\t\t\tlast_color = color;\n+\t\t}\n+\t\twhile (end > start && end[-1] == '\\n')\n+\t\t\tend--;\n+\t\tfwrite(start, end - start, 1, file);\n+\t}\n+}\n+\n static void emit_line_0(struct diff_options *o, const char *set, const char *reset,\n \t\t\tint first, const char *line, int len,\n \t\t\tstruct tagged_pointer *begin,\n@@ -487,7 +531,10 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res\n \t\tfputs(set, file);\n \t\tif (!nofirst)\n \t\t\tfputc(first, file);\n-\t\tfwrite(line, len, 1, file);\n+\t\tif (begin)\n+\t\t\temit_tagged_line(o, set, line, len, begin, end);\n+\t\telse\n+\t\t\tfwrite(line, len, 1, file);\n \t\tfputs(reset, file);\n \t}\n \tif (has_trailing_carriage_return)\n@@ -531,7 +578,7 @@ static void emit_line_checked(const char *reset,\n \t\t\tws = NULL;\n \t}\n \n-\tif (!ws)\n+\tif (!ws || begin)\n \t\temit_line_0(ecbdata->opt, set, reset, sign,\n \t\t\t    line, len, begin, end);\n \telse if (sign == '+' && new_blank_line_at_eof(ecbdata, line, len))\n@@ -1312,7 +1359,7 @@ static void diff_words_flush_unified(struct emit_callback *ecb,\n \t\temit_line_checked(reset, ecb, begin_line->str,\n \t\t\t\t  end_line->str - begin_line->str,\n \t\t\t\t  color, ws_error_highlight, sign,\n-\t\t\t\t  NULL, NULL);\n+\t\t\t\t  begin_line, end_line);\n \t}\n \tb->mark_nr = 0;\n }\n-- \n2.3.0.rc1.137.g477eb31\n"}]}