You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

diffchar.vim 37KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177
  1. " diffchar.vim - Highlight the exact differences, based on characters and words
  2. "
  3. " This plugin has been developed in order to make diff mode more useful.
  4. " DiffText does not show the exact difference, but this plugin will
  5. " highlight its difference, character by character - so called DiffChar.
  6. "
  7. " Use this plugin just after diff command. DiffText area will be narrowed
  8. " down to show the DiffChar. You can use this plugin in non-diff usual
  9. " mode as well.
  10. "
  11. " For example, diff command shows: (<|DiffText area|>)
  12. "
  13. " (file A) The <|swift brown fox jumped over the lazy|> dog.
  14. " (file B) The <|lazy fox jumped over the swift brown|> dog.
  15. "
  16. " then this plugin will narrow down the DiffText area:
  17. "
  18. " (file A) The <|swift brown|> fox jumped over the <|lazy|> dog.
  19. " (file B) The <|lazy|> fox jumped over the <|swift brown|> dog.
  20. "
  21. " Since update 4.7, this plugin has set the DiffCharExpr() to the diffexpr
  22. " option, if it is empty. This function would be useful for smaller files.
  23. " If the total number of lines on both diff windows <= 200, by default,
  24. " it applies the internal difference algorithm to make the diff faster.
  25. " And, by default, it initially shows the exact differences for all lines
  26. " whenever diff mode begins. You can change these arguments of this function
  27. " like "set diffexpr=DiffCharExpr(100, 0)", but if prefer not to use this
  28. " enhancement, set g:DiffExpr = 0.
  29. "
  30. " Until update 5.0, this plugin has always traced the exact differences.
  31. " But for long and less-similar files and lines, it may take time to complete.
  32. " At 5.0, the g:DiffMaxRatio global (and tabpage) variable, which is an
  33. " assumption of how much % the differences exist at a maximum, is introduced.
  34. " Once the difference ratio actually exceeds g:DiffMaxRatio while tracing,
  35. " this plugin recursively splits the tracing at that unit and then joins each
  36. " results. Its default is 100%, meaning it still finds the exact differences
  37. " as before. Try to decrease this ratio if performance is more important than
  38. " diff accuracy.
  39. "
  40. " This plugin has been always positively supporting mulltibyte characters.
  41. "
  42. " Commands
  43. " :[range]SDChar - Highlight difference units for [range]
  44. " :[range]RDChar - Reset the highlight of difference units for [range]
  45. "
  46. " Configurable Keymaps
  47. " <Plug>ToggleDiffCharAllLines (default: <F7>)
  48. " toggle the highlight/reset of difference units for all lines
  49. " <Plug>ToggleDiffCharCurrentLine (default: <F8>)
  50. " toggle the highlight/reset of difference units for current line
  51. " <Plug>JumpDiffCharPrevStart (default: [b)
  52. " jump cursor to the start position of the previous difference unit
  53. " <Plug>JumpDiffCharNextStart (default: ]b)
  54. " jump cursor to the start position of the next difference unit
  55. " <Plug>JumpDiffCharPrevEnd (default: [e)
  56. " jump cursor to the end position of the previous difference unit
  57. " <Plug>JumpDiffCharNextEnd (default: ]e)
  58. " jump cursor to the end position of the next difference unit
  59. "
  60. " Global Variables (and Tabpage variables when using t:)
  61. " g:DiffUnit - type of difference unit
  62. " "Char" : any single character (default)
  63. " "Word1" : \w\+ word and any \W single character
  64. " "Word2" : non-space and space words
  65. " "Word3" : \< or \> character class boundaries
  66. " "CSV(,)" : separated by characters such as ',', ';', and '\t'
  67. " g:DiffColors - matching colors for changed unit pairs
  68. " 0 : always DiffText (default)
  69. " 1 : 4 colors in fixed order
  70. " 2 : 8 colors in fixed order
  71. " 3 : 16 colors in fixed order
  72. " 100 : all available colors in dynamic random order
  73. " (notes : always DiffAdd for added units)
  74. " g:DiffUpdate - interactively updating of highlightings while editing
  75. " 0 : disable (default)
  76. " 1 : enable
  77. " (notes : available on vim 7.4)
  78. " g:DiffMaxRatio - a maximum difference ratio to trace
  79. " 0 ~ 100 : (100% as default)
  80. "
  81. " DiffCharExpr(mxi, exd) function for the diffexpr option
  82. " mxi: the maximum number of total lines of both windows to apply internal
  83. " algorithm, apply external diff command when more lines
  84. " exd: 1 = initially show exact differences, 0 = vim original ones
  85. "
  86. " Update : 5.1
  87. " * Since vim 7.4.682, it has become impossible to overwrite the vim's diff
  88. " highlights with this plugin. Then, for example, DiffText bold typeface
  89. " will be left in all the diff highlighted lines (for more info, see
  90. " https://groups.google.com/forum/?hl=en_US#!topic/vim_use/1jQnbTva2fY).
  91. " This update provides a workaround to reduce its effect and to show the
  92. " differences mostly same as before.
  93. "
  94. " Update : 5.0
  95. " * Significantly improved the way to trace and show the differences and
  96. " make them 1.5 ~ 2.0 times faster.
  97. " * Introduced g:DiffMaxRatio (and t:DiffMaxRatio), a maximum difference
  98. " ratio to trace (100% as default). Once exceeds, the diff tracing is
  99. " recursively split and helps to keep performance instead of diff accuracy.
  100. " * Discontinued other difference algorithms (OND and Basic) than the ONP,
  101. " then g:DiffAlgorithm no longer supported.
  102. " * Improved to allow to specify one or more characters for "CSV(c)" in
  103. " g:DiffUnit (and t:DiffUnit). For example, "CSV(,:\t)" will split the
  104. " units by a comma, colon, and tab. Use '\\' for a backslash.
  105. "
  106. " Update : 4.9
  107. " * Fixed DiffCharExpr() to check the number of total lines, not different
  108. " lines only, of both windows and apply either internal algorithm or
  109. " external diff command, in order to keep the appropriate performance
  110. " for large files.
  111. "
  112. " Update : 4.81
  113. " * Enhanced to make DiffCharExpr() a bit faster by using uniq() or so.
  114. "
  115. " Update : 4.8
  116. " * Enhanced to set the threshold value on DiffCharExpr() to check how many
  117. " differences and then apply either of internal algorithm or external diff
  118. " command. The default for diffexpr option using DiffCharExpr() is changed
  119. " to use this threshold, 200 - apply internal if less than 200 differences,
  120. " apply external if more.
  121. " * Changed the way to select windows when more than 2 windows in the page.
  122. " - automatically select the diff mode's next (wincmd w) window, if any,
  123. " in addition to the current window
  124. " - can select any of splitted windows as vim can do for diff
  125. "
  126. " Update : 4.7
  127. " * Enhanced to set DiffCharExpr() to the diffexpr option, if it is empty.
  128. " When diff mode begins, vim calls this function which finds differences by
  129. " this plugin's internal diff algorithm (default) and then initially shows
  130. " the exact differences (default). You can also explicitly set this function
  131. " to the option with different arguments.
  132. " * Enhanced to make the key mappings configurable.
  133. " For example, the default <F7> can be modified by:
  134. " nmap <silent> "your favorite key" <Plug>ToggleDiffCharAllLines
  135. " * Fixed to correctly adjust the position of difference units when diffopt's
  136. " iwhite option is enabled.
  137. "
  138. " Update : 4.6
  139. " * Fixed to correctly show the colors of changed units in one-by-one defined
  140. " order of g:DiffColors. Since an added unit was improperly counted as
  141. " changed one, some colors were skipped and not shown. The first changed
  142. " unit is now always highlighted with DiffText in any color mode.
  143. "
  144. " Update : 4.5
  145. " * Fixed to trace the differences until the end of the units. Previously
  146. " the last same units were skipped, so last added units were sometimes shown
  147. " as changed ones (eg: the last "swift brown" on above were shown as changed
  148. " units but now shows "brown" as added ones).
  149. " * Enhanced to use your global variables if defined in vimrc.
  150. "
  151. " Update : 4.4
  152. " * Enhanced to follow diffopt's icase and iwhite options for both diff and
  153. " non-diff modes (ignorecase option is not used). Previously, it has been
  154. " always case and space/tab sensitive.
  155. " * Implemented to highlight the difference units using a new matchaddpos()
  156. " function, introduced in 7.4.330, when available to draw faster.
  157. "
  158. " Update : 4.3
  159. " * Enhanced to differently show added/deleted/changed difference units
  160. " with original diff highlightings.
  161. " - added units will be always highlighted with DiffAdd.
  162. " - changed units will be highlighted based on the g:DiffColors (and
  163. " t:DiffColors) variable, but DiffText is always used for the first
  164. " changed unit.
  165. " - when jumping cursor by "[b"/"]b" or "[e"/"]e" on the added unit, it
  166. " highlights around the corresponding deleted units with a cursor-type color
  167. " in another window, and echoes a diff-delete filler with DiffDelete,
  168. " along with common characters on both sides (e.g. a-----b).
  169. "
  170. " Update : 4.2
  171. " * Enhanced to update the highlighted DiffChar units while editing.
  172. " A g:DiffUpdate (and t:DiffUpdate) variable enables and disables (default)
  173. " this update behavior. If a text line was added/deleted, reset all the
  174. " highlightings. This feature is available on vim 7.4.
  175. "
  176. " Update : 4.1
  177. " * Implemented to echo a matching difference unit with its color when jumping
  178. " cursor by "[b"/"]b" or "[e"/"]e".
  179. " * Fixed defects: not using the new uniq() function introduced in vim 7.4.
  180. "
  181. " Update : 4.0
  182. " * Enhanced to easily find a corresponding pair of each difference unit.
  183. " - each unit pair will be shown in individual same color on both windows.
  184. " A g:DiffColors (and t:DiffColors) variable is a type of matching colors,
  185. " 0 (default) for always 1 color as before, 1/2/3 for 4/8/16 colors in
  186. " fixed order, and 100 for all available colors in dynamic random order.
  187. " - when jumping cursor by "[b"/"]b" or "[e"/"]e" in either window,
  188. " the start or end position of a matching unit will be highlighted with
  189. " a cursor-type color in another window.
  190. "
  191. " Update : 3.6
  192. " * Added two g:DiffUnit (and t:DiffUnit) types. "Word3" will split at the
  193. " \< or \> boundaries, which can separate based on the character class like
  194. " CJK, Hiragana, Katakana, Hangul, full width symbols and so on. And "CSV(c)"
  195. " will split the units by a specified character "c". For example, "CSV(,)"
  196. " and "CSV(\t)" can be used for comma and tab separated text.
  197. "
  198. " Update : 3.5
  199. " * Fixed defects: DiffChar highlighting units do not override/hide hlsearch.
  200. "
  201. " Update : 3.4
  202. " * Enhanced to support individual DiffChar handling on each tab page.
  203. " Difference unit and algorithm can also be set page by page using
  204. " tab page local variables, t:DiffUnit and t:DiffAlgorithm.
  205. "
  206. " Update : 3.3
  207. " * Enhanced to jump cursor to the DiffChar highlighting units. Sample keymaps
  208. " "]b" and "[b" will move cursor forwards to the next and backwards to the
  209. " previous start positions. And "]e" and "[e" will move to the end positions.
  210. "
  211. " Update : 3.2
  212. " * Enhanced to follow diff mode without any limitations. Compare between
  213. " the corresponding DiffChange lines on both windows and properly handle
  214. " DiffAdd and DiffDelete lines.
  215. "
  216. " Update : 3.1
  217. " * Enhanced to show/reset/toggle DiffChar highlightings on individual line
  218. " by line.
  219. " * Implemented the window layout handling.
  220. " - the DiffChar'ed windows will remain the highlightings even if the
  221. " window position is rotated/replaced/moved and another new window opens.
  222. " - if either DiffChar'ed window is closed, reset all the DiffChar
  223. " highlightings on another window.
  224. " * Removed limitations:
  225. " - when more than 2 windows exist, current and next (wincmd w) windows
  226. " will be selected.
  227. " - if the specified numbers of lines are different in both windows, ignore
  228. " the redundant lines and continue to compare the text on the same lines.
  229. " - RDChar sample command has a range attribute (e.g. %RDChar).
  230. " * Fixed defects:
  231. " - reset just DiffChar highlightings only and remain others.
  232. "
  233. " Update : 3.0
  234. " * Implemented word by word differences. A g:DiffUnit variable is a type of
  235. " a difference unit. Its default is "Char", which will trace character
  236. " by character as before. "Word1" will split into \w\+ words and
  237. " any \W single characters. And "Word2" will separate the units at the
  238. " \s\+ space boundaries.
  239. " * Improved the performance around 10%.
  240. "
  241. " Update : 2.1
  242. " * Coding changes in the O(NP) function for readability.
  243. "
  244. " Update : 2.0
  245. " * Implemented the O(NP) and O(ND) Difference algorithms to improve the
  246. " performance. This update uses the O(NP) by default, and can be changed
  247. " to the O(ND) if necessary, or to the basic algorithm implemented in
  248. " the initial version.
  249. "
  250. " Author: Rick Howe
  251. " Last Change: 2015/05/30
  252. " Created:
  253. " Requires:
  254. " Version: 5.1
  255. if exists("g:loaded_diffchar")
  256. finish
  257. endif
  258. let g:loaded_diffchar = 5.1
  259. let s:save_cpo = &cpo
  260. set cpo&vim
  261. " Commands
  262. command! -range SDChar call s:ShowDiffChar(<line1>, <line2>)
  263. command! -range RDChar call s:ResetDiffChar(<line1>, <line2>)
  264. " Configurable Keymaps
  265. nnoremap <silent> <Plug>ToggleDiffCharAllLines
  266. \ :call <SID>ToggleDiffChar(1, line('$'))<CR>
  267. nnoremap <silent> <Plug>ToggleDiffCharCurrentLine
  268. \ :call <SID>ToggleDiffChar(line('.'))<CR>
  269. nnoremap <silent> <Plug>JumpDiffCharPrevStart
  270. \ :call <SID>JumpDiffChar(0, 1)<CR>
  271. nnoremap <silent> <Plug>JumpDiffCharNextStart
  272. \ :call <SID>JumpDiffChar(1, 1)<CR>
  273. nnoremap <silent> <Plug>JumpDiffCharPrevEnd
  274. \ :call <SID>JumpDiffChar(0, 0)<CR>
  275. nnoremap <silent> <Plug>JumpDiffCharNextEnd
  276. \ :call <SID>JumpDiffChar(1, 0)<CR>
  277. if !hasmapto('<Plug>ToggleDiffCharAllLines', 'n')
  278. nmap <silent> <F7> <Plug>ToggleDiffCharAllLines
  279. endif
  280. if !hasmapto('<Plug>ToggleDiffCharCurrentLine', 'n')
  281. nmap <silent> <F8> <Plug>ToggleDiffCharCurrentLine
  282. endif
  283. if !hasmapto('<Plug>JumpDiffCharPrevStart', 'n')
  284. nmap <silent> [b <Plug>JumpDiffCharPrevStart
  285. endif
  286. if !hasmapto('<Plug>JumpDiffCharNextStart', 'n')
  287. nmap <silent> ]b <Plug>JumpDiffCharNextStart
  288. endif
  289. if !hasmapto('<Plug>JumpDiffCharPrevEnd', 'n')
  290. nmap <silent> [e <Plug>JumpDiffCharPrevEnd
  291. endif
  292. if !hasmapto('<Plug>JumpDiffCharNextEnd', 'n')
  293. nmap <silent> ]e <Plug>JumpDiffCharNextEnd
  294. endif
  295. " Set a difference unit type
  296. if !exists("g:DiffUnit")
  297. let g:DiffUnit = "Char" " any single character
  298. " let g:DiffUnit = "Word1" " \w\+ word and any \W single character
  299. " let g:DiffUnit = "Word2" " non-space and space words
  300. " let g:DiffUnit = "Word3" " \< or \> character class boundaries
  301. " let g:DiffUnit = "CSV(,)" " split characters
  302. endif
  303. " Set a difference unit matching colors
  304. if !exists("g:DiffColors")
  305. let g:DiffColors = 0 " always 1 color
  306. " let g:DiffColors = 1 " 4 colors in fixed order
  307. " let g:DiffColors = 2 " 8 colors in fixed order
  308. " let g:DiffColors = 3 " 16 colors in fixed order
  309. " let g:DiffColors = 100 " all available colors in dynamic random order
  310. endif
  311. " Set a difference unit updating while editing
  312. if !exists("g:DiffUpdate")
  313. if exists("##TextChanged") && exists("##TextChangedI")
  314. let g:DiffUpdate = 0 " disable
  315. " let g:DiffUpdate = 1 " enable
  316. endif
  317. endif
  318. " Set a maximum diff ratio to trace the unit differences
  319. if !exists("g:DiffMaxRatio")
  320. let g:DiffMaxRatio = 100 " how much % diffs exist at most
  321. endif
  322. " Set a diff expression
  323. if !exists("g:DiffExpr") || g:DiffExpr
  324. if empty(&diffexpr)
  325. let &diffexpr = "DiffCharExpr(200, 1)" " set # of lines and show exact diffs
  326. " let &diffexpr = "DiffCharExpr(0, 0)" " apply ext cmd and show vim original
  327. endif
  328. endif
  329. function! DiffCharExpr(mxi, exd)
  330. " read both files to be diff traced
  331. let [f1, f2] = [readfile(v:fname_in), readfile(v:fname_new)]
  332. " find the fist diff trial call and return here
  333. if [f1, f2] == [["line1"], ["line2"]]
  334. call writefile(["1c1"], v:fname_out)
  335. return
  336. endif
  337. " get a list of diff commands
  338. let dfcmd = (len(f1 + f2) <= a:mxi) ?
  339. \s:ApplyIntDiffAlgorithm(f1, f2) : s:ApplyExtDiffCommand()
  340. " write to output file
  341. call writefile(dfcmd, v:fname_out)
  342. " return if no need to show exact differences
  343. if exists("t:DChar") || !a:exd | return | endif
  344. " find 'c' command and extract the line to be replaced
  345. let [r1, r2] = [[], []]
  346. let cpt = '^\(\d\+\)\%(,\(\d\+\)\)\=c\(\d\+\)\%(,\(\d\+\)\)\=$'
  347. let rep = '\1 \2 \3 \4'
  348. for ct in filter(dfcmd, 'v:val =~ "c"')
  349. let cl = split(substitute(ct, cpt, rep, ''), ' ', 1)
  350. let nr = min([empty(cl[1]) ? 0 : cl[1] - cl[0],
  351. \empty(cl[3]) ? 0 : cl[3] - cl[2]])
  352. let [r1, r2] += [range(cl[0], cl[0] + nr),
  353. \range(cl[2], cl[2] + nr)]
  354. endfor
  355. " return if no replaced lines
  356. if empty(r1) | return | endif
  357. " initialize diffchar
  358. if s:InitializeDiffChar() == -1 | return | endif
  359. " check which window is v:fname_in/v:fname_new and
  360. " set those window numbers and replaced lines
  361. let t:DChar.win = {}
  362. let t:DChar.vdl = {}
  363. for w in range(1, winnr('$'))
  364. if !getwinvar(w, "&diff") | continue | endif
  365. for k in [1, 2]
  366. if !has_key(t:DChar.win, k) &&
  367. \getbufline(winbufnr(w), r{k}[-1]) ==#
  368. \[f{k}[r{k}[-1] - 1]]
  369. let [t:DChar.win[k], t:DChar.vdl[k]] = [w, r{k}]
  370. break
  371. endif
  372. endfor
  373. endfor
  374. " highlight the exact differences on the replaced lines
  375. if exists("t:DChar.win[1]") && exists("t:DChar.win[2]")
  376. call s:MarkDiffCharID(1)
  377. call s:ShowDiffChar(1, line('$'))
  378. else
  379. unlet t:DChar
  380. endif
  381. endfunction
  382. function! s:ApplyIntDiffAlgorithm(f1, f2)
  383. " handle icase and iwhite diff options
  384. let save_igc = &ignorecase
  385. let &ignorecase = (&diffopt =~ "icase")
  386. if &diffopt =~ "iwhite"
  387. for k in [1, 2]
  388. let f{k} = copy(a:f{k})
  389. call map(f{k}, 'substitute(v:val, "\\s\\+", " ", "g")')
  390. call map(f{k}, 'substitute(v:val, "\\s\\+$", "", "")')
  391. endfor
  392. else
  393. let [f1, f2] = [a:f1, a:f2]
  394. endif
  395. " trace the diff lines between f1/f2
  396. let dfcmd = []
  397. let [l1, l2] = [1, 1]
  398. for ed in split(s:TraceDiffChar(f1, f2), '\%(=\+\|[+-]\+\)\zs')
  399. let qn = len(ed)
  400. if ed[0] == '=' " one or more '='
  401. let [l1, l2] += [qn, qn]
  402. else " one or more '[+-]'
  403. let q1 = len(escape(ed, '-')) - qn
  404. let q2 = qn - q1
  405. let dfcmd += [
  406. \((q1 == 0) ? (l1 - 1) : (q1 == 1) ?
  407. \l1 : l1 . ',' . (l1 + q1 - 1)) .
  408. \((q1 == 0) ? 'a' : (q2 == 0) ? 'd' : 'c') .
  409. \((q2 == 0) ? (l2 - 1) : (q2 == 1) ?
  410. \l2 : l2 . ',' . (l2 + q2 - 1))]
  411. let [l1, l2] += [q1, q2]
  412. endif
  413. endfor
  414. " restore ignorecase flag
  415. let &ignorecase = save_igc
  416. return dfcmd
  417. endfunction
  418. function! s:ApplyExtDiffCommand()
  419. " execute a diff command
  420. let opt = "-a --binary "
  421. if &diffopt =~ "icase" | let opt .= "-i " | endif
  422. if &diffopt =~ "iwhite" | let opt .= "-b " | endif
  423. let dfout = system("diff " . opt . v:fname_in . " " . v:fname_new)
  424. " return diff commands only
  425. return filter(split(dfout, '\n'), 'v:val =~ "^\\d"')
  426. endfunction
  427. function! s:InitializeDiffChar()
  428. if winnr('$') < 2
  429. echo "Need more windows on this tab page!"
  430. return -1
  431. endif
  432. " define a DiffChar dictionary on this tab page
  433. let t:DChar = {}
  434. " select current window and next diff mode window if possible
  435. let t:DChar.win = {}
  436. let cwin = winnr()
  437. let w = 0
  438. while 1
  439. let nwin = (cwin + w) % winnr('$') + 1
  440. if nwin == cwin
  441. let nwin = cwin % winnr('$') + 1
  442. break
  443. endif
  444. if getwinvar(nwin, "&diff") | break | endif
  445. let w += 1
  446. endwhile
  447. let [t:DChar.win[1], t:DChar.win[2]] = [cwin, nwin]
  448. call s:MarkDiffCharID(1)
  449. " find corresponding DiffChange/DiffText lines on diff mode windows
  450. let t:DChar.vdl = {}
  451. let dh = [hlID("DiffChange"), hlID("DiffText")]
  452. for k in [1, 2]
  453. if getwinvar(t:DChar.win[k], "&diff")
  454. exec t:DChar.win[k] . "wincmd w"
  455. let t:DChar.vdl[k] = filter(range(1, line('$')),
  456. \'index(dh, diff_hlID(v:val, 1)) != -1')
  457. else
  458. unlet t:DChar.vdl
  459. break
  460. endif
  461. endfor
  462. exec cwin . "wincmd w"
  463. " set ignorecase and ignorespace flags
  464. let t:DChar.igc = (&diffopt =~ "icase")
  465. let t:DChar.igs = (&diffopt =~ "iwhite")
  466. " set line and its highlight id record
  467. let t:DChar.mid = {}
  468. let [t:DChar.mid[1], t:DChar.mid[2]] = [{}, {}]
  469. " set highlighted lines and columns record
  470. let t:DChar.hlc = {}
  471. let [t:DChar.hlc[1], t:DChar.hlc[2]] = [{}, {}]
  472. " set a difference unit type on this tab page and set a split pattern
  473. if !exists("t:DiffUnit")
  474. let t:DiffUnit = g:DiffUnit
  475. endif
  476. if t:DiffUnit == "Char" " any single character
  477. let t:DChar.spt = t:DChar.igs ? '\%(\s\+\|.\)\zs' : '\zs'
  478. elseif t:DiffUnit == "Word1" " \w\+ word and any \W character
  479. let t:DChar.spt = t:DChar.igs ? '\%(\s\+\|\w\+\|\W\)\zs' :
  480. \'\%(\w\+\|\W\)\zs'
  481. elseif t:DiffUnit == "Word2" " non-space and space words
  482. let t:DChar.spt = '\%(\s\+\|\S\+\)\zs'
  483. elseif t:DiffUnit == "Word3" " \< or \> boundaries
  484. let t:DChar.spt = '\<\|\>'
  485. elseif t:DiffUnit =~ '^CSV(.\+)$' " split characters
  486. let s = escape(t:DiffUnit[4 : -2], '^-]')
  487. let t:DChar.spt = '\%([^'. s . ']\+\|[' . s . ']\)\zs'
  488. elseif t:DiffUnit =~ '^SRE(.\+)$' " split regular expression
  489. let t:DChar.spt = t:DiffUnit[4 : -2]
  490. else
  491. let t:DChar.spt = t:DChar.igs ? '\%(\s\+\|.\)\zs' : '\zs'
  492. echo 'Not a valid difference unit type. Use "Char" instead.'
  493. endif
  494. " set a matching pair cursor id on this tab page
  495. let t:DChar.mpc = {}
  496. " set a difference matching colors on this tab page
  497. if !exists("t:DiffColors")
  498. let t:DiffColors = g:DiffColors
  499. endif
  500. " set types of difference matching colors from available highlights
  501. let t:DChar.dmc = ["DiffText"]
  502. if t:DiffColors == 1
  503. let t:DChar.dmc += ["NonText", "Search", "VisualNOS"]
  504. elseif t:DiffColors == 2
  505. let t:DChar.dmc += ["NonText", "Search", "VisualNOS",
  506. \"ErrorMsg", "MoreMsg", "TabLine", "Title"]
  507. elseif t:DiffColors == 3
  508. let t:DChar.dmc += ["NonText", "Search", "VisualNOS",
  509. \"ErrorMsg", "MoreMsg", "TabLine", "Title",
  510. \"StatusLine", "WarningMsg", "Conceal", "SpecialKey",
  511. \"ColorColumn", "ModeMsg", "SignColumn", "CursorLineNr"]
  512. elseif t:DiffColors == 100
  513. redir => hl | silent highlight | redir END
  514. let h = map(filter(split(hl, '\n'),
  515. \'v:val =~ "^\\S" && v:val =~ "="'), 'split(v:val)[0]')
  516. for c in ["DiffAdd", "DiffDelete", "DiffChange", "DiffText",
  517. \hlexists("Cursor") ? "Cursor" : "MatchParen"]
  518. unlet h[index(h, c)]
  519. endfor
  520. while !empty(h)
  521. let r = localtime() % len(h)
  522. let t:DChar.dmc += [h[r]] | unlet h[r]
  523. endwhile
  524. endif
  525. " set a difference unit updating on this tab page
  526. " and a record of line string values
  527. if exists("##TextChanged") && exists("##TextChangedI")
  528. if !exists("t:DiffUpdate")
  529. let t:DiffUpdate = g:DiffUpdate
  530. endif
  531. if t:DiffUpdate
  532. let t:DChar.lsv = {}
  533. endif
  534. endif
  535. " set a maximum difference ratio for the units
  536. if !exists("t:DiffMaxRatio")
  537. let t:DiffMaxRatio = g:DiffMaxRatio
  538. endif
  539. let t:DChar.mxr = t:DiffMaxRatio < 0 ? 0 :
  540. \t:DiffMaxRatio > 100 ? 100 : t:DiffMaxRatio
  541. endfunction
  542. function! s:ShowDiffChar(sl, el)
  543. " initialize when t:DChar is not defined
  544. if !exists("t:DChar") && s:InitializeDiffChar() == -1 | return | endif
  545. call s:RefreshDiffCharWin()
  546. let cwin = winnr()
  547. if cwin == t:DChar.win[1] | let k = 1
  548. elseif cwin == t:DChar.win[2] | let k = 2
  549. else | return | endif
  550. " set a possible DiffChar line list between sl and el
  551. if exists("t:DChar.vdl") " diff mode
  552. let [d1, d2] = s:GetDiffModeLines(k, a:sl, a:el)
  553. else " non-diff mode
  554. let [d1, d2] = [range(a:sl, a:el), range(a:sl, a:el)]
  555. endif
  556. " remove already highlighted lines and get those text
  557. for k in [1, 2]
  558. let hl = map(keys(t:DChar.hlc[k]), 'eval(v:val)')
  559. call filter(d{k}, 'index(hl, v:val) == -1')
  560. let t{k} = []
  561. for d in d{k}
  562. let t{k} += getbufline(winbufnr(t:DChar.win[k]), d)
  563. endfor
  564. let n{k} = len(t{k})
  565. endfor
  566. " remove redundant lines in either window
  567. if n1 > n2
  568. unlet t1[n2 - n1 :]
  569. unlet d1[n2 - n1 :]
  570. let n1 = n2
  571. elseif n1 < n2
  572. unlet t2[n1 - n2 :]
  573. unlet d2[n1 - n2 :]
  574. let n2 = n1
  575. endif
  576. " set ignorecase flag
  577. let save_igc = &ignorecase
  578. let &ignorecase = t:DChar.igc
  579. " a list of different lines and columns
  580. let [lc1, lc2] = [{}, {}]
  581. " compare each line and trace difference units
  582. for n in range(n1)
  583. " split each line to the difference units
  584. let [u1, u2] =
  585. \[split(t1[n], t:DChar.spt), split(t2[n], t:DChar.spt)]
  586. " set unit lists for tracing
  587. let [u1t, u2t] = [copy(u1), copy(u2)]
  588. " handle ignorespace option
  589. if t:DChar.igs
  590. for k in [1, 2]
  591. if !empty(u{k}t)
  592. " convert \s\+ to a single space
  593. call map(u{k}t, 'substitute
  594. \(v:val, "\\s\\+", " ", "g")')
  595. " remove/unlet the last \s\+$
  596. let u{k}t[-1] = substitute
  597. \(u{k}t[-1], '\s\+$', '', '')
  598. if empty(u{k}t[-1])
  599. unlet u{k}t[-1]
  600. endif
  601. endif
  602. endfor
  603. endif
  604. " skip diff tracing if no diff exists
  605. if u1t == u2t | continue | endif
  606. " start diff tracing
  607. let [c1, c2, p1, p2, l1, l2] = [[], [], 0, 0, 1, 1]
  608. for ed in split(s:TraceDiffChar(u1t, u2t),
  609. \'\%(=\+\|[+-]\+\)\zs')
  610. let qn = len(ed)
  611. if ed[0] == '=' " one or more '='
  612. let [l1, l2, p1, p2] += [
  613. \len(join(u1[p1 : p1 + qn - 1], '')),
  614. \len(join(u2[p2 : p2 + qn - 1], '')),
  615. \qn, qn]
  616. else " one or more '[+-]'
  617. let q1 = len(escape(ed, '-')) - qn
  618. let q2 = qn - q1
  619. let [e1, e2] = (q1 == 0) ? ['d', 'a'] :
  620. \(q2 == 0) ? ['a', 'd'] : ['c', 'c']
  621. for k in [1, 2]
  622. if q{k} > 0
  623. let r = len(join(u{k}[
  624. \p{k} : p{k} + q{k} - 1
  625. \], ''))
  626. let h{k} = [l{k}, l{k} + r - 1]
  627. let [l{k}, p{k}] += [r, q{k}]
  628. else
  629. let h{k} = [l{k} - 1, l{k}]
  630. endif
  631. endfor
  632. let [c1, c2] += [[[e1, h1]], [[e2, h2]]]
  633. endif
  634. endfor
  635. " add diff lines and columns to the list
  636. if !empty(c1) || !empty(c2)
  637. let [lc1[d1[n]], lc2[d2[n]]] = [c1, c2]
  638. endif
  639. endfor
  640. " restore ignorecase flag
  641. let &ignorecase = save_igc
  642. " highlight lines and columns and set events
  643. for k in [1, 2]
  644. exec t:DChar.win[k] . "wincmd w"
  645. call s:HighlightDiffChar(k, lc{k})
  646. if empty(t:DChar.hlc[k]) | continue | endif
  647. let buf = winbufnr(t:DChar.win[k])
  648. if !exists("#BufWinLeave#<buffer=" . buf . ">")
  649. exec "au BufWinLeave <buffer=" . buf .
  650. \"> call s:ResetDiffChar(1, line('$'))"
  651. endif
  652. if exists("t:DChar.lsv")
  653. if !exists("#TextChanged#<buffer=" . buf . ">")
  654. exec "au TextChanged <buffer=" . buf .
  655. \"> call s:UpdateDiffChar(" . k . ")"
  656. endif
  657. if !exists("#TextChangedI#<buffer=" . buf . ">")
  658. exec "au TextChangedI <buffer=" . buf .
  659. \"> call s:UpdateDiffChar(" . k . ")"
  660. endif
  661. let t:DChar.lsv[k] = s:GetLineStrValues(k)
  662. endif
  663. endfor
  664. exec cwin . "wincmd w"
  665. " reset t:DChar when no DiffChar highlightings in either buffer
  666. if empty(t:DChar.hlc[1]) || empty(t:DChar.hlc[2])
  667. call s:MarkDiffCharID(0)
  668. unlet t:DChar
  669. elseif has("patch-7.4.682")
  670. call s:ToggleDiffTextHL(1)
  671. endif
  672. endfunction
  673. function! s:ResetDiffChar(sl, el)
  674. if !exists("t:DChar") | return | endif
  675. call s:RefreshDiffCharWin()
  676. let cwin = winnr()
  677. if cwin == t:DChar.win[1] | let k = 1
  678. elseif cwin == t:DChar.win[2] | let k = 2
  679. else | return | endif
  680. " set a possible DiffChar line list between sl and el
  681. if exists("t:DChar.vdl") " diff mode
  682. let [d1, d2] = s:GetDiffModeLines(k, a:sl, a:el)
  683. else " non-diff mode
  684. let [d1, d2] = [range(a:sl, a:el), range(a:sl, a:el)]
  685. endif
  686. for k in [1, 2]
  687. " remove not highlighted lines
  688. let hl = map(keys(t:DChar.hlc[k]), 'eval(v:val)')
  689. call filter(d{k}, 'index(hl, v:val) != -1')
  690. exec t:DChar.win[k] . "wincmd w"
  691. call s:ClearDiffChar(k, d{k})
  692. " when no highlight exists, reset events
  693. if empty(t:DChar.hlc[k])
  694. let buf = winbufnr(t:DChar.win[k])
  695. exec "au! BufWinLeave <buffer=" . buf . ">"
  696. if exists("t:DChar.lsv")
  697. exec "au! TextChanged <buffer=" . buf . ">"
  698. exec "au! TextChangedI <buffer=" . buf . ">"
  699. unlet t:DChar.lsv[k]
  700. endif
  701. call s:ResetDiffCharPair(k)
  702. endif
  703. endfor
  704. exec cwin . "wincmd w"
  705. " reset t:DChar when no DiffChar highlightings in either buffer
  706. if empty(t:DChar.hlc[1]) || empty(t:DChar.hlc[2])
  707. if has("patch-7.4.682")
  708. call s:ToggleDiffTextHL(0)
  709. endif
  710. call s:MarkDiffCharID(0)
  711. unlet t:DChar
  712. endif
  713. endfunction
  714. function! s:ToggleDiffChar(...)
  715. if a:0 == 1 | let sl = a:1 | let el = a:1
  716. elseif a:0 == 2 | let sl = a:1 | let el = a:2
  717. else | return | endif
  718. if exists("t:DChar")
  719. for l in range(sl, el)
  720. if has_key(t:DChar.hlc[1], l) ||
  721. \has_key(t:DChar.hlc[2], l)
  722. call s:ResetDiffChar(sl, el)
  723. return
  724. endif
  725. endfor
  726. endif
  727. call s:ShowDiffChar(sl, el)
  728. endfunction
  729. function! s:HighlightDiffChar(key, lec)
  730. for [l, ec] in items(a:lec)
  731. if has_key(t:DChar.mid[a:key], l) | continue | endif
  732. if exists("*matchaddpos")
  733. let mid = [matchaddpos("DiffChange", [[l]], 0)]
  734. else
  735. let dl = '\%' . l . 'l'
  736. let mid = [matchadd("DiffChange", dl . '.', 0)]
  737. endif
  738. let n = 0
  739. for [e, c] in ec
  740. if e == 'c'
  741. let hl = t:DChar.dmc[n % len(t:DChar.dmc)]
  742. let n += 1
  743. elseif e == 'a' | let hl = "DiffAdd"
  744. else | continue
  745. endif
  746. if exists("*matchaddpos")
  747. let mid += [matchaddpos(hl,
  748. \[[l, c[0], c[1] - c[0] + 1]], 0)]
  749. else
  750. let dc = '\%>' . (c[0] - 1) . 'c\%<' .
  751. \(c[1] + 1) . 'c'
  752. let mid += [matchadd(hl, dl . dc, 0)]
  753. endif
  754. endfor
  755. let [t:DChar.mid[a:key][l], t:DChar.hlc[a:key][l]] = [mid, ec]
  756. endfor
  757. endfunction
  758. function! s:ClearDiffChar(key, lines)
  759. for l in a:lines
  760. if has_key(t:DChar.mid[a:key], l)
  761. call map(t:DChar.mid[a:key][l], 'matchdelete(v:val)')
  762. unlet t:DChar.mid[a:key][l]
  763. unlet t:DChar.hlc[a:key][l]
  764. endif
  765. endfor
  766. endfunction
  767. function! s:UpdateDiffChar(key)
  768. " if number of lines was changed, reset all
  769. if len(t:DChar.lsv[a:key]) != line('$')
  770. call s:ResetDiffChar(1, line('$'))
  771. return
  772. endif
  773. " save the current t:DChar settings except highlightings
  774. let sdc = deepcopy(t:DChar)
  775. let [sdc.mid[1], sdc.mid[2]] = [{}, {}]
  776. let [sdc.hlc[1], sdc.hlc[2]] = [{}, {}]
  777. " update only highlighted and current changed lines
  778. let lsv = s:GetLineStrValues(a:key)
  779. for l in map(keys(t:DChar.hlc[a:key]), 'eval(v:val)')
  780. if lsv[l - 1] != t:DChar.lsv[a:key][l - 1]
  781. call s:ResetDiffChar(l, l)
  782. if !exists("t:DChar") | let t:DChar = sdc | endif
  783. call s:MarkDiffCharID(1)
  784. call s:ShowDiffChar(l, l)
  785. endif
  786. endfor
  787. endfunction
  788. function! s:GetDiffModeLines(key, sl, el)
  789. " in diff mode, need to compare the different line between windows
  790. " if current window is t:DChar.win[1], narrow sl <= t:DChar.vdl[1]
  791. " <= el and get the corresponding lines from t:DChar.vdl[2]
  792. let [i, j] = (a:key == 1) ? [1, 2] : [2, 1]
  793. let [d1, d2] = [copy(t:DChar.vdl[1]), copy(t:DChar.vdl[2])]
  794. for l in range(len(d{i}))
  795. if d{i}[l] < a:sl || a:el < d{i}[l]
  796. let [d{i}[l], d{j}[l]] = [-1, -1]
  797. endif
  798. endfor
  799. call filter(d1, 'v:val != -1')
  800. call filter(d2, 'v:val != -1')
  801. return [d1, d2]
  802. endfunction
  803. function! s:GetLineStrValues(key)
  804. let hl = map(keys(t:DChar.hlc[a:key]), 'eval(v:val)')
  805. let lsv = []
  806. for l in range(1, line('$'))
  807. if index(hl, l) != -1
  808. let str = getbufline(winbufnr(t:DChar.win[a:key]), l)[0]
  809. let val = 0
  810. for n in range(len(str))
  811. let val += char2nr(str[n]) * (n + 1)
  812. endfor
  813. let lsv += [val]
  814. else
  815. let lsv += [-1]
  816. endif
  817. endfor
  818. return lsv
  819. endfunction
  820. function! s:JumpDiffChar(dir, pos)
  821. " dir : 1 = forward, else = backward
  822. " pos : 1 = start, else = end
  823. if !exists("t:DChar") | return | endif
  824. call s:RefreshDiffCharWin()
  825. let cwin = winnr()
  826. if cwin == t:DChar.win[1] | let k = 1
  827. elseif cwin == t:DChar.win[2] | let k = 2
  828. else | return | endif
  829. let found = 0
  830. let l = line('.')
  831. while !found && 1 <= l && l <= line('$')
  832. if has_key(t:DChar.hlc[k], l)
  833. if l == line('.')
  834. let c = col('.')
  835. if !a:pos
  836. " end pos workaround for multibyte char
  837. let c += len(matchstr(getbufline(
  838. \winbufnr(cwin), l)[0],
  839. \'.', c - 1)) - 1
  840. endif
  841. else
  842. let c = a:dir ? 0 : 99999
  843. endif
  844. let hc = map(copy(t:DChar.hlc[k][l]),
  845. \'(v:val[0] == "d") ? "" :
  846. \v:val[1][a:pos ? 0 : 1]')
  847. if !a:dir
  848. let c = - c
  849. call map(reverse(hc),
  850. \'empty(v:val) ? "" : - v:val')
  851. endif
  852. for n in range(len(hc))
  853. if !empty(hc[n]) && c < hc[n]
  854. let c = hc[n]
  855. if !a:dir
  856. let c = - c
  857. let n = len(hc) - n - 1
  858. endif
  859. call cursor(l, c)
  860. call s:ShowDiffCharPair(k, l, n, a:pos)
  861. let found = 1
  862. break
  863. endif
  864. endfor
  865. endif
  866. let l = a:dir ? l + 1 : l - 1
  867. endwhile
  868. endfunction
  869. function! s:ShowDiffCharPair(key, line, col, pos)
  870. let m = (a:key == 1) ? 2 : 1
  871. if exists("t:DChar.vdl") " diff mode
  872. let line = t:DChar.vdl[m][index(t:DChar.vdl[a:key], a:line)]
  873. else " non-diff mode
  874. let line = a:line
  875. endif
  876. let bl = getbufline(winbufnr(t:DChar.win[m]), line)[0]
  877. let [e, c] = t:DChar.hlc[m][line][a:col]
  878. if e == 'd'
  879. " deleted unit
  880. let pc = (0 < c[0]) ? split(bl[ : c[0] - 1], '\zs')[-1] : ""
  881. let nc = (c[0] < len(bl)) ? split(bl[c[0] : ], '\zs')[0] : ""
  882. " echo a-----b with DiffChange/DiffDelete
  883. echohl DiffChange
  884. echon pc
  885. echohl DiffDelete
  886. let col = t:DChar.hlc[a:key][a:line][a:col][1]
  887. echon repeat('-', strwidth(
  888. \getbufline(winbufnr(t:DChar.win[a:key]), a:line)[0]
  889. \[col[0] - 1 : col[1] - 1]))
  890. echohl DiffChange
  891. echon nc
  892. echohl None
  893. " set position/length for both side of deleted unit
  894. let clen = len(pc . nc)
  895. let cpos = c[0] - len(pc) + 1
  896. else
  897. " changed unit
  898. let dc = bl[c[0] - 1 : c[1] - 1]
  899. " echo the matching unit with its color
  900. exec "echohl " . t:DChar.dmc[
  901. \(count(map(t:DChar.hlc[m][line][: a:col],
  902. \'v:val[0]'), 'c') - 1) % len(t:DChar.dmc)]
  903. echon dc
  904. echohl None
  905. " set position/length for matching unit
  906. let clen = len(split(dc, '\zs')[a:pos ? 0 : -1])
  907. let cpos = a:pos ? c[0] : c[1] - clen + 1
  908. endif
  909. " show cursor on deleted unit or matching unit on another window
  910. exec t:DChar.win[m] . "wincmd w"
  911. call s:ResetDiffCharPair(m)
  912. if exists("*matchaddpos")
  913. let t:DChar.mpc[m] = matchaddpos(
  914. \hlexists("Cursor") ? "Cursor" : "MatchParen",
  915. \[[line, cpos, clen]], 0)
  916. else
  917. let t:DChar.mpc[m] = matchadd(
  918. \hlexists("Cursor") ? "Cursor" : "MatchParen",
  919. \'\%' . line . 'l\%>' . (cpos - 1) . 'c\%<' .
  920. \(cpos + clen) . 'c', 0)
  921. endif
  922. if !exists("#WinEnter#<buffer=" . winbufnr(t:DChar.win[m]) . ">")
  923. exec "au WinEnter <buffer=" . winbufnr(t:DChar.win[m]) .
  924. \"> call s:ResetDiffCharPair(" . m . ")"
  925. endif
  926. exec t:DChar.win[a:key] . "wincmd w"
  927. endfunction
  928. function! s:ResetDiffCharPair(key)
  929. if exists("t:DChar.mpc[a:key]")
  930. call matchdelete(t:DChar.mpc[a:key])
  931. unlet t:DChar.mpc[a:key]
  932. exec "au! WinEnter <buffer=" .
  933. \winbufnr(t:DChar.win[a:key]) . ">"
  934. echon ""
  935. endif
  936. endfunction
  937. function! s:MarkDiffCharID(on)
  938. " mark w:DCharID (0/1/2) on all windows, on : 1 = set, 0 = clear
  939. for win in range(1, winnr('$'))
  940. call setwinvar(win, "DCharID",
  941. \(win == t:DChar.win[1]) ? a:on * 1 :
  942. \(win == t:DChar.win[2]) ? a:on * 2 : 0)
  943. endfor
  944. endfunction
  945. function! s:RefreshDiffCharWin()
  946. " find diffchar windows and set their winnr to t:DChar.win again
  947. let t:DChar.win = {}
  948. for win in range(1, winnr('$'))
  949. let id = getwinvar(win, "DCharID", 0)
  950. if id | let t:DChar.win[id] = win | endif
  951. endfor
  952. endfunction
  953. " "An O(NP) Sequence Comparison Algorithm"
  954. " by S.Wu, U.Manber, G.Myers and W.Miller
  955. function! s:TraceDiffChar(u1, u2)
  956. let [l1, l2] = [len(a:u1), len(a:u2)]
  957. if l1 == 0 && l2 == 0 | return ''
  958. elseif l1 == 0 | return repeat('+', l2)
  959. elseif l2 == 0 | return repeat('-', l1)
  960. endif
  961. " reverse to be M >= N
  962. let [M, N, u1, u2, e1, e2] = (l1 >= l2) ?
  963. \[l1, l2, a:u1, a:u2, '+', '-'] :
  964. \[l2, l1, a:u2, a:u1, '-', '+']
  965. let D = M - N
  966. let fp = repeat([-1], M + N + 3)
  967. let etree = [] " [next edit, previous p, previous k]
  968. " set maximum p according to t:DChar.mxr
  969. let maxP = exists("t:DChar.mxr") ? N * t:DChar.mxr / 100.0 : N
  970. let ses = ''
  971. let p = 0
  972. while 1
  973. let etree += [repeat([['', 0, 0]], p * 2 + D + 1)]
  974. for k in range(-p, D - 1, 1) + range(D + p, D + 1, -1) + [D]
  975. let [x, etree[p][k]] = (fp[k - 1] < fp[k + 1]) ?
  976. \[fp[k + 1],
  977. \[e1, k < D ? p - 1 : p, k + 1]] :
  978. \[fp[k - 1] + 1,
  979. \[e2, k > D ? p - 1 : p, k - 1]]
  980. let y = x - k
  981. while x < M && y < N && u1[x] == u2[y]
  982. let etree[p][k][0] .= '='
  983. let [x, y] += [1, 1]
  984. endwhile
  985. let fp[k] = x
  986. endfor
  987. if fp[D] == M | break | endif
  988. " if p exceeds maxP, split the diff tracing recursively
  989. if p > maxP
  990. let [u1, u2] = (l1 >= l2) ?
  991. \[u1[x :], u2[y :]] : [u2[y :], u1[x :]]
  992. let ses = s:TraceDiffChar(u1, u2)
  993. break
  994. endif
  995. let p += 1
  996. endwhile
  997. " create a shortest edit script (SES) from last p and k
  998. while p != 0 || k != 0
  999. let [e, p, k] = etree[p][k]
  1000. let ses = e . ses
  1001. endwhile
  1002. let ses = etree[p][k][0] . ses
  1003. return ses[1:] " remove the first entry
  1004. endfunction
  1005. if has("patch-7.4.682")
  1006. function! s:ToggleDiffTextHL(on)
  1007. " no need in no-diff mode
  1008. if !exists("t:DChar.vdl") | return | endif
  1009. " number of tabpages where DiffText area has been overwritten
  1010. let tn = len(filter(map(range(1, tabpagenr('$')),
  1011. \'gettabvar(v:val, "DChar")'),
  1012. \'!empty(v:val) && exists("v:val.dtm")'))
  1013. if a:on
  1014. call s:OverwriteDiffTextArea()
  1015. if tn == 0
  1016. " disable HL at the first ON among all tabpages
  1017. let s:originalHL = &highlight
  1018. let &highlight = join(filter(split(s:originalHL, ','),
  1019. \'v:val[0] !~ "\\C[CT]"'), ',') . ",Cn,Tn"
  1020. endif
  1021. else
  1022. call s:RestoreDiffTextArea()
  1023. if tn == 1
  1024. " restore HL at the last OFF among all tabpages
  1025. let &highlight = s:originalHL
  1026. unlet s:originalHL
  1027. endif
  1028. endif
  1029. endfunction
  1030. function! s:OverwriteDiffTextArea()
  1031. " overwrite diff's DiffText area with DiffText match
  1032. if exists("t:DChar.dtm") | return | endif
  1033. let t:DChar.dtm = {}
  1034. let dc = hlID("DiffChange") | let dt = hlID("DiffText")
  1035. let cwin = winnr()
  1036. for k in [1, 2]
  1037. exec t:DChar.win[k] . "wincmd w"
  1038. let t:DChar.dtm[k] = []
  1039. for l in t:DChar.vdl[k]
  1040. let t:DChar.dtm[k] += [matchaddpos("DiffChange",
  1041. \[[l]], -1)]
  1042. if index([dc, dt], diff_hlID(l, 1)) != -1
  1043. " normal case
  1044. let c2 = col([l, '$'])
  1045. while c2 > 0 && diff_hlID(l, c2) != dt
  1046. let c2 -= 1
  1047. endwhile
  1048. if c2 == 0 | continue | endif
  1049. let c1 = 1
  1050. while c1 < c2 && diff_hlID(l, c1) != dt
  1051. let c1 += 1
  1052. endwhile
  1053. else
  1054. " DiffCharExpr() exceptional case
  1055. let h = t:DChar.hlc[k][l]
  1056. let c1 = h[0][0] == 'd' ?
  1057. \h[0][1][1] : h[0][1][0]
  1058. let c2 = h[-1][0] == 'd' ?
  1059. \h[-1][1][0] : h[-1][1][1]
  1060. if c1 > c2 | continue | endif
  1061. endif
  1062. let t:DChar.dtm[k] += [matchaddpos("DiffText",
  1063. \[[l, c1, c2 - c1 + 1]], -1)]
  1064. endfor
  1065. endfor
  1066. exec cwin . "wincmd w"
  1067. endfunction
  1068. function! s:RestoreDiffTextArea()
  1069. " delete all the overwritten DiffText matches
  1070. if !exists("t:DChar.dtm") | return | endif
  1071. let cwin = winnr()
  1072. for k in [1, 2]
  1073. exec t:DChar.win[k] . "wincmd w"
  1074. call map(t:DChar.dtm[k], 'matchdelete(v:val)')
  1075. endfor
  1076. exec cwin . "wincmd w"
  1077. unlet t:DChar.dtm
  1078. endfunction
  1079. endif
  1080. let &cpo = s:save_cpo
  1081. unlet s:save_cpo