" Vim plugin for diffing when swap file was found
" ---------------------------------------------------------------
" Author: Christian Brabandt <cb@256bit.org>
" Version: 0.18
" Last Change: Wed, 14 Aug 2013 22:39:13 +0200
" Script:  http://www.vim.org/scripts/script.php?script_id=3068
" License: VIM License
" GetLatestVimScripts: 3068 18 :AutoInstall: recover.vim
"
fu! recover#Recover(on) "{{{1
    if a:on
	call s:ModifySTL(1)
	if !exists("s:old_vsc")
	    let s:old_vsc = v:swapchoice
	endif
	augroup Swap
	    au!
	    au SwapExists * nested :call recover#ConfirmSwapDiff()
	    au BufWinEnter,InsertEnter,InsertLeave,FocusGained * 
			\ call <sid>CheckSwapFileExists()
	augroup END
    else
	augroup Swap
	    au!
	augroup end
	if exists("s:old_vsc")
	    let v:swapchoice=s:old_vsc
	endif
    endif
endfu

fu! s:Swapname() "{{{1
    " Use sil! so a failing redir (e.g. recursive redir call)
    " won't hurt. (https://github.com/chrisbra/Recover.vim/pull/8)
    sil! redir => a |sil swapname|redir end
    if a[1:] == 'No swap file'
	return ''
    else
	return a[1:]
    endif
endfu

fu! s:CheckSwapFileExists() "{{{1
    if !&swapfile 
	return
    endif

    let swap = s:Swapname()
    if !empty(swap) && !filereadable(swap)
	" previous SwapExists autocommand deleted our swapfile,
	" recreate it and avoid E325 Message
	call s:SetSwapfile()
    endif
endfu

fu! s:CheckRecover() "{{{1
    if exists("b:swapname") && !exists("b:did_recovery")
	let t = tempname()
	" Doing manual recovery, otherwise, BufRead autocmd seems to
	" get into the way of the recovery
	try
	    exe 'recover' fnameescape(expand('%:p'))
	catch /^Vim\%((\a\+)\)\=:E/
	    " Prevent any recovery error from disrupting the diff-split.
	endtry
	exe ':sil w' t
	call system('diff '. shellescape(expand('%:p'),1).
		    \ ' '. shellescape(t,1))
	call delete(t)
	if !v:shell_error
	    call inputsave()
	    redraw! " prevent overwriting of 'Select File to use for recovery dialog'
	    let p = confirm("No differences: Delete old swap file '".b:swapname."'?",
		    \ "&No\n&Yes", 2)
	    call inputrestore()
	    if p == 2
		" Workaround for E305 error
		let v:swapchoice=''
		call delete(b:swapname)
		" can trigger SwapExists autocommands again!
		call s:SetSwapfile()
	    endif
	    call recover#AutoCmdBRP(0)
	else
	    echo "Found Swapfile '". b:swapname. "', showing diff!"
	    call recover#DiffRecoveredFile()
	    " Not sure, why this needs feedkeys
	    " Sometimes cursor is wrong, I hate when this happens
	    " Cursor is wrong only when there is a single buffer open, a simple
	    " workaround for that is to check if bufnr('') is 1 and total number
	    " of windows in current tab is less than 3 (i.e. no windows were
	    " autoopen): in this case ':wincmd l\n:0\n' must be fed to
	    " feedkeys
	    if bufnr('') == 1 && winnr('$') < 3
		call feedkeys(":wincmd l\<cr>", 't')
	    endif
	    if !(v:version > 703 || (v:version == 703 && has("patch708")))
		call feedkeys(":0\<cr>", 't')
	    endif
	endif
	let b:did_recovery = 1
	" Don't delete the auto command yet.
	"call recover#AutoCmdBRP(0)
    endif
endfun

