1

:help jumpto-diffs gives commands to jump between changes in a vimdiff:

Two commands can be used to jump to diffs: *[c* [c Jump backwards to the previous start of a change. When a count is used, do it that many times. *]c* ]c Jump forwards to the next start of a change. When a count is used, do it that many times. 

But that jumps to a change of any kind. How can I jump to the next line that's modified, but not a new or removed line? Vim highlights these lines differently, so it must be able to tell them apart.

I thought I could try searching for highlight groups. :help hl-DiffChange shows the highlight groups lines that have changed lines in diff, but I don't see DiffChange (or anything diff) when printing syntax info:

:echo "hi<" . synIDattr(synID(line("."),col("."),1),"name") . '> trans<' . synIDattr(synID(line("."),col("."),0),"name") . "> lo<" . synIDattr(synIDtrans(synID(line("."),col("."),1)),"name") . ">" 

1 Answer 1

1

There is :h diff_hlID() function you can use to identify what diff highlighting is under cursor.

Following is the example of jumping to next/previous change within diff (probably not super efficient).

NextChange

  1. first loop is to find next non diffchange character (if cursor is on diff change already, we want to go to the end of it)
  2. second loop is to find the actual next diffchange

PrevChange is the same in the opposite direction.

The return value of diff_hlID is either 31 or 32 for a diff change -- I just executed it with :echo diff_hlID(line('.'), col('.')) when my cursor was on the diff change. You can probably use synIDattr() to compare against highlight names, didn't try it myself. (update, it is better to use either synIDattr or hlID as the return value of diff_hlID might be different in different environments, example code is updated too)

asciicast

Note, it goes a bit further as it puts cursor on the actual change not the start of the whole change block...

vim9script def NextChange() if !&diff return endif var line = line('.') var col = col('.') while synIDattr(diff_hlID(line, col), "name") =~ 'DiffText.*' && line <= line('$') col += 1 if col > len(getline(line)) line += 1 col = 1 endif endwhile while synIDattr(diff_hlID(line, col), "name") !~ 'DiffText.*' && line <= line('$') col += 1 if col > len(getline(line)) line += 1 col = 1 endif endwhile if synIDattr(diff_hlID(line, col), "name") =~ 'DiffText.*' cursor(line, col) endif enddef def PrevChange() var line = line('.') var col = col('.') while synIDattr(diff_hlID(line, col), "name") =~ 'DiffText.*' && line >= 1 col -= 1 if col < 1 line -= 1 col = len(getline(line)) endif endwhile while synIDattr(diff_hlID(line, col), "name") !~ 'DiffText.*' && line >= 1 col -= 1 if col < 1 line -= 1 col = len(getline(line)) endif endwhile if synIDattr(diff_hlID(line, col), "name") =~ 'DiffText.*' cursor(line, col) endif enddef nnoremap <silent> ]x <scriptcmd>NextChange()<CR> nnoremap <silent> [x <scriptcmd>PrevChange()<CR> 

Updated version with use of hlID and skip of the lines that are not in DiffChange:

vim9script def IsDiffChange(line: number, col: number): bool return [ hlID("DiffChange"), hlID("DiffText"), hlID("DiffTextAdd") ]->index(diff_hlID(line, col)) != -1 enddef def IsDiffText(line: number, col: number): bool return [ hlID("DiffText"), hlID("DiffTextAdd") ]->index(diff_hlID(line, col)) != -1 enddef def NextChange() if !&diff return endif var line = line('.') var col = col('.') while IsDiffText(line, col) && line <= line('$') col += 1 if col > len(getline(line)) line += 1 col = 1 endif endwhile while !IsDiffText(line, col) && line <= line('$') col += 1 if col > len(getline(line)) line += 1 col = 1 while !IsDiffChange(line, col) && line <= line('$') line += 1 endwhile endif endwhile if IsDiffText(line, col) cursor(line, col) endif enddef def PrevChange() if !&diff return endif var line = line('.') var col = col('.') while IsDiffText(line, col) && line >= 1 col -= 1 if col < 1 line -= 1 col = len(getline(line)) endif endwhile while !IsDiffText(line, col) && line >= 1 col -= 1 if col < 1 line -= 1 col = len(getline(line)) while !IsDiffChange(line, col) && line >= 1 line -= 1 col = len(getline(line)) endwhile endif endwhile if IsDiffText(line, col) cursor(line, col) endif enddef nnoremap <silent> ]x <scriptcmd>NextChange()<CR> nnoremap <silent> [x <scriptcmd>PrevChange()<CR> 
5
  • That's very helpful! "The return value is not properly documented though" looks like hlID() would help with that. Although hlID('DiffChange') doesn't return 31 for me, but it's the same value as echo diff_hlID(line('.'), col('.')) Commented Jun 17 at 0:45
  • @idbrii, indeed you can use: synIDattr(diff_hlID(line, col), "name") =~ 'DiffText.*' to compare too Commented Jun 17 at 0:49
  • 1
    @idbrii, note that recent vim has improved diff which is able to show separate changes within the line. Those separate changes have DiffChangeAdd highlight, so you would need to do either 2 comparisons with hlID('DiffChange') and hlID('DiffChangeAdd') or a single regex comparisson with synIDattr(diff_hlID(line, col), "name") =~ 'DiffText.*' Commented Jun 17 at 0:56
  • DiffText and DiffTextAdd of course :) Commented Jun 17 at 1:36
  • adding popup commands make it quite nice too: asciinema.org/a/723453 Commented Jun 17 at 2:06

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.