Open6

gitgutter内製化したい

kawarimidollkawarimidoll

gitgutter本家は「なるべくプラグインが処理しないようにgrepが使えるならgrepを使います」とある
たしかにそう なるべく非同期でやれるならそっちのほうがよい
(vimの処理をブロックしたくない)

kawarimidollkawarimidoll

こんな感じにすればを変更範囲を得られる

git --no-pager diff -U0 --no-color --no-ext-diff {fname} | grep '^@@.*@@$'
@@ -60 +60 @@
@@ -79,0 +80,2 @@

あとはなんとかしてパース

https://zenn.dev/kuu/articles/unified_diff_memo_20221210

変更が1行のときに省略されるのがちょっと厄介(0のときは表示される)

kawarimidollkawarimidoll
  • 現在のファイルのdiffを表示
  • 変更範囲に関係する行をgrep
  • 「1行」が省略されている部分を補完
  • 関係ない部分を削除
git --no-pager diff -U0 --no-color --no-ext-diff README.md
  | grep '^@@'
  | sed -r 's/[-+]([0-9]+) /\1,1,/g'
  | sed -r 's/^[-@ ]*([0-9]+,[0-9]+)[ ,+]+([0-9]+,[0-9]+)[, ].*/\1,\2/'

これで得られるのは

  • 変更前の開始行番号 old_start

  • 変更前のハンクの行数 old_lines

  • 変更後の開始行番号 new_start

  • 変更後のハンクの行数 new_lines

  • old_lines == 0

    • 追加
    • new_startからnew_linesの数だけ追加マーカー +
  • new_lines ==0

    • 削除
    • new_startの位置に削除マーカー -
  • old_lines > 0 && new_lines > 0

    • old_lines == new_lines
      • 対応する行の変更
      • new_startからnew_linesの数だけ変更マーカー ~
    • old_lines > new_lines
      • 変更した結果、行数が減った
      • new_startからnew_linesの数だけ変更マーカー
      • その後、 old_lines - new_linesの数 1行 だけ削除マーカー
    • old_lines < new_lines
      • 変更した結果、行数が増えた
      • new_startからnew_linesの数だけ変更マーカー
      • その後、new_lines - old_linesの数だけ追加マーカー
kawarimidollkawarimidoll

コード

let s:sign_def = 0
function ShowHunk() abort
  if !s:sign_def
    call SignDef()
    set signcolumn=auto
  endif

  let cmd = 'git --no-pager diff -U0 --no-color --no-ext-diff ' .. expand('%')
        \ .. ' | grep ''^@@'' '
        \ .. ' | sed -r ''s/[-+]([0-9]+) /\1,1,/g'' '
        \ .. ' | sed -r ''s/^[-@ ]*([0-9]+,[0-9]+)[ ,+]+([0-9]+,[0-9]+)[, ].*/\1,\2/'' '
  let output = systemlist(cmd)

  let bn = bufnr()
  for line in output
    call s:put_signs(split(line, ','))
  endfor
endfunction

highlight HunkSignAdd ctermfg=red guifg=red
highlight HunkSignDel ctermfg=blue guifg=blue
highlight HunkSignUpd ctermfg=magenta guifg=magenta

function SignDef() abort
  call sign_define([{
        \ 'name' : 'HunkSignAdd',
        \ 'culhl' : 'HunkSignAdd',
        \ 'text' : '+',
        \ }, {
        \ 'name' : 'HunkSignDel',
        \ 'culhl' : 'HunkSignDel',
        \ 'text' : '-',
        \ }, {
        \ 'name' : 'HunkSignUpd',
        \ 'culhl' : 'HunkSignUpd',
        \ 'text' : '~',
        \ }])
  let s:sign_def = 1
endfunction

function s:put_signs(list) abort
  let [old_start, old_lines, new_start, new_lines] = a:list
  echomsg a:list
  let bn = bufnr()
  let list = []
  if old_lines == 0
    let name = 'HunkSignAdd'
    let new_end = new_start + new_lines - 1
    for lnum in range(new_start, new_end)
      call add(list, {'buffer': bn, 'lnum': lnum, 'name': name})
    endfor
  elseif new_lines == 0
    let name = 'HunkSignDel'
    call add(list, {'buffer': bn, 'lnum': new_start, 'name': name})
  else
    let name = 'HunkSignUpd'
    let new_end = new_start + min([old_lines, new_lines]) - 1
    for lnum in range(new_start, new_end)
      call add(list, {'buffer': bn, 'lnum': lnum, 'name': name})
    endfor
    if old_lines > new_lines
      let name = 'HunkSignDel'
      call add(list, {'buffer': bn, 'lnum': new_start, 'name': name})
    elseif old_lines < new_lines
      let name = 'HunkSignAdd'
      let line_dif = new_lines - old_lines
      for lnum in range(new_end + 1, new_end + line_dif)
        call add(list, {'buffer': bn, 'lnum': lnum, 'name': name})
      endfor
    endif
  endif
  call sign_placelist(list)
endfunction

なんでculhlがつかないんだろう
→ gitgutterのコードを見たらtexthlが正しいようだ