X-Git-Url: http://git.iain.cx/?a=blobdiff_plain;ds=sidebyside;f=.vim%2Fautoload%2Fgenutils.vim;fp=.vim%2Fautoload%2Fgenutils.vim;h=c28a25c74909f84080b6b339ffbfee1e56ce7edd;hb=d0d885039e0b7f2242d6db6eb2ba36da132f1b94;hp=0000000000000000000000000000000000000000;hpb=85808cfafda2e74a99654d9a940291563b13cd85;p=profile.git diff --git a/.vim/autoload/genutils.vim b/.vim/autoload/genutils.vim new file mode 100755 index 0000000..c28a25c --- /dev/null +++ b/.vim/autoload/genutils.vim @@ -0,0 +1,1954 @@ +" genutils.vim: Please see plugin/genutils.vim +" +" TODO: +" - Vim7: redir can be used with a variable. +" - fnamemodify() on Unix doesn't expand to full name if the filename doesn't +" really exist on the filesystem. +" - Is setting 'scrolloff' and 'sidescrolloff' to 0 required while moving the +" cursor? +" - http://www.vim.org/tips/tip.php?tip_id=1379 +" +" - EscapeCommand() didn't work for David Fishburn. +" - Save/RestoreWindowSettings doesn't work well. +" +" Vim7: +" - Save/RestoreWindowSettings can use winsave/restview() functions. +" + +" Make sure line-continuations won't cause any problem. This will be restored +" at the end +let s:save_cpo = &cpo +set cpo&vim + + +let g:makeArgumentString = 'exec genutils#MakeArgumentString()' +let g:makeArgumentList = 'exec genutils#MakeArgumentList()' + +let s:makeArgumentString = '' +function! genutils#MakeArgumentString(...) + if s:makeArgumentString == '' + let s:makeArgumentString = genutils#ExtractFuncListing(s:SNR(). + \ '_makeArgumentString', 0, 0) + endif + if a:0 > 0 && a:1 != '' + return substitute(s:makeArgumentString, '\', a:1, 'g') + else + return s:makeArgumentString + endif +endfunction + + +let s:makeArgumentList = '' +function! genutils#MakeArgumentList(...) + if s:makeArgumentList == '' + let s:makeArgumentList = genutils#ExtractFuncListing(s:SNR(). + \ '_makeArgumentList', 0, 0) + endif + if a:0 > 0 && a:1 != '' + let mkArgLst = substitute(s:makeArgumentList, '\', a:1, 'g') + if a:0 > 1 && a:2 != '' + let mkArgLst = substitute(s:makeArgumentList, + \ '\(\s\+let __argSeparator = \)[^'."\n".']*', "\\1'".a:2."'", '') + endif + return mkArgLst + else + return s:makeArgumentList + endif +endfunction + +function! genutils#ExtractFuncListing(funcName, hLines, tLines) + let listing = genutils#GetVimCmdOutput('func '.a:funcName) + let listing = substitute(listing, + \ '^\%(\s\|'."\n".'\)*function '.a:funcName.'([^)]*)'."\n", '', '') + "let listing = substitute(listing, '\%(\s\|'."\n".'\)*endfunction\%(\s\|'."\n".'\)*$', '', '') + " Leave the last newline character. + let listing = substitute(listing, '\%('."\n".'\)\@<=\s*endfunction\s*$', '', '') + let listing = substitute(listing, '\(\%(^\|'."\n".'\)\s*\)\@<=\d\+', + \ '', 'g') + if a:hLines > 0 + let listing = substitute(listing, '^\%([^'."\n".']*'."\n".'\)\{'. + \ a:hLines.'}', '', '') + endif + if a:tLines > 0 + let listing = substitute(listing, '\%([^'."\n".']*'."\n".'\)\{'. + \ a:tLines.'}$', '', '') + endif + return listing +endfunction + +function! genutils#CreateArgString(argList, sep, ...) + let sep = (a:0 == 0) ? a:sep : a:1 " This is no longer used. + " Matching multvals functionality means, we need to ignore the trailing + " separator. + let argList = split(substitute(a:argList, a:sep.'$', '', ''), a:sep, 1) + let argString = "'" + for nextArg in argList + " FIXME: I think this check is not required. If "'" is the separator, we + " don't expect to see them in the elements. + if a:sep != "'" + let nextArg = substitute(nextArg, "'", "' . \"'\" . '", 'g') + endif + let argString = argString . nextArg . "', '" + endfor + let argString = strpart(argString, 0, strlen(argString) - 3) + return argString +endfunction + +" {{{ +function! s:_makeArgumentString() + let __argCounter = 1 + let argumentString = '' + while __argCounter <= a:0 + if type(a:{__argCounter}) + let __nextArg = "'" . + \ substitute(a:{__argCounter}, "'", "' . \"'\" . '", "g") . "'" + else + let __nextArg = a:{__argCounter} + endif + let argumentString = argumentString. __nextArg . + \ ((__argCounter == a:0) ? '' : ', ') + let __argCounter = __argCounter + 1 + endwhile + unlet __argCounter + if exists('__nextArg') + unlet __nextArg + endif +endfunction + +function! s:_makeArgumentList() + let __argCounter = 1 + let __argSeparator = ',' + let argumentList = '' + while __argCounter <= a:0 + let argumentList = argumentList . a:{__argCounter} + if __argCounter != a:0 + let argumentList = argumentList . __argSeparator + endif + let __argCounter = __argCounter + 1 + endwhile + unlet __argCounter + unlet __argSeparator +endfunction +" }}} + + +function! genutils#DebugShowArgs(...) + let i = 0 + let argString = '' + while i < a:0 + let argString = argString . a:{i + 1} . ', ' + let i = i + 1 + endwhile + let argString = strpart(argString, 0, strlen(argString) - 2) + call input("Args: " . argString) +endfunction + +" Window related functions {{{ + +function! genutils#NumberOfWindows() + let i = 1 + while winbufnr(i) != -1 + let i = i+1 + endwhile + return i - 1 +endfunction + +" Find the window number for the buffer passed. +" The fileName argument is treated literally, unlike the bufnr() which treats +" the argument as a regex pattern. +function! genutils#FindWindowForBuffer(bufferName, checkUnlisted) + return bufwinnr(genutils#FindBufferForName(a:bufferName)) +endfunction + +function! genutils#FindBufferForName(fileName) + " The name could be having extra backslashes to protect certain chars (such + " as '#' and '%'), so first expand them. + return s:FindBufferForName(genutils#UnEscape(a:fileName, '#%')) +endfunction + +function! s:FindBufferForName(fileName) + let fileName = genutils#Escape(a:fileName, '[?,{') + let _isf = &isfname + try + set isfname-=\ + set isfname-=[ + let i = bufnr('^' . fileName . '$') + finally + let &isfname = _isf + endtry + return i +endfunction + +function! genutils#GetBufNameForAu(bufName) + let bufName = a:bufName + " Autocommands always require forward-slashes. + let bufName = substitute(bufName, "\\\\", '/', 'g') + let bufName = escape(bufName, '*?,{}[ ') + return bufName +endfunction + +function! genutils#MoveCursorToWindow(winno) + if genutils#NumberOfWindows() != 1 + execute a:winno . " wincmd w" + endif +endfunction + +function! genutils#MoveCurLineToWinLine(n) + normal! zt + if a:n == 1 + return + endif + let _wrap = &l:wrap + setl nowrap + let n = a:n + if n >= winheight(0) + let n = winheight(0) + endif + let n = n - 1 + execute "normal! " . n . "\" + let &l:wrap = _wrap +endfunction + +function! genutils#CloseWindow(win, force) + let _eventignore = &eventignore + try + set eventignore=all + call genutils#MarkActiveWindow() + + let &eventignore = _eventignore + exec a:win 'wincmd w' + exec 'close'.(a:force ? '!' : '') + set eventignore=all + + if a:win < t:curWinnr + let t:curWinnr = t:curWinnr - 1 + endif + if a:win < t:prevWinnr + let t:prevWinnr = t:prevWinnr - 1 + endif + finally + call genutils#RestoreActiveWindow() + let &eventignore = _eventignore + endtry +endfunction + +function! genutils#MarkActiveWindow() + let t:curWinnr = winnr() + " We need to restore the previous-window also at the end. + silent! wincmd p + let t:prevWinnr = winnr() + silent! wincmd p +endfunction + +function! genutils#RestoreActiveWindow() + if !exists('t:curWinnr') + return + endif + + " Restore the original window. + if winnr() != t:curWinnr + exec t:curWinnr'wincmd w' + endif + if t:curWinnr != t:prevWinnr + exec t:prevWinnr'wincmd w' + wincmd p + endif +endfunction + +function! genutils#IsOnlyVerticalWindow() + let onlyVertWin = 1 + let _eventignore = &eventignore + + try + "set eventignore+=WinEnter,WinLeave + set eventignore=all + call genutils#MarkActiveWindow() + + wincmd j + if winnr() != t:curWinnr + let onlyVertWin = 0 + else + wincmd k + if winnr() != t:curWinnr + let onlyVertWin = 0 + endif + endif + finally + call genutils#RestoreActiveWindow() + let &eventignore = _eventignore + endtry + return onlyVertWin +endfunction + +function! genutils#IsOnlyHorizontalWindow() + let onlyHorizWin = 1 + let _eventignore = &eventignore + try + set eventignore=all + call genutils#MarkActiveWindow() + wincmd l + if winnr() != t:curWinnr + let onlyHorizWin = 0 + else + wincmd h + if winnr() != t:curWinnr + let onlyHorizWin = 0 + endif + endif + finally + call genutils#RestoreActiveWindow() + let &eventignore = _eventignore + endtry + return onlyHorizWin +endfunction + +function! genutils#MoveCursorToNextInWinStack(dir) + let newwin = genutils#GetNextWinnrInStack(a:dir) + if newwin != 0 + exec newwin 'wincmd w' + endif +endfunction + +function! genutils#GetNextWinnrInStack(dir) + let newwin = winnr() + let _eventignore = &eventignore + try + set eventignore=all + call genutils#MarkActiveWindow() + let newwin = s:GetNextWinnrInStack(a:dir) + finally + call genutils#RestoreActiveWindow() + let &eventignore = _eventignore + endtry + return newwin +endfunction + +function! genutils#MoveCursorToLastInWinStack(dir) + let newwin = genutils#GetLastWinnrInStack(a:dir) + if newwin != 0 + exec newwin 'wincmd w' + endif +endfunction + +function! genutils#GetLastWinnrInStack(dir) + let newwin = winnr() + let _eventignore = &eventignore + try + set eventignore=all + call genutils#MarkActiveWindow() + while 1 + let wn = s:GetNextWinnrInStack(a:dir) + if wn != 0 + let newwin = wn + exec newwin 'wincmd w' + else + break + endif + endwhile + finally + call genutils#RestoreActiveWindow() + let &eventignore = _eventignore + endtry + return newwin +endfunction + +" Based on the WinStackMv() function posted by Charles E. Campbell, Jr. on vim +" mailing list on Jul 14, 2004. +function! s:GetNextWinnrInStack(dir) + "call Decho("genutils#MoveCursorToNextInWinStack(dir<".a:dir.">)") + + let isHorizontalMov = (a:dir ==# 'h' || a:dir ==# 'l') ? 1 : 0 + + let orgwin = winnr() + let orgdim = s:GetWinDim(a:dir, orgwin) + + let _winwidth = &winwidth + let _winheight = &winheight + try + set winwidth=1 + set winheight=1 + exec 'wincmd' a:dir + let newwin = winnr() + if orgwin == newwin + " No more windows in this direction. + "call Decho("newwin=".newwin." stopped".winheight(newwin)."x".winwidth(newwin)) + return 0 + endif + if s:GetWinDim(a:dir, newwin) != orgdim + " Window dimension has changed, indicates a move across window stacks. + "call Decho("newwin=".newwin." height changed".winheight(newwin)."x".winwidth(newwin)) + return 0 + endif + " Determine if changing original window height affects current window + " height. + exec orgwin 'wincmd w' + try + if orgdim == 1 + exec 'wincmd' (isHorizontalMov ? '_' : '|') + else + exec 'wincmd' (isHorizontalMov ? '-' : '<') + endif + if s:GetWinDim(a:dir, newwin) != s:GetWinDim(a:dir, orgwin) + "call Decho("newwin=".newwin." different row".winheight(newwin)."x".winwidth(newwin)) + return 0 + endif + "call Decho("newwin=".newwin." same row".winheight(newwin)."x".winwidth(newwin)) + finally + exec (isHorizontalMov ? '' : 'vert') 'resize' orgdim + endtry + + "call Decho("genutils#MoveCursorToNextInWinStack") + + return newwin + finally + let &winwidth = _winwidth + let &winheight = _winheight + endtry +endfunction + +function! s:GetWinDim(dir, win) + return (a:dir ==# 'h' || a:dir ==# 'l') ? winheight(a:win) : winwidth(a:win) +endfunction + +function! genutils#OpenWinNoEa(winOpenCmd) + call s:ExecWinCmdNoEa(a:winOpenCmd) +endfunction + +function! genutils#CloseWinNoEa(winnr, force) + call s:ExecWinCmdNoEa(a:winnr.'wincmd w | close'.(a:force?'!':'')) +endfunction + +function! s:ExecWinCmdNoEa(winCmd) + let _eventignore = &eventignore + try + set eventignore=all + call genutils#MarkActiveWindow() + windo let w:_winfixheight = &winfixheight + windo set winfixheight + call genutils#RestoreActiveWindow() + + let &eventignore = _eventignore + exec a:winCmd + set eventignore=all + + call genutils#MarkActiveWindow() + silent! windo let &winfixheight = w:_winfixheight + silent! windo unlet w:_winfixheight + call genutils#RestoreActiveWindow() + finally + let &eventignore = _eventignore + endtry +endfunction + +" Window related functions }}} + +function! genutils#SetupScratchBuffer() + setlocal nobuflisted + setlocal noswapfile + setlocal buftype=nofile + " Just in case, this will make sure we are always hidden. + setlocal bufhidden=delete + setlocal nolist + setlocal nonumber + setlocal foldcolumn=0 nofoldenable + setlocal noreadonly +endfunction + +function! genutils#CleanDiffOptions() + setlocal nodiff + setlocal noscrollbind + setlocal scrollopt-=hor + setlocal wrap + setlocal foldmethod=manual + setlocal foldcolumn=0 + normal zE +endfunction + +function! genutils#ArrayVarExists(varName, index) + try + exec "let test = " . a:varName . "{a:index}" + catch /^Vim\%((\a\+)\)\=:E121/ + return 0 + endtry + return 1 +endfunction + +function! genutils#Escape(str, chars) + return substitute(a:str, '\\\@ 0 + let pipe = join(a:pipe, ' ') + elseif type(a:pipe) == 1 && a:pipe !~# '^\s*$' + let pipe = a:pipe + endif + if pipe != '' + let fullCmd = fullCmd . ' ' . a:pipe + endif + if a:cmd !~# '^\s*$' + let fullCmd = a:cmd . ' ' . fullCmd + endif + if shellEnvType ==# g:ST_WIN_SH && &shell =~# '\' + let fullCmd = substitute(fullCmd, '\\', '\\\\', 'g') + endif + return fullCmd +endfunction + +let g:ST_WIN_CMD = 0 | let g:ST_WIN_SH = 1 | let g:ST_UNIX = 2 +function! genutils#GetShellEnvType() + " When 'shellslash' option is available, then the platform must be one of + " those that support '\' as a pathsep. + if exists('+shellslash') + if stridx(&shell, 'cmd.exe') != -1 || + \ stridx(&shell, 'command.com') != -1 + return g:ST_WIN_CMD + else + return g:ST_WIN_SH + endif + else + return g:ST_UNIX + endif +endfunction + +function! genutils#ExpandStr(str) + let str = substitute(a:str, '"', '\\"', 'g') + exec "let str = \"" . str . "\"" + return str +endfunction + +function! genutils#QuoteStr(str) + return "'".substitute(a:str, "'", "'.\"'\".'", 'g')."'" +endfunction + +function! genutils#GetPreviewWinnr() + let _eventignore = &eventignore + let curWinNr = winnr() + let winnr = -1 + try + set eventignore=all + exec "wincmd P" + let winnr = winnr() + catch /^Vim\%((\a\+)\)\=:E441/ + " Ignore, winnr is already set to -1. + finally + if winnr() != curWinNr + exec curWinNr.'wincmd w' + endif + let &eventignore = _eventignore + endtry + return winnr +endfunction + +" Save/Restore window settings {{{ +function! genutils#SaveWindowSettings() + call genutils#SaveWindowSettings2('SaveWindowSettings', 1) +endfunction + +function! genutils#RestoreWindowSettings() + call genutils#RestoreWindowSettings2('SaveWindowSettings') +endfunction + + +function! genutils#ResetWindowSettings() + call genutils#ResetWindowSettings2('SaveWindowSettings') +endfunction + +function! genutils#SaveWindowSettings2(id, overwrite) + if genutils#ArrayVarExists("t:winSettings", a:id) && ! a:overwrite + return + endif + + let t:winSettings{a:id} = [] + call add(t:winSettings{a:id}, genutils#NumberOfWindows()) + call add(t:winSettings{a:id}, &lines) + call add(t:winSettings{a:id}, &columns) + call add(t:winSettings{a:id}, winnr()) + let i = 1 + while winbufnr(i) != -1 + call add(t:winSettings{a:id}, winheight(i)) + call add(t:winSettings{a:id}, winwidth(i)) + let i = i + 1 + endwhile + "let g:savedWindowSettings = t:winSettings{a:id} " Debug. +endfunction + +function! genutils#RestoreWindowSettings2(id) + " Calling twice fixes most of the resizing issues. This seems to be how the + " :mksession with "winsize" in 'sesionoptions' seems to work. + call s:RestoreWindowSettings2(a:id) + call s:RestoreWindowSettings2(a:id) +endfunction + +function! s:RestoreWindowSettings2(id) + "if ! exists("t:winSettings" . a:id) + if ! genutils#ArrayVarExists("t:winSettings", a:id) + return + endif + + let nWindows = t:winSettings{a:id}[0] + if nWindows != genutils#NumberOfWindows() + unlet t:winSettings{a:id} + return + endif + let orgLines = t:winSettings{a:id}[1] + let orgCols = t:winSettings{a:id}[2] + let activeWindow = t:winSettings{a:id}[3] + let mainWinSizeSame = (orgLines == &lines && orgCols == &columns) + + let winNo = 1 + let i = 4 + while i < len(t:winSettings{a:id}) + let height = t:winSettings{a:id}[i] + let width = t:winSettings{a:id}[i+1] + let height = (mainWinSizeSame ? height : + \ ((&lines * height + (orgLines / 2)) / orgLines)) + let width = (mainWinSizeSame ? width : + \ ((&columns * width + (orgCols / 2)) / orgCols)) + if winheight(winnr()) != height + exec winNo'resize' height + endif + if winwidth(winnr()) != width + exec 'vert' winNo 'resize' width + endif + let winNo = winNo + 1 + let i = i + 2 + endwhile + + " Restore the current window. + call genutils#MoveCursorToWindow(activeWindow) + "unlet g:savedWindowSettings +endfunction + + +function! genutils#ResetWindowSettings2(id) + if genutils#ArrayVarExists("t:winSettings", a:id) + unlet t:winSettings{a:id} + endif +endfunction + +" Save/Restore window settings }}} + +" Save/Restore selection {{{ + +function! genutils#SaveVisualSelection(id) + let curmode = mode() + if curmode == 'n' + normal! gv + endif + let s:vismode{a:id} = mode() + let s:firstline{a:id} = line("'<") + let s:lastline{a:id} = line("'>") + let s:firstcol{a:id} = col("'<") + let s:lastcol{a:id} = col("'>") + if curmode !=# s:vismode{a:id} + exec "normal \" + endif +endfunction + +function! genutils#RestoreVisualSelection(id) + if mode() !=# 'n' + exec "normal \" + endif + if exists('s:vismode{id}') + exec 'normal' s:firstline{a:id}.'gg'.s:firstcol{a:id}.'|'. + \ s:vismode{a:id}.(s:lastline{a:id}-s:firstline{a:id}).'j'. + \ (s:lastcol{a:id}-s:firstcol{a:id}).'l' + endif +endfunction +" Save/Restore selection }}} + +function! genutils#CleanupFileName(fileName) + return genutils#CleanupFileName2(a:fileName, '') +endfunction + +function! genutils#CleanupFileName2(fileName, win32ProtectedChars) + let fileName = substitute(a:fileName, '^\s\+\|\s\+$', '', 'g') + + " Expand relative paths and paths containing relative components (takes care + " of ~ also). + if ! genutils#PathIsAbsolute(fileName) + let fileName = fnamemodify(fileName, ':p') + endif + + " I think we can have UNC paths on UNIX, if samba is installed. + if genutils#OnMS() && (match(fileName, '^//') == 0 || + \ match(fileName, '^\\\\') == 0) + let uncPath = 1 + else + let uncPath = 0 + endif + + " Remove multiple path separators. + if has('win32') + if a:win32ProtectedChars != '' + let fileName=substitute(fileName, '\\['.a:win32ProtectedChars.']\@!', '/', + \ 'g') + else + let fileName=substitute(fileName, '\\', '/', 'g') + endif + elseif genutils#OnMS() + " On non-win32 systems, the forward-slash is not supported, so leave + " back-slash. + let fileName=substitute(fileName, '\\\{2,}', '\', 'g') + endif + let fileName=substitute(fileName, '/\{2,}', '/', 'g') + + " Remove ending extra path separators. + let fileName=substitute(fileName, '/$', '', '') + let fileName=substitute(fileName, '\\$', '', '') + + " If it was an UNC path, add back an extra slash. + if uncPath + let fileName = '/'.fileName + endif + + if genutils#OnMS() + let fileName=substitute(fileName, '^[A-Z]:', '\L&', '') + + " Add drive letter if missing (just in case). + if !uncPath && match(fileName, '^/') == 0 + let curDrive = substitute(getcwd(), '^\([a-zA-Z]:\).*$', '\L\1', '') + let fileName = curDrive . fileName + endif + endif + return fileName +endfunction +"echo genutils#CleanupFileName('\\a///b/c\') +"echo genutils#CleanupFileName('C:\a/b/c\d') +"echo genutils#CleanupFileName('a/b/c\d') +"echo genutils#CleanupFileName('~/a/b/c\d') +"echo genutils#CleanupFileName('~/a/b/../c\d') + +function! genutils#OnMS() + return has('win32') || has('dos32') || has('win16') || has('dos16') || + \ has('win95') +endfunction + +function! genutils#PathIsAbsolute(path) + let absolute=0 + if has('unix') || genutils#OnMS() + if match(a:path, '^/') == 0 + let absolute=1 + endif + endif + if (! absolute) && genutils#OnMS() + if match(a:path, "^\\") == 0 + let absolute=1 + endif + endif + if (! absolute) && genutils#OnMS() + if match(a:path, "^[A-Za-z]:") == 0 + let absolute=1 + endif + endif + return absolute +endfunction + +function! genutils#PathIsFileNameOnly(path) + return (match(a:path, "\\") < 0) && (match(a:path, "/") < 0) +endfunction + +let s:mySNR = '' +function! s:SNR() + if s:mySNR == '' + let s:mySNR = matchstr(expand(''), '\d\+_\zeSNR$') + endif + return s:mySNR +endfun + + +"" --- START save/restore position. {{{ + +function! genutils#SaveSoftPosition(id) + let b:sp_startline_{a:id} = getline(".") + call genutils#SaveHardPosition(a:id) +endfunction + +function! genutils#RestoreSoftPosition(id) + 0 + call genutils#RestoreHardPosition(a:id) + let stLine = b:sp_startline_{a:id} + if getline('.') !=# stLine + if ! search('\V\^'.escape(stLine, "\\").'\$', 'W') + call search('\V\^'.escape(stLine, "\\").'\$', 'bW') + endif + endif + call genutils#MoveCurLineToWinLine(b:sp_winline_{a:id}) +endfunction + +function! genutils#ResetSoftPosition(id) + unlet b:sp_startline_{a:id} +endfunction + +" A synonym for genutils#SaveSoftPosition. +function! genutils#SaveHardPositionWithContext(id) + call genutils#SaveSoftPosition(a:id) +endfunction + +" A synonym for genutils#RestoreSoftPosition. +function! genutils#RestoreHardPositionWithContext(id) + call genutils#RestoreSoftPosition(a:id) +endfunction + +" A synonym for genutils#ResetSoftPosition. +function! genutils#ResetHardPositionWithContext(id) + call genutils#ResetSoftPosition(a:id) +endfunction + +function! genutils#SaveHardPosition(id) + let b:sp_col_{a:id} = virtcol(".") + let b:sp_lin_{a:id} = line(".") + " Avoid accounting for wrapped lines. + let _wrap = &l:wrap + try + setl nowrap + let b:sp_winline_{a:id} = winline() + finally + let &l:wrap = _wrap + endtry +endfunction + +function! genutils#RestoreHardPosition(id) + " This doesn't take virtual column. + "call cursor(b:sp_lin_{a:id}, b:sp_col_{a:id}) + " Vim7 generates E16 if line number is invalid. + " TODO: Why is this leaving cursor on the last-but-one line when the + " condition meets? + execute ((line('$') < b:sp_lin_{a:id}) ? line('$') : + \ b:sp_lin_{a:id}) + "execute b:sp_lin_{a:id} + execute ((line('$') < b:sp_lin_{a:id}) ? line('$') : + \ b:sp_lin_{a:id}) + "execute b:sp_lin_{a:id} + execute "normal!" b:sp_col_{a:id} . "|" + call genutils#MoveCurLineToWinLine(b:sp_winline_{a:id}) +endfunction + +function! genutils#ResetHardPosition(id) + unlet b:sp_col_{a:id} + unlet b:sp_lin_{a:id} + unlet b:sp_winline_{a:id} +endfunction + +function! genutils#GetLinePosition(id) + return b:sp_lin_{a:id} +endfunction + +function! genutils#GetColPosition(id) + return b:sp_col_{a:id} +endfunction + +function! genutils#IsPositionSet(id) + return exists('b:sp_col_' . a:id) +endfunction + +"" --- END save/restore position. }}} + + + +"" +"" --- START: Notify window close -- {{{ +"" + +let s:notifyWindow = {} +function! genutils#AddNotifyWindowClose(windowTitle, functionName) + let bufName = a:windowTitle + + let s:notifyWindow[bufName] = a:functionName + + "let g:notifyWindow = s:notifyWindow " Debug. + + " Start listening to events. + aug NotifyWindowClose + au! + au WinEnter * :call genutils#CheckWindowClose() + au BufEnter * :call genutils#CheckWindowClose() + aug END +endfunction + +function! genutils#RemoveNotifyWindowClose(windowTitle) + let bufName = a:windowTitle + + if has_key(s:notifyWindow, bufName) + call remove(s:notifyWindow, bufName) + if len(s:notifyWindow) == 0 + "unlet g:notifyWindow " Debug. + + aug NotifyWindowClose + au! + aug END + endif + endif +endfunction + +function! genutils#CheckWindowClose() + if !exists('s:notifyWindow') + return + endif + + " Now iterate over all the registered window titles and see which one's are + " closed. + for nextWin in keys(s:notifyWindow) + if bufwinnr(s:FindBufferForName(nextWin)) == -1 + let funcName = s:notifyWindow[nextWin] + " Remove this entry as these are going to be processed. The caller can add + " them back if needed. + unlet s:notifyWindow[nextWin] + "call input("cmd: " . cmd) + call call(funcName, [nextWin]) + endif + endfor +endfunction + +"function! NotifyWindowCloseF(title) +" call input(a:title . " closed") +"endfunction +" +"function! RunNotifyWindowCloseTest() +" call input("Creating three windows, 'ABC', 'XYZ' and 'b':") +" split ABC +" split X Y Z +" split b +" redraw +" call genutils#AddNotifyWindowClose("ABC", "NotifyWindowCloseF") +" call genutils#AddNotifyWindowClose("X Y Z", "NotifyWindowCloseF") +" call input("notifyWindow: " . string(s:notifyWindow)) +" au NotifyWindowClose WinEnter +" call input("Added notifications for 'ABC' and 'XYZ'\n". +" \ "Now closing the windows, you should see ONLY two notifications:") +" quit +" quit +" quit +"endfunction + +"" +"" --- END: Notify window close -- }}} +"" + +" TODO: For large ranges, the cmd can become too big, so make it one cmd per +" line. +function! genutils#ShowLinesWithSyntax() range + " This makes sure we start (subsequent) echo's on the first line in the + " command-line area. + " + echo '' + + let cmd = '' + let prev_group = ' x ' " Something that won't match any syntax group. + + let show_line = a:firstline + let isMultiLine = ((a:lastline - a:firstline) > 1) + while show_line <= a:lastline + let cmd = '' + let length = strlen(getline(show_line)) + let column = 1 + + while column <= length + let group = synIDattr(synID(show_line, column, 1), 'name') + if group != prev_group + if cmd != '' + let cmd = cmd . "'|" + endif + let cmd = cmd . 'echohl ' . (group == '' ? 'NONE' : group) . "|echon '" + let prev_group = group + endif + let char = strpart(getline(show_line), column - 1, 1) + if char == "'" + let char = "'."'".'" + endif + let cmd = cmd . char + let column = column + 1 + endwhile + + try + exec cmd."\n'" + catch + echo '' + endtry + let show_line = show_line + 1 + endwhile + echohl NONE +endfunction + + +function! genutils#AlignWordWithWordInPreviousLine() + "First go to the first col in the word. + if getline('.')[col('.') - 1] =~ '\s' + normal! w + else + normal! "_yiw + endif + let orgVcol = virtcol('.') + let prevLnum = prevnonblank(line('.') - 1) + if prevLnum == -1 + return + endif + let prevLine = getline(prevLnum) + + " First get the column to align with. + if prevLine[orgVcol - 1] =~ '\s' + " column starts from 1 where as index starts from 0. + let nonSpaceStrInd = orgVcol " column starts from 1 where as index starts from 0. + while prevLine[nonSpaceStrInd] =~ '\s' + let nonSpaceStrInd = nonSpaceStrInd + 1 + endwhile + else + if strlen(prevLine) < orgVcol + let nonSpaceStrInd = strlen(prevLine) - 1 + else + let nonSpaceStrInd = orgVcol - 1 + endif + + while prevLine[nonSpaceStrInd - 1] !~ '\s' && nonSpaceStrInd > 0 + let nonSpaceStrInd = nonSpaceStrInd - 1 + endwhile + endif + let newVcol = nonSpaceStrInd + 1 " Convert to column number. + + if orgVcol > newVcol " We need to reduce the spacing. + let sub = strpart(getline('.'), newVcol - 1, (orgVcol - newVcol)) + if sub =~ '^\s\+$' + " Remove the excess space. + exec 'normal! ' . newVcol . '|' + exec 'normal! ' . (orgVcol - newVcol) . 'x' + endif + elseif orgVcol < newVcol " We need to insert spacing. + exec 'normal! ' . orgVcol . '|' + exec 'normal! ' . (newVcol - orgVcol) . 'i ' + endif +endfunction + +function! genutils#ShiftWordInSpace(dir) + if a:dir == 1 " forward. + " If currently on ... + if getline(".")[col(".") - 1] == " " + let move1 = 'wf ' + else + " If next col is a + "if getline(".")[col(".") + 1] + let move1 = 'f ' + endif + let removeCommand = "x" + let pasteCommand = "bi " + let move2 = 'w' + let offset = 0 + else " backward. + " If currently on ... + if getline(".")[col(".") - 1] == " " + let move1 = 'w' + else + let move1 = '"_yiW' + endif + let removeCommand = "hx" + let pasteCommand = 'h"_yiwEa ' + let move2 = 'b' + let offset = -3 + endif + + let savedCol = col(".") + exec "normal!" move1 + let curCol = col(".") + let possible = 0 + " Check if there is a space at the end. + if col("$") == (curCol + 1) " Works only for forward case, as expected. + let possible = 1 + elseif getline(".")[curCol + offset] == " " + " Remove the space from here. + exec "normal!" removeCommand + let possible = 1 + endif + + " Move back into the word. + "exec "normal!" savedCol . "|" + if possible == 1 + exec "normal!" pasteCommand + exec "normal!" move2 + else + " Move to the original location. + exec "normal!" savedCol . "|" + endif +endfunction + + +function! genutils#CenterWordInSpace() + let line = getline('.') + let orgCol = col('.') + " If currently on ... + if line[orgCol - 1] == " " + let matchExpr = ' *\%'. orgCol . 'c *\w\+ \+' + else + let matchExpr = ' \+\(\w*\%' . orgCol . 'c\w*\) \+' + endif + let matchInd = match(line, matchExpr) + if matchInd == -1 + return + endif + let matchStr = matchstr(line, matchExpr) + let nSpaces = strlen(substitute(matchStr, '[^ ]', '', 'g')) + let word = substitute(matchStr, ' ', '', 'g') + let middle = nSpaces / 2 + let left = nSpaces - middle + let newStr = '' + while middle > 0 + let newStr = newStr . ' ' + let middle = middle - 1 + endwhile + let newStr = newStr . word + while left > 0 + let newStr = newStr . ' ' + let left = left - 1 + endwhile + + let newLine = strpart(line, 0, matchInd) + let newLine = newLine . newStr + let newLine = newLine . strpart (line, matchInd + strlen(matchStr)) + silent! keepjumps call setline(line('.'), newLine) +endfunction + +function! genutils#MapAppendCascaded(lhs, rhs, mapMode) + + " Determine the map mode from the map command. + let mapChar = strpart(a:mapMode, 0, 1) + + " Check if this is already mapped. + let oldrhs = maparg(a:lhs, mapChar) + if oldrhs != "" + let self = oldrhs + else + let self = a:lhs + endif + "echomsg a:mapMode . "oremap" . " " . a:lhs . " " . self . a:rhs + exec a:mapMode . "oremap" a:lhs self . a:rhs +endfunction + +" smartSlash simply says whether to depend on shellslash and ArgLead for +" determining path separator. If it shouldn't depend, it will always assume +" that the required pathsep is forward-slash. +function! genutils#UserFileComplete(ArgLead, CmdLine, CursorPos, smartSlash, + \ searchPath) + let glob = '' + let opathsep = "\\" + let npathsep = '/' + if exists('+shellslash') && ! &shellslash && a:smartSlash && + \ stridx(a:ArgLead, "\\") != -1 + let opathsep = '/' + let npathsep = "\\" + endif + if a:searchPath !=# '' + for nextPath in split(a:searchPath, genutils#CrUnProtectedCharsPattern(',')) + let nextPath = genutils#CleanupFileName(nextPath) + let matches = glob(nextPath.'/'.a:ArgLead.'*') + if matches !~# '^\_s*$' + let matches = s:FixPathSep(matches, opathsep, npathsep) + let nextPath = substitute(nextPath, opathsep, npathsep, 'g') + let matches = substitute(matches, '\V'.escape(nextPath.npathsep, "\\"), + \ '', 'g') + let glob = glob . matches . "\n" + endif + endfor + else + let glob = s:FixPathSep(glob(a:ArgLead.'*'), opathsep, npathsep) + endif + " FIXME: Need an option to control if ArgLead should also be returned or + " not. + return glob."\n".a:ArgLead +endfunction + +command! -complete=file -nargs=* GUDebugEcho :echo +function! genutils#UserFileExpand(fileArgs) + return substitute(genutils#GetVimCmdOutput( + \ 'GUDebugEcho ' . a:fileArgs), '^\_s\+\|\_s\+$', '', 'g') +endfunction + +function! s:FixPathSep(matches, opathsep, npathsep) + let matches = a:matches + let matches = substitute(matches, a:opathsep, a:npathsep, 'g') + let matches = substitute(matches, "\\([^\n]\\+\\)", '\=submatch(1).'. + \ '(isdirectory(submatch(1)) ? a:npathsep : "")', 'g') + return matches +endfunction + +function! genutils#GetVimCmdOutput(cmd) + let v:errmsg = '' + let output = '' + let _z = @z + let _shortmess = &shortmess + try + set shortmess= + redir @z + silent exec a:cmd + catch /.*/ + let v:errmsg = substitute(v:exception, '^[^:]\+:', '', '') + finally + redir END + let &shortmess = _shortmess + if v:errmsg == '' + let output = @z + endif + let @z = _z + endtry + return output +endfunction + +function! genutils#OptClearBuffer() + " Go as far as possible in the undo history to conserve Vim resources. + let _modifiable = &l:modifiable + let _undolevels = &undolevels + try + setl modifiable + set undolevels=-1 + silent! keepjumps 0,$delete _ + finally + let &undolevels = _undolevels + let &l:modifiable = _modifiable + endtry +endfunction + + +"" START: Sorting support. {{{ +"" + +" +" Comapare functions. +" + +function! genutils#CmpByLineLengthNname(line1, line2, ...) + let direction = (a:0?a:1:1) + let cmp = genutils#CmpByLength(a:line1, a:line2, direction) + if cmp == 0 + let cmp = genutils#CmpByString(a:line1, a:line2, direction) + endif + return cmp +endfunction + +function! genutils#CmpByLength(line1, line2, ...) + let direction = (a:0?a:1:1) + let len1 = strlen(a:line1) + let len2 = strlen(a:line2) + return direction * (len1 - len2) +endfunction + +" Compare first by name and then by length. +" Useful for sorting Java imports. +function! genutils#CmpJavaImports(line1, line2, ...) + let direction = (a:0?a:1:1) + " FIXME: Simplify this. + if stridx(a:line1, '.') == -1 + let pkg1 = '' + let cls1 = substitute(a:line1, '.* \(^[ ]\+\)', '\1', '') + else + let pkg1 = substitute(a:line1, '.*import\s\+\(.*\)\.[^. ;]\+.*$', '\1', '') + let cls1 = substitute(a:line1, '^.*\.\([^. ;]\+\).*$', '\1', '') + endif + if stridx(a:line2, '.') == -1 + let pkg2 = '' + let cls2 = substitute(a:line2, '.* \(^[ ]\+\)', '\1', '') + else + let pkg2 = substitute(a:line2, '.*import\s\+\(.*\)\.[^. ;]\+.*$', '\1', '') + let cls2 = substitute(a:line2, '^.*\.\([^. ;]\+\).*$', '\1', '') + endif + + let cmp = genutils#CmpByString(pkg1, pkg2, direction) + if cmp == 0 + let cmp = genutils#CmpByLength(cls1, cls2, direction) + endif + return cmp +endfunction + +function! genutils#CmpByString(line1, line2, ...) + let direction = (a:0?a:1:1) + if a:line1 < a:line2 + return -direction + elseif a:line1 > a:line2 + return direction + else + return 0 + endif +endfunction + +function! genutils#CmpByStringIgnoreCase(line1, line2, ...) + let direction = (a:0?a:1:1) + if a:line1 ? a:line2 + return direction + else + return 0 + endif +endfunction + +function! genutils#CmpByNumber(line1, line2, ...) + let direction = (a:0 ? a:1 :1) + let num1 = a:line1 + 0 + let num2 = a:line2 + 0 + + if num1 < num2 + return -direction + elseif num1 > num2 + return direction + else + return 0 + endif +endfunction + +function! genutils#QSort(cmp, direction) range + call s:QSortR(a:firstline, a:lastline, a:cmp, a:direction, + \ 's:BufLineAccessor', 's:BufLineSwapper', '') +endfunction + +function! genutils#QSort2(start, end, cmp, direction, accessor, swapper, context) + call s:QSortR(a:start, a:end, a:cmp, a:direction, a:accessor, a:swapper, + \ a:context) +endfunction + +" The default swapper that swaps lines in the current buffer. +function! s:BufLineSwapper(line1, line2, context) + let str2 = getline(a:line1) + silent! keepjumps call setline(a:line1, getline(a:line2)) + silent! keepjumps call setline(a:line2, str2) +endfunction + +" The default accessor that returns lines from the current buffer. +function! s:BufLineAccessor(line, context) + return getline(a:line) +endfunction + +" The default mover that moves lines from one place to another in the current +" buffer. +function! s:BufLineMover(from, to, context) + let line = getline(a:from) + exec a:from.'d_' + call append(a:to, line) +endfunction + +" +" Sort lines. QSortR() is called recursively. +" +function! s:QSortR(start, end, cmp, direction, accessor, swapper, context) + if a:end > a:start + let low = a:start + let high = a:end + + " Arbitrarily establish partition element at the midpoint of the data. + let midStr = {a:accessor}(((a:start + a:end) / 2), a:context) + + " Loop through the data until indices cross. + while low <= high + + " Find the first element that is greater than or equal to the partition + " element starting from the left Index. + while low < a:end + let result = {a:cmp}({a:accessor}(low, a:context), midStr, a:direction) + if result < 0 + let low = low + 1 + else + break + endif + endwhile + + " Find an element that is smaller than or equal to the partition element + " starting from the right Index. + while high > a:start + let result = {a:cmp}({a:accessor}(high, a:context), midStr, a:direction) + if result > 0 + let high = high - 1 + else + break + endif + endwhile + + " If the indexes have not crossed, swap. + if low <= high + " Swap lines low and high. + call {a:swapper}(high, low, a:context) + let low = low + 1 + let high = high - 1 + endif + endwhile + + " If the right index has not reached the left side of data must now sort + " the left partition. + if a:start < high + call s:QSortR(a:start, high, a:cmp, a:direction, a:accessor, a:swapper, + \ a:context) + endif + + " If the left index has not reached the right side of data must now sort + " the right partition. + if low < a:end + call s:QSortR(low, a:end, a:cmp, a:direction, a:accessor, a:swapper, + \ a:context) + endif + endif +endfunction + +function! genutils#BinSearchForInsert(start, end, line, cmp, direction) + return genutils#BinSearchForInsert2(a:start, a:end, a:line, a:cmp, + \ a:direction, 's:BufLineAccessor', '') +endfunction + +function! genutils#BinSearchForInsert2(start, end, line, cmp, direction, + \ accessor, context) + let start = a:start - 1 + let end = a:end + while start < end + let middle = (start + end + 1) / 2 + " Support passing both Funcref's as well as names. + if type(a:cmp) == 2 + if type(a:accessor) == 2 + let result = a:cmp(a:accessor(middle, a:context), a:line, a:direction) + else + let result = a:cmp({a:accessor}(middle, a:context), a:line, a:direction) + endif + else + if type(a:accessor) == 2 + let result = {a:cmp}(a:accessor(middle, a:context), a:line, a:direction) + else + let result = {a:cmp}({a:accessor}(middle, a:context), a:line, a:direction) + endif + endif + if result < 0 + let start = middle + else + let end = middle - 1 + endif + endwhile + return start +endfunction + +function! genutils#BinSearchList(list, start, end, item, cmp) + let start = a:start - 1 + let end = a:end + while start < end + let middle = (start + end + 1) / 2 + let result = call(a:cmp, [get(a:list, middle), a:item]) + if result < 0 + let start = middle + else + let end = middle - 1 + endif + endwhile + return start +endfunction + +function! genutils#BinInsertSort(cmp, direction) range + call genutils#BinInsertSort2(a:firstline, a:lastline, a:cmp, a:direction, + \ 's:BufLineAccessor', 's:BufLineMover', '') +endfunction + +function! genutils#BinInsertSort2(start, end, cmp, direction, accessor, mover, context) + let i = a:start + 1 + while i <= a:end + let low = s:BinSearchToAppend2(a:start, i, {a:accessor}(i, a:context), + \ a:cmp, a:direction, a:accessor, a:context) + " Move the object. + if low < i + call {a:mover}(i, low - 1, a:context) + endif + let i = i + 1 + endwhile +endfunction + +function! s:BinSearchToAppend(start, end, line, cmp, direction) + return s:BinSearchToAppend2(a:start, a:end, a:line, a:cmp, a:direction, + \ 's:BufLineAccessor', '') +endfunction + +function! s:BinSearchToAppend2(start, end, line, cmp, direction, accessor, + \ context) + let low = a:start + let high = a:end + while low < high + let mid = (low + high) / 2 + let diff = {a:cmp}({a:accessor}(mid, a:context), a:line, a:direction) + if diff > 0 + let high = mid + else + let low = mid + 1 + if diff == 0 + break + endif + endif + endwhile + return low +endfunction + +""" END: Sorting support. }}} + + +" Eats character if it matches the given pattern. +" +" Originally, +" From: Benji Fisher +" Date: Mon, 25 Mar 2002 15:05:14 -0500 +" +" Based on Bram's idea of eating a character while type to expand an +" abbreviation. This solves the problem with abbreviations, where we are +" left with an extra space after the expansion. +" Ex: +" inoreabbr \stdout\ System.out.println("");=genutils#EatChar('\s') +function! genutils#EatChar(pat) + let c = nr2char(getchar()) + "call input('Pattern: '.a:pat.' '. + " \ ((c =~ a:pat) ? 'Returning empty' : 'Returning: '.char2nr(c))) + return (c =~ a:pat) ? '' : c +endfun + + +" Can return a spacer from 0 to 80 characters width. +let s:spacer= " ". + \ " " +function! genutils#GetSpacer(width) + return strpart(s:spacer, 0, a:width) +endfunction + +function! genutils#SilentSubstitute(pat, cmd) + call genutils#SaveHardPosition('SilentSubstitute') + let _search = @/ + try + let @/ = a:pat + keepjumps silent! exec a:cmd + finally + let @/ = _search + call genutils#RestoreHardPosition('SilentSubstitute') + call genutils#ResetHardPosition('SilentSubstitute') + endtry +endfunction + +function! genutils#SilentDelete(arg1, ...) + " For backwards compatibility. + if a:0 + let range = a:arg1 + let pat = a:1 + else + let range = '' + let pat = a:arg1 + endif + let _search = @/ + call genutils#SaveHardPosition('SilentDelete') + try + let @/ = pat + keepjumps silent! exec range'g//d _' + finally + let @/ = _search + call genutils#RestoreHardPosition('SilentDelete') + call genutils#ResetHardPosition('SilentDelete') + endtry +endfunction + +" START: genutils#Roman2Decimal {{{ +let s:I = 1 +let s:V = 5 +let s:X = 10 +let s:L = 50 +let s:C = 100 +let s:D = 500 +let s:M = 1000 + +function! s:Char2Num(c) + " A bit of magic on empty strings + if a:c == "" + return 0 + endif + exec 'let n = s:' . toupper(a:c) + return n +endfun + +function! genutils#Roman2Decimal(str) + if a:str !~? '^[IVXLCDM]\+$' + return a:str + endif + let sum = 0 + let i = 0 + let n0 = s:Char2Num(a:str[i]) + let len = strlen(a:str) + while i < len + let i = i + 1 + let n1 = s:Char2Num(a:str[i]) + " Magic: n1=0 when i exceeds len + if n1 > n0 + let sum = sum - n0 + else + let sum = sum + n0 + endif + let n0 = n1 + endwhile + return sum +endfun +" END: genutils#Roman2Decimal }}} + + +" BEGIN: Relative path {{{ +function! genutils#CommonPath(path1, path2, ...) + let path1 = genutils#CleanupFileName(a:path1) . ((a:0 > 0 ? a:1 : 0) ? '/' : '') + let path2 = genutils#CleanupFileName(a:path2) . ((a:0 > 1 ? a:2 : 0) ? '/' : '') + return genutils#CommonString(path1, path2) +endfunction + +function! genutils#CommonString(str1, str2) + let str1 = a:str1 + let str2 = a:str2 + if str1 == str2 + return str1 + endif + let n = 0 + while str1[n] == str2[n] + let n = n+1 + endwhile + return strpart(str1, 0, n) +endfunction + +function! genutils#RelPathFromFile(srcFile, tgtFile) + return genutils#RelPathFromDir(fnamemodify(a:srcFile, ':h'), a:tgtFile) +endfunction + +function! genutils#RelPathFromDir(srcDir, tgtFile) + let cleanDir = genutils#CleanupFileName(a:srcDir).'/' + let cleanFile = genutils#CleanupFileName(a:tgtFile) + let cmnPath = genutils#CommonPath(cleanDir, cleanFile, 1) + let relDirFromCmnPath = strpart(cleanDir, strlen(cmnPath)) + if relDirFromCmnPath == '/' " This means the file is under the srcDir. + let relDirFromCmnPath = '' + endif + let relFileFromCmnPath = strpart(cleanFile, strlen(cmnPath)) + return substitute(relDirFromCmnPath, '[^/]\+', '..', 'g') . + \ relFileFromCmnPath +endfunction + +" END: Relative path }}} + + +" BEGIN: Persistent settings {{{ +if ! exists("g:genutilsNoPersist") || ! g:genutilsNoPersist + " Make sure the '!' option to store global variables that are upper cased are + " stored in viminfo file. + " Make sure it is the first option, so that it will not interfere with the + " 'n' option ("Todd J. Cosgrove" (todd dot cosgrove at softechnics dot + " com)). + " Also take care of empty value, when 'compatible' mode is on (Bram + " Moolenar) + if &viminfo == '' + set viminfo=!,'20,"50,h + else + set viminfo^=! + endif +endif + +function! genutils#PutPersistentVar(pluginName, persistentVar, value) + if ! exists("g:genutilsNoPersist") || ! g:genutilsNoPersist + let globalVarName = s:PersistentVarName(a:pluginName, a:persistentVar) + exec 'let ' . globalVarName . " = '" . a:value . "'" + endif +endfunction + +function! genutils#GetPersistentVar(pluginName, persistentVar, default) + if ! exists("g:genutilsNoPersist") || ! g:genutilsNoPersist + let globalVarName = s:PersistentVarName(a:pluginName, a:persistentVar) + if (exists(globalVarName)) + exec 'let value = ' . globalVarName + exec 'unlet ' . globalVarName + else + let value = a:default + endif + return value + else + return default + endif +endfunction + +function! s:PersistentVarName(pluginName, persistentVar) + return 'g:GU_' . toupper(a:pluginName) . '_' . toupper(a:persistentVar) +endfunction +" END: Persistent settings }}} + + +" FileChangedShell handling {{{ +if !exists('s:fcShellPreFuncs') + let s:fcShellPreFuncs = {} +endif + +function! genutils#AddToFCShellPre(funcName) + let s:fcShellPreFuncs[a:funcName] = a:funcName +endfunction + +function! genutils#RemoveFromFCShellPre(funcName) + if has_key(s:fcShellPreFuncs, a:funcName) + unlet s:fcShellPreFuncs[a:funcName] + endif +endfunction + +let s:defFCShellInstalled = 0 +function! genutils#DefFCShellInstall() + if ! s:defFCShellInstalled + aug DefFCShell + au! + au FileChangedShell * nested call genutils#DefFileChangedShell() + aug END + endif + let s:defFCShellInstalled = s:defFCShellInstalled + 1 +endfunction + +function! genutils#DefFCShellUninstall() + if s:defFCShellInstalled <= 0 + return + endif + let s:defFCShellInstalled = s:defFCShellInstalled - 1 + if ! s:defFCShellInstalled + aug DefFCShell + au! + aug END + endif +endfunction + +function! genutils#DefFileChangedShell() + let autoread = s:InvokeFuncs(s:fcShellPreFuncs) + let bufNo = expand('') + 0 + let fName = expand('') + let msg = '' + let v:fcs_choice = '' + if getbufvar(bufNo, '&modified') + let v:fcs_choice = 'ask' + elseif ! autoread + let v:fcs_choice = 'ask' + else + let v:fcs_choice = 'reload' + endif + return +endfunction + +function! s:InvokeFuncs(funcList) + let autoread = &autoread + if len(a:funcList) != 0 + for nextFunc in keys(a:funcList) + let result = call(nextFunc, []) + if result != -1 + let autoread = autoread || result + endif + endfor + endif + return autoread +endfunction +" FileChangedShell handling }}} + + +" Sign related utilities {{{ +function! genutils#CurLineHasSign() + let signs = genutils#GetVimCmdOutput('sign place buffer=' . bufnr('%'), 1) + return (match(signs, + \ 'line=' . line('.') . '\s\+id=\d\+\s\+name=VimBreakPt') != -1) +endfunction + +function! genutils#ClearAllSigns() + let signs = genutils#GetVimCmdOutput('sign place buffer=' . bufnr('%'), 1) + let curIdx = 0 + let pat = 'line=\d\+\s\+id=\zs\d\+\ze\s\+name=VimBreakPt' + let id = 0 + while curIdx != -1 && curIdx < strlen(signs) + let id = matchstr(signs, pat, curIdx) + if id != '' + exec 'sign unplace ' . id . ' buffer=' . bufnr('%') + endif + let curIdx = matchend(signs, pat, curIdx) + endwhile +endfunction +" }}} + +let s:UNPROTECTED_CHAR_PRFX = '\%(\%([^\\]\|^\)\\\%(\\\\\)*\)\@ 0?1:0) + let regex = s:UNPROTECTED_CHAR_PRFX + let chars = a:chars + if strlen(chars) > 1 + let chars = '['.chars.']' + endif + if capture + let chars = '\('.chars.'\)' + endif + return regex.chars +endfunction + +function! genutils#PromptForElement(array, default, msg, skip, useDialog, + \ nCols) + let nCols = a:nCols + let index = 0 + let line = "" + let element = "" + let optionsMsg = "" + let colWidth = &columns / nCols - 1 " Leave a margin of one column as a gap. + let curCol = 1 + let nElements = len(a:array) + let newArray = [] " Without the skip element. + if index(a:array, a:skip) != -1 + let nElements = nElements - 1 + endif + for element in a:array + if element ==# a:skip + continue + endif + call add(newArray, element) + let element = strpart(index." ", 0, 4) . element + let eleColWidth = (strlen(element) - 1) / colWidth + 1 + " Fill up the spacer for the rest of the partial column. + let element = element . genutils#GetSpacer( + \ eleColWidth * (colWidth + 1) - strlen(element) - 1) + let wouldBeLength = strlen(line) + strlen(element) + 1 + if wouldBeLength > (curCol * (colWidth + eleColWidth)) && + \ wouldBeLength > &columns + let splitLine = 2 " Split before adding the new element. + elseif curCol == nCols + let splitLine = 1 " Split after adding the new element. + else + let splitLine = 0 + endif + if splitLine == 2 + if strlen(line) == &columns + " Remove the last space as it otherwise results in an extra empty line + " on the screen. + let line = strpart(line, 0, strlen(line) - 1) + endif + let optionsMsg = optionsMsg . line . "\n" + let line = element . ' ' + let curCol = strlen(element) / (colWidth + 1) + else + let line = line . element . ' ' + if splitLine == 1 + if strlen(line) == &columns + " Remove the last space as it otherwise results in an extra empty line + " on the screen. + let line = strpart(line, 0, strlen(line) - 1) + endif + let curCol = 0 " Reset col count. + let optionsMsg = optionsMsg . line . "\n" + let line = "" + endif + endif + let curCol = curCol + 1 + let index = index + 1 + endfor + " Finally if there is anything left in line, then append that too. + if line.'' != '' + let optionsMsg = optionsMsg . line . "\n" + let line = "" + endif + + " Default index or empty string. + let default = '' + if type(a:default) == 0 + let default = a:default + elseif a:default.'' != '' + let default = index(a:array, a:default) + endif + if a:default == -1 + let default = '' + endif + + while !exists("selectedElement") + if a:useDialog + let s:selection = inputdialog(optionsMsg . a:msg, default) + else + let s:selection = input(optionsMsg . a:msg, default) + endif + if s:selection.'' == '' + let selectedElement = '' + let s:selection = -1 + else + let s:selection = (s:selection !~# '^\d\+$') ? -1 : (s:selection + 0) + if s:selection >= 0 && s:selection < nElements + let selectedElement = newArray[s:selection] + else + echohl ERROR | echo "\nInvalid selection, please try again" | + \ echohl NONE + endif + endif + echo "\n" + endwhile + return selectedElement +endfunction + +let s:selection = -1 +function! genutils#GetSelectedIndex() + return s:selection +endfunction + +" Always match() with 'ignorecase' and 'smartcase' off. +function! s:Match(expr, pat, start) + let _ic = &ignorecase + let _scs = &smartcase + let result = -1 + try + set noignorecase + set nosmartcase + let result = match(a:expr, a:pat, a:start) + finally + let &ignorecase = _ic + let &smartcase = _scs + endtry + return result +endfunction + +" Restore cpo. +let &cpo = s:save_cpo +unlet s:save_cpo + +" vim6:fdm=marker et