fu! recover#ConfirmSwapDiff() "{{{1
    if exists("b:swapchoice")
	let v:swapchoice = b:swapchoice
	return
    endif
    let delete = 0
    let do_modification_check = exists("g:RecoverPlugin_Edit_Unmodified") ? g:RecoverPlugin_Edit_Unmodified : 0
    let not_modified = 0
    let msg = ""
    let bufname = s:isWin() ? fnamemodify(expand('%'), ':p:8') : shellescape(expand('%'))
    let tfile = tempname()
    if executable('vim') && !s:isWin()
	" Doesn't work on windows (system() won't be able to fetch the output)
	" Capture E325 Warning message
	" Leave English output, so parsing will be easier
	" TODO: make it work on windows.
	if s:isWin()
	  let wincmd = printf('-c "redir > %s|1d|:q!" ', tfile)
	  let wincmd = printf('-c "call feedkeys(\"o\n\e:q!\n\")"')
	endif
	let cmd = printf("%svim -u NONE -es -V %s %s",
	    \ (s:isWin() ? '' : 'TERM=vt100 LC_ALL=C '),
	    \ (s:isWin() ? wincmd : ''),
	    \ bufname)
	let msg = system(cmd)
	let msg = substitute(msg, '.*\(E325.*process ID:.\{-}\)\%x0d.*', '\1', '')
	let msg = substitute(msg, "\e\\[\\d\\+C", "", "g")
	if do_modification_check
	    let not_modified = (match(msg, "modified: no") > -1)
	endif
    endif
    if has("unix") && !empty(msg) && system("uname") =~? "linux"
	" try to get process name from pid
	" This is Linux specific.
	" TODO Is there a portable way to retrive this info for at least unix?
	let pid_pat = 'process ID:\s*\zs\d\+'
	let pid = matchstr(msg, pid_pat)+0
	if !empty(pid) && isdirectory('/proc')
	    let pname = 'not existing'
	    let proc = '/proc/'. pid. '/status'
	    if filereadable(proc)
		let pname = matchstr(readfile(proc)[0], '^Name:\s*\zs.*')
	    endif
	    let msg = substitute(msg, pid_pat, '& ['.pname."]\n", '')
	    if not_modified && pname !~? 'vim'
		let not_modified = 0
	    endif
	endif
    endif
    if executable('vim') && executable('diff') "&& s:isWin()
	" Check, whether the files differ issue #7
	" doesn't work on Windows? (cmd is ok, should be executable)
	if s:isWin()
	    let tfile = substitute(tfile, '/', '\\', 'g')
	endif
	let cmd = printf("vim -u NONE -N %s -r %s -c \":w %s|:q!\" %s diff %s %s",
		    \ (s:isWin() ? '' : '-es'), 
		    \ (s:isWin() ? fnamemodify(v:swapname, ':p:8') : shellescape(v:swapname)),
		    \ tfile, (s:isWin() ? '&' : '&&'),
		    \ bufname, tfile)
	call system(cmd)
	" if return code of diff is zero, files are identical
	let delete = !v:shell_error
	if !do_modification_check
	    echo msg
	endif
    endif
    call delete(tfile)
    if delete && !do_modification_check
	echomsg "Swap and on-disk file seem to be identical"
    endif
    let cmd = printf("D&iff\n&Open Read-Only\n&Edit anyway\n&Recover\n&Quit\n&Abort%s",
		\ ( (delete || !empty(msg)) ? "\n&Delete" : ""))
    if !empty(msg)
	let info = 'Please choose: '
    else
	let info = "Swap File '". v:swapname. "' found: "
    endif
