1
0

mundo.vim 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. " ============================================================================
  2. " File: mundo.vim
  3. " Description: vim global plugin to visualize your undo tree
  4. " Maintainer: Hyeon Kim <simnalamburt@gmail.com>
  5. " License: GPLv2+ -- look it up.
  6. " Notes: Much of this code was thiefed from Mercurial, and the rest was
  7. " heavily inspired by scratch.vim and histwin.vim.
  8. "
  9. " ============================================================================
  10. "{{{ Init
  11. let s:save_cpo = &cpo
  12. set cpo&vim
  13. if v:version < '703'"{{{
  14. function! s:MundoDidNotLoad()
  15. echohl WarningMsg|echomsg "Mundo unavailable: requires Vim 7.3+"|echohl None
  16. endfunction
  17. command! -nargs=0 MundoToggle call s:MundoDidNotLoad()
  18. finish
  19. endif"}}}
  20. call mundo#util#init()
  21. let s:has_supported_python = 0
  22. if g:mundo_prefer_python3 && has('python3')"{{{
  23. let s:has_supported_python = 2
  24. elseif has('python')"
  25. let s:has_supported_python = 1
  26. endif
  27. if !s:has_supported_python
  28. function! s:MundoDidNotLoad()
  29. echohl WarningMsg|echomsg "Mundo requires Vim to be compiled with Python 2.4+"|echohl None
  30. endfunction
  31. command! -nargs=0 MundoToggle call s:MundoDidNotLoad()
  32. finish
  33. endif"}}}
  34. let s:plugin_path = escape(expand('<sfile>:p:h'), '\')
  35. "}}}
  36. "{{{ Mundo utility functions
  37. function! s:MundoGoToWindowForBufferName(name)"{{{
  38. if bufwinnr(bufnr(a:name)) != -1
  39. exe bufwinnr(bufnr(a:name)) . "wincmd w"
  40. return 1
  41. else
  42. return 0
  43. endif
  44. endfunction"}}}
  45. function! s:MundoIsVisible()"{{{
  46. if bufwinnr(bufnr("__Mundo__")) != -1 || bufwinnr(bufnr("__Mundo_Preview__")) != -1
  47. return 1
  48. else
  49. return 0
  50. endif
  51. endfunction"}}}
  52. function! s:MundoInlineHelpLength()"{{{
  53. if g:mundo_help
  54. return 10
  55. else
  56. return 0
  57. endif
  58. endfunction"}}}
  59. "}}}
  60. "{{{ Mundo buffer settings
  61. function! s:MundoMapGraph()"{{{
  62. exec 'nnoremap <script> <silent> <buffer> ' . g:mundo_map_move_older . " :<C-u>call <sid>MundoPython('MundoMove(1,'. v:count .')')<CR>"
  63. exec 'nnoremap <script> <silent> <buffer> ' . g:mundo_map_move_newer . " :<C-u>call <sid>MundoPython('MundoMove(-1,'. v:count .')')<CR>"
  64. nnoremap <script> <silent> <buffer> <CR> :call <sid>MundoPython('MundoRevert()')<CR>
  65. nnoremap <script> <silent> <buffer> o :call <sid>MundoPython('MundoRevert()')<CR>
  66. nnoremap <script> <silent> <buffer> <down> :<C-u>call <sid>MundoPython('MundoMove(1,'. v:count .')')<CR>
  67. nnoremap <script> <silent> <buffer> <up> :<C-u>call <sid>MundoPython('MundoMove(-1,'. v:count .')')<CR>
  68. nnoremap <script> <silent> <buffer> J :<C-u>call <sid>MundoPython('MundoMove(1,'. v:count .',True,True)')<CR>
  69. nnoremap <script> <silent> <buffer> K :<C-u>call <sid>MundoPython('MundoMove(-1,'. v:count .',True,True)')<CR>
  70. nnoremap <script> <silent> <buffer> gg gg:<C-u>call <sid>MundoPython('MundoMove(1,'. v:count .')')<CR>
  71. nnoremap <script> <silent> <buffer> P :call <sid>MundoPython('MundoPlayTo()')<CR>
  72. nnoremap <script> <silent> <buffer> d :call <sid>MundoPython('MundoRenderPatchdiff()')<CR>
  73. nnoremap <script> <silent> <buffer> i :call <sid>MundoPython('MundoRenderToggleInlineDiff()')<CR>
  74. nnoremap <script> <silent> <buffer> / :call <sid>MundoPython('MundoSearch()')<CR>
  75. nnoremap <script> <silent> <buffer> n :call <sid>MundoPython('MundoNextMatch()')<CR>
  76. nnoremap <script> <silent> <buffer> N :call <sid>MundoPython('MundoPrevMatch()')<CR>
  77. nnoremap <script> <silent> <buffer> p :call <sid>MundoPython('MundoRenderChangePreview()')<CR>
  78. nnoremap <script> <silent> <buffer> r :call <sid>MundoPython('MundoRenderPreview()')<CR>
  79. nnoremap <script> <silent> <buffer> ? :call <sid>MundoPython('MundoToggleHelp()')<CR>
  80. nnoremap <script> <silent> <buffer> q :call <sid>MundoClose()<CR>
  81. cabbrev <script> <silent> <buffer> q call <sid>MundoClose()
  82. cabbrev <script> <silent> <buffer> quit call <sid>MundoClose()
  83. nnoremap <script> <silent> <buffer> <2-LeftMouse> :call <sid>MundoMouseDoubleClick()<CR>
  84. endfunction"}}}
  85. function! s:MundoMapPreview()"{{{
  86. nnoremap <script> <silent> <buffer> q :call <sid>MundoClose()<CR>
  87. cabbrev <script> <silent> <buffer> q call <sid>MundoClose()
  88. cabbrev <script> <silent> <buffer> quit call <sid>MundoClose()
  89. endfunction"}}}
  90. function! s:MundoSettingsGraph()"{{{
  91. setlocal buftype=nofile
  92. setlocal bufhidden=hide
  93. setlocal noswapfile
  94. setlocal nobuflisted
  95. setlocal nomodifiable
  96. setlocal filetype=mundo
  97. setlocal nolist
  98. setlocal nonumber
  99. setlocal norelativenumber
  100. setlocal nowrap
  101. call s:MundoSyntaxGraph()
  102. call s:MundoMapGraph()
  103. endfunction"}}}
  104. function! s:MundoSettingsPreview()"{{{
  105. setlocal buftype=nofile
  106. setlocal bufhidden=hide
  107. setlocal noswapfile
  108. setlocal nobuflisted
  109. setlocal nomodifiable
  110. setlocal filetype=diff
  111. setlocal nonumber
  112. setlocal norelativenumber
  113. setlocal nowrap
  114. setlocal foldlevel=20
  115. setlocal foldmethod=diff
  116. call s:MundoMapPreview()
  117. endfunction"}}}
  118. function! s:MundoSyntaxGraph()"{{{
  119. let b:current_syntax = 'mundo'
  120. syn match MundoCurrentLocation '@'
  121. syn match MundoHelp '\v^".*$'
  122. syn match MundoNumberField '\v\[[0-9]+\]'
  123. syn match MundoNumber '\v[0-9]+' contained containedin=MundoNumberField
  124. syn region MundoDiff start=/\v<ago> / end=/$/
  125. syn match MundoDiffAdd '\v\+[^+-]+\+' contained containedin=MundoDiff
  126. syn match MundoDiffDelete '\v-[^+-]+-' contained containedin=MundoDiff
  127. hi def link MundoCurrentLocation Keyword
  128. hi def link MundoHelp Comment
  129. hi def link MundoNumberField Comment
  130. hi def link MundoNumber Identifier
  131. hi def link MundoDiffAdd DiffAdd
  132. hi def link MundoDiffDelete DiffDelete
  133. endfunction"}}}
  134. "}}}
  135. "{{{ Mundo buffer/window management
  136. function! s:MundoResizeBuffers(backto)"{{{
  137. call s:MundoGoToWindowForBufferName('__Mundo__')
  138. exe "vertical resize " . g:mundo_width
  139. call s:MundoGoToWindowForBufferName('__Mundo_Preview__')
  140. exe "resize " . g:mundo_preview_height
  141. exe a:backto . "wincmd w"
  142. endfunction"}}}
  143. function! s:MundoOpenGraph()"{{{
  144. let existing_mundo_buffer = bufnr("__Mundo__")
  145. if existing_mundo_buffer == -1
  146. call s:MundoGoToWindowForBufferName('__Mundo_Preview__')
  147. exe "new __Mundo__"
  148. set fdm=manual
  149. if g:mundo_preview_bottom
  150. if g:mundo_right
  151. wincmd L
  152. else
  153. wincmd H
  154. endif
  155. endif
  156. call s:MundoResizeBuffers(winnr())
  157. else
  158. let existing_mundo_window = bufwinnr(existing_mundo_buffer)
  159. if existing_mundo_window != -1
  160. if winnr() != existing_mundo_window
  161. exe existing_mundo_window . "wincmd w"
  162. endif
  163. else
  164. call s:MundoGoToWindowForBufferName('__Mundo_Preview__')
  165. if g:mundo_preview_bottom
  166. if g:mundo_right
  167. exe "botright vsplit +buffer" . existing_mundo_buffer
  168. else
  169. exe "topleft vsplit +buffer" . existing_mundo_buffer
  170. endif
  171. else
  172. exe "split +buffer" . existing_mundo_buffer
  173. endif
  174. call s:MundoResizeBuffers(winnr())
  175. endif
  176. endif
  177. if exists("g:mundo_tree_statusline")
  178. let &l:statusline = g:mundo_tree_statusline
  179. endif
  180. endfunction"}}}
  181. function! s:MundoOpenPreview()"{{{
  182. let existing_preview_buffer = bufnr("__Mundo_Preview__")
  183. if existing_preview_buffer == -1
  184. if g:mundo_preview_bottom
  185. exe "botright keepalt new __Mundo_Preview__"
  186. else
  187. if g:mundo_right
  188. exe "botright keepalt vnew __Mundo_Preview__"
  189. else
  190. exe "topleft keepalt vnew __Mundo_Preview__"
  191. endif
  192. endif
  193. else
  194. let existing_preview_window = bufwinnr(existing_preview_buffer)
  195. if existing_preview_window != -1
  196. if winnr() != existing_preview_window
  197. exe existing_preview_window . "wincmd w"
  198. endif
  199. else
  200. if g:mundo_preview_bottom
  201. exe "botright keepalt split +buffer" . existing_preview_buffer
  202. else
  203. if g:mundo_right
  204. exe "botright keepalt vsplit +buffer" . existing_preview_buffer
  205. else
  206. exe "topleft keepalt vsplit +buffer" . existing_preview_buffer
  207. endif
  208. endif
  209. endif
  210. endif
  211. if exists("g:mundo_preview_statusline")
  212. let &l:statusline = g:mundo_preview_statusline
  213. endif
  214. endfunction"}}}
  215. function! s:MundoClose()"{{{
  216. if s:MundoGoToWindowForBufferName('__Mundo__')
  217. quit
  218. endif
  219. if s:MundoGoToWindowForBufferName('__Mundo_Preview__')
  220. quit
  221. endif
  222. exe bufwinnr(g:mundo_target_n) . "wincmd w"
  223. endfunction"}}}
  224. function! s:MundoOpen()"{{{
  225. if !exists('g:mundo_py_loaded')
  226. if s:has_supported_python == 2 && g:mundo_prefer_python3
  227. exe 'py3file ' . escape(s:plugin_path, ' ') . '/mundo.py'
  228. python3 initPythonModule()
  229. else
  230. exe 'pyfile ' . escape(s:plugin_path, ' ') . '/mundo.py'
  231. python initPythonModule()
  232. endif
  233. if !s:has_supported_python
  234. function! s:MundoDidNotLoad()
  235. echohl WarningMsg|echomsg "Mundo unavailable: requires Vim 7.3+"|echohl None
  236. endfunction
  237. command! -nargs=0 MundoToggle call s:MundoDidNotLoad()
  238. call s:MundoDidNotLoad()
  239. return
  240. endif
  241. let g:mundo_py_loaded = 1
  242. endif
  243. " Save `splitbelow` value and set it to default to avoid problems with
  244. " positioning new windows.
  245. let saved_splitbelow = &splitbelow
  246. let &splitbelow = 0
  247. call s:MundoOpenPreview()
  248. exe bufwinnr(g:mundo_target_n) . "wincmd w"
  249. call s:MundoOpenGraph()
  250. call s:MundoPython('MundoRenderGraph()')
  251. call s:MundoPython('MundoRenderPreview()')
  252. " Restore `splitbelow` value.
  253. let &splitbelow = saved_splitbelow
  254. endfunction"}}}
  255. " This has to be outside of a function otherwise it just picks up the CWD
  256. let s:mundo_path = escape( expand( '<sfile>:p:h' ), '\' )
  257. function! s:MundoToggle()"{{{
  258. if g:mundo_python_path_setup == 0
  259. let g:mundo_python_path_setup = 1
  260. call s:MundoPython('sys.path.insert(1, "'. s:mundo_path .'")')
  261. call s:MundoPython('sys.path.insert(1, "'. s:mundo_path .'/mundo")')
  262. end
  263. if s:MundoIsVisible()
  264. call s:MundoClose()
  265. else
  266. let g:mundo_target_n = bufnr('')
  267. let g:mundo_target_f = @%
  268. call s:MundoOpen()
  269. endif
  270. endfunction"}}}
  271. function! s:MundoShow()"{{{
  272. if !s:MundoIsVisible()
  273. let g:mundo_target_n = bufnr('')
  274. let g:mundo_target_f = @%
  275. call s:MundoOpen()
  276. endif
  277. endfunction"}}}
  278. function! s:MundoHide()"{{{
  279. if s:MundoIsVisible()
  280. call s:MundoClose()
  281. endif
  282. endfunction"}}}
  283. "}}}
  284. "{{{ Mundo mouse handling
  285. function! s:MundoMouseDoubleClick()"{{{
  286. let start_line = getline('.')
  287. if stridx(start_line, '[') == -1
  288. return
  289. else
  290. call <sid>MundoPython('MundoRevert()')
  291. endif
  292. endfunction"}}}
  293. "}}}
  294. "{{{ Mundo rendering
  295. function! s:MundoPython(fn)"{{{
  296. if s:has_supported_python == 2 && g:mundo_prefer_python3
  297. exec "python3 ". a:fn
  298. else
  299. exec "python ". a:fn
  300. endif
  301. endfunction"}}}
  302. "}}}
  303. "{{{ Misc
  304. function! mundo#MundoToggle()"{{{
  305. call s:MundoToggle()
  306. endfunction"}}}
  307. function! mundo#MundoShow()"{{{
  308. call s:MundoShow()
  309. endfunction"}}}
  310. function! mundo#MundoHide()"{{{
  311. call s:MundoHide()
  312. endfunction"}}}
  313. function! mundo#MundoRenderGraph()"{{{
  314. call s:MundoPython('MundoRenderGraph()')
  315. endfunction"}}}
  316. " automatically reload Mundo buffer if open
  317. function! s:MundoRefresh()"{{{
  318. " abort when there were no changes
  319. let mundoWin = bufwinnr('__Mundo__')
  320. let mundoPreWin = bufwinnr('__Mundo_Preview__')
  321. let currentWin = bufwinnr('%')
  322. " abort if Mundo is closed or is current window
  323. if (mundoWin == -1) || (mundoPreWin == -1) || (mundoPreWin == currentWin)
  324. return
  325. endif
  326. let winView = winsaveview()
  327. :MundoRenderGraph
  328. " switch back to previous window
  329. execute currentWin .'wincmd w'
  330. call winrestview(winView)
  331. endfunction"}}}
  332. augroup MundoAug
  333. autocmd!
  334. autocmd BufNewFile __Mundo__ call s:MundoSettingsGraph()
  335. autocmd BufNewFile __Mundo_Preview__ call s:MundoSettingsPreview()
  336. autocmd CursorHold * call s:MundoRefresh()
  337. autocmd CursorMoved * call s:MundoRefresh()
  338. autocmd BufEnter * let b:mundoChangedtick = 0
  339. augroup END
  340. "}}}
  341. let &cpo = s:save_cpo
  342. unlet s:save_cpo