diff --git a/bundle/git.vim/autoload/git/blame.vim b/bundle/git.vim/autoload/git/blame.vim index b0fbc7e09..d94d816e8 100644 --- a/bundle/git.vim/autoload/git/blame.vim +++ b/bundle/git.vim/autoload/git/blame.vim @@ -6,89 +6,89 @@ let s:blame_buffer_nr = -1 let s:blame_show_buffer_nr = -1 " @todo rewrite Git blame in lua function! git#blame#run(...) - if len(a:1) == 0 - let cmd = ['git', 'blame', '--line-porcelain', expand('%')] - else - let cmd = ['git', 'blame', '--line-porcelain'] + a:1 - endif - let s:lines = [] - call git#logger#debug('git-blame cmd:' . string(cmd)) - call s:JOB.start(cmd, - \ { - \ 'on_stderr' : function('s:on_stderr'), - \ 'on_stdout' : function('s:on_stdout'), - \ 'on_exit' : function('s:on_exit'), - \ } - \ ) + if len(a:1) == 0 + let cmd = ['git', 'blame', '--line-porcelain', expand('%')] + else + let cmd = ['git', 'blame', '--line-porcelain'] + a:1 + endif + let s:lines = [] + call git#logger#debug('git-blame cmd:' . string(cmd)) + call s:JOB.start(cmd, + \ { + \ 'on_stderr' : function('s:on_stderr'), + \ 'on_stdout' : function('s:on_stdout'), + \ 'on_exit' : function('s:on_exit'), + \ } + \ ) endfunction function! s:on_stdout(id, data, event) abort - for data in a:data - call git#logger#debug('git-blame stdout:' . data) - endfor - let s:lines += a:data + for data in a:data + call git#logger#debug('git-blame stdout:' . data) + endfor + let s:lines += a:data endfunction function! s:on_stderr(id, data, event) abort - for data in a:data - call git#logger#debug('git-blame stderr:' . data) - endfor + for data in a:data + call git#logger#debug('git-blame stderr:' . data) + endfor endfunction function! s:on_exit(id, data, event) abort - call git#logger#debug('git-blame exit data:' . string(a:data)) - let rst = s:parser(s:lines) - if !empty(rst) - if !bufexists(s:blame_buffer_nr) - let s:blame_buffer_nr = s:openBlameWindow() - endif - call setbufvar(s:blame_buffer_nr, 'git_blame_info', rst) - call s:BUFFER.buf_set_lines(s:blame_buffer_nr, 0 , -1, 0, map(deepcopy(rst), 's:STRING.fill(v:val.summary, 40) . repeat(" ", 4) . strftime("%Y %b %d %X", v:val.time)')) - let fname = rst[0].filename - if !bufexists(s:blame_show_buffer_nr) - let s:blame_show_buffer_nr = s:openBlameShowWindow(fname) - endif - call s:BUFFER.buf_set_lines(s:blame_show_buffer_nr, 0 , -1, 0, map(deepcopy(rst), 'v:val.line')) + call git#logger#debug('git-blame exit data:' . string(a:data)) + let rst = s:parser(s:lines) + if !empty(rst) + if !bufexists(s:blame_buffer_nr) + let s:blame_buffer_nr = s:openBlameWindow() endif + call setbufvar(s:blame_buffer_nr, 'git_blame_info', rst) + call s:BUFFER.buf_set_lines(s:blame_buffer_nr, 0 , -1, 0, map(deepcopy(rst), 's:STRING.fill(v:val.summary, 40) . repeat(" ", 4) . strftime("%Y %b %d %X", v:val.time)')) + let fname = rst[0].filename + if !bufexists(s:blame_show_buffer_nr) + let s:blame_show_buffer_nr = s:openBlameShowWindow(fname) + endif + call s:BUFFER.buf_set_lines(s:blame_show_buffer_nr, 0 , -1, 0, map(deepcopy(rst), 'v:val.line')) + endif endfunction function! s:openBlameWindow() abort - tabedit git://blame - normal! "_dd - setl nobuflisted - setl nomodifiable - setl nonumber norelativenumber - setl buftype=nofile - setl scrollbind - setf git-blame - setlocal bufhidden=wipe - nnoremap :call open_previous() - nnoremap :call back() - nnoremap q :call close_blame_win() - return bufnr('%') + tabedit git://blame + normal! "_dd + setl nobuflisted + setl nomodifiable + setl nonumber norelativenumber + setl buftype=nofile + setl scrollbind + setf git-blame + setlocal bufhidden=wipe + nnoremap :call open_previous() + nnoremap :call back() + nnoremap q :call close_blame_win() + return bufnr('%') endfunction function! s:openBlameShowWindow(fname) abort - exe 'rightbelow vsplit git://blame:show/' . a:fname - normal! "_dd - setl nobuflisted - setl nomodifiable - setl scrollbind - setl buftype=nofile - setlocal bufhidden=wipe - nnoremap q :bd! - return bufnr('%') + exe 'rightbelow vsplit git://blame:show/' . a:fname + normal! "_dd + setl nobuflisted + setl nomodifiable + setl scrollbind + setl buftype=nofile + setlocal bufhidden=wipe + nnoremap q :bd! + return bufnr('%') endfunction function! s:close_blame_win() abort - let s:blame_history = [] - call s:closeBlameShowWindow() - q + let s:blame_history = [] + call s:closeBlameShowWindow() + q endfunction function! s:closeBlameShowWindow() abort - if bufexists(s:blame_show_buffer_nr) - exe 'bd ' . s:blame_show_buffer_nr - endif + if bufexists(s:blame_show_buffer_nr) + exe 'bd ' . s:blame_show_buffer_nr + endif endfunction " revision @@ -105,58 +105,58 @@ endfunction " filename autoload/git/blame.vim " let s:JOB = SpaceVim#api#import('job') function! s:parser(lines) abort - let rst = [] - let obj = {} - for line in a:lines - if line =~# '^[a-zA-Z0-9]\{40}' - call extend(obj, {'revision' : line[:39]}) - elseif line =~# '^summary' - call extend(obj, {'summary' : line[8:]}) - elseif line =~# '^filename' - call extend(obj, {'filename' : line[9:]}) - elseif line =~# '^previous' - call extend(obj, {'previous' : line[9:48]}) - elseif line =~# '^committer-time' - call extend(obj, {'time' : str2nr(line[15:])}) - elseif line =~# '^\t' - call extend(obj, {'line' : line[1:]}) - if !empty(obj) && has_key(obj, 'summary') && has_key(obj, 'line') - call add(rst, obj) - endif - let obj = {} - endif - endfor - return rst + let rst = [] + let obj = {} + for line in a:lines + if line =~# '^[a-zA-Z0-9]\{40}' + call extend(obj, {'revision' : line[:39]}) + elseif line =~# '^summary' + call extend(obj, {'summary' : line[8:]}) + elseif line =~# '^filename' + call extend(obj, {'filename' : line[9:]}) + elseif line =~# '^previous' + call extend(obj, {'previous' : line[9:48]}) + elseif line =~# '^committer-time' + call extend(obj, {'time' : str2nr(line[15:])}) + elseif line =~# '^\t' + call extend(obj, {'line' : line[1:]}) + if !empty(obj) && has_key(obj, 'summary') && has_key(obj, 'line') + call add(rst, obj) + endif + let obj = {} + endif + endfor + return rst endfunction let s:blame_history = [] function! s:back() abort - if empty(s:blame_history) - echo 'No navigational history is found' - return - endif - let [rev, fname] = remove(s:blame_history, -1) - exe 'Git blame' rev fname + if empty(s:blame_history) + echo 'No navigational history is found' + return + endif + let [rev, fname] = remove(s:blame_history, -1) + exe 'Git blame' rev fname endfunction function! s:open_previous() abort - let rst = get(b:, 'git_blame_info', []) - if empty(rst) - return - endif - let blame_info = rst[line('.') - 1] - if has_key(blame_info, 'previous') - call add(s:blame_history, [blame_info.revision, blame_info.filename]) - exe 'Git blame' blame_info.previous blame_info.filename - else - echo 'No related parent commit exists' - endif + let rst = get(b:, 'git_blame_info', []) + if empty(rst) + return + endif + let blame_info = rst[line('.') - 1] + if has_key(blame_info, 'previous') + call add(s:blame_history, [blame_info.revision, blame_info.filename]) + exe 'Git blame' blame_info.previous blame_info.filename + else + echo 'No related parent commit exists' + endif endfunction function! git#blame#complete(ArgLead, CmdLine, CursorPos) - return "%\n" . join(getcompletion(a:ArgLead, 'file'), "\n") + return "%\n" . join(getcompletion(a:ArgLead, 'file'), "\n") endfunction diff --git a/bundle/git.vim/lua/git/command/blame.lua b/bundle/git.vim/lua/git/command/blame.lua new file mode 100644 index 000000000..654007e6f --- /dev/null +++ b/bundle/git.vim/lua/git/command/blame.lua @@ -0,0 +1,198 @@ +local M = {} + +local job = require('spacevim.api.job') +local nt = require('spacevim.api.notify') +local log = require('git.log') +local str = require('spacevim.api.data.string') + +local blame_buffer_nr = -1 +local blame_show_buffer_nr = -1 + +local lines = {} +local blame_history = {} + +local function update_buf_context(buf, context) + vim.api.nvim_buf_set_option(buf, 'modifiable', true) + vim.api.nvim_buf_set_lines(buf, 0, -1, false, context) + vim.api.nvim_buf_set_option(buf, 'modifiable', false) +end + +local function on_stdout(id, data) + for _, v in ipairs(data) do + log.debug('git-blame stdout:' .. v) + table.insert(lines, v) + end +end + +local function on_stderr(id, data) + for _, v in ipairs(data) do + log.debug('git-blame stderr:' .. v) + nt.notify(v, 'WarningMsg') + end +end + +local function parser(l) + local rst = {} + local obj = {} + for _, line in ipairs(l) do + if vim.regex('^[a-zA-Z0-9]\\{40}'):match_str(line) then + if obj.summary and obj.line then + table.insert(rst, obj) + end + obj = {} + obj.revision = string.sub(line, 1, 40) + elseif vim.startswith(line, 'summary') then + obj.summary = string.sub(line, 9) + elseif vim.startswith(line, 'filename') then + obj.filename = string.sub(line, 10) + elseif vim.startswith(line, 'previous') then + obj.previous = string.sub(line, 10, 49) + elseif vim.startswith(line, 'committer-time') then + obj.time = tonumber(string.sub(line, 15)) + elseif vim.startswith(line, '\t') then + obj.line = string.sub(line, 2) + end + end + return rst +end + +local function open_previous() + local rst = vim.fn.getbufvar(blame_buffer_nr, 'git_blame_info') + if vim.fn.empty(rst) == 1 then + return + end + + local blame_info = rst[vim.fn.line('.')] + if blame_info.previous then + table.insert(blame_history, {blame_info.revision, blame_info.filename}) + vim.cmd('Git blame ' .. blame_info.previous .. ' ' .. blame_info.filename) + else + nt.notify('No related parent commit') + end + +end + + +local function go_back() + if #blame_history == 0 then + nt.notify('No navigational history') + return + end + local info = table.remove(blame_history) + vim.cmd('Git blame ' .. info[1] .. ' ' .. info[2]) +end + +local function close_blame_show_win() + if vim.api.nvim_buf_is_valid(blame_show_buffer_nr) then + vim.cmd('bd ' .. blame_show_buffer_nr) + end +end + +local function close_blame() + blame_history = {} + close_blame_show_win() + vim.cmd('q') + +end + +local function open_blame_win() + vim.cmd([[ + tabedit git://blame + normal! "_dd + setl nobuflisted + setl nomodifiable + setl nonumber norelativenumber + setl buftype=nofile + setl scrollbind + setf git-blame + setlocal bufhidden=wipe + ]]) + blame_buffer_nr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_keymap(blame_buffer_nr, 'n', '', '', { + callback = open_previous, + }) + vim.api.nvim_buf_set_keymap(blame_buffer_nr, 'n', '', '', { + callback = go_back, + }) + vim.api.nvim_buf_set_keymap(blame_buffer_nr, 'n', 'q', '', { + callback = close_blame, + }) +end + +local function generate_context(ls) + local rst = {} + + for _, v in ipairs(ls) do + log.debug(vim.inspect(v)) + table.insert( + rst, + str.fill(v.summary, 40) .. string.rep(' ', 4) .. vim.fn.strftime('%Y %b %d %X', v.time) + ) + end + return rst +end + +local function open_blame_show_win(fname) + vim.cmd('rightbelow vsplit git://blame:show/' .. fname) + vim.cmd([[ + normal! "_dd + setl nobuflisted + setl nomodifiable + setl scrollbind + setl buftype=nofile + setlocal bufhidden=wipe + nnoremap q :bd! + ]]) + return vim.api.nvim_get_current_buf() +end + +local function on_exit(id, code, single) + log.debug('git-blame exit code:' .. code .. ' single:' .. single) + if code == 0 and single == 0 then + local rst = parser(lines) + -- log.debug(vim.inspect(rst)) + if #rst > 0 then + if not vim.api.nvim_buf_is_valid(blame_buffer_nr) then + open_blame_win() + end + vim.fn.setbufvar(blame_buffer_nr, 'git_blame_info', rst) + update_buf_context(blame_buffer_nr, generate_context(rst)) + local fname = rst[1].filename + if not vim.api.nvim_buf_is_valid(blame_show_buffer_nr) then + blame_show_buffer_nr = open_blame_show_win(fname) + end + vim.api.nvim_buf_set_option(blame_show_buffer_nr, 'modifiable', true) + local ls = {} + for _, v in ipairs(rst) do + table.insert(ls, v.line) + end + vim.api.nvim_buf_set_lines(blame_show_buffer_nr, 0, -1, false, ls) + vim.api.nvim_buf_set_option(blame_show_buffer_nr, 'modifiable', false) + end + else + -- local max_w = nt.notify_max_width + -- nt.notify_max_width = math.floor(vim.o.columns / 2) + nt.notify(table.concat(lines, '\n')) + -- nt.notify_max_width = max_w + end +end + +function M.run(argv) + local cmd = { 'git', 'blame', '--line-porcelain' } + if #argv == 0 then + table.insert(cmd, vim.fn.expand('%')) + else + for _, v in ipairs(argv) do + table.insert(cmd, v) + end + end + log.debug('git-blame cmd:' .. vim.inspect(cmd)) + lines = {} + job.start(cmd, { + on_stdout = on_stdout, + on_stderr = on_stderr, + on_exit = on_exit, + }) +end + +return M