<commit_before>let s:V = gita#vital()
let s:Dict = s:V.import('Data.Dict')
let s:String = s:V.import('Data.String')
let s:Path = s:V.import('System.Filepath')
let s:DateTime = s:V.import('DateTime')
let s:MemoryCache = s:V.import('System.Cache.Memory')
let s:Guard = s:V.import('Vim.Guard')
let s:Python = s:V.import('Vim.Python')
let s:Git = s:V.import('Git')
let s:GitParser = s:V.import('Git.Parser')
let s:ProgressBar = s:V.import('ProgressBar')
let s:ArgumentParser = s:V.import('ArgumentParser')

function! s:format_timestamp(timestamp, timezone, now) abort
  let datetime  = s:DateTime.from_unix_time(a:timestamp, a:timezone)
  let timedelta = datetime.delta(a:now)
  if timedelta.duration().months() < 3
    return timedelta.about()
  elseif datetime.year() == a:now.year()
    return 'on ' . datetime.strftime('%d %b')
  else
    return 'on ' . datetime.strftime('%d %b, %Y')
  endif
endfunction

function! s:get_max_linenum(chunks) abort
  let chunk = a:chunks[len(a:chunks) - 1]
  return chunk.linenum.final + get(chunk, 'nlines', 1)
endfunction

function! s:build_chunkinfo(chunk, width, now, whitespaces) abort
  let summary = s:String.wrap(a:chunk.summary, a:width)
  let revision = (get(a:chunk, 'boundary') ? '^' : ' ') . a:chunk.revision[:6]
  let author = a:chunk.author
  let timestr = s:format_timestamp(
        \ a:chunk.author_time,
        \ a:chunk.author_tz,
        \ a:now,
        \)
  if author =~# 'Not Committed Yet'
    let author_info = 'Not committed yet ' . timestr
  else
    let author_info = author . ' authored ' . timestr
  endif
  let epilogue = author_info . a:whitespaces[9+len(author_info):] . revision
  return { 'nlines': len(summary), 'summary': summary, 'epilogue': epilogue }
endfunction

function! s:format_chunk(chunk, width, height, cache, now, whitespaces) abort
  let chunkinfo = a:cache.get(a:chunk.revision, {})
  if empty(chunkinfo)
    let chunkinfo = s:build_chunkinfo(a:chunk, a:width, a:now, a:whitespaces)
    call a:cache.set(a:chunk.revision, chunkinfo)
  endif
  if a:height == 1
    if !has_key(chunkinfo, 'linesummary')
      " produce a linesummary only when it becomes necessary
      let linesummary = s:String.truncate(a:chunk.summary, a:width - 1)
      let chunkinfo.linesummary = substitute(linesummary, '\s\+$', '', '')
      call a:cache.set(a:chunk.revision, chunkinfo)
    endif
    return [chunkinfo.linesummary, chunkinfo.epilogue]
  else
    let summary = chunkinfo.nlines > a:height
          \ ? chunkinfo.summary[:(a:height-1)]
          \ : chunkinfo.summary
    return summary + [chunkinfo.epilogue]
  endif
endfunction

function! s:format_blameobj(blameobj, width, progressbar) abort
  let chunks    = a:blameobj.chunks
  let revisions = a:blameobj.revisions
  let now   = s:DateTime.now()
  let cache = s:MemoryCache.new()
  let linenum_width  = len(s:get_max_linenum(chunks))
  let linenum_spacer = repeat(' ', linenum_width)
  let linenum_pseudo = 1
  let width = a:width - linenum_width - 2
  let whitespaces = repeat(' ', width)
  let navi_content = []
  let view_content = []
  let lineinfos = []
  let linerefs = []
  let separators = []
  for chunk in chunks
    call extend(chunk, revisions[chunk.revision])
    let n_contents = get(chunk, 'nlines', 1)
    let height = max([2, n_contents])
    let formatted_chunk = s:format_chunk(
          \ chunk, width, height-1, cache, now, whitespaces
          \)
    for cursor in range(height)
      if cursor < n_contents
        call add(linerefs, linenum_pseudo)
      endif
      let linenum = cursor >= n_contents ? '' : chunk.linenum.final + cursor
      call add(navi_content,
              \ linenum_spacer[len(linenum):] . linenum . ' ' . get(formatted_chunk, cursor, '')
              \)
      if empty(linenum)
        call add(view_content, '')
      elseif len(chunk.contents) == n_contents
        call add(view_content, chunk.contents[cursor])
      else
        call add(view_content, a:blameobj.file_content[linenum-1])
      endif
      call add(lineinfos, {
            \ 'chunkref': chunk.index,
            \ 'linenum': {
            \   'original': chunk.linenum.original + cursor,
            \   'final': chunk.linenum.final + cursor,
            \ },
            \})
      let linenum_pseudo += 1
    endfor
    " add pseudo separator line
    call add(navi_content, '')
    call add(view_content, '')
    call add(lineinfos, {
          \ 'chunkref': chunk.index,
          \ 'linenum': {
          \   'original': chunk.linenum.original + (height - 1),
          \   'final': chunk.linenum.final + (height - 1),
          \ },
          \})
    call add(separators, linenum_pseudo)
    let linenum_pseudo += 1
    call a:progressbar.update()
  endfor
  let offset = -2
  let blamemeta = {
        \ 'chunks':       chunks,
        \ 'lineinfos':    lineinfos[:offset],
        \ 'linerefs':     linerefs,
        \ 'separators':   len(separators) < 2 ? [] : separators[:offset],
        \ 'navi_content': navi_content[:offset],
        \ 'view_content': view_content[:offset],
        \ 'linenum_width': linenum_width,
        \}
  return blamemeta