"    if has("gui_running") && &go !~ 'c'
"	call inputsave()
"	let p = confirm(info, cmd, (modified ? 3 : delete ? 7 : 1), 'I')
"    else
"	echo info
"	call s:Output(cmd)
    if not_modified
	let p = 3
    else
	call inputsave()
	let p = confirm(info, cmd, (delete ? 7 : 1), 'I')
    "    endif
	call inputrestore()
    endif
    let b:swapname=v:swapname
    if p == 1 || p == 3
	" Diff or Edit Anyway
	call s:SwapChoice('e')
	" postpone recovering until later, for now, we are opening anyways...
	" (this is done by s:CheckRecover()
	" in an BufReadPost autocommand
	if (p == 1)
	    call recover#AutoCmdBRP(1)
	endif
    elseif p == 2
	" Open Read-Only
	" Don't show the Recovery dialog
	let v:swapchoice='o'
	call <sid>EchoMsg("Found SwapFile, opening file readonly!")
	sleep 2
    elseif p == 4
	" Recover
	let v:swapchoice='r'
    elseif p == 5
	" Quit
	let v:swapchoice='q'
    elseif p == 6
	" Abort
	let v:swapchoice='a'
    elseif p == 7
	" Delete Swap file, if not different
	call s:SwapChoice('d')
	call <sid>EchoMsg("Found SwapFile, deleting...")
	" might trigger SwapExists again!
	call s:SetSwapfile()
    else
	" Show default menu from vim
	return
    endif
endfun

fu! s:Output(msg) "{{{1
    " Display as one string, without linebreaks
    let msg = substitute(a:msg, '\n', '/', 'g')
    for item in split(msg, '&')
	echohl WarningMsg
	echon item[0]
	echohl Normal
	echon item[1:]
    endfor
endfun

fu! s:SwapChoice(char) "{{{1
    let v:swapchoice = a:char
    let b:swapchoice = a:char
endfu

fu! recover#DiffRecoveredFile() "{{{1
    " recovered version
    diffthis
    let b:mod='recovered version'
    let l:filetype = &ft
    if has("balloon_eval")
	set ballooneval
	setl bexpr=recover#BalloonExprRecover()
    endif
    " saved version
    let curspr = &spr
    set nospr
    noa vert new
    let &l:spr = curspr
    if !empty(glob(fnameescape(expand('#'))))
	0r #
	$d _
    endif
    if l:filetype != ""
	exe "setl filetype=".l:filetype
    endif
    exe "f! " . escape(expand("<afile>")," ") .
	    \ escape(' (on-disk version)', ' ')
    diffthis
    setl noswapfile buftype=nowrite bufhidden=delete nobuflisted
    let b:mod='unmodified version on-disk'
    let swapbufnr=bufnr('')
    if has("balloon_eval")
	set ballooneval
	setl bexpr=recover#BalloonExprRecover()
    endif
    noa wincmd l
    let b:swapbufnr = swapbufnr
    command! -buffer RecoverPluginFinish :FinishRecovery
    command! -buffer FinishRecovery :call recover#RecoverFinish()
    setl modified
endfu

fu! recover#Help() "{{{1
    echohl Title
    echo "Diff key mappings\n".
    \ "-----------------\n"
    echo "Normal mode commands:\n"
    echohl Normal
    echo "]c - next diff\n".
    \ "[c - prev diff\n".
    \ "do - diff obtain - get change from other window\n".
    \ "dp - diff put    - put change into other window\n"
    echohl Title
    echo "Ex-commands:\n"
    echohl Normal
    echo ":[range]diffget - get changes from other window\n".
    \ ":[range]diffput - put changes into other window\n".
    \ ":RecoverPluginDisable - DisablePlugin\n".
    \ ":RecoverPluginEnable  - EnablePlugin\n".
    \ ":RecoverPluginHelp    - this help"
    if exists(":RecoverPluginFinish")
	echo ":RecoverPluginFinish  - finish recovery"
    endif
endfun



fu! s:EchoMsg(msg) "{{{1
    echohl WarningMsg
    uns echomsg a:msg
    echohl Normal
endfu

fu! s:ModifySTL(enable) "{{{1
    if a:enable
	" Inject some info into the statusline
	let s:ostl = &stl
	let s:nstl = substitute(&stl, '%f',
		\ "\\0 %{exists('b:mod')?('['.b:mod.']') : ''}", 'g')
	let &l:stl = s:nstl
    else
	" Restore old statusline setting
	if exists("s:ostl") && s:nstl == &stl
	    let &stl=s:ostl
	endif
    endif
endfu

