Skip to content

Commit 0d9160e

Browse files
ychinchrisbra
authored andcommitted
patch 9.1.1557: not possible to anchor specific lines in difff mode
Problem: not possible to anchor specific lines in difff mode Solution: Add support for the anchoring lines in diff mode using the 'diffanchor' option (Yee Cheng Chin). Adds support for anchoring specific lines to each other while viewing a diff. While lines are anchored, they are guaranteed to be aligned to each other in a diff view, allowing the user to control and inform the diff algorithm what the desired alignment is. Internally, this is done by splitting up the buffer at each anchor and run the diff algorithm on each split section separately, and then merge the results back for a logically consistent diff result. To do this, add a new "diffanchors" option that takes a list of `{address}`, and a new "diffopt" option value "anchor". Each address specified will be an anchor, and the user can choose to use any type of address, including marks, line numbers, or pattern search. Anchors are sorted by line number in each file, and it's possible to have multiple anchors on the same line (this is useful when doing multi-buffer diff). Update documentation to provide examples. This is similar to Git diff's `--anchored` flag. Other diff tools like Meld/Araxis Merge also have similar features (called "synchronization points" or "synchronization links"). We are not using Git/Xdiff's `--anchored` implementation here because it has a very limited API (it requires usage of the Patience algorithm, and can only anchor unique lines that are the same across both files). Because the user could anchor anywhere, diff anchors could result in adjacent diff blocks (one block is directly touching another without a gap), if there is a change right above the anchor point. We don't want to merge these diff blocks because we want to line up the change at the anchor. Adjacent diff blocks were first allowed when linematch was added, but the existing code had a lot of branched paths where line-matched diff blocks were handled differently. As a part of this change, refactor them to have a more unified code path that is generalized enough to handle adjacent diff blocks correctly and without needing to carve in exceptions all over the place. closes: #17615 Signed-off-by: Yee Cheng Chin <ychin.git@gmail.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
1 parent 393d398 commit 0d9160e

54 files changed

Lines changed: 1877 additions & 567 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

runtime/doc/diff.txt

Lines changed: 126 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*diff.txt* For Vim version 9.1. Last change: 2025 Jun 20
1+
*diff.txt* For Vim version 9.1. Last change: 2025 Jul 26
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -14,7 +14,8 @@ The basics are explained in section |08.7| of the user manual.
1414
2. Viewing diffs |view-diffs|
1515
3. Jumping to diffs |jumpto-diffs|
1616
4. Copying diffs |copy-diffs|
17-
5. Diff options |diff-options|
17+
5. Diff anchors |diff-anchors|
18+
6. Diff options |diff-options|
1819

1920
==============================================================================
2021
1. Starting diff mode *start-vimdiff*
@@ -336,7 +337,129 @@ name or a part of a buffer name. Examples:
336337
diff mode (e.g., "file.c.v2")
337338