endfunction


function! s:get_content(git, commit, filename) abort
  let args = [
        \ 'blame',
        \ g:gita#command#blame#use_porcelain_instead ? '--porcelain' : '--incremental',
        \ a:commit,
        \ '--',
        \ s:Path.unixpath(s:Git.get_relative_path(a:git, a:filename)),
        \]
  return gita#execute(a:git, args, {
        \ 'quiet': 1,
        \})
endfunction

function! s:get_blameobj(content, commit, filename) abort
  let progressbar = s:ProgressBar.new(
        \ len(a:content), {
        \   'barwidth': 80,
        \   'statusline': 0,
        \})
  try
    let blameobj = s:GitParser.parse_blame(a:content, {
          \ 'progressbar': progressbar,
          \ 'python': g:gita#command#blame#use_python,
          \})
    if !g:gita#command#blame#use_porcelain_instead
      if empty(a:commit)
        let blameobj.file_content = readfile(a:filename)
      else
        let blameobj.file_content = gita#command#show#call({
              \ 'commit': a:commit,
              \ 'filename': a:filename,
              \}).content
      endif
    endif
    return blameobj
  finally
    call progressbar.exit()
  endtry
endfunction

function! s:get_blamemeta(blameobj, width) abort
  let progressbar = s:ProgressBar.new(
        \ len(a:blameobj.chunks), {
        \   'barwidth': 80,
        \   'statusline': 0,
        \})
  try
    return s:format_blameobj(a:blameobj, a:width, progressbar)
  finally
    call progressbar.exit()
  endtry
endfunction

function! s:retrieve(commit, filename) abort
  let git = gita#core#get_or_fail()
  let guard = s:Guard.store('&l:statusline')
  try<commit_msg>Fix Gita blame annoying output issue<commit_after>let s:V = gita#vital()
let s:Dict = s:V.import('Data.Dict')
let s:String = s:V.import('Data.String')
let s:Path = s:V.import('System.Filepath')
let s:DateTime = s:V.import('DateTime')
let s:MemoryCache = s:V.import('System.Cache.Memory')
let s:Guard = s:V.import('Vim.Guard')
let s:Python = s:V.import('Vim.Python')
let s:Git = s:V.import('Git')
let s:GitParser = s:V.import('Git.Parser')
let s:ProgressBar = s:V.import('ProgressBar')
let s:ArgumentParser = s:V.import('ArgumentParser')

function! s:format_timestamp(timestamp, timezone, now) abort
  let datetime  = s:DateTime.from_unix_time(a:timestamp, a:timezone)
  let timedelta = datetime.delta(a:now)
  if timedelta.duration().months() < 3
    return timedelta.about()
  elseif datetime.year() == a:now.year()
    return 'on ' . datetime.strftime('%d %b')
  else
    return 'on ' . datetime.strftime('%d %b, %Y')
  endif
endfunction

function! s:get_max_linenum(chunks) abort
  let chunk = a:chunks[len(a:chunks) - 1]
  return chunk.linenum.final + get(chunk, 'nlines', 1)
endfunction

function! s:build_chunkinfo(chunk, width, now, whitespaces) abort
  let summary = s:String.wrap(a:chunk.summary, a:width)
  let revision = (get(a:chunk, 'boundary') ? '^' : ' ') . a:chunk.revision[:6]
  let author = a:chunk.author
  let timestr = s:format_timestamp(
        \ a:chunk.author_time,
        \ a:chunk.author_tz,
        \ a:now,
        \)
  if author =~# 'Not Committed Yet'
    let author_info = 'Not committed yet ' . timestr
  else
    let author_info = author . ' authored ' . timestr
  endif
  let epilogue = author_info . a:whitespaces[9+len(author_info):] . revision
  return { 'nlines': len(summary), 'summary': summary, 'epilogue': epilogue }
endfunction

function! s:format_chunk(chunk, width, height, cache, now, whitespaces) abort
  let chunkinfo = a:cache.get(a:chunk.revision, {})
  if empty(chunkinfo)
    let chunkinfo = s:build_chunkinfo(a:chunk, a:width, a:now, a:whitespaces)
    call a:cache.set(a:chunk.revision, chunkinfo)
  endif
  if a:height == 1
    if !has_key(chunkinfo, 'linesummary')
      " produce a linesummary only when it becomes necessary
      let linesummary = s:String.truncate(a:chunk.summary, a:width - 1)
      let chunkinfo.linesummary = substitute(linesummary, '\s\+$', '', '')
      call a:cache.set(a:chunk.revision, chunkinfo)
    endif
    return [chunkinfo.linesummary, chunkinfo.epilogue]
  else
    let summary = chunkinfo.nlines > a:height
          \ ? chunkinfo.summary[:(a:height-1)]
          \ : chunkinfo.summary
    return summary + [chunkinfo.epilogue]
  endif
