Initial checkin
This commit is contained in:
@@ -0,0 +1,343 @@
|
||||
" ============================================================================
|
||||
" File: git_status.vim
|
||||
" Description: plugin for NERD Tree that provides git status support
|
||||
" Maintainer: Xuyuan Pang <xuyuanp at gmail dot com>
|
||||
" License: This program is free software. It comes without any warranty,
|
||||
" to the extent permitted by applicable law. You can redistribute
|
||||
" it and/or modify it under the terms of the Do What The Fuck You
|
||||
" Want To Public License, Version 2, as published by Sam Hocevar.
|
||||
" See http://sam.zoy.org/wtfpl/COPYING for more details.
|
||||
" ============================================================================
|
||||
scriptencoding utf-8
|
||||
|
||||
if exists('g:loaded_nerdtree_git_status')
|
||||
finish
|
||||
endif
|
||||
let g:loaded_nerdtree_git_status = 1
|
||||
|
||||
let s:is_win = gitstatus#isWin()
|
||||
|
||||
" stolen from nerdtree
|
||||
"Function: s:initVariable() function {{{2
|
||||
"This function is used to initialise a given variable to a given value. The
|
||||
"variable is only initialised if it does not exist prior
|
||||
"
|
||||
"Args:
|
||||
"var: the name of the var to be initialised
|
||||
"value: the value to initialise var to
|
||||
"
|
||||
"Returns:
|
||||
"1 if the var is set, 0 otherwise
|
||||
function! s:initVariable(var, value) abort
|
||||
if !exists(a:var)
|
||||
exec 'let ' . a:var . ' = ' . "'" . substitute(a:value, "'", "''", 'g') . "'"
|
||||
return 1
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
let s:default_vals = {
|
||||
\ 'g:NERDTreeGitStatusEnable': 1,
|
||||
\ 'g:NERDTreeGitStatusUpdateOnWrite': 1,
|
||||
\ 'g:NERDTreeGitStatusUpdateOnCursorHold': 1,
|
||||
\ 'g:NERDTreeGitStatusShowIgnored': 0,
|
||||
\ 'g:NERDTreeGitStatusUseNerdFonts': 0,
|
||||
\ 'g:NERDTreeGitStatusDirDirtyOnly': 1,
|
||||
\ 'g:NERDTreeGitStatusConcealBrackets': 0,
|
||||
\ 'g:NERDTreeGitStatusAlignIfConceal': 1,
|
||||
\ 'g:NERDTreeGitStatusShowClean': 0,
|
||||
\ 'g:NERDTreeGitStatusLogLevel': 2,
|
||||
\ 'g:NERDTreeGitStatusPorcelainVersion': 2,
|
||||
\ 'g:NERDTreeGitStatusMapNextHunk': ']c',
|
||||
\ 'g:NERDTreeGitStatusMapPrevHunk': '[c',
|
||||
\ 'g:NERDTreeGitStatusUntrackedFilesMode': 'normal',
|
||||
\ 'g:NERDTreeGitStatusGitBinPath': 'git',
|
||||
\ }
|
||||
|
||||
for [s:var, s:value] in items(s:default_vals)
|
||||
call s:initVariable(s:var, s:value)
|
||||
endfor
|
||||
|
||||
let s:logger = gitstatus#log#NewLogger(g:NERDTreeGitStatusLogLevel)
|
||||
|
||||
function! s:deprecated(oldv, newv) abort
|
||||
call s:logger.warning(printf("option '%s' is deprecated, please use '%s'", a:oldv, a:newv))
|
||||
endfunction
|
||||
|
||||
function! s:migrateVariable(oldv, newv) abort
|
||||
if exists(a:oldv)
|
||||
call s:deprecated(a:oldv, a:newv)
|
||||
exec 'let ' . a:newv . ' = ' . a:oldv
|
||||
return 1
|
||||
endif
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
let s:need_migrate_vals = {
|
||||
\ 'g:NERDTreeShowGitStatus': 'g:NERDTreeGitStatusEnable',
|
||||
\ 'g:NERDTreeUpdateOnWrite': 'g:NERDTreeGitStatusUpdateOnWrite',
|
||||
\ 'g:NERDTreeMapNextHunk': 'g:NERDTreeGitStatusMapNextHunk',
|
||||
\ 'g:NERDTreeMapPrevHunk': 'g:NERDTreeGitStatusMapPrevHunk',
|
||||
\ 'g:NERDTreeShowIgnoredStatus': 'g:NERDTreeGitStatusShowIgnored',
|
||||
\ 'g:NERDTreeIndicatorMapCustom': 'g:NERDTreeGitStatusIndicatorMapCustom',
|
||||
\ }
|
||||
|
||||
for [s:oldv, s:newv] in items(s:need_migrate_vals)
|
||||
call s:migrateVariable(s:oldv, s:newv)
|
||||
endfor
|
||||
|
||||
if !g:NERDTreeGitStatusEnable
|
||||
finish
|
||||
endif
|
||||
|
||||
if !executable(g:NERDTreeGitStatusGitBinPath)
|
||||
call s:logger.error('git command not found')
|
||||
finish
|
||||
endif
|
||||
|
||||
" FUNCTION: path2str
|
||||
" This function is used to format nerdtree.Path.
|
||||
" For Windows, returns in format 'C:/path/to/file'
|
||||
"
|
||||
" ARGS:
|
||||
" path: nerdtree.Path
|
||||
"
|
||||
" RETURNS:
|
||||
" absolute path
|
||||
function! s:path2str(path) abort
|
||||
return gitstatus#util#FormatPath(a:path)
|
||||
endfunction
|
||||
|
||||
" disable ProhibitUnusedVariable because these three functions used to callback
|
||||
" vint: -ProhibitUnusedVariable
|
||||
function! s:onGitWorkdirSuccessCB(job) abort
|
||||
let g:NTGitWorkdir = split(join(a:job.chunks, ''), "\n")[0]
|
||||
call s:logger.debug(printf("'%s' is in a git repo: '%s'", a:job.opts.cwd, g:NTGitWorkdir))
|
||||
call s:enableLiveUpdate()
|
||||
|
||||
call s:refreshGitStatus('init', g:NTGitWorkdir)
|
||||
endfunction
|
||||
|
||||
function! s:onGitWorkdirFailedCB(job) abort
|
||||
let l:errormsg = join(a:job.err_chunks, '')
|
||||
if l:errormsg =~# 'fatal: Not a git repository'
|
||||
call s:logger.debug(printf("'%s' is not in a git repo", a:job.opts.cwd))
|
||||
endif
|
||||
call s:disableLiveUpdate()
|
||||
unlet! g:NTGitWorkdir
|
||||
endfunction
|
||||
|
||||
function! s:getGitWorkdir(ntRoot) abort
|
||||
call gitstatus#job#Spawn('git-workdir',
|
||||
\ s:buildGitWorkdirCommand(a:ntRoot),
|
||||
\ {
|
||||
\ 'on_success_cb': function('s:onGitWorkdirSuccessCB'),
|
||||
\ 'on_failed_cb': function('s:onGitWorkdirFailedCB'),
|
||||
\ 'cwd': a:ntRoot,
|
||||
\ })
|
||||
endfunction
|
||||
" vint: +ProhibitUnusedVariable
|
||||
|
||||
function! s:buildGitWorkdirCommand(root) abort
|
||||
return gitstatus#util#BuildGitWorkdirCommand(a:root, g:)
|
||||
endfunction
|
||||
|
||||
function! s:buildGitStatusCommand(workdir) abort
|
||||
return gitstatus#util#BuildGitStatusCommand(a:workdir, g:)
|
||||
endfunction
|
||||
|
||||
function! s:refreshGitStatus(name, workdir) abort
|
||||
let l:opts = {
|
||||
\ 'on_failed_cb': function('s:onGitStatusFailedCB'),
|
||||
\ 'on_success_cb': function('s:onGitStatusSuccessCB'),
|
||||
\ 'cwd': a:workdir
|
||||
\ }
|
||||
let l:job = gitstatus#job#Spawn(a:name, s:buildGitStatusCommand(a:workdir), l:opts)
|
||||
return l:job
|
||||
endfunction
|
||||
|
||||
" vint: -ProhibitUnusedVariable
|
||||
function! s:onGitStatusSuccessCB(job) abort
|
||||
if !exists('g:NTGitWorkdir') || g:NTGitWorkdir !=# a:job.opts.cwd
|
||||
call s:logger.debug(printf("git workdir has changed: '%s' -> '%s'", a:job.opts.cwd, get(g:, 'NTGitWorkdir', '')))
|
||||
return
|
||||
endif
|
||||
let l:output = join(a:job.chunks, '')
|
||||
let l:lines = split(l:output, "\n")
|
||||
let l:cache = gitstatus#util#ParseGitStatusLines(a:job.opts.cwd, l:lines, g:)
|
||||
|
||||
call s:listener.SetNext(l:cache)
|
||||
call s:listener.TryUpdateNERDTreeUI()
|
||||
endfunction
|
||||
|
||||
function! s:onGitStatusFailedCB(job) abort
|
||||
let l:errormsg = join(a:job.err_chunks, '')
|
||||
if l:errormsg =~# "error: option `porcelain' takes no value"
|
||||
call s:logger.error(printf("'git status' command failed, please upgrade your git binary('v2.11.0' or higher) or set option 'g:NERDTreeGitStatusPorcelainVersion' to 1 in vimrc"))
|
||||
call s:disableLiveUpdate()
|
||||
unlet! g:NTGitWorkdir
|
||||
elseif l:errormsg =~# '^warning: could not open .* Permission denied'
|
||||
call s:onGitStatusSuccessCB(a:job)
|
||||
else
|
||||
call s:logger.error(printf('job[%s] failed: %s', a:job.name, l:errormsg))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" FUNCTION: s:onCursorHold(fname) {{{2
|
||||
function! s:onCursorHold(fname)
|
||||
" Do not update when a special buffer is selected
|
||||
if !empty(&l:buftype)
|
||||
return
|
||||
endif
|
||||
let l:fname = s:is_win ?
|
||||
\ substitute(a:fname, '\', '/', 'g') :
|
||||
\ a:fname
|
||||
|
||||
if !exists('g:NTGitWorkdir') || !s:hasPrefix(l:fname, g:NTGitWorkdir)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:job = s:refreshGitStatus('cursor-hold', g:NTGitWorkdir)
|
||||
call s:logger.debug('run cursor-hold job: ' . l:job.id)
|
||||
endfunction
|
||||
|
||||
" FUNCTION: s:onFileUpdate(fname) {{{2
|
||||
function! s:onFileUpdate(fname)
|
||||
let l:fname = s:is_win ?
|
||||
\ substitute(a:fname, '\', '/', 'g') :
|
||||
\ a:fname
|
||||
if !exists('g:NTGitWorkdir') || !s:hasPrefix(l:fname, g:NTGitWorkdir)
|
||||
return
|
||||
endif
|
||||
let l:job = s:refreshGitStatus('file-update', g:NTGitWorkdir)
|
||||
call s:logger.debug('run file-update job: ' . l:job.id)
|
||||
endfunction
|
||||
" vint: +ProhibitUnusedVariable
|
||||
|
||||
function! s:hasPrefix(text, prefix) abort
|
||||
return len(a:text) >= len(a:prefix) && a:text[:len(a:prefix)-1] ==# a:prefix
|
||||
endfunction
|
||||
|
||||
function! s:setupNERDTreeListeners(listener) abort
|
||||
call g:NERDTreePathNotifier.AddListener('init', a:listener.OnInit)
|
||||
call g:NERDTreePathNotifier.AddListener('refresh', a:listener.OnRefresh)
|
||||
call g:NERDTreePathNotifier.AddListener('refreshFlags', a:listener.OnRefreshFlags)
|
||||
endfunction
|
||||
|
||||
" FUNCTION: s:findHunk(node, direction)
|
||||
" Args:
|
||||
" node: the current node
|
||||
" direction: next(>0) or prev(<0)
|
||||
"
|
||||
" Returns:
|
||||
" lineNum if the hunk found, -1 otherwise
|
||||
function! s:findHunk(node, direction) abort
|
||||
let l:ui = b:NERDTree.ui
|
||||
let l:rootLn = l:ui.getRootLineNum()
|
||||
let l:totalLn = line('$')
|
||||
let l:currLn = l:ui.getLineNum(a:node)
|
||||
let l:currLn = l:currLn <= l:rootLn ? l:rootLn+1 : l:currLn
|
||||
let l:step = a:direction > 0 ? 1 : -1
|
||||
let l:lines = a:direction > 0 ?
|
||||
\ range(l:currLn+1, l:totalLn, l:step) + range(l:rootLn+1, l:currLn-1, l:step) :
|
||||
\ range(l:currLn-1, l:rootLn+1, l:step) + range(l:totalLn, l:currLn+1, l:step)
|
||||
for l:ln in l:lines
|
||||
let l:path = s:path2str(l:ui.getPath(l:ln))
|
||||
if s:listener.HasPath(l:path)
|
||||
return l:ln
|
||||
endif
|
||||
endfor
|
||||
return -1
|
||||
endfunction
|
||||
|
||||
" vint: -ProhibitUnusedVariable
|
||||
" FUNCTION: s:jumpToNextHunk(node) {{{2
|
||||
function! s:jumpToNextHunk(node)
|
||||
let l:ln = s:findHunk(a:node, 1)
|
||||
if l:ln > 0
|
||||
exec '' . l:ln
|
||||
call s:logger.info('Jump to next hunk')
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" FUNCTION: s:jumpToPrevHunk(node) {{{2
|
||||
function! s:jumpToPrevHunk(node)
|
||||
let l:ln = s:findHunk(a:node, -1)
|
||||
if l:ln > 0
|
||||
exec '' . l:ln
|
||||
call s:logger.info('Jump to prev hunk')
|
||||
endif
|
||||
endfunction
|
||||
" vint: +ProhibitUnusedVariable
|
||||
|
||||
" Function: s:SID() {{{2
|
||||
function s:SID()
|
||||
if !exists('s:sid')
|
||||
let s:sid = matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$')
|
||||
endif
|
||||
return s:sid
|
||||
endfun
|
||||
|
||||
" FUNCTION: s:setupNERDTreeKeyMappings {{{2
|
||||
function! s:setupNERDTreeKeyMappings()
|
||||
let l:s = '<SNR>' . s:SID() . '_'
|
||||
|
||||
call NERDTreeAddKeyMap({
|
||||
\ 'key': g:NERDTreeGitStatusMapNextHunk,
|
||||
\ 'scope': 'Node',
|
||||
\ 'callback': l:s.'jumpToNextHunk',
|
||||
\ 'quickhelpText': 'Jump to next git hunk' })
|
||||
|
||||
call NERDTreeAddKeyMap({
|
||||
\ 'key': g:NERDTreeGitStatusMapPrevHunk,
|
||||
\ 'scope': 'Node',
|
||||
\ 'callback': l:s.'jumpToPrevHunk',
|
||||
\ 'quickhelpText': 'Jump to prev git hunk' })
|
||||
endfunction
|
||||
|
||||
|
||||
" I don't know why, but vint said they are unused.
|
||||
" vint: -ProhibitUnusedVariable
|
||||
function! s:onNERDTreeDirChanged(path) abort
|
||||
call s:getGitWorkdir(a:path)
|
||||
endfunction
|
||||
|
||||
function! s:onNERDTreeInit(path) abort
|
||||
call s:getGitWorkdir(a:path)
|
||||
endfunction
|
||||
" vint: +ProhibitUnusedVariable
|
||||
|
||||
function! s:enableLiveUpdate() abort
|
||||
augroup nerdtreegitplugin_liveupdate
|
||||
autocmd!
|
||||
if g:NERDTreeGitStatusUpdateOnWrite
|
||||
autocmd BufWritePost * silent! call s:onFileUpdate(expand('%:p'))
|
||||
endif
|
||||
|
||||
if g:NERDTreeGitStatusUpdateOnCursorHold
|
||||
autocmd CursorHold * silent! call s:onCursorHold(expand('%:p'))
|
||||
endif
|
||||
|
||||
" TODO: is it necessary to pass the buffer name?
|
||||
autocmd User FugitiveChanged silent! call s:onFileUpdate(expand('%:p'))
|
||||
|
||||
autocmd BufEnter NERD_tree_* call s:onNERDTreeInit(s:path2str(b:NERDTree.root.path))
|
||||
augroup end
|
||||
endfunction
|
||||
|
||||
function! s:disableLiveUpdate() abort
|
||||
augroup nerdtreegitplugin_liveupdate
|
||||
autocmd!
|
||||
augroup end
|
||||
endfunction
|
||||
|
||||
augroup nerdtreegitplugin
|
||||
autocmd!
|
||||
autocmd User NERDTreeInit call s:onNERDTreeInit(s:path2str(b:NERDTree.root.path))
|
||||
autocmd User NERDTreeNewRoot call s:onNERDTreeDirChanged(s:path2str(b:NERDTree.root.path))
|
||||
augroup end
|
||||
|
||||
call s:setupNERDTreeKeyMappings()
|
||||
|
||||
let s:listener = gitstatus#listener#New(g:)
|
||||
call s:setupNERDTreeListeners(s:listener)
|
||||
Reference in New Issue
Block a user