Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 123 additions & 1 deletion runtime/doc/diff.txt
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,129 @@ name or a part of a buffer name. Examples:
diff mode (e.g., "file.c.v2")

==============================================================================
5. Diff options *diff-options*
5. Diff anchors *diff-anchors*

Diff anchors allow you to control where the diff algorithm aligns and
synchronize text across files. Each anchor matches each other in each file,
allowing you to control the output of a diff.

This is useful when a change involves complicated edits. For example, if a
function was moved to another location and further edited. By default, the
algorithm aims to create the smallest diff, which results in that entire
function being considered to be deleted and added on the other side, making it
hard to see what the actual edit on it was. You can use diff anchors to pin
that function so the diff algorithm will align based on it.

To use it, set anchors using 'diffanchors' which is a comma-separated list of
{address} in each file, and then add "anchor" to 'diffopt'. Internaly, Vim
splits each file up into sections split by the anchors. It performs the diff
on each pair of sections separately before merging the results back.

Setting 'diffanchors' will update the diff immediately. If an anchor is tied
to a mark, and you change what the mark is pointed to, you need to manually
call |:diffupdate| afterwards to get the updated diff results.

Example:

Let's say we have the following files, side-by-side. We are interested in the
change that happened to the function `foo()`, which was both edited and moved.

File A: >
int foo() {
int n = 1;
return n;
}

int g = 1;

int bar(int a) {
a *= 2;
a += 3;
return a;
}
<File B: >
int bar(int a) {
a *= 2;
a += 3;
return a;
}

int foo() {
int n = 999;
return n;
}

int g = 1;
<
A normal diff will usually align the diff result as such: >

int foo() { |----------------
int n = 1; |----------------
return n; |----------------
} |----------------
|----------------
int g = 1; |----------------
|----------------
int bar(int a) {|int bar(int a) {
a *= 2; | a *= 2;
a += 3; | a += 3;
return a; | return a;
} |}
----------------|
----------------|int foo() {
----------------| int n = 999;
----------------| return n;
----------------|}
----------------|
----------------|int g = 1;
<
What we want is to instead ask the diff to align on `foo()`: >

----------------|int bar(int a) {
----------------| a *= 2;
----------------| a += 3;
----------------| return a;
----------------|}
----------------|
int foo() { |int foo() {
int n = 1; | int n = 999;
return n; | return n;
} |}
|
int g = 1; |int g = 1;
|----------------
int bar(int a) {|----------------
a *= 2; |----------------
a += 3; |----------------
return a; |----------------
} |----------------
<

Below are some ways of setting diff anchors to get the above result. In each
example, 'diffopt' needs to have `anchor` set for this to take effect.

Marks: Set the |'a| mark on the `int foo()` lines in each file first before
setting the anchors: >
set diffanchors='a

