Map q to quit easily when submitting p4 and svn.
[profile.git] / .vim / autoload / genutils.vim
1 " genutils.vim: Please see plugin/genutils.vim
2 "
3 " TODO:
4 "   - Vim7: redir can be used with a variable.
5 "   - fnamemodify() on Unix doesn't expand to full name if the filename doesn't
6 "     really exist on the filesystem.
7 "   - Is setting 'scrolloff' and 'sidescrolloff' to 0 required while moving the
8 "     cursor?
9 "   - http://www.vim.org/tips/tip.php?tip_id=1379
10 "
11 "   - EscapeCommand() didn't work for David Fishburn.
12 "   - Save/RestoreWindowSettings doesn't work well.
13 "
14 "   Vim7:
15 "   - Save/RestoreWindowSettings can use winsave/restview() functions.
16 "
17
18 " Make sure line-continuations won't cause any problem. This will be restored
19 "   at the end
20 let s:save_cpo = &cpo
21 set cpo&vim
22
23
24 let g:makeArgumentString = 'exec genutils#MakeArgumentString()'
25 let g:makeArgumentList = 'exec genutils#MakeArgumentList()'
26
27 let s:makeArgumentString = ''
28 function! genutils#MakeArgumentString(...)
29   if s:makeArgumentString == ''
30     let s:makeArgumentString = genutils#ExtractFuncListing(s:SNR().
31           \ '_makeArgumentString', 0, 0)
32   endif
33   if a:0 > 0 && a:1 != ''
34     return substitute(s:makeArgumentString, '\<argumentString\>', a:1, 'g')
35   else
36     return s:makeArgumentString
37   endif
38 endfunction
39
40
41 let s:makeArgumentList = ''
42 function! genutils#MakeArgumentList(...)
43   if s:makeArgumentList == ''
44     let s:makeArgumentList = genutils#ExtractFuncListing(s:SNR().
45           \ '_makeArgumentList', 0, 0)
46   endif
47   if a:0 > 0 && a:1 != ''
48     let mkArgLst = substitute(s:makeArgumentList, '\<argumentList\>', a:1, 'g')
49     if a:0 > 1 && a:2 != ''
50       let mkArgLst = substitute(s:makeArgumentList,
51             \ '\(\s\+let __argSeparator = \)[^'."\n".']*', "\\1'".a:2."'", '')
52     endif
53     return mkArgLst
54   else
55     return s:makeArgumentList
56   endif
57 endfunction
58
59 function! genutils#ExtractFuncListing(funcName, hLines, tLines)
60   let listing = genutils#GetVimCmdOutput('func '.a:funcName)
61   let listing = substitute(listing,
62         \ '^\%(\s\|'."\n".'\)*function '.a:funcName.'([^)]*)'."\n", '', '')
63   "let listing = substitute(listing, '\%(\s\|'."\n".'\)*endfunction\%(\s\|'."\n".'\)*$', '', '')
64   " Leave the last newline character.
65   let listing = substitute(listing, '\%('."\n".'\)\@<=\s*endfunction\s*$', '', '')
66   let listing = substitute(listing, '\(\%(^\|'."\n".'\)\s*\)\@<=\d\+',
67         \ '', 'g')
68   if a:hLines > 0
69     let listing = substitute(listing, '^\%([^'."\n".']*'."\n".'\)\{'.
70           \ a:hLines.'}', '', '')
71   endif
72   if a:tLines > 0
73     let listing = substitute(listing, '\%([^'."\n".']*'."\n".'\)\{'.
74           \ a:tLines.'}$', '', '')
75   endif
76   return listing
77 endfunction
78
79 function! genutils#CreateArgString(argList, sep, ...)
80   let sep = (a:0 == 0) ? a:sep : a:1 " This is no longer used.
81   " Matching multvals functionality means, we need to ignore the trailing
82   " separator.
83   let argList = split(substitute(a:argList, a:sep.'$', '', ''), a:sep, 1)
84   let argString = "'"
85   for nextArg in argList
86     " FIXME: I think this check is not required. If "'" is the separator, we
87     "   don't expect to see them in the elements.
88     if a:sep != "'"
89       let nextArg = substitute(nextArg, "'", "' . \"'\" . '", 'g')
90     endif
91     let argString = argString . nextArg . "', '"
92   endfor
93   let argString = strpart(argString, 0, strlen(argString) - 3)
94   return argString
95 endfunction
96
97 " {{{
98 function! s:_makeArgumentString()
99   let __argCounter = 1
100   let argumentString = ''
101   while __argCounter <= a:0
102     if type(a:{__argCounter})
103       let __nextArg =  "'" .
104             \ substitute(a:{__argCounter}, "'", "' . \"'\" . '", "g") . "'"
105     else
106       let __nextArg = a:{__argCounter}
107     endif
108     let argumentString = argumentString. __nextArg .
109           \ ((__argCounter == a:0) ? '' : ', ')
110     let __argCounter = __argCounter + 1
111   endwhile
112   unlet __argCounter
113   if exists('__nextArg')
114     unlet __nextArg
115   endif
116 endfunction
117
118 function! s:_makeArgumentList()
119   let __argCounter = 1
120   let __argSeparator = ','
121   let argumentList = ''
122   while __argCounter <= a:0
123     let argumentList = argumentList . a:{__argCounter}
124     if __argCounter != a:0
125       let argumentList = argumentList . __argSeparator
126     endif
127     let __argCounter = __argCounter + 1
128   endwhile
129   unlet __argCounter
130   unlet __argSeparator
131 endfunction
132 " }}}
133
134
135 function! genutils#DebugShowArgs(...)
136   let i = 0
137   let argString = ''
138   while i < a:0
139     let argString = argString . a:{i + 1} . ', '
140     let i = i + 1
141   endwhile
142   let argString = strpart(argString, 0, strlen(argString) - 2)
143   call input("Args: " . argString)
144 endfunction
145
146 " Window related functions {{{
147
148 function! genutils#NumberOfWindows()
149   let i = 1
150   while winbufnr(i) != -1
151     let i = i+1
152   endwhile
153   return i - 1
154 endfunction
155
156 " Find the window number for the buffer passed.
157 " The fileName argument is treated literally, unlike the bufnr() which treats
158 "   the argument as a regex pattern.
159 function! genutils#FindWindowForBuffer(bufferName, checkUnlisted)
160   return bufwinnr(genutils#FindBufferForName(a:bufferName))
161 endfunction
162
163 function! genutils#FindBufferForName(fileName)
164   " The name could be having extra backslashes to protect certain chars (such
165   "   as '#' and '%'), so first expand them.
166   return s:FindBufferForName(genutils#UnEscape(a:fileName, '#%'))
167 endfunction
168
169 function! s:FindBufferForName(fileName)
170   let fileName = genutils#Escape(a:fileName, '[?,{')
171   let _isf = &isfname
172   try
173     set isfname-=\
174     set isfname-=[
175     let i = bufnr('^' . fileName . '$')
176   finally
177     let &isfname = _isf
178   endtry
179   return i
180 endfunction
181
182 function! genutils#GetBufNameForAu(bufName)
183   let bufName = a:bufName
184   " Autocommands always require forward-slashes.
185   let bufName = substitute(bufName, "\\\\", '/', 'g')
186   let bufName = escape(bufName, '*?,{}[ ')
187   return bufName
188 endfunction
189
190 function! genutils#MoveCursorToWindow(winno)
191   if genutils#NumberOfWindows() != 1
192     execute a:winno . " wincmd w"
193   endif
194 endfunction
195
196 function! genutils#MoveCurLineToWinLine(n)
197   normal! zt
198   if a:n == 1
199     return
200   endif
201   let _wrap = &l:wrap
202   setl nowrap
203   let n = a:n
204   if n >= winheight(0)
205     let n = winheight(0)
206   endif
207   let n = n - 1
208   execute "normal! " . n . "\<C-Y>"
209   let &l:wrap = _wrap
210 endfunction
211
212 function! genutils#CloseWindow(win, force)
213   let _eventignore = &eventignore
214   try
215     set eventignore=all
216     call genutils#MarkActiveWindow()
217
218     let &eventignore = _eventignore
219     exec a:win 'wincmd w'
220     exec 'close'.(a:force ? '!' : '')
221     set eventignore=all
222
223     if a:win < t:curWinnr
224       let t:curWinnr = t:curWinnr - 1
225     endif
226     if a:win < t:prevWinnr
227       let t:prevWinnr = t:prevWinnr - 1
228     endif
229   finally
230     call genutils#RestoreActiveWindow()
231     let &eventignore = _eventignore
232   endtry
233 endfunction
234
235 function! genutils#MarkActiveWindow()
236   let t:curWinnr = winnr()
237   " We need to restore the previous-window also at the end.
238   silent! wincmd p
239   let t:prevWinnr = winnr()
240   silent! wincmd p
241 endfunction
242
243 function! genutils#RestoreActiveWindow()
244   if !exists('t:curWinnr')
245     return
246   endif
247
248   " Restore the original window.
249   if winnr() != t:curWinnr
250     exec t:curWinnr'wincmd w'
251   endif
252   if t:curWinnr != t:prevWinnr
253     exec t:prevWinnr'wincmd w'
254     wincmd p
255   endif
256 endfunction
257
258 function! genutils#IsOnlyVerticalWindow()
259   let onlyVertWin = 1
260   let _eventignore = &eventignore
261
262   try
263     "set eventignore+=WinEnter,WinLeave
264     set eventignore=all
265     call genutils#MarkActiveWindow()
266
267     wincmd j
268     if winnr() != t:curWinnr
269       let onlyVertWin = 0
270     else
271       wincmd k
272       if winnr() != t:curWinnr
273         let onlyVertWin = 0
274       endif
275     endif
276   finally
277     call genutils#RestoreActiveWindow()
278     let &eventignore = _eventignore
279   endtry
280   return onlyVertWin
281 endfunction
282
283 function! genutils#IsOnlyHorizontalWindow()
284   let onlyHorizWin = 1
285   let _eventignore = &eventignore
286   try
287     set eventignore=all
288     call genutils#MarkActiveWindow()
289     wincmd l
290     if winnr() != t:curWinnr
291       let onlyHorizWin = 0
292     else
293       wincmd h
294       if winnr() != t:curWinnr
295         let onlyHorizWin = 0
296       endif
297     endif
298   finally
299     call genutils#RestoreActiveWindow()
300     let &eventignore = _eventignore
301   endtry
302   return onlyHorizWin
303 endfunction
304
305 function! genutils#MoveCursorToNextInWinStack(dir)
306   let newwin = genutils#GetNextWinnrInStack(a:dir)
307   if newwin != 0
308     exec newwin 'wincmd w'
309   endif
310 endfunction
311
312 function! genutils#GetNextWinnrInStack(dir)
313   let newwin = winnr()
314   let _eventignore = &eventignore
315   try
316     set eventignore=all
317     call genutils#MarkActiveWindow()
318     let newwin = s:GetNextWinnrInStack(a:dir)
319   finally
320     call genutils#RestoreActiveWindow()
321     let &eventignore = _eventignore
322   endtry
323   return newwin
324 endfunction
325
326 function! genutils#MoveCursorToLastInWinStack(dir)
327   let newwin = genutils#GetLastWinnrInStack(a:dir)
328   if newwin != 0
329     exec newwin 'wincmd w'
330   endif
331 endfunction
332
333 function! genutils#GetLastWinnrInStack(dir)
334   let newwin = winnr()
335   let _eventignore = &eventignore
336   try
337     set eventignore=all
338     call genutils#MarkActiveWindow()
339     while 1
340       let wn = s:GetNextWinnrInStack(a:dir)
341       if wn != 0
342         let newwin = wn
343         exec newwin 'wincmd w'
344       else
345         break
346       endif
347     endwhile
348   finally
349     call genutils#RestoreActiveWindow()
350     let &eventignore = _eventignore
351   endtry
352   return newwin
353 endfunction
354
355 " Based on the WinStackMv() function posted by Charles E. Campbell, Jr. on vim
356 "   mailing list on Jul 14, 2004.
357 function! s:GetNextWinnrInStack(dir)
358   "call Decho("genutils#MoveCursorToNextInWinStack(dir<".a:dir.">)")
359
360   let isHorizontalMov = (a:dir ==# 'h' || a:dir ==# 'l') ? 1 : 0
361
362   let orgwin = winnr()
363   let orgdim = s:GetWinDim(a:dir, orgwin)
364
365   let _winwidth = &winwidth
366   let _winheight = &winheight
367   try
368     set winwidth=1
369     set winheight=1
370     exec 'wincmd' a:dir
371     let newwin = winnr()
372     if orgwin == newwin
373       " No more windows in this direction.
374       "call Decho("newwin=".newwin." stopped".winheight(newwin)."x".winwidth(newwin))
375       return 0
376     endif
377     if s:GetWinDim(a:dir, newwin) != orgdim
378       " Window dimension has changed, indicates a move across window stacks.
379       "call Decho("newwin=".newwin." height changed".winheight(newwin)."x".winwidth(newwin))
380       return 0
381     endif
382     " Determine if changing original window height affects current window
383     "   height.
384     exec orgwin 'wincmd w'
385     try
386       if orgdim == 1
387         exec 'wincmd' (isHorizontalMov ? '_' : '|')
388       else
389         exec 'wincmd' (isHorizontalMov ? '-' : '<')
390       endif
391       if s:GetWinDim(a:dir, newwin) != s:GetWinDim(a:dir, orgwin)
392         "call Decho("newwin=".newwin." different row".winheight(newwin)."x".winwidth(newwin))
393         return 0
394       endif
395       "call Decho("newwin=".newwin." same row".winheight(newwin)."x".winwidth(newwin))
396     finally
397       exec (isHorizontalMov ? '' : 'vert') 'resize' orgdim
398     endtry
399
400     "call Decho("genutils#MoveCursorToNextInWinStack")
401
402     return newwin
403   finally
404     let &winwidth = _winwidth
405     let &winheight = _winheight
406   endtry
407 endfunction
408
409 function! s:GetWinDim(dir, win)
410   return (a:dir ==# 'h' || a:dir ==# 'l') ? winheight(a:win) : winwidth(a:win)
411 endfunction
412
413 function! genutils#OpenWinNoEa(winOpenCmd)
414   call s:ExecWinCmdNoEa(a:winOpenCmd)
415 endfunction
416
417 function! genutils#CloseWinNoEa(winnr, force)
418   call s:ExecWinCmdNoEa(a:winnr.'wincmd w | close'.(a:force?'!':''))
419 endfunction
420
421 function! s:ExecWinCmdNoEa(winCmd)
422   let _eventignore = &eventignore
423   try
424     set eventignore=all
425     call genutils#MarkActiveWindow()
426     windo let w:_winfixheight = &winfixheight
427     windo set winfixheight
428     call genutils#RestoreActiveWindow()
429
430     let &eventignore = _eventignore
431     exec a:winCmd
432     set eventignore=all
433
434     call genutils#MarkActiveWindow()
435     silent! windo let &winfixheight = w:_winfixheight
436     silent! windo unlet w:_winfixheight
437     call genutils#RestoreActiveWindow()
438   finally
439     let &eventignore = _eventignore
440   endtry
441 endfunction
442
443 " Window related functions }}}
444
445 function! genutils#SetupScratchBuffer()
446   setlocal nobuflisted
447   setlocal noswapfile
448   setlocal buftype=nofile
449   " Just in case, this will make sure we are always hidden.
450   setlocal bufhidden=delete
451   setlocal nolist
452   setlocal nonumber
453   setlocal foldcolumn=0 nofoldenable
454   setlocal noreadonly
455 endfunction
456
457 function! genutils#CleanDiffOptions()
458   setlocal nodiff
459   setlocal noscrollbind
460   setlocal scrollopt-=hor
461   setlocal wrap
462   setlocal foldmethod=manual
463   setlocal foldcolumn=0
464   normal zE
465 endfunction
466
467 function! genutils#ArrayVarExists(varName, index)
468   try
469     exec "let test = " . a:varName . "{a:index}"
470   catch /^Vim\%((\a\+)\)\=:E121/
471     return 0
472   endtry
473   return 1
474 endfunction
475
476 function! genutils#Escape(str, chars)
477   return substitute(a:str, '\\\@<!\(\%(\\\\\)*\)\([' . a:chars .']\)', '\1\\\2',
478         \ 'g')
479 endfunction
480
481 function! genutils#UnEscape(str, chars)
482   return substitute(a:str, '\\\@<!\(\\\\\)*\\\([' . a:chars . ']\)',
483         \ '\1\2', 'g')
484 endfunction
485
486 function! genutils#DeEscape(str)
487   let str = a:str
488   let str = substitute(str, '\\\(\\\|[^\\]\)', '\1', 'g')
489   return str
490 endfunction
491
492 " - For windoze+native, use double-quotes to sorround the arguments and for
493 "   embedded double-quotes, just double them.
494 " - For windoze+sh, use single-quotes to sorround the aruments and for embedded
495 "   single-quotes, just replace them with '""'""' (if 'shq' or 'sxq' is a
496 "   double-quote) and just '"'"' otherwise. Embedded double-quotes also need
497 "   to be doubled.
498 " - For Unix+sh, use single-quotes to sorround the arguments and for embedded
499 "   single-quotes, just replace them with '"'"'. 
500 function! genutils#EscapeCommand(cmd, args, pipe)
501   if type(a:args) == 3
502     let args = copy(a:args)
503   else
504     let args = split(a:args, genutils#CrUnProtectedCharsPattern(' '))
505   endif
506   " I am only worried about passing arguments with spaces as they are to the
507   "   external commands, I currently don't care about back-slashes
508   "   (backslashes are normally expected only on windows when 'shellslash'
509   "   option is set, but even then the 'shell' is expected to take care of
510   "   them.). However, for cygwin bash, there is a loss of one level
511   "   of the back-slashes somewhere in the chain of execution (most probably
512   "   between CreateProcess() and cygwin?), so we need to double them.
513   let shellEnvType = genutils#GetShellEnvType()
514   if shellEnvType ==# g:ST_WIN_CMD
515     let quoteChar = '"'
516     " genutils#Escape the existing double-quotes (by doubling them).
517     call map(args, "substitute(v:val, '\"', '\"\"', 'g')")
518   else
519     let quoteChar = "'"
520     if shellEnvType ==# g:ST_WIN_SH
521       " genutils#Escape the existing double-quotes (by doubling them).
522       call map(args, "substitute(v:val, '\"', '\"\"', 'g')")
523     endif
524     " Take care of existing single-quotes (by exposing them, as you can't have
525     "   single-quotes inside a single-quoted string).
526     if &shellquote == '"' || &shellxquote == '"'
527       let squoteRepl = "'\"\"'\"\"'"
528     else
529       let squoteRepl = "'\"'\"'"
530     endif
531     call map(args, "substitute(v:val, \"'\", squoteRepl, 'g')")
532   endif
533
534   " Now sorround the arguments with quotes, considering the protected
535   "   spaces. Skip the && or || construct from doing this.
536   call map(args, 'v:val=~"^[&|]\\{2}$"?(v:val):(quoteChar.v:val.quoteChar)')
537   let fullCmd = join(args, ' ')
538   " We delay adding pipe part so that we can avoid the above processing.
539   let pipe = ''
540   if type(a:pipe) == 3 && len(a:pipe) > 0
541     let pipe = join(a:pipe, ' ')
542   elseif type(a:pipe) == 1 && a:pipe !~# '^\s*$'
543     let pipe = a:pipe
544   endif
545   if pipe != ''
546     let fullCmd = fullCmd . ' ' . a:pipe
547   endif
548   if a:cmd !~# '^\s*$'
549     let fullCmd = a:cmd . ' ' . fullCmd
550   endif
551   if shellEnvType ==# g:ST_WIN_SH && &shell =~# '\<bash\>'
552     let fullCmd = substitute(fullCmd, '\\', '\\\\', 'g')
553   endif
554   return fullCmd
555 endfunction
556
557 let g:ST_WIN_CMD = 0 | let g:ST_WIN_SH = 1 | let g:ST_UNIX = 2
558 function! genutils#GetShellEnvType()
559   " When 'shellslash' option is available, then the platform must be one of
560   "     those that support '\' as a pathsep.
561   if exists('+shellslash')
562     if stridx(&shell, 'cmd.exe') != -1 ||
563           \ stridx(&shell, 'command.com') != -1
564       return g:ST_WIN_CMD
565     else
566       return g:ST_WIN_SH
567     endif
568   else
569     return g:ST_UNIX
570   endif
571 endfunction
572
573 function! genutils#ExpandStr(str)
574   let str = substitute(a:str, '"', '\\"', 'g')
575   exec "let str = \"" . str . "\"" 
576   return str
577 endfunction
578
579 function! genutils#QuoteStr(str)
580   return "'".substitute(a:str, "'", "'.\"'\".'", 'g')."'"
581 endfunction
582
583 function! genutils#GetPreviewWinnr()
584   let _eventignore = &eventignore
585   let curWinNr = winnr()
586   let winnr = -1
587   try
588     set eventignore=all
589     exec "wincmd P"
590     let winnr = winnr()
591   catch /^Vim\%((\a\+)\)\=:E441/
592     " Ignore, winnr is already set to -1.
593   finally
594     if winnr() != curWinNr
595       exec curWinNr.'wincmd w'
596     endif
597     let &eventignore = _eventignore
598   endtry
599   return winnr
600 endfunction
601
602 " Save/Restore window settings {{{
603 function! genutils#SaveWindowSettings()
604   call genutils#SaveWindowSettings2('SaveWindowSettings', 1)
605 endfunction
606
607 function! genutils#RestoreWindowSettings()
608   call genutils#RestoreWindowSettings2('SaveWindowSettings')
609 endfunction
610
611
612 function! genutils#ResetWindowSettings()
613   call genutils#ResetWindowSettings2('SaveWindowSettings')
614 endfunction
615
616 function! genutils#SaveWindowSettings2(id, overwrite)
617   if genutils#ArrayVarExists("t:winSettings", a:id) && ! a:overwrite
618     return
619   endif
620
621   let t:winSettings{a:id} = []
622   call add(t:winSettings{a:id}, genutils#NumberOfWindows())
623   call add(t:winSettings{a:id}, &lines)
624   call add(t:winSettings{a:id}, &columns)
625   call add(t:winSettings{a:id}, winnr())
626   let i = 1
627   while winbufnr(i) != -1
628     call add(t:winSettings{a:id}, winheight(i))
629     call add(t:winSettings{a:id}, winwidth(i))
630     let i = i + 1
631   endwhile
632   "let g:savedWindowSettings = t:winSettings{a:id} " Debug.
633 endfunction
634
635 function! genutils#RestoreWindowSettings2(id)
636   " Calling twice fixes most of the resizing issues. This seems to be how the
637   " :mksession with "winsize" in 'sesionoptions' seems to work.
638   call s:RestoreWindowSettings2(a:id)
639   call s:RestoreWindowSettings2(a:id)
640 endfunction
641
642 function! s:RestoreWindowSettings2(id)
643   "if ! exists("t:winSettings" . a:id)
644   if ! genutils#ArrayVarExists("t:winSettings", a:id)
645     return
646   endif
647
648   let nWindows = t:winSettings{a:id}[0]
649   if nWindows != genutils#NumberOfWindows()
650     unlet t:winSettings{a:id}
651     return
652   endif
653   let orgLines = t:winSettings{a:id}[1]
654   let orgCols = t:winSettings{a:id}[2]
655   let activeWindow = t:winSettings{a:id}[3]
656   let mainWinSizeSame = (orgLines == &lines && orgCols == &columns)
657
658   let winNo = 1
659   let i = 4
660   while i < len(t:winSettings{a:id})
661     let height = t:winSettings{a:id}[i]
662     let width = t:winSettings{a:id}[i+1]
663     let height = (mainWinSizeSame ? height :
664           \ ((&lines * height + (orgLines / 2)) / orgLines))
665     let width = (mainWinSizeSame ? width :
666           \ ((&columns * width + (orgCols / 2)) / orgCols))
667     if winheight(winnr()) != height
668       exec winNo'resize' height
669     endif
670     if winwidth(winnr()) != width
671       exec 'vert' winNo 'resize' width
672     endif
673     let winNo = winNo + 1
674     let i = i + 2
675   endwhile
676   
677   " Restore the current window.
678   call genutils#MoveCursorToWindow(activeWindow)
679   "unlet g:savedWindowSettings
680 endfunction
681
682
683 function! genutils#ResetWindowSettings2(id)
684   if genutils#ArrayVarExists("t:winSettings", a:id)
685     unlet t:winSettings{a:id}
686   endif
687 endfunction
688
689 " Save/Restore window settings }}}
690
691 " Save/Restore selection {{{
692
693 function! genutils#SaveVisualSelection(id)
694   let curmode = mode()
695   if curmode == 'n'
696     normal! gv
697   endif
698   let s:vismode{a:id} = mode()
699   let s:firstline{a:id} = line("'<")
700   let s:lastline{a:id} = line("'>")
701   let s:firstcol{a:id} = col("'<")
702   let s:lastcol{a:id} = col("'>")
703   if curmode !=# s:vismode{a:id}
704     exec "normal \<Esc>"
705   endif
706 endfunction
707
708 function! genutils#RestoreVisualSelection(id)
709   if mode() !=# 'n'
710     exec "normal \<Esc>"
711   endif
712   if exists('s:vismode{id}')
713     exec 'normal' s:firstline{a:id}.'gg'.s:firstcol{a:id}.'|'.
714           \ s:vismode{a:id}.(s:lastline{a:id}-s:firstline{a:id}).'j'.
715           \ (s:lastcol{a:id}-s:firstcol{a:id}).'l'
716   endif
717 endfunction
718 " Save/Restore selection }}}
719
720 function! genutils#CleanupFileName(fileName)
721   return genutils#CleanupFileName2(a:fileName, '')
722 endfunction
723
724 function! genutils#CleanupFileName2(fileName, win32ProtectedChars)
725   let fileName = substitute(a:fileName, '^\s\+\|\s\+$', '', 'g')
726
727   " Expand relative paths and paths containing relative components (takes care
728   " of ~ also).
729   if ! genutils#PathIsAbsolute(fileName)
730     let fileName = fnamemodify(fileName, ':p')
731   endif
732
733   " I think we can have UNC paths on UNIX, if samba is installed.
734   if genutils#OnMS() && (match(fileName, '^//') == 0 ||
735         \ match(fileName, '^\\\\') == 0)
736     let uncPath = 1
737   else
738     let uncPath = 0
739   endif
740
741   " Remove multiple path separators.
742   if has('win32')
743     if a:win32ProtectedChars != ''
744       let fileName=substitute(fileName, '\\['.a:win32ProtectedChars.']\@!', '/',
745             \ 'g')
746     else
747       let fileName=substitute(fileName, '\\', '/', 'g')
748     endif
749   elseif genutils#OnMS()
750     " On non-win32 systems, the forward-slash is not supported, so leave
751     " back-slash.
752     let fileName=substitute(fileName, '\\\{2,}', '\', 'g')
753   endif
754   let fileName=substitute(fileName, '/\{2,}', '/', 'g')
755
756   " Remove ending extra path separators.
757   let fileName=substitute(fileName, '/$', '', '')
758   let fileName=substitute(fileName, '\\$', '', '')
759
760   " If it was an UNC path, add back an extra slash.
761   if uncPath
762     let fileName = '/'.fileName
763   endif
764
765   if genutils#OnMS()
766     let fileName=substitute(fileName, '^[A-Z]:', '\L&', '')
767
768     " Add drive letter if missing (just in case).
769     if !uncPath && match(fileName, '^/') == 0
770       let curDrive = substitute(getcwd(), '^\([a-zA-Z]:\).*$', '\L\1', '')
771       let fileName = curDrive . fileName
772     endif
773   endif
774   return fileName
775 endfunction
776 "echo genutils#CleanupFileName('\\a///b/c\')
777 "echo genutils#CleanupFileName('C:\a/b/c\d')
778 "echo genutils#CleanupFileName('a/b/c\d')
779 "echo genutils#CleanupFileName('~/a/b/c\d')
780 "echo genutils#CleanupFileName('~/a/b/../c\d')
781
782 function! genutils#OnMS()
783   return has('win32') || has('dos32') || has('win16') || has('dos16') ||
784        \ has('win95')
785 endfunction
786
787 function! genutils#PathIsAbsolute(path)
788   let absolute=0
789   if has('unix') || genutils#OnMS()
790     if match(a:path, '^/') == 0
791       let absolute=1
792     endif
793   endif
794   if (! absolute) && genutils#OnMS()
795     if match(a:path, "^\\") == 0
796       let absolute=1
797     endif
798   endif
799   if (! absolute) && genutils#OnMS()
800     if match(a:path, "^[A-Za-z]:") == 0
801       let absolute=1
802     endif
803   endif
804   return absolute
805 endfunction
806
807 function! genutils#PathIsFileNameOnly(path)
808   return (match(a:path, "\\") < 0) && (match(a:path, "/") < 0)
809 endfunction
810
811 let s:mySNR = ''
812 function! s:SNR()
813   if s:mySNR == ''
814     let s:mySNR = matchstr(expand('<sfile>'), '<SNR>\d\+_\zeSNR$')
815   endif
816   return s:mySNR
817 endfun
818
819
820 "" --- START save/restore position. {{{
821
822 function! genutils#SaveSoftPosition(id)
823   let b:sp_startline_{a:id} = getline(".")
824   call genutils#SaveHardPosition(a:id)
825 endfunction
826
827 function! genutils#RestoreSoftPosition(id)
828   0
829   call genutils#RestoreHardPosition(a:id)
830   let stLine = b:sp_startline_{a:id}
831   if getline('.') !=# stLine
832     if ! search('\V\^'.escape(stLine, "\\").'\$', 'W') 
833       call search('\V\^'.escape(stLine, "\\").'\$', 'bW')
834     endif
835   endif
836   call genutils#MoveCurLineToWinLine(b:sp_winline_{a:id})
837 endfunction
838
839 function! genutils#ResetSoftPosition(id)
840   unlet b:sp_startline_{a:id}
841 endfunction
842
843 " A synonym for genutils#SaveSoftPosition.
844 function! genutils#SaveHardPositionWithContext(id)
845   call genutils#SaveSoftPosition(a:id)
846 endfunction
847
848 " A synonym for genutils#RestoreSoftPosition.
849 function! genutils#RestoreHardPositionWithContext(id)
850   call genutils#RestoreSoftPosition(a:id)
851 endfunction
852
853 " A synonym for genutils#ResetSoftPosition.
854 function! genutils#ResetHardPositionWithContext(id)
855   call genutils#ResetSoftPosition(a:id)
856 endfunction
857
858 function! genutils#SaveHardPosition(id)
859   let b:sp_col_{a:id} = virtcol(".")
860   let b:sp_lin_{a:id} = line(".")
861   " Avoid accounting for wrapped lines.
862   let _wrap = &l:wrap
863   try
864     setl nowrap
865     let b:sp_winline_{a:id} = winline()
866   finally
867     let &l:wrap = _wrap
868   endtry
869 endfunction
870
871 function! genutils#RestoreHardPosition(id)
872   " This doesn't take virtual column.
873   "call cursor(b:sp_lin_{a:id}, b:sp_col_{a:id})
874   " Vim7 generates E16 if line number is invalid.
875   " TODO: Why is this leaving cursor on the last-but-one line when the
876   " condition meets?
877   execute ((line('$') < b:sp_lin_{a:id}) ? line('$') :
878         \ b:sp_lin_{a:id})
879   "execute b:sp_lin_{a:id}
880   execute ((line('$') < b:sp_lin_{a:id}) ? line('$') :
881         \ b:sp_lin_{a:id})
882   "execute b:sp_lin_{a:id}
883   execute "normal!" b:sp_col_{a:id} . "|"
884   call genutils#MoveCurLineToWinLine(b:sp_winline_{a:id})
885 endfunction
886
887 function! genutils#ResetHardPosition(id)
888   unlet b:sp_col_{a:id}
889   unlet b:sp_lin_{a:id}
890   unlet b:sp_winline_{a:id}
891 endfunction
892
893 function! genutils#GetLinePosition(id)
894   return b:sp_lin_{a:id}
895 endfunction
896
897 function! genutils#GetColPosition(id)
898   return b:sp_col_{a:id}
899 endfunction
900
901 function! genutils#IsPositionSet(id)
902   return exists('b:sp_col_' . a:id)
903 endfunction
904
905 "" --- END save/restore position. }}}
906
907
908
909 ""
910 "" --- START: Notify window close -- {{{
911 ""
912
913 let s:notifyWindow = {}
914 function! genutils#AddNotifyWindowClose(windowTitle, functionName)
915   let bufName = a:windowTitle
916
917   let s:notifyWindow[bufName] = a:functionName
918
919   "let g:notifyWindow = s:notifyWindow " Debug.
920
921   " Start listening to events.
922   aug NotifyWindowClose
923     au!
924     au WinEnter * :call genutils#CheckWindowClose()
925     au BufEnter * :call genutils#CheckWindowClose()
926   aug END
927 endfunction
928
929 function! genutils#RemoveNotifyWindowClose(windowTitle)
930   let bufName = a:windowTitle
931
932   if has_key(s:notifyWindow, bufName)
933     call remove(s:notifyWindow, bufName)
934     if len(s:notifyWindow) == 0
935       "unlet g:notifyWindow " Debug.
936   
937       aug NotifyWindowClose
938         au!
939       aug END
940     endif
941   endif
942 endfunction
943
944 function! genutils#CheckWindowClose()
945   if !exists('s:notifyWindow')
946     return
947   endif
948
949   " Now iterate over all the registered window titles and see which one's are
950   "   closed.
951   for nextWin in keys(s:notifyWindow)
952     if bufwinnr(s:FindBufferForName(nextWin)) == -1
953       let funcName = s:notifyWindow[nextWin]
954       " Remove this entry as these are going to be processed. The caller can add
955       "   them back if needed.
956       unlet s:notifyWindow[nextWin]
957       "call input("cmd: " . cmd)
958       call call(funcName, [nextWin])
959     endif
960   endfor
961 endfunction
962
963 "function! NotifyWindowCloseF(title)
964 "  call input(a:title . " closed")
965 "endfunction
966 "
967 "function! RunNotifyWindowCloseTest()
968 "  call input("Creating three windows, 'ABC', 'XYZ' and 'b':")
969 "  split ABC
970 "  split X Y Z
971 "  split b
972 "  redraw
973 "  call genutils#AddNotifyWindowClose("ABC", "NotifyWindowCloseF")
974 "  call genutils#AddNotifyWindowClose("X Y Z", "NotifyWindowCloseF")
975 "  call input("notifyWindow: " . string(s:notifyWindow))
976 "  au NotifyWindowClose WinEnter
977 "  call input("Added notifications for 'ABC' and 'XYZ'\n".
978 "       \ "Now closing the windows, you should see ONLY two notifications:")
979 "  quit
980 "  quit
981 "  quit
982 "endfunction
983
984 ""
985 "" --- END: Notify window close -- }}}
986 ""
987
988 " TODO: For large ranges, the cmd can become too big, so make it one cmd per
989 "       line.
990 function! genutils#ShowLinesWithSyntax() range
991   " This makes sure we start (subsequent) echo's on the first line in the
992   " command-line area.
993   "
994   echo ''
995
996   let cmd        = ''
997   let prev_group = ' x '     " Something that won't match any syntax group.
998
999   let show_line = a:firstline
1000   let isMultiLine = ((a:lastline - a:firstline) > 1)
1001   while show_line <= a:lastline
1002     let cmd = ''
1003     let length = strlen(getline(show_line))
1004     let column = 1
1005
1006     while column <= length
1007       let group = synIDattr(synID(show_line, column, 1), 'name')
1008       if group != prev_group
1009         if cmd != ''
1010           let cmd = cmd . "'|"
1011         endif
1012         let cmd = cmd . 'echohl ' . (group == '' ? 'NONE' : group) . "|echon '"
1013         let prev_group = group
1014       endif
1015       let char = strpart(getline(show_line), column - 1, 1)
1016       if char == "'"
1017         let char = "'."'".'"
1018       endif
1019       let cmd = cmd . char
1020       let column = column + 1
1021     endwhile
1022
1023     try
1024       exec cmd."\n'"
1025     catch
1026       echo ''
1027     endtry
1028     let show_line = show_line + 1
1029   endwhile
1030   echohl NONE
1031 endfunction
1032
1033
1034 function! genutils#AlignWordWithWordInPreviousLine()
1035   "First go to the first col in the word.
1036   if getline('.')[col('.') - 1] =~ '\s'
1037     normal! w
1038   else
1039     normal! "_yiw
1040   endif
1041   let orgVcol = virtcol('.')
1042   let prevLnum = prevnonblank(line('.') - 1)
1043   if prevLnum == -1
1044     return
1045   endif
1046   let prevLine = getline(prevLnum)
1047
1048   " First get the column to align with.
1049   if prevLine[orgVcol - 1] =~ '\s'
1050     " column starts from 1 where as index starts from 0.
1051     let nonSpaceStrInd = orgVcol " column starts from 1 where as index starts from 0.
1052     while prevLine[nonSpaceStrInd] =~ '\s'
1053       let nonSpaceStrInd = nonSpaceStrInd + 1
1054     endwhile
1055   else
1056     if strlen(prevLine) < orgVcol
1057       let nonSpaceStrInd = strlen(prevLine) - 1
1058     else
1059       let nonSpaceStrInd = orgVcol - 1
1060     endif
1061
1062     while prevLine[nonSpaceStrInd - 1] !~ '\s' && nonSpaceStrInd > 0
1063       let nonSpaceStrInd = nonSpaceStrInd - 1
1064     endwhile
1065   endif
1066   let newVcol = nonSpaceStrInd + 1 " Convert to column number.
1067
1068   if orgVcol > newVcol " We need to reduce the spacing.
1069     let sub = strpart(getline('.'), newVcol - 1, (orgVcol - newVcol))
1070     if sub =~ '^\s\+$'
1071       " Remove the excess space.
1072       exec 'normal! ' . newVcol . '|'
1073       exec 'normal! ' . (orgVcol - newVcol) . 'x'
1074     endif
1075   elseif orgVcol < newVcol " We need to insert spacing.
1076     exec 'normal! ' . orgVcol . '|'
1077     exec 'normal! ' . (newVcol - orgVcol) . 'i '
1078   endif
1079 endfunction
1080
1081 function! genutils#ShiftWordInSpace(dir)
1082   if a:dir == 1 " forward.
1083     " If currently on <Space>...
1084     if getline(".")[col(".") - 1] == " "
1085       let move1 = 'wf '
1086     else
1087       " If next col is a 
1088       "if getline(".")[col(".") + 1]
1089       let move1 = 'f '
1090     endif
1091     let removeCommand = "x"
1092     let pasteCommand = "bi "
1093     let move2 = 'w'
1094     let offset = 0
1095   else " backward.
1096     " If currently on <Space>...
1097     if getline(".")[col(".") - 1] == " "
1098       let move1 = 'w'
1099     else
1100       let move1 = '"_yiW'
1101     endif
1102     let removeCommand = "hx"
1103     let pasteCommand = 'h"_yiwEa '
1104     let move2 = 'b'
1105     let offset = -3
1106   endif
1107
1108   let savedCol = col(".")
1109   exec "normal!" move1
1110   let curCol = col(".")
1111   let possible = 0
1112   " Check if there is a space at the end.
1113   if col("$") == (curCol + 1) " Works only for forward case, as expected.
1114     let possible = 1
1115   elseif getline(".")[curCol + offset] == " "
1116     " Remove the space from here.
1117     exec "normal!" removeCommand
1118     let possible = 1
1119   endif
1120
1121   " Move back into the word.
1122   "exec "normal!" savedCol . "|"
1123   if possible == 1
1124     exec "normal!" pasteCommand
1125     exec "normal!" move2
1126   else
1127     " Move to the original location.
1128     exec "normal!" savedCol . "|"
1129   endif
1130 endfunction
1131
1132
1133 function! genutils#CenterWordInSpace()
1134   let line = getline('.')
1135   let orgCol = col('.')
1136   " If currently on <Space>...
1137   if line[orgCol - 1] == " "
1138     let matchExpr = ' *\%'. orgCol . 'c *\w\+ \+'
1139   else
1140     let matchExpr = ' \+\(\w*\%' . orgCol . 'c\w*\) \+'
1141   endif
1142   let matchInd = match(line, matchExpr)
1143   if matchInd == -1
1144     return
1145   endif
1146   let matchStr = matchstr(line,  matchExpr)
1147   let nSpaces = strlen(substitute(matchStr, '[^ ]', '', 'g'))
1148   let word = substitute(matchStr, ' ', '', 'g')
1149   let middle = nSpaces / 2
1150   let left = nSpaces - middle
1151   let newStr = ''
1152   while middle > 0
1153     let newStr = newStr . ' '
1154     let middle = middle - 1
1155   endwhile
1156   let newStr = newStr . word
1157   while left > 0
1158     let newStr = newStr . ' '
1159     let left = left - 1
1160   endwhile
1161
1162   let newLine = strpart(line, 0, matchInd)
1163   let newLine = newLine . newStr
1164   let newLine = newLine . strpart (line, matchInd + strlen(matchStr))
1165   silent! keepjumps call setline(line('.'), newLine)
1166 endfunction
1167
1168 function! genutils#MapAppendCascaded(lhs, rhs, mapMode)
1169
1170   " Determine the map mode from the map command.
1171   let mapChar = strpart(a:mapMode, 0, 1)
1172
1173   " Check if this is already mapped.
1174   let oldrhs = maparg(a:lhs, mapChar)
1175   if oldrhs != ""
1176     let self = oldrhs
1177   else
1178     let self = a:lhs
1179   endif
1180   "echomsg a:mapMode . "oremap" . " " . a:lhs . " " . self . a:rhs
1181   exec a:mapMode . "oremap" a:lhs self . a:rhs
1182 endfunction
1183
1184 " smartSlash simply says whether to depend on shellslash and ArgLead for
1185 "   determining path separator. If it shouldn't depend, it will always assume
1186 "   that the required pathsep is forward-slash.
1187 function! genutils#UserFileComplete(ArgLead, CmdLine, CursorPos, smartSlash,
1188       \ searchPath)
1189   let glob = ''
1190   let opathsep = "\\"
1191   let npathsep = '/'
1192   if exists('+shellslash') && ! &shellslash && a:smartSlash &&
1193         \ stridx(a:ArgLead, "\\") != -1
1194     let opathsep = '/'
1195     let npathsep = "\\"
1196   endif
1197   if a:searchPath !=# ''
1198     for nextPath in split(a:searchPath, genutils#CrUnProtectedCharsPattern(','))
1199       let nextPath = genutils#CleanupFileName(nextPath)
1200       let matches = glob(nextPath.'/'.a:ArgLead.'*')
1201       if matches !~# '^\_s*$'
1202         let matches = s:FixPathSep(matches, opathsep, npathsep)
1203         let nextPath = substitute(nextPath, opathsep, npathsep, 'g')
1204         let matches = substitute(matches, '\V'.escape(nextPath.npathsep, "\\"),
1205               \ '', 'g')
1206         let glob = glob . matches . "\n"
1207       endif
1208     endfor
1209   else
1210     let glob = s:FixPathSep(glob(a:ArgLead.'*'), opathsep, npathsep)
1211   endif
1212   " FIXME: Need an option to control if ArgLead should also be returned or
1213   " not.
1214   return glob."\n".a:ArgLead
1215 endfunction
1216
1217 command! -complete=file -nargs=* GUDebugEcho :echo <q-args>
1218 function! genutils#UserFileExpand(fileArgs)
1219   return substitute(genutils#GetVimCmdOutput(
1220         \ 'GUDebugEcho ' . a:fileArgs), '^\_s\+\|\_s\+$', '', 'g')
1221 endfunction
1222
1223 function! s:FixPathSep(matches, opathsep, npathsep)
1224   let matches = a:matches
1225   let matches = substitute(matches, a:opathsep, a:npathsep, 'g')
1226   let matches = substitute(matches, "\\([^\n]\\+\\)", '\=submatch(1).'.
1227         \ '(isdirectory(submatch(1)) ? a:npathsep : "")', 'g')
1228   return matches
1229 endfunction
1230
1231 function! genutils#GetVimCmdOutput(cmd)
1232   let v:errmsg = ''
1233   let output = ''
1234   let _z = @z
1235   let _shortmess = &shortmess
1236   try
1237     set shortmess=
1238     redir @z
1239     silent exec a:cmd
1240   catch /.*/
1241     let v:errmsg = substitute(v:exception, '^[^:]\+:', '', '')
1242   finally
1243     redir END
1244     let &shortmess = _shortmess
1245     if v:errmsg == ''
1246       let output = @z
1247     endif
1248     let @z = _z
1249   endtry
1250   return output
1251 endfunction
1252
1253 function! genutils#OptClearBuffer()
1254   " Go as far as possible in the undo history to conserve Vim resources.
1255   let _modifiable = &l:modifiable
1256   let _undolevels = &undolevels
1257   try
1258     setl modifiable
1259     set undolevels=-1
1260     silent! keepjumps 0,$delete _
1261   finally
1262     let &undolevels = _undolevels
1263     let &l:modifiable = _modifiable
1264   endtry
1265 endfunction
1266
1267
1268 "" START: Sorting support. {{{
1269 ""
1270
1271 "
1272 " Comapare functions.
1273 "
1274
1275 function! genutils#CmpByLineLengthNname(line1, line2, ...)
1276   let direction = (a:0?a:1:1)
1277   let cmp = genutils#CmpByLength(a:line1, a:line2, direction)
1278   if cmp == 0
1279     let cmp = genutils#CmpByString(a:line1, a:line2, direction)
1280   endif
1281   return cmp
1282 endfunction
1283
1284 function! genutils#CmpByLength(line1, line2, ...)
1285   let direction = (a:0?a:1:1)
1286   let len1 = strlen(a:line1)
1287   let len2 = strlen(a:line2)
1288   return direction * (len1 - len2)
1289 endfunction
1290
1291 " Compare first by name and then by length.
1292 " Useful for sorting Java imports.
1293 function! genutils#CmpJavaImports(line1, line2, ...)
1294   let direction = (a:0?a:1:1)
1295   " FIXME: Simplify this.
1296   if stridx(a:line1, '.') == -1
1297     let pkg1 = ''
1298     let cls1 = substitute(a:line1, '.* \(^[ ]\+\)', '\1', '')
1299   else
1300     let pkg1 = substitute(a:line1, '.*import\s\+\(.*\)\.[^. ;]\+.*$', '\1', '')
1301     let cls1 = substitute(a:line1, '^.*\.\([^. ;]\+\).*$', '\1', '')
1302   endif
1303   if stridx(a:line2, '.') == -1
1304     let pkg2 = ''
1305     let cls2 = substitute(a:line2, '.* \(^[ ]\+\)', '\1', '')
1306   else
1307     let pkg2 = substitute(a:line2, '.*import\s\+\(.*\)\.[^. ;]\+.*$', '\1', '')
1308     let cls2 = substitute(a:line2, '^.*\.\([^. ;]\+\).*$', '\1', '')
1309   endif
1310
1311   let cmp = genutils#CmpByString(pkg1, pkg2, direction)
1312   if cmp == 0
1313     let cmp = genutils#CmpByLength(cls1, cls2, direction)
1314   endif
1315   return cmp
1316 endfunction
1317
1318 function! genutils#CmpByString(line1, line2, ...)
1319   let direction = (a:0?a:1:1)
1320   if a:line1 < a:line2
1321     return -direction
1322   elseif a:line1 > a:line2
1323     return direction
1324   else
1325     return 0
1326   endif
1327 endfunction
1328
1329 function! genutils#CmpByStringIgnoreCase(line1, line2, ...)
1330   let direction = (a:0?a:1:1)
1331   if a:line1 <? a:line2
1332     return -direction
1333   elseif a:line1 >? a:line2
1334     return direction
1335   else
1336     return 0
1337   endif
1338 endfunction
1339
1340 function! genutils#CmpByNumber(line1, line2, ...)
1341   let direction = (a:0 ? a:1 :1)
1342   let num1 = a:line1 + 0
1343   let num2 = a:line2 + 0
1344
1345   if num1 < num2
1346     return -direction
1347   elseif num1 > num2
1348     return direction
1349   else
1350     return 0
1351   endif
1352 endfunction
1353
1354 function! genutils#QSort(cmp, direction) range
1355   call s:QSortR(a:firstline, a:lastline, a:cmp, a:direction,
1356         \ 's:BufLineAccessor', 's:BufLineSwapper', '')
1357 endfunction
1358
1359 function! genutils#QSort2(start, end, cmp, direction, accessor, swapper, context)
1360   call s:QSortR(a:start, a:end, a:cmp, a:direction, a:accessor, a:swapper,
1361         \ a:context)
1362 endfunction
1363
1364 " The default swapper that swaps lines in the current buffer.
1365 function! s:BufLineSwapper(line1, line2, context)
1366   let str2 = getline(a:line1)
1367   silent! keepjumps call setline(a:line1, getline(a:line2))
1368   silent! keepjumps call setline(a:line2, str2)
1369 endfunction
1370
1371 " The default accessor that returns lines from the current buffer.
1372 function! s:BufLineAccessor(line, context)
1373   return getline(a:line)
1374 endfunction
1375
1376 " The default mover that moves lines from one place to another in the current
1377 " buffer.
1378 function! s:BufLineMover(from, to, context)
1379   let line = getline(a:from)
1380   exec a:from.'d_'
1381   call append(a:to, line)
1382 endfunction
1383
1384 "
1385 " Sort lines.  QSortR() is called recursively.
1386 "
1387 function! s:QSortR(start, end, cmp, direction, accessor, swapper, context)
1388   if a:end > a:start
1389     let low = a:start
1390     let high = a:end
1391
1392     " Arbitrarily establish partition element at the midpoint of the data.
1393     let midStr = {a:accessor}(((a:start + a:end) / 2), a:context)
1394
1395     " Loop through the data until indices cross.
1396     while low <= high
1397
1398       " Find the first element that is greater than or equal to the partition
1399       "   element starting from the left Index.
1400       while low < a:end
1401         let result = {a:cmp}({a:accessor}(low, a:context), midStr, a:direction)
1402         if result < 0
1403           let low = low + 1
1404         else
1405           break
1406         endif
1407       endwhile
1408
1409       " Find an element that is smaller than or equal to the partition element
1410       "   starting from the right Index.
1411       while high > a:start
1412         let result = {a:cmp}({a:accessor}(high, a:context), midStr, a:direction)
1413         if result > 0
1414           let high = high - 1
1415         else
1416           break
1417         endif
1418       endwhile
1419
1420       " If the indexes have not crossed, swap.
1421       if low <= high
1422         " Swap lines low and high.
1423         call {a:swapper}(high, low, a:context)
1424         let low = low + 1
1425         let high = high - 1
1426       endif
1427     endwhile
1428
1429     " If the right index has not reached the left side of data must now sort
1430     "   the left partition.
1431     if a:start < high
1432       call s:QSortR(a:start, high, a:cmp, a:direction, a:accessor, a:swapper,
1433             \ a:context)
1434     endif
1435
1436     " If the left index has not reached the right side of data must now sort
1437     "   the right partition.
1438     if low < a:end
1439       call s:QSortR(low, a:end, a:cmp, a:direction, a:accessor, a:swapper,
1440             \ a:context)
1441     endif
1442   endif
1443 endfunction
1444
1445 function! genutils#BinSearchForInsert(start, end, line, cmp, direction)
1446   return genutils#BinSearchForInsert2(a:start, a:end, a:line, a:cmp,
1447         \ a:direction, 's:BufLineAccessor', '')
1448 endfunction
1449
1450 function! genutils#BinSearchForInsert2(start, end, line, cmp, direction,
1451       \ accessor, context)
1452   let start = a:start - 1
1453   let end = a:end
1454   while start < end
1455     let middle = (start + end + 1) / 2
1456     " Support passing both Funcref's as well as names.
1457     if type(a:cmp) == 2
1458       if type(a:accessor) == 2
1459         let result = a:cmp(a:accessor(middle, a:context), a:line, a:direction)
1460       else
1461         let result = a:cmp({a:accessor}(middle, a:context), a:line, a:direction)
1462       endif
1463     else
1464       if type(a:accessor) == 2
1465         let result = {a:cmp}(a:accessor(middle, a:context), a:line, a:direction)
1466       else
1467         let result = {a:cmp}({a:accessor}(middle, a:context), a:line, a:direction)
1468       endif
1469     endif
1470     if result < 0
1471       let start = middle
1472     else
1473       let end = middle - 1
1474     endif
1475   endwhile
1476   return start
1477 endfunction
1478
1479 function! genutils#BinSearchList(list, start, end, item, cmp)
1480   let start = a:start - 1
1481   let end = a:end
1482   while start < end
1483     let middle = (start + end + 1) / 2
1484     let result = call(a:cmp, [get(a:list, middle), a:item])
1485     if result < 0
1486       let start = middle
1487     else
1488       let end = middle - 1
1489     endif
1490   endwhile
1491   return start
1492 endfunction
1493
1494 function! genutils#BinInsertSort(cmp, direction) range
1495   call genutils#BinInsertSort2(a:firstline, a:lastline, a:cmp, a:direction,
1496         \ 's:BufLineAccessor', 's:BufLineMover', '')
1497 endfunction
1498
1499 function! genutils#BinInsertSort2(start, end, cmp, direction, accessor, mover, context)
1500   let i = a:start + 1
1501   while i <= a:end
1502     let low = s:BinSearchToAppend2(a:start, i, {a:accessor}(i, a:context),
1503           \ a:cmp, a:direction, a:accessor, a:context)
1504     " Move the object.
1505     if low < i
1506       call {a:mover}(i, low - 1, a:context)
1507     endif
1508     let i = i + 1
1509   endwhile
1510 endfunction
1511
1512 function! s:BinSearchToAppend(start, end, line, cmp, direction)
1513   return s:BinSearchToAppend2(a:start, a:end, a:line, a:cmp, a:direction,
1514         \ 's:BufLineAccessor', '')
1515 endfunction
1516
1517 function! s:BinSearchToAppend2(start, end, line, cmp, direction, accessor,
1518       \ context)
1519   let low = a:start
1520   let high = a:end
1521   while low < high
1522     let mid = (low + high) / 2
1523     let diff = {a:cmp}({a:accessor}(mid, a:context), a:line, a:direction)
1524     if diff > 0
1525       let high = mid
1526     else
1527       let low = mid + 1
1528       if diff == 0
1529         break
1530       endif
1531     endif
1532   endwhile
1533   return low
1534 endfunction
1535
1536 """ END: Sorting support. }}}
1537
1538
1539 " Eats character if it matches the given pattern.
1540 "
1541 " Originally,
1542 " From: Benji Fisher <fisherbb@bc.edu>
1543 " Date: Mon, 25 Mar 2002 15:05:14 -0500
1544 "
1545 " Based on Bram's idea of eating a character while type <Space> to expand an
1546 "   abbreviation. This solves the problem with abbreviations, where we are
1547 "   left with an extra space after the expansion.
1548 " Ex:
1549 "   inoreabbr \stdout\ System.out.println("");<Left><Left><Left><C-R>=genutils#EatChar('\s')<CR>
1550 function! genutils#EatChar(pat)
1551    let c = nr2char(getchar())
1552    "call input('Pattern: '.a:pat.' '.
1553    "      \ ((c =~ a:pat) ? 'Returning empty' : 'Returning: '.char2nr(c)))
1554    return (c =~ a:pat) ? '' : c
1555 endfun
1556
1557
1558 " Can return a spacer from 0 to 80 characters width.
1559 let s:spacer= "                                                               ".
1560       \ "                 "
1561 function! genutils#GetSpacer(width)
1562   return strpart(s:spacer, 0, a:width)
1563 endfunction
1564
1565 function! genutils#SilentSubstitute(pat, cmd)
1566   call genutils#SaveHardPosition('SilentSubstitute')
1567   let _search = @/
1568   try
1569     let @/ = a:pat
1570     keepjumps silent! exec a:cmd
1571   finally
1572     let @/ = _search
1573     call genutils#RestoreHardPosition('SilentSubstitute')
1574     call genutils#ResetHardPosition('SilentSubstitute')
1575   endtry
1576 endfunction
1577
1578 function! genutils#SilentDelete(arg1, ...)
1579   " For backwards compatibility.
1580   if a:0
1581     let range = a:arg1
1582     let pat = a:1
1583   else
1584     let range = ''
1585     let pat = a:arg1
1586   endif
1587   let _search = @/
1588   call genutils#SaveHardPosition('SilentDelete')
1589   try
1590     let @/ = pat
1591     keepjumps silent! exec range'g//d _'
1592   finally
1593     let @/ = _search
1594     call genutils#RestoreHardPosition('SilentDelete')
1595     call genutils#ResetHardPosition('SilentDelete')
1596   endtry
1597 endfunction
1598
1599 " START: genutils#Roman2Decimal {{{
1600 let s:I = 1
1601 let s:V = 5
1602 let s:X = 10
1603 let s:L = 50
1604 let s:C = 100
1605 let s:D = 500
1606 let s:M = 1000
1607
1608 function! s:Char2Num(c)
1609   " A bit of magic on empty strings
1610   if a:c == ""
1611     return 0
1612   endif
1613   exec 'let n = s:' . toupper(a:c)
1614   return n
1615 endfun
1616
1617 function! genutils#Roman2Decimal(str)
1618   if a:str !~? '^[IVXLCDM]\+$'
1619     return a:str
1620   endif
1621   let sum = 0
1622   let i = 0
1623   let n0 = s:Char2Num(a:str[i])
1624   let len = strlen(a:str)
1625   while i < len
1626     let i = i + 1
1627     let n1 = s:Char2Num(a:str[i])
1628     " Magic: n1=0 when i exceeds len
1629     if n1 > n0
1630       let sum = sum - n0
1631     else
1632       let sum = sum + n0
1633     endif
1634     let n0 = n1
1635   endwhile
1636   return sum
1637 endfun
1638 " END: genutils#Roman2Decimal }}}
1639
1640
1641 " BEGIN: Relative path {{{
1642 function! genutils#CommonPath(path1, path2, ...)
1643   let path1 = genutils#CleanupFileName(a:path1) . ((a:0 > 0 ? a:1 : 0) ? '/' : '')
1644   let path2 = genutils#CleanupFileName(a:path2) . ((a:0 > 1 ? a:2 : 0) ? '/' : '')
1645   return genutils#CommonString(path1, path2)
1646 endfunction
1647
1648 function! genutils#CommonString(str1, str2)
1649   let str1 = a:str1
1650   let str2 = a:str2
1651   if str1 == str2
1652     return str1
1653   endif
1654   let n = 0
1655   while str1[n] == str2[n]
1656     let n = n+1
1657   endwhile
1658   return strpart(str1, 0, n)
1659 endfunction
1660
1661 function! genutils#RelPathFromFile(srcFile, tgtFile)
1662   return genutils#RelPathFromDir(fnamemodify(a:srcFile, ':h'), a:tgtFile)
1663 endfunction
1664
1665 function! genutils#RelPathFromDir(srcDir, tgtFile)
1666   let cleanDir = genutils#CleanupFileName(a:srcDir).'/'
1667   let cleanFile = genutils#CleanupFileName(a:tgtFile)
1668   let cmnPath = genutils#CommonPath(cleanDir, cleanFile, 1)
1669   let relDirFromCmnPath = strpart(cleanDir, strlen(cmnPath))
1670   if relDirFromCmnPath == '/' " This means the file is under the srcDir.
1671     let relDirFromCmnPath = ''
1672   endif
1673   let relFileFromCmnPath = strpart(cleanFile, strlen(cmnPath))
1674   return substitute(relDirFromCmnPath, '[^/]\+', '..', 'g') .
1675         \ relFileFromCmnPath
1676 endfunction
1677
1678 " END: Relative path }}}
1679
1680
1681 " BEGIN: Persistent settings {{{
1682 if ! exists("g:genutilsNoPersist") || ! g:genutilsNoPersist
1683   " Make sure the '!' option to store global variables that are upper cased are
1684   "   stored in viminfo file.
1685   " Make sure it is the first option, so that it will not interfere with the
1686   "   'n' option ("Todd J. Cosgrove" (todd dot cosgrove at softechnics dot
1687   "   com)).
1688   " Also take care of empty value, when 'compatible' mode is on (Bram
1689   "   Moolenar)
1690   if &viminfo == ''
1691     set viminfo=!,'20,"50,h
1692   else
1693     set viminfo^=!
1694   endif
1695 endif
1696
1697 function! genutils#PutPersistentVar(pluginName, persistentVar, value)
1698   if ! exists("g:genutilsNoPersist") || ! g:genutilsNoPersist
1699     let globalVarName = s:PersistentVarName(a:pluginName, a:persistentVar)
1700     exec 'let ' . globalVarName . " = '" . a:value . "'"
1701   endif
1702 endfunction
1703
1704 function! genutils#GetPersistentVar(pluginName, persistentVar, default)
1705   if ! exists("g:genutilsNoPersist") || ! g:genutilsNoPersist
1706     let globalVarName = s:PersistentVarName(a:pluginName, a:persistentVar)
1707     if (exists(globalVarName))
1708       exec 'let value = ' . globalVarName
1709       exec 'unlet ' . globalVarName
1710     else
1711       let value = a:default
1712     endif
1713     return value
1714   else
1715     return default
1716   endif
1717 endfunction
1718
1719 function! s:PersistentVarName(pluginName, persistentVar)
1720   return 'g:GU_' . toupper(a:pluginName) . '_' . toupper(a:persistentVar)
1721 endfunction
1722 " END: Persistent settings }}}
1723
1724
1725 " FileChangedShell handling {{{
1726 if !exists('s:fcShellPreFuncs')
1727   let s:fcShellPreFuncs = {}
1728 endif
1729
1730 function! genutils#AddToFCShellPre(funcName)
1731   let s:fcShellPreFuncs[a:funcName] = a:funcName
1732 endfunction
1733
1734 function! genutils#RemoveFromFCShellPre(funcName)
1735   if has_key(s:fcShellPreFuncs, a:funcName)
1736     unlet s:fcShellPreFuncs[a:funcName]
1737   endif
1738 endfunction
1739
1740 let s:defFCShellInstalled = 0
1741 function! genutils#DefFCShellInstall()
1742   if ! s:defFCShellInstalled
1743     aug DefFCShell
1744     au!
1745     au FileChangedShell * nested call genutils#DefFileChangedShell()
1746     aug END
1747   endif
1748   let s:defFCShellInstalled = s:defFCShellInstalled + 1
1749 endfunction
1750
1751 function! genutils#DefFCShellUninstall()
1752   if s:defFCShellInstalled <= 0
1753     return
1754   endif
1755   let s:defFCShellInstalled = s:defFCShellInstalled - 1
1756   if ! s:defFCShellInstalled
1757     aug DefFCShell
1758     au!
1759     aug END
1760   endif
1761 endfunction
1762
1763 function! genutils#DefFileChangedShell()
1764   let autoread = s:InvokeFuncs(s:fcShellPreFuncs)
1765   let bufNo = expand('<abuf>') + 0
1766   let fName = expand('<afile>')
1767   let msg = ''
1768   let v:fcs_choice = ''
1769   if getbufvar(bufNo, '&modified')
1770     let v:fcs_choice = 'ask'
1771   elseif ! autoread
1772     let v:fcs_choice = 'ask'
1773   else
1774     let v:fcs_choice = 'reload'
1775   endif
1776   return
1777 endfunction
1778
1779 function! s:InvokeFuncs(funcList)
1780   let autoread = &autoread
1781   if len(a:funcList) != 0
1782     for nextFunc in keys(a:funcList)
1783       let result = call(nextFunc, [])
1784       if result != -1
1785         let autoread = autoread || result
1786       endif
1787     endfor
1788   endif
1789   return autoread
1790 endfunction
1791 " FileChangedShell handling }}}
1792
1793
1794 " Sign related utilities {{{
1795 function! genutils#CurLineHasSign()
1796   let signs = genutils#GetVimCmdOutput('sign place buffer=' . bufnr('%'), 1)
1797   return (match(signs,
1798         \ 'line=' . line('.') . '\s\+id=\d\+\s\+name=VimBreakPt') != -1)
1799 endfunction
1800
1801 function! genutils#ClearAllSigns()
1802   let signs = genutils#GetVimCmdOutput('sign place buffer=' . bufnr('%'), 1)
1803   let curIdx = 0
1804   let pat = 'line=\d\+\s\+id=\zs\d\+\ze\s\+name=VimBreakPt'
1805   let id = 0
1806   while curIdx != -1 && curIdx < strlen(signs)
1807     let id = matchstr(signs, pat, curIdx)
1808     if id != ''
1809       exec 'sign unplace ' . id . ' buffer=' . bufnr('%')
1810     endif
1811     let curIdx = matchend(signs, pat, curIdx)
1812   endwhile
1813 endfunction
1814 " }}}
1815
1816 let s:UNPROTECTED_CHAR_PRFX = '\%(\%([^\\]\|^\)\\\%(\\\\\)*\)\@<!' " Doesn't eat slashes.
1817 "let s:UNPROTECTED_CHAR_PRFX = '\\\@<!\%(\\\\\)*' " Eats slashes.
1818 function! genutils#CrUnProtectedCharsPattern(chars, ...)
1819   let capture = (a:0 > 0?1:0)
1820   let regex = s:UNPROTECTED_CHAR_PRFX
1821   let chars = a:chars
1822   if strlen(chars) > 1
1823     let chars = '['.chars.']'
1824   endif
1825   if capture
1826     let chars = '\('.chars.'\)'
1827   endif
1828   return regex.chars
1829 endfunction
1830
1831 function! genutils#PromptForElement(array, default, msg, skip, useDialog,
1832       \ nCols)
1833   let nCols = a:nCols
1834   let index = 0
1835   let line = ""
1836   let element = ""
1837   let optionsMsg = ""
1838   let colWidth = &columns / nCols - 1 " Leave a margin of one column as a gap.
1839   let curCol = 1
1840   let nElements = len(a:array)
1841   let newArray = [] " Without the skip element.
1842   if index(a:array, a:skip) != -1
1843     let nElements = nElements - 1
1844   endif
1845   for element in a:array
1846     if element ==# a:skip
1847       continue
1848     endif
1849     call add(newArray, element)
1850     let element = strpart(index."   ", 0, 4) . element
1851     let eleColWidth = (strlen(element) - 1) / colWidth + 1
1852     " Fill up the spacer for the rest of the partial column.
1853     let element = element . genutils#GetSpacer(
1854           \ eleColWidth * (colWidth + 1) - strlen(element) - 1)
1855     let wouldBeLength = strlen(line) + strlen(element) + 1
1856     if wouldBeLength > (curCol * (colWidth + eleColWidth)) &&
1857           \ wouldBeLength > &columns
1858       let splitLine = 2 " Split before adding the new element.
1859     elseif curCol == nCols
1860       let splitLine = 1 " Split after adding the new element.
1861     else
1862       let splitLine = 0
1863     endif
1864     if splitLine == 2
1865       if strlen(line) == &columns
1866         " Remove the last space as it otherwise results in an extra empty line
1867         " on the screen.
1868         let line = strpart(line, 0, strlen(line) - 1)
1869       endif
1870       let optionsMsg = optionsMsg . line . "\n"
1871       let line = element . ' '
1872       let curCol = strlen(element) / (colWidth + 1)
1873     else
1874       let line = line . element . ' '
1875       if splitLine == 1
1876         if strlen(line) == &columns
1877           " Remove the last space as it otherwise results in an extra empty line
1878           " on the screen.
1879           let line = strpart(line, 0, strlen(line) - 1)
1880         endif
1881         let curCol = 0 " Reset col count.
1882         let optionsMsg = optionsMsg . line . "\n"
1883         let line = ""
1884       endif
1885     endif
1886     let curCol = curCol + 1
1887     let index = index + 1
1888   endfor
1889   " Finally if there is anything left in line, then append that too.
1890   if line.'' != ''
1891     let optionsMsg = optionsMsg . line . "\n"
1892     let line = ""
1893   endif
1894
1895   " Default index or empty string.
1896   let default = ''
1897   if type(a:default) == 0
1898     let default = a:default
1899   elseif a:default.'' != ''
1900     let default = index(a:array, a:default)
1901   endif
1902   if a:default == -1
1903     let default = ''
1904   endif
1905
1906   while !exists("selectedElement")
1907     if a:useDialog
1908       let s:selection = inputdialog(optionsMsg . a:msg, default)
1909     else
1910       let s:selection = input(optionsMsg . a:msg, default)
1911     endif
1912     if s:selection.'' == ''
1913       let selectedElement = ''
1914       let s:selection = -1
1915     else
1916       let s:selection = (s:selection !~# '^\d\+$') ? -1 : (s:selection + 0)
1917       if s:selection >= 0 && s:selection < nElements
1918         let selectedElement = newArray[s:selection]
1919       else
1920         echohl ERROR | echo "\nInvalid selection, please try again" |
1921               \ echohl NONE
1922       endif
1923     endif
1924     echo "\n"
1925   endwhile
1926   return selectedElement
1927 endfunction
1928
1929 let s:selection = -1
1930 function! genutils#GetSelectedIndex()
1931   return s:selection
1932 endfunction
1933
1934 " Always match() with 'ignorecase' and 'smartcase' off.
1935 function! s:Match(expr, pat, start)
1936   let _ic = &ignorecase
1937   let _scs = &smartcase
1938   let result = -1
1939   try
1940     set noignorecase
1941     set nosmartcase
1942     let result = match(a:expr, a:pat, a:start)
1943   finally
1944     let &ignorecase = _ic
1945     let &smartcase = _scs
1946   endtry
1947   return result
1948 endfunction
1949  
1950 " Restore cpo.
1951 let &cpo = s:save_cpo
1952 unlet s:save_cpo
1953
1954 " vim6:fdm=marker et