{"thread":{"id":"65604","subject":"[PATCH] add -p: introduce 'w' command to view hunk with --word-diff","startedAt":"2026-05-06T23:58:25Z","lastAt":"2026-05-12T00:04:13Z","messageCount":9,"participants":["Javier Bassi","Pablo","Phillip Wood","D. Ben Knoble","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"542828","messageId":"20260506235459.529862-1-javierbassi@gmail.com","threadId":"65604","inReplyTo":null,"subject":"[PATCH] add -p: introduce 'w' command to view hunk with --word-diff","fromName":"Javier Bassi","fromEmail":"javierbassi@gmail.com","sentAt":"2026-05-06T23:54:59Z","receivedAt":"2026-05-06T23:58:25Z","isPatch":true,"body":"When using `git add --patch`, reviewing changes in long lines can be\ndifficult with the default line-based diff. This is particularly\nnoticeable in formats such as JSONP, CSV, LaTeX, Markdown, or other\nplain text where small inline edits can be hard to spot.\n\nAdded `w - print the current hunk with word-diff` during hunk selection\nto re-display the current hunk using `--word-diff`. This provides a\nclearer inline view of changes without modifying the hunk or affecting\nhow patches are applied or staged.\n\nSigned-off-by: Javier Bassi <javierbassi@gmail.com>\n---\n Documentation/git-add.adoc |   1 +\n add-patch.c                | 105 ++++++++++++++++++++++++++++++++++++-\n t/t3701-add-interactive.sh |  59 +++++++++++++--------\n 3 files changed, 142 insertions(+), 23 deletions(-)\n\ndiff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc\nindex 941135dc63..d2ee1cf9a9 100644\n--- a/Documentation/git-add.adoc\n+++ b/Documentation/git-add.adoc\n@@ -351,6 +351,7 @@ patch::\n        K - go to the previous hunk, roll over at the top\n        s - split the current hunk into smaller hunks\n        e - manually edit the current hunk\n+       w - print the current hunk with word-diff\n        p - print the current hunk\n        P - print the current hunk using the pager\n        ? - print help\ndiff --git a/add-patch.c b/add-patch.c\nindex f27edcbe8d..0364f4bc97 100644\n--- a/add-patch.c\n+++ b/add-patch.c\n@@ -7,6 +7,7 @@\n #include \"commit.h\"\n #include \"config.h\"\n #include \"diff.h\"\n+#include \"diffcore.h\"\n #include \"editor.h\"\n #include \"environment.h\"\n #include \"gettext.h\"\n@@ -1508,6 +1509,105 @@ static void summarize_hunk(struct add_p_state *s, struct hunk *hunk,\n \tstrbuf_complete_line(out);\n }\n \n+static void trim_trailing_lf(struct strbuf *buf)\n+{\n+\tif (buf->len && buf->buf[buf->len - 1] == '\\n')\n+\t\tstrbuf_setlen(buf, buf->len - 1);\n+}\n+\n+static void add_word_diff_line(struct strbuf *old, struct strbuf *new,\n+\t\t\t       const char *line, size_t len, char marker)\n+{\n+\tif (marker == '-' || marker == '+' || *line == ' ') {\n+\t\tline++;\n+\t\tlen--;\n+\t}\n+\n+\tif (marker != '+')\n+\t\tstrbuf_add(old, line, len);\n+\tif (marker != '-')\n+\t\tstrbuf_add(new, line, len);\n+}\n+\n+static void build_word_diff_files(struct add_p_state *s, struct hunk *hunk,\n+\t\t\t\t  struct strbuf *old, struct strbuf *new)\n+{\n+\tsize_t i;\n+\tchar last_marker = '\\0';\n+\n+\tfor (i = hunk->start; i < hunk->end; i = find_next_line(&s->plain, i)) {\n+\t\tsize_t next = find_next_line(&s->plain, i);\n+\t\tchar marker = normalize_marker(s->plain.buf + i);\n+\n+\t\tif (marker == '\\\\') {\n+\t\t\tif (last_marker != '+')\n+\t\t\t\ttrim_trailing_lf(old);\n+\t\t\tif (last_marker != '-')\n+\t\t\t\ttrim_trailing_lf(new);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (marker != ' ' && marker != '-' && marker != '+')\n+\t\t\tBUG(\"unhandled diff marker: '%c'\", marker);\n+\n+\t\tadd_word_diff_line(old, new, s->plain.buf + i, next - i,\n+\t\t\t\t   marker);\n+\t\tlast_marker = marker;\n+\t}\n+}\n+\n+static struct diff_filespec *word_diff_filespec(struct repository *r,\n+\t\t\t\t\t\tconst char *name,\n+\t\t\t\t\t\tstruct strbuf *buf)\n+{\n+\tstruct diff_filespec *spec = alloc_filespec(name);\n+\tsize_t size;\n+\n+\tfill_filespec(spec, null_oid(r->hash_algo), 0, 0100644);\n+\tspec->data = strbuf_detach(buf, &size);\n+\tspec->size = size;\n+\tspec->should_free = 1;\n+\tspec->is_stdin = 1;\n+\n+\treturn spec;\n+}\n+\n+static void show_hunk_word_diff(struct add_p_state *s, struct hunk *hunk,\n+\t\t\t\tint colored)\n+{\n+\tstruct hunk_header *header = &hunk->header;\n+\tstruct strbuf old = STRBUF_INIT, new = STRBUF_INIT;\n+\tstruct diff_options opts;\n+\tstruct diff_queue_struct queue;\n+\n+\tif (!header->old_offset && !header->new_offset) {\n+\t\tstrbuf_reset(&s->buf);\n+\t\trender_hunk(s, hunk, 0, colored, &s->buf);\n+\t\tfputs(s->buf.buf, stdout);\n+\t\treturn;\n+\t}\n+\n+\tbuild_word_diff_files(s, hunk, &old, &new);\n+\n+\trepo_diff_setup(s->r, &opts);\n+\topts.output_format = DIFF_FORMAT_PATCH;\n+\topts.use_color = colored ? s->cfg.use_color_diff : GIT_COLOR_NEVER;\n+\topts.word_diff = DIFF_WORDS_PLAIN;\n+\topts.context = header->old_count > header->new_count ?\n+\t\theader->old_count : header->new_count;\n+\topts.flags.suppress_diff_headers = 1;\n+\tdiff_setup_done(&opts);\n+\n+\tmemcpy(&queue, &diff_queued_diff, sizeof(diff_queued_diff));\n+\tdiff_queue_init(&diff_queued_diff);\n+\tdiff_queue(&diff_queued_diff,\n+\t\t   word_diff_filespec(s->r, \"a\", &old),\n+\t\t   word_diff_filespec(s->r, \"b\", &new));\n+\tdiffcore_std(&opts);\n+\tdiff_flush(&opts);\n+\tmemcpy(&diff_queued_diff, &queue, sizeof(diff_queued_diff));\n+}\n+\n #define DISPLAY_HUNKS_LINES 20\n static size_t display_hunks(struct add_p_state *s,\n \t\t\t    struct file_diff *file_diff, size_t start_index)\n@@ -1540,6 +1640,7 @@ N_(\"j - go to the next undecided hunk, roll over at the bottom\\n\"\n    \"/ - search for a hunk matching the given regex\\n\"\n    \"s - split the current hunk into smaller hunks\\n\"\n    \"e - manually edit the current hunk\\n\"\n+   \"w - print the current hunk with word-diff\\n\"\n    \"p - print the current hunk\\n\"\n    \"P - print the current hunk using the pager\\n\"\n    \"> - go to the next file, roll over at the bottom\\n\"\n@@ -1731,7 +1832,7 @@ static size_t patch_update_file(struct add_p_state *s,\n \t\t\t\tpermitted |= ALLOW_GOTO_PREVIOUS_FILE;\n \t\t\t\tstrbuf_addstr(&s->buf, \",<\");\n \t\t\t}\n-\t\t\tstrbuf_addstr(&s->buf, \",p,P\");\n+\t\t\tstrbuf_addstr(&s->buf, \",w,p,P\");\n \t\t}\n \t\tif (file_diff->deleted)\n \t\t\tprompt_mode_type = PROMPT_DELETION;\n@@ -1953,6 +2054,8 @@ static size_t patch_update_file(struct add_p_state *s,\n \t\t\t\thunk->use = USE_HUNK;\n \t\t\t\tgoto soft_increment;\n \t\t\t}\n+\t\t} else if (s->answer.buf[0] == 'w') {\n+\t\t\tshow_hunk_word_diff(s, hunk, colored);\n \t\t} else if (ch == 'p') {\n \t\t\trendered_hunk_index = -1;\n \t\t\tuse_pager = (s->answer.buf[0] == 'P') ? 1 : 0;\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex 6e120a4001..e1ce98d62b 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -48,8 +48,8 @@ test_expect_success 'unknown command' '\n \tgit add -N command &&\n \tgit diff command >expect &&\n \tcat >>expect <<-EOF &&\n-\t(1/1) Stage addition [y,n,q,a,d,e,p,P,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)\n-\t(1/1) Stage addition [y,n,q,a,d,e,p,P,?]?$SP\n+\t(1/1) Stage addition [y,n,q,a,d,e,w,p,P,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)\n+\t(1/1) Stage addition [y,n,q,a,d,e,w,p,P,?]?$SP\n \tEOF\n \tgit add -p -- command <command >actual 2>&1 &&\n \ttest_cmp expect actual\n@@ -332,9 +332,9 @@ test_expect_success 'different prompts for mode change/deleted' '\n \tgit -c core.filemode=true add -p >actual &&\n \tsed -n \"s/^\\(([0-9/]*) Stage .*?\\).*/\\1/p\" actual >actual.filtered &&\n \tcat >expect <<-\\EOF &&\n-\t(1/1) Stage deletion [y,n,q,a,d,p,P,?]?\n-\t(1/2) Stage mode change [y,n,q,a,d,k,K,j,J,g,/,p,P,?]?\n-\t(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,P,?]?\n+\t(1/1) Stage deletion [y,n,q,a,d,w,p,P,?]?\n+\t(1/2) Stage mode change [y,n,q,a,d,k,K,j,J,g,/,w,p,P,?]?\n+\t(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,w,p,P,?]?\n \tEOF\n \ttest_cmp expect actual.filtered\n '\n@@ -521,13 +521,13 @@ test_expect_success 'split hunk setup' '\n test_expect_success 'goto hunk 1 with \"g 1\"' '\n \ttest_when_finished \"git reset\" &&\n \ttr _ \" \" >expect <<-EOF &&\n-\t(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,P,?]? + 1:  -1,2 +1,3          +15\n+\t(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,w,p,P,?]? + 1:  -1,2 +1,3          +15\n \t_ 2:  -2,4 +3,8          +21\n \tgo to which hunk? @@ -1,2 +1,3 @@\n \t_10\n \t+15\n \t_20\n-\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_\n+\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]?_\n \tEOF\n \ttest_write_lines s y g 1 | git add -p >actual &&\n \ttail -n 7 <actual >actual.trimmed &&\n@@ -540,7 +540,7 @@ test_expect_success 'goto hunk 1 with \"g1\"' '\n \t_10\n \t+15\n \t_20\n-\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_\n+\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]?_\n \tEOF\n \ttest_write_lines s y g1 | git add -p >actual &&\n \ttail -n 4 <actual >actual.trimmed &&\n@@ -550,11 +550,11 @@ test_expect_success 'goto hunk 1 with \"g1\"' '\n test_expect_success 'navigate to hunk via regex /pattern' '\n \ttest_when_finished \"git reset\" &&\n \ttr _ \" \" >expect <<-EOF &&\n-\t(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,P,?]? @@ -1,2 +1,3 @@\n+\t(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,w,p,P,?]? @@ -1,2 +1,3 @@\n \t_10\n \t+15\n \t_20\n-\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_\n+\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]?_\n \tEOF\n \ttest_write_lines s y /1,2 | git add -p >actual &&\n \ttail -n 5 <actual >actual.trimmed &&\n@@ -567,7 +567,7 @@ test_expect_success 'navigate to hunk via regex / pattern' '\n \t_10\n \t+15\n \t_20\n-\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_\n+\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]?_\n \tEOF\n \ttest_write_lines s y / 1,2 | git add -p >actual &&\n \ttail -n 4 <actual >actual.trimmed &&\n@@ -579,27 +579,42 @@ test_expect_success 'print again the hunk' '\n \ttr _ \" \" >expect <<-EOF &&\n \t+15\n \t 20\n-\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? @@ -1,2 +1,3 @@\n+\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]? @@ -1,2 +1,3 @@\n \t 10\n \t+15\n \t 20\n-\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_\n+\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]?_\n \tEOF\n \ttest_write_lines s y g 1 p | git add -p >actual &&\n \ttail -n 7 <actual >actual.trimmed &&\n \ttest_cmp expect actual.trimmed\n '\n \n+test_expect_success 'print hunk with word-diff' '\n+\ttest_when_finished \"rm -rf word-diff-repo\" &&\n+\tgit init word-diff-repo &&\n+\t(\n+\t\tcd word-diff-repo &&\n+\t\ttest_write_lines \"alpha old beta\" context >word-diff &&\n+\t\tgit add word-diff &&\n+\t\tgit commit -m word-diff &&\n+\t\ttest_write_lines \"alpha new beta\" context >word-diff &&\n+\t\ttest_write_lines w n | git add -p word-diff >actual &&\n+\t\ttest_grep \"alpha \\\\[-old-\\\\]{+new+} beta\" actual &&\n+\t\tgit diff --cached --exit-code\n+\t)\n+'\n+\n test_expect_success TTY 'print again the hunk (PAGER)' '\n \ttest_when_finished \"git reset\" &&\n \tcat >expect <<-EOF &&\n \t<GREEN>+<RESET><GREEN>15<RESET>\n \t 20<RESET>\n-\t<BOLD;BLUE>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>PAGER <CYAN>@@ -1,2 +1,3 @@<RESET>\n+\t<BOLD;BLUE>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]? <RESET>PAGER <CYAN>@@ -1,2 +1,3 @@<RESET>\n \tPAGER  10<RESET>\n \tPAGER <GREEN>+<RESET><GREEN>15<RESET>\n \tPAGER  20<RESET>\n-\t<BOLD;BLUE>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>\n+\t<BOLD;BLUE>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]? <RESET>\n \tEOF\n \ttest_write_lines s y g 1 P |\n \t(\n@@ -796,21 +811,21 @@ test_expect_success 'colors can be overridden' '\n \t<BLUE>+<RESET><BLUE>new<RESET>\n \t<CYAN> more-context<RESET>\n \t<BLUE>+<RESET><BLUE>another-one<RESET>\n-\t<YELLOW>(1/1) Stage this hunk [y,n,q,a,d,s,e,p,P,?]? <RESET><BOLD>Split into 2 hunks.<RESET>\n+\t<YELLOW>(1/1) Stage this hunk [y,n,q,a,d,s,e,w,p,P,?]? <RESET><BOLD>Split into 2 hunks.<RESET>\n \t<MAGENTA>@@ -1,3 +1,3 @@<RESET>\n \t<CYAN> context<RESET>\n \t<BOLD>-old<RESET>\n \t<BLUE>+<RESET><BLUE>new<RESET>\n \t<CYAN> more-context<RESET>\n-\t<YELLOW>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET>\n+\t<YELLOW>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET>\n \t<CYAN> more-context<RESET>\n \t<BLUE>+<RESET><BLUE>another-one<RESET>\n-\t<YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,P,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET>\n+\t<YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,w,p,P,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET>\n \t<CYAN> context<RESET>\n \t<BOLD>-old<RESET>\n \t<BLUE>+new<RESET>\n \t<CYAN> more-context<RESET>\n-\t<YELLOW>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>\n+\t<YELLOW>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]? <RESET>\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -1424,9 +1439,9 @@ test_expect_success 'invalid option s is rejected' '\n \ttest_write_lines j s q | git add -p >out &&\n \tsed -ne \"s/ @@.*//\" -e \"s/ \\$//\" -e \"/^(/p\" <out >actual &&\n \tcat >expect <<-EOF &&\n-\t(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,s,e,p,P,?]?\n-\t(2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? Sorry, cannot split this hunk\n-\t(2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?\n+\t(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,s,e,w,p,P,?]?\n+\t(2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]? Sorry, cannot split this hunk\n+\t(2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]?\n \tEOF\n \ttest_cmp expect actual\n '\n-- \n2.54.0\n\n"},{"id":"542834","messageId":"CAN5EUNRT7V3BrtyU0UYwGVnJ51LWSsNi1OnzMB5WL=w8vhKmrw@mail.gmail.com","threadId":"65604","inReplyTo":"20260506235459.529862-1-javierbassi@gmail.com","subject":"Re: [PATCH] add -p: introduce 'w' command to view hunk with --word-diff","fromName":"Pablo","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-05-07T07:55:18Z","receivedAt":"2026-05-07T07:55:30Z","isPatch":true,"body":"El jue, 7 may 2026 a las 1:58, Javier Bassi (<javierbassi@gmail.com>) escribió:\n>\n> When using `git add --patch`, reviewing changes in long lines can be\n> difficult with the default line-based diff. This is particularly\n> noticeable in formats such as JSONP, CSV, LaTeX, Markdown, or other\n> plain text where small inline edits can be hard to spot.\n\nVery good explanation. I think it's a good idea.\n\n>\n> Added `w - print the current hunk with word-diff` during hunk selection\n  ^^^^^^^\nNit: following Documentation/SubmittingPatches, try to use an\nimperative mood to write what has been done.\n\n> to re-display the current hunk using `--word-diff`. This provides a\n> clearer inline view of changes without modifying the hunk or affecting\n> how patches are applied or staged.\n>\n> Signed-off-by: Javier Bassi <javierbassi@gmail.com>\n> ---\n>  Documentation/git-add.adoc |   1 +\n>  add-patch.c                | 105 ++++++++++++++++++++++++++++++++++++-\n>  t/t3701-add-interactive.sh |  59 +++++++++++++--------\n>  3 files changed, 142 insertions(+), 23 deletions(-)\n>\n> diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc\n> index 941135dc63..d2ee1cf9a9 100644\n> --- a/Documentation/git-add.adoc\n> +++ b/Documentation/git-add.adoc\n> @@ -351,6 +351,7 @@ patch::\n>         K - go to the previous hunk, roll over at the top\n>         s - split the current hunk into smaller hunks\n>         e - manually edit the current hunk\n> +       w - print the current hunk with word-diff\n>         p - print the current hunk\n>         P - print the current hunk using the pager\n>         ? - print help\n> diff --git a/add-patch.c b/add-patch.c\n> index f27edcbe8d..0364f4bc97 100644\n> --- a/add-patch.c\n> +++ b/add-patch.c\n> @@ -7,6 +7,7 @@\n>  #include \"commit.h\"\n>  #include \"config.h\"\n>  #include \"diff.h\"\n> +#include \"diffcore.h\"\n>  #include \"editor.h\"\n>  #include \"environment.h\"\n>  #include \"gettext.h\"\n> @@ -1508,6 +1509,105 @@ static void summarize_hunk(struct add_p_state *s, struct hunk *hunk,\n>         strbuf_complete_line(out);\n>  }\n>\n> +static void trim_trailing_lf(struct strbuf *buf)\n> +{\n> +       if (buf->len && buf->buf[buf->len - 1] == '\\n')\n> +               strbuf_setlen(buf, buf->len - 1);\n> +}\n> +\n> +static void add_word_diff_line(struct strbuf *old, struct strbuf *new,\n> +                              const char *line, size_t len, char marker)\n> +{\n> +       if (marker == '-' || marker == '+' || *line == ' ') {\n> +               line++;\n> +               len--;\n> +       }\n\nMaybe a tiny comment here would help, to know why '*line' is being\nchecked here instead of 'marker'. They seem the same and one has to go\nto marker declaration and see the comment at 'normalize_marker()'\n\n  /* Empty context lines may omit the leading ' ' */\n\n> +\n> +       if (marker != '+')\n> +               strbuf_add(old, line, len);\n> +       if (marker != '-')\n> +               strbuf_add(new, line, len);\n> +}\n> +\n> +static void build_word_diff_files(struct add_p_state *s, struct hunk *hunk,\n> +                                 struct strbuf *old, struct strbuf *new)\n> +{\n> +       size_t i;\n> +       char last_marker = '\\0';\n> +\n> +       for (i = hunk->start; i < hunk->end; i = find_next_line(&s->plain, i)) {\n> +               size_t next = find_next_line(&s->plain, i);\n> +               char marker = normalize_marker(s->plain.buf + i);\n> +\n> +               if (marker == '\\\\') {\n> +                       if (last_marker != '+')\n> +                               trim_trailing_lf(old);\n> +                       if (last_marker != '-')\n> +                               trim_trailing_lf(new);\n> +                       continue;\n> +               }\n\nHere we check about \"\\No newline at end of file\", after this point I\nbelieve that 'buf->buf[buf->len - 1] == '\\n'' will always be true.\nSame should be for 'buf->len' because \"\\No newline at end of file\"\nshouldn't come first and a '+' '-' line should have been added on a\nprevious iteration, but the check it's fine, just in case I'm wrong.\n\nWhat I want to point out is, is the 'trim_trailing_lf' function\nnecessary? It's only called in the same place and it carries a check\nthat could be on the caller instead, leaving  the function only with\n'strbuf_setlen(buf, buf->len - 1);\" making sense to inline it at this\npoint.\nYou could keep the buf->len check:\n\n  if (marker == '\\\\') {\n          if (last_marker != '+' && old->len)\n                   strbuf_setlen(old, old->len - 1);\n          if (last_marker != '-' && new->len)\n                   strbuf_setlen(new, new->len - 1);\n          continue;\n  }\n\n> +\n> +               if (marker != ' ' && marker != '-' && marker != '+')\n> +                       BUG(\"unhandled diff marker: '%c'\", marker);\n> +\n> +               add_word_diff_line(old, new, s->plain.buf + i, next - i,\n> +                                  marker);\n> +               last_marker = marker;\n> +       }\n> +}\n> +\n> +static struct diff_filespec *word_diff_filespec(struct repository *r,\n> +                                               const char *name,\n> +                                               struct strbuf *buf)\n> +{\n> +       struct diff_filespec *spec = alloc_filespec(name);\n> +       size_t size;\n> +\n> +       fill_filespec(spec, null_oid(r->hash_algo), 0, 0100644);\n> +       spec->data = strbuf_detach(buf, &size);\n> +       spec->size = size;\n> +       spec->should_free = 1;\n> +       spec->is_stdin = 1;\n> +\n> +       return spec;\n> +}\n> +\n> +static void show_hunk_word_diff(struct add_p_state *s, struct hunk *hunk,\n> +                               int colored)\n> +{\n> +       struct hunk_header *header = &hunk->header;\n> +       struct strbuf old = STRBUF_INIT, new = STRBUF_INIT;\n> +       struct diff_options opts;\n> +       struct diff_queue_struct queue;\n> +\n> +       if (!header->old_offset && !header->new_offset) {\n\nNit: a comment here would help to understand this early return\n\n> +               strbuf_reset(&s->buf);\n> +               render_hunk(s, hunk, 0, colored, &s->buf);\n> +               fputs(s->buf.buf, stdout);\n> +               return;\n> +       }\n> +\n> +       build_word_diff_files(s, hunk, &old, &new);\n> +\n> +       repo_diff_setup(s->r, &opts);\n> +       opts.output_format = DIFF_FORMAT_PATCH;\n> +       opts.use_color = colored ? s->cfg.use_color_diff : GIT_COLOR_NEVER;\n> +       opts.word_diff = DIFF_WORDS_PLAIN;\n> +       opts.context = header->old_count > header->new_count ?\n> +               header->old_count : header->new_count;\n> +       opts.flags.suppress_diff_headers = 1;\n> +       diff_setup_done(&opts);\n> +\n> +       memcpy(&queue, &diff_queued_diff, sizeof(diff_queued_diff));\n> +       diff_queue_init(&diff_queued_diff);\n> +       diff_queue(&diff_queued_diff,\n> +                  word_diff_filespec(s->r, \"a\", &old),\n> +                  word_diff_filespec(s->r, \"b\", &new));\n> +       diffcore_std(&opts);\n> +       diff_flush(&opts);\n> +       memcpy(&diff_queued_diff, &queue, sizeof(diff_queued_diff));\n> +}\n> +\n>  #define DISPLAY_HUNKS_LINES 20\n>  static size_t display_hunks(struct add_p_state *s,\n>                             struct file_diff *file_diff, size_t start_index)\n> @@ -1540,6 +1640,7 @@ N_(\"j - go to the next undecided hunk, roll over at the bottom\\n\"\n>     \"/ - search for a hunk matching the given regex\\n\"\n>     \"s - split the current hunk into smaller hunks\\n\"\n>     \"e - manually edit the current hunk\\n\"\n> +   \"w - print the current hunk with word-diff\\n\"\n>     \"p - print the current hunk\\n\"\n>     \"P - print the current hunk using the pager\\n\"\n>     \"> - go to the next file, roll over at the bottom\\n\"\n> @@ -1731,7 +1832,7 @@ static size_t patch_update_file(struct add_p_state *s,\n>                                 permitted |= ALLOW_GOTO_PREVIOUS_FILE;\n>                                 strbuf_addstr(&s->buf, \",<\");\n>                         }\n> -                       strbuf_addstr(&s->buf, \",p,P\");\n> +                       strbuf_addstr(&s->buf, \",w,p,P\");\n>                 }\n>                 if (file_diff->deleted)\n>                         prompt_mode_type = PROMPT_DELETION;\n> @@ -1953,6 +2054,8 @@ static size_t patch_update_file(struct add_p_state *s,\n>                                 hunk->use = USE_HUNK;\n>                                 goto soft_increment;\n>                         }\n> +               } else if (s->answer.buf[0] == 'w') {\n> +                       show_hunk_word_diff(s, hunk, colored);\n>                 } else if (ch == 'p') {\n>                         rendered_hunk_index = -1;\n>                         use_pager = (s->answer.buf[0] == 'P') ? 1 : 0;\n> diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\n> index 6e120a4001..e1ce98d62b 100755\n> --- a/t/t3701-add-interactive.sh\n> +++ b/t/t3701-add-interactive.sh\n> @@ -48,8 +48,8 @@ test_expect_success 'unknown command' '\n>         git add -N command &&\n>         git diff command >expect &&\n>         cat >>expect <<-EOF &&\n> -       (1/1) Stage addition [y,n,q,a,d,e,p,P,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)\n> -       (1/1) Stage addition [y,n,q,a,d,e,p,P,?]?$SP\n> +       (1/1) Stage addition [y,n,q,a,d,e,w,p,P,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)\n> +       (1/1) Stage addition [y,n,q,a,d,e,w,p,P,?]?$SP\n>         EOF\n>         git add -p -- command <command >actual 2>&1 &&\n>         test_cmp expect actual\n> @@ -332,9 +332,9 @@ test_expect_success 'different prompts for mode change/deleted' '\n>         git -c core.filemode=true add -p >actual &&\n>         sed -n \"s/^\\(([0-9/]*) Stage .*?\\).*/\\1/p\" actual >actual.filtered &&\n>         cat >expect <<-\\EOF &&\n> -       (1/1) Stage deletion [y,n,q,a,d,p,P,?]?\n> -       (1/2) Stage mode change [y,n,q,a,d,k,K,j,J,g,/,p,P,?]?\n> -       (2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,P,?]?\n> +       (1/1) Stage deletion [y,n,q,a,d,w,p,P,?]?\n> +       (1/2) Stage mode change [y,n,q,a,d,k,K,j,J,g,/,w,p,P,?]?\n> +       (2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,w,p,P,?]?\n>         EOF\n>         test_cmp expect actual.filtered\n>  '\n> @@ -521,13 +521,13 @@ test_expect_success 'split hunk setup' '\n>  test_expect_success 'goto hunk 1 with \"g 1\"' '\n>         test_when_finished \"git reset\" &&\n>         tr _ \" \" >expect <<-EOF &&\n> -       (2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,P,?]? + 1:  -1,2 +1,3          +15\n> +       (2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,w,p,P,?]? + 1:  -1,2 +1,3          +15\n>         _ 2:  -2,4 +3,8          +21\n>         go to which hunk? @@ -1,2 +1,3 @@\n>         _10\n>         +15\n>         _20\n> -       (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_\n> +       (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]?_\n>         EOF\n>         test_write_lines s y g 1 | git add -p >actual &&\n>         tail -n 7 <actual >actual.trimmed &&\n> @@ -540,7 +540,7 @@ test_expect_success 'goto hunk 1 with \"g1\"' '\n>         _10\n>         +15\n>         _20\n> -       (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_\n> +       (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]?_\n>         EOF\n>         test_write_lines s y g1 | git add -p >actual &&\n>         tail -n 4 <actual >actual.trimmed &&\n> @@ -550,11 +550,11 @@ test_expect_success 'goto hunk 1 with \"g1\"' '\n>  test_expect_success 'navigate to hunk via regex /pattern' '\n>         test_when_finished \"git reset\" &&\n>         tr _ \" \" >expect <<-EOF &&\n> -       (2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,P,?]? @@ -1,2 +1,3 @@\n> +       (2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,w,p,P,?]? @@ -1,2 +1,3 @@\n>         _10\n>         +15\n>         _20\n> -       (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_\n> +       (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]?_\n>         EOF\n>         test_write_lines s y /1,2 | git add -p >actual &&\n>         tail -n 5 <actual >actual.trimmed &&\n> @@ -567,7 +567,7 @@ test_expect_success 'navigate to hunk via regex / pattern' '\n>         _10\n>         +15\n>         _20\n> -       (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_\n> +       (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]?_\n>         EOF\n>         test_write_lines s y / 1,2 | git add -p >actual &&\n>         tail -n 4 <actual >actual.trimmed &&\n> @@ -579,27 +579,42 @@ test_expect_success 'print again the hunk' '\n>         tr _ \" \" >expect <<-EOF &&\n>         +15\n>          20\n> -       (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? @@ -1,2 +1,3 @@\n> +       (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]? @@ -1,2 +1,3 @@\n>          10\n>         +15\n>          20\n> -       (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_\n> +       (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]?_\n>         EOF\n>         test_write_lines s y g 1 p | git add -p >actual &&\n>         tail -n 7 <actual >actual.trimmed &&\n>         test_cmp expect actual.trimmed\n>  '\n>\n> +test_expect_success 'print hunk with word-diff' '\n> +       test_when_finished \"rm -rf word-diff-repo\" &&\n> +       git init word-diff-repo &&\n> +       (\n> +               cd word-diff-repo &&\n> +               test_write_lines \"alpha old beta\" context >word-diff &&\n> +               git add word-diff &&\n> +               git commit -m word-diff &&\n> +               test_write_lines \"alpha new beta\" context >word-diff &&\n> +               test_write_lines w n | git add -p word-diff >actual &&\n> +               test_grep \"alpha \\\\[-old-\\\\]{+new+} beta\" actual &&\n> +               git diff --cached --exit-code\n> +       )\n> +'\n> +\n>  test_expect_success TTY 'print again the hunk (PAGER)' '\n>         test_when_finished \"git reset\" &&\n>         cat >expect <<-EOF &&\n>         <GREEN>+<RESET><GREEN>15<RESET>\n>          20<RESET>\n> -       <BOLD;BLUE>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>PAGER <CYAN>@@ -1,2 +1,3 @@<RESET>\n> +       <BOLD;BLUE>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]? <RESET>PAGER <CYAN>@@ -1,2 +1,3 @@<RESET>\n>         PAGER  10<RESET>\n>         PAGER <GREEN>+<RESET><GREEN>15<RESET>\n>         PAGER  20<RESET>\n> -       <BOLD;BLUE>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>\n> +       <BOLD;BLUE>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]? <RESET>\n>         EOF\n>         test_write_lines s y g 1 P |\n>         (\n> @@ -796,21 +811,21 @@ test_expect_success 'colors can be overridden' '\n>         <BLUE>+<RESET><BLUE>new<RESET>\n>         <CYAN> more-context<RESET>\n>         <BLUE>+<RESET><BLUE>another-one<RESET>\n> -       <YELLOW>(1/1) Stage this hunk [y,n,q,a,d,s,e,p,P,?]? <RESET><BOLD>Split into 2 hunks.<RESET>\n> +       <YELLOW>(1/1) Stage this hunk [y,n,q,a,d,s,e,w,p,P,?]? <RESET><BOLD>Split into 2 hunks.<RESET>\n>         <MAGENTA>@@ -1,3 +1,3 @@<RESET>\n>         <CYAN> context<RESET>\n>         <BOLD>-old<RESET>\n>         <BLUE>+<RESET><BLUE>new<RESET>\n>         <CYAN> more-context<RESET>\n> -       <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET>\n> +       <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET>\n>         <CYAN> more-context<RESET>\n>         <BLUE>+<RESET><BLUE>another-one<RESET>\n> -       <YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,P,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET>\n> +       <YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,w,p,P,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET>\n>         <CYAN> context<RESET>\n>         <BOLD>-old<RESET>\n>         <BLUE>+new<RESET>\n>         <CYAN> more-context<RESET>\n> -       <YELLOW>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>\n> +       <YELLOW>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]? <RESET>\n>         EOF\n>         test_cmp expect actual\n>  '\n> @@ -1424,9 +1439,9 @@ test_expect_success 'invalid option s is rejected' '\n>         test_write_lines j s q | git add -p >out &&\n>         sed -ne \"s/ @@.*//\" -e \"s/ \\$//\" -e \"/^(/p\" <out >actual &&\n>         cat >expect <<-EOF &&\n> -       (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,s,e,p,P,?]?\n> -       (2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? Sorry, cannot split this hunk\n> -       (2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?\n> +       (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,s,e,w,p,P,?]?\n> +       (2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]? Sorry, cannot split this hunk\n> +       (2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]?\n>         EOF\n>         test_cmp expect actual\n>  '\n> --\n> 2.54.0\n>\n>\n\nI tried and found out that if a character is contiguous to a word diff\nit will be carried as part of the diff. e.g.:\n\n  diff --git a/file b/file\n  index f33f47f..93dd39c 100644\n  --- a/file\n  +++ b/file\n  @@ -1 +1 @@\n  -foo, bar\n  +baz, bar\n  (1/1) Stage this hunk [y,n,q,a,d,e,w,p,P,?]? w\n  @@ -1 +1 @@\n  [-foo,-]{+baz,+} bar\n\nthe ',' remains at the same place but became part of the diff which adds noise.\n\nRegards,\n--\nPablo\n"},{"id":"542845","messageId":"e640ac2d-9e46-4f1e-9aef-deac80c65361@gmail.com","threadId":"65604","inReplyTo":"20260506235459.529862-1-javierbassi@gmail.com","subject":"Re: [PATCH] add -p: introduce 'w' command to view hunk with --word-diff","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-05-07T13:24:24Z","receivedAt":"2026-05-07T13:24:24Z","isPatch":true,"body":"Hi Javier\n\nOn 07/05/2026 00:54, Javier Bassi wrote:\n> When using `git add --patch`, reviewing changes in long lines can be\n> difficult with the default line-based diff. This is particularly\n> noticeable in formats such as JSONP, CSV, LaTeX, Markdown, or other\n> plain text where small inline edits can be hard to spot.\n> \n> Added `w - print the current hunk with word-diff` during hunk selection\n> to re-display the current hunk using `--word-diff`. This provides a\n> clearer inline view of changes without modifying the hunk or affecting\n> how patches are applied or staged.\n\nI think this is useful. While one can set interactive.diffFilter to \nhighlight intraline differences with \"diff-highlight\" or \"delta\" they do \nnot cope well if the text has been reflowed.\n\nI think the approach of using the current hunk text, rather than running \n\"git diff --diff-words\" is sensible because it works correctly if the \nuser has edited the hunk and it makes it easy to handle hunks that have \nbeen split. It would be good to propagate the filename when creating the \nfilespec so that we use the correct word diff regex for the file. We \nshould also propagate the context and inter-hunk context settings from \nstruct interactive_options. It would also be a good idea to reuse the \nhunk header from the unified diff so that the line numbers and hunk \nfragment are correct.\n\nThanks\n\nPhillip\n\n> Signed-off-by: Javier Bassi <javierbassi@gmail.com>\n> ---\n>   Documentation/git-add.adoc |   1 +\n>   add-patch.c                | 105 ++++++++++++++++++++++++++++++++++++-\n>   t/t3701-add-interactive.sh |  59 +++++++++++++--------\n>   3 files changed, 142 insertions(+), 23 deletions(-)\n> \n> diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc\n> index 941135dc63..d2ee1cf9a9 100644\n> --- a/Documentation/git-add.adoc\n> +++ b/Documentation/git-add.adoc\n> @@ -351,6 +351,7 @@ patch::\n>          K - go to the previous hunk, roll over at the top\n>          s - split the current hunk into smaller hunks\n>          e - manually edit the current hunk\n> +       w - print the current hunk with word-diff\n>          p - print the current hunk\n>          P - print the current hunk using the pager\n>          ? - print help\n> diff --git a/add-patch.c b/add-patch.c\n> index f27edcbe8d..0364f4bc97 100644\n> --- a/add-patch.c\n> +++ b/add-patch.c\n> @@ -7,6 +7,7 @@\n>   #include \"commit.h\"\n>   #include \"config.h\"\n>   #include \"diff.h\"\n> +#include \"diffcore.h\"\n>   #include \"editor.h\"\n>   #include \"environment.h\"\n>   #include \"gettext.h\"\n> @@ -1508,6 +1509,105 @@ static void summarize_hunk(struct add_p_state *s, struct hunk *hunk,\n>   \tstrbuf_complete_line(out);\n>   }\n>   \n> +static void trim_trailing_lf(struct strbuf *buf)\n> +{\n> +\tif (buf->len && buf->buf[buf->len - 1] == '\\n')\n> +\t\tstrbuf_setlen(buf, buf->len - 1);\n> +}\n> +\n> +static void add_word_diff_line(struct strbuf *old, struct strbuf *new,\n> +\t\t\t       const char *line, size_t len, char marker)\n> +{\n> +\tif (marker == '-' || marker == '+' || *line == ' ') {\n> +\t\tline++;\n> +\t\tlen--;\n> +\t}\n> +\n> +\tif (marker != '+')\n> +\t\tstrbuf_add(old, line, len);\n> +\tif (marker != '-')\n> +\t\tstrbuf_add(new, line, len);\n> +}\n> +\n> +static void build_word_diff_files(struct add_p_state *s, struct hunk *hunk,\n> +\t\t\t\t  struct strbuf *old, struct strbuf *new)\n> +{\n> +\tsize_t i;\n> +\tchar last_marker = '\\0';\n> +\n> +\tfor (i = hunk->start; i < hunk->end; i = find_next_line(&s->plain, i)) {\n> +\t\tsize_t next = find_next_line(&s->plain, i);\n> +\t\tchar marker = normalize_marker(s->plain.buf + i);\n> +\n> +\t\tif (marker == '\\\\') {\n> +\t\t\tif (last_marker != '+')\n> +\t\t\t\ttrim_trailing_lf(old);\n> +\t\t\tif (last_marker != '-')\n> +\t\t\t\ttrim_trailing_lf(new);\n> +\t\t\tcontinue;\n> +\t\t}\n> +\n> +\t\tif (marker != ' ' && marker != '-' && marker != '+')\n> +\t\t\tBUG(\"unhandled diff marker: '%c'\", marker);\n> +\n> +\t\tadd_word_diff_line(old, new, s->plain.buf + i, next - i,\n> +\t\t\t\t   marker);\n> +\t\tlast_marker = marker;\n> +\t}\n> +}\n> +\n> +static struct diff_filespec *word_diff_filespec(struct repository *r,\n> +\t\t\t\t\t\tconst char *name,\n> +\t\t\t\t\t\tstruct strbuf *buf)\n> +{\n> +\tstruct diff_filespec *spec = alloc_filespec(name);\n> +\tsize_t size;\n> +\n> +\tfill_filespec(spec, null_oid(r->hash_algo), 0, 0100644);\n> +\tspec->data = strbuf_detach(buf, &size);\n> +\tspec->size = size;\n> +\tspec->should_free = 1;\n> +\tspec->is_stdin = 1;\n> +\n> +\treturn spec;\n> +}\n> +\n> +static void show_hunk_word_diff(struct add_p_state *s, struct hunk *hunk,\n> +\t\t\t\tint colored)\n> +{\n> +\tstruct hunk_header *header = &hunk->header;\n> +\tstruct strbuf old = STRBUF_INIT, new = STRBUF_INIT;\n> +\tstruct diff_options opts;\n> +\tstruct diff_queue_struct queue;\n> +\n> +\tif (!header->old_offset && !header->new_offset) {\n> +\t\tstrbuf_reset(&s->buf);\n> +\t\trender_hunk(s, hunk, 0, colored, &s->buf);\n> +\t\tfputs(s->buf.buf, stdout);\n> +\t\treturn;\n> +\t}\n> +\n> +\tbuild_word_diff_files(s, hunk, &old, &new);\n> +\n> +\trepo_diff_setup(s->r, &opts);\n> +\topts.output_format = DIFF_FORMAT_PATCH;\n> +\topts.use_color = colored ? s->cfg.use_color_diff : GIT_COLOR_NEVER;\n> +\topts.word_diff = DIFF_WORDS_PLAIN;\n> +\topts.context = header->old_count > header->new_count ?\n> +\t\theader->old_count : header->new_count;\n> +\topts.flags.suppress_diff_headers = 1;\n> +\tdiff_setup_done(&opts);\n> +\n> +\tmemcpy(&queue, &diff_queued_diff, sizeof(diff_queued_diff));\n> +\tdiff_queue_init(&diff_queued_diff);\n> +\tdiff_queue(&diff_queued_diff,\n> +\t\t   word_diff_filespec(s->r, \"a\", &old),\n> +\t\t   word_diff_filespec(s->r, \"b\", &new));\n> +\tdiffcore_std(&opts);\n> +\tdiff_flush(&opts);\n> +\tmemcpy(&diff_queued_diff, &queue, sizeof(diff_queued_diff));\n> +}\n> +\n>   #define DISPLAY_HUNKS_LINES 20\n>   static size_t display_hunks(struct add_p_state *s,\n>   \t\t\t    struct file_diff *file_diff, size_t start_index)\n> @@ -1540,6 +1640,7 @@ N_(\"j - go to the next undecided hunk, roll over at the bottom\\n\"\n>      \"/ - search for a hunk matching the given regex\\n\"\n>      \"s - split the current hunk into smaller hunks\\n\"\n>      \"e - manually edit the current hunk\\n\"\n> +   \"w - print the current hunk with word-diff\\n\"\n>      \"p - print the current hunk\\n\"\n>      \"P - print the current hunk using the pager\\n\"\n>      \"> - go to the next file, roll over at the bottom\\n\"\n> @@ -1731,7 +1832,7 @@ static size_t patch_update_file(struct add_p_state *s,\n>   \t\t\t\tpermitted |= ALLOW_GOTO_PREVIOUS_FILE;\n>   \t\t\t\tstrbuf_addstr(&s->buf, \",<\");\n>   \t\t\t}\n> -\t\t\tstrbuf_addstr(&s->buf, \",p,P\");\n> +\t\t\tstrbuf_addstr(&s->buf, \",w,p,P\");\n>   \t\t}\n>   \t\tif (file_diff->deleted)\n>   \t\t\tprompt_mode_type = PROMPT_DELETION;\n> @@ -1953,6 +2054,8 @@ static size_t patch_update_file(struct add_p_state *s,\n>   \t\t\t\thunk->use = USE_HUNK;\n>   \t\t\t\tgoto soft_increment;\n>   \t\t\t}\n> +\t\t} else if (s->answer.buf[0] == 'w') {\n> +\t\t\tshow_hunk_word_diff(s, hunk, colored);\n>   \t\t} else if (ch == 'p') {\n>   \t\t\trendered_hunk_index = -1;\n>   \t\t\tuse_pager = (s->answer.buf[0] == 'P') ? 1 : 0;\n> diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\n> index 6e120a4001..e1ce98d62b 100755\n> --- a/t/t3701-add-interactive.sh\n> +++ b/t/t3701-add-interactive.sh\n> @@ -48,8 +48,8 @@ test_expect_success 'unknown command' '\n>   \tgit add -N command &&\n>   \tgit diff command >expect &&\n>   \tcat >>expect <<-EOF &&\n> -\t(1/1) Stage addition [y,n,q,a,d,e,p,P,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)\n> -\t(1/1) Stage addition [y,n,q,a,d,e,p,P,?]?$SP\n> +\t(1/1) Stage addition [y,n,q,a,d,e,w,p,P,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help)\n> +\t(1/1) Stage addition [y,n,q,a,d,e,w,p,P,?]?$SP\n>   \tEOF\n>   \tgit add -p -- command <command >actual 2>&1 &&\n>   \ttest_cmp expect actual\n> @@ -332,9 +332,9 @@ test_expect_success 'different prompts for mode change/deleted' '\n>   \tgit -c core.filemode=true add -p >actual &&\n>   \tsed -n \"s/^\\(([0-9/]*) Stage .*?\\).*/\\1/p\" actual >actual.filtered &&\n>   \tcat >expect <<-\\EOF &&\n> -\t(1/1) Stage deletion [y,n,q,a,d,p,P,?]?\n> -\t(1/2) Stage mode change [y,n,q,a,d,k,K,j,J,g,/,p,P,?]?\n> -\t(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,P,?]?\n> +\t(1/1) Stage deletion [y,n,q,a,d,w,p,P,?]?\n> +\t(1/2) Stage mode change [y,n,q,a,d,k,K,j,J,g,/,w,p,P,?]?\n> +\t(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,w,p,P,?]?\n>   \tEOF\n>   \ttest_cmp expect actual.filtered\n>   '\n> @@ -521,13 +521,13 @@ test_expect_success 'split hunk setup' '\n>   test_expect_success 'goto hunk 1 with \"g 1\"' '\n>   \ttest_when_finished \"git reset\" &&\n>   \ttr _ \" \" >expect <<-EOF &&\n> -\t(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,P,?]? + 1:  -1,2 +1,3          +15\n> +\t(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,w,p,P,?]? + 1:  -1,2 +1,3          +15\n>   \t_ 2:  -2,4 +3,8          +21\n>   \tgo to which hunk? @@ -1,2 +1,3 @@\n>   \t_10\n>   \t+15\n>   \t_20\n> -\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_\n> +\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]?_\n>   \tEOF\n>   \ttest_write_lines s y g 1 | git add -p >actual &&\n>   \ttail -n 7 <actual >actual.trimmed &&\n> @@ -540,7 +540,7 @@ test_expect_success 'goto hunk 1 with \"g1\"' '\n>   \t_10\n>   \t+15\n>   \t_20\n> -\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_\n> +\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]?_\n>   \tEOF\n>   \ttest_write_lines s y g1 | git add -p >actual &&\n>   \ttail -n 4 <actual >actual.trimmed &&\n> @@ -550,11 +550,11 @@ test_expect_success 'goto hunk 1 with \"g1\"' '\n>   test_expect_success 'navigate to hunk via regex /pattern' '\n>   \ttest_when_finished \"git reset\" &&\n>   \ttr _ \" \" >expect <<-EOF &&\n> -\t(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,P,?]? @@ -1,2 +1,3 @@\n> +\t(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,w,p,P,?]? @@ -1,2 +1,3 @@\n>   \t_10\n>   \t+15\n>   \t_20\n> -\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_\n> +\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]?_\n>   \tEOF\n>   \ttest_write_lines s y /1,2 | git add -p >actual &&\n>   \ttail -n 5 <actual >actual.trimmed &&\n> @@ -567,7 +567,7 @@ test_expect_success 'navigate to hunk via regex / pattern' '\n>   \t_10\n>   \t+15\n>   \t_20\n> -\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_\n> +\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]?_\n>   \tEOF\n>   \ttest_write_lines s y / 1,2 | git add -p >actual &&\n>   \ttail -n 4 <actual >actual.trimmed &&\n> @@ -579,27 +579,42 @@ test_expect_success 'print again the hunk' '\n>   \ttr _ \" \" >expect <<-EOF &&\n>   \t+15\n>   \t 20\n> -\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? @@ -1,2 +1,3 @@\n> +\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]? @@ -1,2 +1,3 @@\n>   \t 10\n>   \t+15\n>   \t 20\n> -\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_\n> +\t(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]?_\n>   \tEOF\n>   \ttest_write_lines s y g 1 p | git add -p >actual &&\n>   \ttail -n 7 <actual >actual.trimmed &&\n>   \ttest_cmp expect actual.trimmed\n>   '\n>   \n> +test_expect_success 'print hunk with word-diff' '\n> +\ttest_when_finished \"rm -rf word-diff-repo\" &&\n> +\tgit init word-diff-repo &&\n> +\t(\n> +\t\tcd word-diff-repo &&\n> +\t\ttest_write_lines \"alpha old beta\" context >word-diff &&\n> +\t\tgit add word-diff &&\n> +\t\tgit commit -m word-diff &&\n> +\t\ttest_write_lines \"alpha new beta\" context >word-diff &&\n> +\t\ttest_write_lines w n | git add -p word-diff >actual &&\n> +\t\ttest_grep \"alpha \\\\[-old-\\\\]{+new+} beta\" actual &&\n> +\t\tgit diff --cached --exit-code\n> +\t)\n> +'\n> +\n>   test_expect_success TTY 'print again the hunk (PAGER)' '\n>   \ttest_when_finished \"git reset\" &&\n>   \tcat >expect <<-EOF &&\n>   \t<GREEN>+<RESET><GREEN>15<RESET>\n>   \t 20<RESET>\n> -\t<BOLD;BLUE>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>PAGER <CYAN>@@ -1,2 +1,3 @@<RESET>\n> +\t<BOLD;BLUE>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]? <RESET>PAGER <CYAN>@@ -1,2 +1,3 @@<RESET>\n>   \tPAGER  10<RESET>\n>   \tPAGER <GREEN>+<RESET><GREEN>15<RESET>\n>   \tPAGER  20<RESET>\n> -\t<BOLD;BLUE>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>\n> +\t<BOLD;BLUE>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]? <RESET>\n>   \tEOF\n>   \ttest_write_lines s y g 1 P |\n>   \t(\n> @@ -796,21 +811,21 @@ test_expect_success 'colors can be overridden' '\n>   \t<BLUE>+<RESET><BLUE>new<RESET>\n>   \t<CYAN> more-context<RESET>\n>   \t<BLUE>+<RESET><BLUE>another-one<RESET>\n> -\t<YELLOW>(1/1) Stage this hunk [y,n,q,a,d,s,e,p,P,?]? <RESET><BOLD>Split into 2 hunks.<RESET>\n> +\t<YELLOW>(1/1) Stage this hunk [y,n,q,a,d,s,e,w,p,P,?]? <RESET><BOLD>Split into 2 hunks.<RESET>\n>   \t<MAGENTA>@@ -1,3 +1,3 @@<RESET>\n>   \t<CYAN> context<RESET>\n>   \t<BOLD>-old<RESET>\n>   \t<BLUE>+<RESET><BLUE>new<RESET>\n>   \t<CYAN> more-context<RESET>\n> -\t<YELLOW>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET>\n> +\t<YELLOW>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET>\n>   \t<CYAN> more-context<RESET>\n>   \t<BLUE>+<RESET><BLUE>another-one<RESET>\n> -\t<YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,p,P,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET>\n> +\t<YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,J,g,/,e,w,p,P,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET>\n>   \t<CYAN> context<RESET>\n>   \t<BOLD>-old<RESET>\n>   \t<BLUE>+new<RESET>\n>   \t<CYAN> more-context<RESET>\n> -\t<YELLOW>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>\n> +\t<YELLOW>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]? <RESET>\n>   \tEOF\n>   \ttest_cmp expect actual\n>   '\n> @@ -1424,9 +1439,9 @@ test_expect_success 'invalid option s is rejected' '\n>   \ttest_write_lines j s q | git add -p >out &&\n>   \tsed -ne \"s/ @@.*//\" -e \"s/ \\$//\" -e \"/^(/p\" <out >actual &&\n>   \tcat >expect <<-EOF &&\n> -\t(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,s,e,p,P,?]?\n> -\t(2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? Sorry, cannot split this hunk\n> -\t(2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?\n> +\t(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,s,e,w,p,P,?]?\n> +\t(2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]? Sorry, cannot split this hunk\n> +\t(2/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,w,p,P,?]?\n>   \tEOF\n>   \ttest_cmp expect actual\n>   '\n\n"},{"id":"542846","messageId":"1f811deb-7cbb-4fe0-ab40-49274b1db165@gmail.com","threadId":"65604","inReplyTo":"CAN5EUNRT7V3BrtyU0UYwGVnJ51LWSsNi1OnzMB5WL=w8vhKmrw@mail.gmail.com","subject":"Re: [PATCH] add -p: introduce 'w' command to view hunk with --word-diff","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-05-07T13:24:31Z","receivedAt":"2026-05-07T13:24:30Z","isPatch":true,"body":"On 07/05/2026 08:55, Pablo wrote:\n> El jue, 7 may 2026 a las 1:58, Javier Bassi (<javierbassi@gmail.com>) escribió:\n>>\n>> +static void add_word_diff_line(struct strbuf *old, struct strbuf *new,\n>> +                              const char *line, size_t len, char marker)\n>> +{\n>> +       if (marker == '-' || marker == '+' || *line == ' ') {\n>> +               line++;\n>> +               len--;\n>> +       }\n> \n> Maybe a tiny comment here would help, to know why '*line' is being\n> checked here instead of 'marker'. They seem the same and one has to go\n> to marker declaration and see the comment at 'normalize_marker()'\n> \n>    /* Empty context lines may omit the leading ' ' */\n\nThat's a good point - it might be clearer to use\n\n\tif (marker == *line) {\n\t\tline++;\n\t\tlen--;\n\t}\n\ninstead. That also trims lines starting with '\\' but that shouldn't \nmatter as the code should be checking \"marker\" rather than \"line\".\n\n>> +\n>> +               if (marker == '\\\\') {\n>> +                       if (last_marker != '+')\n>> +                               trim_trailing_lf(old);\n>> +                       if (last_marker != '-')\n>> +                               trim_trailing_lf(new);\n>> +                       continue;\n>> +               }\n> \n> Here we check about \"\\No newline at end of file\", after this point I\n> believe that 'buf->buf[buf->len - 1] == '\\n'' will always be true.\n> Same should be for 'buf->len' because \"\\No newline at end of file\"\n> shouldn't come first and a '+' '-' line should have been added on a\n> previous iteration, but the check it's fine, just in case I'm wrong.\n> \n> What I want to point out is, is the 'trim_trailing_lf' function\n> necessary? It's only called in the same place and it carries a check\n> that could be on the caller instead, leaving  the function only with\n> 'strbuf_setlen(buf, buf->len - 1);\" making sense to inline it at this\n> point.\n> You could keep the buf->len check:\n> \n>    if (marker == '\\\\') {\n>            if (last_marker != '+' && old->len)\n>                     strbuf_setlen(old, old->len - 1);\n>            if (last_marker != '-' && new->len)\n>                     strbuf_setlen(new, new->len - 1);\n>            continue;\n>    }\n\nShould we be trimming '\\r\\n' if the file has dos style line endings?\n\nThanks\n\nPhillip\n\n"},{"id":"542850","messageId":"CALnO6CCsOMyLxAhGW10L4duaahUORwedfmtdpY3zDGpQcZoP2Q@mail.gmail.com","threadId":"65604","inReplyTo":"e640ac2d-9e46-4f1e-9aef-deac80c65361@gmail.com","subject":"Re: [PATCH] add -p: introduce 'w' command to view hunk with --word-diff","fromName":"D. Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-05-07T14:39:03Z","receivedAt":"2026-05-07T14:39:15Z","isPatch":true,"body":"On Thu, May 7, 2026 at 9:28 AM Phillip Wood <phillip.wood123@gmail.com> wrote:\n>\n> Hi Javier\n>\n> On 07/05/2026 00:54, Javier Bassi wrote:\n> > When using `git add --patch`, reviewing changes in long lines can be\n> > difficult with the default line-based diff. This is particularly\n> > noticeable in formats such as JSONP, CSV, LaTeX, Markdown, or other\n> > plain text where small inline edits can be hard to spot.\n> >\n> > Added `w - print the current hunk with word-diff` during hunk selection\n> > to re-display the current hunk using `--word-diff`. This provides a\n> > clearer inline view of changes without modifying the hunk or affecting\n> > how patches are applied or staged.\n>\n> I think this is useful. While one can set interactive.diffFilter to\n> highlight intraline differences with \"diff-highlight\" or \"delta\" they do\n> not cope well if the text has been reflowed.\n\nI concur, though I wonder what other diff options would be useful. I\ncan imagine a world where \"add --patch\" allows to reshow the patch\nwith any set of custom options. Spitballing…\n\n     :show <opts>\n\n…could work, since we have room to allow long-form commands still.\nThis could be extended to arbitrary git-* commands, and \":!cmd\" could\nbe used for shelling out.\n\n(Can you tell I use Vim?)\n\nAnyway, what I like about this is we don't have to worry about running\nout of (or thinking up new mnemonics for) single letters for different\ncombinations of diff options, so it's a bit more extensible.\n\n> I think the approach of using the current hunk text, rather than running\n> \"git diff --diff-words\" is sensible because it works correctly if the\n> user has edited the hunk and it makes it easy to handle hunks that have\n> been split. It would be good to propagate the filename when creating the\n> filespec so that we use the correct word diff regex for the file. We\n> should also propagate the context and inter-hunk context settings from\n> struct interactive_options. It would also be a good idea to reuse the\n> hunk header from the unified diff so that the line numbers and hunk\n> fragment are correct.\n>\n> Thanks\n>\n> Phillip\n\n\n-- \nD. Ben Knoble\n"},{"id":"542851","messageId":"CAN5EUNTOtRvjiJ4v4wdCPazSafLRdUmzc9rGCMvwjRKVz6Hz=A@mail.gmail.com","threadId":"65604","inReplyTo":"1f811deb-7cbb-4fe0-ab40-49274b1db165@gmail.com","subject":"Re: [PATCH] add -p: introduce 'w' command to view hunk with --word-diff","fromName":"Pablo","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-05-07T14:53:27Z","receivedAt":"2026-05-07T14:53:41Z","isPatch":true,"body":"El jue, 7 may 2026 a las 15:24, Phillip Wood\n(<phillip.wood123@gmail.com>) escribió:\n>\n> On 07/05/2026 08:55, Pablo wrote:\n> > El jue, 7 may 2026 a las 1:58, Javier Bassi (<javierbassi@gmail.com>) escribió:\n> >>\n> >> +static void add_word_diff_line(struct strbuf *old, struct strbuf *new,\n> >> +                              const char *line, size_t len, char marker)\n> >> +{\n> >> +       if (marker == '-' || marker == '+' || *line == ' ') {\n> >> +               line++;\n> >> +               len--;\n> >> +       }\n> >\n> > Maybe a tiny comment here would help, to know why '*line' is being\n> > checked here instead of 'marker'. They seem the same and one has to go\n> > to marker declaration and see the comment at 'normalize_marker()'\n> >\n> >    /* Empty context lines may omit the leading ' ' */\n>\n> That's a good point - it might be clearer to use\n>\n>         if (marker == *line) {\n>                 line++;\n>                 len--;\n>         }\n>\n> instead. That also trims lines starting with '\\' but that shouldn't\n> matter as the code should be checking \"marker\" rather than \"line\".\n\nYeah, I like \"marker == *line\".\nAbout lines starting with '\\', \"add_word_diff_line()\" is called after\n\"marker == '\\\\'\" block which ends in \"continue;\" skipping\n\"add_word_diff_line()\" call when marker == '\\\\'.\n\n>\n> >> +\n> >> +               if (marker == '\\\\') {\n> >> +                       if (last_marker != '+')\n> >> +                               trim_trailing_lf(old);\n> >> +                       if (last_marker != '-')\n> >> +                               trim_trailing_lf(new);\n> >> +                       continue;\n> >> +               }\n> >\n> > Here we check about \"\\No newline at end of file\", after this point I\n> > believe that 'buf->buf[buf->len - 1] == '\\n'' will always be true.\n> > Same should be for 'buf->len' because \"\\No newline at end of file\"\n> > shouldn't come first and a '+' '-' line should have been added on a\n> > previous iteration, but the check it's fine, just in case I'm wrong.\n> >\n> > What I want to point out is, is the 'trim_trailing_lf' function\n> > necessary? It's only called in the same place and it carries a check\n> > that could be on the caller instead, leaving  the function only with\n> > 'strbuf_setlen(buf, buf->len - 1);\" making sense to inline it at this\n> > point.\n> > You could keep the buf->len check:\n> >\n> >    if (marker == '\\\\') {\n> >            if (last_marker != '+' && old->len)\n> >                     strbuf_setlen(old, old->len - 1);\n> >            if (last_marker != '-' && new->len)\n> >                     strbuf_setlen(new, new->len - 1);\n> >            continue;\n> >    }\n>\n> Should we be trimming '\\r\\n' if the file has dos style line endings?\n\nTrue, I haven't thought of that. Then I take back what I said about\ninlining it, I'd keep the helper just as is (same for the if (marker\n== '\\\\' block)) adding a check for '\\r' after trimming '\\n'.\n\n--\nPablo\n\n>\n> Thanks\n>\n> Phillip\n>\n"},{"id":"542989","messageId":"xmqqqznix0ko.fsf@gitster.g","threadId":"65604","inReplyTo":"CALnO6CCsOMyLxAhGW10L4duaahUORwedfmtdpY3zDGpQcZoP2Q@mail.gmail.com","subject":"Re: [PATCH] add -p: introduce 'w' command to view hunk with --word-diff","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-11T00:16:23Z","receivedAt":"2026-05-11T00:16:26Z","isPatch":true,"body":"\"D. Ben Knoble\" <ben.knoble@gmail.com> writes:\n\n> I concur, though I wonder what other diff options would be useful. I\n> can imagine a world where \"add --patch\" allows to reshow the patch\n> with any set of custom options. Spitballing…\n>\n>      :show <opts>\n>\n> …could work, since we have room to allow long-form commands still.\n> This could be extended to arbitrary git-* commands, and \":!cmd\" could\n> be used for shelling out.\n> (Can you tell I use Vim?)\n>\n> Anyway, what I like about this is we don't have to worry about running\n> out of (or thinking up new mnemonics for) single letters for different\n> combinations of diff options, so it's a bit more extensible.\n\n\nIf you mean \"don't waste a new letter, like 'w', every time you come\nup with a use case to show the patch hunk differently\", I had the\nsame reaction to suggest reusing 'p' but allow options after it,\njust like the output from \"git diff\" can be modified by giving it an\noption \"--word-diff\".  But how would it work for single-key folks (I\nam not one of them so I have less sympathy than I should in this\ncase, but still...).\n\n"},{"id":"543083","messageId":"CALnO6CCR9bQ71LR_buEAzX4UcR4sERnaVLc6_19sJUfXyi-HPA@mail.gmail.com","threadId":"65604","inReplyTo":"xmqqqznix0ko.fsf@gitster.g","subject":"Re: [PATCH] add -p: introduce 'w' command to view hunk with --word-diff","fromName":"D. Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-05-11T19:16:48Z","receivedAt":"2026-05-11T19:16:59Z","isPatch":true,"body":"On Sun, May 10, 2026 at 8:16 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"D. Ben Knoble\" <ben.knoble@gmail.com> writes:\n>\n> > I concur, though I wonder what other diff options would be useful. I\n> > can imagine a world where \"add --patch\" allows to reshow the patch\n> > with any set of custom options. Spitballing…\n> >\n> >      :show <opts>\n> >\n> > …could work, since we have room to allow long-form commands still.\n> > This could be extended to arbitrary git-* commands, and \":!cmd\" could\n> > be used for shelling out.\n> > (Can you tell I use Vim?)\n> >\n> > Anyway, what I like about this is we don't have to worry about running\n> > out of (or thinking up new mnemonics for) single letters for different\n> > combinations of diff options, so it's a bit more extensible.\n>\n>\n> If you mean \"don't waste a new letter, like 'w', every time you come\n> up with a use case to show the patch hunk differently\",\n\nPrecisely\n\n> I had the\n> same reaction to suggest reusing 'p' but allow options after it,\n> just like the output from \"git diff\" can be modified by giving it an\n> option \"--word-diff\".  But how would it work for single-key folks (I\n> am not one of them so I have less sympathy than I should in this\n> case, but still...).\n\nAh, I hadn't remembered we already had p/P. Perhaps\n- in non-single-key mode, p/P can take options.\nand/or\n- \":\" is an exception to single-key mode and always allows more text\nto compose a longer-form \"command\". The first command could be \"print\"\nwhich takes options of this kind.\n\nSince \":\" would be new, exempting it from single-key mode shouldn't be\ntoo jarring I guess.\n\n-- \nD. Ben Knoble\n"},{"id":"543100","messageId":"xmqqo6ilqyrp.fsf@gitster.g","threadId":"65604","inReplyTo":"CALnO6CCR9bQ71LR_buEAzX4UcR4sERnaVLc6_19sJUfXyi-HPA@mail.gmail.com","subject":"Re: [PATCH] add -p: introduce 'w' command to view hunk with --word-diff","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-12T00:04:10Z","receivedAt":"2026-05-12T00:04:13Z","isPatch":true,"body":"\"D. Ben Knoble\" <ben.knoble@gmail.com> writes:\n\n>> >      :show <opts>\n> ...\n> - \":\" is an exception to single-key mode and always allows more text\n> to compose a longer-form \"command\". The first command could be \"print\"\n> which takes options of this kind.\n>\n> Since \":\" would be new, exempting it from single-key mode shouldn't be\n> too jarring I guess.\n\nOK.  And we can use ':blah' to rarely used actions and give them\ndescriptive names.  Makes sense.  word-diff sounds like a good\ncandidate but it is not immediately obvious to me if machinery to\nset up the \"colon is special and handles longhand commands with\npossible arguments\" is worth doing.\n"}]}