--- /dev/null
+" 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, '\<argumentString\>', 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, '\<argumentList\>', 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 . "\<C-Y>"
+ 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, '\\\@<!\(\%(\\\\\)*\)\([' . a:chars .']\)', '\1\\\2',
+ \ 'g')
+endfunction
+
+function! genutils#UnEscape(str, chars)
+ return substitute(a:str, '\\\@<!\(\\\\\)*\\\([' . a:chars . ']\)',
+ \ '\1\2', 'g')
+endfunction
+
+function! genutils#DeEscape(str)
+ let str = a:str
+ let str = substitute(str, '\\\(\\\|[^\\]\)', '\1', 'g')
+ return str
+endfunction
+
+" - For windoze+native, use double-quotes to sorround the arguments and for
+" embedded double-quotes, just double them.
+" - For windoze+sh, use single-quotes to sorround the aruments and for embedded
+" single-quotes, just replace them with '""'""' (if 'shq' or 'sxq' is a
+" double-quote) and just '"'"' otherwise. Embedded double-quotes also need
+" to be doubled.
+" - For Unix+sh, use single-quotes to sorround the arguments and for embedded
+" single-quotes, just replace them with '"'"'.
+function! genutils#EscapeCommand(cmd, args, pipe)
+ if type(a:args) == 3
+ let args = copy(a:args)
+ else
+ let args = split(a:args, genutils#CrUnProtectedCharsPattern(' '))
+ endif
+ " I am only worried about passing arguments with spaces as they are to the
+ " external commands, I currently don't care about back-slashes
+ " (backslashes are normally expected only on windows when 'shellslash'
+ " option is set, but even then the 'shell' is expected to take care of
+ " them.). However, for cygwin bash, there is a loss of one level
+ " of the back-slashes somewhere in the chain of execution (most probably
+ " between CreateProcess() and cygwin?), so we need to double them.
+ let shellEnvType = genutils#GetShellEnvType()
+ if shellEnvType ==# g:ST_WIN_CMD
+ let quoteChar = '"'
+ " genutils#Escape the existing double-quotes (by doubling them).
+ call map(args, "substitute(v:val, '\"', '\"\"', 'g')")
+ else
+ let quoteChar = "'"
+ if shellEnvType ==# g:ST_WIN_SH
+ " genutils#Escape the existing double-quotes (by doubling them).
+ call map(args, "substitute(v:val, '\"', '\"\"', 'g')")
+ endif
+ " Take care of existing single-quotes (by exposing them, as you can't have
+ " single-quotes inside a single-quoted string).
+ if &shellquote == '"' || &shellxquote == '"'
+ let squoteRepl = "'\"\"'\"\"'"
+ else
+ let squoteRepl = "'\"'\"'"
+ endif
+ call map(args, "substitute(v:val, \"'\", squoteRepl, 'g')")
+ endif
+
+ " Now sorround the arguments with quotes, considering the protected
+ " spaces. Skip the && or || construct from doing this.
+ call map(args, 'v:val=~"^[&|]\\{2}$"?(v:val):(quoteChar.v:val.quoteChar)')
+ let fullCmd = join(args, ' ')
+ " We delay adding pipe part so that we can avoid the above processing.
+ let pipe = ''
+ if type(a:pipe) == 3 && len(a:pipe) > 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 =~# '\<bash\>'
+ 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 \<Esc>"
+ endif
+endfunction
+
+function! genutils#RestoreVisualSelection(id)
+ if mode() !=# 'n'
+ exec "normal \<Esc>"
+ 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('<sfile>'), '<SNR>\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 <Space>...
+ 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 <Space>...
+ 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 <Space>...
+ 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 <q-args>
+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
+ elseif 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 <fisherbb@bc.edu>
+" Date: Mon, 25 Mar 2002 15:05:14 -0500
+"
+" Based on Bram's idea of eating a character while type <Space> 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("");<Left><Left><Left><C-R>=genutils#EatChar('\s')<CR>
+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('<abuf>') + 0
+ let fName = expand('<afile>')
+ 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 = '\%(\%([^\\]\|^\)\\\%(\\\\\)*\)\@<!' " Doesn't eat slashes.
+"let s:UNPROTECTED_CHAR_PRFX = '\\\@<!\%(\\\\\)*' " Eats slashes.
+function! genutils#CrUnProtectedCharsPattern(chars, ...)
+ let capture = (a:0 > 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