fu! s:SetSwapfile() "{{{1
    if &l:swf
	" Reset swapfile to use .swp extension
	sil setl noswapfile swapfile
    endif
endfu

fu! s:isWin() "{{{1
    return has("win32") || has("win16") || has("win64")
endfu
fu! recover#BalloonExprRecover() "{{{1
    " Set up a balloon expr.
    if exists("b:swapbufnr") && v:beval_bufnr!=?b:swapbufnr
	return "This buffer shows the recovered and modified version of your file"
    else
	return "This buffer shows the unmodified version of your file as it is stored on disk"
    endif
endfun

fu! recover#RecoverFinish() abort "{{{1
    let swapname = b:swapname
    let curbufnr = bufnr('')
    delcommand FinishRecovery
    exe bufwinnr(b:swapbufnr) " wincmd w"
    diffoff
    bd!
    call delete(swapname)
    diffoff
    call s:ModifySTL(0)
    exe bufwinnr(curbufnr) " wincmd w"
    call s:SetSwapfile()
    unlet! b:swapname b:did_recovery b:swapbufnr b:swapchoice
endfun

fu! recover#AutoCmdBRP(on) "{{{1
    if a:on && !exists("#SwapBRP")
	augroup SwapBRP
	    au!
	    au BufNewFile,BufReadPost <buffer> :call s:CheckRecover()
	augroup END
    elseif !a:on && exists('#SwapBRP')
	augroup SwapBRP
	    au!
	augroup END
	augroup! SwapBRP
    endif
endfu
" Old functions, not used anymore "{{{1
finish

fu! recover#DiffRecoveredFileOld() "{{{2
	" For some reason, this only works with feedkeys.
	" I am not sure  why.
	let  histnr = histnr('cmd')+1
	call feedkeys(":diffthis\n", 't')
	call feedkeys(":setl modified\n", 't')
	call feedkeys(":let b:mod='recovered version'\n", 't')
	call feedkeys(":let g:recover_bufnr=bufnr('%')\n", 't')
	let l:filetype = &ft
	call feedkeys(":vert new\n", 't')
	call feedkeys(":0r #\n", 't')
	call feedkeys(":$delete _\n", 't')
	if l:filetype != ""
	    call feedkeys(":setl filetype=".l:filetype."\n", 't')
	endif
	call feedkeys(":f! " . escape(expand("<afile>")," ") . "\\ (on-disk\\ version)\n", 't')
	call feedkeys(":let swapbufnr = bufnr('')\n", 't')
	call feedkeys(":diffthis\n", 't')
	call feedkeys(":setl noswapfile buftype=nowrite bufhidden=delete nobuflisted\n", 't')
	call feedkeys(":let b:mod='unmodified version on-disk'\n", 't')
	call feedkeys(":exe bufwinnr(g:recover_bufnr) ' wincmd w'"."\n", 't')
	call feedkeys(":let b:swapbufnr=swapbufnr\n", 't')
	"call feedkeys(":command! -buffer DeleteSwapFile :call delete(b:swapname)|delcommand DeleteSwapFile\n", 't')
	call feedkeys(":command! -buffer RecoverPluginFinish :FinishRecovery\n", 't')
	call feedkeys(":command! -buffer FinishRecovery :call recover#RecoverFinish()\n", 't')
	call feedkeys(":0\n", 't')
	if has("balloon_eval")
	"call feedkeys(':if has("balloon_eval")|:set ballooneval|setl bexpr=recover#BalloonExprRecover()|endif'."\n", 't')
	    call feedkeys(":set ballooneval|setl bexpr=recover#BalloonExprRecover()\n", 't')
	endif
	"call feedkeys(":redraw!\n", 't')
	call feedkeys(":for i in range(".histnr.", histnr('cmd'), 1)|:call histdel('cmd',i)|:endfor\n",'t')
	call feedkeys(":echo 'Found Swapfile '.b:swapname . ', showing diff!'\n", 'm')
	" Delete Autocommand
	call recover#AutoCmdBRP(0)
    "endif
endfu


" Modeline "{{{1
" vim:fdl=0
