" diffunitsyntax: Highlight word or character based diff units in diff format " " Last Change: 2025/07/15 " Version: 3.1 " Author: Rick Howe (Takumi Ohtani) " Copyright: (c) 2024-2025 Rick Howe " License: MIT let s:save_cpo = &cpoptions set cpo&vim let s:IndexCount = 1 function! diffutil#DiffOpt() abort let op = #{} for [ot, vn] in items(#{b: [['icase', ''], \['iblank', 'ignore_blank_lines'], \['iwhiteall', 'ignore_whitespace'], \['iwhite', 'ignore_whitespace_change'], \['iwhiteeol', 'ignore_whitespace_change_at_eol'], \['indent-heuristic', 'indent_heuristic']], \s: [['algorithm', '']], n: [['linematch', '']]}) for [vo, no] in vn if &diffopt =~ '\<' . vo . '\>' if ot == 'b' let op[vo] = v:true else let op[vo] = matchstr(&diffopt, vo . ':\zs\w\+\ze') if ot == 'n' let op[vo] = str2nr(op[vo]) endif endif if has('nvim') && !empty(no) let op[no] = op[vo] unlet op[vo] endif endif endfor endfor return op endfunction if get(g:, 'BuiltinDiffFunc', 1) && \(has('nvim') ? type(luaeval('vim.diff')) == v:t_func : \exists('*diff') && has('patch-9.1.0099')) function! diffutil#DiffFunc(u1, u2, op) abort let [n1, n2] = [len(a:u1), len(a:u2)] if a:u1 ==# a:u2 | let ic = [] elseif n1 == 0 | let ic = [[0, 0, 0, n2]] elseif n2 == 0 | let ic = [[0, n1, 0, 0]] else | let ic = s:BuiltinDiff(a:u1, a:u2, a:op) endif if s:IndexCount return ic else let es = '' let p1 = 0 for [i1, c1, i2, c2] in ic + [[n1, 0, 0, 0]] let es .= repeat('=', i1 - p1) . repeat('-', c1) . repeat('+', c2) let p1 = i1 + c1 endfor return es endif endfunction if has('nvim') function! s:BuiltinDiff(u1, u2, op) abort let op = copy(a:op) let [l1, l2] = [join(a:u1, "\n") . "\n", join(a:u2, "\n") . "\n"] if has_key(op, 'icase') let [l1, l2] = [tolower(l1), tolower(l2)] unlet op['icase'] endif let op['result_type'] = 'indices' return map(v:lua.vim.diff(l1, l2, op), \'[v:val[0] - ((0 < v:val[1]) ? 1 : 0), v:val[1], \v:val[2] - ((0 < v:val[3]) ? 1 : 0), v:val[3]]') endfunction else function! s:BuiltinDiff(u1, u2, op) abort let op = copy(a:op) let op['output'] = 'indices' return map(diff(a:u1, a:u2, op), \'[v:val.from_idx, v:val.from_count, v:val.to_idx, v:val.to_count]') endfunction endif else " buitin or plugin function! diffutil#DiffFunc(u1, u2, op) abort let [u1, u2] = [copy(a:u1), copy(a:u2)] for uu in [u1, u2] if has_key(a:op, 'icase') call map(uu, 'tolower(v:val)') endif if has_key(a:op, 'iwhiteall') call map(uu, 'substitute(v:val, "\\s\\+", "", "g")') elseif has_key(a:op, 'iwhite') call map(uu, 'substitute(v:val, "\\s\\+", " ", "g")') call map(uu, 'substitute(v:val, "\\s\\+$", "", "")') elseif has_key(a:op, 'iwhiteeol') call map(uu, 'substitute(v:val, "\\s\\+$", "", "")') endif endfor let es = s:PluginDiff(u1, u2, \has_key(a:op, 'indent-heuristic') && a:op['indent-heuristic']) if s:IndexCount let ic = [] let [i1, i2] = [0, 0] for ed in split(es, '[-+]\+\zs', 1)[: -2] let [ce, c1, c2] = map(['=', '-', '+'], 'count(ed, v:val)') let [i1, i2] += [ce, ce] let ic += [[i1, c1, i2, c2]] let [i1, i2] += [c1, c2] endfor return ic else return es endif endfunction if has('vim9script') function! s:Vim9PluginDiff() abort def! s:PluginDiff(u1: list, u2: list, ih: bool): string const [eq, n1, n2] = ['=', len(u1), len(u2)] var [e1, e2] = ['-', '+'] if u1 ==# u2 | return repeat(eq, n1) elseif n1 == 0 | return repeat(e2, n2) elseif n2 == 0 | return repeat(e1, n1) endif const [N, M, v1, v2] = (n1 >= n2) ? [n1, n2, u1, u2] : [n2, n1, u2, u1] if n1 < n2 | [e1, e2] = [e2, e1] | endif const D = N - M var fp = repeat([-1], M + N + 1) var etree = [] var p = -1 while fp[D] != N p += 1 var epk = repeat([[]], p * 2 + D + 1) for k in range(-p, D - 1, 1) + range(D + p, D, -1) var x: number | var y: number [y, epk[k]] = (fp[k - 1] + 1 > fp[k + 1]) ? [fp[k - 1] + 1, [e1, [(k > D) ? p - 1 : p, k - 1]]] : [fp[k + 1], [e2, [(k < D) ? p - 1 : p, k + 1]]] x = y - k while x < M && y < N && v2[x] ==# v1[y] epk[k][0] ..= eq | [x, y] += [1, 1] endwhile fp[k] = y endfor etree += [epk] endwhile var k = D var ses = '' while 1 ses = etree[p][k][0] .. ses if [p, k] == [0, 0] | break | endif [p, k] = etree[p][k][1] endwhile ses = ses[1 :] return ih ? s:ReduceDiffHunk(u1, u2, ses) : ses enddef def! s:ReduceDiffHunk(u1: list, u2: list, ses: string): string # in ==++++/==----, if == units equal to last ++/-- units, swap their SESs # (AB vs AxByAB : =+=+++ -> =++++= -> ++++==) const [eq, e1, e2] = ['=', '-', '+'] var [p1, p2] = [-1, -1] | var xes = '' | var ez = '' for ed in reverse(split(ses, '[+-]\+\zs')) var es = ed .. ez | ez = '' | const qe = count(es, eq) if 0 < qe const [q1, q2] = [count(es, e1), count(es, e2)] const [uu, pp, qq] = (qe <= q1 && q2 == 0) ? [u1, p1, q1] : (q1 == 0 && qe <= q2) ? [u2, p2, q2] : [[], 0, 0] if !empty(uu) && uu[pp - qq - qe + 1 : pp - qq] ==# uu[pp - qe + 1 : pp] ez = es[-qe :] .. es[qe : -qe - 1] | es = es[: qe - 1] else [p1, p2] -= [q1, q2] endif endif [p1, p2] -= [qe, qe] xes = es .. xes endfor xes = ez .. xes return xes enddef endfunction call s:Vim9PluginDiff() else " vim9script or vim8script function! s:PluginDiff(u1, u2, ih) abort " An O(NP) Sequence Comparison Algorithm let [u1, u2, eq, e1, e2] = [a:u1, a:u2, '=', '-', '+'] let [n1, n2] = [len(u1), len(u2)] if u1 ==# u2 | return repeat(eq, n1) elseif n1 == 0 | return repeat(e2, n2) elseif n2 == 0 | return repeat(e1, n1) endif let [N, M, u1, u2] = (n1 >= n2) ? [n1, n2, u1, u2] : [n2, n1, u2, u1] if n1 < n2 | let [e1, e2] = [e2, e1] | endif let D = N - M let fp = repeat([-1], M + N + 1) let etree = [] " [next edit, previous p, previous k] let p = -1 while fp[D] != N let p += 1 let epk = repeat([[]], p * 2 + D + 1) for k in range(-p, D - 1, 1) + range(D + p, D, -1) let [y, epk[k]] = (fp[k - 1] + 1 > fp[k + 1]) ? \[fp[k - 1] + 1, [e1, [(k > D) ? p - 1 : p, k - 1]]] : \[fp[k + 1], [e2, [(k < D) ? p - 1 : p, k + 1]]] let x = y - k while x < M && y < N && u2[x] ==# u1[y] let epk[k][0] .= eq | let [x, y] += [1, 1] endwhile let fp[k] = y endfor let etree += [epk] endwhile let ses = '' while 1 let ses = etree[p][k][0] . ses if [p, k] == [0, 0] | break | endif let [p, k] = etree[p][k][1] endwhile let ses = ses[1 :] return a:ih ? s:ReduceDiffHunk(a:u1, a:u2, ses) : ses endfunction function! s:ReduceDiffHunk(u1, u2, ses) abort " in ==++++/==----, if == units equal to last ++/-- units, swap their SESs " (AB vs AxByAB : =+=+++ -> =++++= -> ++++==) let [eq, e1, e2] = ['=', '-', '+'] let [p1, p2] = [-1, -1] | let ses = '' | let ez = '' for ed in reverse(split(a:ses, '[+-]\+\zs')) let es = ed . ez | let ez = '' | let qe = count(es, eq) if 0 < qe let [q1, q2] = [count(es, e1), count(es, e2)] let [uu, pp, qq] = (qe <= q1 && q2 == 0) ? [a:u1, p1, q1] : \(q1 == 0 && qe <= q2) ? [a:u2, p2, q2] : [[], 0, 0] if !empty(uu) && uu[pp - qq - qe + 1 : pp - qq] ==# uu[pp - qe + 1 : pp] let ez = es[-qe :] . es[qe : -qe - 1] | let es = es[: qe - 1] else let [p1, p2] -= [q1, q2] endif endif let [p1, p2] -= [qe, qe] let ses = es . ses endfor let ses = ez . ses return ses endfunction endif " vim9script or vim8script endif " buitin or plugin let &cpoptions = s:save_cpo unlet s:save_cpo " vim: ts=2 sw=0 sts=-1 et