<commit_before>let s:V = gita#vital()
let s:String = s:V.import('Data.String')
let s:DateTime = s:V.import('DateTime')
let s:Path = s:V.import('System.Filepath')
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:ProgressBar = s:V.import('ProgressBar')
let s:Git = s:V.import('Git')
let s:GitInfo = s:V.import('Git.Info')
let s:GitTerm = s:V.import('Git.Term')
let s:GitParser = s:V.import('Git.Parser')

function! s:execute_command(options) abort
  let git = gita#core#get_or_fail()
  let commit = a:options.commit
  if commit =~# '^.\{-}\.\.\..\{-}$'
    " support A...B style
    let [lhs, rhs] = s:GitTerm.split_range(commit)
    let lhs = empty(lhs) ? 'HEAD' : lhs
    let rhs = empty(rhs) ? 'HEAD' : rhs
    let commit = s:GitInfo.find_common_ancestor(git, lhs, rhs)
  elseif commit =~# '^.\{-}\.\..\{-}$'
    " support A..B style
    let commit  = s:GitTerm.split_range(commit)[0]
  endif
  let guard = s:Guard.store('&l:statusline')
  try
    setlocal statusline=Retriving\ blame\ content\ [1/3]\ ...
    redrawstatus
    let args = [
          \ 'blame',
          \ g:gita#content#blame#use_porcelain_instead
          \   ? '--porcelain'
          \   : '--incremental',
          \ commit,
          \ '--',
          \ a:options.filename,
          \]
    let content = gita#process#execute(git, args, {
          \ 'quiet': 1,
          \ 'encode_output': 0,
          \})
    setlocal statusline=Parsing\ blame\ content\ [2/3]\ ...
    redrawstatus
    let blameobj = s:get_blameobj(content, commit, a:options.filename)
    setlocal statusline=Formatting\ blame\ content\ [3/3]\ ...
    redrawstatus
    let blamemeta = s:get_blamemeta(
          \ blameobj,
          \ g:gita#content#blame#navigator_width
          \)
    return blamemeta
  finally
    call guard.restore()
  endtry
endfunction

function! s:get_blameobj(content, commit, filename) abort
  let progressbar = s:ProgressBar.new(
        \ len(a:content), {
        \   'barwidth': 80,
        \   'method': 'echo',
        \})
  try
    let blameobj = s:GitParser.parse_blame(a:content, {
          \ 'progressbar': progressbar,
          \ 'python': g:gita#content#blame#use_python,
          \})
    if !g:gita#content#blame#use_porcelain_instead
      if empty(a:commit)
        let blameobj.file_content = readfile(a:filename)
      else
        let git = gita#core#get_or_fail()
        let treeish = printf('%s:%s',
              \ a:commit,
              \ s:Path.unixpath(s:Git.get_relative_path(git, a:filename)),
              \)
        let blameobj.file_content = gita#process#execute(
              \ git,
              \ ['show', treeish],
              \ { 'quiet': 1, 'encode_output': 0 },
              \)
      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,
        \   'method': 'echo',
        \})
  try
    return s:format_blameobj(a:blameobj, a:width, progressbar)
  finally
    call progressbar.exit()
  endtry
endfunction

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 height = winheight(0)
  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])<commit_msg>Fix open/show/diff actions in gita-blame-navi window<commit_after>let s:V = gita#vital()
let s:String = s:V.import('Data.String')
let s:DateTime = s:V.import('DateTime')
let s:Path = s:V.import('System.Filepath')
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:ProgressBar = s:V.import('ProgressBar')
let s:Git = s:V.import('Git')
let s:GitInfo = s:V.import('Git.Info')
let s:GitTerm = s:V.import('Git.Term')
let s:GitParser = s:V.import('Git.Parser')

function! s:execute_command(options) abort
  let git = gita#core#get_or_fail()
  let commit = a:options.commit
  if commit =~# '^.\{-}\.\.\..\{-}$'
    " support A...B style
    let [lhs, rhs] = s:GitTerm.split_range(commit)
    let lhs = empty(lhs) ? 'HEAD' : lhs
    let rhs = empty(rhs) ? 'HEAD' : rhs
    let commit = s:GitInfo.find_common_ancestor(git, lhs, rhs)
  elseif commit =~# '^.\{-}\.\..\{-}$'
    " support A..B style
    let commit  = s:GitTerm.split_range(commit)[0]
  endif
  let guard = s:Guard.store('&l:statusline')
  try
    setlocal statusline=Retriving\ blame\ content\ [1/3]\ ...
    redrawstatus
    let args = [
          \ 'blame',
          \ g:gita#content#blame#use_porcelain_instead
          \   ? '--porcelain'
          \   : '--incremental',
          \ commit,
          \ '--',
          \ a:options.filename,
          \]
    let content = gita#process#execute(git, args, {
          \ 'quiet': 1,
          \ 'encode_output': 0,
          \})
    setlocal statusline=Parsing\ blame\ content\ [2/3]\ ...
    redrawstatus
    let blameobj = s:get_blameobj(content, commit, a:options.filename)
    setlocal statusline=Formatting\ blame\ content\ [3/3]\ ...
    redrawstatus
    let blamemeta = s:get_blamemeta(
          \ blameobj,
          \ g:gita#content#blame#navigator_width
          \)
    return blamemeta
  finally
    call guard.restore()
  endtry
endfunction

function! s:get_blameobj(content, commit, filename) abort
  let progressbar = s:ProgressBar.new(
        \ len(a:content), {
        \   'barwidth': 80,
        \   'method': 'echo',
        \})
  try
    let blameobj = s:GitParser.parse_blame(a:content, {
          \ 'progressbar': progressbar,
          \ 'python': g:gita#content#blame#use_python,
          \})
    if !g:gita#content#blame#use_porcelain_instead
      if empty(a:commit)
        let blameobj.file_content = readfile(a:filename)
      else
        let git = gita#core#get_or_fail()
        let treeish = printf('%s:%s',
              \ a:commit,
              \ s:Path.unixpath(s:Git.get_relative_path(git, a:filename)),
              \)
        let blameobj.file_content = gita#process#execute(
              \ git,
              \ ['show', treeish],
              \ { 'quiet': 1, 'encode_output': 0 },
              \)
      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,
        \   'method': 'echo',
        \})
  try
    return s:format_blameobj(a:blameobj, a:width, progressbar)
  finally
    call progressbar.exit()
  endtry
endfunction

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 height = winheight(0)
  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])
    " NOTE:
    " edit/show/diff actions require 'path' attribute
    let chunk.path = chunk.filename
{'fraction_non_alphanumeric': 0.1423239611843742, 'fraction_numerical': 0.004312847308617401, 'mean_word_length': 2.8746786632390746, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, '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': '5913846', 'n_tokens_mistral': 4324, 'n_tokens_neox': 4209, 'n_words': 879}