338339
==============================================================================
339-
5. Diff options *diff-options*
340+
5. Diff anchors *diff-anchors*
341+
342+
Diff anchors allow you to control where the diff algorithm aligns and
343+
synchronize text across files. Each anchor matches each other in each file,
344+
allowing you to control the output of a diff.
345+
346+
This is useful when a change involves complicated edits. For example, if a
347+
function was moved to another location and further edited. By default, the
348+
algorithm aims to create the smallest diff, which results in that entire
349+
function being considered to be deleted and added on the other side, making it
350+
hard to see what the actual edit on it was. You can use diff anchors to pin
351+
that function so the diff algorithm will align based on it.
352+
353+
To use it, set anchors using 'diffanchors' which is a comma-separated list of
354+
{address} in each file, and then add "anchor" to 'diffopt'. Internaly, Vim
355+
splits each file up into sections split by the anchors. It performs the diff
356+
on each pair of sections separately before merging the results back.
357+
358+
Setting 'diffanchors' will update the diff immediately. If an anchor is tied
359+
to a mark, and you change what the mark is pointed to, you need to manually
360+
call |:diffupdate| afterwards to get the updated diff results.
361+
362+
Example:
363+
364+
Let's say we have the following files, side-by-side. We are interested in the
365+
change that happened to the function `foo()`, which was both edited and moved.
366+
367+
File A: >
368+
int foo() {
369+
int n = 1;
370+
return n;
371+
}
372+
373+
int g = 1;
374+
375+
int bar(int a) {
376+
a *= 2;
377+
a += 3;
378+
return a;
379+
}
380+
<File B: >
381+
int bar(int a) {
382+
a *= 2;
383+
a += 3;
384+
return a;
385+
}
386+
387+
int foo() {
388+
int n = 999;
389+
return n;
390+
}
391+
392+
int g = 1;
393+
<
394+
A normal diff will usually align the diff result as such: >
395+
396+
int foo() { |----------------
397+
int n = 1; |----------------
398+
return n; |----------------
399+
} |----------------
400+
|----------------
401+
int g = 1; |----------------
402+
|----------------
403+
int bar(int a) {|int bar(int a) {
404+
a *= 2; | a *= 2;
405+
a += 3; | a += 3;
406+
return a; | return a;
407+
} |}
408+
----------------|
409+
----------------|int foo() {
410+
----------------| int n = 999;
411+
----------------| return n;
412+
----------------|}
413+
----------------|
414+
----------------|int g = 1;
415+
<
416+
What we want is to instead ask the diff to align on `foo()`: >
417+
418+
----------------|int bar(int a) {
419+
----------------| a *= 2;
420+
----------------| a += 3;
421+
----------------| return a;
422+
----------------|}
423+
----------------|
424+
int foo() { |int foo() {
425+
int n = 1; | int n = 999;
426+
return n; | return n;
427+
} |}
428+
|
429+
int g = 1; |int g = 1;
430+
|----------------
431+
int bar(int a) {|----------------
432+
a *= 2; |----------------
433+
a += 3; |----------------
434+
return a; |----------------
435+
} |----------------
436+
<
437+
438+
Below are some ways of setting diff anchors to get the above result. In each
439+
example, 'diffopt' needs to have `anchor` set for this to take effect.
440+
441+
Marks: Set the |'a| mark on the `int foo()` lines in each file first before
442+
setting the anchors: >
443+
set diffanchors='a
444+
445+
Pattern: Specify the anchor using a |pattern| (see |:/|). Here, we make sure
446+
to always start search from line 1 for consistency: >
447+
set diffanchors=1/int\ foo(/
448+
<
449+
Selection: Use visual mode to select the entire `foo()` function body in each
450+
file. Here, we use two anchors. This does a better job of making sure only
451+
the function bodies are anchored against each other but not the lines after
452+
it. Note the `'>+1` below. The "+1" is necessary as we want the split to
453+
happen below the last line of the function, not above: >
454+
set diffanchors='<,'>+1
455+
<
456+
Manually set two anchors using line numbers via buffer-local options: >
457+
setlocal diffanchors=1,5
458+
wincmd w
459+
setlocal diffanchors=7,11
460+
<
461+
==============================================================================
462+
6. Diff options *diff-options*
340463

341464
Also see |'diffopt'| and the "diff" item of |'fillchars'|.
342465

runtime/doc/options.txt

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*options.txt* For Vim version 9.1. Last change: 2025 Jul 13
1+
*options.txt* For Vim version 9.1. Last change: 2025 Jul 16
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -2986,6 +2986,28 @@ A jump table for the options with a short description can be found at |Q_op|.
29862986
Join the current window in the group of windows that shows differences
29872987
between files. See |vimdiff|.
29882988

2989+
*'dia'* *'diffanchors'* *E1549*
2990+
'diffanchors' 'dia' string (default "")
2991+
global or local to buffer |global-local|
2992+
List of {address} in each buffer, separated by commas, that are
2993+
considered anchors when used for diffing. It's valid to specify "$+1"
2994+
for 1 past the last line. "%" cannot be used for this option. There
2995+
can be at most 20 anchors set for each buffer.
2996+
2997+
Each anchor line splits the buffer (the split happens above the
2998+
anchor), with each part being diff'ed separately before the final
2999+
result is joined. When more than one {address} are provided, the
3000+
anchors will be sorted interally by line number. If using buffer
3001+
local options, each buffer should have the same number of anchors
3002+
(extra anchors will be ignored). This option is only used when
3003+
'diffopt' has "anchor" set. See |diff-anchors| for more details and
3004+
examples.
3005+
*E1550*
3006+
If some of the {address} do not resolve to a line in each buffer (e.g.
3007+
a pattern search that does not match anything), none of the anchors
3008+
will be used.
3009+
3010+
29893011
*'dex'* *'diffexpr'*
29903012
'diffexpr' 'dex' string (default "")
29913013
global
@@ -3014,6 +3036,10 @@ A jump table for the options with a short description can be found at |Q_op|.
30143036
patience patience diff algorithm
30153037
histogram histogram diff algorithm
30163038

3039+
anchor Anchor specific lines in each buffer to be
3040+
aligned with each other if 'diffanchors' is
3041+
set. See |diff-anchors|.
3042+
30173043
closeoff When a window is closed where 'diff' is set
30183044
and there is only one window remaining in the
30193045
same tab page with 'diff' set, execute
@@ -3116,6 +3142,7 @@ A jump table for the options with a short description can be found at |Q_op|.
31163142
"linematch:60", as this will enable alignment
31173143
for a 2 buffer diff hunk of 30 lines each,
31183144
or a 3 buffer diff hunk of 20 lines each.
3145+
Implicitly sets "filler" when this is set.
31193146

31203147
vertical Start diff mode with vertical splits (unless
31213148
explicitly specified otherwise).

runtime/doc/quickref.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*quickref.txt* For Vim version 9.1. Last change: 2025 Jun 28
1+
*quickref.txt* For Vim version 9.1. Last change: 2025 Jul 16
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -682,6 +682,7 @@ Short explanation of each option: *option-list*
682682
'delcombine' 'deco' delete combining characters on their own
683683
'dictionary' 'dict' list of file names used for keyword completion
684684
'diff' use diff mode for the current window
685+
'diffanchors' 'dia' list of {address} to force anchoring of a diff
685686
'diffexpr' 'dex' expression used to obtain a diff file
686687
'diffopt' 'dip' options for using diff mode
687688
'digraph' 'dg' enable the entering of digraphs in Insert mode

runtime/doc/tags

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,11 @@ $quote eval.txt /*$quote*
216216
'delcombine' options.txt /*'delcombine'*
217217
'dex' options.txt /*'dex'*
218218
'dg' options.txt /*'dg'*
219+
'dia' options.txt /*'dia'*
219220
'dict' options.txt /*'dict'*
220221
'dictionary' options.txt /*'dictionary'*
221222
'diff' options.txt /*'diff'*
223+
'diffanchors' options.txt /*'diffanchors'*
222224
'diffexpr' options.txt /*'diffexpr'*
223225
'diffopt' options.txt /*'diffopt'*
224226
'digraph' options.txt /*'digraph'*
@@ -4667,7 +4669,9 @@ E1540 eval.txt /*E1540*
46674669
E1541 vi_diff.txt /*E1541*
46684670
E1547 various.txt /*E1547*
46694671
E1548 wayland.txt /*E1548*
4672+
E1549 options.txt /*E1549*
46704673
E155 sign.txt /*E155*
4674+
E1550 options.txt /*E1550*
46714675
E156 sign.txt /*E156*
46724676
E157 sign.txt /*E157*
46734677
E158 sign.txt /*E158*
@@ -6935,6 +6939,7 @@ dict-modification eval.txt /*dict-modification*
69356939
did_filetype() builtin.txt /*did_filetype()*
69366940
diff diff.txt /*diff*
69376941
diff() builtin.txt /*diff()*
6942+
diff-anchors diff.txt /*diff-anchors*
69386943
diff-diffexpr diff.txt /*diff-diffexpr*
69396944
diff-func-examples diff.txt /*diff-func-examples*
69406945
diff-mode diff.txt /*diff-mode*

runtime/doc/version9.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*version9.txt* For Vim version 9.1. Last change: 2025 Jul 15
1+
*version9.txt* For Vim version 9.1. Last change: 2025 Jul 16
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -41570,6 +41570,12 @@ the "inline" sub option value for the 'diffopt' setting, with "inline:simple"
4157041570
being added to the default "diffopt" value (but this does not change how diff
4157141571
mode works).
4157241572

41573+
The 'diffanchors' option specifies a comma-separated list of addresses in
41574+
a buffer that act as anchor points for splitting and independently diffing
41575+
buffer sections, improving diff alignment. It is only used when 'diffopt'
41576+
includes "anchor" and all specified addresses must resolve in every buffer, or
41577+
else the anchors are ignored.
41578+
4157341579
Completion~
4157441580
----------
4157541581
- New Insert-mode completion: |i_CTRL-X_CTRL-R| to complete words from
@@ -41799,6 +41805,7 @@ Options: ~
4179941805
|ins-completion| modes
4180041806
'completeitemalign' Order of |complete-items| in Insert mode completion
4180141807
popup
41808+
'diffanchors' list of {address} to force syncing of diffs
4180241809
'eventignorewin' autocommand events that are ignored in a window
4180341810
'findfunc' Vim function to obtain the results for a |:find|
4180441811
command

runtime/optwin.vim

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
" These commands create the option window.
22
"
33
" Maintainer: The Vim Project <https://github.com/vim/vim>
4-
" Last Change: 2025 Jul 10
4+
" Last Change: 2025 Jul 16
55
" Former Maintainer: Bram Moolenaar <Bram@vim.org>
66

77
" If there already is an option window, jump to that one.
@@ -1056,6 +1056,9 @@ if has("diff")
10561056
call <SID>OptionG("dip", &dip)
10571057
call <SID>AddOption("diffexpr", gettext("expression used to obtain a diff file"))
10581058
call <SID>OptionG("dex", &dex)
1059+
call <SID>AddOption("diffanchors", gettext("list of addresses for anchoring a diff"))
1060+
call append("$", "\t" .. s:global_or_local)
1061+
call <SID>OptionG("dia", &dia)
10591062
call <SID>AddOption("patchexpr", gettext("expression used to patch a file"))
10601063
call <SID>OptionG("pex", &pex)
10611064
endif

0 commit comments

Comments
 (0)