[
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014-2025 Rick Howe (Takumi Ohtani)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# diffchar.vim\n*Highlight the exact differences, based on characters and words*\n```\n ____   _  ____  ____  _____  _   _  _____  ____   \n|    | | ||    ||    ||     || | | ||  _  ||  _ |  \n|  _  || ||  __||  __||     || | | || | | || | ||  \n| | | || || |__ | |__ |   __|| |_| || |_| || |_||_ \n| |_| || ||  __||  __||  |   |     ||     ||  __  |\n|     || || |   | |   |  |__ |  _  ||  _  || |  | |\n|____| |_||_|   |_|   |_____||_| |_||_| |_||_|  |_|\n```\n\nThis plugin has been developed in order to make diff mode more useful. Vim\nhighlights all the text in between the first and last different characters on\na changed line. But this plugin will find the exact differences between them,\ncharacter by character - so called *DiffChar*.\n\nFor example, in diff mode:\n![example1](example1.png)\n\nThis plugin will exactly show the changed/added/deleted units:\n![example2](example2.png)\n\n#### Sync with diff mode\nThis plugin will synchronously show/reset the highlights of the exact\ndifferences as soon as the diff mode begins/ends. And the exact differences\nwill be kept updated while editing. Note that this plugin does nothing if an\n`inline` item is set in the `doffopt` option and its value is other than\n`simple`. On plugin loading, the item is removed only if set by default so\nthat this plugin can work.\n\n#### Diff unit\nThis plugin shows the diffs based on a `g:DiffUnit`. Its default is 'Word1'\nand it handles a `\\w\\+` word and a `\\W` character as a diff unit. There are other\ntypes of word provided and you can also set 'Char' to compare character by\ncharacter. In addition, you can specify one or more diff unit delimiters, such\nas comma (','), colon (':'), tab (\"\\t\"), and HTML tag symbols ('<' and '>'),\nand also specify a custom pattern in the `g:DiffUnit`.\n\n#### Diff matching colors\nIn diff mode, the corresponding `hl-DiffChange` lines are compared between two\nwindows. As a default, all the changed units are highlighted with\n`hl-DiffText`. You can set `g:DiffColors` to use more than one matching color\nto make it easy to find the corresponding units between two windows. The\nnumber of colors depends on the color scheme. In addition, an added unit is\nalways highlighted with `hl-DiffAdd` and the position of the corresponding\ndeleted unit is shown with bold/underline or a virtual blank column,\ndepending on a `g:DiffDelPosVisible`.\n\n#### Diff pair visible\nWhile showing the exact differences, when the cursor is moved on a diff unit,\nyou can see its corresponding unit highlighted with `hl-Cursor`,\n`hl-TermCursor`, or similar one in another window, based on a\n`g:DiffPairVisible`. If you change its default, the corresponding unit is\nechoed in the command line or displayed in a popup/floating window just below\nthe cursor position or at the mouse position. Those options take effect on\n`:diffupdate` command as well.\n\n#### Jump to next/prev diff unit\nYou can use `]b` or `]e` to jump cursor to start or end position of the next\ndiff unit, and `[b` or `[e` to the start or end position of the previous unit.\n\n#### Get/put a diff unit\nLike line-based `:diffget`/`:diffput` and `do`/`dp` vim commands, you can use\n`<Leader>g` and `<Leader>p` commands in normal mode to get and put each diff\nunit, where the cursor is on, between 2 buffers and undo its difference. Those\nkeymaps are configurable in your vimrc and so on.\n\n#### Check diff lines locally\nWhen the diff mode begins, this plugin locally checks the `hl-DiffChange`\nlines in the limited range of the current visible and its upper/lower lines of\na window. And each time a cursor is moved on to another range upon scrolling\nor searching, those diff lines will be checked in that range. Which means,\nindependently of the file size, the number of lines to be checked and then the\ntime consumed are always constant.\n\n#### Tab page individual\nThis plugin works on each tab page individually. You can use a tab page\nvariable (t:), instead of a global one (g:), to specify different options on\neach tab page. Note that this plugin can not handle more than two diff mode\nwindows in a tab page. If it would happen, to prevent any trouble, all the\nhighlighted units are to be reset in the tab page.\n\n#### Follow 'diffopt' option\nThis plugin supports `icase`, `iwhite`, `iwhiteall`, and `iwhiteeol` in the\n`diffopt` option. In addition, when `indent-heuristic` is specified,\npositioning of the added/deleted diff units is adjusted to reduce the number\nof diff hunks and make them easier to read.\n\n#### Comparison algorithm\nTo find the exact differences, this plugin uses \"An O(NP) Sequence Comparison\nAlgorithm\" developed by S.Wu, et al., which always finds an optimum sequence.\nBut it takes time to check a long and dissimilar line. To improve the\nperformance, the algorithm is also implemented in Vim9 script. In addition,\nif available, this plugin uses a builtin diff function (`diff()` in vim\npatch-9.1.0071 and Lua `vim.diff()` in nvim 0.6.0) and makes it much faster.\n\n#### See also\nThere are other diff related plugins available:\n* [spotdiff.vim](https://github.com/rickhowe/spotdiff.vim): A range and area selectable `:diffthis` to compare partially\n* [wrapfiller](https://github.com/rickhowe/wrapfiller): Align each wrapped line virtually between windows\n* [difffilter](https://github.com/rickhowe/difffilter): Selectively compare lines as you want in diff mode\n* [diffunitsyntax](https://github.com/rickhowe/diffunitsyntax): Highlight word or character based diff units in diff format\n\n### Options\n\n* `g:DiffUnit`, `t:DiffUnit`: A type of difference unit\n\n  | Value | Description |\n  | --- | --- |\n  | 'Char' | any single character |\n  | 'Word1' | `\\w\\+` word and any `\\W` single character (default) |\n  | 'Word2' | non-space and space words |\n  | 'Word3' | `\\<` or `\\>` character class boundaries (set by `iskeyword`) |\n  | 'word' | see `word` |\n  | 'WORD' | see `WORD` |\n  | '[{del}]' | one or more diff unit delimiters (e.g. \"[,:\\t<>]\") |\n  | '/{pat}/' | a pattern to split into diff units (e.g. '/.\\{4}\\zs/') |\n\n* `g:DiffColors`, `t:DiffColors`: Matching colors for changed units\n\n  | Value | Description |\n  | --- | --- |\n  | 0 | `hl-DiffText` (default) |\n  | 1 | `hl-DiffText` + a few (3, 4, ...) highlight groups |\n  | 2 | `hl-DiffText` + several (7, 8, ...) highlight groups |\n  | 3 | `hl-DiffText` + many (11, 12, ...) highlight groups |\n  | 100 | all available highlight groups in random order |\n  | [{hlg}] | a list of your favorite highlight groups |\n\n* `g:DiffPairVisible`, `t:DiffPairVisible`: Visibility of corresponding diff units\n\n  | Value | Description |\n  | --- | --- |\n  | 0 | disable |\n  | 1 | highlight with `hl-Cursor` (default) |\n  | 2 | highlight with `hl-Cursor` + echo in the command line |\n  | 3 | highlight with `hl-Cursor` + popup/floating window at cursor position |\n  | 4 | highlight with `hl-Cursor` + popup/floating window at mouse position |\n\n* `g:DiffDelPosVisible`, `t:DiffDelPosVisible`: Visibility of the position of deleted units\n\n  | Value | Description |\n  | --- | --- |\n  | 0 | disable |\n  | 1 | highlight previous/next chars of a deleted unit in bold/underline (default if inline \"virtual-text\" is not available) |\n  | 2 | virtually show a blank column (set by `space` item in `listchars`) wih `hl-DiffDelete` (default if inline \"virtual-text\" is available) |\n\n### Keymaps\n\n| Mapping | Default Key | Description |\n| --- | --- | --- |\n| `<Plug>JumpDiffCharPrevStart` | `[b` | Jump cursor to the start position of the previous diff unit |\n| `<Plug>JumpDiffCharNextStart` | `]b` | Jump cursor to the start position of the next diff unit |\n| `<Plug>JumpDiffCharPrevEnd` | `[e` | Jump cursor to the end position of the previous diff unit |\n| `<Plug>JumpDiffCharNextEnd` | `]e` | Jump cursor to the end position of the next diff unit |\n| `<Plug>GetDiffCharPair` | `<Leader>g` | Get a corresponding diff unit from another buffer to undo difference |\n| `<Plug>PutDiffCharPair` | `<Leader>p` | Put a corresponding diff unit to another buffer to undo difference |\n"
  },
  {
    "path": "autoload/diffchar.vim",
    "content": "\" diffchar.vim: Highlight the exact differences, based on characters and words\n\"\n\"  ____   _  ____  ____  _____  _   _  _____  ____   \n\" |    | | ||    ||    ||     || | | ||  _  ||  _ |  \n\" |  _  || ||  __||  __||     || | | || | | || | ||  \n\" | | | || || |__ | |__ |   __|| |_| || |_| || |_||_ \n\" | |_| || ||  __||  __||  |   |     ||     ||  __  |\n\" |     || || |   | |   |  |__ |  _  ||  _  || |  | |\n\" |____| |_||_|   |_|   |_____||_| |_||_| |_||_|  |_|\n\"\n\" Last Change: 2025/10/01\n\" Version:     10.0 (on or after vim 9.0 and nvim 0.7.0)\n\" Author:      Rick Howe (Takumi Ohtani) <rdcxy754@ybb.ne.jp>\n\" Copyright:   (c) 2014-2025 Rick Howe\n\" License:     MIT\n\nlet s:save_cpo = &cpoptions\nset cpo&vim\n\n\" Vim feature, function, event and patch number which this plugin depends on\n\" patch-8.2.4204: screenpos() fixed to return zero row for invisible line\n\" patch-9.0.1067: virtual text fixed to correctly highlight in diff mode\n\" patch-9.1.0099: diff() fixed to correctly use in &diffexpr\nlet s:VF = #{\n  \\ScreenPos: exists('*screenpos') &&\n                                \\(has('patch-8.2.4204') || has('nvim-0.8.2')),\n  \\InlineVirtText: has('textprop') && has('patch-9.0.1067') ||\n                                                          \\has('nvim-0.10.0'),\n  \\BuiltinDiffFunc: exists('*diff') && has('patch-9.1.0099') ||\n                                                          \\has('nvim-0.6.0')}\n\nfunction! s:ShowDiffChar(...) abort\n  \" !a:0 - all dfl from scratch, a:1 - specified dfl only\n  if !a:0 && !exists('t:DChar') && s:InitializeDiffChar() == -1\n    return\n  endif\n  let ak = 2 | while t:DChar.wid[ak] != win_getid() | let ak -= 1\n    if ak == 0 | return | endif\n  endwhile\n  let lc = #{1: {}, 2: {}}\n  for n in (!a:0) ? range(len(t:DChar.dfl[ak])) :\n      \\filter(map(copy(a:1), 'index(t:DChar.dfl[ak], v:val)'), 'v:val != -1')\n    let tu = #{1: {}, 2: {}}\n    for k in [1, 2]\n      let tu[k].l = t:DChar.dfl[k][n]\n      if has_key(t:DChar.hlc[k], tu[k].l) | let tu[k].l = 0 | endif\n    endfor\n    if 0 < tu[1].l && 0 < tu[2].l\n      for k in [1, 2]\n        let tu[k].t = getbufline(t:DChar.bnr[k], tu[k].l)[0]\n        let t = t:DChar.opt.ic ? tolower(tu[k].t) : tu[k].t\n        let tu[k].u = split(t:DChar.opt.iw == 0 ? t :\n            \\substitute(t, (t:DChar.opt.iw == 1) ? '\\s\\+' : '\\s\\+$', '', 'g'),\n                                                                \\t:DChar.upa)\n        let tu[k].w = (tu[k].t =~ '\\s\\+' &&\n          \\(t:DChar.opt.iw == 1 || t:DChar.opt.iw == 2)) ? t:DChar.opt.iw : 0\n        if tu[k].w == 2\n          let u = [] | let s = ''\n          for z in tu[k].u + ['']\n            if z =~ '^\\s\\+$'\n              let s .= z\n            else\n              if !empty(s) | let u += [s] | let s = '' | endif\n              let u += [z]\n            endif\n          endfor\n          let tu[k].u = u[: -2]\n        endif\n      endfor\n      let uu = [copy(tu[1].u), copy(tu[2].u)]\n      for k in [1, 2]\n        if tu[k].w == 2\n          call map(tu[k].u, 'substitute(v:val, \"\\\\s\\\\+\", \" \", \"g\")')\n        endif\n      endfor\n      if tu[1].u !=# tu[2].u\n        let [tu[1].c, tu[2].c] = s:GetDiffUnitPos(uu,\n                              \\t:DChar.dfn(tu[1].u, tu[2].u, t:DChar.opt.ih))\n        for k in [1, 2]\n          if tu[k].w == 1\n            let pc = [0] + filter(range(1, len(tu[k].t)),\n                              \\'tu[k].t[v:key] =~ \"\\\\S\"') + [len(tu[k].t) + 1]\n            for n in range(len(tu[k].c))\n              call map(tu[k].c[n][1], 'pc[v:val]')\n            endfor\n          endif\n          let lc[k][tu[k].l] = tu[k].c\n          let t:DChar.cks[k][tu[k].l] = s:ChecksumStr(tu[k].t)\n        endfor\n      endif\n    endif\n  endfor\n  if !a:0\n    call s:ToggleDiffCharEvent(1)\n    call s:ToggleDiffHL(1)\n    call s:ToggleDiffCharPair(1)\n  endif\n  if !empty(lc[ak])\n    call s:HighlightDiffChar(lc)\n    if 0 < t:DChar.dpv.pv | call s:ShowDiffCharPair(ak) | endif\n  endif\nendfunction\n\nfunction! s:ResetDiffChar(...) abort\n  \" !a:0 - all dfl to scratch, a:1 - specified dfl only\n  if !exists('t:DChar') | return | endif\n  let ak = 2 | while t:DChar.wid[ak] != win_getid() | let ak -= 1\n    if ak == 0 | return | endif\n  endwhile\n  let dl = #{1: [], 2: []}\n  for n in (!a:0) ? range(len(t:DChar.dfl[ak])) :\n      \\filter(map(copy(a:1), 'index(t:DChar.dfl[ak], v:val)'), 'v:val != -1')\n    for k in [1, 2]\n      let l = t:DChar.dfl[k][n]\n      if has_key(t:DChar.hlc[k], l)\n        let dl[k] += [l]\n        unlet t:DChar.cks[k][l]\n      endif\n    endfor\n  endfor\n  if !empty(dl[ak])\n    if 0 < t:DChar.dpv.pv | call s:ClearDiffCharPair(ak) | endif\n    call s:ClearDiffChar(dl)\n  endif\n  if !a:0\n    call s:ToggleDiffCharPair(0)\n    call s:ToggleDiffHL(0)\n    unlet t:DChar\n    call s:ToggleDiffCharEvent(0)\n  endif\nendfunction\n\nfunction! s:InitializeDiffChar() abort\n  if matchstr(&diffopt, '^.*inline:\\zs.*') !~ '^simple\\|^$'\n    call s:EchoWarning('Disabled because \"inline:\" value set in &diffopt is\n                                                            \\ not \"simple\"!')\n    return -1\n  endif\n  let cw = win_getid() | let cb = winbufnr(cw)\n  let nw = filter(map(range(winnr() + 1, winnr('$')) +\n                                  \\range(1, winnr() - 1), 'win_getid(v:val)'),\n                        \\'getwinvar(v:val, \"&diff\") && winbufnr(v:val) != cb')\n  let nb = map(copy(nw), 'winbufnr(v:val)')\n  if !getwinvar(cw, '&diff') || empty(nw) || min(nb) != max(nb)\n    return -1\n  endif\n  for tn in filter(range(1, tabpagenr('$')), 'v:val != tabpagenr()')\n    let dc = gettabvar(tn, 'DChar')\n    if !empty(dc)\n      for bn in values(dc.bnr)\n        if index([cb, nb[0]], bn) != -1\n          call s:EchoWarning('Both or either selected buffer already\n                                      \\ highlighted in tab page ' . tn . '!')\n          return -1\n        endif\n      endfor\n    endif\n  endfor\n  call s:SetDiffCharHL()\n  let t:DChar = {}\n  let t:DChar.wid = #{1: cw, 2: nw[0]}\n  let t:DChar.bnr = #{1: cb, 2: nb[0]}\n  let t:DChar.opt = s:GetDiffCharOptions()\n  let t:DChar.lcc = s:GetLineColCnr()\n  let t:DChar.dfl = s:FocusDiffLines(0)\n  let t:DChar.upa = s:GetDiffSplitRegExp(t:DChar.opt.ut)\n  let t:DChar.dpv = s:GetDiffPairVisible(t:DChar.opt.pv)\n  let t:DChar.hgp = s:GetDiffUnitHL(t:DChar.opt.cl)\n  let t:DChar.csn = s:GetColorsName()\n  let t:DChar.mid = #{1: {}, 2: {}}\n  let t:DChar.hlc = #{1: {}, 2: {}}\n  let t:DChar.cks = #{1: {}, 2: {}}\n  let t:DChar.dfn = function(t:DChar.opt.df ?\n                                      \\'s:ApplyDiffFunc' : 's:TraceDiffChar')\nendfunction\n\nfunction! s:GetDiffSplitRegExp(du) abort\n  if a:du == 'Char'\n    let upa = '\\zs'\n  elseif a:du == 'Word2' || a:du ==# 'WORD'\n    let upa = '\\%(\\s\\+\\|\\S\\+\\)\\zs'\n  elseif a:du == 'Word3' || a:du ==# 'word'\n    let upa = '\\<\\|\\>'\n  elseif a:du =~ '^\\[.\\+\\]$'\n    let s = escape(a:du[1 : -2], ']^-\\')\n    let upa = '\\%([^' . s . ']\\+\\|[' . s . ']\\)\\zs'\n  elseif a:du =~ '^\\([/?]\\).\\+\\1$'\n    let upa = a:du[1 : -2]\n  else\n    let upa = '\\%(\\w\\+\\|\\W\\)\\zs'\n    if a:du != 'Word1'\n      call s:EchoWarning('Not a valid difference unit type.\n                                                      \\ Use \"Word1\" instead.')\n    endif\n  endif\n  return upa\nendfunction\n\nfunction! s:GetDiffPairVisible(pv) abort\n  let dpv = #{pv: a:pv}\n  if 0 < dpv.pv\n    let dpv.ch = {}\n    if dpv.pv == 3 || dpv.pv == 4 | let dpv.pw = has('nvim') ? {} : 0 | endif\n  endif\n  return dpv\nendfunction\n\nfunction! s:GetDiffUnitHL(dc) abort\n  let hgp = [s:DCharHL.T]\n  if type(a:dc) == type([])\n    let hgp += filter(copy(a:dc),\n                  \\'0 < hlID(v:val) && !empty(synIDattr(hlID(v:val), \"bg#\"))')\n    if 1 < len(hgp) | unlet hgp[0] | endif\n  elseif 1 <= a:dc && a:dc <= 3\n    let lv = a:dc - 1\n    let bx = []\n    for nm in values(s:DCharHL)\n      let [fc, bc] = map(['fg#', 'bg#'],\n                              \\'s:ColorClass(synIDattr(hlID(nm), v:val), lv)')\n      if !empty(bc) | let bx += [bc] | endif\n      if nm == s:DCharHL.n | let fn = fc | endif\n    endfor\n    let hl = {} | let id = 1\n    while 1\n      let nm = synIDattr(id, 'name')\n      if empty(nm) | break | endif\n      if id == synIDtrans(id) && empty(filter(['underline', 'undercurl',\n                          \\'strikethrough', 'reverse', 'inverse', 'standout'],\n                                            \\'!empty(synIDattr(id, v:val))'))\n        let [fc, bc] = map(['fg#', 'bg#'],\n                                    \\'s:ColorClass(synIDattr(id, v:val), lv)')\n        if !empty(bc) && index(bx + [!empty(fc) ? fc : fn], bc) == -1\n          let wt = !empty(fc) + (!empty(filter(['bold', 'italic'],\n                                        \\'!empty(synIDattr(id, v:val))'))) * 2\n          if !has_key(hl, bc) || hl[bc][0] < wt\n            let hl[bc] = [wt, nm]\n          endif\n        endif\n      endif\n      let id += 1\n    endwhile\n    let hgp += map(values(hl), 'v:val[1]')\n  elseif a:dc == 100\n    let bx = map(values(s:DCharHL), 'synIDattr(hlID(v:val), \"bg#\")')\n    let hl = {} | let id = 1\n    while 1\n      let nm = synIDattr(id, 'name')\n      if empty(nm) | break | endif\n      if id == synIDtrans(id)\n        let bg = synIDattr(id, 'bg#')\n        if !empty(bg) && index(bx, bg) == -1\n          let hl[reltimestr(reltime())[-2 :] . id] = nm\n          let bx += [bg]\n        endif\n      endif\n      let id += 1\n    endwhile\n    let hgp += values(hl)\n  endif\n  return hgp\nendfunction\n\nfunction! s:GetColorsName() abort\n  return get(g:, 'colors_name', 'default')\nendfunction\n\nfunction! s:ColorClass(cn, lv) abort\n  if empty(a:cn) | return a:cn | endif\n  if a:cn[0] != '#'\n    let cn = a:cn % 256\n    if cn < 16\n      let cv = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],\n                  \\[0, 0, 128], [128, 0, 128], [0, 128, 128], [192, 192, 192],\n                  \\[128, 128, 128], [255, 0, 0], [0, 255, 0], [255, 255, 0],\n                  \\[0, 0, 255], [255, 0, 255], [0, 255, 255], [255, 255, 255]]\n      if &t_Co < 256\n        let [cv[9], cv[12], cv[11], cv[14]] = [cv[12], cv[9], cv[14], cv[11]]\n      endif\n      let rgb = cv[cn]\n    elseif cn < 232\n      let cv = [0, 95, 135, 175, 215, 255]\n      let cn -= 16\n      let rgb = [cv[(cn / 36) % 6], cv[(cn / 6) % 6], cv[cn % 6]]\n    else\n      let cn = 10 * (cn - 232) + 8\n      let rgb = [cn, cn, cn]\n    endif\n  else\n    let rgb = map(split(a:cn[1 :], '..\\zs'), 'str2nr(v:val, 16)')\n  endif\n  let cl = [[0, 0, 0, 0, 1, 1, 1, 1], [0, 0, 0, 0, 1, 1, 2, 2],\n                                              \\[0, 1, 2, 3, 4, 5, 6, 7]][a:lv]\n  call map(rgb, 'v:val / 32')\n  if max(rgb) == min(rgb)\n    return '99' . cl[(rgb[0] + rgb[1] + rgb[2]) / 3]\n  else\n    return join(map(rgb, 'cl[v:val]'), '')\n  endif\nendfunction\n\nfunction! s:SetDiffCharHL() abort\n  \" set diff hl in original and DChar modes\n  let s:DiffHL = #{A: 'DiffAdd', C: 'DiffChange', D: 'DiffDelete',\n                                                              \\T: 'DiffText'}\n  for [hs, hl] in items(s:DiffHL)\n    let dh = {}\n    let dh.id = hlID(hl)\n    let dh.it = synIDtrans(dh.id)       \" in case of linked\n    let dh.nm = synIDattr(dh.it, 'name')\n    \" 0 : for original, 1 : for DChar\n    let dh[0] = {}\n    for hm in ['cterm', 'gui']\n      for hc in ['fg', 'bg']\n        let dh[0][hm . hc] = synIDattr(dh.it, hc, hm)\n      endfor\n      let dh[0][hm] = join(filter(['bold', 'underline', 'undercurl',\n                \\'strikethrough', 'reverse', 'inverse', 'italic', 'standout'],\n                                \\'!empty(synIDattr(dh.it, v:val, hm))'), ',')\n    endfor\n    call map(dh[0], '!empty(v:val) ? v:val : \"NONE\"')\n    let dh[1] = (hs == 'C' || hs == 'T') ?\n                  \\map(copy(dh[0]), 'v:key =~ \"bg$\" ? v:val : \"NONE\"') : dh[0]\n    let s:DiffHL[hs] = dh\n  endfor\n  for at in ['ctermbg', 'guibg']              \" in case no bg in C, also in T\n    if s:DiffHL.C[1][at] == 'NONE' | let s:DiffHL.T[1][at] = 'NONE' | endif\n  endfor\n  \" set DChar hl\n  let s:DCharHL = {}\n  let s:DCharHL.n = 'Normal'\n  let s:DCharHL.c = has('nvim') ? 'TermCursor' : 'Cursor'\n  if !empty(filter(['fg', 'bg'],\n                              \\'empty(synIDattr(hlID(s:DCharHL.c), v:val))'))\n    let s:DCharHL.c = 'dcCursor'\n    for at in ['NONE', 'ctermfg=fg', 'ctermbg=bg', 'cterm=reverse',\n                                      \\'guifg=fg', 'guibg=bg', 'gui=reverse']\n      call execute(join(['highlight', s:DCharHL.c, at]), 'silent!')\n    endfor\n  endif\n  for [fs, ts, th, ta] in [['C', 'C', 'dcDiffChange', ''],\n                          \\['T', 'T', 'dcDiffText', ''],\n                          \\['A', 'A', 'dcDiffAdd', ''],\n                          \\['D', 'D', 'dcDiffDelete', ''],\n                          \\['C', 'E', 'dcDiffDelPos', 'bold,underline']]\n    let fa = copy(s:DiffHL[fs][0])\n    if !empty(ta)\n      for hm in ['cterm', 'gui']\n        let fa[hm] = ((fa[hm] != 'NONE') ? fa[hm] . ',' : '') . ta\n      endfor\n    endif\n    for at in ['NONE'] + map(items(fa), 'join(v:val, \"=\")')\n      call execute(join(['highlight', th, at]), 'silent!')\n    endfor\n    let s:DCharHL[ts] = th\n  endfor\n  if s:VF.InlineVirtText | call s:SetVirtColumn() | endif\nendfunction\n\nfunction! s:GetDiffCharOptions() abort\n  let Dip = {op -> &diffopt =~ op}\n  let do = #{ic: Dip('icase'),\n            \\iw: Dip('iwhiteall') ? 1 : Dip('\\<iwhite\\>') ? 2 :\n                                                    \\Dip('iwhiteeol') ? 3 : 0,\n            \\ih: Dip('indent-heuristic'), lm: Dip('linematch'), vc: ' '}\n  if &listchars =~ '\\<space\\>'\n    let do.vc = matchstr(&listchars, '^.*\\<space:\\zs[^,]\\+')\n    if do.vc =~ '^\\\\[ux]'\n      let do.vc = nr2char(str2nr('0x' . do.vc[2 :], 16))\n    endif\n  endif\n  for [ok, pv] in items(#{ut: ['DiffUnit', 'Word1'], cl: ['DiffColors', 0],\n                    \\pv: ['DiffPairVisible', 1], dv: ['DiffDelPosVisible', 2],\n                    \\fp: ['DiffFocusPages', 3], df: ['BuiltinDiffFunc', 1]})\n    let [op, ov] = pv\n    let ov = s:GetOptionVar(op, ov)\n    if op == 'DiffPairVisible'\n      if ov < 0 || 4 < ov | let ov = 0 | endif\n    elseif op == 'DiffDelPosVisible'\n      let ov = (ov < 0) ? 0 : min([ov, (s:VF.InlineVirtText ? 2 : 1)])\n    elseif op == 'DiffFocusPages'\n      \" avoid to check off-screen diff lines linematch is not yet run\n      if do.lm | let ov = (ov < 0) ? -1 : (0 < ov) ? 1 : 0 | endif\n    elseif op == 'BuiltinDiffFunc'\n      if !s:VF.BuiltinDiffFunc | let ov = 0 | endif\n    endif\n    let do[ok] = ov\n  endfor\n  return do\nendfunction\n\nfunction! s:GetOptionVar(op, ov) abort\n  return get(t:, a:op, get(g:, a:op, a:ov))\nendfunction\n\nfunction! s:ToggleDiffHL(on) abort\n  for dh in [s:DiffHL.C, s:DiffHL.T]\n    call execute(join(['highlight', dh.nm] +\n                                  \\map(items(dh[a:on]), 'join(v:val, \"=\")')))\n  endfor\nendfunction\n\nfunction! s:RefreshDiffCharHL(event) abort\n  \" a:event : 0 = TabEnter, 1 = ColorScheme\n  if a:event == 1 | call s:SetDiffCharHL() | endif\n  let on = exists('t:DChar')\n  call s:ToggleDiffHL(on)\n  if on\n    \" redraw DChar units with the latest colorscheme\n    let csn = s:GetColorsName()\n    if t:DChar.csn != csn\n      let t:DChar.csn = csn\n      if 1 < len(t:DChar.hgp)\n        let hlc = deepcopy(t:DChar.hlc)\n        call s:ClearDiffChar(map(copy(hlc), 'keys(v:val)'))\n        let t:DChar.hgp = s:GetDiffUnitHL(t:DChar.opt.cl)\n        call s:HighlightDiffChar(hlc)\n      endif\n    endif\n  endif\nendfunction\n\nfunction! s:ToggleDiffCharEvent(on) abort\n  call execute(g:DiffCharInitEvent)\n  let tv = filter(map(range(1, tabpagenr('$')),\n                              \\'gettabvar(v:val, \"DChar\")'), '!empty(v:val)')\n  if empty(tv) | return | endif\n  let ac = []\n  for td in tv\n    for k in [1, 2]\n      let bl = '<buffer=' . td.bnr[k] . '>'\n      let ac += [['WinClosed', bl, 's:WinClosedDiffChar()']]\n      if td.opt.fp != 0\n        let ac += [['WinScrolled', bl, 's:ScrollDiffLines(0)']]\n      endif\n      if 0 < td.dpv.pv\n        let ac += [['CursorMoved', bl, 's:ShowDiffCharPair(' . k . ')']]\n      endif\n    endfor\n  endfor\n  let ac += [['TabEnter', '*', 's:RefreshDiffCharHL(0)']]\n  let ac += [['ColorScheme', '*', 's:RefreshDiffCharHL(1)']]\n  let ac += [['BufWinEnter', '*', 's:RepairDiffChar()']]\n  let ac += [['DiffUpdated', '*', 's:UpdateDiffChar()']]\n  call execute(map(ac, 'join([\"autocmd\", \"diffchar\", v:val[0], v:val[1],\n                                                        \\\"call\", v:val[2]])'))\nendfunction\n\nfunction! s:ShiftDiffChar(key, lines, shift) abort\n  let im = {}\n  for gm in getmatches(t:DChar.wid[a:key])\n    let im[gm.id] = gm\n  endfor\n  let [lid, hlc, cks] = [{}, {}, {}]\n  for ln in a:lines\n    if has_key(t:DChar.mid[a:key], ln)\n      let lid[ln + a:shift] = []\n      for id in t:DChar.mid[a:key][ln]\n        if 0 < id\n          if has_key(im, id)\n            call matchdelete(id, t:DChar.wid[a:key])\n            let lid[ln + a:shift] += [matchaddpos(im[id].group,\n                  \\map(values(filter(copy(im[id]), 'v:key =~ \"^pos\\\\d\\\\+$\"')),\n                                        \\'[v:val[0] + a:shift] + v:val[1 :]'),\n                        \\im[id].priority, -1, #{window: t:DChar.wid[a:key]})]\n          endif\n        elseif id < 0\n          \" virtual column is kept on the line, no need to remake it\n          let lid[ln + a:shift] += [id]\n        endif\n      endfor\n      unlet t:DChar.mid[a:key][ln]\n    endif\n    if has_key(t:DChar.hlc[a:key], ln)\n      let hlc[ln + a:shift] = t:DChar.hlc[a:key][ln]\n      unlet t:DChar.hlc[a:key][ln]\n    endif\n    if has_key(t:DChar.cks[a:key], ln)\n      let cks[ln + a:shift] = t:DChar.cks[a:key][ln]\n      unlet t:DChar.cks[a:key][ln]\n    endif\n  endfor\n  call extend(t:DChar.mid[a:key], lid)\n  call extend(t:DChar.hlc[a:key], hlc)\n  call extend(t:DChar.cks[a:key], cks)\nendfunction\n\nfunction! s:GetLineColCnr() abort\n  let lcc = {}\n  for k in [1, 2]\n    call win_execute(t:DChar.wid[k], 'let lcc[k] =\n                            \\#{tl: line(\"w0\"), bl: line(\"w$\"), ll: line(\"$\"),\n                              \\cl: line(\".\"), cc: col(\".\"), cn: changenr()}')\n    if lcc[k].bl < lcc[k].ll && (s:VF.ScreenPos ?\n                      \\screenpos(t:DChar.wid[k], lcc[k].bl + 1, 1).row != 0 :\n      \\&display =~ 'lastline\\|truncate' && getwinvar(t:DChar.wid[k], '&wrap'))\n      let lcc[k].bl += 1\n    endif\n  endfor\n  return lcc\nendfunction\n\nfunction! s:UpdateDiffChar() abort\n  if mode(1) != 'n' || !exists('t:DChar') ||\n                              \\len(filter(gettabinfo(tabpagenr())[0].windows,\n                                                \\'getwinvar(v:val, \"&diff\") &&\n                              \\index(values(t:DChar.wid), v:val) != -1')) != 2\n    return\n  endif\n  let lcc = t:DChar.lcc | let t:DChar.lcc = s:GetLineColCnr()\n  let ak = 2\n  while 0 < ak && lcc[ak].cn == t:DChar.lcc[ak].cn | let ak -= 1 | endwhile\n  if 0 < ak\n    \" when text changed, find DChar lines to delete/add/shift, and\n    \" to leave undeleted/unadded\n    let NoUpdate = {pl, cl -> get(t:DChar.cks[ak], pl, '') ==\n                        \\s:ChecksumStr(getbufline(t:DChar.bnr[ak], cl)[0]) &&\n                      \\(has('nvim') || 0 < min(get(t:DChar.mid[ak], pl, [])))}\n    let bk = (ak == 1) ? 2 : 1\n    let lnd = t:DChar.lcc[ak].ll - lcc[ak].ll\n    let pfl = t:DChar.dfl\n    let cfl = s:FocusDiffLines(0)\n    let m = min([len(pfl[ak]), len(cfl[ak])])\n    let s = 0\n    while s < m && [pfl[ak][s], pfl[bk][s]] == [cfl[ak][s], cfl[bk][s]] &&\n                                            \\NoUpdate(pfl[ak][s], cfl[ak][s])\n      let s += 1\n    endwhile\n    let m -= s\n    let e = -1\n    while e >= -m && [pfl[ak][e] + lnd, pfl[bk][e]] ==\n                \\[cfl[ak][e], cfl[bk][e]] && NoUpdate(pfl[ak][e], cfl[ak][e])\n      let e -= 1\n    endwhile\n    let ddl = pfl[ak][s : e]\n    let adl = cfl[ak][s : e]\n    if lnd != 0 && e < -1\n      let sdl = pfl[ak][e + 1 :]\n    else\n      let sdl = []\n      for d in range(len(ddl) - 1, 0, -1)\n        let a = index(adl, ddl[d])\n        if a != -1 && pfl[bk][s + d] == cfl[bk][s + a] &&\n                                                    \\NoUpdate(ddl[d], adl[a])\n          unlet ddl[d]\n          unlet adl[a]\n        endif\n      endfor\n    endif\n    if 0 < t:DChar.dpv.pv | call s:ClearDiffCharPair(ak) | endif\n    if !empty(ddl)\n      call win_execute(t:DChar.wid[ak], 'call s:ResetDiffChar(ddl)')\n    endif\n    let t:DChar.dfl = cfl\n    if !empty(sdl) | call s:ShiftDiffChar(ak, sdl, lnd) | endif\n    if !empty(adl)\n      call win_execute(t:DChar.wid[ak], 'call s:ShowDiffChar(adl)')\n    endif\n  else\n    \" when diffupdate invoked or diffopt changed, delete all previous and\n    \" then add all current DChar lines if some of DChar option is changed\n    let opt = s:GetDiffCharOptions()\n    if opt != t:DChar.opt\n      let k = (t:DChar.wid[1] == win_getid()) ? 1 : 2\n      call s:ResetDiffChar(t:DChar.dfl[k])\n      if opt.ut != t:DChar.opt.ut\n        let t:DChar.upa = s:GetDiffSplitRegExp(opt.ut)\n      endif\n      if opt.pv != t:DChar.opt.pv\n        let t:DChar.dpv = s:GetDiffPairVisible(opt.pv)\n      endif\n      if opt.cl != t:DChar.opt.cl\n        let t:DChar.hgp = s:GetDiffUnitHL(opt.cl)\n      endif\n      let t:DChar.opt = opt\n      let t:DChar.dfl = s:FocusDiffLines(0)\n      call s:ShowDiffChar(t:DChar.dfl[k])\n    endif\n  endif\nendfunction\n\nfunction! diffchar#JumpDiffChar(dp) abort\n  \" a:dp : 0=backward/start, 1=forward/start, 2=backward/end, 3=forward/end\n  if !exists('t:DChar') | return | endif\n  let k = 2 | while t:DChar.wid[k] != win_getid() | let k -= 1\n    if k == 0 | return | endif\n  endwhile\n  let [dir, pos] = (a:dp == 0) ? [0, 0] : (a:dp == 1) ? [1, 0] :\n                                              \\(a:dp == 2) ? [0, -1] : [1, -1]\n  let [ln, co] = [line('.'), col('.')]\n  if co == col('$')   \" empty line\n    if !dir | let co = 0 | endif\n  else\n    if pos != 0\n      let co += len(strcharpart(\n                      \\getbufline(t:DChar.bnr[k], ln)[0][co - 1 :], 0, 1)) - 1\n    endif\n  endif\n  if has_key(t:DChar.hlc[k], ln) &&\n                                \\(dir ? co < t:DChar.hlc[k][ln][-1][1][pos] :\n                                          \\co > t:DChar.hlc[k][ln][0][1][pos])\n    let co = filter(map(copy(t:DChar.hlc[k][ln]), 'v:val[1][pos]'),\n                            \\dir ? 'co < v:val' : 'co > v:val')[dir ? 0 : -1]\n  else\n    let dl = s:SearchDiffLines(dir, dir ? ln + 1 : ln - 1, 1)\n    if empty(dl) | return | endif\n    if t:DChar.opt.lm | let lp = ln | endif\n    let ln = dl[0] | let lx = ln\n    while 1\n      \" go up/down and check on-screen diff lines, linematch can realign\n      if dir ? t:DChar.lcc[k].ll < lx : lx < 1 | return | endif\n      noautocmd call cursor(lx, 0) | call s:ScrollDiffLines(k)\n      if has_key(t:DChar.hlc[k], ln) | break | endif\n      if t:DChar.opt.lm\n        let dl = filter(sort(map(keys(t:DChar.hlc[k]), 'eval(v:val)'), 'n'),\n                                          \\dir ? 'lp < v:val' : 'lp > v:val')\n        if !empty(dl) | let ln = dir ? dl[0] : dl[-1] | break | endif\n      endif\n      let lx = dir ? max([lx, t:DChar.lcc[k].bl]) + 1 :\n                                            \\min([lx, t:DChar.lcc[k].tl]) - 1\n    endwhile\n    let co = t:DChar.hlc[k][ln][dir ? 0 : -1][1][pos]\n  endif\n  \" set a dummy cursor position to adjust the start/end\n  if 0 < t:DChar.dpv.pv\n    call s:ClearDiffCharPair(k)\n    if a:dp == 1         \" forward/start : rightmost\n      let [t:DChar.lcc[k].cl, t:DChar.lcc[k].cc] = [ln, col('$')]\n    elseif a:dp == 2     \" backward/end : leftmost\n      let [t:DChar.lcc[k].cl, t:DChar.lcc[k].cc] = [ln, 0]\n    endif\n  endif\n  call cursor(ln, co)\nendfunction\n\nfunction! diffchar#CopyDiffCharPair(dir) abort\n  \" a:dir : 0 = get, 1 = put\n  if !exists('t:DChar') | return | endif\n  let ak = 2 | while t:DChar.wid[ak] != win_getid() | let ak -= 1\n    if ak == 0 | return | endif\n  endwhile\n  let bk = (ak == 1) ? 2 : 1\n  let un = -1\n  if 0 < t:DChar.dpv.pv\n    if !empty(t:DChar.dpv.ch) | let [al, un] = t:DChar.dpv.ch.lc | endif\n  else\n    let [al, co] = [line('.'), col('.')]\n    if co == col('$') | let co = 0 | endif\n    if has_key(t:DChar.hlc[ak], al)\n      let hc = filter(map(copy(t:DChar.hlc[ak][al]), '[v:key, v:val[1]]'),\n                                  \\'v:val[1][0] <= co && co <= v:val[1][-1]')\n      if !empty(hc) | let un = hc[0][0] | endif\n    endif\n  endif\n  if un == -1\n    call s:EchoWarning('Cursor is not on a difference unit!')\n    return\n  endif\n  let bl = t:DChar.dfl[bk][index(t:DChar.dfl[ak], al)]\n  let et = #{a: {}, b: {}}\n  let [et.a.e, et.a.c] = t:DChar.hlc[ak][al][un]\n  let [et.b.e, et.b.c] = t:DChar.hlc[bk][bl][un]\n  let [et.a.t, et.b.t] = [getbufline(t:DChar.bnr[ak], al)[0],\n                                          \\getbufline(t:DChar.bnr[bk], bl)[0]]\n  let [x, y] = a:dir ? ['b', 'a'] : ['a', 'b']\n  let s1 = (1 < et[x].c[0]) ? et[x].t[: et[x].c[0] - 2] : ''\n  let s2 = (et[x].e != 'a') ? et[y].t[et[y].c[0] - 1 : et[y].c[-1] - 1] : ''\n  if et[x].e == 'd' && [et[x].c[0], et[x].c[-1]] != [0, 0]\n    let ds = split(et[x].t[et[x].c[0] - 1 : et[x].c[-1] - 1], '\\zs')\n    let s2 = ((1 < et[y].c[0]) ? ds[0] : '') . s2 .\n                                \\((et[y].c[-1] < len(et[y].t)) ? ds[-1] : '')\n  endif\n  let s3 = (et[x].c[-1] < len(et[x].t)) ? et[x].t[et[x].c[-1] :] : ''\n  let ss = s1 . s2 . s3\n  if a:dir\n    call setbufline(t:DChar.bnr[bk], bl, ss)\n    call win_execute(t:DChar.wid[bk], 'let &undolevels = &undolevels')\n  else\n    call setbufline(t:DChar.bnr[ak], al, ss)\n  endif\nendfunction\n\nfunction! s:ToggleDiffCharPair(on) abort\n  if t:DChar.dpv.pv == 3 || t:DChar.dpv.pv == 4\n    let on = (a:on && empty(t:DChar.dpv.pw)) ? 1 :\n                                  \\(!a:on && !empty(t:DChar.dpv.pw)) ? 0 : -1\n    if on != -1 | call s:SetPopupWindow(on) | endif\n  endif\nendfunction\n\nfunction! s:ShowDiffCharPair(key) abort\n  if mode(1) != 'n' || !exists('t:DChar') | return | endif\n  let [pl, pc] = [t:DChar.lcc[a:key].cl, t:DChar.lcc[a:key].cc]\n  let [cl, cc] = [line('.'), col('.')]\n  if cc == col('$') | let cc = 0 | endif\n  let [t:DChar.lcc[a:key].cl, t:DChar.lcc[a:key].cc] = [cl, cc]\n  if t:DChar.lcc[a:key].cn == changenr()\n    if !empty(t:DChar.dpv.ch)\n      if t:DChar.dpv.ch.bk == a:key\n        \" clear if a pair accidentally remains on diffsplit\n        call s:ClearDiffCharPair((a:key == 1) ? 2 : 1)\n      else\n        let [hl, hi] = t:DChar.dpv.ch.lc\n        let hc = t:DChar.hlc[a:key][hl][hi][1]\n        if cl == hl && hc[0] <= cc && cc <= hc[-1] | return | endif\n        call s:ClearDiffCharPair(a:key) \" outside, clear it\n      endif\n    endif\n    if has_key(t:DChar.hlc[a:key], cl)\n      let hu = filter(map(copy(t:DChar.hlc[a:key][cl]), '[v:key, v:val[1]]'),\n                                  \\'v:val[1][0] <= cc && cc <= v:val[1][-1]')\n      if !empty(hu)\n        \" for 2 continuous 'd', check if cursor moved forward/backward\n        let ix = (len(hu) == 1) ? 0 : (cl == pl) ? cc < pc : cl < pl\n        call s:HighlightDiffCharPair(a:key, cl, hu[ix][0])\n      endif\n    endif\n  endif\nendfunction\n\nfunction! s:HighlightDiffCharPair(key, line, col) abort\n  let [ak, bk] = (a:key == 1) ? [1, 2] : [2, 1]\n  let [al, bl] = [a:line, t:DChar.dfl[bk][index(t:DChar.dfl[ak], a:line)]]\n  let t:DChar.dpv.ch = #{lc: [al, a:col], bk: bk, id: 0}\n  let ae = t:DChar.hlc[ak][al][a:col][0]\n  let bc = t:DChar.hlc[bk][bl][a:col][1]\n  if [bc[0], bc[-1]] != [0, 0]\n    if t:DChar.opt.dv == 2 && ae == 'a'\n      let id = t:DChar.mid[bk][bl][a:col]\n      let t:DChar.mid[bk][bl][a:col] =\n                        \\s:ChangeVirtColumn(t:DChar.bnr[bk], id, s:DCharHL.c)\n      let t:DChar.dpv.ch.id = t:DChar.mid[bk][bl][a:col]\n    else\n      let [pos, len] = [bc[0], bc[-1] - bc[0] + 1]\n      let t:DChar.dpv.ch.id = matchaddpos(s:DCharHL.c, [[bl, pos, len]],\n                                          \\-1, -1, #{window: t:DChar.wid[bk]})\n    endif\n  endif\n  call execute('autocmd! diffchar WinLeave <buffer=' . t:DChar.bnr[ak] .\n                                    \\'> call s:ClearDiffCharPair(' . ak . ')')\n  if t:DChar.dpv.pv < 2 | return | endif\n  let at = getbufline(t:DChar.bnr[ak], al)[0]\n  let bt = getbufline(t:DChar.bnr[bk], bl)[0]\n  if ae == 'c'\n    let hl = t:DChar.hgp[(count(map(t:DChar.hlc[ak][al][: a:col], 'v:val[0]'),\n                                                \\'c') - 1) % len(t:DChar.hgp)]\n    let [tb, tx, te] = ['', bt[bc[0] - 1 : bc[-1] - 1], '']\n  elseif ae == 'd'\n    let hl = s:DCharHL.A\n    let [tb, tx, te] = [(1 < bc[0]) ? '<' : '', bt[bc[0] - 1 : bc[-1] - 1],\n                                              \\(bc[-1] < len(bt)) ? '>' : '']\n  elseif ae == 'a'\n    let hl = s:DCharHL.D\n    let [tb, tx, te] = ['>', '', '<']\n  endif\n  if t:DChar.dpv.pv == 2\n    call execute(['echon tb', 'echohl ' . hl, 'echon tx', 'echohl None',\n                                                            \\'echon te'], '')\n  elseif t:DChar.dpv.pv == 3 || t:DChar.dpv.pv == 4\n    call s:HighlightPopupWindow(tb . tx . te)\n  endif\nendfunction\n\nfunction! s:ClearDiffCharPair(key) abort\n  if !exists('t:DChar') | return | endif\n  if !empty(t:DChar.dpv.ch)\n    let [bk, id] = [t:DChar.dpv.ch.bk, t:DChar.dpv.ch.id]\n    if win_id2win(t:DChar.wid[bk]) != 0\n      if 0 < id\n        silent! call matchdelete(id, t:DChar.wid[bk])\n      elseif id < 0\n        let [al, bi] = t:DChar.dpv.ch.lc\n        let bl = t:DChar.dfl[bk][index(t:DChar.dfl[(bk == 1) ? 2 : 1], al)]\n        let t:DChar.mid[bk][bl][bi] =\n                        \\s:ChangeVirtColumn(t:DChar.bnr[bk], id, s:DCharHL.D)\n      endif\n    endif\n    call execute('autocmd! diffchar WinLeave <buffer=' .\n                                        \\t:DChar.bnr[(bk == 1) ? 2 : 1] . '>')\n    let t:DChar.dpv.ch = {}\n  endif\n  if t:DChar.dpv.pv == 2 | call execute('echo', '')\n  elseif t:DChar.dpv.pv == 3 || t:DChar.dpv.pv == 4\n    call s:ClearPopupWindow()\n  endif\nendfunction\n\nif has('nvim')\nfunction! s:SetPopupWindow(on) abort\n  if a:on\n    let t:DChar.dpv.pw.fb = nvim_create_buf(0, 1)\n    let t:DChar.dpv.pw.fw = nvim_open_win(t:DChar.dpv.pw.fb, 0,\n                  \\#{relative: 'editor', row: 0, col: 0, height: 1, width: 1,\n                                            \\focusable: 0, style: 'minimal'})\n    call setbufline(t:DChar.dpv.pw.fb, 1, '')\n    call setwinvar(t:DChar.dpv.pw.fw, '&winblend', 100)\n    call setwinvar(t:DChar.dpv.pw.fw, '&winhighlight',\n                                                    \\'Normal:' . s:DCharHL.c)\n  else\n    call nvim_win_close(t:DChar.dpv.pw.fw, 1)\n    call nvim_buf_delete(t:DChar.dpv.pw.fb, #{force: 1})\n    let t:DChar.dpv.pw = {}\n  endif\nendfunction\n\nfunction! s:HighlightPopupWindow(tx) abort\n  if t:DChar.dpv.pv == 4 | let mp = getmousepos() | endif\n  call nvim_win_set_config(t:DChar.dpv.pw.fw,\n    \\extend((t:DChar.dpv.pv == 3) ? #{relative: 'cursor', row: 1, col: 0} :\n                \\#{relative: 'editor', row: mp.screenrow, col: mp.screencol},\n                                            \\#{width: strdisplaywidth(a:tx)}))\n  call setbufline(t:DChar.dpv.pw.fb, 1, a:tx)\n  call setwinvar(t:DChar.dpv.pw.fw, '&winblend', 0)\nendfunction\n\nfunction! s:ClearPopupWindow() abort\n  call nvim_win_set_config(t:DChar.dpv.pw.fw,\n                            \\#{relative: 'editor', row: 0, col: 0, width: 1})\n  call setbufline(t:DChar.dpv.pw.fb, 1, '')\n  call setwinvar(t:DChar.dpv.pw.fw, '&winblend', 100)\nendfunction\n\nfunction! s:SetVirtColumn() abort\n  let s:DCharNS = nvim_create_namespace('diffchar')\nendfunction\n\nfunction! s:HighlightVirtColumn(bn, ln, co, hl) abort\n  return -nvim_buf_set_extmark(a:bn, s:DCharNS, a:ln - 1, a:co - 1,\n                                \\#{virt_text: [[t:DChar.opt.vc, a:hl]],\n                                \\virt_text_pos: 'inline', invalidate: v:true})\nendfunction\n\nfunction! s:ClearVirtColumn(bn, id) abort\n  call nvim_buf_del_extmark(a:bn, s:DCharNS, -a:id)\nendfunction\n\nfunction! s:PurgeVirtColumn(bn, ln) abort\nendfunction\n\nfunction! s:ChangeVirtColumn(bn, id, hl) abort\n  let lc = nvim_buf_get_extmark_by_id(a:bn, s:DCharNS, -a:id, {})\n  if !empty(lc)\n    call s:ClearVirtColumn(a:bn, a:id)\n    return s:HighlightVirtColumn(a:bn, lc[0] + 1, lc[1] + 1, a:hl)\n  endif\n  return a:id\nendfunction\nelse\nfunction! s:SetPopupWindow(on) abort\n  if a:on\n    let t:DChar.dpv.pw = popup_create('', #{hidden: 1, scrollbar: 0, wrap: 0,\n                                                    \\highlight: s:DCharHL.c})\n  else\n    let t:DChar.dpv.pw = popup_close(t:DChar.dpv.pw)\n  endif\nendfunction\n\nfunction! s:HighlightPopupWindow(tx) abort\n  if t:DChar.dpv.pv == 4 | let mp = getmousepos() | endif\n  call popup_move(t:DChar.dpv.pw, (t:DChar.dpv.pv == 3) ?\n                                        \\#{line: 'cursor+1', col: 'cursor'} :\n                                    \\#{line: mp.screenrow, col: mp.screencol})\n  call popup_settext(t:DChar.dpv.pw, a:tx)\n  call popup_show(t:DChar.dpv.pw)\nendfunction\n\nfunction! s:ClearPopupWindow() abort\n  call popup_hide(t:DChar.dpv.pw)\nendfunction\n\nfunction! s:SetVirtColumn() abort\n  for hl in [s:DCharHL.c, s:DCharHL.D]\n    call call(empty(prop_type_get(hl)) ?\n                \\'prop_type_add' : 'prop_type_change', [hl, #{highlight: hl}])\n  endfor\nendfunction\n\nfunction! s:HighlightVirtColumn(bn, ln, co, hl) abort\n  return prop_add(a:ln, a:co, #{bufnr: a:bn, type: a:hl,\n                                                      \\text: t:DChar.opt.vc})\nendfunction\n\nfunction! s:ClearVirtColumn(bn, id) abort\n  call prop_remove(#{bufnr: a:bn, id: a:id})\nendfunction\n\nfunction! s:PurgeVirtColumn(bn, ln) abort\n  for hl in [s:DCharHL.c, s:DCharHL.D]\n    call prop_remove(#{bufnr: a:bn, type: hl, all: 1}, a:ln)\n  endfor\nendfunction\n\nfunction! s:ChangeVirtColumn(bn, id, hl) abort\n  let pr = prop_find(#{bufnr: a:bn, id: a:id, lnum: 1, col: 1})\n  if !empty(pr)\n    call s:ClearVirtColumn(a:bn, a:id)\n    return s:HighlightVirtColumn(a:bn, pr.lnum, pr.col, a:hl)\n  endif\n  return a:id\nendfunction\nendif\n\nfunction! diffchar#ToggleDiffModeSync(...) abort\n  \" a:0 : 0 = OptionSet diff, 1 = VimEnter\n  if !exists('t:DChar') && !s:GetOptionVar('DiffChar', 1) | return | endif\n  if a:0 || v:option_old != v:option_new\n    let cw = win_getid()\n    if exists('t:DChar') && ((a:0 || v:option_new) ?\n                            \\index(values(t:DChar.bnr), winbufnr(cw)) == -1 :\n                                        \\index(values(t:DChar.wid), cw) != -1)\n      \" diff mode ON on non-DChar buf || OFF on DChar win, try reset\n      let dk = filter([1, 2], 'getwinvar(t:DChar.wid[v:val], \"&diff\")')\n      if !empty(dk)\n        if empty(filter(copy(dk), 't:DChar.wid[v:val] == cw'))\n          let cw = t:DChar.wid[dk[0]]\n        endif\n        call win_execute(cw, 'call s:ResetDiffChar()')\n      endif\n    endif\n    if !exists('t:DChar')\n      let aw = win_id2win(cw)\n      let dw = filter(map(range(aw, winnr('$')) + range(1, aw - 1),\n                            \\'win_getid(v:val)'), 'getwinvar(v:val, \"&diff\")')\n      if 1 < len(dw)\n        \" 2 or more diff mode wins exists, try show\n        call win_execute(dw[0], 'call s:ShowDiffChar()')\n      endif\n    endif\n  endif\nendfunction\n\nfunction! s:WinClosedDiffChar() abort\n  \" reset and show (if possible) DChar on WinClosed\n  for ti in filter(gettabinfo(), 'has_key(v:val.variables, \"DChar\")')\n    let dc = ti.variables.DChar\n    for k in [1, 2]\n      if dc.wid[k] == eval(expand('<amatch>'))\n        call win_execute(dc.wid[k], 'call s:ResetDiffChar()')\n        let dw = filter(ti.windows, 'v:val != dc.wid[k] &&\n                  \\winbufnr(v:val) == dc.bnr[k] && getwinvar(v:val, \"&diff\")')\n        if !empty(dw)\n          call win_execute(dw[0], 'call s:ShowDiffChar()')\n        endif\n        return\n      endif\n    endfor\n  endfor\nendfunction\n\nfunction! s:RepairDiffChar() abort\n  \" repair DChar whose win was accidentally closed on BufWinEnter/WinEnter\n  if exists('t:DChar')\n    let dc = t:DChar\n    let dw = filter(copy(dc.wid), 'win_id2win(v:val) != 0 &&\n              \\winbufnr(v:val) == dc.bnr[v:key] && getwinvar(v:val, \"&diff\")')\n    if len(dw) == 1\n      call win_execute(values(dw)[0], ['call s:ResetDiffChar()',\n                                                    \\'call s:ShowDiffChar()'])\n    endif\n  endif\nendfunction\n\nfunction! s:EchoWarning(msg) abort\n  call timer_start(0, {-> execute(['echohl WarningMsg',\n                  \\'echo \"[diffchar]\" ' . string(a:msg), 'echohl None'], '')})\nendfunction\n\nif !has('vim9script') || !get(g:, 'vim9script', 1)\nfunction! s:TraceDiffChar(u1, u2, ih) abort\n  \" An O(NP) Sequence Comparison Algorithm\n  let [u1, u2, eq, e1, e2] = [a:u1, a:u2, '=', '-', '+']\n  let [n1, n2] = [len(u1), len(u2)]\n  if u1 ==# u2 | return repeat(eq, n1)\n  elseif n1 == 0 | return repeat(e2, n2)\n  elseif n2 == 0 | return repeat(e1, n1)\n  endif\n  let [N, M, u1, u2] = (n1 >= n2) ? [n1, n2, u1, u2] : [n2, n1, u2, u1]\n  if n1 < n2 | let [e1, e2] = [e2, e1] | endif\n  let D = N - M\n  let fp = repeat([-1], M + N + 1)\n  let etree = []    \" [next edit, previous p, previous k]\n  let p = -1\n  while fp[D] != N\n    let p += 1\n    let epk = repeat([[]], p * 2 + D + 1)\n    for k in range(-p, D - 1, 1) + range(D + p, D, -1)\n      let [y, epk[k]] = (fp[k - 1] + 1 > fp[k + 1]) ?\n                        \\[fp[k - 1] + 1, [e1, [(k > D) ? p - 1 : p, k - 1]]] :\n                        \\[fp[k + 1], [e2, [(k < D) ? p - 1 : p, k + 1]]]\n      let x = y - k\n      while x < M && y < N && u2[x] ==# u1[y]\n        let epk[k][0] .= eq | let [x, y] += [1, 1]\n      endwhile\n      let fp[k] = y\n    endfor\n    let etree += [epk]\n  endwhile\n  let ses = ''\n  while 1\n    let ses = etree[p][k][0] . ses\n    if [p, k] == [0, 0] | break | endif\n    let [p, k] = etree[p][k][1]\n  endwhile\n  let ses = ses[1 :]\n  return a:ih ? s:ReduceDiffHunk(a:u1, a:u2, ses) : ses\nendfunction\n\nfunction! s:ApplyDiffFunc(u1, u2, ih) abort\n  let [eq, e1, e2] = ['=', '-', '+']\n  let [n1, n2] = [len(a:u1), len(a:u2)]\n  if a:u1 ==# a:u2 | return repeat(eq, n1)\n  elseif n1 == 0 | return repeat(e2, n2)\n  elseif n2 == 0 | return repeat(e1, n1)\n  endif\n  let ses = ''\n  let vd = s:DiffFunc(a:u1, a:u2)\n  if !empty(vd)\n    let p1 = 0\n    for [i1, c1, i2, c2] in vd + [[n1, 0, 0, 0]]\n      let ses .= repeat(eq, i1 - p1) . repeat(e1, c1) . repeat(e2, c2)\n      let p1 = i1 + c1\n    endfor\n  endif\n  return a:ih ? s:ReduceDiffHunk(a:u1, a:u2, ses) : ses\nendfunction\n\nfunction! s:ReduceDiffHunk(u1, u2, ses) abort\n  \" in ==++++/==----, if == units equal to last ++/-- units, swap their SESs\n  \" (AB vs AxByAB : =+=+++ -> =++++= -> ++++==)\n  let [eq, e1, e2] = ['=', '-', '+']\n  let [p1, p2] = [-1, -1] | let ses = '' | let ez = ''\n  for ed in reverse(split(a:ses, '[+-]\\+\\zs'))\n    let es = ed . ez | let ez = '' | let qe = count(es, eq)\n    if 0 < qe\n      let [q1, q2] = [count(es, e1), count(es, e2)]\n      let [uu, pp, qq] = (qe <= q1 && q2 == 0) ? [a:u1, p1, q1] :\n                        \\(q1 == 0 && qe <= q2) ? [a:u2, p2, q2] : [[], 0, 0]\n      if !empty(uu) && uu[pp - qq - qe + 1 : pp - qq] ==# uu[pp - qe + 1 : pp]\n        let ez = es[-qe :] . es[qe : -qe - 1] | let es = es[: qe - 1]\n      else\n        let [p1, p2] -= [q1, q2]\n      endif\n    endif\n    let [p1, p2] -= [qe, qe]\n    let ses = es . ses\n  endfor\n  let ses = ez . ses\n  return ses\nendfunction\n\nif has('nvim')\nfunction! s:DiffFunc(u1, u2) abort\n  return map(v:lua.vim.diff(join(a:u1, \"\\n\") . \"\\n\", join(a:u2, \"\\n\") . \"\\n\",\n                                                  \\#{result_type: 'indices'}),\n                            \\'[v:val[0] - ((0 < v:val[1]) ? 1 : 0), v:val[1],\n                            \\v:val[2] - ((0 < v:val[3]) ? 1 : 0), v:val[3]]')\nendfunction\nelse\nfunction! s:DiffFunc(u1, u2) abort\n  return map(diff(a:u1, a:u2, #{output: 'indices'}),\n          \\'[v:val.from_idx, v:val.from_count, v:val.to_idx, v:val.to_count]')\nendfunction\nendif\n\nfunction! s:GetDiffUnitPos(uu, es) abort\n  if empty(a:uu[0])\n    return [[['d', [0, 0]]], [['a', [1, len(join(a:uu[1], ''))]]]]\n  elseif empty(a:uu[1])\n    return [[['a', [1, len(join(a:uu[0], ''))]]], [['d', [0, 0]]]]\n  endif\n  let cc = [[], []] | let ll = [1, 1] | let pp = [0, 0]\n  for ed in split(a:es, '[+-]\\+\\zs', 1)[: -2]\n    let qe = count(ed, '=') | let qq = [count(ed, '-'), count(ed, '+')]\n    let ee = (qq[0] == 0) ? ['d', 'a'] : (qq[1] == 0) ? ['a', 'd'] :\n                                                                  \\['c', 'c']\n    for k in [0, 1]\n      if 0 < qe\n        let [ll[k], pp[k]] +=\n                        \\[len(join(a:uu[k][pp[k] : pp[k] + qe - 1], '')), qe]\n      endif\n      if 0 < qq[k]\n        let hh = [ll[k]]\n        let [ll[k], pp[k]] +=\n                  \\[len(join(a:uu[k][pp[k] : pp[k] + qq[k] - 1], '')), qq[k]]\n        let hh += [ll[k] - 1]\n      else\n        let hh = [ll[k] - ((0 < pp[k]) ?\n                                \\len(matchstr(a:uu[k][pp[k] - 1], '.$')) : 0),\n                  \\ll[k] + ((pp[k] < len(a:uu[k])) ?\n                                \\len(matchstr(a:uu[k][pp[k]], '^.')) : 0) - 1]\n      endif\n      if t:DChar.opt.dv == 2\n        call extend(hh, [(0 < qq[k]) ? 0 : ll[k]], 1)\n      endif\n      let cc[k] += [[ee[k], hh]]\n    endfor\n  endfor\n  return cc\nendfunction\n\nfunction! s:HighlightDiffChar(lec) abort\n  let hn = len(t:DChar.hgp)\n  for k in [1, 2]\n    for [ln, ec] in items(a:lec[k])\n      if has_key(t:DChar.mid[k], ln) | continue | endif\n      if t:DChar.opt.dv == 2\n        call s:PurgeVirtColumn(t:DChar.bnr[k], ln)\n      endif\n      let t:DChar.mid[k][ln] = []\n      let t:DChar.hlc[k][ln] = ec\n      let cn = 0\n      for [ed, co] in ec\n        if ed == 'c'\n          let hl = t:DChar.hgp[cn % hn] | let cn += 1\n        elseif ed == 'a'\n          let hl = s:DCharHL.A\n        elseif ed == 'd'\n          if t:DChar.opt.dv == 0 || [co[0], co[-1]] == [0, 0]\n            continue\n          endif\n          let hl = s:DCharHL.E\n        endif\n        let t:DChar.mid[k][ln] += [(t:DChar.opt.dv == 2 && ed == 'd') ?\n              \\s:HighlightVirtColumn(t:DChar.bnr[k], ln, co[1], s:DCharHL.D) :\n              \\matchaddpos(hl, [[ln, co[0], co[-1] - co[0] + 1]], -3, -1,\n                                                  \\#{window: t:DChar.wid[k]})]\n      endfor\n      let t:DChar.mid[k][ln] += [matchaddpos(s:DCharHL.C, [[ln]], -5, -1,\n                                                  \\#{window: t:DChar.wid[k]})]\n    endfor\n  endfor\nendfunction\n\nfunction! s:ClearDiffChar(lines) abort\n  for k in [1, 2]\n    let wd = win_id2win(t:DChar.wid[k])\n    for ln in a:lines[k]\n      if wd != 0\n        for id in t:DChar.mid[k][ln]\n          if 0 < id\n            silent! call matchdelete(id, t:DChar.wid[k])\n          elseif id < 0\n            call s:ClearVirtColumn(t:DChar.bnr[k], id)\n          endif\n        endfor\n        if t:DChar.opt.dv == 2\n          call s:PurgeVirtColumn(t:DChar.bnr[k], ln)\n        endif\n      endif\n      unlet t:DChar.mid[k][ln]\n      unlet t:DChar.hlc[k][ln]\n    endfor\n  endfor\nendfunction\n\nfunction! s:ScrollDiffLines(key) abort\n  \" called on WinScrolled or other\n  if !exists('t:DChar') | return | endif\n  if 0 < a:key | let ak = a:key\n  else\n    let wid = eval(expand('<amatch>'))\n    let ak = 2 | while t:DChar.wid[ak] != wid | let ak -= 1\n      if ak == 0 | return | endif\n    endwhile\n  endif\n  let lcc = s:GetLineColCnr()\n  let sk = 0\n  for k in [1, 2]\n    \" check if a scroll happens in either window with no change on both\n    let sk += (t:DChar.lcc[k].cn != lcc[k].cn) ? -k * 10 :\n          \\(lcc[k].tl < t:DChar.lcc[k].tl || t:DChar.lcc[k].bl < lcc[k].bl) &&\n                  \\(empty(t:DChar.dfl[k]) || lcc[k].tl < t:DChar.dfl[k][0] ||\n                                      \\t:DChar.dfl[k][-1] < lcc[k].bl) ? k : 0\n    let [t:DChar.lcc[k].tl, t:DChar.lcc[k].bl, t:DChar.lcc[k].cl] =\n                                            \\[lcc[k].tl, lcc[k].bl, lcc[k].cl]\n  endfor\n  if 0 < sk\n    let dfl = s:FocusDiffLines(sk)\n    if t:DChar.dfl != dfl\n      \" reset/show DChar lines on dfl changes\n      let [dl, al] = [t:DChar.dfl[ak], dfl[ak]]\n      let ix = map([0, -1], 'index(t:DChar.dfl[ak], dfl[ak][v:val])')\n      if ix != [-1, -1]\n        let [dl, al] = [[], []]\n        if ix[0] != -1\n          let dl += (0 < ix[0]) ? t:DChar.dfl[ak][: ix[0] - 1] : []\n        else\n          let al = dfl[ak][: index(dfl[ak], t:DChar.dfl[ak][0]) - 1]\n        endif\n        if ix[1] != -1\n          let dl += (ix[1] < len(t:DChar.dfl[ak])) ?\n                                            \\t:DChar.dfl[ak][ix[1] + 1 :] : []\n        else\n          let al = dfl[ak][index(dfl[ak], t:DChar.dfl[ak][-1]) + 1 :]\n        endif\n      endif\n      if !empty(dl)\n        call win_execute(t:DChar.wid[ak], 'call s:ResetDiffChar(dl)')\n      endif\n      let t:DChar.dfl = dfl\n      if !empty(al)\n        call win_execute(t:DChar.wid[ak], 'call s:ShowDiffChar(al)')\n      endif\n    endif\n  endif\nendfunction\n\nfunction! s:FocusDiffLines(key) abort\n  \" a:key : initiate dfl (0), either (1/2) or both (3) key using existing dfl\n  let dfl = {}\n  if t:DChar.opt.fp == 0\n    if a:key == 0\n      for k in [1, 2]\n        call win_execute(t:DChar.wid[k], 'let dfl[k] =\n                                      \\s:GetDiffLines(1, t:DChar.lcc[k].ll)')\n      endfor\n      return dfl\n    else\n      return t:DChar.dfl\n    endif\n  endif\n  \" select specified win or more line displaying win as main\n  let tb = (a:key == 1) ? [1, 0] : (a:key == 2) ? [0, 1] :\n                \\map([1, 2], 't:DChar.lcc[v:val].bl - t:DChar.lcc[v:val].tl')\n  let [ak, bk] = (tb[0] >= tb[1]) ? [1, 2] : [2, 1]\n  \" get visible and upper/lower dfl in main win\n  let [tl, bl] = [t:DChar.lcc[ak].tl, t:DChar.lcc[ak].bl]\n  call win_execute(t:DChar.wid[ak], 'let dfl[ak] = s:GetDiffLines(tl, bl)')\n  let [tx, bx] = [1, t:DChar.lcc[ak].ll]\n  if 0 < t:DChar.opt.fp && 0 < a:key && !empty(t:DChar.dfl[ak])\n    if tl > t:DChar.dfl[ak][0] | let tx = t:DChar.dfl[ak][-1] | endif\n    if bl < t:DChar.dfl[ak][-1] | let bx = t:DChar.dfl[ak][0] | endif\n  endif\n  let [tl, bl] += [-1, 1]\n  if 1 < abs(t:DChar.opt.fp)\n    let [tz, bz] = [[], []]\n    let rc = winheight(t:DChar.wid[ak]) * (abs(t:DChar.opt.fp) - 1)\n    while 0 < rc\n      let fc = 0\n      if tx <= tl\n        call win_execute(t:DChar.wid[ak], 'let fc = foldclosed(tl)')\n        if fc == -1 | let tz = [tl] + tz | else | let tl = fc | endif\n        let tl -= 1 | let rc -= 1\n      endif\n      if bl <= bx\n        call win_execute(t:DChar.wid[ak], 'let fc = foldclosedend(bl)')\n        if fc == -1 | let bz += [bl] | else | let bl = fc | endif\n        let bl += 1 | let rc -= 1\n      endif\n      if fc == 0 | break | endif\n    endwhile\n    call win_execute(t:DChar.wid[ak], 'let dfl[ak] =\n                      \\s:CheckDiffLines(tz) + dfl[ak] + s:CheckDiffLines(bz)')\n  endif\n  \" if no dfl found in dfp, try to find one toward top/bottom\n  if empty(dfl[ak])\n    if 0 < a:key | return t:DChar.dfl | endif\n    call win_execute(t:DChar.wid[ak], 'let dfl[ak] =\n                  \\s:SearchDiffLines(0, tl, 1) + s:SearchDiffLines(1, bl, 1)')\n    if empty(dfl[ak]) | let dfl[bk] = [] | return dfl | endif\n  endif\n  \" get dfl in sub win based on the corresponding line between main/sub\n  let ds = #{t: [1, 1, 1, dfl[ak][0] - 1],\n            \\b: [0, t:DChar.lcc[bk].ll, dfl[ak][-1] + 1, t:DChar.lcc[ak].ll]}\n  let ix = -1\n  if 0 < a:key && !empty(t:DChar.dfl[ak])\n    let ix = index(t:DChar.dfl[ak], dfl[ak][0])\n    if ix != -1\n      let [sd, sl, dc] = [1, t:DChar.dfl[bk][ix], 0]\n    else\n      let ix = index(t:DChar.dfl[ak], dfl[ak][-1])\n      if ix != -1\n        let [sd, sl, dc] = [0, t:DChar.dfl[bk][ix], 0]\n      else\n        if t:DChar.dfl[ak][-1] < dfl[ak][0]\n          let ds.t = [1, t:DChar.dfl[bk][-1], t:DChar.dfl[ak][-1],\n                                                              \\dfl[ak][0] - 1]\n        elseif dfl[ak][-1] < t:DChar.dfl[ak][0]\n          let ds.b = [0, t:DChar.dfl[bk][0], dfl[ak][-1] + 1,\n                                                          \\t:DChar.dfl[ak][0]]\n        endif\n      endif\n    endif\n  endif\n  if ix == -1\n    let [sd, sl, df, dl] = (ds.t[3] - ds.t[2] <= ds.b[3] - ds.b[2]) ?\n                                                                  \\ds.t : ds.b\n    call win_execute(t:DChar.wid[ak], 'let dc = len(s:GetDiffLines(df, dl))')\n  endif\n  let ac = len(dfl[ak])\n  call win_execute(t:DChar.wid[bk], 'let dfl[bk] =\n                                        \\s:SearchDiffLines(sd, sl, dc + ac)')\n  let bc = len(dfl[bk])\n  if ac != bc\n    let [xk, xc] = (ac < bc) ? [bk, ac] : [ak, bc]\n    let dfl[xk] = (xc == 0) ? [] : sd ? dfl[xk][-xc :] : dfl[xk][: xc - 1]\n  endif\n  \" repair current dfl and merge with new dfl\n  if 0 < a:key && !empty(t:DChar.dfl[ak]) && t:DChar.dfl != dfl\n    \" repair and redraw current on-screen dfl realigned by linematch\n    if t:DChar.opt.lm\n      let dxl = deepcopy(t:DChar.dfl)\n      let dfi = {}\n      for k in [1, 2]\n        let dfi[k] = map(copy(dfl[k]), 'index(dxl[k], v:val)')\n      endfor\n      let [dl, al] = [[], []]\n      if dfi[1] != dfi[2]\n        for fi in range(len(dfi[ak]))\n          let [i1, i2] = [dfi[1][fi], dfi[2][fi]]\n          let [ki, xi] = (i1 == -1 && i2 != -1) ? [1, i2] :\n                                  \\(i1 != -1 && i2 == -1) ? [2, i1] : [0, -1]\n          if ki != 0\n            let [dl, al] += [[dxl[ak][xi]], [dfl[ak][fi]]]\n            let dxl[ki][xi] = dfl[ki][fi]\n          endif\n        endfor\n      endif\n      if !empty(dl)\n        call win_execute(t:DChar.wid[ak], 'call s:ResetDiffChar(dl)')\n      endif\n      let t:DChar.dfl = dxl\n      if !empty(al)\n        call win_execute(t:DChar.wid[ak], 'call s:ShowDiffChar(al)')\n      endif\n    endif\n    \" merge current and new dfls if overlapped or continued\n    if 0 < t:DChar.opt.fp\n      let mx = 0\n      if t:DChar.dfl[ak][0] <= dfl[ak][0]\n        if t:DChar.dfl[ak][-1] >= dfl[ak][0] | let mx = 1\n        else\n          call win_execute(t:DChar.wid[ak],\n                \\'let nl = s:SearchDiffLines(1, t:DChar.dfl[ak][-1] + 1, 1)')\n          if !empty(nl) && dfl[ak][0] <= nl[0] | let mx = 2 | endif\n        endif\n      elseif dfl[ak][-1] <= t:DChar.dfl[ak][-1]\n        if dfl[ak][-1] >= t:DChar.dfl[ak][0] | let mx = -1\n        else\n          call win_execute(t:DChar.wid[ak],\n                  \\'let nl = s:SearchDiffLines(0, t:DChar.dfl[ak][0] - 1, 1)')\n          if !empty(nl) && nl[0] <= dfl[ak][-1] | let mx = -2 | endif\n        endif\n      endif\n      if mx != 0\n        for k in [1, 2]\n          let dfl[k] = (0 < mx) ?\n              \\t:DChar.dfl[k] + filter(dfl[k], 'v:val > t:DChar.dfl[k][-1]') :\n              \\filter(dfl[k], 'v:val < t:DChar.dfl[k][0]') + t:DChar.dfl[k]\n        endfor\n      endif\n    endif\n  endif\n  return dfl\nendfunction\n\nfunction! s:SearchDiffLines(sd, sl, sc) abort\n  \" a:sd = direction (1:down, 0:up), a:sl = start line, a:sc = count\n  let dl = [] | let [ln, sc] = [a:sl, a:sc]\n  if a:sd\n    while 0 < sc && ln <= line('$')\n      let zl = []\n      while len(zl) < sc\n        let fc = foldclosedend(ln)\n        if fc == -1 | let zl += [ln] | else | let ln = fc | endif\n        let ln += 1\n      endwhile\n      let zl = s:CheckDiffLines(zl) | let sc -= len(zl) | let dl += zl\n    endwhile\n  else\n    while 0 < sc && 1 <= ln\n      let zl = []\n      while len(zl) < sc\n        let fc = foldclosed(ln)\n        if fc == -1 | let zl = [ln] + zl | else | let ln = fc | endif\n        let ln -= 1\n      endwhile\n      let zl = s:CheckDiffLines(zl) | let sc -= len(zl) | let dl = zl + dl\n    endwhile\n  endif\n  return dl\nendfunction\n\nfunction! s:GetDiffLines(sl, el) abort\n  let dl = [] | let ln = a:sl\n  while ln <= a:el\n    let fc = foldclosedend(ln)\n    if fc == -1 | let dl += [ln] | else | let ln = fc | endif\n    let ln += 1\n  endwhile\n  return s:CheckDiffLines(dl)\nendfunction\n\nfunction! s:CheckDiffLines(ll) abort\n  \" check [0] first, diff_hlID() sometimes fails for the 1st entry of the list\n  return filter([0] + a:ll,\n          \\'index([s:DiffHL.C.id, s:DiffHL.T.id], diff_hlID(v:val, 1)) != -1')\nendfunction\n\nfunction! s:ChecksumStr(str) abort\n  return sha256(a:str)[: 5]\nendfunction\nelse\nfunction! s:DiffCharVim9Functions() abort\ndef! s:TraceDiffChar(u1: list<string>, u2: list<string>, ih: bool): string\n  # An O(NP) Sequence Comparison Algorithm\n  const [eq, n1, n2] = ['=', len(u1), len(u2)]\n  var [e1, e2] = ['-', '+']\n  if u1 ==# u2 | return repeat(eq, n1)\n  elseif n1 == 0 | return repeat(e2, n2)\n  elseif n2 == 0 | return repeat(e1, n1)\n  endif\n  const [N, M, v1, v2] = (n1 >= n2) ? [n1, n2, u1, u2] : [n2, n1, u2, u1]\n  if n1 < n2 | [e1, e2] = [e2, e1] | endif\n  const D = N - M\n  var fp = repeat([-1], M + N + 1)\n  var etree = []    # [next edit, previous p, previous k]\n  var p = -1\n  while fp[D] != N\n    p += 1\n    var epk = repeat([[]], p * 2 + D + 1)\n    for k in range(-p, D - 1, 1) + range(D + p, D, -1)\n      var x: number | var y: number\n      [y, epk[k]] = (fp[k - 1] + 1 > fp[k + 1]) ?\n                        [fp[k - 1] + 1, [e1, [(k > D) ? p - 1 : p, k - 1]]] :\n                        [fp[k + 1], [e2, [(k < D) ? p - 1 : p, k + 1]]]\n      x = y - k\n      while x < M && y < N && v2[x] ==# v1[y]\n        epk[k][0] ..= eq | [x, y] += [1, 1]\n      endwhile\n      fp[k] = y\n    endfor\n    etree += [epk]\n  endwhile\n  var k = D\n  var ses = ''\n  while 1\n    ses = etree[p][k][0] .. ses\n    if [p, k] == [0, 0] | break | endif\n    [p, k] = etree[p][k][1]\n  endwhile\n  ses = ses[1 :]\n  return ih ? s:ReduceDiffHunk(u1, u2, ses) : ses\nenddef\n\ndef! s:ApplyDiffFunc(u1: list<string>, u2: list<string>, ih: bool): string\n  const [eq, e1, e2] = ['=', '-', '+']\n  const [n1, n2] = [len(u1), len(u2)]\n  if u1 ==# u2 | return repeat(eq, n1)\n  elseif n1 == 0 | return repeat(e2, n2)\n  elseif n2 == 0 | return repeat(e1, n1)\n  endif\n  var ses = ''\n  var vd = map(diff(u1, u2, {'output': 'indices'}),\n                  (_, v) => [v.from_idx, v.from_count, v.to_idx, v.to_count])\n  if !empty(vd)\n    var p1 = 0\n    for [i1, c1, i2, c2] in vd + [[n1, 0, 0, 0]]\n      ses ..= repeat(eq, i1 - p1) .. repeat(e1, c1) .. repeat(e2, c2)\n      p1 = i1 + c1\n    endfor\n  endif\n  return ih ? s:ReduceDiffHunk(u1, u2, ses) : ses\nenddef\n\ndef! s:ReduceDiffHunk(u1: list<string>, u2: list<string>, ses: string): string\n  # in ==++++/==----, if == units equal to last ++/-- units, swap their SESs\n  # (AB vs AxByAB : =+=+++ -> =++++= -> ++++==)\n  const [eq, e1, e2] = ['=', '-', '+']\n  var [p1, p2] = [-1, -1] | var xes = '' | var ez = ''\n  for ed in reverse(split(ses, '[+-]\\+\\zs'))\n    var es = ed .. ez | ez = '' | const qe = count(es, eq)\n    if 0 < qe\n      const [q1, q2] = [count(es, e1), count(es, e2)]\n      const [uu, pp, qq] = (qe <= q1 && q2 == 0) ? [u1, p1, q1] :\n                            (q1 == 0 && qe <= q2) ? [u2, p2, q2] : [[], 0, 0]\n      if !empty(uu) && uu[pp - qq - qe + 1 : pp - qq] ==# uu[pp - qe + 1 : pp]\n        ez = es[-qe :] .. es[qe : -qe - 1] | es = es[: qe - 1]\n      else\n        [p1, p2] -= [q1, q2]\n      endif\n    endif\n    [p1, p2] -= [qe, qe]\n    xes = es .. xes\n  endfor\n  xes = ez .. xes\n  return xes\nenddef\n\ndef! s:GetDiffUnitPos(uu: list<any>, es: string): list<any>\n  if empty(uu[0])\n    return [[['d', [0, 0]]], [['a', [1, len(join(uu[1], ''))]]]]\n  elseif empty(uu[1])\n    return [[['a', [1, len(join(uu[0], ''))]]], [['d', [0, 0]]]]\n  endif\n  var cc = [[], []] | var ll = [1, 1] | var pp = [0, 0]\n  for ed in split(es, '[+-]\\+\\zs', 1)[: -2]\n    var qe = count(ed, '=') | var qq = [count(ed, '-'), count(ed, '+')]\n    var ee = (qq[0] == 0) ? ['d', 'a'] : (qq[1] == 0) ? ['a', 'd'] :\n                                                                    ['c', 'c']\n    for k in [0, 1]\n      if 0 < qe\n        [ll[k], pp[k]] += [len(join(uu[k][pp[k] : pp[k] + qe - 1], '')), qe]\n      endif\n      var hh: list<number>\n      if 0 < qq[k]\n        hh = [ll[k]]\n        [ll[k], pp[k]] +=\n                      [len(join(uu[k][pp[k] : pp[k] + qq[k] - 1], '')), qq[k]]\n        hh += [ll[k] - 1]\n      else\n        hh = [ll[k] - ((0 < pp[k]) ?\n                                  len(matchstr(uu[k][pp[k] - 1], '.$')) : 0),\n              ll[k] + ((pp[k] < len(uu[k])) ?\n                                  len(matchstr(uu[k][pp[k]], '^.')) : 0) - 1]\n      endif\n      if t:DChar.opt.dv == 2\n        extend(hh, [(0 < qq[k]) ? 0 : ll[k]], 1)\n      endif\n      cc[k] += [[ee[k], hh]]\n    endfor\n  endfor\n  return cc\nenddef\n\ndef! s:HighlightDiffChar(lec: dict<any>)\n  const hn = len(t:DChar.hgp)\n  for k in [1, 2]\n    for [l, ec] in items(lec[k])\n      var ln = eval(l)\n      if has_key(t:DChar.mid[k], ln) | continue | endif\n      if t:DChar.opt.dv == 2\n        s:PurgeVirtColumn(t:DChar.bnr[k], ln)\n      endif\n      t:DChar.mid[k][ln] = []\n      t:DChar.hlc[k][ln] = ec\n      var hl: string | var cn = 0\n      for [ed, co] in ec\n        if ed == 'c'\n          hl = t:DChar.hgp[cn % hn] | cn += 1\n        elseif ed == 'a'\n          hl = s:DCharHL.A\n        elseif ed == 'd'\n          if t:DChar.opt.dv == 0 || [co[0], co[-1]] == [0, 0]\n            continue\n          endif\n          hl = s:DCharHL.E\n        endif\n        t:DChar.mid[k][ln] += [(t:DChar.opt.dv == 2 && ed == 'd') ?\n              s:HighlightVirtColumn(t:DChar.bnr[k], ln, co[1], s:DCharHL.D) :\n              matchaddpos(hl, [[ln, co[0], co[-1] - co[0] + 1]], -3, -1,\n                                                  {'window': t:DChar.wid[k]})]\n      endfor\n      t:DChar.mid[k][ln] += [matchaddpos(s:DCharHL.C, [ln], -5, -1,\n                                                  {'window': t:DChar.wid[k]})]\n    endfor\n  endfor\nenddef\n\ndef! s:ClearDiffChar(lines: dict<any>)\n  for k in [1, 2]\n    const wd = win_id2win(t:DChar.wid[k])\n    for ln in lines[k]\n      if wd != 0\n        for id in t:DChar.mid[k][ln]\n          if 0 < id\n            silent! matchdelete(id, t:DChar.wid[k])\n          elseif id < 0\n            s:ClearVirtColumn(t:DChar.bnr[k], id)\n          endif\n        endfor\n        if t:DChar.opt.dv == 2\n          s:PurgeVirtColumn(t:DChar.bnr[k], ln)\n        endif\n      endif\n      unlet t:DChar.mid[k][ln]\n      unlet t:DChar.hlc[k][ln]\n    endfor\n  endfor\nenddef\n\ndef! s:ScrollDiffLines(key: number)\n  # called on WinScrolled or other\n  if !exists('t:DChar') | return | endif\n  var ak: number\n  if 0 < key | ak = key\n  else\n    const wid = eval(expand('<amatch>'))\n    ak = 2 | while t:DChar.wid[ak] != wid | ak -= 1\n      if ak == 0 | return | endif\n    endwhile\n  endif\n  const lcc = s:GetLineColCnr()\n  var sk = 0\n  for k in [1, 2]\n    # check if a scroll happens in either window with no change on both\n    sk += (t:DChar.lcc[k].cn != lcc[k].cn) ? -k * 10 :\n          (lcc[k].tl < t:DChar.lcc[k].tl || t:DChar.lcc[k].bl < lcc[k].bl) &&\n                    (empty(t:DChar.dfl[k]) || lcc[k].tl < t:DChar.dfl[k][0] ||\n                                      t:DChar.dfl[k][-1] < lcc[k].bl) ? k : 0\n    [t:DChar.lcc[k].tl, t:DChar.lcc[k].bl, t:DChar.lcc[k].cl] =\n                                            [lcc[k].tl, lcc[k].bl, lcc[k].cl]\n  endfor\n  if 0 < sk\n    const dfl = s:FocusDiffLines(sk)\n    if t:DChar.dfl != dfl\n      # reset/show DChar lines on dfl changes\n      var [dl, al] = [t:DChar.dfl[ak], dfl[ak]]\n      const ix = map([0, -1], (_, v) => index(t:DChar.dfl[ak], dfl[ak][v]))\n      if ix != [-1, -1]\n        [dl, al] = [[], []]\n        if ix[0] != -1\n          dl += (0 < ix[0]) ? t:DChar.dfl[ak][: ix[0] - 1] : []\n        else\n          al = dfl[ak][: index(dfl[ak], t:DChar.dfl[ak][0]) - 1]\n        endif\n        if ix[1] != -1\n          dl += (ix[1] < len(t:DChar.dfl[ak])) ?\n                                            t:DChar.dfl[ak][ix[1] + 1 :] : []\n        else\n          al = dfl[ak][index(dfl[ak], t:DChar.dfl[ak][-1]) + 1 :]\n        endif\n      endif\n      if !empty(dl)\n        s:WinExec(t:DChar.wid[ak], 's:ResetDiffChar', [dl])\n      endif\n      t:DChar.dfl = dfl\n      if !empty(al)\n        s:WinExec(t:DChar.wid[ak], 's:ShowDiffChar', [al])\n      endif\n    endif\n  endif\nenddef\n\ndef! s:FocusDiffLines(key: number): dict<any>\n  # a:key : initiate dfl (0), either (1/2) or both (3) key using existing dfl\n  var dfl = {}\n  if t:DChar.opt.fp == 0\n    if key == 0\n      for k in [1, 2]\n        dfl[k] = s:WinExec(t:DChar.wid[k], 's:GetDiffLines',\n                                                      [1, t:DChar.lcc[k].ll])\n      endfor\n      return dfl\n    else\n      return t:DChar.dfl\n    endif\n  endif\n  # select specified win or more line displaying win as main\n  const tb = (key == 1) ? [1, 0] : (key == 2) ? [0, 1] :\n                  map([1, 2], (_, v) => t:DChar.lcc[v].bl - t:DChar.lcc[v].tl)\n  const [ak, bk] = (tb[0] >= tb[1]) ? [1, 2] : [2, 1]\n  # get visible and upper/lower dfl in main win\n  var [tl, bl] = [t:DChar.lcc[ak].tl, t:DChar.lcc[ak].bl]\n  dfl[ak] = s:WinExec(t:DChar.wid[ak], 's:GetDiffLines', [tl, bl])\n  var [tx, bx] = [1, t:DChar.lcc[ak].ll]\n  if 0 < t:DChar.opt.fp && 0 < key && !empty(t:DChar.dfl[ak])\n    if tl > t:DChar.dfl[ak][0] | tx = t:DChar.dfl[ak][-1] | endif\n    if bl < t:DChar.dfl[ak][-1] | bx = t:DChar.dfl[ak][0] | endif\n  endif\n  [tl, bl] += [-1, 1]\n  if 1 < abs(t:DChar.opt.fp)\n    var [tz, bz] = [[], []]\n    var rc = winheight(t:DChar.wid[ak]) * (abs(t:DChar.opt.fp) - 1)\n    while 0 < rc\n      var fc = 0\n      if tx <= tl\n        fc = s:WinExec(t:DChar.wid[ak], 'foldclosed', [tl])\n        if fc == -1 | tz = [tl] + tz | else | tl = fc | endif\n        tl -= 1 | rc -= 1\n      endif\n      if bl <= bx\n        fc = s:WinExec(t:DChar.wid[ak], 'foldclosedend', [bl])\n        if fc == -1 | bz += [bl] | else | bl = fc | endif\n        bl += 1 | rc -= 1\n      endif\n      if fc == 0 | break | endif\n    endwhile\n    dfl[ak] = s:WinExec(t:DChar.wid[ak], 's:CheckDiffLines', [tz]) +\n                dfl[ak] + s:WinExec(t:DChar.wid[ak], 's:CheckDiffLines', [bz])\n  endif\n  # if no dfl found in dfp, try to find one toward top/bottom\n  if empty(dfl[ak])\n    if 0 < key | return t:DChar.dfl | endif\n    dfl[ak] = s:WinExec(t:DChar.wid[ak], 's:SearchDiffLines', [0, tl, 1]) +\n                  s:WinExec(t:DChar.wid[ak], 's:SearchDiffLines', [1, bl, 1])\n    if empty(dfl[ak]) | dfl[bk] = [] | return dfl | endif\n  endif\n  # get dfl in sub win based on the corresponding line between main/sub\n  var [sd, sl, dc, fl, ll] = [0, 0, 0, 0, 0]\n  var ds = {'t': [1, 1, 1, dfl[ak][0] - 1],\n            'b': [0, t:DChar.lcc[bk].ll, dfl[ak][-1] + 1, t:DChar.lcc[ak].ll]}\n  var ix = -1\n  if 0 < key && !empty(t:DChar.dfl[ak])\n    ix = index(t:DChar.dfl[ak], dfl[ak][0])\n    if ix != -1\n      [sd, sl, dc] = [1, t:DChar.dfl[bk][ix], 0]\n    else\n      ix = index(t:DChar.dfl[ak], dfl[ak][-1])\n      if ix != -1\n        [sd, sl, dc] = [0, t:DChar.dfl[bk][ix], 0]\n      else\n        if t:DChar.dfl[ak][-1] < dfl[ak][0]\n          ds.t = [1, t:DChar.dfl[bk][-1], t:DChar.dfl[ak][-1], dfl[ak][0] - 1]\n        elseif dfl[ak][-1] < t:DChar.dfl[ak][0]\n          ds.b = [0, t:DChar.dfl[bk][0], dfl[ak][-1] + 1, t:DChar.dfl[ak][0]]\n        endif\n      endif\n    endif\n  endif\n  if ix == -1\n    [sd, sl, fl, ll] = (ds.t[3] - ds.t[2] <= ds.b[3] - ds.b[2]) ? ds.t : ds.b\n    dc = len(s:WinExec(t:DChar.wid[ak], 's:GetDiffLines', [fl, ll]))\n  endif\n  const ac = len(dfl[ak])\n  dfl[bk] = s:WinExec(t:DChar.wid[bk], 's:SearchDiffLines', [sd, sl, dc + ac])\n  const bc = len(dfl[bk])\n  if ac != bc\n    var [xk, xc] = (ac < bc) ? [bk, ac] : [ak, bc]\n    dfl[xk] = (xc == 0) ? [] : sd ? dfl[xk][-xc :] : dfl[xk][: xc - 1]\n  endif\n  # repair current dfl and merge with new dfl\n  if 0 < key && !empty(t:DChar.dfl[ak]) && t:DChar.dfl != dfl\n    # repair and redraw current on-screen dfl realigned by linematch\n    if t:DChar.opt.lm\n      var dxl = deepcopy(t:DChar.dfl)\n      var dfi = {}\n      for k in [1, 2]\n        dfi[k] = map(copy(dfl[k]), (_, v) => index(dxl[k], v))\n      endfor\n      var [dl, al] = [[], []]\n      if dfi[1] != dfi[2]\n        for fi in range(len(dfi[ak]))\n          const [i1, i2] = [dfi[1][fi], dfi[2][fi]]\n          const [ki, xi] = (i1 == -1 && i2 != -1) ? [1, i2] :\n                                  (i1 != -1 && i2 == -1) ? [2, i1] : [0, -1]\n          if ki != 0\n            [dl, al] += [[dxl[ak][xi]], [dfl[ak][fi]]]\n            dxl[ki][xi] = dfl[ki][fi]\n          endif\n        endfor\n      endif\n      if !empty(dl)\n        s:WinExec(t:DChar.wid[ak], 's:ResetDiffChar', [dl])\n      endif\n      t:DChar.dfl = dxl\n      if !empty(al)\n        s:WinExec(t:DChar.wid[ak], 's:ShowDiffChar', [al])\n      endif\n    endif\n    # merge current and new dfls if overlapped or continued\n    if 0 < t:DChar.opt.fp\n      var nl: list<number>\n      var mx = 0\n      if t:DChar.dfl[ak][0] <= dfl[ak][0]\n        if t:DChar.dfl[ak][-1] >= dfl[ak][0] | mx = 1\n        else\n          nl = s:WinExec(t:DChar.wid[ak], 's:SearchDiffLines',\n                                              [1, t:DChar.dfl[ak][-1] + 1, 1])\n          if !empty(nl) && dfl[ak][0] <= nl[0] | mx = 2 | endif\n        endif\n      elseif dfl[ak][-1] <= t:DChar.dfl[ak][-1]\n        if dfl[ak][-1] >= t:DChar.dfl[ak][0] | mx = -1\n        else\n          nl = s:WinExec(t:DChar.wid[ak], 's:SearchDiffLines',\n                                              [0, t:DChar.dfl[ak][0] - 1, 1])\n          if !empty(nl) && nl[0] <= dfl[ak][-1] | mx = -2 | endif\n        endif\n      endif\n      if mx != 0\n        for k in [1, 2]\n          dfl[k] = (0 < mx) ?\n            t:DChar.dfl[k] + filter(dfl[k], (_, v) => v > t:DChar.dfl[k][-1]) :\n            filter(dfl[k], (_, v) => v < t:DChar.dfl[k][0]) + t:DChar.dfl[k]\n        endfor\n      endif\n    endif\n  endif\n  return dfl\nenddef\n\ndef! s:SearchDiffLines(sd: number, sl: number, sc: number): list<number>\n  # a:sd = direction (1:down, 0:up), a:sl = start line, a:sc = count\n  var dl = [] | var ln = sl | var sn = sc\n  if sd\n    while 0 < sn && ln <= line('$')\n      var zl = []\n      while len(zl) < sn\n        var fc = foldclosedend(ln)\n        if fc == -1 | zl += [ln] | else | ln = fc | endif\n        ln += 1\n      endwhile\n      zl = s:CheckDiffLines(zl) | sn -= len(zl) | dl += zl\n    endwhile\n  else\n    while 0 < sn && 1 <= ln\n      var zl = []\n      while len(zl) < sn\n        var fc = foldclosed(ln)\n        if fc == -1 | zl = [ln] + zl | else | ln = fc | endif\n        ln -= 1\n      endwhile\n      zl = s:CheckDiffLines(zl) | sn -= len(zl) | dl = zl + dl\n    endwhile\n  endif\n  return dl\nenddef\n\ndef! s:GetDiffLines(sl: number, el: number): list<number>\n  var dl = [] | var ln = sl\n  while ln <= el\n    var fc = foldclosedend(ln)\n    if fc == -1 | dl += [ln] | else | ln = fc | endif\n    ln += 1\n  endwhile\n  return s:CheckDiffLines(dl)\nenddef\n\ndef! s:CheckDiffLines(ll: list<number>): list<number>\n  # check [0] first, diff_hlID() sometimes fails for the 1st entry of the list\n  return filter([0] + ll,\n      (_, v) => index([s:DiffHL.C.id, s:DiffHL.T.id], diff_hlID(v, 1)) != -1)\nenddef\n\ndef! s:ChecksumStr(str: string): string\n  return sha256(str)[: 5]\nenddef\n\ndef! s:WinExec(wn: number, fn: string, ag: list<any>): any\n  win_execute(wn,\n          $'vim9cmd t:DChar.tmp = call({string(function(fn))}, {string(ag)})')\n  return t:DChar.tmp\nenddef\nendfunction\ncall s:DiffCharVim9Functions()\nendif\n\nlet &cpoptions = s:save_cpo\nunlet s:save_cpo\n\n\" vim: ts=2 sw=0 sts=-1 et\n"
  },
  {
    "path": "doc/diffchar.txt",
    "content": "*diffchar.txt*  Highlight the exact differences, based on characters and words\n>\n  ____   _  ____  ____  _____  _   _  _____  ____   \n |    | | ||    ||    ||     || | | ||  _  ||  _ |  \n |  _  || ||  __||  __||     || | | || | | || | ||  \n | | | || || |__ | |__ |   __|| |_| || |_| || |_||_ \n | |_| || ||  __||  __||  |   |     ||     ||  __  |\n |     || || |   | |   |  |__ |  _  ||  _  || |  | |\n |____| |_||_|   |_|   |_____||_| |_||_| |_||_|  |_|\n<\nLast Change: 2025/10/01\nVersion:     10.0 (on or after vim 9.0 and nvim 0.7.0)\nAuthor:      Rick Howe (Takumi Ohtani) <rdcxy754@ybb.ne.jp>\nCopyright:   (c) 2014-2025 Rick Howe\nLicense:     MIT\n\n-----------------------------------------------------------------------------\nINTRODUCTION                                           *diffchar*\n\nThis plugin has been developed in order to make diff mode more useful. Vim\nhighlights all the text in between the first and last different characters on\na changed line. But this plugin will find the exact differences between them,\ncharacter by character - so called DiffChar.\n\nFor example, in diff mode: (`^`:`DiffText`, |#|:|DiffAdd|, |@|:|DiffDelete|)\n    +-------------------------------------------------+\n    |The `quick brown fox jumps over the lazy` dog.     |\n    |    `^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^`          |\n    |~                                                |\n    +-------------------------------------------------+\n    |The `lazy fox jumps over the quick brown` dog.     |\n    |    `^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^`          |\n    |~                                                |\n    +-------------------------------------------------+\n\nThis plugin will exactly show the changed/added/deleted units:\n    +-------------------------------------------------+\n    |The `quick` |brown| fox jumps over the `lazy`  dog.    |\n    |    `^^^^^` |######|                   `^^^^` |@|        |\n    |~                                                |\n    +-------------------------------------------------+\n    |The `lazy`  fox jumps over the `quick` |brown| dog.    |\n    |    `^^^^` |@|                   `^^^^^` |######|        |\n    |~                                                |\n    +-------------------------------------------------+\n\nSync with diff mode ~\nThis plugin will synchronously show/reset the highlights of the exact\ndifferences as soon as the diff mode begins/ends. And the exact differences\nwill be kept updated while editing. Note that this plugin does nothing if an\n\"inline\" item is set in the 'doffopt' option and its value is other than\n\"simple\". On plugin loading, the item is removed only if set by default so\nthat this plugin can work.\n\nDiff unit ~\nThis plugin shows the diffs based on a |g:DiffUnit|. Its default is 'Word1'\nand it handles a \\w\\+ word and a \\W character as a diff unit. There are other\ntypes of word provided and you can also set 'Char' to compare character by\ncharacter. In addition, you can specify one or more diff unit delimiters, such\nas comma (','), colon (':'), tab (\"\\t\"), and HTML tag symbols ('<' and '>'),\nand also specify a custom pattern in the |g:DiffUnit|.\n\nDiff matching colors ~\nIn diff mode, the corresponding |hl-DiffChange| lines are compared between two\nwindows. As a default, all the changed units are highlighted with\n|hl-DiffText|. You can set |g:DiffColors| to use more than one matching color\nto make it easy to find the corresponding units between two windows. The\nnumber of colors depends on the color scheme. In addition, an added unit is\nalways highlighted with |hl-DiffAdd| and the position of the corresponding\ndeleted unit is shown with bold/underline or a virtual blank column,\ndepending on a |g:DiffDelPosVisible|.\n\nDiff pair visible ~\nWhile showing the exact differences, when the cursor is moved on a diff unit,\nyou can see its corresponding unit highlighted with |hl-Cursor|,\n|hl-TermCursor|, or similar one in another window, based on a\n|g:DiffPairVisible|. If you change its default, the corresponding unit is\nechoed in the command line or displayed in a popup/floating window just below\nthe cursor position or at the mouse position. Those options take effect on\n`:diffupdate` command as well.\n\nJump to next/prev diff unit ~\nYou can use `]b` or `]e` to jump cursor to start or end position of the next\ndiff unit, and `[b` or `[e` to the start or end position of the previous unit.\n\nGet/put a diff unit ~\nLike line-based `:diffget`/`:diffput` and `do`/`dp` vim commands, you can use\n`<Leader>g` and `<Leader>p` commands in normal mode to get and put each diff\nunit, where the cursor is on, between 2 buffers and undo its difference. Those\nkeymaps are configurable in your vimrc and so on.\n\nCheck diff lines locally ~\nWhen the diff mode begins, this plugin locally checks the |hl-DiffChange|\nlines in the limited range of the current visible and its upper/lower lines of\na window. And each time a cursor is moved on to another range upon scrolling\nor searching, those diff lines will be checked in that range. Which means,\nindependently of the file size, the number of lines to be checked and then the\ntime consumed are always constant.\n\nTab page individual ~\nThis plugin works on each tab page individually. You can use a tab page\nvariable (t:), instead of a global one (g:), to specify different options on\neach tab page. Note that this plugin can not handle more than two diff mode\nwindows in a tab page. If it would happen, to prevent any trouble, all the\nhighlighted units are to be reset in the tab page.\n\nFollow 'diffopt' option ~\nThis plugin supports \"icase\", \"iwhite\", \"iwhiteall\", and \"iwhiteeol\" in the\n'diffopt' option. In addition, when \"indent-heuristic\" is specified,\npositioning of the added/deleted diff units is adjusted to reduce the number\nof diff hunks and make them easier to read.\n\nComparison algorithm ~\nTo find the exact differences, this plugin uses \"An O(NP) Sequence Comparison\nAlgorithm\" developed by S.Wu, et al., which always finds an optimum sequence.\nBut it takes time to check a long and dissimilar line. To improve the\nperformance, the algorithm is also implemented in Vim9 script. In addition,\nif available, this plugin uses a builtin diff function (|diff()| in vim\npatch-9.1.0071 and Lua |vim.diff()| in nvim 0.6.0) and makes it much faster.\n\nSee also ~\nThere are other diff related plugins available:\n- |spotdiff.vim|: A range and area selectable `:diffthis` to compare partially\n  (https://github.com/rickhowe/spotdiff.vim)\n- |wrapfiller|: Align each wrapped line virtually between windows\n  (https://github.com/rickhowe/wrapfiller)\n- |difffilter|: Selectively compare lines as you want in diff mode\n  (https://github.com/rickhowe/difffilter)\n- |diffunitsyntax|: Highlight word or character based diff units in diff format\n  (https://github.com/rickhowe/diffunitsyntax)\n\n-----------------------------------------------------------------------------\nOPTIONS                                                *diffchar-options*\n\n|g:DiffUnit|, |t:DiffUnit|\n    A type of diff unit\n    'Char'    : any single character\n    'Word1'   : \\w\\+ word and any \\W single character (default)\n    'Word2'   : non-space and space words\n    'Word3'   : \\< or \\> character class boundaries (set by 'iskeyword')\n    'word'    : see `word`\n    'WORD'    : see `WORD`\n    '[{del}]' : one or more diff unit delimiters (e.g. \"[,:\\t<>]\")\n    '/{pat}/' : a pattern to split into diff units (e.g. '/.\\{4}\\zs/')\n\n|g:DiffColors|, |t:DiffColors|\n    Matching colors for changed units\n    0       : |hl-DiffText| (default)\n    1       : |hl-DiffText| + a few (3, 4, ...) highlight groups\n    2       : |hl-DiffText| + several (7, 8, ...) highlight groups\n    3       : |hl-DiffText| + many (11, 12, ...) highlight groups\n    100     : all available highlight groups in random order\n    [{hlg}] : a list of your favorite highlight groups\n\n|g:DiffPairVisible|, |t:DiffPairVisible|\n    Visibility of corresponding diff units\n    0 : disable\n    1 : highlight with |hl-Cursor| (default)\n    2 : highlight with |hl-Cursor| + echo in the command line\n    3 : highlight with |hl-Cursor| + popup/floating window at cursor position\n    4 : highlight with |hl-Cursor| + popup/floating window at mouse position\n\n|g:DiffDelPosVisible|, |t:DiffDelPosVisible|\n    Visibility of the position of deleted units\n    0 : disable\n    1 : highlight previous/next chars of a deleted unit in bold/underline\n        (default if inline |virtual-text| is not available)\n    2 : virtually show a blank column (set by \"space\" item in 'listchars')\n        with |hl-DiffDelete| (default if inline |virtual-text| is available)\n\n-----------------------------------------------------------------------------\nKEYMAPS                                                *diffchar-keymaps*\n\n<Plug>JumpDiffCharPrevStart (default: `[b`)\n    Jump cursor to the start position of the previous diff unit\n\n<Plug>JumpDiffCharNextStart (default: `]b`)\n    Jump cursor to the start position of the next diff unit\n\n<Plug>JumpDiffCharPrevEnd (default: `[e`)\n    Jump cursor to the end position of the previous diff unit\n\n<Plug>JumpDiffCharNextEnd (default: `]e`)\n    Jump cursor to the end position of the next diff unit\n\n<Plug>GetDiffCharPair (default: `<Leader>g`)\n    Get a corresponding diff unit from another buffer to undo difference\n\n<Plug>PutDiffCharPair (default: `<Leader>p`)\n    Put a corresponding diff unit to another buffer to undo difference\n\n-----------------------------------------------------------------------------\nCHANGE HISTORY                                         *diffchar-history*\n\n10.0\n* Added |g:DiffDelPosVisible| option to show the position of deleted units.\n* Implemented to remove an \"inline\" item from the 'diffopt' option on initial\n  loading so that this plugin can work.\n* Made this plugin available on or after vim 9.0 and nvim 0.7.0.\n\n9.9\n* Improved to remain previous diff lines highlighted upon scrolling if\n  possible and not to check them later again.\n* Implemented a few more functions in Vim9 script to make them faster.\n\n9.8\n* Improved to follow \"linematch\", which is run to realign diff lines when\n  displayed on screen, if specified in the 'diffopt' option (default on nvim\n  0.11). Accordingly, fixed errors such as E684 and E716 on nvim.\n* Changed to disable this plugin to avoid a conflict with character/word-wise\n  \"inline\" diff (patch-9.1.1243), if specified in the 'diffopt' option.\n* Changed to overwrite nvim default mappings to set plugin specific ones on\n  plugin loading. As of nvim 0.11, `[b` and `]b` keys are duplicated.\n* Fixed to handle tab and space as a same whitespace character if \"iwhite\" or\n  \"iwhiteall\" are specified in the 'diffopt' options.\n\n9.7\n* Implemented to use a new builtin |diff()| function (available on\n  patch-9.1.0071) to compare diff units and make it faster in vim.\n\n9.6, 9.61\n* Changed to locally but not incrementally check the limited number of the\n  |hl-DiffChange| lines upon scrolling or searching.\n* Fixed not to use |hl-DiffText| as the first highlight group if [{hlg}] is\n  specified in the |g:DiffColors| option.\n\n9.5\n* Improved not to update diff unit highlighting on unchanged diff lines on the\n  DiffUpdated event.\n* Fixed the E421 error when 'termguicolors' is off on nvim.\n\n9.4\n* Implemented to use a builtin Lua |vim.diff()| function to compare diff units\n  and make it faster in nvim.\n\n9.3\n* Improved to follow the 'wincolor' option and show the colors accordingly.\n* Fixed to work on some color scheme in which the \"g:colors_name\" variable is\n  different from its file name.\n\n9.2\n* Fixed the plugin error when 3 or more windows turn to be diff mode.\n* Fixed the issue which the new diff lines can not be incrementally found upon\n  scrolling on patch-9.0.0913 or later.\n* Made it faster to find diff lines in a file with many lines folded.\n\n9.1\n* Added vim original 'word' and 'WORD' units, one or more unit delimiters, and\n  a custom pattern to split into diff units in |g:DiffUnit| option.\n* Improved to redraw diff units whenever the ColorScheme event happens when\n  multiple matching colors are being used (0 < |g:DiffColors|).\n* Changed the category of |g:DiffColors| option as a few, several, and many\n  numbers of matching colors, depending on the loaded color scheme.\n* Added a list of your favorite highlight groups in |g:DiffColors| option.\n\n9.01\n* Fixed to work on a |hl-Diff| highlighting group even if it is linked.\n\n9.0\n* Enhanced to make diff units easier to read when \"indent-heuristic\" is\n  specified in the 'diffopt' option.\n* Improved to update diff units using `:diffupdate` command when |g:DiffUnit|,\n  |g:DiffColors|, and |g:DiffPairVisible| options are modified.\n* Updated to check a new WinScrolled event (patch-8.2.4713) to incrementally\n  find scrolled diff lines.\n* Implemented the comparison algorithm and diff unit highlighting in Vim9\n  script and made them 10 times faster on patch-8.2.3965 or later.\n* Made this plugin available on or after patch-8.1.1418 and nvim-0.5.0.\n\n vim:tw=78:ts=8:ft=help:norl:\n"
  },
  {
    "path": "plugin/diffchar.vim",
    "content": "\" diffchar.vim: Highlight the exact differences, based on characters and words\n\"\n\"  ____   _  ____  ____  _____  _   _  _____  ____   \n\" |    | | ||    ||    ||     || | | ||  _  ||  _ |  \n\" |  _  || ||  __||  __||     || | | || | | || | ||  \n\" | | | || || |__ | |__ |   __|| |_| || |_| || |_||_ \n\" | |_| || ||  __||  __||  |   |     ||     ||  __  |\n\" |     || || |   | |   |  |__ |  _  ||  _  || |  | |\n\" |____| |_||_|   |_|   |_____||_| |_||_| |_||_|  |_|\n\"\n\" Last Change: 2025/10/01\n\" Version:     10.0 (on or after vim 9.0 and nvim 0.7.0)\n\" Author:      Rick Howe (Takumi Ohtani) <rdcxy754@ybb.ne.jp>\n\" Copyright:   (c) 2014-2025 Rick Howe\n\" License:     MIT\n\nif exists('g:loaded_diffchar') || !has('diff') ||\n                                      \\(v:version < 900 && !has('nvim-0.7.0'))\nlet g:loaded_diffchar = 0\n  finish\nendif\nlet g:loaded_diffchar = 10.0\n\nlet s:save_cpo = &cpoptions\nset cpo&vim\n\n\" Keymaps\nfor [key, plg, cmd] in [\n  \\['[b', '<Plug>JumpDiffCharPrevStart',\n                                      \\':<C-U>call diffchar#JumpDiffChar(0)'],\n  \\[']b', '<Plug>JumpDiffCharNextStart',\n                                      \\':<C-U>call diffchar#JumpDiffChar(1)'],\n  \\['[e', '<Plug>JumpDiffCharPrevEnd',\n                                      \\':<C-U>call diffchar#JumpDiffChar(2)'],\n  \\[']e', '<Plug>JumpDiffCharNextEnd',\n                                      \\':<C-U>call diffchar#JumpDiffChar(3)'],\n  \\['<Leader>g', '<Plug>GetDiffCharPair',\n                                  \\':<C-U>call diffchar#CopyDiffCharPair(0)'],\n  \\['<Leader>p', '<Plug>PutDiffCharPair',\n                                  \\':<C-U>call diffchar#CopyDiffCharPair(1)']]\n  if !hasmapto(plg, 'n') && maparg(key, 'n') =~ '^$\\|_defaults.lua'\n    if get(g:, 'DiffCharDoMapping', 1)\n      call execute('nmap <silent> ' . key . ' ' . plg)\n    endif\n  endif\n  call execute('nnoremap <silent> ' . plg . ' ' . cmd . '<CR>')\nendfor\n\n\" Event groups\nlet g:DiffCharInitEvent = ['augroup diffchar', 'autocmd!',\n                \\'autocmd OptionSet diff call diffchar#ToggleDiffModeSync()',\n                                                              \\'augroup END']\ncall execute(g:DiffCharInitEvent)\ncall execute('autocmd diffchar VimEnter * ++once\n                    \\ if &diff | call diffchar#ToggleDiffModeSync(1) | endif')\n\n\" remove 'inline' from &diffopt if set only by default not to disable plugin\nlet inl = 'inline'\nif &diffopt =~ inl\n  let dip = split(&diffopt, ',')\n  set diffopt&\n  let def = split(&diffopt, ',')\n  if match(filter(copy(dip), 'index(def, v:val) == -1'), inl) == -1\n    call filter(dip, 'v:val !~ inl')\n  endif\n  let &diffopt = join(dip, ',')\nendif\n\nlet &cpoptions = s:save_cpo\nunlet s:save_cpo\n\n\" vim: ts=2 sw=0 sts=-1 et\n"
  }
]