1659 lines
54 KiB
VimL
1659 lines
54 KiB
VimL
" 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
|