1 " genutils.vim: Please see plugin/genutils.vim
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
9 " - http://www.vim.org/tips/tip.php?tip_id=1379
11 " - EscapeCommand() didn't work for David Fishburn.
12 " - Save/RestoreWindowSettings doesn't work well.
15 " - Save/RestoreWindowSettings can use winsave/restview() functions.
18 " Make sure line-continuations won't cause any problem. This will be restored
24 let g:makeArgumentString = 'exec genutils#MakeArgumentString()'
25 let g:makeArgumentList = 'exec genutils#MakeArgumentList()'
27 let s:makeArgumentString = ''
28 function! genutils#MakeArgumentString(...)
29 if s:makeArgumentString == ''
30 let s:makeArgumentString = genutils#ExtractFuncListing(s:SNR().
31 \ '_makeArgumentString', 0, 0)
33 if a:0 > 0 && a:1 != ''
34 return substitute(s:makeArgumentString, '\<argumentString\>', a:1, 'g')
36 return s:makeArgumentString
41 let s:makeArgumentList = ''
42 function! genutils#MakeArgumentList(...)
43 if s:makeArgumentList == ''
44 let s:makeArgumentList = genutils#ExtractFuncListing(s:SNR().
45 \ '_makeArgumentList', 0, 0)
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."'", '')
55 return s:makeArgumentList
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\+',
69 let listing = substitute(listing, '^\%([^'."\n".']*'."\n".'\)\{'.
70 \ a:hLines.'}', '', '')
73 let listing = substitute(listing, '\%([^'."\n".']*'."\n".'\)\{'.
74 \ a:tLines.'}$', '', '')
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
83 let argList = split(substitute(a:argList, a:sep.'$', '', ''), a:sep, 1)
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.
89 let nextArg = substitute(nextArg, "'", "' . \"'\" . '", 'g')
91 let argString = argString . nextArg . "', '"
93 let argString = strpart(argString, 0, strlen(argString) - 3)
98 function! s:_makeArgumentString()
100 let argumentString = ''
101 while __argCounter <= a:0
102 if type(a:{__argCounter})
103 let __nextArg = "'" .
104 \ substitute(a:{__argCounter}, "'", "' . \"'\" . '", "g") . "'"
106 let __nextArg = a:{__argCounter}
108 let argumentString = argumentString. __nextArg .
109 \ ((__argCounter == a:0) ? '' : ', ')
110 let __argCounter = __argCounter + 1
113 if exists('__nextArg')
118 function! s:_makeArgumentList()
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
127 let __argCounter = __argCounter + 1
135 function! genutils#DebugShowArgs(...)
139 let argString = argString . a:{i + 1} . ', '
142 let argString = strpart(argString, 0, strlen(argString) - 2)
143 call input("Args: " . argString)
146 " Window related functions {{{
148 function! genutils#NumberOfWindows()
150 while winbufnr(i) != -1
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))
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, '#%'))
169 function! s:FindBufferForName(fileName)
170 let fileName = genutils#Escape(a:fileName, '[?,{')
175 let i = bufnr('^' . fileName . '$')
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, '*?,{}[ ')
190 function! genutils#MoveCursorToWindow(winno)
191 if genutils#NumberOfWindows() != 1
192 execute a:winno . " wincmd w"
196 function! genutils#MoveCurLineToWinLine(n)
208 execute "normal! " . n . "\<C-Y>"
212 function! genutils#CloseWindow(win, force)
213 let _eventignore = &eventignore
216 call genutils#MarkActiveWindow()
218 let &eventignore = _eventignore
219 exec a:win 'wincmd w'
220 exec 'close'.(a:force ? '!' : '')
223 if a:win < t:curWinnr
224 let t:curWinnr = t:curWinnr - 1
226 if a:win < t:prevWinnr
227 let t:prevWinnr = t:prevWinnr - 1
230 call genutils#RestoreActiveWindow()
231 let &eventignore = _eventignore
235 function! genutils#MarkActiveWindow()
236 let t:curWinnr = winnr()
237 " We need to restore the previous-window also at the end.
239 let t:prevWinnr = winnr()
243 function! genutils#RestoreActiveWindow()
244 if !exists('t:curWinnr')
248 " Restore the original window.
249 if winnr() != t:curWinnr
250 exec t:curWinnr'wincmd w'
252 if t:curWinnr != t:prevWinnr
253 exec t:prevWinnr'wincmd w'
258 function! genutils#IsOnlyVerticalWindow()
260 let _eventignore = &eventignore
263 "set eventignore+=WinEnter,WinLeave
265 call genutils#MarkActiveWindow()
268 if winnr() != t:curWinnr
272 if winnr() != t:curWinnr
277 call genutils#RestoreActiveWindow()
278 let &eventignore = _eventignore
283 function! genutils#IsOnlyHorizontalWindow()
285 let _eventignore = &eventignore
288 call genutils#MarkActiveWindow()
290 if winnr() != t:curWinnr
294 if winnr() != t:curWinnr
299 call genutils#RestoreActiveWindow()
300 let &eventignore = _eventignore
305 function! genutils#MoveCursorToNextInWinStack(dir)
306 let newwin = genutils#GetNextWinnrInStack(a:dir)
308 exec newwin 'wincmd w'
312 function! genutils#GetNextWinnrInStack(dir)
314 let _eventignore = &eventignore
317 call genutils#MarkActiveWindow()
318 let newwin = s:GetNextWinnrInStack(a:dir)
320 call genutils#RestoreActiveWindow()
321 let &eventignore = _eventignore
326 function! genutils#MoveCursorToLastInWinStack(dir)
327 let newwin = genutils#GetLastWinnrInStack(a:dir)
329 exec newwin 'wincmd w'
333 function! genutils#GetLastWinnrInStack(dir)
335 let _eventignore = &eventignore
338 call genutils#MarkActiveWindow()
340 let wn = s:GetNextWinnrInStack(a:dir)
343 exec newwin 'wincmd w'
349 call genutils#RestoreActiveWindow()
350 let &eventignore = _eventignore
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.">)")
360 let isHorizontalMov = (a:dir ==# 'h' || a:dir ==# 'l') ? 1 : 0
363 let orgdim = s:GetWinDim(a:dir, orgwin)
365 let _winwidth = &winwidth
366 let _winheight = &winheight
373 " No more windows in this direction.
374 "call Decho("newwin=".newwin." stopped".winheight(newwin)."x".winwidth(newwin))
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))
382 " Determine if changing original window height affects current window
384 exec orgwin 'wincmd w'
387 exec 'wincmd' (isHorizontalMov ? '_' : '|')
389 exec 'wincmd' (isHorizontalMov ? '-' : '<')
391 if s:GetWinDim(a:dir, newwin) != s:GetWinDim(a:dir, orgwin)
392 "call Decho("newwin=".newwin." different row".winheight(newwin)."x".winwidth(newwin))
395 "call Decho("newwin=".newwin." same row".winheight(newwin)."x".winwidth(newwin))
397 exec (isHorizontalMov ? '' : 'vert') 'resize' orgdim
400 "call Decho("genutils#MoveCursorToNextInWinStack")
404 let &winwidth = _winwidth
405 let &winheight = _winheight
409 function! s:GetWinDim(dir, win)
410 return (a:dir ==# 'h' || a:dir ==# 'l') ? winheight(a:win) : winwidth(a:win)
413 function! genutils#OpenWinNoEa(winOpenCmd)
414 call s:ExecWinCmdNoEa(a:winOpenCmd)
417 function! genutils#CloseWinNoEa(winnr, force)
418 call s:ExecWinCmdNoEa(a:winnr.'wincmd w | close'.(a:force?'!':''))
421 function! s:ExecWinCmdNoEa(winCmd)
422 let _eventignore = &eventignore
425 call genutils#MarkActiveWindow()
426 windo let w:_winfixheight = &winfixheight
427 windo set winfixheight
428 call genutils#RestoreActiveWindow()
430 let &eventignore = _eventignore
434 call genutils#MarkActiveWindow()
435 silent! windo let &winfixheight = w:_winfixheight
436 silent! windo unlet w:_winfixheight
437 call genutils#RestoreActiveWindow()
439 let &eventignore = _eventignore
443 " Window related functions }}}
445 function! genutils#SetupScratchBuffer()
448 setlocal buftype=nofile
449 " Just in case, this will make sure we are always hidden.
450 setlocal bufhidden=delete
453 setlocal foldcolumn=0 nofoldenable
457 function! genutils#CleanDiffOptions()
459 setlocal noscrollbind
460 setlocal scrollopt-=hor
462 setlocal foldmethod=manual
463 setlocal foldcolumn=0
467 function! genutils#ArrayVarExists(varName, index)
469 exec "let test = " . a:varName . "{a:index}"
470 catch /^Vim\%((\a\+)\)\=:E121/
476 function! genutils#Escape(str, chars)
477 return substitute(a:str, '\\\@<!\(\%(\\\\\)*\)\([' . a:chars .']\)', '\1\\\2',
481 function! genutils#UnEscape(str, chars)
482 return substitute(a:str, '\\\@<!\(\\\\\)*\\\([' . a:chars . ']\)',
486 function! genutils#DeEscape(str)
488 let str = substitute(str, '\\\(\\\|[^\\]\)', '\1', 'g')
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
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)
502 let args = copy(a:args)
504 let args = split(a:args, genutils#CrUnProtectedCharsPattern(' '))
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
516 " genutils#Escape the existing double-quotes (by doubling them).
517 call map(args, "substitute(v:val, '\"', '\"\"', 'g')")
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')")
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 = "'\"\"'\"\"'"
529 let squoteRepl = "'\"'\"'"
531 call map(args, "substitute(v:val, \"'\", squoteRepl, 'g')")
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.
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*$'
546 let fullCmd = fullCmd . ' ' . a:pipe
549 let fullCmd = a:cmd . ' ' . fullCmd
551 if shellEnvType ==# g:ST_WIN_SH && &shell =~# '\<bash\>'
552 let fullCmd = substitute(fullCmd, '\\', '\\\\', 'g')
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
573 function! genutils#ExpandStr(str)
574 let str = substitute(a:str, '"', '\\"', 'g')
575 exec "let str = \"" . str . "\""
579 function! genutils#QuoteStr(str)
580 return "'".substitute(a:str, "'", "'.\"'\".'", 'g')."'"
583 function! genutils#GetPreviewWinnr()
584 let _eventignore = &eventignore
585 let curWinNr = winnr()
591 catch /^Vim\%((\a\+)\)\=:E441/
592 " Ignore, winnr is already set to -1.
594 if winnr() != curWinNr
595 exec curWinNr.'wincmd w'
597 let &eventignore = _eventignore
602 " Save/Restore window settings {{{
603 function! genutils#SaveWindowSettings()
604 call genutils#SaveWindowSettings2('SaveWindowSettings', 1)
607 function! genutils#RestoreWindowSettings()
608 call genutils#RestoreWindowSettings2('SaveWindowSettings')
612 function! genutils#ResetWindowSettings()
613 call genutils#ResetWindowSettings2('SaveWindowSettings')
616 function! genutils#SaveWindowSettings2(id, overwrite)
617 if genutils#ArrayVarExists("t:winSettings", a:id) && ! a:overwrite
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())
627 while winbufnr(i) != -1
628 call add(t:winSettings{a:id}, winheight(i))
629 call add(t:winSettings{a:id}, winwidth(i))
632 "let g:savedWindowSettings = t:winSettings{a:id} " Debug.
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)
642 function! s:RestoreWindowSettings2(id)
643 "if ! exists("t:winSettings" . a:id)
644 if ! genutils#ArrayVarExists("t:winSettings", a:id)
648 let nWindows = t:winSettings{a:id}[0]
649 if nWindows != genutils#NumberOfWindows()
650 unlet t:winSettings{a:id}
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)
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
670 if winwidth(winnr()) != width
671 exec 'vert' winNo 'resize' width
673 let winNo = winNo + 1
677 " Restore the current window.
678 call genutils#MoveCursorToWindow(activeWindow)
679 "unlet g:savedWindowSettings
683 function! genutils#ResetWindowSettings2(id)
684 if genutils#ArrayVarExists("t:winSettings", a:id)
685 unlet t:winSettings{a:id}
689 " Save/Restore window settings }}}
691 " Save/Restore selection {{{
693 function! genutils#SaveVisualSelection(id)
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}
708 function! genutils#RestoreVisualSelection(id)
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'
718 " Save/Restore selection }}}
720 function! genutils#CleanupFileName(fileName)
721 return genutils#CleanupFileName2(a:fileName, '')
724 function! genutils#CleanupFileName2(fileName, win32ProtectedChars)
725 let fileName = substitute(a:fileName, '^\s\+\|\s\+$', '', 'g')
727 " Expand relative paths and paths containing relative components (takes care
729 if ! genutils#PathIsAbsolute(fileName)
730 let fileName = fnamemodify(fileName, ':p')
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)
741 " Remove multiple path separators.
743 if a:win32ProtectedChars != ''
744 let fileName=substitute(fileName, '\\['.a:win32ProtectedChars.']\@!', '/',
747 let fileName=substitute(fileName, '\\', '/', 'g')
749 elseif genutils#OnMS()
750 " On non-win32 systems, the forward-slash is not supported, so leave
752 let fileName=substitute(fileName, '\\\{2,}', '\', 'g')
754 let fileName=substitute(fileName, '/\{2,}', '/', 'g')
756 " Remove ending extra path separators.
757 let fileName=substitute(fileName, '/$', '', '')
758 let fileName=substitute(fileName, '\\$', '', '')
760 " If it was an UNC path, add back an extra slash.
762 let fileName = '/'.fileName
766 let fileName=substitute(fileName, '^[A-Z]:', '\L&', '')
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
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')
782 function! genutils#OnMS()
783 return has('win32') || has('dos32') || has('win16') || has('dos16') ||
787 function! genutils#PathIsAbsolute(path)
789 if has('unix') || genutils#OnMS()
790 if match(a:path, '^/') == 0
794 if (! absolute) && genutils#OnMS()
795 if match(a:path, "^\\") == 0
799 if (! absolute) && genutils#OnMS()
800 if match(a:path, "^[A-Za-z]:") == 0
807 function! genutils#PathIsFileNameOnly(path)
808 return (match(a:path, "\\") < 0) && (match(a:path, "/") < 0)
814 let s:mySNR = matchstr(expand('<sfile>'), '<SNR>\d\+_\zeSNR$')
820 "" --- START save/restore position. {{{
822 function! genutils#SaveSoftPosition(id)
823 let b:sp_startline_{a:id} = getline(".")
824 call genutils#SaveHardPosition(a:id)
827 function! genutils#RestoreSoftPosition(id)
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')
836 call genutils#MoveCurLineToWinLine(b:sp_winline_{a:id})
839 function! genutils#ResetSoftPosition(id)
840 unlet b:sp_startline_{a:id}
843 " A synonym for genutils#SaveSoftPosition.
844 function! genutils#SaveHardPositionWithContext(id)
845 call genutils#SaveSoftPosition(a:id)
848 " A synonym for genutils#RestoreSoftPosition.
849 function! genutils#RestoreHardPositionWithContext(id)
850 call genutils#RestoreSoftPosition(a:id)
853 " A synonym for genutils#ResetSoftPosition.
854 function! genutils#ResetHardPositionWithContext(id)
855 call genutils#ResetSoftPosition(a:id)
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.
865 let b:sp_winline_{a:id} = winline()
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
877 execute ((line('$') < b:sp_lin_{a:id}) ? line('$') :
879 "execute b:sp_lin_{a:id}
880 execute ((line('$') < b:sp_lin_{a:id}) ? line('$') :
882 "execute b:sp_lin_{a:id}
883 execute "normal!" b:sp_col_{a:id} . "|"
884 call genutils#MoveCurLineToWinLine(b:sp_winline_{a:id})
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}
893 function! genutils#GetLinePosition(id)
894 return b:sp_lin_{a:id}
897 function! genutils#GetColPosition(id)
898 return b:sp_col_{a:id}
901 function! genutils#IsPositionSet(id)
902 return exists('b:sp_col_' . a:id)
905 "" --- END save/restore position. }}}
910 "" --- START: Notify window close -- {{{
913 let s:notifyWindow = {}
914 function! genutils#AddNotifyWindowClose(windowTitle, functionName)
915 let bufName = a:windowTitle
917 let s:notifyWindow[bufName] = a:functionName
919 "let g:notifyWindow = s:notifyWindow " Debug.
921 " Start listening to events.
922 aug NotifyWindowClose
924 au WinEnter * :call genutils#CheckWindowClose()
925 au BufEnter * :call genutils#CheckWindowClose()
929 function! genutils#RemoveNotifyWindowClose(windowTitle)
930 let bufName = a:windowTitle
932 if has_key(s:notifyWindow, bufName)
933 call remove(s:notifyWindow, bufName)
934 if len(s:notifyWindow) == 0
935 "unlet g:notifyWindow " Debug.
937 aug NotifyWindowClose
944 function! genutils#CheckWindowClose()
945 if !exists('s:notifyWindow')
949 " Now iterate over all the registered window titles and see which one's are
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])
963 "function! NotifyWindowCloseF(title)
964 " call input(a:title . " closed")
967 "function! RunNotifyWindowCloseTest()
968 " call input("Creating three windows, 'ABC', 'XYZ' and 'b':")
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:")
985 "" --- END: Notify window close -- }}}
988 " TODO: For large ranges, the cmd can become too big, so make it one cmd per
990 function! genutils#ShowLinesWithSyntax() range
991 " This makes sure we start (subsequent) echo's on the first line in the
997 let prev_group = ' x ' " Something that won't match any syntax group.
999 let show_line = a:firstline
1000 let isMultiLine = ((a:lastline - a:firstline) > 1)
1001 while show_line <= a:lastline
1003 let length = strlen(getline(show_line))
1006 while column <= length
1007 let group = synIDattr(synID(show_line, column, 1), 'name')
1008 if group != prev_group
1010 let cmd = cmd . "'|"
1012 let cmd = cmd . 'echohl ' . (group == '' ? 'NONE' : group) . "|echon '"
1013 let prev_group = group
1015 let char = strpart(getline(show_line), column - 1, 1)
1017 let char = "'."'".'"
1019 let cmd = cmd . char
1020 let column = column + 1
1028 let show_line = show_line + 1
1034 function! genutils#AlignWordWithWordInPreviousLine()
1035 "First go to the first col in the word.
1036 if getline('.')[col('.') - 1] =~ '\s'
1041 let orgVcol = virtcol('.')
1042 let prevLnum = prevnonblank(line('.') - 1)
1046 let prevLine = getline(prevLnum)
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
1056 if strlen(prevLine) < orgVcol
1057 let nonSpaceStrInd = strlen(prevLine) - 1
1059 let nonSpaceStrInd = orgVcol - 1
1062 while prevLine[nonSpaceStrInd - 1] !~ '\s' && nonSpaceStrInd > 0
1063 let nonSpaceStrInd = nonSpaceStrInd - 1
1066 let newVcol = nonSpaceStrInd + 1 " Convert to column number.
1068 if orgVcol > newVcol " We need to reduce the spacing.
1069 let sub = strpart(getline('.'), newVcol - 1, (orgVcol - newVcol))
1071 " Remove the excess space.
1072 exec 'normal! ' . newVcol . '|'
1073 exec 'normal! ' . (orgVcol - newVcol) . 'x'
1075 elseif orgVcol < newVcol " We need to insert spacing.
1076 exec 'normal! ' . orgVcol . '|'
1077 exec 'normal! ' . (newVcol - orgVcol) . 'i '
1081 function! genutils#ShiftWordInSpace(dir)
1082 if a:dir == 1 " forward.
1083 " If currently on <Space>...
1084 if getline(".")[col(".") - 1] == " "
1088 "if getline(".")[col(".") + 1]
1091 let removeCommand = "x"
1092 let pasteCommand = "bi "
1096 " If currently on <Space>...
1097 if getline(".")[col(".") - 1] == " "
1102 let removeCommand = "hx"
1103 let pasteCommand = 'h"_yiwEa '
1108 let savedCol = col(".")
1109 exec "normal!" move1
1110 let curCol = col(".")
1112 " Check if there is a space at the end.
1113 if col("$") == (curCol + 1) " Works only for forward case, as expected.
1115 elseif getline(".")[curCol + offset] == " "
1116 " Remove the space from here.
1117 exec "normal!" removeCommand
1121 " Move back into the word.
1122 "exec "normal!" savedCol . "|"
1124 exec "normal!" pasteCommand
1125 exec "normal!" move2
1127 " Move to the original location.
1128 exec "normal!" savedCol . "|"
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\+ \+'
1140 let matchExpr = ' \+\(\w*\%' . orgCol . 'c\w*\) \+'
1142 let matchInd = match(line, matchExpr)
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
1153 let newStr = newStr . ' '
1154 let middle = middle - 1
1156 let newStr = newStr . word
1158 let newStr = newStr . ' '
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)
1168 function! genutils#MapAppendCascaded(lhs, rhs, mapMode)
1170 " Determine the map mode from the map command.
1171 let mapChar = strpart(a:mapMode, 0, 1)
1173 " Check if this is already mapped.
1174 let oldrhs = maparg(a:lhs, mapChar)
1180 "echomsg a:mapMode . "oremap" . " " . a:lhs . " " . self . a:rhs
1181 exec a:mapMode . "oremap" a:lhs self . a:rhs
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,
1192 if exists('+shellslash') && ! &shellslash && a:smartSlash &&
1193 \ stridx(a:ArgLead, "\\") != -1
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, "\\"),
1206 let glob = glob . matches . "\n"
1210 let glob = s:FixPathSep(glob(a:ArgLead.'*'), opathsep, npathsep)
1212 " FIXME: Need an option to control if ArgLead should also be returned or
1214 return glob."\n".a:ArgLead
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')
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')
1231 function! genutils#GetVimCmdOutput(cmd)
1235 let _shortmess = &shortmess
1241 let v:errmsg = substitute(v:exception, '^[^:]\+:', '', '')
1244 let &shortmess = _shortmess
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
1260 silent! keepjumps 0,$delete _
1262 let &undolevels = _undolevels
1263 let &l:modifiable = _modifiable
1268 "" START: Sorting support. {{{
1272 " Comapare functions.
1275 function! genutils#CmpByLineLengthNname(line1, line2, ...)
1276 let direction = (a:0?a:1:1)
1277 let cmp = genutils#CmpByLength(a:line1, a:line2, direction)
1279 let cmp = genutils#CmpByString(a:line1, a:line2, direction)
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)
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
1298 let cls1 = substitute(a:line1, '.* \(^[ ]\+\)', '\1', '')
1300 let pkg1 = substitute(a:line1, '.*import\s\+\(.*\)\.[^. ;]\+.*$', '\1', '')
1301 let cls1 = substitute(a:line1, '^.*\.\([^. ;]\+\).*$', '\1', '')
1303 if stridx(a:line2, '.') == -1
1305 let cls2 = substitute(a:line2, '.* \(^[ ]\+\)', '\1', '')
1307 let pkg2 = substitute(a:line2, '.*import\s\+\(.*\)\.[^. ;]\+.*$', '\1', '')
1308 let cls2 = substitute(a:line2, '^.*\.\([^. ;]\+\).*$', '\1', '')
1311 let cmp = genutils#CmpByString(pkg1, pkg2, direction)
1313 let cmp = genutils#CmpByLength(cls1, cls2, direction)
1318 function! genutils#CmpByString(line1, line2, ...)
1319 let direction = (a:0?a:1:1)
1320 if a:line1 < a:line2
1322 elseif a:line1 > a:line2
1329 function! genutils#CmpByStringIgnoreCase(line1, line2, ...)
1330 let direction = (a:0?a:1:1)
1331 if a:line1 <? a:line2
1333 elseif a:line1 >? a:line2
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
1354 function! genutils#QSort(cmp, direction) range
1355 call s:QSortR(a:firstline, a:lastline, a:cmp, a:direction,
1356 \ 's:BufLineAccessor', 's:BufLineSwapper', '')
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,
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)
1371 " The default accessor that returns lines from the current buffer.
1372 function! s:BufLineAccessor(line, context)
1373 return getline(a:line)
1376 " The default mover that moves lines from one place to another in the current
1378 function! s:BufLineMover(from, to, context)
1379 let line = getline(a:from)
1381 call append(a:to, line)
1385 " Sort lines. QSortR() is called recursively.
1387 function! s:QSortR(start, end, cmp, direction, accessor, swapper, context)
1392 " Arbitrarily establish partition element at the midpoint of the data.
1393 let midStr = {a:accessor}(((a:start + a:end) / 2), a:context)
1395 " Loop through the data until indices cross.
1398 " Find the first element that is greater than or equal to the partition
1399 " element starting from the left Index.
1401 let result = {a:cmp}({a:accessor}(low, a:context), midStr, a:direction)
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)
1420 " If the indexes have not crossed, swap.
1422 " Swap lines low and high.
1423 call {a:swapper}(high, low, a:context)
1429 " If the right index has not reached the left side of data must now sort
1430 " the left partition.
1432 call s:QSortR(a:start, high, a:cmp, a:direction, a:accessor, a:swapper,
1436 " If the left index has not reached the right side of data must now sort
1437 " the right partition.
1439 call s:QSortR(low, a:end, a:cmp, a:direction, a:accessor, a:swapper,
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', '')
1450 function! genutils#BinSearchForInsert2(start, end, line, cmp, direction,
1451 \ accessor, context)
1452 let start = a:start - 1
1455 let middle = (start + end + 1) / 2
1456 " Support passing both Funcref's as well as names.
1458 if type(a:accessor) == 2
1459 let result = a:cmp(a:accessor(middle, a:context), a:line, a:direction)
1461 let result = a:cmp({a:accessor}(middle, a:context), a:line, a:direction)
1464 if type(a:accessor) == 2
1465 let result = {a:cmp}(a:accessor(middle, a:context), a:line, a:direction)
1467 let result = {a:cmp}({a:accessor}(middle, a:context), a:line, a:direction)
1473 let end = middle - 1
1479 function! genutils#BinSearchList(list, start, end, item, cmp)
1480 let start = a:start - 1
1483 let middle = (start + end + 1) / 2
1484 let result = call(a:cmp, [get(a:list, middle), a:item])
1488 let end = middle - 1
1494 function! genutils#BinInsertSort(cmp, direction) range
1495 call genutils#BinInsertSort2(a:firstline, a:lastline, a:cmp, a:direction,
1496 \ 's:BufLineAccessor', 's:BufLineMover', '')
1499 function! genutils#BinInsertSort2(start, end, cmp, direction, accessor, mover, context)
1502 let low = s:BinSearchToAppend2(a:start, i, {a:accessor}(i, a:context),
1503 \ a:cmp, a:direction, a:accessor, a:context)
1506 call {a:mover}(i, low - 1, a:context)
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', '')
1517 function! s:BinSearchToAppend2(start, end, line, cmp, direction, accessor,
1522 let mid = (low + high) / 2
1523 let diff = {a:cmp}({a:accessor}(mid, a:context), a:line, a:direction)
1536 """ END: Sorting support. }}}
1539 " Eats character if it matches the given pattern.
1542 " From: Benji Fisher <fisherbb@bc.edu>
1543 " Date: Mon, 25 Mar 2002 15:05:14 -0500
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.
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
1558 " Can return a spacer from 0 to 80 characters width.
1561 function! genutils#GetSpacer(width)
1562 return strpart(s:spacer, 0, a:width)
1565 function! genutils#SilentSubstitute(pat, cmd)
1566 call genutils#SaveHardPosition('SilentSubstitute')
1570 keepjumps silent! exec a:cmd
1573 call genutils#RestoreHardPosition('SilentSubstitute')
1574 call genutils#ResetHardPosition('SilentSubstitute')
1578 function! genutils#SilentDelete(arg1, ...)
1579 " For backwards compatibility.
1588 call genutils#SaveHardPosition('SilentDelete')
1591 keepjumps silent! exec range'g//d _'
1594 call genutils#RestoreHardPosition('SilentDelete')
1595 call genutils#ResetHardPosition('SilentDelete')
1599 " START: genutils#Roman2Decimal {{{
1608 function! s:Char2Num(c)
1609 " A bit of magic on empty strings
1613 exec 'let n = s:' . toupper(a:c)
1617 function! genutils#Roman2Decimal(str)
1618 if a:str !~? '^[IVXLCDM]\+$'
1623 let n0 = s:Char2Num(a:str[i])
1624 let len = strlen(a:str)
1627 let n1 = s:Char2Num(a:str[i])
1628 " Magic: n1=0 when i exceeds len
1638 " END: genutils#Roman2Decimal }}}
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)
1648 function! genutils#CommonString(str1, str2)
1655 while str1[n] == str2[n]
1658 return strpart(str1, 0, n)
1661 function! genutils#RelPathFromFile(srcFile, tgtFile)
1662 return genutils#RelPathFromDir(fnamemodify(a:srcFile, ':h'), a:tgtFile)
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 = ''
1673 let relFileFromCmnPath = strpart(cleanFile, strlen(cmnPath))
1674 return substitute(relDirFromCmnPath, '[^/]\+', '..', 'g') .
1675 \ relFileFromCmnPath
1678 " END: Relative path }}}
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
1688 " Also take care of empty value, when 'compatible' mode is on (Bram
1691 set viminfo=!,'20,"50,h
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 . "'"
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
1711 let value = a:default
1719 function! s:PersistentVarName(pluginName, persistentVar)
1720 return 'g:GU_' . toupper(a:pluginName) . '_' . toupper(a:persistentVar)
1722 " END: Persistent settings }}}
1725 " FileChangedShell handling {{{
1726 if !exists('s:fcShellPreFuncs')
1727 let s:fcShellPreFuncs = {}
1730 function! genutils#AddToFCShellPre(funcName)
1731 let s:fcShellPreFuncs[a:funcName] = a:funcName
1734 function! genutils#RemoveFromFCShellPre(funcName)
1735 if has_key(s:fcShellPreFuncs, a:funcName)
1736 unlet s:fcShellPreFuncs[a:funcName]
1740 let s:defFCShellInstalled = 0
1741 function! genutils#DefFCShellInstall()
1742 if ! s:defFCShellInstalled
1745 au FileChangedShell * nested call genutils#DefFileChangedShell()
1748 let s:defFCShellInstalled = s:defFCShellInstalled + 1
1751 function! genutils#DefFCShellUninstall()
1752 if s:defFCShellInstalled <= 0
1755 let s:defFCShellInstalled = s:defFCShellInstalled - 1
1756 if ! s:defFCShellInstalled
1763 function! genutils#DefFileChangedShell()
1764 let autoread = s:InvokeFuncs(s:fcShellPreFuncs)
1765 let bufNo = expand('<abuf>') + 0
1766 let fName = expand('<afile>')
1768 let v:fcs_choice = ''
1769 if getbufvar(bufNo, '&modified')
1770 let v:fcs_choice = 'ask'
1772 let v:fcs_choice = 'ask'
1774 let v:fcs_choice = 'reload'
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, [])
1785 let autoread = autoread || result
1791 " FileChangedShell handling }}}
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)
1801 function! genutils#ClearAllSigns()
1802 let signs = genutils#GetVimCmdOutput('sign place buffer=' . bufnr('%'), 1)
1804 let pat = 'line=\d\+\s\+id=\zs\d\+\ze\s\+name=VimBreakPt'
1806 while curIdx != -1 && curIdx < strlen(signs)
1807 let id = matchstr(signs, pat, curIdx)
1809 exec 'sign unplace ' . id . ' buffer=' . bufnr('%')
1811 let curIdx = matchend(signs, pat, curIdx)
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
1822 if strlen(chars) > 1
1823 let chars = '['.chars.']'
1826 let chars = '\('.chars.'\)'
1831 function! genutils#PromptForElement(array, default, msg, skip, useDialog,
1838 let colWidth = &columns / nCols - 1 " Leave a margin of one column as a gap.
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
1845 for element in a:array
1846 if element ==# a:skip
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.
1865 if strlen(line) == &columns
1866 " Remove the last space as it otherwise results in an extra empty line
1868 let line = strpart(line, 0, strlen(line) - 1)
1870 let optionsMsg = optionsMsg . line . "\n"
1871 let line = element . ' '
1872 let curCol = strlen(element) / (colWidth + 1)
1874 let line = line . element . ' '
1876 if strlen(line) == &columns
1877 " Remove the last space as it otherwise results in an extra empty line
1879 let line = strpart(line, 0, strlen(line) - 1)
1881 let curCol = 0 " Reset col count.
1882 let optionsMsg = optionsMsg . line . "\n"
1886 let curCol = curCol + 1
1887 let index = index + 1
1889 " Finally if there is anything left in line, then append that too.
1891 let optionsMsg = optionsMsg . line . "\n"
1895 " Default index or empty string.
1897 if type(a:default) == 0
1898 let default = a:default
1899 elseif a:default.'' != ''
1900 let default = index(a:array, a:default)
1906 while !exists("selectedElement")
1908 let s:selection = inputdialog(optionsMsg . a:msg, default)
1910 let s:selection = input(optionsMsg . a:msg, default)
1912 if s:selection.'' == ''
1913 let selectedElement = ''
1914 let s:selection = -1
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]
1920 echohl ERROR | echo "\nInvalid selection, please try again" |
1926 return selectedElement
1929 let s:selection = -1
1930 function! genutils#GetSelectedIndex()
1934 " Always match() with 'ignorecase' and 'smartcase' off.
1935 function! s:Match(expr, pat, start)
1936 let _ic = &ignorecase
1937 let _scs = &smartcase
1942 let result = match(a:expr, a:pat, a:start)
1944 let &ignorecase = _ic
1945 let &smartcase = _scs
1951 let &cpo = s:save_cpo
1954 " vim6:fdm=marker et