265 lines
8.2 KiB
VimL
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
|