1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-24 06:30:03 +08:00
SpaceVim/bundle/vim-mundo/autoload/mundo.vim
2020-10-31 15:52:34 +08:00

562 lines
18 KiB
VimL
Vendored
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

" ============================================================================
" File: mundo.vim
" Description: vim global plugin to visualize your undo tree
" Maintainer: Hyeon Kim <simnalamburt@gmail.com>
" License: GPLv2+ -- look it up.
" Notes: Much of this code was thiefed from Mercurial, and the rest was
" heavily inspired by scratch.vim and histwin.vim.
"
" ============================================================================
let s:save_cpo = &cpoptions
set cpoptions&vim
"{{{ Init
" Initialise global vars
let s:auto_preview_timer = -1"{{{
let s:preview_outdated = 1
let s:has_supported_python = 0
let s:has_timers = 0
let s:init_error = 'Initialisation failed due to an unknown error. '
\ . 'Please submit a bug report :)'
" This has to be outside of a function, otherwise it just picks up the CWD
let s:plugin_path = escape(expand('<sfile>:p:h'), '\')"}}}
" Default to placeholder functions for exposed methods
function! mundo#MundoToggle() abort "{{{
call mundo#util#Echo('WarningMsg',
\ 'Mundo init error: ' . s:init_error)
endfunction
function! mundo#MundoShow() abort
call mundo#util#Echo('WarningMsg',
\ 'Mundo init error: ' . s:init_error)
endfunction
function! mundo#MundoHide() abort
call mundo#util#Echo('WarningMsg',
\ 'Mundo init error: ' . s:init_error)
endfunction
"}}}
" Check vim version
if v:version <? '703'"{{{
let s:init_error = 'Vim version 7.03+ or later is required.'
let &cpoptions = s:save_cpo
finish
elseif v:version >=? '800' && has('timers')
let s:has_timers = 1
endif"}}}
" Check python version
if g:mundo_prefer_python3 && has('python3')"{{{
let s:has_supported_python = 2
elseif has('python')"
let s:has_supported_python = 1
elseif has('python3')"
let s:has_supported_python = 2
endif
if !s:has_supported_python
let s:init_error = 'A supported python version was not found.'
let &cpoptions = s:save_cpo
finish
endif"}}}
" Python init methods
function! s:InitPythonModule(python)"{{{
exe a:python .' import sys'
exe a:python .' if sys.version_info[:2] < (2, 4): '.
\ 'vim.command("let s:has_supported_python = 0")'
endfunction"}}}
function! s:MundoSetupPythonPath()"{{{
if g:mundo_python_path_setup == 0
let g:mundo_python_path_setup = 1
call s:MundoPython('sys.path.insert(1, "'. s:plugin_path .'")')
call s:MundoPython('sys.path.insert(1, "'. s:plugin_path .'/mundo")')
end
endfunction"}}}
"}}}
"{{{ Mundo buffer settings
function! s:MundoMakeMapping(mapping, action)
exec 'nnoremap <script> <silent> <buffer> ' . a:mapping .' '. a:action
endfunction
function! s:MundoMapGraph()"{{{
for key in keys(g:mundo_mappings)
let l:value = g:mundo_mappings[key]
if l:value == "move_older"
call s:MundoMakeMapping(key, ":<C-u>call <sid>MundoPython('MundoMove(1,'. v:count .')')<CR>")
elseif l:value == "move_newer"
call s:MundoMakeMapping(key, ":<C-u>call <sid>MundoPython('MundoMove(-1,'. v:count .')')<CR>")
elseif l:value == "preview"
call s:MundoMakeMapping(key, ":<C-u>call <sid>MundoRenderPreview(1)<CR>:<C-u> call <sid>MundoPythonRestoreView('MundoRevert()')<CR>")
elseif l:value == "move_older_write"
call s:MundoMakeMapping(key, ":<C-u>call <sid>MundoPython('MundoMove(1,'.v:count.',True,True)')<CR>")
elseif l:value == "move_newer_write"
call s:MundoMakeMapping(key, ":<C-u>call <sid>MundoPython('MundoMove(-1,'.v:count.',True,True)')<CR>")
elseif l:value == "move_top"
call s:MundoMakeMapping(key, "gg:<C-u>call <sid>MundoPython('MundoMove(1,'.v:count.')')<CR>")
elseif l:value == "move_bottom"
call s:MundoMakeMapping(key, "G:<C-u>call <sid>MundoPython('MundoMove(0,0)')<CR>:<C-u>call <sid>MundoRefresh()<CR>")
elseif l:value == "play_to"
call s:MundoMakeMapping(key, ":<C-u>call <sid>MundoPythonRestoreView('MundoPlayTo()')<CR>zz")
elseif l:value == "diff"
call s:MundoMakeMapping(key, ":<C-u>call <sid>MundoPythonRestoreView('MundoRenderPatchdiff()')<CR>")
elseif l:value == "toggle_inline"
call s:MundoMakeMapping(key, ":<C-u>call <sid>MundoPythonRestoreView('MundoRenderToggleInlineDiff()')<CR>")
elseif l:value == "search"
call s:MundoMakeMapping(key, ":<C-u>call <sid>MundoPython('MundoSearch()')<CR>")
elseif l:value == "next_match"
call s:MundoMakeMapping(key, ":<C-u>call <sid>MundoPython('MundoNextMatch()')<CR>")
elseif l:value == "previous_match"
call s:MundoMakeMapping(key, ":<C-u>call <sid>MundoPython('MundoPrevMatch()')<CR>")
elseif l:value == "diff_current_buffer"
call s:MundoMakeMapping(key, ":<C-u>call <sid>MundoPythonRestoreView('MundoRenderChangePreview()')<CR>")
elseif l:value == "diff"
call s:MundoMakeMapping(key, ":<C-u>call <sid>MundoRenderPreview(1)<CR>")
elseif l:value == "toggle_help"
call s:MundoMakeMapping(key, ":<C-u>call <sid>MundoPython('MundoToggleHelp()')<CR>")
elseif l:value == "quit"
call s:MundoMakeMapping(key, ":<C-u>call <sid>MundoClose()<CR>")
elseif l:value == "mouse_click"
call s:MundoMakeMapping(key, ":<C-u>call <sid>MundoMouseDoubleClick()<CR>")
endif
endfor
cabbrev <script> <silent> <buffer> q call <sid>MundoClose()
cabbrev <script> <silent> <buffer> quit call <sid>MundoClose()
endfunction"}}}
function! s:MundoMapPreview()"{{{
nnoremap <script> <silent> <buffer> q :<C-u>call <sid>MundoClose()<CR>
cabbrev <script> <silent> <buffer> q call <sid>MundoClose()
cabbrev <script> <silent> <buffer> quit call <sid>MundoClose()
endfunction"}}}
function! s:MundoSettingsGraph()"{{{
setlocal buftype=nofile
setlocal bufhidden=hide
setlocal noswapfile
setlocal nobuflisted
setlocal nomodifiable
setlocal filetype=Mundo
setlocal nolist
setlocal nonumber
setlocal norelativenumber
setlocal nowrap
call s:MundoSyntaxGraph()
call s:MundoMapGraph()
endfunction"}}}
function! s:MundoSettingsPreview()"{{{
setlocal buftype=nofile
setlocal bufhidden=hide
setlocal noswapfile
setlocal nobuflisted
setlocal nomodifiable
setlocal filetype=MundoDiff
setlocal syntax=diff
setlocal nonumber
setlocal norelativenumber
setlocal nowrap
setlocal foldlevel=20
setlocal foldmethod=diff
call s:MundoMapPreview()
endfunction"}}}
function! s:MundoSyntaxGraph()"{{{
let b:current_syntax = 'mundo'
syn match MundoCurrentLocation '@'
syn match MundoHelp '\v^".*$'
syn match MundoNumberField '\v\[[0-9]+\]'
syn match MundoNumber '\v[0-9]+' contained containedin=MundoNumberField
syn region MundoDiff start=/\v<ago> / end=/$/
syn match MundoDiffAdd '\v\+[^+-]+\+' contained containedin=MundoDiff
syn match MundoDiffDelete '\v-[^+-]+-' contained containedin=MundoDiff
hi def link MundoCurrentLocation Keyword
hi def link MundoHelp Comment
hi def link MundoNumberField Comment
hi def link MundoNumber Identifier
hi def link MundoDiffAdd DiffAdd
hi def link MundoDiffDelete DiffDelete
endfunction"}}}
"}}}
"{{{ Mundo buffer/window management
function! s:MundoResizeBuffers(backto)"{{{
call mundo#util#GoToBuffer('__Mundo__')
exe "vertical resize " . g:mundo_width
call mundo#util#GoToBuffer('__Mundo_Preview__')
exe "resize " . g:mundo_preview_height
exe a:backto . "wincmd w"
endfunction"}}}
" Open the graph window. Assumes that the preview window is open.
function! s:MundoOpenGraph()"{{{
if !mundo#util#GoToBuffer("__Mundo__")
call assert_true(mundo#util#GoToBuffer('__Mundo_Preview__'))
let existing_mundo_buffer = bufnr("__Mundo__")
if existing_mundo_buffer == -1
" Create buffer
silent new __Mundo__
if g:mundo_preview_bottom
execute 'wincmd ' . (g:mundo_right ? 'L' : 'H')
endif
else
" Open a window for existing buffer
if g:mundo_preview_bottom
let pos = (g:mundo_right ? 'botright' : 'topleft')
silent execute pos.' vsplit +buffer' . existing_mundo_buffer
else
silent execute 'split +buffer' . existing_mundo_buffer
endif
endif
call s:MundoResizeBuffers(winnr())
endif
if exists("g:mundo_tree_statusline")
let &l:statusline = g:mundo_tree_statusline
endif
endfunction"}}}
function! s:MundoOpenPreview()"{{{
if !mundo#util#GoToBuffer("__Mundo_Preview__")
let existing_preview_buffer = bufnr("__Mundo_Preview__")
if existing_preview_buffer == -1
" Create buffer
if g:mundo_preview_bottom
silent botright keepalt new __Mundo_Preview__
else
let pos = (g:mundo_right ? 'botright' : 'topleft')
silent execute pos.' keepalt vnew __Mundo_Preview__'
endif
else
" Open a window for existing buffer
if g:mundo_preview_bottom
silent execute 'botright keepalt split +buffer' .
\ existing_preview_buffer
else
let pos = (g:mundo_right ? 'botright' : 'topleft')
silent execute pos.' keepalt vsplit +buffer' .
\ existing_preview_buffer
endif
endif
endif
if exists("g:mundo_preview_statusline")
let &l:statusline = g:mundo_preview_statusline
endif
endfunction"}}}
" Quits *all* open Mundo graph and preview windows.
function! s:MundoClose() abort
let [l:tabid, l:winid] = win_id2tabwin(win_getid())
" Close all graph and preview windows
while mundo#util#GoToBufferGlobal('__Mundo__') ||
\ mundo#util#GoToBufferGlobal('__Mundo_Preview__')
quit
endwhile
" Attempt to return to previous window / tab or target buffer
if win_gotoid(l:winid)
return
elseif l:tabid != 0 && l:tabid <= tabpagenr('$')
execute 'normal! ' . l:tabid . 'gt'
endif
call mundo#util#GoToBuffer(get(g:, 'mundo_target_n', -1))
endfunction
" Returns 1 if the current buffer is a valid target buffer for Mundo, or a
" (falsy) string indicating the reason if otherwise.
function! s:MundoValidateBuffer()"{{{
if !&modifiable
let reason = 'is not modifiable'
elseif &previewwindow
let reason = 'is a preview window'
elseif &buftype == 'help' || &buftype == 'quickfix' || &buftype == 'terminal'
let reason = 'is a '.&buftype.' window'
else
return 1
endif
call mundo#util#Echo('None', 'Current buffer ('.bufnr('').') is not a '
\ .'valid target for Mundo (Reason: '.reason.')')
return 0
endfunction "}}}
" Returns True if the graph or preview windows are open in the current tab.
function! s:MundoIsVisible()"{{{
return bufwinnr(bufnr("__Mundo__")) != -1 ||
\ bufwinnr(bufnr("__Mundo_Preview__")) != -1
endfunction"}}}
" Open/reopen Mundo for the current buffer, initialising the python module if
" necessary.
function! s:MundoOpen() abort "{{{
" Validate current buffer
if !s:MundoValidateBuffer()
return
endif
let g:mundo_target_n = bufnr('')
call s:MundoClose()
" Initialise python module if necessary
if !exists('g:mundo_py_loaded')
call s:MundoSetupPythonPath()
if s:has_supported_python == 2
exe 'py3file ' . escape(s:plugin_path, ' ') . '/mundo.py'
call s:InitPythonModule('python3')
else
exe 'pyfile ' . escape(s:plugin_path, ' ') . '/mundo.py'
call s:InitPythonModule('python')
endif
let g:mundo_py_loaded = 1
endif
" Save and reset `splitbelow` to avoid window positioning problems
let saved_splitbelow = &splitbelow
let &splitbelow = 0
" Temporarily disable automatic previews until Mundo is opened
let saved_auto_preview = g:mundo_auto_preview
let g:mundo_auto_preview = 0
" Create / open graph and preview windows
call s:MundoOpenPreview()
call mundo#util#GoToBuffer(g:mundo_target_n)
call s:MundoOpenGraph()
" Render the graph and preview, ensure the cursor is on a graph node
call s:MundoPythonRestoreView('MundoRenderGraph(True)')
call s:MundoRenderPreview()
call s:MundoPython('MundoMove(0,0)')
" Restore `splitbelow` and automatic preview option
let &splitbelow = saved_splitbelow
let g:mundo_auto_preview = saved_auto_preview
endfunction"}}}
function! s:MundoToggle()"{{{
if s:MundoIsVisible()
call s:MundoClose()
else
call s:MundoOpen()
endif
endfunction"}}}
function! s:MundoShow()"{{{
if !s:MundoIsVisible()
call s:MundoOpen()
endif
endfunction"}}}
function! s:MundoHide()"{{{
call s:MundoSetupPythonPath()
if s:MundoIsVisible()
call s:MundoClose()
endif
endfunction"}}}
"}}}
"{{{ Mundo mouse handling
function! s:MundoMouseDoubleClick()"{{{
let start_line = getline('.')
if stridx(start_line, '[') == -1
return
else
call <sid>MundoPythonRestoreView('MundoRevert()')
endif
endfunction"}}}
"}}}
"{{{ Mundo rendering
function! s:MundoPython(fn)"{{{
exec "python".(s:has_supported_python == 2 ? '3' : '')." ". a:fn
endfunction"}}}
" Wrapper for MundoPython() that restores the window state and prevents other
" Mundo autocommands (with the exception of BufNewFile) from triggering.
function! s:MundoPythonRestoreView(fn)"{{{
" Store view data, mode, window and 'evntignore' value
let currentmode = mode()
let currentWin = winnr()
let winView = winsaveview()
let eventignoreBack = &eventignore
set eventignore=BufLeave,BufEnter,CursorHold,CursorMoved,TextChanged
\,InsertLeave
" Call python function
call s:MundoPython(a:fn)
" Restore view data
execute currentWin .'wincmd w'
call winrestview(winView)
exec 'set eventignore='.eventignoreBack
" Re-select visual selection
if currentmode == 'v' || currentmode == 'V' || currentmode == ''
execute 'normal! gv'
endif
endfunction"}}}
" Accepts an optional integer that forces rendering if nonzero.
function! s:MundoRenderPreview(...)"{{{
if !s:preview_outdated && (a:0 < 1 || !a:1)
return
endif
call s:MundoPythonRestoreView('MundoRenderPreview()')
endfunction"}}}
"}}}
"{{{ Misc
" automatically reload Mundo buffer if open
function! s:MundoRefresh()"{{{
" abort if Mundo is closed or cursor is in the preview window
let mundoWin = bufwinnr('__Mundo__')
let mundoPreWin = bufwinnr('__Mundo_Preview__')
let currentWin = bufwinnr('%')
if mundoWin == -1 || mundoPreWin == -1 || mundoPreWin == currentWin
return
endif
" Disable the automatic preview delay if vim lacks support for timers
if g:mundo_auto_preview_delay > 0 && !s:has_timers
let g:mundo_auto_preview_delay = 0
call mundo#util#Echo('WarningMsg',
\ 'The "g:mundo_auto_preview_delay" option requires'
\ .' support for timers. Please upgrade to either vim 8.0+'
\ .' (with +timers) or neovim to use this feature. Press '
\ .'any key to continue.')
" Prevent the warning being cleared
call getchar()
endif
" Handle normal refresh
if g:mundo_auto_preview_delay <= 0
call s:MundoPythonRestoreView('MundoRenderGraph()')
if g:mundo_auto_preview && currentWin == mundoWin && mode() == 'n'
call s:MundoRenderPreview()
endif
return
endif
" Handle delayed refresh
call s:MundoRestartRefreshTimer()
endfunction"}}}
function! s:MundoRestartRefreshTimer()"{{{
call s:MundoStopRefreshTimer()
let s:auto_preview_timer = timer_start(
\ get(g:, 'mundo_auto_preview_delay', 0),
\ function('s:MundoRefreshDelayed')
\ )
endfunction"}}}
function! s:MundoStopRefreshTimer()"{{{
if s:auto_preview_timer != -1
call timer_stop(s:auto_preview_timer)
let s:auto_preview_timer = -1
endif
endfunction"}}}
function! s:MundoRefreshDelayed(...)"{{{
" abort if Mundo is closed or cursor is in the preview window
let mundoWin = bufwinnr('__Mundo__')
let mundoPreWin = bufwinnr('__Mundo_Preview__')
let currentWin = bufwinnr('%')
if mundoWin == -1 || mundoPreWin == -1 || mundoPreWin == currentWin
return
endif
" Update graph
call s:MundoPythonRestoreView('MundoRenderGraph()')
" Update preview
if currentWin != mundoWin || !g:mundo_auto_preview
return
endif
if mode() != 'n'
call s:MundoRestartRefreshTimer()
return
endif
call s:MundoRenderPreview()
endfunction"}}}
" Mark the preview as being up-to-date (0) or outdated (1)
function! mundo#MundoPreviewOutdated(outdated)"{{{
if s:preview_outdated && !a:outdated
call s:MundoStopRefreshTimer()
endif
let s:preview_outdated = a:outdated
endfunction"}}}
augroup MundoAug
autocmd!
autocmd BufEnter __Mundo__ call mundo#MundoPreviewOutdated(1)
autocmd BufLeave __Mundo__
\ if g:mundo_auto_preview |
\ call s:MundoRenderPreview() |
\ call s:MundoStopRefreshTimer() |
\ endif |
autocmd BufEnter __Mundo__ call s:MundoSettingsGraph()
autocmd BufEnter __Mundo_Preview__ call s:MundoSettingsPreview()
autocmd CursorHold,CursorMoved,TextChanged,InsertLeave *
\ call s:MundoRefresh()
augroup END
"}}}
" Exposed functions{{{
function! mundo#MundoToggle()"{{{
call s:MundoToggle()
endfunction"}}}
function! mundo#MundoShow()"{{{
call s:MundoShow()
endfunction"}}}
function! mundo#MundoHide()"{{{
call s:MundoHide()
endfunction"}}}
"}}}
let &cpoptions = s:save_cpo
unlet s:save_cpo