2026-01-25 09:38:17 +01:00

265 lines
8.2 KiB
VimL

" diffunitsyntax: Highlight word or character based diff units in diff format
"
" Last Change: 2025/07/15
" Version: 3.1
" Author: Rick Howe (Takumi Ohtani) <rdcxy754@ybb.ne.jp>
" 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<string>, u2: list<string>, 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<string>, u2: list<string>, 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