dotfiles/.vim/bundle/vim-diffchar/autoload/diffchar.vim

1659 lines
54 KiB
VimL
Raw Normal View History

2022-12-04 23:41:31 +01:00
" diffchar.vim: Highlight the exact differences, based on characters and words
"
" ____ _ ____ ____ _____ _ _ _____ ____
" | | | || || || || | | || _ || _ |
" | _ || || __|| __|| || | | || | | || | ||
" | | | || || |__ | |__ | __|| |_| || |_| || |_||_
" | |_| || || __|| __|| | | || || __ |
" | || || | | | | |__ | _ || _ || | | |
" |____| |_||_| |_| |_____||_| |_||_| |_||_| |_|
"
" Last Change: 2021/12/07
" Version: 8.91
" Author: Rick Howe (Takumi Ohtani) <rdcxy754@ybb.ne.jp>
" Copyright: (c) 2014-2021 by Rick Howe
let s:save_cpo = &cpoptions
set cpo&vim
" Vim feature, function, event and patch number which this plugin depends on
" patch-8.0.736: OptionSet event triggered with diff option
" patch-8.0.794: count() fixed to accept a string
" patch-8.0.914: nocombine attribute available
" patch-8.0.1038: strikethrough attribute available
" patch-8.0.1160: gettabvar() fixed not to return empty
" patch-8.0.1290: changenr() fixed to return correct value
" patch-8.1.414: v:option fixed in OptionSet diff autocmd
" patch-8.1.1084: window ID argument available in all match functions
" patch-8.1.1832: win_execute() fixed to work in other tabpage
let s:VF = {
\'DiffUpdated': exists('##DiffUpdated'),
\'WinScrolled': exists('##WinScrolled'),
\'WinClosed': exists('##WinClosed'),
\'GUIColors': has('gui_running') ||
\has('termguicolors') && &termguicolors,
\'DiffExecutable': executable('diff'),
\'PopupWindow': has('popupwin'),
\'FloatingWindow': exists('*nvim_create_buf'),
\'GetMousePos': exists('*getmousepos'),
\'WinExecute': exists('*win_execute') &&
\(has('patch-8.1.1832') || has('nvim-0.5.0')),
\'DiffOptionSet': has('patch-8.0.736'),
\'CountString': has('patch-8.0.794'),
\'StrikeAttr': has('patch-8.0.1038') &&
\(has('gui_running') || !empty(&t_Ts) && !empty(&t_Te)),
\'GettabvarFixed': has('patch-8.0.1160'),
\'ChangenrFixed': has('patch-8.0.1290'),
\'VOptionFixed': has('patch-8.1.414') || has('nvim-0.3.2'),
\'WinIDinMatch': has('patch-8.1.1084') || has('nvim-0.5.0'),
\'NvimDiffHLID': has('nvim') && !has('nvim-0.4.0')}
function! s:SetDiffCharHL() abort
" check vim original Diff highlights and set attributes for changes
let s:DiffHL = {}
for [hs, hl] in [['A', 'DiffAdd'], ['C', 'DiffChange'],
\['D', 'DiffDelete'], ['T', 'DiffText']]
let dh = {}
let hn = has('nvim') ? '' :
\matchstr(split(&highlight, ','), '^' . hs . '\C:')
let dh.id = hlID(empty(hn) ? hl : hn[2:])
let dh.it = synIDtrans(dh.id) " in case of linked
let dh.nm = synIDattr(dh.it, 'name')
" dh: 0 = original, 1 = for single color, 2 = for multi color
let dh.0 = {}
for hm in ['term', 'cterm', 'gui']
for hc in ['fg', 'bg', 'sp']
let dh.0[hm . hc] = synIDattr(dh.it, hc, hm)
endfor
let dh.0[hm] = join(filter(['bold', 'underline', 'undercurl',
\'strikethrough', 'reverse', 'inverse', 'italic', 'standout'],
\'!empty(synIDattr(dh.it, v:val, hm))'), ',')
endfor
call filter(dh.0, '!empty(v:val)')
let dh.1 = (hs == 'C' || hs == 'T') ?
\filter(copy(dh.0), 'v:key =~ "bg$"') : dh.0
let dh.2 = (hs == 'T') ? {} : dh.1
" diff_hlID() incorrectly returns (hlID() - 1) until nvim 0.4.0
if s:VF.NvimDiffHLID | let dh.id -= 1 | endif
let s:DiffHL[hs] = dh
endfor
" set DiffChar specific highlights
let s:DCharHL = {'A': 'DiffAdd', 'D': 'DiffDelete', 'n': 'LineNr'}
if has('nvim')
let s:DCharHL.c = 'TermCursor'
else
let s:DCharHL.c = 'Cursor'
if !s:VF.GUIColors
let id = 1
while 1
let nm = synIDattr(id, 'name')
if empty(nm) | break | endif
if id == synIDtrans(id) && !empty(synIDattr(id, 'reverse')) &&
\empty(filter(['fg', 'bg', 'sp', 'bold', 'underline',
\'undercurl', 'strikethrough', 'italic', 'standout'],
\'!empty(synIDattr(id, v:val))'))
let s:DCharHL.c = nm
break
endif
let id += 1
endwhile
endif
endif
for [fs, ts, th, ta] in [['C', 'C', 'dcDiffChange', ''],
\['T', 'T', 'dcDiffText', ''],
\['C', 'E', 'dcDiffErase', 'bold,underline']] +
\(s:VF.StrikeAttr ? [['D', 'D', 'dcDiffDelete', 'strikethrough']] : [])
let fa = copy(s:DiffHL[fs].0)
if !empty(ta)
for hm in ['term', 'cterm', 'gui']
let fa[hm] = has_key(fa, hm) ? fa[hm] . ',' . ta : ta
endfor
endif
call execute(['highlight clear ' . th,
\'highlight ' . th . ' ' .
\join(map(items(fa), 'join(v:val, "=")'))])
let s:DCharHL[ts] = th
endfor
" change diff highlights according to current DChar
call s:ToggleDiffHL(exists('t:DChar'))
endfunction
function! s:InitializeDiffChar() abort
" select current and next diff mode windows whose buffer is different
" do no initiate if more than 2 diff mode windows exist in a tab page and
" if a selected buffer already DChar highlighted in other tab pages
let cw = win_getid()
let cb = winbufnr(cw)
let nw = filter(map(range(winnr() + 1, winnr('$')) +
\range(1, winnr() - 1), 'win_getid(v:val)'),
\'getwinvar(v:val, "&diff") && winbufnr(v:val) != cb')
let nb = map(copy(nw), 'winbufnr(v:val)')
if !getwinvar(cw, '&diff') || empty(nw) || min(nb) != max(nb)
return -1
endif
for tn in filter(range(1, tabpagenr('$')), 'v:val != tabpagenr()')
let dc = s:Gettabvar(tn, 'DChar')
if !empty(dc)
for bn in values(dc.bnr)
if index([cb, nb[0]], bn) != -1
call s:EchoWarning('Both or either selected buffer already
\ highlighted in tab page ' . tn . '!')
return -1
endif
endfor
endif
endfor
" set diffchar highlights
call s:SetDiffCharHL()
" define a DiffChar dictionary on this tab page
let t:DChar = {}
" windowID and bufnr
let t:DChar.wid = {'1': cw, '2': nw[0]}
let t:DChar.bnr = {'1': cb, '2': nb[0]}
" diff mode synchronization flag
let t:DChar.dsy = get(g:, 'DiffModeSync', 1)
" a multiple of the current visible page to locally detect diff lines
let t:DChar.dfp = get(g:, 'DiffFocalPages', 3)
" top/bottom/last lines, cursor line/column, changenr on each window
let t:DChar.lcc = s:GetLineColCnr(1)
" a list of diff focus lines
let t:DChar.dfl = s:FocusDiffLines(1, 1)
" a type of diff pair visible
let pv = get(t:, 'DiffPairVisible', g:DiffPairVisible)
if (pv == 3 || pv == 4) && !(s:VF.PopupWindow || s:VF.FloatingWindow) ||
\pv == 4 && !s:VF.GetMousePos
let pv = 1
endif
let t:DChar.dpv = {'pv': pv}
if 0 < pv
let t:DChar.dpv.ch = {}
if pv == 3 || pv == 4
let t:DChar.dpv.pw = s:VF.PopupWindow ? 0 :
\s:VF.FloatingWindow ? {'fb': -1, 'fw': -1} : -1
endif
endif
" a list of highlight IDs per line
let t:DChar.mid = {'1': {}, '2': {}}
" a list of added/deleted/changed columns per line
let t:DChar.hlc = {'1': {}, '2': {}}
" checksum per line
let t:DChar.cks = {'1': {}, '2': {}}
" ignorecase and ignorespace flags
let do = split(&diffopt, ',')
let t:DChar.igc = (index(do, 'icase') != -1)
let t:DChar.igs = (index(do, 'iwhiteall') != -1) ? 1 :
\(index(do, 'iwhite') != -1) ? 2 :
\(index(do, 'iwhiteeol') != -1) ? 3 : 0
" a pattern to split difference units
let du = get(t:, 'DiffUnit', g:DiffUnit)
if du == 'Char' " any single character
let t:DChar.upa = '\zs'
elseif du == 'Word2' " non-space and space words
let t:DChar.upa = '\%(\s\+\|\S\+\)\zs'
elseif du == 'Word3' " \< or \> boundaries
let t:DChar.upa = '\<\|\>'
elseif du =~ '^CSV(.\+)$' " split characters
let s = escape(du[4 : -2], '^-]')
let t:DChar.upa = '\%([^'. s . ']\+\|[' . s . ']\)\zs'
elseif du =~ '^SRE(.\+)$' " split regular expression
let t:DChar.upa = du[4 : -2]
else
" \w\+ word and any \W character
let t:DChar.upa = '\%(\w\+\|\W\)\zs'
if du != 'Word1'
call s:EchoWarning('Not a valid difference unit type.
\ Use "Word1" instead.')
endif
endif
" a list of difference matching colors
let t:DChar.hgp = [s:DCharHL.T]
let dc = get(t:, 'DiffColors', g:DiffColors)
if 1 <= dc && dc <= 4
" select all available hl which has bg and has not attribute
let [fd, bd] = map(['fg#', 'bg#'], 'synIDattr(hlID("Normal"), v:val)')
let id = 1
while empty(fd) || empty(bd)
let nm = synIDattr(id, 'name')
if empty(nm) | break | endif
if id == synIDtrans(id)
if empty(fd) && synIDattr(id, 'bg') == 'fg'
let fd = synIDattr(id, 'bg#')
endif
if empty(bd) && synIDattr(id, 'fg') == 'bg'
let bd = synIDattr(id, 'fg#')
endif
endif
let id += 1
endwhile
let xb = map(values(s:DCharHL), 'synIDattr(hlID(v:val), "bg#")')
let hl = {}
let id = 1
while 1
let nm = synIDattr(id, 'name')
if empty(nm) | break | endif
if id == synIDtrans(id)
let [fg, bg, rv] = map(['fg#', 'bg#', 'reverse'],
\'synIDattr(id, v:val)')
if empty(fg) | let fg = fd | endif
if !empty(rv) | let bg = !empty(fg) ? fg : fd | endif
if !empty(bg) && bg != fg && bg != bd &&
\index(xb, bg) == -1 && empty(filter(map(['bold',
\'underline', 'undercurl', 'strikethrough', 'italic',
\'standout'],
\'synIDattr(id, v:val)'), '!empty(v:val)'))
let hl[bg] = nm
endif
endif
let id += 1
endwhile
let t:DChar.hgp += values(hl)[: ((dc == 1) ? 2 : (dc == 2) ? 6 :
\(dc == 3) ? 14 : -1)]
elseif dc == 100
let hl = {}
let id = 1
while 1
let nm = synIDattr(id, 'name')
if empty(nm) | break | endif
if index(values(s:DCharHL), nm) == -1 && id == synIDtrans(id) &&
\!empty(filter(['fg', 'bg', 'sp', 'bold', 'underline',
\'undercurl', 'strikethrough', 'reverse', 'inverse',
\'italic', 'standout'],
'!empty(synIDattr(id, v:val))'))
let hl[reltimestr(reltime())[-2 :] . id] = nm
endif
let id += 1
endwhile
let t:DChar.hgp += values(hl)
elseif -3 <= dc && dc <= -1
let t:DChar.hgp += ['SpecialKey', 'Search', 'CursorLineNr',
\'Visual', 'WarningMsg', 'StatusLineNC', 'MoreMsg',
\'ErrorMsg', 'LineNr', 'Conceal', 'NonText',
\'ColorColumn', 'ModeMsg', 'PmenuSel', 'Title']
\[: ((dc == -1) ? 2 : (dc == -2) ? 6 : -1)]
endif
endfunction
function! diffchar#ToggleDiffChar(lines) abort
if exists('t:DChar')
for k in [1, 2, 0]
if k == 0 | return | endif
if t:DChar.wid[k] == win_getid() | break | endif
endfor
for hl in keys(t:DChar.hlc[k])
if index(a:lines, eval(hl)) != -1
call diffchar#ResetDiffChar(a:lines)
return
endif
endfor
endif
call diffchar#ShowDiffChar(a:lines)
endfunction
function! diffchar#ShowDiffChar(...) abort
let init = !exists('t:DChar')
if init && s:InitializeDiffChar() == -1 | return | endif
for ak in [1, 2, 0]
if ak == 0 | return | endif
if t:DChar.wid[ak] == win_getid() | break | endif
endfor
let dl = []
for n in filter(map(copy(t:DChar.dfl[ak]),
\'(a:0 && index(a:1, v:val) == -1) ? -1 : v:key'), 'v:val != -1')
let [l1, l2] = [t:DChar.dfl[1][n], t:DChar.dfl[2][n]]
if !has_key(t:DChar.hlc[1], l1) && !has_key(t:DChar.hlc[2], l2)
let dl += [[{l1: getbufline(t:DChar.bnr[1], l1)[0]},
\{l2: getbufline(t:DChar.bnr[2], l2)[0]}]]
endif
endfor
if !init && empty(dl) | return | endif
let save_igc = &ignorecase | let &ignorecase = t:DChar.igc
let uu = []
for n in range(len(dl))
let [d1, d2] = dl[n]
for k in [1, 2]
let t = values(d{k})[0]
if t:DChar.igs
let u{k} = split(substitute(t, '\s\+$', '', ''), t:DChar.upa)
if t:DChar.igs == 1 " iwhiteall
call filter(u{k}, 'v:val !~ "^\\s\\+$"')
elseif t:DChar.igs == 2 " iwhite
let s = len(u{k}) - 1
while 0 < s
if u{k}[s - 1] . u{k}[s] =~ '^\s\+$'
let u{k}[s - 1] .= u{k}[s]
unlet u{k}[s]
endif
let s -= 1
endwhile
endif
else
let u{k} = split(t, t:DChar.upa)
endif
endfor
if u1 == u2 | let dl[n] = []
else | let uu += [[u1, u2]]
endif
endfor
call filter(dl, '!empty(v:val)')
let es = []
if s:VF.DiffExecutable
let [mu, mt, st] = [get(g:, 'DiffIntMaxUnits', 2000),
\get(g:, 'DiffIntMaxTime', 1.0), reltime()]
endif
for n in range(len(uu))
let [u1, u2] = uu[n]
if s:VF.DiffExecutable &&
\(mu < len(u1) + len(u2) || mt < reltimefloat(reltime(st)))
" the next line includes 2000+ units OR already spent 1.0+s
let es += s:ExecDiffCommand(uu[n :])
break
endif
if t:DChar.igs == 2 " iwhite
for k in [1, 2]
let u{k} = map(copy(u{k}),
\'(v:val =~ "^\\s\\+$") ? " " : v:val')
endfor
endif
let es += [s:TraceDiffChar(u1, u2)]
endfor
let lc = {'1': {}, '2': {}}
for n in range(len(dl))
let [d1, d2] = dl[n]
let [c1, c2] = s:GetDiffUnitPos(es[n], uu[n])
for k in [1, 2]
let [l, t] = items(d{k})[0]
if t:DChar.igs == 1 " iwhiteall
if t =~ '\s\+'
let ap = filter(range(1, len(t)), 't[v:val - 1] !~ "\\s"')
call map(c{k}, '[v:val[0],
\[ap[v:val[1][0] - 1], ap[v:val[1][1] - 1]]]')
endif
endif
let lc[k][l] = c{k}
let t:DChar.cks[k][l] = s:ChecksumStr(t)
endfor
endfor
let &ignorecase = save_igc
call s:HighlightDiffChar(ak, lc)
if !t:DChar.dsy && index(values(t:DChar.hlc), {}) != -1
unlet t:DChar
else
if init " set event when DChar HL is newly defined
call s:ToggleDiffCharEvent(1)
call s:ToggleDiffHL(1)
call s:ToggleDiffCharPair(1)
endif
if 0 < t:DChar.dpv.pv | call s:ShowDiffCharPair(ak) | endif
endif
endfunction
function! s:ExecDiffCommand(uu) abort
" prepare 2 input files for diff
for [k, u] in [[1, 0], [2, 1]]
" insert '|<number>:' before each line and
" add '=<number>:' at the beginning of each unit
let u{k} = [''] " a dummy to avoid 1st null unit error
for n in range(len(a:uu))
let u{k} += ['|' . n . ':'] +
\map(copy(a:uu[n][u]), '"=" . n . ":" . v:val')
endfor
let f{k} = tempname()
call writefile(u{k}, f{k})
endfor
" call diff in unified format and assign edit symbols [=+-] to each unit
let dc = ['diff', '-a', '--binary', ((t:DChar.igc == 1) ? '-i' : ''),
\((t:DChar.igs == 1) ? '-w' : (t:DChar.igs == 2) ? '-b' :
\(t:DChar.igs == 3) ? '-Z' : ''),
\'-d', '-U', len(u1) + len(u2), f1, f2]
let save_stmp = &shelltemp
let &shelltemp = 0
let dt = systemlist(join(dc))
let &shelltemp = save_stmp
for k in [1, 2] | call delete(f{k}) | endfor
return split(join(map(filter(dt, 'v:val =~ "^[ +-][|=]"'),
\'(v:val[0] != " ") ? v:val[0] : v:val[1]'), ''), '|')
endfunction
function! s:GetDiffUnitPos(es, uu) abort
let [u1, u2] = a:uu
if empty(u1)
return [[['d', [0, 0]]], [['a', [1, len(join(u2, ''))]]]]
elseif empty(u2)
return [[['a', [1, len(join(u1, ''))]]], [['d', [0, 0]]]]
endif
let [c1, c2] = [[], []]
let [l1, l2, p1, p2] = [1, 1, 0, 0]
for ed in split(a:es, '[+-]\+\zs', 1)[: -2]
let [qe, q1, q2] = [s:CountChar(ed, '='), s:CountChar(ed, '-'),
\s:CountChar(ed, '+')]
for k in [1, 2]
if 0 < qe
let [l{k}, p{k}] +=
\[len(join(u{k}[p{k} : p{k} + qe - 1], '')), qe]
endif
if 0 < q{k}
let l = l{k}
let [l{k}, p{k}] +=
\[len(join(u{k}[p{k} : p{k} + q{k} - 1], '')), q{k}]
let h{k} = [l, l{k} - 1]
else
let h{k} = [
\l{k} - ((0 < p{k}) ? len(strcharpart(u{k}[p{k} - 1],
\strchars(u{k}[p{k} - 1]) - 1, 1)) : 0),
\l{k} + ((p{k} < len(u{k})) ?
\len(strcharpart(u{k}[p{k}], 0, 1)) - 1 : -1)]
endif
endfor
let [r1, r2] = (q1 == 0) ? ['d', 'a'] :
\(q2 == 0) ? ['a', 'd'] : ['c', 'c']
let [c1, c2] += [[[r1, h1]], [[r2, h2]]]
endfor
return [c1, c2]
endfunction
function! s:TraceDiffChar(u1, u2) abort
" An O(NP) Sequence Comparison Algorithm
let [n1, n2] = [len(a:u1), len(a:u2)]
if a:u1 == a:u2 | return repeat('=', n1)
elseif n1 == 0 | return repeat('+', n2)
elseif n2 == 0 | return repeat('-', n1)
endif
" reverse to be N >= M
let [N, M, u1, u2, e1, e2] = (n1 >= n2) ?
\[n1, n2, a:u1, a:u2, '+', '-'] : [n2, n1, a:u2, a:u1, '-', '+']
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] < fp[k + 1]) ?
\[fp[k + 1], [e1, (k < D) ? p - 1 : p, k + 1]] :
\[fp[k - 1] + 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] .= '='
let [x, y] += [1, 1]
endwhile
let fp[k] = y
endfor
let etree += [epk]
endwhile
" create a shortest edit script (SES) from last p and k
let ses = ''
while 1
let ses = etree[p][k][0] . ses
if p == 0 && k == 0 | return ses[1 :] | endif
let [p, k] = etree[p][k][1 : 2]
endwhile
endfunction
function! diffchar#ResetDiffChar(...) abort
if !exists('t:DChar') | return | endif
let last = (a:0 && type(a:1) == type(0)) ? a:1 : 0
for k in [1, 2, 0]
if k == 0 | return | endif
if t:DChar.wid[k] == win_getid() | break | endif
endfor
let dl = {'1': [], '2': []}
for n in filter(map(copy(t:DChar.dfl[k]),
\'(a:0 && type(a:1) == type([]) &&
\index(a:1, v:val) == -1) ? -1 : v:key'), 'v:val != -1')
let [l1, l2] = [t:DChar.dfl[1][n], t:DChar.dfl[2][n]]
if has_key(t:DChar.hlc[1], l1) || has_key(t:DChar.hlc[2], l2)
let [dl[1], dl[2]] += [[l1], [l2]]
unlet t:DChar.cks[1][l1] | unlet t:DChar.cks[2][l2]
endif
endfor
if !last && empty(dl[k])| return | endif
call s:ClearDiffChar(k, dl)
if 0 < t:DChar.dpv.pv | call s:ClearDiffCharPair(k) | endif
if last || !t:DChar.dsy && index(values(t:DChar.hlc), {}) != -1
call s:ToggleDiffHL(0)
call s:ToggleDiffCharPair(0)
unlet t:DChar
call s:ToggleDiffCharEvent(0)
endif
endfunction
function! s:ToggleDiffCharEvent(on) abort
call execute(g:DiffCharInitEvent)
let tv = filter(map(range(1, tabpagenr('$')),
\'s:Gettabvar(v:val, "DChar")'), '!empty(v:val)')
if empty(tv) | return | endif
let ac = []
for td in tv
for k in [1, 2]
let bl = '<buffer=' . td.bnr[k] . '>'
if td.dsy
let ac += [['TextChanged,InsertLeave', bl,
\'s:UpdateDiffChar(' . k . ', 0)']]
if s:VF.DiffUpdated
let ac += [['DiffUpdated', bl,
\'s:UpdateDiffChar(' . k . ', 1)']]
endif
let ac += [[s:VF.WinClosed ? 'WinClosed' : 'BufWinLeave', bl,
\'s:WinClosedDiffChar()']]
if td.dfp != 0
let ac += [[
\s:VF.WinScrolled ? 'WinScrolled' : 'CursorMoved', bl,
\'s:ScrollDiffChar(' . k . ')']]
endif
endif
if 0 < td.dpv.pv
let ac += [['CursorMoved', bl,
\'s:ShowDiffCharPair(' . k . ')']]
endif
endfor
endfor
let ac += [['TabEnter', '*', 's:AdjustGlobalOption()']]
let ac += [['ColorScheme', '*', 's:SetDiffCharHL()']]
let ac += [[s:VF.WinClosed ? 'BufWinEnter' : 'BufWinEnter,WinEnter', '*',
\'s:RepairDiffChar()']]
if !s:VF.DiffUpdated
if s:VF.DiffOptionSet
let ac += [['OptionSet', 'diffopt', 's:FollowDiffOption()']]
elseif !empty(filter(tv, 'v:val.dsy'))
let s:save_ch = 's:ResetDiffModeSync()'
let ac += [['CursorHold', '*', s:save_ch]]
if !a:on | unlet s:save_ch | endif
call s:ChangeUTOpt(a:on)
endif
endif
call execute(map(ac, '"autocmd diffchar " . v:val[0] . " " . v:val[1] .
\" call " . v:val[2]'))
endfunction
function! s:FocusDiffLines(key, init) abort
" a:init : initialize dfl (do not use previous dfl)
let dfl = {}
let ks = (a:key == 1) ? [2, 1] : [1, 2]
" check all diff lines
if t:DChar.dfp == 0
if a:init
for k in ks
call s:WinGotoID(t:DChar.wid[k])
call s:WinExecute('let dfl[k] =
\s:GetDiffLines(1, t:DChar.lcc[k].ll)')
endfor
return dfl
else
return t:DChar.dfl
endif
endif
" check visible diff lines and merge with previous dfl
" 1. get dfl in current visible lines and return if no new in both wins
for k in ks
call s:WinGotoID(t:DChar.wid[k])
call s:WinExecute('let dfl[k] =
\s:GetDiffLines(t:DChar.lcc[k].tl, t:DChar.lcc[k].bl)')
endfor
if !a:init
let nd = 0
for k in ks
if !empty(dfl[k])
let [ti, bi] = [index(t:DChar.dfl[k], dfl[k][0]),
\index(t:DChar.dfl[k], dfl[k][-1])]
let nd += (ti == -1) || (bi == -1) ||
\(t:DChar.dfl[k][ti : bi] != dfl[k])
endif
endfor
if nd == 0 | return t:DChar.dfl | endif
let lh = {}
for k in ks
let lh[k] = empty(dfl[k]) ? {'l': 0, 'h': 0} :
\{'l': len(filter(copy(t:DChar.dfl[k]), 'v:val < dfl[k][0]')),
\'h': len(filter(copy(t:DChar.dfl[k]), 'dfl[k][-1] < v:val'))}
endfor
endif
" 2. get dfl in upper/lower lines and return if not found in both wins
for k in ks
let [fl, tl, bl, ll] =
\[1, t:DChar.lcc[k].tl, t:DChar.lcc[k].bl, t:DChar.lcc[k].ll]
if !a:init && 0 < t:DChar.dfp
if 0 < lh[k].l | let fl = t:DChar.dfl[k][lh[k].l - 1] + 1 | endif
if 0 < lh[k].h | let ll = t:DChar.dfl[k][-lh[k].h] - 1 | endif
endif
let [tz, bz] = [tl, bl]
let rc = min([(tl - fl) + (ll - bl),
\(abs(t:DChar.dfp) - 1) * (bl - tl + 1)])
if 0 < rc
let hc = (rc + 1) / 2
let [tr, br] = [tl - fl, ll - bl]
let tb = [hc <= tr, rc - hc <= br]
let [tc, bc] = (tb == [1, 1]) ? [hc, rc - hc] : (tb == [0, 1]) ?
\[tr, rc - tr] : (tb == [1, 0]) ? [rc - br, br] : [tr, br]
let [tz, bz] += [-tc, bc]
endif
call s:WinGotoID(t:DChar.wid[k])
call s:WinExecute('let dfl[k] = s:GetDiffLines(tz, tl - 1) + dfl[k] +
\s:GetDiffLines(bl + 1, bz)')
endfor
if empty(dfl[1]) && empty(dfl[2]) | return dfl | endif
if index(values(dfl), []) != -1
" 3. if no dfl found in either, set a reference line and search dfl
let [ek, fk] = empty(dfl[1]) ? [1, 2] : [2, 1]
let rl = {}
let [rl[ek], rl[fk]] = [{'l': 1, 'h': t:DChar.lcc[ek].ll},
\{'l': 1, 'h': t:DChar.lcc[fk].ll}]
if !a:init
if 0 < lh[fk].l
let [rl[ek].l, rl[fk].l] = [t:DChar.dfl[ek][lh[fk].l - 1],
\t:DChar.dfl[fk][lh[fk].l - 1]]
endif
if 0 < lh[fk].h
let [rl[ek].h, rl[fk].h] = [t:DChar.dfl[ek][-lh[fk].h],
\t:DChar.dfl[fk][-lh[fk].h]]
endif
endif
let sd = dfl[fk][0] - rl[fk].l < rl[fk].h - dfl[fk][-1]
call s:WinGotoID(t:DChar.wid[fk])
call s:WinExecute('let dc = len(sd ?
\s:GetDiffLines(rl[fk].l, dfl[fk][0] - 1) :
\s:GetDiffLines(dfl[fk][-1] + 1, rl[fk].h))')
let fc = len(dfl[fk])
call s:WinGotoID(t:DChar.wid[ek])
call s:WinExecute('let dfl[ek] =
\s:SearchDiffLines(sd, sd ? rl[ek].l : rl[ek].h, dc + fc)')
let dfl[ek] = sd ? dfl[ek][-fc :] : dfl[ek][: fc - 1]
call s:WinGotoID(t:DChar.wid[ks[1]])
else
" 4. set reference lines using the closest line of previous dfl
let rl = [[1, 1], [t:DChar.lcc[1].ll, t:DChar.lcc[2].ll]]
if !a:init && index(values(t:DChar.dfl), []) == -1
let rx = []
for k in ks
let rx += ((0 < lh[k].l) ? [lh[k].l - 1] : [0]) +
\((0 < lh[k].h) ? [-lh[k].h] : [-1])
endfor
let rl += map(rx,
\'[t:DChar.dfl[1][v:val], t:DChar.dfl[2][v:val]]')
endif
" 5. select a reference line which is closest to new dfl
let ds = map(copy(rl), 'abs(v:val[0] - (dfl[1][0] + dfl[1][-1]) / 2) +
\abs(v:val[1] - (dfl[2][0] + dfl[2][-1]) / 2)')
let ci = index(ds, min(ds))
let cl = {'1': rl[ci][0], '2': rl[ci][1]}
" 6. get # of dfl (+/-) between reference and top/bottom lines
let tb = {}
for k in ks
let [tl, bl, xl] = [dfl[k][0], dfl[k][-1], cl[k]]
let td = (tl <= xl) ? [tl, xl, 1] : [xl, tl, -1]
let bd = (bl <= xl) ? [bl, xl, 1] : [xl, bl, -1]
call s:WinGotoID(t:DChar.wid[k])
call s:WinExecute('let tb[k] =
\[(len(s:GetDiffLines(td[0], td[1])) - 1) * td[2],
\(len(s:GetDiffLines(bd[0], bd[1])) - 1) * bd[2]]')
endfor
" 7. search and adjust dfl above/below the top/bottom in each window
let dx = {'1': [0, 0, len(dfl[2])], '2': [0, 0, len(dfl[1])]}
let [tx, bx] = [tb[1][0] - tb[2][0], tb[1][1] - tb[2][1]]
let k = (tx < 0) ? 1 : (tx > 0) ? 2 : 0
if k != 0 | let dx[k][0] = abs(tx) | endif
let k = (bx < 0) ? 2 : (bx > 0) ? 1 : 0
if k != 0 | let dx[k][1] = abs(bx) | endif
for k in ks
call s:WinGotoID(t:DChar.wid[k])
call s:WinExecute('let [td, bd] =
\[s:SearchDiffLines(0, dfl[k][0] - 1, dx[k][0]),
\s:SearchDiffLines(1, dfl[k][-1] + 1, dx[k][1])]')
let [tx, bx] =
\[min([dx[k][2], len(td)]), min([dx[k][2], len(bd)])]
let dfl[k] = td[: tx - 1] + dfl[k] + bd[-bx :]
endfor
endif
" 8. merge with previous dfl when 0 < dfp
if !a:init && index(values(t:DChar.dfl), []) == -1 && 0 < t:DChar.dfp
for k in ks
call filter(dfl[k], 'index(t:DChar.dfl[k], v:val) == -1')
let dfl[k] = empty(dfl[k]) ? t:DChar.dfl[k] :
\(t:DChar.dfl[k][-1] < dfl[k][0]) ? t:DChar.dfl[k] + dfl[k] :
\(dfl[k][-1] < t:DChar.dfl[k][0]) ? dfl[k] + t:DChar.dfl[k] :
\filter(copy(t:DChar.dfl[k]), 'v:val < dfl[k][0]') + dfl[k] +
\filter(copy(t:DChar.dfl[k]), 'dfl[k][-1] < v:val')
endfor
endif
return dfl
endfunction
function! s:SearchDiffLines(sd, sl, sc) abort
" a:sd = direction (1:down, 0:up), a:sl = start line, a:sc = count
let dl = []
if 0 < a:sc
let sl = a:sl
if a:sd
while sl <= line('$')
let fl = foldclosedend(sl)
if fl != -1 | let sl = fl + 1 | endif
let dl += s:GetDiffLines(sl, min([sl + a:sc - 1, line('$')]))
if a:sc <= len(dl) | let dl = dl[: a:sc - 1] | break | endif
let sl += a:sc
endwhile
else
while 1 <= sl
let fl = foldclosed(sl)
if fl != -1 | let sl = fl - 1 | endif
let dl = s:GetDiffLines(max([sl - a:sc + 1, 1]), sl) + dl
if a:sc <= len(dl) | let dl = dl[-a:sc :] | break | endif
let sl -= a:sc
endwhile
endif
endif
return dl
endfunction
function! s:GetDiffLines(sl, el) abort
return (a:sl > a:el) ? [] :
\filter(filter(range(a:sl, a:el), 'foldlevel(v:val) == 0'),
\'index([s:DiffHL.C.id, s:DiffHL.T.id], diff_hlID(v:val, 1)) != -1')
endfunction
function! s:GetLineColCnr(key) abort
let lcc = {}
for k in (a:key == 1) ? [2, 1] : [1, 2]
call s:WinGotoID(t:DChar.wid[k])
call s:WinExecute('let lcc[k] =
\{"tl": line("w0"), "bl": line("w$"), "ll": line("$"),
\"cl": line("."), "cc": col("."), "cn": s:Changenr(), "ig": 0}')
call s:WinExecute('let [tl, bl] =
\[foldclosedend(lcc[k].tl), foldclosed(lcc[k].bl)]')
if tl != -1 | let lcc[k].tl = tl | endif
if bl != -1 | let lcc[k].bl = bl | endif
endfor
return lcc
endfunction
function! s:ScrollDiffChar(key) abort
if !exists('t:DChar') || t:DChar.wid[a:key] != win_getid()
return
endif
let lcc = s:GetLineColCnr(a:key)
let scl = 0
for k in [1, 2]
" check if a scroll happens in either window with no change on both
let scl += (t:DChar.lcc[k].cn != lcc[k].cn) ? -1 :
\([t:DChar.lcc[k].tl, t:DChar.lcc[k].bl] !=
\[lcc[k].tl, lcc[k].bl]) ? 1 : 0
let [t:DChar.lcc[k].tl, t:DChar.lcc[k].bl] = [lcc[k].tl, lcc[k].bl]
endfor
if 0 < scl
let dfl = s:FocusDiffLines(a:key, 0)
if t:DChar.dfl != dfl
" reset/show DChar lines on dfl changes
if t:DChar.dfp < 0
let ddl = filter(copy(t:DChar.dfl[a:key]),
\'index(dfl[a:key], v:val) == -1')
if !empty(ddl) | call diffchar#ResetDiffChar(ddl) | endif
endif
let adl = filter(copy(dfl[a:key]),
\'index(t:DChar.dfl[a:key], v:val) == -1')
let t:DChar.dfl = dfl
if !empty(adl) | call diffchar#ShowDiffChar(adl) | endif
endif
endif
endfunction
function! s:HighlightDiffChar(key, lec) abort
let hn = len(t:DChar.hgp)
for k in (a:key == 1) ? [2, 1] : [1, 2]
if !s:VF.WinIDinMatch | call s:WinGotoID(t:DChar.wid[k]) | endif
for [l, ec] in items(a:lec[k])
if has_key(t:DChar.mid[k], l) | continue | endif
let t:DChar.hlc[k][l] = ec
" collect all the column positions per highlight group
let hc = {}
let cn = 0
for [e, c] in ec
if e == 'c'
let h = t:DChar.hgp[cn % hn]
let cn += 1
elseif e == 'a'
let h = s:DCharHL.A
elseif e == 'd'
if c == [0, 0] | continue | endif
let h = s:DCharHL.E
endif
if !has_key(hc, h) | let hc[h] = [] | endif
let hc[h] += [[l, c[0], c[1] - c[0] + 1]]
endfor
let t:DChar.mid[k][l] = [s:Matchaddpos(s:DCharHL.C, [[l]], -5, -1,
\{'window': t:DChar.wid[k]})]
for [h, c] in items(hc)
let t:DChar.mid[k][l] += map(range(0, len(c) - 1, 8),
\'s:Matchaddpos(h, c[v:val : v:val + 7], -3, -1,
\{"window": t:DChar.wid[k]})')
endfor
endfor
endfor
endfunction
function! s:ClearDiffChar(key, lines) abort
for k in (a:key == 1) ? [2, 1] : [1, 2]
if win_id2win(t:DChar.wid[k]) != 0
if !s:VF.WinIDinMatch | call s:WinGotoID(t:DChar.wid[k]) | endif
for l in a:lines[k]
silent! call map(t:DChar.mid[k][l],
\'s:Matchdelete(v:val, t:DChar.wid[k])')
endfor
endif
for l in a:lines[k]
unlet t:DChar.mid[k][l]
unlet t:DChar.hlc[k][l]
endfor
endfor
endfunction
function! s:ShiftDiffChar(key, lines, shift) abort
let [lid, hlc, cks] = [[], {}, {}]
for l in filter(copy(a:lines), 'has_key(t:DChar.mid[a:key], v:val)')
let lid += [[l, t:DChar.mid[a:key][l]]]
let hlc[l + a:shift] = t:DChar.hlc[a:key][l]
let cks[l + a:shift] = t:DChar.cks[a:key][l]
unlet t:DChar.mid[a:key][l]
unlet t:DChar.hlc[a:key][l]
unlet t:DChar.cks[a:key][l]
endfor
call extend(t:DChar.mid[a:key],
\s:ShiftMatchaddLines(t:DChar.wid[a:key], lid, a:shift))
call extend(t:DChar.hlc[a:key], hlc)
call extend(t:DChar.cks[a:key], cks)
endfunction
function! s:ShiftMatchaddLines(wid, lid, shift) abort
let lid = {}
let gm = s:Getmatches(a:wid)
for [l, id] in a:lid
let mx = filter(copy(gm), 'index(id, v:val.id) != -1')
call map(copy(mx), 's:Matchdelete(v:val.id, a:wid)')
let lid[l + a:shift] = map(reverse(mx), 's:Matchaddpos(v:val.group,
\map(filter(items(v:val), "v:val[0] =~ ''^pos\\d\\+$''"),
\"[v:val[1][0] + a:shift] + v:val[1][1 :]"), v:val.priority, -1,
\{"window": a:wid})')
endfor
return lid
endfunction
function! s:UpdateDiffChar(key, event) abort
" a:event : 0 = TextChanged/InsertLeave, 1 = DiffUpdated
if mode(1) != 'n' || !exists('t:DChar') ||
\!empty(filter(values(t:DChar.wid), '!getwinvar(v:val, "&diff")'))
return
endif
if !s:VF.DiffUpdated | call s:RedrawDiffChar(a:key, 1) | return | endif
" try to redraw updated DChar lines at the last DiffUpdated which comes
" just after TextChanged or InsertLeave if text changed
if a:event == 1
if t:DChar.lcc[a:key].ig == 0
if t:DChar.lcc[a:key].cn == s:Changenr()
call s:RedrawDiffChar(a:key, 0)
else
" in case of e:, DiffUpdated happens 3 times,
" first, all dfl not diff highlighed but no line diff folded,
" then wait for the next two events
if &foldmethod == 'diff' && !empty(t:DChar.dfl[a:key]) &&
\empty(filter(copy(t:DChar.dfl[a:key]),
\'diff_hlID(v:val, 1) != 0')) &&
\empty(filter(range(t:DChar.dfl[a:key][0],
\t:DChar.dfl[a:key][-1]), '0 < foldlevel(v:val)'))
let t:DChar.lcc[a:key].ig += 1
endif
endif
elseif t:DChar.lcc[a:key].ig == 1 " wait for another one
let t:DChar.lcc[a:key].ig += 1
elseif t:DChar.lcc[a:key].ig == 2 " the last one came then redraw
let t:DChar.lcc[a:key].ig = 0
call s:RedrawDiffChar(a:key, 1)
endif
else
if &diffopt =~ 'internal' && empty(&diffexpr)
if t:DChar.lcc[a:key].cn != s:Changenr()
let t:DChar.lcc[a:key].ig = 2 " wait for the next/last one
endif
else
call s:RedrawDiffChar(a:key, 1) " DiffUpdated not happen next
endif
endif
endfunction
function! s:RedrawDiffChar(key, txtcg) abort
" a:txtcg : 0 = for text unchanged, 1 = for text changed
let ll = t:DChar.lcc[a:key].ll
let t:DChar.lcc = s:GetLineColCnr(a:key)
let cfl = s:FocusDiffLines(a:key, 1)
if a:txtcg
" compare between previous and current DChar and diff lines
" using checksum and find ones to be deleted, added, and shifted
let lnd = t:DChar.lcc[a:key].ll - ll
let bk = (a:key == 1) ? 2 : 1
let [pfa, pfb] = [t:DChar.dfl[a:key], t:DChar.dfl[bk]]
let [cfa, cfb] = [cfl[a:key], cfl[bk]]
let m = min([len(pfa), len(cfa)])
if pfa == cfa
let ddl = []
for s in range(m)
if pfb[s] != cfb[s] || get(t:DChar.cks[a:key], pfa[s]) !=
\s:ChecksumStr(getline(cfa[s]))
let ddl += [pfa[s]]
endif
endfor
let adl = ddl
let sdl = []
else
let s = 0
while s < m && pfa[s] == cfa[s] && pfb[s] == cfb[s] &&
\get(t:DChar.cks[a:key], pfa[s]) ==
\s:ChecksumStr(getline(cfa[s]))
let s += 1
endwhile
let e = -1
let m -= s
while e >= -m && pfa[e] + lnd == cfa[e] && pfb[e] == cfb[e] &&
\get(t:DChar.cks[a:key], pfa[e]) ==
\s:ChecksumStr(getline(cfa[e]))
let e -= 1
endwhile
let ddl = pfa[s : e]
let adl = cfa[s : e]
let sdl = (lnd != 0 && e < -1) ? pfa[e + 1 :] : []
endif
" redraw updated DChar lines
if 0 < t:DChar.dpv.pv | call s:ClearDiffCharPair(a:key) | endif
if !empty(ddl) | call diffchar#ResetDiffChar(ddl) | endif
let t:DChar.dfl = cfl
if !empty(sdl) | call s:ShiftDiffChar(a:key, sdl, lnd) | endif
if !empty(adl) | call diffchar#ShowDiffChar(adl) | endif
else
" reset dfl and redraw all DChar lines on text unchanged
" (diffupdate and diffopt changes)
let do = split(&diffopt, ',')
let igc = (index(do, 'icase') != -1)
let igs = (index(do, 'iwhiteall') != -1) ? 1 :
\(index(do, 'iwhite') != -1) ? 2 :
\(index(do, 'iwhiteeol') != -1) ? 3 : 0
if [t:DChar.dfl, t:DChar.igc, t:DChar.igs] != [cfl, igc, igs]
call diffchar#ResetDiffChar()
let [t:DChar.dfl, t:DChar.igc, t:DChar.igs] = [cfl, igc, igs]
call diffchar#ShowDiffChar()
endif
endif
endfunction
function! diffchar#JumpDiffChar(dir, pos) abort
" a:dir : 0 = backward, 1 = forward / a:pos : 0 = start, 1 = end
if !exists('t:DChar') | return | endif
for k in [1, 2, 0]
if k == 0 | return | endif
if t:DChar.wid[k] == win_getid() | break | endif
endfor
let [ln, co] = [line('.'), col('.')]
if co == col('$') " empty line
if !a:dir | let co = 0 | endif
else
if a:pos
let co += len(strcharpart(getline(ln)[co - 1 :], 0, 1)) - 1
endif
endif
if has_key(t:DChar.hlc[k], ln) &&
\(a:dir ? co < t:DChar.hlc[k][ln][-1][1][a:pos] :
\co > t:DChar.hlc[k][ln][0][1][a:pos])
" found in current line
let hc = filter(map(copy(t:DChar.hlc[k][ln]), 'v:val[1][a:pos]'),
\a:dir ? 'co < v:val' : 'co > v:val')
let co = hc[a:dir ? 0 : -1]
else
if t:DChar.dfp == 0
" try to find the next highlighted line
let hl = filter(map(keys(t:DChar.hlc[k]), 'eval(v:val)'),
\a:dir ? 'ln < v:val' : 'ln > v:val')
if empty(hl) | return | endif
let ln = a:dir ? min(hl) : max(hl)
else
" try to find the next diff line and then apply hlc or scroll
let cp = [line('.'), col('.')]
while 1
let dl = s:SearchDiffLines(a:dir, a:dir ? ln + 1 : ln - 1, 1)
if empty(dl) | noautocmd call cursor(cp) | return | endif
let ln = dl[0]
if has_key(t:DChar.hlc[k], ln) | break | endif
noautocmd call cursor(ln, 0)
call s:ScrollDiffChar(k)
if has_key(t:DChar.hlc[k], ln) | break | endif
endwhile
endif
let co = t:DChar.hlc[k][ln][a:dir ? 0 : -1][1][a:pos]
endif
" set a dummy cursor position to adjust the start/end
if 0 < t:DChar.dpv.pv
call s:ClearDiffCharPair(k)
if [a:dir, a:pos] == [1, 0] " forward/start : rightmost
let [t:DChar.lcc[k].cl, t:DChar.lcc[k].cc] = [ln, col('$')]
elseif [a:dir, a:pos] == [0, 1] " backward/end : leftmost
let [t:DChar.lcc[k].cl, t:DChar.lcc[k].cc] = [ln, 0]
endif
endif
call cursor(ln, co)
endfunction
function! s:ShowDiffCharPair(key) abort
if mode(1) != 'n' || !exists('t:DChar') ||
\t:DChar.wid[a:key] != win_getid()
return
endif
let [pl, pc, pn] = [t:DChar.lcc[a:key].cl, t:DChar.lcc[a:key].cc,
\t:DChar.lcc[a:key].cn]
let [cl, cc] = [line('.'), col('.')]
if cc == col('$') | let cc = 0 | endif
let [t:DChar.lcc[a:key].cl, t:DChar.lcc[a:key].cc] = [cl, cc]
if pn != s:Changenr() | return | endif " do nothing on TextChanged
if !empty(t:DChar.dpv.ch)
" pair highlight exists
let [hl, hi] = t:DChar.dpv.ch.lc
let hc = t:DChar.hlc[a:key][hl][hi][1]
" inside the highlight, do nothing
if cl == hl && hc[0] <= cc && cc <= hc[1] | return | endif
call s:ClearDiffCharPair(a:key) " outside, clear it
endif
if has_key(t:DChar.hlc[a:key], cl)
let hu = filter(map(copy(t:DChar.hlc[a:key][cl]),
\'[v:key, v:val[1]]'), 'v:val[1][0] <= cc && cc <= v:val[1][1]')
if !empty(hu)
" for 2 contineous 'd', check if cursor moved forward or backward
let ix = (len(hu) == 1) ? 0 : (cl == pl) ? cc < pc : cl < pl
call s:HighlightDiffCharPair(a:key, cl, hu[ix][0])
endif
endif
endfunction
function! s:HighlightDiffCharPair(key, line, col) abort
let [ak, bk] = (a:key == 1) ? [1, 2] : [2, 1]
let [al, bl] = [a:line, t:DChar.dfl[bk][index(t:DChar.dfl[ak], a:line)]]
" set a pair cursor position (line, colnum) and match id
let t:DChar.dpv.ch.lc = [al, a:col]
let t:DChar.dpv.ch.bk = bk
" show a cursor-like highlight at the corresponding position
let bc = t:DChar.hlc[bk][bl][a:col][1]
if bc != [0, 0]
let [pos, len] = [bc[0], bc[1] - bc[0] + 1]
if !s:VF.WinIDinMatch | call s:WinGotoID(t:DChar.wid[bk]) | endif
let t:DChar.dpv.ch.id = s:Matchaddpos(s:DCharHL.c, [[bl, pos, len]],
\-1, -1, {'window': t:DChar.wid[bk]})
if !s:VF.WinIDinMatch | call s:WinGotoID(t:DChar.wid[ak]) | endif
else
let t:DChar.dpv.ch.id = -1 " no cursor hl on empty line
endif
call execute(['augroup diffchar2', 'autocmd!',
\'autocmd WinLeave <buffer=' . t:DChar.bnr[ak] .
\'> call s:ClearDiffCharPair(' . ak . ')', 'augroup END'])
if t:DChar.dpv.pv < 2 | return | endif
" show the corresponding unit in echo or popup-window
let at = getbufline(t:DChar.bnr[ak], al)[0]
let bt = getbufline(t:DChar.bnr[bk], bl)[0]
let [ae, ac] = t:DChar.hlc[ak][al][a:col]
if ae == 'c'
let hl = t:DChar.hgp[(count(map(t:DChar.hlc[ak][al][: a:col],
\'v:val[0]'), 'c') - 1) % len(t:DChar.hgp)]
let [tb, tx, te] = ['', bt[bc[0] - 1 : bc[1] - 1], '']
elseif ae == 'd'
let hl = s:DCharHL.A
let [tb, tx, te] = [(1 < bc[0]) ? '<' : '',
\bt[bc[0] - 1 : bc[1] - 1], (bc[1] < len(bt)) ? '>' : '']
elseif ae == 'a'
let hl = s:DCharHL.D
let [tb, tx, te] = [(1 < ac[0]) ? '>' : '',
\repeat((t:DChar.dpv.pv == 2 && s:VF.StrikeAttr) ? ' ' :
\(&fillchars =~ 'diff') ? matchstr(&fillchars, 'diff:\zs.') : '-',
\strwidth(at[ac[0] - 1 : ac[1] - 1])),
\(ac[1] < len(at)) ? '<' : '']
endif
if t:DChar.dpv.pv == 2
call execute(['echon tb', 'echohl ' . hl, 'echon tx', 'echohl None',
\'echon te'], '')
elseif t:DChar.dpv.pv == 3 || t:DChar.dpv.pv == 4
if t:DChar.dpv.pv == 4 | let mp = getmousepos() | endif
if s:VF.PopupWindow
call popup_move(t:DChar.dpv.pw, (t:DChar.dpv.pv == 3) ?
\{'line': 'cursor+1', 'col': 'cursor'} :
\{'line': mp.screenrow, 'col': mp.screencol})
call popup_settext(t:DChar.dpv.pw, tb . tx . te)
call popup_show(t:DChar.dpv.pw)
elseif s:VF.FloatingWindow
call nvim_win_set_config(t:DChar.dpv.pw.fw,
\extend((t:DChar.dpv.pv == 3) ?
\{'relative': 'cursor', 'row': 1, 'col': 0} :
\{'relative': 'editor', 'row': mp.screenrow,
\'col': mp.screencol},
\{'width': strdisplaywidth(tb . tx . te)}))
call setbufline(t:DChar.dpv.pw.fb, 1, tb . tx . te)
call setwinvar(t:DChar.dpv.pw.fw, '&winblend', 0)
endif
endif
endfunction
function! s:ClearDiffCharPair(key) abort
if !empty(t:DChar.dpv.ch)
let [bk, id] = [t:DChar.dpv.ch.bk, t:DChar.dpv.ch.id]
if id != -1 && win_id2win(t:DChar.wid[bk]) != 0
if !s:VF.WinIDinMatch | call s:WinGotoID(t:DChar.wid[bk]) | endif
silent! call s:Matchdelete(id, t:DChar.wid[bk])
if !s:VF.WinIDinMatch
call s:WinGotoID(t:DChar.wid[a:key])
endif
endif
call execute(['augroup diffchar2', 'autocmd!', 'augroup END',
\'augroup! diffchar2'])
let t:DChar.dpv.ch = {}
endif
if t:DChar.dpv.pv == 2 | call execute('echo', '')
elseif t:DChar.dpv.pv == 3 || t:DChar.dpv.pv == 4
if s:VF.PopupWindow | call popup_hide(t:DChar.dpv.pw)
elseif s:VF.FloatingWindow
call nvim_win_set_config(t:DChar.dpv.pw.fw,
\{'relative': 'editor', 'row': 0, 'col': 0, 'width': 1})
call setbufline(t:DChar.dpv.pw.fb, 1, '')
call setwinvar(t:DChar.dpv.pw.fw, '&winblend', 100)
endif
endif
endfunction
function! s:ToggleDiffCharPair(on) abort
if t:DChar.dpv.pv == 3 || t:DChar.dpv.pv == 4
if s:VF.PopupWindow
let t:DChar.dpv.pw = a:on ?
\popup_create('', {'hidden': 1, 'scrollbar': 0, 'wrap': 0,
\'highlight': s:DCharHL.c}) :
\popup_close(t:DChar.dpv.pw)
elseif s:VF.FloatingWindow
if a:on
let t:DChar.dpv.pw.fb = nvim_create_buf(0, 1)
let t:DChar.dpv.pw.fw = nvim_open_win(t:DChar.dpv.pw.fb, 0,
\{'relative': 'editor', 'row': 0, 'col': 0, 'height': 1,
\'width': 1, 'focusable': 0, 'style': 'minimal'})
call setbufline(t:DChar.dpv.pw.fb, 1, '')
call setwinvar(t:DChar.dpv.pw.fw, '&winblend', 100)
call setwinvar(t:DChar.dpv.pw.fw, '&winhighlight',
\'Normal:' . s:DCharHL.c)
else
call nvim_win_close(t:DChar.dpv.pw.fw, 1)
let t:DChar.dpv.pw = {'fb': -1, 'fw': -1}
endif
endif
endif
endfunction
function! diffchar#CopyDiffCharPair(dir) abort
" a:dir : 0 = get, 1 = put
if !exists('t:DChar') | return | endif
for ak in [1, 2, 0]
if ak == 0 | return | endif
if t:DChar.wid[ak] == win_getid() | break | endif
endfor
let bk = (ak == 1) ? 2 : 1
let un = -1
if 0 < t:DChar.dpv.pv
if !empty(t:DChar.dpv.ch) | let [al, un] = t:DChar.dpv.ch.lc | endif
else
let [al, co] = [line('.'), col('.')]
if co == col('$') | let co = 0 | endif
if has_key(t:DChar.hlc[ak], al)
let hc = filter(map(copy(t:DChar.hlc[ak][al]),
\'[v:key, v:val[1]]'),
\'v:val[1][0] <= co && co <= v:val[1][1]')
if !empty(hc) | let un = hc[0][0] | endif
endif
endif
if un == -1
call s:EchoWarning('Cursor is not on a difference unit!')
return
endif
let bl = t:DChar.dfl[bk][index(t:DChar.dfl[ak], al)]
let [ae, ac] = t:DChar.hlc[ak][al][un]
let [be, bc] = t:DChar.hlc[bk][bl][un]
let at = getbufline(t:DChar.bnr[ak], al)[0]
let bt = getbufline(t:DChar.bnr[bk], bl)[0]
let [x, y] = a:dir ? ['b', 'a'] : ['a', 'b'] " put : get
let s1 = (1 < {x}c[0]) ? {x}t[: {x}c[0] - 2] : ''
let s2 = ({x}e != 'a') ? {y}t[{y}c[0] - 1 : {y}c[1] - 1] : ''
if {x}e == 'd' && {x}c != [0, 0]
let ds = split({x}t[{x}c[0] - 1 : {x}c[1] - 1], '\zs')
let s2 = ((1 < {y}c[0]) ? ds[0] : '') . s2 .
\(({y}c[1] < len({y}t)) ? ds[-1] : '')
endif
let s3 = ({x}c[1] < len({x}t)) ? {x}t[{x}c[1] :] : ''
let ss = s1 . s2 . s3
if a:dir " put
call s:WinGotoID(t:DChar.wid[bk])
call s:WinExecute('noautocmd call setline(bl, ss)')
call s:WinExecute('call s:RedrawDiffChar(bk, 1)')
call s:WinGotoID(t:DChar.wid[ak])
else " get
call setline(al, ss)
endif
endfunction
function! diffchar#EchoDiffChar(lines, short) abort
if !exists('t:DChar') | return | endif
for ak in [1, 2, 0]
if ak == 0 | return | endif
if t:DChar.wid[ak] == win_getid() | break | endif
endfor
let bk = (ak == 1) ? 2 : 1
let nw = max([&numberwidth - 1, len(string(line('$')))])
let ec = []
for al in a:lines
let gt = []
if &number || &relativenumber
let gt += [[s:DCharHL.n, printf('%'. nw . 'd ',
\(&relativenumber ? abs(al - line('.')) : al))]]
endif
let at = getbufline(t:DChar.bnr[ak], al)[0]
if !has_key(t:DChar.hlc[ak], al)
if a:short | continue | endif
let gt += [['', empty(at) ? "\n" : at]]
else
let bl = t:DChar.dfl[bk][index(t:DChar.dfl[ak], al)]
let bt = getbufline(t:DChar.bnr[bk], bl)[0]
let hl = repeat('C', len(at))
let tx = at
for an in range(len(t:DChar.hlc[ak][al]) - 1, 0, -1)
let [ae, ac] = t:DChar.hlc[ak][al][an]
" enclose highlight and text in '[+' and '+]'
" if strike not available
if ae == 'c' || ae == 'a'
let it = at[ac[0] - 1 : ac[1] - 1]
if !s:VF.StrikeAttr | let it = '[+' . it . '+]' | endif
let ih = repeat((ae == 'a') ? 'A' : 'T', len(it))
let hl = ((1 < ac[0]) ? hl[: ac[0] - 2] : '') . ih .
\hl[ac[1] :]
let tx = ((1 < ac[0]) ? tx[: ac[0] - 2] : '') . it .
\tx[ac[1] :]
endif
" enclose corresponding changed/deleted units in '[-' and '-]'
" if strike not available,
" and insert them to highlight and text
if ae == 'c' || ae == 'd'
let bc = t:DChar.hlc[bk][bl][an][1]
let it = bt[bc[0] - 1 : bc[1] - 1]
if !s:VF.StrikeAttr | let it = '[-' . it . '-]' | endif
let ih = repeat('D', len(it))
if ae == 'c'
let hl = ((1 < ac[0]) ? hl[: ac[0] - 2] : '') . ih .
\hl[ac[0] - 1 :]
let tx = ((1 < ac[0]) ? tx[: ac[0] - 2] : '') . it .
\tx[ac[0] - 1 :]
else
if ac[0] == 1 && bc[0] == 1
let hl = ih . hl
let tx = it . tx
else
let ix = ac[0] +
\len(strcharpart(at[ac[0] - 1 :], 0, 1)) - 2
let hl = hl[: ix] . ih . hl[ix + 1 :]
let tx = tx[: ix] . it . tx[ix + 1 :]
endif
endif
endif
endfor
let sm = a:short && &columns <= strdisplaywidth(tx)
let ix = 0
let tn = 0
for h in split(hl, '\%(\(.\)\1*\)\zs')
if h[0] == 'T'
let g = t:DChar.hgp[tn % len(t:DChar.hgp)]
let tn += 1
else
let g = s:DCharHL[h[0]]
endif
let t = tx[ix : ix + len(h) - 1]
if sm && h[0] == 'C'
let s = split(t, '\zs')
if ix == 0 && 1 < len(s) &&
\3 < strdisplaywidth(join(s[: -2], ''))
let t = '...' . s[-1]
elseif ix + len(h) == len(tx) && 1 < len(s) &&
\3 < strdisplaywidth(join(s[1 :], ''))
let t = s[0] . '...'
elseif 2 < len(s) &&
\3 < strdisplaywidth(join(s[1 : -2], ''))
let t = s[0] . '...' . s[-1]
endif
endif
let gt += [[g, t]]
let ix += len(h)
endfor
endif
let ec += ['echo ""']
for [g, t] in gt
let ec += ['echohl ' . g, 'echon "' . escape(t, '"') . '"']
endfor
let ec += ['echohl None']
endfor
call execute(ec, '')
endfunction
function! diffchar#DiffCharExpr() abort
let [f1, f2] = [readfile(v:fname_in), readfile(v:fname_new)]
call writefile(([f1, f2] == [['line1'], ['line2']]) ? ['1c1'] :
\(s:VF.DiffExecutable && len(f1) + len(f2) > 100) ?
\s:ExtDiffExpr(v:fname_in, v:fname_new) :
\s:IntDiffExpr(f1, f2), v:fname_out)
endfunction
function! s:IntDiffExpr(f1, f2) abort
let [f1, f2] = [a:f1, a:f2]
let do = split(&diffopt, ',')
let save_igc = &ignorecase
let &ignorecase = (index(do, 'icase') != -1)
if index(do, 'iwhiteall') != -1
for k in [1, 2]
call map(f{k}, 'substitute(v:val, "\\s\\+", "", "g")')
endfor
elseif index(do, 'iwhite') != -1
for k in [1, 2]
call map(f{k}, 'substitute(v:val, "\\s\\+", " ", "g")')
call map(f{k}, 'substitute(v:val, "\\s\\+$", "", "")')
endfor
elseif index(do, 'iwhiteeol') != -1
for k in [1, 2]
call map(f{k}, 'substitute(v:val, "\\s\\+$", "", "")')
endfor
endif
let dfcmd = []
let [l1, l2] = [1, 1]
for ed in split(s:TraceDiffChar(f1, f2), '[+-]\+\zs', 1)[: -2]
let [qe, q1, q2] = [s:CountChar(ed, '='), s:CountChar(ed, '-'),
\s:CountChar(ed, '+')]
let [l1, l2] += [qe, qe]
let dfcmd += [((1 < q1) ? l1 . ',' : '') . (l1 + q1 - 1) .
\((q1 == 0) ? 'a' : (q2 == 0) ? 'd' : 'c') .
\((1 < q2) ? l2 . ',' : '') . (l2 + q2 - 1)]
let [l1, l2] += [q1, q2]
endfor
let &ignorecase = save_igc
return dfcmd
endfunction
function! s:ExtDiffExpr(f1, f2) abort
let do = split(&diffopt, ',')
let dc = ['diff', '-a', '--binary',
\((index(do, 'icase') != -1) ? '-i' : ''),
\((index(do, 'iwhiteall') != -1) ? '-w' :
\(index(do, 'iwhite') != -1) ? '-b' :
\(index(do, 'iwhiteeol') != -1) ? '-Z' : ''), a:f1, a:f2]
let save_stmp = &shelltemp
let &shelltemp = 0
let dt = systemlist(join(dc))
let &shelltemp = save_stmp
return filter(dt, 'v:val[0] =~ "\\d"')
endfunction
if s:VF.DiffOptionSet
function! diffchar#ToggleDiffModeSync(event) abort
" a:event : 0 = OptionSet diff, 1 = VimEnter
if !get(g:, 'DiffModeSync', 1) | return | endif
if s:VF.VOptionFixed
if a:event || v:option_old != v:option_new
call s:SwitchDiffChar(a:event || v:option_new)
endif
else
call s:SwitchDiffChar(a:event || &diff)
endif
endfunction
else
function! diffchar#SetDiffModeSync() abort
" DiffModeSync is triggered ON by FilterWritePost
if !get(g:, 'DiffModeSync', 1) | return | endif
if !exists('s:dmbuf')
" as a diff session, when FilterWritePos comes, current buf and
" other 1 or more buf should be diff mode
let s:dmbuf = map(filter(gettabinfo(tabpagenr())[0].windows,
\'getwinvar(v:val, "&diff")'), 'winbufnr(v:val)')
if index(s:dmbuf, bufnr('%')) == -1 ||
\min(s:dmbuf) == max(s:dmbuf)
" not a diff session, then clear
unlet s:dmbuf
return
endif
" wait for the contineous 1 or more FilterWitePost (diff) or
" 1 ShellFilterPost (non diff)
call execute('autocmd! diffchar ShellFilterPost *
\ call s:ClearDiffModeSync()')
" prepare to complete sync just in case for accidents
let s:id = timer_start(0, function('s:CompleteDiffModeSync'))
endif
" check if all the FilterWritePost has come
if empty(filter(s:dmbuf, 'v:val != bufnr("%")'))
call s:CompleteDiffModeSync(0)
endif
endfunction
function! s:CompleteDiffModeSync(id) abort
if exists('s:id')
if a:id == 0 | call timer_stop(s:id) | endif
unlet s:id
else
if exists('s:save_ch') && !empty(s:save_ch)
call execute('autocmd! diffchar CursorHold * call ' .
\s:save_ch)
call s:ChangeUTOpt(1)
else
call execute('autocmd! diffchar CursorHold *')
call s:ChangeUTOpt(0)
endif
silent call feedkeys("g\<Esc>", 'n')
endif
call s:ClearDiffModeSync()
call timer_start(0, function('s:SwitchDiffChar'))
endfunction
function! s:ClearDiffModeSync() abort
unlet s:dmbuf
call execute('autocmd! diffchar ShellFilterPost *')
endfunction
function! s:ResetDiffModeSync() abort
" DiffModeSync is triggered OFF by CursorHold
if exists('t:DChar') && t:DChar.dsy &&
\!empty(filter(values(t:DChar.wid), '!getwinvar(v:val, "&diff")'))
" if either or both of DChar win is now non-diff mode,
" reset it and show with current diff mode wins
call s:SwitchDiffChar(0)
endif
endfunction
endif
function! s:SwitchDiffChar(on) abort
let cw = win_getid()
let aw = cw
if exists('t:DChar') &&
\(a:on ? index(values(t:DChar.bnr), winbufnr(cw)) == -1 :
\index(values(t:DChar.wid), cw) != -1)
" diff mode ON on non-DChar buf || OFF on DChar win, try reset
for k in [1, 2]
if getwinvar(t:DChar.wid[k], '&diff')
let aw = t:DChar.wid[k]
call s:WinGotoID(aw)
call s:WinExecute('call diffchar#ResetDiffChar(1)')
call s:WinGotoID(cw)
break
endif
endfor
endif
if !exists('t:DChar') && get(g:, 'DiffModeSync', 1)
let aw = win_id2win(aw)
let dw = filter(map(range(aw, winnr('$')) + range(1, aw - 1),
\'win_getid(v:val)'), 'getwinvar(v:val, "&diff")')
if 1 < len(dw)
" 2 or more diff mode wins exists, try show
call s:WinGotoID(dw[0])
call s:WinExecute('call diffchar#ShowDiffChar()')
call s:WinGotoID(cw)
endif
endif
endfunction
function! s:WinClosedDiffChar() abort
" reset and show (if possible) DChar on WinClosed or BufWinLeave
for tn in range(1, tabpagenr('$'))
let dc = s:Gettabvar(tn, 'DChar')
if !empty(dc)
for k in [1, 2]
if s:VF.WinClosed ? dc.wid[k] == eval(expand('<afile>')) :
\dc.bnr[k] == eval(expand('<abuf>'))
let cw = win_getid()
call s:WinGotoID(dc.wid[k])
call s:WinExecute('call diffchar#ResetDiffChar(1)')
if dc.dsy
let dw = filter(gettabinfo(tn)[0].windows,
\'v:val != dc.wid[k] &&
\winbufnr(v:val) == dc.bnr[k] &&
\getwinvar(v:val, "&diff")')
if !empty(dw)
call s:WinGotoID(dw[0])
call s:WinExecute('call diffchar#ShowDiffChar()')
endif
endif
call s:WinGotoID(cw)
return
endif
endfor
endif
endfor
endfunction
function! s:RepairDiffChar() abort
" repair DChar whose win was accidentally closed on BufWinEnter/WinEnter
if exists('t:DChar')
let dc = t:DChar
let dw = filter(copy(dc.wid), 'win_id2win(v:val) != 0 &&
\winbufnr(v:val) == dc.bnr[v:key] && getwinvar(v:val, "&diff")')
if len(dw) == 1
let cw = win_getid()
call s:WinGotoID(values(dw)[0])
call s:WinExecute('call diffchar#ResetDiffChar(1)')
if dc.dsy
call s:WinExecute('call diffchar#ShowDiffChar()')
endif
call s:WinGotoID(cw)
endif
endif
endfunction
function! s:ChecksumStr(str) abort
return eval('0x' . sha256(a:str)[-4 :])
endfunction
function! s:EchoWarning(msg) abort
call execute(['echohl WarningMsg', 'echo a:msg', 'echohl None'], '')
endfunction
if s:VF.CountString
let s:CountChar = function('count')
else
function! s:CountChar(str, chr) abort
return len(a:str) - len(substitute(a:str, a:chr, '', 'g'))
endfunction
endif
if s:VF.GettabvarFixed
let s:Gettabvar = function('gettabvar')
else
function! s:Gettabvar(tp, var) abort
call gettabvar(a:tp, a:var) " call twice as a workaround
return gettabvar(a:tp, a:var)
endfunction
endif
if s:VF.ChangenrFixed
let s:Changenr = function('changenr')
else
function! s:Changenr() abort
let ute = undotree().entries
for n in range(len(ute))
if has_key(ute[n], 'curhead')
" if curhead exists, undotree().seq_cur should be this but not
" then changenr() returns a wrong number
return (0 < n) ? ute[n - 1].seq : 0
endif
endfor
return changenr()
endfunction
endif
if s:VF.WinExecute
function! s:WinGotoID(wid) abort
let s:WinExecute = function('win_execute', [a:wid])
endfunction
else
function! s:WinGotoID(wid) abort
noautocmd call win_gotoid(a:wid)
endfunction
let s:WinExecute = function('execute')
endif
if s:VF.WinIDinMatch
let s:Matchaddpos = function('matchaddpos')
let s:Matchdelete = function('matchdelete')
let s:Getmatches = function('getmatches')
else
function! s:Matchaddpos(grp, pos, pri, ...) abort
return matchaddpos(a:grp, a:pos, a:pri)
endfunction
function! s:Matchdelete(id, ...) abort
return matchdelete(a:id)
endfunction
function! s:Getmatches(...) abort
return getmatches()
endfunction
endif
function! s:AdjustGlobalOption() abort
if !s:VF.DiffUpdated && !s:VF.DiffOptionSet
call s:ChangeUTOpt(exists('t:DChar') && t:DChar.dsy)
endif
call s:ToggleDiffHL(exists('t:DChar'))
endfunction
if !s:VF.DiffUpdated
if s:VF.DiffOptionSet
function! s:FollowDiffOption() abort
if v:option_old != v:option_new
let cw = win_getid()
for dc in filter(map(range(1, tabpagenr('$')),
\'s:Gettabvar(v:val, "DChar")'), '!empty(v:val)')
call s:WinGotoID(dc.wid[1])
call s:WinExecute('call s:RedrawDiffChar(1, 0)')
endfor
call s:WinGotoID(cw)
endif
endfunction
else
function! s:ChangeUTOpt(on) abort
if a:on && !exists('s:save_ut')
let s:save_ut = &updatetime
let &updatetime = 500
elseif !a:on && exists('s:save_ut')
let &updatetime = s:save_ut
unlet s:save_ut
endif
endfunction
endif
endif
function! s:ToggleDiffHL(on) abort
" dh: 0 = original, 1 = for single color, 2 = for multi color
for dh in values(s:DiffHL)
call execute(['highlight clear ' . dh.nm, 'highlight ' . dh.nm . ' ' .
\join(map(items(dh[!a:on ? 0 : (len(t:DChar.hgp) == 1) ? 1 : 2]),
\'join(v:val, "=")'))])
endfor
endfunction
let &cpoptions = s:save_cpo
unlet s:save_cpo
" vim: ts=4 sw=4