gitgutter.vim 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. " Primary functions {{{
  2. function! gitgutter#all(force) abort
  3. let visible = tabpagebuflist()
  4. for bufnr in range(1, bufnr('$') + 1)
  5. if buflisted(bufnr)
  6. let file = expand('#'.bufnr.':p')
  7. if !empty(file)
  8. if index(visible, bufnr) != -1
  9. call gitgutter#process_buffer(bufnr, a:force)
  10. elseif a:force
  11. call s:reset_tick(bufnr)
  12. endif
  13. endif
  14. endif
  15. endfor
  16. endfunction
  17. function! gitgutter#process_buffer(bufnr, force) abort
  18. " NOTE a:bufnr is not necessarily the current buffer.
  19. if gitgutter#utility#getbufvar(a:bufnr, 'enabled', -1) == -1
  20. call gitgutter#utility#setbufvar(a:bufnr, 'enabled', g:gitgutter_enabled)
  21. endif
  22. if gitgutter#utility#is_active(a:bufnr)
  23. if has('patch-7.4.1559')
  24. let l:Callback = function('gitgutter#process_buffer', [a:bufnr, a:force])
  25. else
  26. let l:Callback = {'function': 'gitgutter#process_buffer', 'arguments': [a:bufnr, a:force]}
  27. endif
  28. let how = s:setup_path(a:bufnr, l:Callback)
  29. if [how] == ['async'] " avoid string-to-number conversion if how is a number
  30. return
  31. endif
  32. if a:force || s:has_fresh_changes(a:bufnr)
  33. let diff = 'NOT SET'
  34. try
  35. let diff = gitgutter#diff#run_diff(a:bufnr, g:gitgutter_diff_relative_to, 0)
  36. catch /gitgutter not tracked/
  37. call gitgutter#debug#log('Not tracked: '.gitgutter#utility#file(a:bufnr))
  38. catch /gitgutter assume unchanged/
  39. call gitgutter#debug#log('Assume unchanged: '.gitgutter#utility#file(a:bufnr))
  40. catch /gitgutter diff failed/
  41. call gitgutter#debug#log('Diff failed: '.gitgutter#utility#file(a:bufnr))
  42. call gitgutter#hunk#reset(a:bufnr)
  43. endtry
  44. if diff != 'async' && diff != 'NOT SET'
  45. call gitgutter#diff#handler(a:bufnr, diff)
  46. endif
  47. endif
  48. endif
  49. endfunction
  50. function! gitgutter#disable() abort
  51. call s:toggle_each_buffer(0)
  52. let g:gitgutter_enabled = 0
  53. endfunction
  54. function! gitgutter#enable() abort
  55. call s:toggle_each_buffer(1)
  56. let g:gitgutter_enabled = 1
  57. endfunction
  58. function s:toggle_each_buffer(enable)
  59. for bufnr in range(1, bufnr('$') + 1)
  60. if buflisted(bufnr)
  61. let file = expand('#'.bufnr.':p')
  62. if !empty(file)
  63. if a:enable
  64. call gitgutter#buffer_enable(bufnr)
  65. else
  66. call gitgutter#buffer_disable(bufnr)
  67. end
  68. endif
  69. endif
  70. endfor
  71. endfunction
  72. function! gitgutter#toggle() abort
  73. if g:gitgutter_enabled
  74. call gitgutter#disable()
  75. else
  76. call gitgutter#enable()
  77. endif
  78. endfunction
  79. function! gitgutter#buffer_disable(...) abort
  80. let bufnr = a:0 ? a:1 : bufnr('')
  81. call gitgutter#utility#setbufvar(bufnr, 'enabled', 0)
  82. call s:clear(bufnr)
  83. endfunction
  84. function! gitgutter#buffer_enable(...) abort
  85. let bufnr = a:0 ? a:1 : bufnr('')
  86. call gitgutter#utility#setbufvar(bufnr, 'enabled', 1)
  87. call gitgutter#process_buffer(bufnr, 1)
  88. endfunction
  89. function! gitgutter#buffer_toggle(...) abort
  90. let bufnr = a:0 ? a:1 : bufnr('')
  91. if gitgutter#utility#getbufvar(bufnr, 'enabled', 1)
  92. call gitgutter#buffer_disable(bufnr)
  93. else
  94. call gitgutter#buffer_enable(bufnr)
  95. endif
  96. endfunction
  97. " }}}
  98. function! gitgutter#setup_maps()
  99. if !g:gitgutter_map_keys
  100. return
  101. endif
  102. " Note hasmapto() and maparg() operate on the current buffer.
  103. let bufnr = bufnr('')
  104. if gitgutter#utility#getbufvar(bufnr, 'mapped', 0)
  105. return
  106. endif
  107. if !hasmapto('<Plug>(GitGutterPrevHunk)') && maparg('[c', 'n') ==# ''
  108. nmap <buffer> [c <Plug>(GitGutterPrevHunk)
  109. endif
  110. if !hasmapto('<Plug>(GitGutterNextHunk)') && maparg(']c', 'n') ==# ''
  111. nmap <buffer> ]c <Plug>(GitGutterNextHunk)
  112. endif
  113. if !hasmapto('<Plug>(GitGutterStageHunk)', 'v') && maparg('<Leader>hs', 'x') ==# ''
  114. xmap <buffer> <Leader>hs <Plug>(GitGutterStageHunk)
  115. endif
  116. if !hasmapto('<Plug>(GitGutterStageHunk)', 'n') && maparg('<Leader>hs', 'n') ==# ''
  117. nmap <buffer> <Leader>hs <Plug>(GitGutterStageHunk)
  118. endif
  119. if !hasmapto('<Plug>(GitGutterUndoHunk)') && maparg('<Leader>hu', 'n') ==# ''
  120. nmap <buffer> <Leader>hu <Plug>(GitGutterUndoHunk)
  121. endif
  122. if !hasmapto('<Plug>(GitGutterPreviewHunk)') && maparg('<Leader>hp', 'n') ==# ''
  123. nmap <buffer> <Leader>hp <Plug>(GitGutterPreviewHunk)
  124. endif
  125. if !hasmapto('<Plug>(GitGutterTextObjectInnerPending)') && maparg('ic', 'o') ==# ''
  126. omap <buffer> ic <Plug>(GitGutterTextObjectInnerPending)
  127. endif
  128. if !hasmapto('<Plug>(GitGutterTextObjectOuterPending)') && maparg('ac', 'o') ==# ''
  129. omap <buffer> ac <Plug>(GitGutterTextObjectOuterPending)
  130. endif
  131. if !hasmapto('<Plug>(GitGutterTextObjectInnerVisual)') && maparg('ic', 'x') ==# ''
  132. xmap <buffer> ic <Plug>(GitGutterTextObjectInnerVisual)
  133. endif
  134. if !hasmapto('<Plug>(GitGutterTextObjectOuterVisual)') && maparg('ac', 'x') ==# ''
  135. xmap <buffer> ac <Plug>(GitGutterTextObjectOuterVisual)
  136. endif
  137. call gitgutter#utility#setbufvar(bufnr, 'mapped', 1)
  138. endfunction
  139. function! s:setup_path(bufnr, continuation)
  140. if gitgutter#utility#has_repo_path(a:bufnr) | return | endif
  141. return gitgutter#utility#set_repo_path(a:bufnr, a:continuation)
  142. endfunction
  143. function! s:has_fresh_changes(bufnr) abort
  144. return getbufvar(a:bufnr, 'changedtick') != gitgutter#utility#getbufvar(a:bufnr, 'tick')
  145. endfunction
  146. function! s:reset_tick(bufnr) abort
  147. call gitgutter#utility#setbufvar(a:bufnr, 'tick', 0)
  148. endfunction
  149. function! s:clear(bufnr)
  150. call gitgutter#sign#clear_signs(a:bufnr)
  151. call gitgutter#hunk#reset(a:bufnr)
  152. call s:reset_tick(a:bufnr)
  153. call gitgutter#utility#setbufvar(a:bufnr, 'path', '')
  154. endfunction
  155. " Note:
  156. " - this runs synchronously
  157. " - it ignores unsaved changes in buffers
  158. " - it does not change to the repo root
  159. function! gitgutter#quickfix(current_file)
  160. let cmd = g:gitgutter_git_executable.' '.g:gitgutter_git_args.' rev-parse --show-cdup'
  161. let path_to_repo = get(systemlist(cmd), 0, '')
  162. if !empty(path_to_repo) && path_to_repo[-1:] != '/'
  163. let path_to_repo .= '/'
  164. endif
  165. let locations = []
  166. let cmd = g:gitgutter_git_executable.' '.g:gitgutter_git_args.' --no-pager'.
  167. \ ' diff --no-ext-diff --no-color -U0'.
  168. \ ' --src-prefix=a/'.path_to_repo.' --dst-prefix=b/'.path_to_repo.' '.
  169. \ g:gitgutter_diff_args. ' '. g:gitgutter_diff_base
  170. if a:current_file
  171. let cmd = cmd.' -- '.expand('%:p')
  172. endif
  173. let diff = systemlist(cmd)
  174. let lnum = 0
  175. for line in diff
  176. if line =~ '^diff --git [^"]'
  177. let paths = line[11:]
  178. let mid = (len(paths) - 1) / 2
  179. let [fnamel, fnamer] = [paths[:mid-1], paths[mid+1:]]
  180. let fname = fnamel ==# fnamer ? fnamel : fnamel[2:]
  181. elseif line =~ '^diff --git "'
  182. let [_, fnamel, _, fnamer] = split(line, '"')
  183. let fname = fnamel ==# fnamer ? fnamel : fnamel[2:]
  184. elseif line =~ '^diff --cc [^"]'
  185. let fname = line[10:]
  186. elseif line =~ '^diff --cc "'
  187. let [_, fname] = split(line, '"')
  188. elseif line =~ '^@@'
  189. let lnum = matchlist(line, '+\(\d\+\)')[1]
  190. elseif lnum > 0
  191. call add(locations, {'filename': fname, 'lnum': lnum, 'text': line})
  192. let lnum = 0
  193. endif
  194. endfor
  195. if !g:gitgutter_use_location_list
  196. call setqflist(locations)
  197. else
  198. call setloclist(0, locations)
  199. endif
  200. endfunction
  201. function! gitgutter#difforig()
  202. let bufnr = bufnr('')
  203. let path = gitgutter#utility#repo_path(bufnr, 1)
  204. let filetype = &filetype
  205. vertical new
  206. set buftype=nofile
  207. let &filetype = filetype
  208. if g:gitgutter_diff_relative_to ==# 'index'
  209. let index_name = gitgutter#utility#get_diff_base(bufnr).':'.path
  210. let cmd = gitgutter#utility#cd_cmd(bufnr,
  211. \ g:gitgutter_git_executable.' '.g:gitgutter_git_args.' --no-pager show '.index_name
  212. \ )
  213. " NOTE: this uses &shell to execute cmd. Perhaps we should use instead
  214. " gitgutter#utility's use_known_shell() / restore_shell() functions.
  215. silent! execute "read ++edit !" cmd
  216. else
  217. silent! execute "read ++edit" path
  218. endif
  219. 0d_
  220. diffthis
  221. wincmd p
  222. diffthis
  223. endfunction