1
0

gundo.vim 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. " ============================================================================
  2. " File: gundo.vim
  3. " Description: vim global plugin to visualize your undo tree
  4. " Maintainer: Steve Losh <steve@stevelosh.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. if v:version < '703'"{{{
  12. function! s:GundoDidNotLoad()
  13. echohl WarningMsg|echomsg "Gundo unavailable: requires Vim 7.3+"|echohl None
  14. endfunction
  15. command! -nargs=0 GundoToggle call s:GundoDidNotLoad()
  16. finish
  17. endif"}}}
  18. if !exists('g:gundo_width')"{{{
  19. let g:gundo_width = 45
  20. endif"}}}
  21. if !exists('g:gundo_preview_height')"{{{
  22. let g:gundo_preview_height = 15
  23. endif"}}}
  24. if !exists('g:gundo_preview_bottom')"{{{
  25. let g:gundo_preview_bottom = 0
  26. endif"}}}
  27. if !exists('g:gundo_right')"{{{
  28. let g:gundo_right = 0
  29. endif"}}}
  30. if !exists('g:gundo_help')"{{{
  31. let g:gundo_help = 1
  32. endif"}}}
  33. if !exists("g:gundo_map_move_older")"{{{
  34. let g:gundo_map_move_older = 'j'
  35. endif"}}}
  36. if !exists("g:gundo_map_move_newer")"{{{
  37. let g:gundo_map_move_newer = 'k'
  38. endif"}}}
  39. if !exists("g:gundo_close_on_revert")"{{{
  40. let g:gundo_close_on_revert = 0
  41. endif"}}}
  42. if !exists("g:gundo_prefer_python3")"{{{
  43. let g:gundo_prefer_python3 = 0
  44. endif"}}}
  45. let s:has_supported_python = 0
  46. if g:gundo_prefer_python3 && has('python3')"{{{
  47. let s:has_supported_python = 2
  48. elseif has('python')"
  49. let s:has_supported_python = 1
  50. endif
  51. if !s:has_supported_python
  52. function! s:GundoDidNotLoad()
  53. echohl WarningMsg|echomsg "Gundo requires Vim to be compiled with Python 2.4+"|echohl None
  54. endfunction
  55. command! -nargs=0 GundoToggle call s:GundoDidNotLoad()
  56. finish
  57. endif"}}}
  58. let s:plugin_path = escape(expand('<sfile>:p:h'), '\')
  59. "}}}
  60. "{{{ Gundo utility functions
  61. function! s:GundoGetTargetState()"{{{
  62. let target_line = matchstr(getline("."), '\v\[[0-9]+\]')
  63. return matchstr(target_line, '\v[0-9]+')
  64. endfunction"}}}
  65. function! s:GundoGoToWindowForBufferName(name)"{{{
  66. if bufwinnr(bufnr(a:name)) != -1
  67. exe bufwinnr(bufnr(a:name)) . "wincmd w"
  68. return 1
  69. else
  70. return 0
  71. endif
  72. endfunction"}}}
  73. function! s:GundoIsVisible()"{{{
  74. if bufwinnr(bufnr("__Gundo__")) != -1 || bufwinnr(bufnr("__Gundo_Preview__")) != -1
  75. return 1
  76. else
  77. return 0
  78. endif
  79. endfunction"}}}
  80. function! s:GundoInlineHelpLength()"{{{
  81. if g:gundo_help
  82. return 6
  83. else
  84. return 0
  85. endif
  86. endfunction"}}}
  87. "}}}
  88. "{{{ Gundo buffer settings
  89. function! s:GundoMapGraph()"{{{
  90. exec 'nnoremap <script> <silent> <buffer> ' . g:gundo_map_move_older . " :call <sid>GundoMove(1)<CR>"
  91. exec 'nnoremap <script> <silent> <buffer> ' . g:gundo_map_move_newer . " :call <sid>GundoMove(-1)<CR>"
  92. nnoremap <script> <silent> <buffer> <CR> :call <sid>GundoRevert()<CR>
  93. nnoremap <script> <silent> <buffer> o :call <sid>GundoRevert()<CR>
  94. nnoremap <script> <silent> <buffer> <down> :call <sid>GundoMove(1)<CR>
  95. nnoremap <script> <silent> <buffer> <up> :call <sid>GundoMove(-1)<CR>
  96. nnoremap <script> <silent> <buffer> gg gg:call <sid>GundoMove(1)<CR>
  97. nnoremap <script> <silent> <buffer> P :call <sid>GundoPlayTo()<CR>
  98. nnoremap <script> <silent> <buffer> p :call <sid>GundoRenderChangePreview()<CR>
  99. nnoremap <script> <silent> <buffer> q :call <sid>GundoClose()<CR>
  100. cabbrev <script> <silent> <buffer> q call <sid>GundoClose()
  101. cabbrev <script> <silent> <buffer> quit call <sid>GundoClose()
  102. nnoremap <script> <silent> <buffer> <2-LeftMouse> :call <sid>GundoMouseDoubleClick()<CR>
  103. endfunction"}}}
  104. function! s:GundoMapPreview()"{{{
  105. nnoremap <script> <silent> <buffer> q :call <sid>GundoClose()<CR>
  106. cabbrev <script> <silent> <buffer> q call <sid>GundoClose()
  107. cabbrev <script> <silent> <buffer> quit call <sid>GundoClose()
  108. endfunction"}}}
  109. function! s:GundoSettingsGraph()"{{{
  110. setlocal buftype=nofile
  111. setlocal bufhidden=hide
  112. setlocal noswapfile
  113. setlocal nobuflisted
  114. setlocal nomodifiable
  115. setlocal filetype=gundo
  116. setlocal nolist
  117. setlocal nonumber
  118. setlocal norelativenumber
  119. setlocal nowrap
  120. call s:GundoSyntaxGraph()
  121. call s:GundoMapGraph()
  122. endfunction"}}}
  123. function! s:GundoSettingsPreview()"{{{
  124. setlocal buftype=nofile
  125. setlocal bufhidden=hide
  126. setlocal noswapfile
  127. setlocal nobuflisted
  128. setlocal nomodifiable
  129. setlocal filetype=diff
  130. setlocal nonumber
  131. setlocal norelativenumber
  132. setlocal nowrap
  133. setlocal foldlevel=20
  134. setlocal foldmethod=diff
  135. call s:GundoMapPreview()
  136. endfunction"}}}
  137. function! s:GundoSyntaxGraph()"{{{
  138. let b:current_syntax = 'gundo'
  139. syn match GundoCurrentLocation '@'
  140. syn match GundoHelp '\v^".*$'
  141. syn match GundoNumberField '\v\[[0-9]+\]'
  142. syn match GundoNumber '\v[0-9]+' contained containedin=GundoNumberField
  143. hi def link GundoCurrentLocation Keyword
  144. hi def link GundoHelp Comment
  145. hi def link GundoNumberField Comment
  146. hi def link GundoNumber Identifier
  147. endfunction"}}}
  148. "}}}
  149. "{{{ Gundo buffer/window management
  150. function! s:GundoResizeBuffers(backto)"{{{
  151. call s:GundoGoToWindowForBufferName('__Gundo__')
  152. exe "vertical resize " . g:gundo_width
  153. call s:GundoGoToWindowForBufferName('__Gundo_Preview__')
  154. exe "resize " . g:gundo_preview_height
  155. exe a:backto . "wincmd w"
  156. endfunction"}}}
  157. function! s:GundoOpenGraph()"{{{
  158. let existing_gundo_buffer = bufnr("__Gundo__")
  159. if existing_gundo_buffer == -1
  160. call s:GundoGoToWindowForBufferName('__Gundo_Preview__')
  161. exe "new __Gundo__"
  162. if g:gundo_preview_bottom
  163. if g:gundo_right
  164. wincmd L
  165. else
  166. wincmd H
  167. endif
  168. endif
  169. call s:GundoResizeBuffers(winnr())
  170. else
  171. let existing_gundo_window = bufwinnr(existing_gundo_buffer)
  172. if existing_gundo_window != -1
  173. if winnr() != existing_gundo_window
  174. exe existing_gundo_window . "wincmd w"
  175. endif
  176. else
  177. call s:GundoGoToWindowForBufferName('__Gundo_Preview__')
  178. if g:gundo_preview_bottom
  179. if g:gundo_right
  180. exe "botright vsplit +buffer" . existing_gundo_buffer
  181. else
  182. exe "topleft vsplit +buffer" . existing_gundo_buffer
  183. endif
  184. else
  185. exe "split +buffer" . existing_gundo_buffer
  186. endif
  187. call s:GundoResizeBuffers(winnr())
  188. endif
  189. endif
  190. if exists("g:gundo_tree_statusline")
  191. let &l:statusline = g:gundo_tree_statusline
  192. endif
  193. endfunction"}}}
  194. function! s:GundoOpenPreview()"{{{
  195. let existing_preview_buffer = bufnr("__Gundo_Preview__")
  196. if existing_preview_buffer == -1
  197. if g:gundo_preview_bottom
  198. exe "botright new __Gundo_Preview__"
  199. else
  200. if g:gundo_right
  201. exe "botright vnew __Gundo_Preview__"
  202. else
  203. exe "topleft vnew __Gundo_Preview__"
  204. endif
  205. endif
  206. else
  207. let existing_preview_window = bufwinnr(existing_preview_buffer)
  208. if existing_preview_window != -1
  209. if winnr() != existing_preview_window
  210. exe existing_preview_window . "wincmd w"
  211. endif
  212. else
  213. if g:gundo_preview_bottom
  214. exe "botright split +buffer" . existing_preview_buffer
  215. else
  216. if g:gundo_right
  217. exe "botright vsplit +buffer" . existing_preview_buffer
  218. else
  219. exe "topleft vsplit +buffer" . existing_preview_buffer
  220. endif
  221. endif
  222. endif
  223. endif
  224. if exists("g:gundo_preview_statusline")
  225. let &l:statusline = g:gundo_preview_statusline
  226. endif
  227. endfunction"}}}
  228. function! s:GundoClose()"{{{
  229. if s:GundoGoToWindowForBufferName('__Gundo__')
  230. quit
  231. endif
  232. if s:GundoGoToWindowForBufferName('__Gundo_Preview__')
  233. quit
  234. endif
  235. exe bufwinnr(g:gundo_target_n) . "wincmd w"
  236. endfunction"}}}
  237. function! s:GundoOpen()"{{{
  238. if !exists('g:gundo_py_loaded')
  239. if s:has_supported_python == 2 && g:gundo_prefer_python3
  240. exe 'py3file ' . s:plugin_path . '/gundo.py'
  241. python3 initPythonModule()
  242. else
  243. exe 'pyfile ' . s:plugin_path . '/gundo.py'
  244. python initPythonModule()
  245. endif
  246. if !s:has_supported_python
  247. function! s:GundoDidNotLoad()
  248. echohl WarningMsg|echomsg "Gundo unavailable: requires Vim 7.3+"|echohl None
  249. endfunction
  250. command! -nargs=0 GundoToggle call s:GundoDidNotLoad()
  251. call s:GundoDidNotLoad()
  252. return
  253. endif"
  254. let g:gundo_py_loaded = 1
  255. endif
  256. " Save `splitbelow` value and set it to default to avoid problems with
  257. " positioning new windows.
  258. let saved_splitbelow = &splitbelow
  259. let &splitbelow = 0
  260. call s:GundoOpenPreview()
  261. exe bufwinnr(g:gundo_target_n) . "wincmd w"
  262. call s:GundoRenderGraph()
  263. call s:GundoRenderPreview()
  264. " Restore `splitbelow` value.
  265. let &splitbelow = saved_splitbelow
  266. endfunction"}}}
  267. function! s:GundoToggle()"{{{
  268. if s:GundoIsVisible()
  269. call s:GundoClose()
  270. else
  271. let g:gundo_target_n = bufnr('')
  272. let g:gundo_target_f = @%
  273. call s:GundoOpen()
  274. endif
  275. endfunction"}}}
  276. function! s:GundoShow()"{{{
  277. call s:GundoOpen()
  278. endfunction"}}}
  279. function! s:GundoHide()"{{{
  280. call s:GundoClose()
  281. endfunction"}}}
  282. "}}}
  283. "{{{ Gundo mouse handling
  284. function! s:GundoMouseDoubleClick()"{{{
  285. let start_line = getline('.')
  286. if stridx(start_line, '[') == -1
  287. return
  288. else
  289. call s:GundoRevert()
  290. endif
  291. endfunction"}}}
  292. "}}}
  293. "{{{ Gundo movement
  294. function! s:GundoMove(direction) range"{{{
  295. let start_line = getline('.')
  296. if v:count1 == 0
  297. let move_count = 1
  298. else
  299. let move_count = v:count1
  300. endif
  301. let distance = 2 * move_count
  302. " If we're in between two nodes we move by one less to get back on track.
  303. if stridx(start_line, '[') == -1
  304. let distance = distance - 1
  305. endif
  306. let target_n = line('.') + (distance * a:direction)
  307. " Bound the movement to the graph.
  308. if target_n <= s:GundoInlineHelpLength() - 1
  309. call cursor(s:GundoInlineHelpLength(), 0)
  310. else
  311. call cursor(target_n, 0)
  312. endif
  313. let line = getline('.')
  314. " Move to the node, whether it's an @ or an o
  315. let idx1 = stridx(line, '@')
  316. let idx2 = stridx(line, 'o')
  317. if idx1 != -1
  318. call cursor(0, idx1 + 1)
  319. else
  320. call cursor(0, idx2 + 1)
  321. endif
  322. call s:GundoRenderPreview()
  323. endfunction"}}}
  324. "}}}
  325. "{{{ Gundo rendering
  326. function! s:GundoRenderGraph()"{{{
  327. if s:has_supported_python == 2 && g:gundo_prefer_python3
  328. python3 GundoRenderGraph()
  329. else
  330. python GundoRenderGraph()
  331. endif
  332. endfunction"}}}
  333. function! s:GundoRenderPreview()"{{{
  334. if s:has_supported_python == 2 && g:gundo_prefer_python3
  335. python3 GundoRenderPreview()
  336. else
  337. python GundoRenderPreview()
  338. endif
  339. endfunction"}}}
  340. function! s:GundoRenderChangePreview()"{{{
  341. if s:has_supported_python == 2 && g:gundo_prefer_python3
  342. python3 GundoRenderChangePreview()
  343. else
  344. python GundoRenderChangePreview()
  345. endif
  346. endfunction"}}}
  347. "}}}
  348. "{{{ Gundo undo/redo
  349. function! s:GundoRevert()"{{{
  350. if s:has_supported_python == 2 && g:gundo_prefer_python3
  351. python3 GundoRevert()
  352. else
  353. python GundoRevert()
  354. endif
  355. endfunction"}}}
  356. function! s:GundoPlayTo()"{{{
  357. if s:has_supported_python == 2 && g:gundo_prefer_python3
  358. python3 GundoPlayTo()
  359. else
  360. python GundoPlayTo()
  361. endif
  362. endfunction"}}}
  363. "}}}
  364. "{{{ Misc
  365. function! gundo#GundoToggle()"{{{
  366. call s:GundoToggle()
  367. endfunction"}}}
  368. function! gundo#GundoRenderGraph()"{{{
  369. call s:GundoRenderGraph()
  370. endfunction"}}}
  371. augroup GundoAug
  372. autocmd!
  373. autocmd BufNewFile __Gundo__ call s:GundoSettingsGraph()
  374. autocmd BufNewFile __Gundo_Preview__ call s:GundoSettingsPreview()
  375. augroup END
  376. "}}}