endfunction

function! s:format_blameobj(blameobj, width, progressbar) abort
  let chunks    = a:blameobj.chunks
  let revisions = a:blameobj.revisions
  let now   = s:DateTime.now()
  let cache = s:MemoryCache.new()
  let linenum_width  = len(s:get_max_linenum(chunks))
  let linenum_spacer = repeat(' ', linenum_width)
  let linenum_pseudo = 1
  let width = a:width - linenum_width - 2
  let whitespaces = repeat(' ', width)
  let navi_content = []
  let view_content = []
  let lineinfos = []
  let linerefs = []
  let separators = []
  for chunk in chunks
    call extend(chunk, revisions[chunk.revision])
    let n_contents = get(chunk, 'nlines', 1)
    let height = max([2, n_contents])
    let formatted_chunk = s:format_chunk(
          \ chunk, width, height-1, cache, now, whitespaces
          \)
    for cursor in range(height)
      if cursor < n_contents
        call add(linerefs, linenum_pseudo)
      endif
      let linenum = cursor >= n_contents ? '' : chunk.linenum.final + cursor
      call add(navi_content,
              \ linenum_spacer[len(linenum):] . linenum . ' ' . get(formatted_chunk, cursor, '')
              \)
      if empty(linenum)
        call add(view_content, '')
      elseif len(chunk.contents) == n_contents
        call add(view_content, chunk.contents[cursor])
      else
        call add(view_content, a:blameobj.file_content[linenum-1])
      endif
      call add(lineinfos, {
            \ 'chunkref': chunk.index,
            \ 'linenum': {
            \   'original': chunk.linenum.original + cursor,
            \   'final': chunk.linenum.final + cursor,
            \ },
            \})
      let linenum_pseudo += 1
    endfor
    " add pseudo separator line
    call add(navi_content, '')
    call add(view_content, '')
    call add(lineinfos, {
          \ 'chunkref': chunk.index,
          \ 'linenum': {
          \   'original': chunk.linenum.original + (height - 1),
          \   'final': chunk.linenum.final + (height - 1),
          \ },
          \})
    call add(separators, linenum_pseudo)
    let linenum_pseudo += 1
    call a:progressbar.update()
  endfor
  let offset = -2
  let blamemeta = {
        \ 'chunks':       chunks,
        \ 'lineinfos':    lineinfos[:offset],
        \ 'linerefs':     linerefs,
        \ 'separators':   len(separators) < 2 ? [] : separators[:offset],
        \ 'navi_content': navi_content[:offset],
        \ 'view_content': view_content[:offset],
        \ 'linenum_width': linenum_width,
        \}
  return blamemeta
endfunction


function! s:get_content(git, commit, filename) abort
  let args = [
        \ 'blame',
        \ g:gita#command#blame#use_porcelain_instead ? '--porcelain' : '--incremental',
        \ a:commit,
        \ '--',
        \ s:Path.unixpath(s:Git.get_relative_path(a:git, a:filename)),
        \]
  return gita#execute(a:git, args, {
        \ 'quiet': 1,
        \})
endfunction

function! s:get_blameobj(content, commit, filename) abort
  let progressbar = s:ProgressBar.new(
        \ len(a:content), {
        \   'barwidth': 80,
        \   'statusline': 0,
        \})
  try
    let blameobj = s:GitParser.parse_blame(a:content, {
          \ 'progressbar': progressbar,
          \ 'python': g:gita#command#blame#use_python,
          \})
    if !g:gita#command#blame#use_porcelain_instead
      if empty(a:commit)
        let blameobj.file_content = readfile(a:filename)
      else
        let blameobj.file_content = gita#command#show#call({
              \ 'quiet': 1,
              \ 'commit': a:commit,
              \ 'filename': a:filename,
              \}).content
      endif
    endif
    return blameobj
  finally
    call progressbar.exit()
  endtry
endfunction

function! s:get_blamemeta(blameobj, width) abort
  let progressbar = s:ProgressBar.new(
        \ len(a:blameobj.chunks), {
        \   'barwidth': 80,
        \   'statusline': 0,
        \})
  try
    return s:format_blameobj(a:blameobj, a:width, progressbar)
  finally
    call progressbar.exit()
  endtry
endfunction

function! s:retrieve(commit, filename) abort
  let git = gita#core#get_or_fail()
  let guard = s:Guard.store('&l:statusline')
  try
{'fraction_non_alphanumeric': 0.1362126245847176, 'fraction_numerical': 0.004060538944259874, 'mean_word_length': 2.805056179775281, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1532075', 'n_tokens_mistral': 4729, 'n_tokens_neox': 4590, 'n_words': 962}