Pattern: Specify the anchor using a |pattern| (see |:/|). Here, we make sure
to always start search from line 1 for consistency: >
set diffanchors=1/int\ foo(/
<
Selection: Use visual mode to select the entire `foo()` function body in each
file. Here, we use two anchors. This does a better job of making sure only
the function bodies are anchored against each other but not the lines after
it. Note the `'>+1` below. The "+1" is necessary as we want the split to
happen below the last line of the function, not above: >
set diffanchors='<,'>+1
<
Manually set two anchors using line numbers via buffer-local options: >
setlocal diffanchors=1,5
wincmd w
setlocal diffanchors=7,11
<
==============================================================================
6. Diff options *diff-options*

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

Expand Down
1 change: 1 addition & 0 deletions runtime/doc/news.txt
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ OPTIONS
• "o" complete using 'omnifunc'
• 'complete' allows limiting matches for sources using "{flag}^<limit>".
• 'completeopt' flag "nearset" sorts completion results by distance to cursor.
• 'diffanchors' specifies addresses to anchor a diff.
• 'diffopt' `inline:` configures diff highlighting for changes within a line.
• 'grepformat' is now a |global-local| option.
• 'maxsearchcount' sets maximum value for |searchcount()| and defaults to 999.
Expand Down
26 changes: 26 additions & 0 deletions runtime/doc/options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2107,6 +2107,27 @@ A jump table for the options with a short description can be found at |Q_op|.
Join the current window in the group of windows that shows differences
between files. See |diff-mode|.

*'diffanchors'* *'dia'*
'diffanchors' 'dia' string (default "")
global or local to buffer |global-local|
List of {address} in each buffer, separated by commas, that are
considered anchors when used for diffing. It's valid to specify "$+1"
for 1 past the last line. "%" cannot be used for this option. There
can be at most 20 anchors set for each buffer.

Each anchor line splits the buffer (the split happens above the
anchor), with each part being diff'ed separately before the final
result is joined. When more than one {address} are provided, the
anchors will be sorted interally by line number. If using buffer
local options, each buffer should have the same number of anchors
(extra anchors will be ignored). This option is only used when
'diffopt' has "anchor" set. See |diff-anchors| for more details and
examples.
*E1550*
If some of the {address} do not resolve to a line in each buffer (e.g.
a pattern search that does not match anything), none of the anchors
will be used.

*'diffexpr'* *'dex'*
'diffexpr' 'dex' string (default "")
global
Expand All @@ -2130,6 +2151,10 @@ A jump table for the options with a short description can be found at |Q_op|.
patience patience diff algorithm
histogram histogram diff algorithm

anchor Anchor specific lines in each buffer to be
aligned with each other if 'diffanchors' is
set. See |diff-anchors|.

closeoff When a window is closed where 'diff' is set
and there is only one window remaining in the
same tab page with 'diff' set, execute
Expand Down Expand Up @@ -2232,6 +2257,7 @@ A jump table for the options with a short description can be found at |Q_op|.
"linematch:60", as this will enable alignment
for a 2 buffer diff hunk of 30 lines each,
or a 3 buffer diff hunk of 20 lines each.
Implicitly sets "filler" when this is set.

vertical Start diff mode with vertical splits (unless
explicitly specified otherwise).
Expand Down
1 change: 1 addition & 0 deletions runtime/doc/quickref.txt
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,7 @@ Short explanation of each option: *option-list*
'delcombine' 'deco' delete combining characters on their own
'dictionary' 'dict' list of file names used for keyword completion
'diff' use diff mode for the current window
'diffanchors' 'dia' list of {address} to force anchoring of a diff
'diffexpr' 'dex' expression used to obtain a diff file
'diffopt' 'dip' options for using diff mode
'digraph' 'dg' enable the entering of digraphs in Insert mode
Expand Down
31 changes: 31 additions & 0 deletions runtime/lua/vim/_meta/options.lua

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion runtime/optwin.vim
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
" These commands create the option window.
"
" Maintainer: The Vim Project <https://github.com/vim/vim>
" Last Change: 2025 Jul 10
" Last Change: 2025 Jul 16
" Former Maintainer: Bram Moolenaar <Bram@vim.org>

" If there already is an option window, jump to that one.
Expand Down Expand Up @@ -913,6 +913,9 @@ if has("diff")
call <SID>OptionG("dip", &dip)
call <SID>AddOption("diffexpr", gettext("expression used to obtain a diff file"))
call <SID>OptionG("dex", &dex)
call <SID>AddOption("diffanchors", gettext("list of addresses for anchoring a diff"))
call append("$", "\t" .. s:global_or_local)
call <SID>OptionG("dia", &dia)
call <SID>AddOption("patchexpr", gettext("expression used to patch a file"))
call <SID>OptionG("pex", &pex)
endif
Expand Down
1 change: 1 addition & 0 deletions src/nvim/buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -2113,6 +2113,7 @@ void free_buf_options(buf_T *buf, bool free_p_ff)
clear_string_option(&buf->b_p_ffu);
callback_free(&buf->b_ffu_cb);
clear_string_option(&buf->b_p_dict);
clear_string_option(&buf->b_p_dia);
clear_string_option(&buf->b_p_tsr);
clear_string_option(&buf->b_p_qe);
buf->b_p_ar = -1;
Expand Down
9 changes: 6 additions & 3 deletions src/nvim/buffer_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,7 @@ struct file_buffer {
char *b_p_tc; ///< 'tagcase' local value
unsigned b_tc_flags; ///< flags for 'tagcase'
char *b_p_dict; ///< 'dictionary' local value
char *b_p_dia; ///< 'diffanchors' local value
char *b_p_tsr; ///< 'thesaurus' local value
char *b_p_tsrfu; ///< 'thesaurusfunc' local value
Callback b_tsrfu_cb; ///< 'thesaurusfunc' callback
Expand Down Expand Up @@ -759,9 +760,11 @@ struct file_buffer {
/// and how many lines it occupies in that buffer. When the lines are missing
/// in the buffer the df_count[] is zero. This is all counted in
/// buffer lines.
/// There is always at least one unchanged line in between the diffs (unless
/// linematch is used). Otherwise it would have been included in the diff above
/// or below it.
/// Usually there is always at least one unchanged line in between the diffs as
/// otherwise it would have been included in the diff above or below it. When
/// linematch or diff anchors are used, this is no longer guaranteed, and we may
/// have adjacent diff blocks. In all cases they will not overlap, although it
/// is possible to have multiple 0-count diff blocks at the same line.
/// df_lnum[] + df_count[] is the lnum below the change. When in one buffer
/// lines have been inserted, in the other buffer df_lnum[] is the line below
/// the insertion and df_count[] is zero. When appending lines at the end of
Expand Down
Loading
Loading