" 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
