Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@ychin
Copy link
Contributor

@ychin ychin commented Jun 27, 2025

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.


Notes

As mentioned above a decent chunk of the change is the refactoring of the linematch code to remove as much if (dp->is_linematched) { … } special casing as possible as most of the time it duplicated functionality just to support adjacent diff blocks. The refactored done here is to simplify the code and make adjacent diff blocks a native concept rather than hidden in some if/else branching. Existing functionality should all still work. One caveat is that existing linematch code implicitly assumes diffopt+=filler to be set. The refactored code does not mandate that, but we now explicitly set filler to be true in code when linematch is set to match old behavior (and also because it doesn't make sense to use linematch without filler lines anyway).

This feature requires a bit of user action to take advantage of, so below are some examples to illustrate how it could be used.

Examples

Some examples of actual commits that I have used diff anchors to help as I have been testing this locally for a few months:

Example 1

In #16881 (commit 9943d47), there were a lot of code being moved around and refactored into functions. It's impossible for any diff algorithm to know which part of the code you are interested in. So if you try to make sense of what happened to the top-level function diff_find_change this is what you see in regular Vim diff:

image

This is not ideal. However, if I re-anchor using set diffopt+=anchor diffanchors=1/diff_find_change(/ I now get a much better sense of this particular spot in code:

image

If I'm interested how parts of this code got refactored into a child function (diff_find_change_simple) instead, I can simply re-anchor to now have them side-by-side (this can be done using marks and set diffanchors='a):

image

Example 2

In #17579 (commit d75ab0c), I moved the text around in a help doc:

image

The resulting diff is a little hard to make use of. So I could re-anchor the "There can be deleted…" lines and now the diff makes sense for this particular section so I could see if I introduced any typos:

image

Example 3

For this PR itself, the diff_try_update() function was quite difficult to see what's new versus old code due to indentation change. I could turn on diffopt+=iwhite to just ignore all whitespace, but another way was to just re-anchor quickly. Original diff:

image

With the re-anchoring (by doing set diffopt+=anchor dia='a,'b). We can now still see the indentation change reflected in inline diff, but the existing code is now correctly shown as unchanged.

image

Multi-file example

Here is an example of how it could work in multi-file diff'ing. Below are three files where one function was moved, causing the diff results to be difficult to read (the marks are shown using a plugin):

image

However, if we just use set diffopt+=anchor diffanchors='a,'b, we can realign things in a much nicer way. Note that in file 1, both marks are on the same line, which is allowed:

image

FAQs

Why not use Git / Xdiff's --anchored directly?

As mentioned above, Git's feature is quite limited. It is essentially doing the same thing (splitting the file up into multiple smaller sections), but it piggybacks on the patience algorithm and as such requires anchoring unique lines only. The new Vim diff anchors has no such restrictions and allows anchoring any lines that you can specify using a Vim {address}. It also works with any diff algorithm and also external diff tools.

Doesn't line match already solve some of this?

Line match can solve a local alignment issue based on text similarity, but it cannot do this across the whole file. Also, this feature allows the user to manually place the anchors, which is important in ambiguous situations where there is no right answer.

How does this relate to move detection?

Move detection is a feature in some diff tools that can detect moved code and highlights them. However, they don't always work when the moved code has some small changes, and they don't usually have a way to show that difference. Diff anchors is a more manual (as it requires user actions) but flexible method to look at moved code. It's still possible to implement move detection in Vim diff in the future though and they complement each other.

Why do we need diffopt+=anchor when we already need to set diffanchors separately?

I think there could be times where you want to pre-set diffanchors but not immediately enable it. E.g. if you use anchors frequently you can do set diffanchors='a,'b,'c in vimrc and then just turn on/off anchors while viewing diff. That said it's possible to refactor this change to not have the extra diffopt+=anchor and just have it be implied from whether diffanchors is non-empty or not.


TODOs

  • Implement tests.

@habamax
Copy link
Contributor

habamax commented Jun 27, 2025

Looks great!

Are diffanchors local to window or buffer? (found description, global or local to buffer)

What would happen on completely invalid anchors, like global mark A pointing to another buffer line? Or completely unrelated to each other, say mark a for both buffers points to non-related text?

@habamax
Copy link
Contributor

habamax commented Jun 27, 2025

What exactly it means: diffanchors=1/diff_find_change(/? Is 1 for anchoring the first found diff_find_change(?

ps, start search from line 1.

*'dia'* *'diffanchors'* *E1548*
'diffanchors' 'dia' string (default "")
global or local to buffer |global-local|
List of {address} in each buffer, separated by commas, that are
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the meaning of the anchor if address is a current line . or the whole buffer %?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess for the current line it would just anchor it and then after :diffupdate I should be able to see results.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and the whole buffer probably wouldn't make sense to anchor.

Copy link
Contributor Author

@ychin ychin Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

. would use the cursor location of the first diff window containing the buffer (there could be multiple windows for this buffer). It's not a terribly useful way to specify anchors, but it does work and has a consistent behavior. In parse_diffanchors we specifically set curwin and curbuf correctly to make sure these addresses correspond to the window with the buffer.

% would not work (setting it would trigger an error) because it's not a valid {address}. It's usually used as a [range] instead, as a shortcut for 1,$. I decided not to add specialized handling for % because it would break how diff anchors are supposed to be specified (i.e. a comma-separated list of addresses) and I think the use cases of % for diff anchors is niche. FWIW, it's possible to add this, but if we want to add it, % would probably need to mean 1,$+1 for this use case.

Copy link
Contributor Author

@ychin ychin Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and the whole buffer probably wouldn't make sense to anchor.

It could make sense to anchor the whole file sometimes, if set using buffer-local option. So if you set buffer 1's anchor to setlocal dia=1,$+1, and buffer 2 to say setlocal dia='a,'b, that means everything in buffer 1 will be diff'ed against only the part you specified in buffer 2. There is some use.

Copy link
Contributor Author

@ychin ychin Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess for the current line it would just anchor it and then after :diffupdate I should be able to see results.

Yes. You could do that. Just move the cursor and call :diffupdate. You can do the same with setting marks, or selecting texts using visual mode (which updates the < / > marks). Even something like `` would work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

% would not work (setting it would trigger an error) because it's not a valid {address}

It is specified as equal to 1,$ but still within :h {address} though.

Copy link
Contributor Author

@ychin ychin Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is specified as equal to 1,$ but still within :h {address} though

Hm, I guess you are right. I would probably just specify in the 'diffanchors' docs that this is not supported than adding support for it, since it seems pretty low value for this feature. (Edit: Just updated the docs to reflect that)

@ychin
Copy link
Contributor Author

ychin commented Jun 27, 2025

Are diffanchors local to window or buffer? (found description, global or local to buffer)

As you found later: Global / local-to-buffer. I imagine most of the time you would use global. Local-to-buffer is useful for situations where you just want to set line numbers directly, where you probably need to set it differently for each file. The docs in this PR has examples for doing that as well.

What would happen on completely invalid anchors, like global mark A pointing to another buffer line? Or completely unrelated to each other, say mark a for both buffers points to non-related text?

It would act like you typed in an invalid mark. That's because we call get_address() with to_other_file set to FALSE. This prevents it from loading any marks from other files. 'A does work though if that mark is in the same file.

What exactly it means: diffanchors=1/diff_find_change(/? Is 1 for anchoring the first found diff_find_change(?

Start searching from line 1. I think otherwise the default is to start searching from cursor which probably isn't what you want as it's not as consistent (but it may work most of the time if the pattern is unique). FWIW the updated docs (diff.txt) also have an example doing the same thing.

This is all from the existing {address} parsing code btw. The logic would be the same as say if you do :1/foobar/delete (which calls :delete on the first line that has "foobar").

@ychin
Copy link
Contributor Author

ychin commented Jun 29, 2025

Or completely unrelated to each other, say mark a for both buffers points to non-related text?

Forgot to answer this part. There is no such thing as "unrelated" text so to speak. If you anchor them poorly the diff will just be harder to read but it's up to you. Internally it just split up the file at anchor point and diff each part separately.

The worst case scenario of a diff algorithm comparing files of N and M lines is always "delete N lines, add M lines". So that could be what happens if you don't anchor at sensible locations. The diff will still be logically consistent but not easy to understand.

ychin added 3 commits July 8, 2025 15:34
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.
This could happen if we are setting diffopt multiple times, e.g. `set
diffopt=internal,filler diffopt+=anchor` which results in a stale
w_topfill that's not updated to reflect the new diff. The clamp makes
sure the calculated value is sane.
ychin added 4 commits July 13, 2025 07:57
This gives a general error to let the user know the anchor resolution
has failed, in addition to whatever specific message they see like
"invalid mark". Also, currently a failed pattern search does not show
the error message since we are searching with silent mode on, so this is
the only error they will see.
This prevents diff anchors using pattern search from polluting @/ and to
make sure it doesn't reset the search highlight. The only other user of
this function which relies on the silent flag is the incsearch but that
function already caches the pattern using `save_last_search_pattern()`.
We could do that for diff anchors too but it's simply more efficient to
just not pollute the register to begin with. It's unlikely a caller
passing "silent" in would want to do that.
ychin added 3 commits July 13, 2025 19:43
- Clean up prototype as we removed the `diff_check()` function

- Minor logic fix to remove unnecessary condition
This should cover all the new functionality as well as testing that
adjacent blocks still work properly regarding scrollbind and
diffget/diffput.

Add add checks to line match to test that the "filler" setting is done
implicitly whenever line match is used.
@ychin
Copy link
Contributor Author

ychin commented Jul 14, 2025

I added some code clean ups, minor fixes, docs clarifications. Also implemented a suite of tests that should exercise all the modified / new functionality.

This PR should be completely good to go and review! Let me know if there are thoughts or feedbacks.

Copy link
Member

@h-east h-east left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a very minor issue here.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This is useful when a change involves complicated edits. For example, if a
This is useful when a change involves complicated edits. For example, if a

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


Example:

Let's say we have the following files, side-by-side. We are interested in the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Let's say we have the following files, side-by-side. We are interested in the
Let's say we have the following files, side-by-side. We are interested in the

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
set. See |diff-anchors|.
set. See |diff-anchors|.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Member

@chrisbra chrisbra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is very nice. I think this can help reviewing changes much easier. Now we only need a way to tell which change on each side should be synced with which pair on the other diff side :)


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if I understand correctly, this should be correct, right?

Note: By setting mark a in both buffers, we can set the global value of the 'diffanchors' option, when using a numerical address we must set the diffanchors option in each buffer to a different value.

Copy link
Contributor Author

@ychin ychin Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's mostly correct, and it is what the example here is intended to communicate. (Technically for line numbers you can use a global option too, but it usually won't make sense to do so other than toy examples as the lines you want to anchor in each file would usually be different)

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
anchors will be sorted interally by line number. If using buffer
anchors will be sorted 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: when using line-numbers as anchors, the diff hunks to be synced must be in the same order within each file.

Should we spell out this?

Copy link
Contributor Author

@ychin ychin Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when using line-numbers as anchors

There's nothing special about using line numbers as anchors. It works the same way as marks and patterns. The diff anchor doesn't really care what you use at all. It just calls get_address() to translate a bunch of {address} to line numbers. That {address} could be 5, or 'b. It then just sort the results.

So basically, set diffanchors=1,5,8 does the exact same thing as set diffanchors=8,1,5, which does the same thing as set diffanchors=4-3,9-1,4+1 etc. They all get sorted internally to 1,5,8.

Is the documentation not clear on this? Maybe the part about "internally sorted by line number" could be confusing? The "line number" here refers to what each {address} resolves to, e.g. 'a may resolve to line 201, and 'b resolves to 560, and 45 resolves to… 45.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could reword this a bit if you think it's confusing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No that is fine. What I was trying to say, even so we can split the diff and force syncing on those pairs happens in the order in which they appear. It is not yet possible to make the first pair in the first buffer sync with the second pair on the other buffer as those cannot be re-ordered. But it is fine for this PR and could be done later if it turns out to be a missing feature.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. So what you mean is if we have setlocal dia=3,6 on one buffer, and setlocal dia=150,104 on another, you want to match lines 3/150, then 6/104 right? Instead of the current behavior which is to sort and match 3/104, 6/150.

I actually did think about this issue. I originally wanted to preserve the ordering as specified by the user, but when thinking more about it, the issue is that if you match lines 3/150, it will become impossible to match 6 with 104 afterwards, since line 6 (buf 1) is below the previous split, but line 104 (buf 2) is above the split. It means you have to ignore the second anchor which could be confusing in its own right.

So it's definitely doable, but it is trading one cons (sorting anchors leading to Vim ignoring the user-specified order) with another (not sorting anchors leading to some anchors needing to be dropped/ignored).

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose the anchors are included in the pairs, right?

Copy link
Contributor Author

@ychin ychin Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The split happens above the anchor. You can think of the split as between two lines so we have to decide whether it happens above or below the anchor and it makes much more intuitive sense to do the split above. If we have an anchor at line 5, then the split will end up with two regions that we diff separately: (1,4), (5,$).

This is why the example in the docs for :h diff-anchors recommends using set diffanchors='<,'>+1 if you want to anchor a visual range so the next split will happen after the last visually selected line.

I tried to communicate this in the diffanchors docs by saying the following, but I was struggling to word this without sounding too verbose and giving out too much unnecessary information:

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.

@chrisbra chrisbra requested a review from Copilot July 14, 2025 21:09
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Adds a new diffanchors feature to Vim’s diff mode, letting users specify explicit alignment points and refactors linematch logic to natively support adjacent diff blocks.

  • Introduce a new diffanchors option (buffer/window-local) with parsing, storage, and change callback
  • Extend diffopt to include an "anchor" flag and update diff rendering and scrollbind logic
  • Add and adjust dozens of tests to validate diffanchors parsing, application, and interaction with scrolling, diffget/diffput, and error handling

Reviewed Changes

Copilot reviewed 49 out of 49 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/testdir/*.vim Add and update tests for the new diffanchors option and the anchor diffopt flag
src/structs.h Add b_p_dia member to store the diffanchors value
src/optionstr.c Register "anchor" in diffopt values and implement did_set_diffanchors()
src/optiondefs.h / option.c Define and clear the new diffanchors option (PV_DIA)
src/drawline.c Adjust diff rendering logic to remove old filler check and rely on linestatus
src/move.c / src/mouse.c Replace diff_check() calls with diff_check_fill()
src/errors.h Add new error messages (E1549, E1550) for diffanchors
src/ex_docmd.c Promote get_address() to public and tweak search flags for silent search
Comments suppressed due to low confidence (5)

src/optiondefs.h:865

  • The new diffanchors option is not yet documented in :help options.txt or in the diff documentation. Please update the runtime doc files to explain its syntax and behavior.
    {"diffanchors", "dia",  P_STRING|P_VI_DEF|P_ONECOMMA,

src/testdir/util/gen_opt_test.vim:185

  • This test case includes a comma within an address pattern ('a-1,'>,/foo,xxx/,'b,123). Please add an assertion that verifies the parsing handles escaped or embedded commas correctly, or clarify how the splitting logic should work.
      \ 'diffanchors': [['', "'a", '/foo/', "'a-1,'>,/foo,xxx/,'b,123",

src/drawline.c:1479

  • The original check also included wlv.filler_lines < 0, which prevented filler logic when lines were implicitly suppressed. Removing it could regress diff rendering in some cases. Consider reintroducing a combined check or adding a comment explaining why only linestatus is sufficient.
    if (linestatus < 0)

src/move.c:1786

  • Switching to diff_check_fill() may affect how filler is counted when moving windows. Verify that it fully replaces diff_check() in all contexts or document the intended difference.
	if (curwin->w_topfill < diff_check_fill(curwin, curwin->w_topline)

src/mouse.c:2072

  • As above, confirm that diff_check_fill() correctly replaces diff_check() for mouse scroll events, or document any behavioral change.
		if (curwin->w_topfill < diff_check_fill(curwin, curwin->w_topline))

@@ -4552,7 +4551,7 @@ get_address(
else
curwin->w_cursor.col = 0;
searchcmdlen = 0;
Copy link

Copilot AI Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change from silent ? 0 to silent ? SEARCH_KEEP alters search behavior subtly for silent commands. Please add a comment explaining why SEARCH_KEEP is needed and what previous behavior is preserved.

Suggested change
searchcmdlen = 0;
searchcmdlen = 0;
// Use SEARCH_KEEP in silent mode to retain the current search pattern
// without adding it to the search history or displaying messages.
// This preserves the previous behavior of silent searches.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

@ychin ychin Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The silent flag is to make sure no side effects happen when you get get_address(). Previously it only made sure the pattern wouldn't be added to search history, but we need to also make sure it doesn't pollute @/ (last search register), which is what SEARCH_KEEP does. Previously the only user for silent = TRUE is the incsearch code when it needs to parse an Ex command that user is typing, but it just manually worked around this by using save_last_search_pattern() to restore the now polluted @/. It's better to just fix this function.

See commit message from 2ebf698 in this PR which describes this.

@@ -16,6 +16,7 @@ int cmdmod_error(int ignore_silent);
void apply_cmdmod(cmdmod_T *cmod);
void undo_cmdmod(cmdmod_T *cmod);
int parse_cmd_address(exarg_T *eap, char **errormsg, int silent);
Copy link

Copilot AI Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The public prototype for get_address() was added here, but there is no grouping or comment explaining its role. Consider relocating it next to other address‐related functions or adding a brief description.

Suggested change
int parse_cmd_address(exarg_T *eap, char **errormsg, int silent);
int parse_cmd_address(exarg_T *eap, char **errormsg, int silent);
// Retrieves a line number address based on the given command arguments.

Copilot uses AI. Check for mistakes.
@chrisbra chrisbra closed this in 0d9160e Jul 16, 2025
@chrisbra
Copy link
Member

Thanks, this is a very nice feature enhancement!

@ychin ychin deleted the diffanchors branch July 16, 2025 23:13
zeertzjq added a commit to zeertzjq/neovim that referenced this pull request Jul 17, 2025
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: vim/vim#17615

vim/vim@0d9160e

Co-authored-by: Yee Cheng Chin <[email protected]>
zeertzjq added a commit to zeertzjq/neovim that referenced this pull request Jul 17, 2025
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: vim/vim#17615

vim/vim@0d9160e

Co-authored-by: Yee Cheng Chin <[email protected]>
zeertzjq added a commit to zeertzjq/neovim that referenced this pull request Jul 17, 2025
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: vim/vim#17615

vim/vim@0d9160e

Co-authored-by: Yee Cheng Chin <[email protected]>
zeertzjq added a commit to neovim/neovim that referenced this pull request Jul 18, 2025
#34967)

Problem:  not possible to anchor specific lines in diff 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: vim/vim#17615

vim/vim@0d9160e

Co-authored-by: Yee Cheng Chin <[email protected]>
AungMyoKyaw pushed a commit to AungMyoKyaw/neovim that referenced this pull request Jul 20, 2025
neovim#34967)

Problem:  not possible to anchor specific lines in diff 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: vim/vim#17615

vim/vim@0d9160e

Co-authored-by: Yee Cheng Chin <[email protected]>
ychin added a commit to ychin/vim that referenced this pull request Aug 3, 2025
Diff anchors currently will fail to parse if a buffer used for diff'ing
is hidden. Previously it would just fail as the code assumes it would
not happen normally, but this is actually possible to do if `closeoff`
and `hideoff` are not set in diffopt. Git's default diff tool "vimdiff3"
also takes advantage of this.

This fix this properly would require the `{address}` parser to be
smarter about whether a particular address relies on window position or
not (e.g. the `'.` address requires an active window, but `'a` or `1234`
do not). Since hidden diff buffers seem relatively niche, just provide a
better error message / documentation for now. This could be improved
later if there's a demand for it.

Related: vim#17615
ychin added a commit to ychin/vim that referenced this pull request Aug 3, 2025
Diff anchors currently will fail to parse if a buffer used for diff'ing
is hidden. Previously it would just fail as the code assumes it would
not happen normally, but this is actually possible to do if `closeoff`
and `hideoff` are not set in diffopt. Git's default diff tool "vimdiff3"
also takes advantage of this.

This fix this properly would require the `{address}` parser to be
smarter about whether a particular address relies on window position or
not (e.g. the `'.` address requires an active window, but `'a` or `1234`
do not). Since hidden diff buffers seem relatively niche, just provide a
better error message / documentation for now. This could be improved
later if there's a demand for it.

Related: vim#17615
ychin added a commit to ychin/vim that referenced this pull request Aug 3, 2025
Diff anchors currently will fail to parse if a buffer used for diff'ing
is hidden. Previously it would just fail as the code assumes it would
not happen normally, but this is actually possible to do if `closeoff`
and `hideoff` are not set in diffopt. Git's default diff tool "vimdiff3"
also takes advantage of this.

This fix this properly would require the `{address}` parser to be
smarter about whether a particular address relies on window position or
not (e.g. the `'.` address requires an active window, but `'a` or `1234`
do not). Since hidden diff buffers seem relatively niche, just provide a
better error message / documentation for now. This could be improved
later if there's a demand for it.

Related: vim#17615
chrisbra pushed a commit that referenced this pull request Aug 7, 2025
Problem:  diff: using diff anchors with hidden buffers fails silently
Solution: Give specific error message for diff anchors when using hidden
          buffers (Yee Cheng Chin).

Diff anchors currently will fail to parse if a buffer used for diff'ing
is hidden. Previously it would just fail as the code assumes it would
not happen normally, but this is actually possible to do if `closeoff`
and `hideoff` are not set in diffopt. Git's default diff tool "vimdiff3"
also takes advantage of this.

This fix this properly would require the `{address}` parser to be
smarter about whether a particular address relies on window position or
not (e.g. the `'.` address requires an active window, but `'a` or `1234`
do not). Since hidden diff buffers seem relatively niche, just provide a
better error message / documentation for now. This could be improved
later if there's a demand for it.

related: #17615
closes: #17904

Signed-off-by: Yee Cheng Chin <[email protected]>
Signed-off-by: Christian Brabandt <[email protected]>
zeertzjq added a commit to zeertzjq/neovim that referenced this pull request Aug 7, 2025
…ntly

Problem:  diff: using diff anchors with hidden buffers fails silently
Solution: Give specific error message for diff anchors when using hidden
          buffers (Yee Cheng Chin).

Diff anchors currently will fail to parse if a buffer used for diff'ing
is hidden. Previously it would just fail as the code assumes it would
not happen normally, but this is actually possible to do if `closeoff`
and `hideoff` are not set in diffopt. Git's default diff tool "vimdiff3"
also takes advantage of this.

This fix this properly would require the `{address}` parser to be
smarter about whether a particular address relies on window position or
not (e.g. the `'.` address requires an active window, but `'a` or `1234`
do not). Since hidden diff buffers seem relatively niche, just provide a
better error message / documentation for now. This could be improved
later if there's a demand for it.

related: vim/vim#17615
closes: vim/vim#17904

vim/vim@cad3b24

Co-authored-by: Yee Cheng Chin <[email protected]>
zeertzjq added a commit to neovim/neovim that referenced this pull request Aug 7, 2025
…ntly (#35218)

Problem:  diff: using diff anchors with hidden buffers fails silently
Solution: Give specific error message for diff anchors when using hidden
          buffers (Yee Cheng Chin).

Diff anchors currently will fail to parse if a buffer used for diff'ing
is hidden. Previously it would just fail as the code assumes it would
not happen normally, but this is actually possible to do if `closeoff`
and `hideoff` are not set in diffopt. Git's default diff tool "vimdiff3"
also takes advantage of this.

This fix this properly would require the `{address}` parser to be
smarter about whether a particular address relies on window position or
not (e.g. the `'.` address requires an active window, but `'a` or `1234`
do not). Since hidden diff buffers seem relatively niche, just provide a
better error message / documentation for now. This could be improved
later if there's a demand for it.

related: vim/vim#17615
closes: vim/vim#17904

vim/vim@cad3b24

Co-authored-by: Yee Cheng Chin <[email protected]>
yochem pushed a commit to yochem/neovim that referenced this pull request Aug 23, 2025
…ntly (neovim#35218)

Problem:  diff: using diff anchors with hidden buffers fails silently
Solution: Give specific error message for diff anchors when using hidden
          buffers (Yee Cheng Chin).

Diff anchors currently will fail to parse if a buffer used for diff'ing
is hidden. Previously it would just fail as the code assumes it would
not happen normally, but this is actually possible to do if `closeoff`
and `hideoff` are not set in diffopt. Git's default diff tool "vimdiff3"
also takes advantage of this.

This fix this properly would require the `{address}` parser to be
smarter about whether a particular address relies on window position or
not (e.g. the `'.` address requires an active window, but `'a` or `1234`
do not). Since hidden diff buffers seem relatively niche, just provide a
better error message / documentation for now. This could be improved
later if there's a demand for it.

related: vim/vim#17615
closes: vim/vim#17904

vim/vim@cad3b24

Co-authored-by: Yee Cheng Chin <[email protected]>
sahinf pushed a commit to sahinf/vim that referenced this pull request Sep 7, 2025
Problem:  diff: using diff anchors with hidden buffers fails silently
Solution: Give specific error message for diff anchors when using hidden
          buffers (Yee Cheng Chin).

Diff anchors currently will fail to parse if a buffer used for diff'ing
is hidden. Previously it would just fail as the code assumes it would
not happen normally, but this is actually possible to do if `closeoff`
and `hideoff` are not set in diffopt. Git's default diff tool "vimdiff3"
also takes advantage of this.

This fix this properly would require the `{address}` parser to be
smarter about whether a particular address relies on window position or
not (e.g. the `'.` address requires an active window, but `'a` or `1234`
do not). Since hidden diff buffers seem relatively niche, just provide a
better error message / documentation for now. This could be improved
later if there's a demand for it.

related: vim#17615
closes: vim#17904

Signed-off-by: Yee Cheng Chin <[email protected]>
Signed-off-by: Christian Brabandt <[email protected]>
dundargoc pushed a commit to dundargoc/neovim that referenced this pull request Sep 27, 2025
neovim#34967)

Problem:  not possible to anchor specific lines in diff 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: vim/vim#17615

vim/vim@0d9160e

Co-authored-by: Yee Cheng Chin <[email protected]>
dundargoc pushed a commit to dundargoc/neovim that referenced this pull request Sep 27, 2025
…ntly (neovim#35218)

Problem:  diff: using diff anchors with hidden buffers fails silently
Solution: Give specific error message for diff anchors when using hidden
          buffers (Yee Cheng Chin).

Diff anchors currently will fail to parse if a buffer used for diff'ing
is hidden. Previously it would just fail as the code assumes it would
not happen normally, but this is actually possible to do if `closeoff`
and `hideoff` are not set in diffopt. Git's default diff tool "vimdiff3"
also takes advantage of this.

This fix this properly would require the `{address}` parser to be
smarter about whether a particular address relies on window position or
not (e.g. the `'.` address requires an active window, but `'a` or `1234`
do not). Since hidden diff buffers seem relatively niche, just provide a
better error message / documentation for now. This could be improved
later if there's a demand for it.

related: vim/vim#17615
closes: vim/vim#17904

vim/vim@cad3b24

Co-authored-by: Yee Cheng Chin <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants