3

I'm asking about some sort of specialized diff highlighting mode. I need to diff, line by line, files A and B, which look similar to this in vimdiff:

A                             B
repo/manipulate_1.0.1.out  |  package ../manipulate-1.0.1-x86_64-1.txz created.
repo/evaluate_0.5.5.out    |  package ../evaluate-0.5.5-x86_64-1.txz created.
repo/formatR_1.1.out       |  package ../formatR-1.1-x86_64-1.txz created.
repo/highr_0.4.1.out       |  package ../highr-0.4.1-x86_64-1.txz created.
...                        |  ...

I would like for vim to highlight only the common subsequence of the file names on both sides, that is, manipulate, evaluate, formatR and highr.

Is this possible? How?

It occurs to me that what I want is a way to highlight (or highlight differently from the rest of the line) the longest common subsequence (lcs) between each pair of lines; but there might be other, possibly easier, ways to frame this problem.

Here is a graphic comparison of the current state (top) vs. what I want to achive (bottom). Note that the fourth line shows two different highlights (green and red) because both strings qualify as the lcs of length five:

vim diff default highlight vs. longest common subsequence highlight

  • I thought vimdiff syntax highlighting ready had syntax for word diffs in lines. – Sukima Apr 7 '15 at 2:47
  • Vimdiff does, but the syntax does not support highlight the longest common subsequence, which needs to be identified before it can be highlighted. As far as I can tell, when (vim)diff compares two lines and they differ on their first character (ignoring white space in some cases) the lines are considered different, and they are highlighted from beginning to end. That behavior isn't what I need and described in my question. – stepse Apr 7 '15 at 7:13
  • I have added some graphics to better illustrate my question. – stepse Apr 7 '15 at 8:12
  • I found the diffchar plugin, which can be used to alter vimdiff default highlighting to better match differences. However, this plugin isn't designed to recognize the longest common subsequence (lcs), so I don't know if it can be made to highlight just the lcs, as shown in the graphics above. – stepse Apr 7 '15 at 8:41
0

If you can change your files A and B to include a diff unit delimiter, diffchar might be help to see the LCS. For example, replace '/' and '_' with a space character in A, and remove '../' and replace the first '-' with a space in B. And let t:DiffUnit="Word2" and try F7. Does it look like this?

enter image description here

  • Thank you Rick, I can reproduce your picture. The method you outline does help with the specific task in my question. I am also looking for something more general, that can work automatically without needing to adjust the strings before and after the lcs. – stepse Apr 7 '15 at 16:25
  • If the strings before/after the lcs do not look alike, this method doesn't help showing the lcs, try: repo evaluate xx 0.5.5.out | pack age evaluate 0.5.5-x86_64-1.txz – stepse Apr 7 '15 at 16:36
0

Thanks to Rick Howe, who answered and then emailed me the following script, we have a perfect solution for my question. Rick Howe's script highlights the longest common subsequence (LCS) in each line pair of two vimdiff buffers. When equal-length LCS matches are found, they are highlighted in different colors.

Credit

Rick Howe's wrote this script, I didn't. Rick's script is posted here with his permission.

I only added a couple of lines to suspend difference highlights so that LCS matches stand out more clearly. I also changed highlighting groups to choices that should work for the standard color scheme in vim and gvim on linux. Resulting colors aren't exactly pretty, but they work in cterm and gui with no need to fiddle them (you can change g:hlt if you really want). Here's a vim (top) and gvim (bottom) screencap.

screen capture of longest common subsequence vim diff script output

Usage

Save the script as lcs.vim and put it in a folder included in the runtimepath option list, then start vimdiff: vim -d +"runtime lcs.vim" A B and press F4 to toggle highlighting LCS.

lcs.vim

nnoremap <silent> <F4> :call <SID>ToggleHighlightLCS()<CR>

" Cycle through these groups to highlight equal-length LCS in each line.
" You may change this choice but do not include Diff* groups, i.e., DiffAdd.
let g:hlt = ["MatchParen", "TabLineFill", "Error", "Folded"]

function! s:ToggleHighlightLCS()
    if winnr('$') < 2 | echo "Need more windows" | return | endif
    let win1 = winnr()
    let win2 = win1 % winnr('$') + 1
    if exists("t:mid")
        for [w, mid] in t:mid
            exec w . "wincmd w"
            call map(mid, 'matchdelete(v:val)')
        endfor
        unlet t:mid
        exec win1 . "wincmd w"
        exec join(map(["Add","Text","Change","Delete"], '"hi! link Diff".v:val." NONE"'),"|")
        return
    endif
    let ln1 = getbufline(winbufnr(win1), 1, '$')
    let ln2 = getbufline(winbufnr(win2), 1, '$')
    let lcs = map(range(min([len(ln1), len(ln2)])),
                    \'s:FindLCS(ln1[v:val], ln2[v:val])')
    let t:mid = []
    for w in [win1, win2]
        exec w . "wincmd w"
        let mid = []
        for n in range(len(lcs))
            let mid += map(map(copy(lcs[n]),
                \'(&ic ? "\\c" : "\\C") .
                \"\\V\\%" . (n + 1) . "l" .
                \escape(v:val, "\\")'),
                \'matchadd(g:hlt[v:key % len(g:hlt)], v:val)')
        endfor
        let t:mid += [[w, mid]]
    endfor
    exec win1 . "wincmd w"
    exec join(map(["Add","Text","Change","Delete"], '"hi! link Diff".v:val." Normal"'),"|")
endfunction

function! s:FindLCS(str1, str2)
    let [len1, len2] = [strchars(a:str1), strchars(a:str2)]
    let [lng, sht, len] = (len1 >= len2) ?
            \[a:str1, a:str2, len2] : [a:str2, a:str1, len1]
    let idx = map(range(len + 1), 'byteidx(sht, v:val)')
    for n in range(len, 1, -1)
        let lcs = []
        for p in range(0, len - n)
            let s = sht[idx[p] : idx[p + n] - 1]
            if !count(lcs, s, &ic) | let lcs += [s] | endif
        endfor
        call filter(lcs, 'lng =~ "\\V" . escape(v:val, "\\")')
        if !empty(lcs) | break | endif
    endfor
    return lcs
endfunction

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.