From d0d885039e0b7f2242d6db6eb2ba36da132f1b94 Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Thu, 27 Nov 2008 15:28:57 +0000 Subject: [PATCH] Added Perforce plugin. Added gvim titlestring. Fixed mapping typo. git-svn-id: https://svn.cambridge.iain.cx/profile/trunk@156 6be0d1a5-5cfe-0310-89b6-964be062b18b --- .vim/autoload/genutils.vim | 1954 ++++++++++++++++++ .vim/autoload/perforce.vim | 3715 ++++++++++++++++++++++++++++++++++ .vim/autoload/perforceutils.vim | 260 +++ .vim/doc/perforce.txt | 2441 ++++++++++++++++++++++ .vim/ftplugin/perforce.vim | 59 + .vim/ftplugin/selectbuf_perforce.vim | 15 + .vim/perforce/bakup.sh | 396 ++++ .vim/perforce/p4Utils.sh | 24 + .vim/perforce/perforcebugrep.vim | 97 + .vim/perforce/perforcemenu.vim | 335 +++ .vim/perforce/perforceutils.vim | 39 + .vim/perforce/restor.sh | 125 ++ .vim/plugin/genutils.vim | 996 +++++++++ .vim/plugin/perforce.vim | 375 ++++ .vim/syntax/perforce.vim | 184 ++ .vimrc | 19 +- 16 files changed, 11033 insertions(+), 1 deletion(-) create mode 100755 .vim/autoload/genutils.vim create mode 100755 .vim/autoload/perforce.vim create mode 100755 .vim/autoload/perforceutils.vim create mode 100755 .vim/doc/perforce.txt create mode 100755 .vim/ftplugin/perforce.vim create mode 100755 .vim/ftplugin/selectbuf_perforce.vim create mode 100755 .vim/perforce/bakup.sh create mode 100755 .vim/perforce/p4Utils.sh create mode 100755 .vim/perforce/perforcebugrep.vim create mode 100755 .vim/perforce/perforcemenu.vim create mode 100755 .vim/perforce/perforceutils.vim create mode 100755 .vim/perforce/restor.sh create mode 100755 .vim/plugin/genutils.vim create mode 100755 .vim/plugin/perforce.vim create mode 100755 .vim/syntax/perforce.vim diff --git a/.vim/autoload/genutils.vim b/.vim/autoload/genutils.vim new file mode 100755 index 0000000..c28a25c --- /dev/null +++ b/.vim/autoload/genutils.vim @@ -0,0 +1,1954 @@ +" genutils.vim: Please see plugin/genutils.vim +" +" TODO: +" - Vim7: redir can be used with a variable. +" - fnamemodify() on Unix doesn't expand to full name if the filename doesn't +" really exist on the filesystem. +" - Is setting 'scrolloff' and 'sidescrolloff' to 0 required while moving the +" cursor? +" - http://www.vim.org/tips/tip.php?tip_id=1379 +" +" - EscapeCommand() didn't work for David Fishburn. +" - Save/RestoreWindowSettings doesn't work well. +" +" Vim7: +" - Save/RestoreWindowSettings can use winsave/restview() functions. +" + +" Make sure line-continuations won't cause any problem. This will be restored +" at the end +let s:save_cpo = &cpo +set cpo&vim + + +let g:makeArgumentString = 'exec genutils#MakeArgumentString()' +let g:makeArgumentList = 'exec genutils#MakeArgumentList()' + +let s:makeArgumentString = '' +function! genutils#MakeArgumentString(...) + if s:makeArgumentString == '' + let s:makeArgumentString = genutils#ExtractFuncListing(s:SNR(). + \ '_makeArgumentString', 0, 0) + endif + if a:0 > 0 && a:1 != '' + return substitute(s:makeArgumentString, '\', a:1, 'g') + else + return s:makeArgumentString + endif +endfunction + + +let s:makeArgumentList = '' +function! genutils#MakeArgumentList(...) + if s:makeArgumentList == '' + let s:makeArgumentList = genutils#ExtractFuncListing(s:SNR(). + \ '_makeArgumentList', 0, 0) + endif + if a:0 > 0 && a:1 != '' + let mkArgLst = substitute(s:makeArgumentList, '\', a:1, 'g') + if a:0 > 1 && a:2 != '' + let mkArgLst = substitute(s:makeArgumentList, + \ '\(\s\+let __argSeparator = \)[^'."\n".']*', "\\1'".a:2."'", '') + endif + return mkArgLst + else + return s:makeArgumentList + endif +endfunction + +function! genutils#ExtractFuncListing(funcName, hLines, tLines) + let listing = genutils#GetVimCmdOutput('func '.a:funcName) + let listing = substitute(listing, + \ '^\%(\s\|'."\n".'\)*function '.a:funcName.'([^)]*)'."\n", '', '') + "let listing = substitute(listing, '\%(\s\|'."\n".'\)*endfunction\%(\s\|'."\n".'\)*$', '', '') + " Leave the last newline character. + let listing = substitute(listing, '\%('."\n".'\)\@<=\s*endfunction\s*$', '', '') + let listing = substitute(listing, '\(\%(^\|'."\n".'\)\s*\)\@<=\d\+', + \ '', 'g') + if a:hLines > 0 + let listing = substitute(listing, '^\%([^'."\n".']*'."\n".'\)\{'. + \ a:hLines.'}', '', '') + endif + if a:tLines > 0 + let listing = substitute(listing, '\%([^'."\n".']*'."\n".'\)\{'. + \ a:tLines.'}$', '', '') + endif + return listing +endfunction + +function! genutils#CreateArgString(argList, sep, ...) + let sep = (a:0 == 0) ? a:sep : a:1 " This is no longer used. + " Matching multvals functionality means, we need to ignore the trailing + " separator. + let argList = split(substitute(a:argList, a:sep.'$', '', ''), a:sep, 1) + let argString = "'" + for nextArg in argList + " FIXME: I think this check is not required. If "'" is the separator, we + " don't expect to see them in the elements. + if a:sep != "'" + let nextArg = substitute(nextArg, "'", "' . \"'\" . '", 'g') + endif + let argString = argString . nextArg . "', '" + endfor + let argString = strpart(argString, 0, strlen(argString) - 3) + return argString +endfunction + +" {{{ +function! s:_makeArgumentString() + let __argCounter = 1 + let argumentString = '' + while __argCounter <= a:0 + if type(a:{__argCounter}) + let __nextArg = "'" . + \ substitute(a:{__argCounter}, "'", "' . \"'\" . '", "g") . "'" + else + let __nextArg = a:{__argCounter} + endif + let argumentString = argumentString. __nextArg . + \ ((__argCounter == a:0) ? '' : ', ') + let __argCounter = __argCounter + 1 + endwhile + unlet __argCounter + if exists('__nextArg') + unlet __nextArg + endif +endfunction + +function! s:_makeArgumentList() + let __argCounter = 1 + let __argSeparator = ',' + let argumentList = '' + while __argCounter <= a:0 + let argumentList = argumentList . a:{__argCounter} + if __argCounter != a:0 + let argumentList = argumentList . __argSeparator + endif + let __argCounter = __argCounter + 1 + endwhile + unlet __argCounter + unlet __argSeparator +endfunction +" }}} + + +function! genutils#DebugShowArgs(...) + let i = 0 + let argString = '' + while i < a:0 + let argString = argString . a:{i + 1} . ', ' + let i = i + 1 + endwhile + let argString = strpart(argString, 0, strlen(argString) - 2) + call input("Args: " . argString) +endfunction + +" Window related functions {{{ + +function! genutils#NumberOfWindows() + let i = 1 + while winbufnr(i) != -1 + let i = i+1 + endwhile + return i - 1 +endfunction + +" Find the window number for the buffer passed. +" The fileName argument is treated literally, unlike the bufnr() which treats +" the argument as a regex pattern. +function! genutils#FindWindowForBuffer(bufferName, checkUnlisted) + return bufwinnr(genutils#FindBufferForName(a:bufferName)) +endfunction + +function! genutils#FindBufferForName(fileName) + " The name could be having extra backslashes to protect certain chars (such + " as '#' and '%'), so first expand them. + return s:FindBufferForName(genutils#UnEscape(a:fileName, '#%')) +endfunction + +function! s:FindBufferForName(fileName) + let fileName = genutils#Escape(a:fileName, '[?,{') + let _isf = &isfname + try + set isfname-=\ + set isfname-=[ + let i = bufnr('^' . fileName . '$') + finally + let &isfname = _isf + endtry + return i +endfunction + +function! genutils#GetBufNameForAu(bufName) + let bufName = a:bufName + " Autocommands always require forward-slashes. + let bufName = substitute(bufName, "\\\\", '/', 'g') + let bufName = escape(bufName, '*?,{}[ ') + return bufName +endfunction + +function! genutils#MoveCursorToWindow(winno) + if genutils#NumberOfWindows() != 1 + execute a:winno . " wincmd w" + endif +endfunction + +function! genutils#MoveCurLineToWinLine(n) + normal! zt + if a:n == 1 + return + endif + let _wrap = &l:wrap + setl nowrap + let n = a:n + if n >= winheight(0) + let n = winheight(0) + endif + let n = n - 1 + execute "normal! " . n . "\" + let &l:wrap = _wrap +endfunction + +function! genutils#CloseWindow(win, force) + let _eventignore = &eventignore + try + set eventignore=all + call genutils#MarkActiveWindow() + + let &eventignore = _eventignore + exec a:win 'wincmd w' + exec 'close'.(a:force ? '!' : '') + set eventignore=all + + if a:win < t:curWinnr + let t:curWinnr = t:curWinnr - 1 + endif + if a:win < t:prevWinnr + let t:prevWinnr = t:prevWinnr - 1 + endif + finally + call genutils#RestoreActiveWindow() + let &eventignore = _eventignore + endtry +endfunction + +function! genutils#MarkActiveWindow() + let t:curWinnr = winnr() + " We need to restore the previous-window also at the end. + silent! wincmd p + let t:prevWinnr = winnr() + silent! wincmd p +endfunction + +function! genutils#RestoreActiveWindow() + if !exists('t:curWinnr') + return + endif + + " Restore the original window. + if winnr() != t:curWinnr + exec t:curWinnr'wincmd w' + endif + if t:curWinnr != t:prevWinnr + exec t:prevWinnr'wincmd w' + wincmd p + endif +endfunction + +function! genutils#IsOnlyVerticalWindow() + let onlyVertWin = 1 + let _eventignore = &eventignore + + try + "set eventignore+=WinEnter,WinLeave + set eventignore=all + call genutils#MarkActiveWindow() + + wincmd j + if winnr() != t:curWinnr + let onlyVertWin = 0 + else + wincmd k + if winnr() != t:curWinnr + let onlyVertWin = 0 + endif + endif + finally + call genutils#RestoreActiveWindow() + let &eventignore = _eventignore + endtry + return onlyVertWin +endfunction + +function! genutils#IsOnlyHorizontalWindow() + let onlyHorizWin = 1 + let _eventignore = &eventignore + try + set eventignore=all + call genutils#MarkActiveWindow() + wincmd l + if winnr() != t:curWinnr + let onlyHorizWin = 0 + else + wincmd h + if winnr() != t:curWinnr + let onlyHorizWin = 0 + endif + endif + finally + call genutils#RestoreActiveWindow() + let &eventignore = _eventignore + endtry + return onlyHorizWin +endfunction + +function! genutils#MoveCursorToNextInWinStack(dir) + let newwin = genutils#GetNextWinnrInStack(a:dir) + if newwin != 0 + exec newwin 'wincmd w' + endif +endfunction + +function! genutils#GetNextWinnrInStack(dir) + let newwin = winnr() + let _eventignore = &eventignore + try + set eventignore=all + call genutils#MarkActiveWindow() + let newwin = s:GetNextWinnrInStack(a:dir) + finally + call genutils#RestoreActiveWindow() + let &eventignore = _eventignore + endtry + return newwin +endfunction + +function! genutils#MoveCursorToLastInWinStack(dir) + let newwin = genutils#GetLastWinnrInStack(a:dir) + if newwin != 0 + exec newwin 'wincmd w' + endif +endfunction + +function! genutils#GetLastWinnrInStack(dir) + let newwin = winnr() + let _eventignore = &eventignore + try + set eventignore=all + call genutils#MarkActiveWindow() + while 1 + let wn = s:GetNextWinnrInStack(a:dir) + if wn != 0 + let newwin = wn + exec newwin 'wincmd w' + else + break + endif + endwhile + finally + call genutils#RestoreActiveWindow() + let &eventignore = _eventignore + endtry + return newwin +endfunction + +" Based on the WinStackMv() function posted by Charles E. Campbell, Jr. on vim +" mailing list on Jul 14, 2004. +function! s:GetNextWinnrInStack(dir) + "call Decho("genutils#MoveCursorToNextInWinStack(dir<".a:dir.">)") + + let isHorizontalMov = (a:dir ==# 'h' || a:dir ==# 'l') ? 1 : 0 + + let orgwin = winnr() + let orgdim = s:GetWinDim(a:dir, orgwin) + + let _winwidth = &winwidth + let _winheight = &winheight + try + set winwidth=1 + set winheight=1 + exec 'wincmd' a:dir + let newwin = winnr() + if orgwin == newwin + " No more windows in this direction. + "call Decho("newwin=".newwin." stopped".winheight(newwin)."x".winwidth(newwin)) + return 0 + endif + if s:GetWinDim(a:dir, newwin) != orgdim + " Window dimension has changed, indicates a move across window stacks. + "call Decho("newwin=".newwin." height changed".winheight(newwin)."x".winwidth(newwin)) + return 0 + endif + " Determine if changing original window height affects current window + " height. + exec orgwin 'wincmd w' + try + if orgdim == 1 + exec 'wincmd' (isHorizontalMov ? '_' : '|') + else + exec 'wincmd' (isHorizontalMov ? '-' : '<') + endif + if s:GetWinDim(a:dir, newwin) != s:GetWinDim(a:dir, orgwin) + "call Decho("newwin=".newwin." different row".winheight(newwin)."x".winwidth(newwin)) + return 0 + endif + "call Decho("newwin=".newwin." same row".winheight(newwin)."x".winwidth(newwin)) + finally + exec (isHorizontalMov ? '' : 'vert') 'resize' orgdim + endtry + + "call Decho("genutils#MoveCursorToNextInWinStack") + + return newwin + finally + let &winwidth = _winwidth + let &winheight = _winheight + endtry +endfunction + +function! s:GetWinDim(dir, win) + return (a:dir ==# 'h' || a:dir ==# 'l') ? winheight(a:win) : winwidth(a:win) +endfunction + +function! genutils#OpenWinNoEa(winOpenCmd) + call s:ExecWinCmdNoEa(a:winOpenCmd) +endfunction + +function! genutils#CloseWinNoEa(winnr, force) + call s:ExecWinCmdNoEa(a:winnr.'wincmd w | close'.(a:force?'!':'')) +endfunction + +function! s:ExecWinCmdNoEa(winCmd) + let _eventignore = &eventignore + try + set eventignore=all + call genutils#MarkActiveWindow() + windo let w:_winfixheight = &winfixheight + windo set winfixheight + call genutils#RestoreActiveWindow() + + let &eventignore = _eventignore + exec a:winCmd + set eventignore=all + + call genutils#MarkActiveWindow() + silent! windo let &winfixheight = w:_winfixheight + silent! windo unlet w:_winfixheight + call genutils#RestoreActiveWindow() + finally + let &eventignore = _eventignore + endtry +endfunction + +" Window related functions }}} + +function! genutils#SetupScratchBuffer() + setlocal nobuflisted + setlocal noswapfile + setlocal buftype=nofile + " Just in case, this will make sure we are always hidden. + setlocal bufhidden=delete + setlocal nolist + setlocal nonumber + setlocal foldcolumn=0 nofoldenable + setlocal noreadonly +endfunction + +function! genutils#CleanDiffOptions() + setlocal nodiff + setlocal noscrollbind + setlocal scrollopt-=hor + setlocal wrap + setlocal foldmethod=manual + setlocal foldcolumn=0 + normal zE +endfunction + +function! genutils#ArrayVarExists(varName, index) + try + exec "let test = " . a:varName . "{a:index}" + catch /^Vim\%((\a\+)\)\=:E121/ + return 0 + endtry + return 1 +endfunction + +function! genutils#Escape(str, chars) + return substitute(a:str, '\\\@ 0 + let pipe = join(a:pipe, ' ') + elseif type(a:pipe) == 1 && a:pipe !~# '^\s*$' + let pipe = a:pipe + endif + if pipe != '' + let fullCmd = fullCmd . ' ' . a:pipe + endif + if a:cmd !~# '^\s*$' + let fullCmd = a:cmd . ' ' . fullCmd + endif + if shellEnvType ==# g:ST_WIN_SH && &shell =~# '\' + let fullCmd = substitute(fullCmd, '\\', '\\\\', 'g') + endif + return fullCmd +endfunction + +let g:ST_WIN_CMD = 0 | let g:ST_WIN_SH = 1 | let g:ST_UNIX = 2 +function! genutils#GetShellEnvType() + " When 'shellslash' option is available, then the platform must be one of + " those that support '\' as a pathsep. + if exists('+shellslash') + if stridx(&shell, 'cmd.exe') != -1 || + \ stridx(&shell, 'command.com') != -1 + return g:ST_WIN_CMD + else + return g:ST_WIN_SH + endif + else + return g:ST_UNIX + endif +endfunction + +function! genutils#ExpandStr(str) + let str = substitute(a:str, '"', '\\"', 'g') + exec "let str = \"" . str . "\"" + return str +endfunction + +function! genutils#QuoteStr(str) + return "'".substitute(a:str, "'", "'.\"'\".'", 'g')."'" +endfunction + +function! genutils#GetPreviewWinnr() + let _eventignore = &eventignore + let curWinNr = winnr() + let winnr = -1 + try + set eventignore=all + exec "wincmd P" + let winnr = winnr() + catch /^Vim\%((\a\+)\)\=:E441/ + " Ignore, winnr is already set to -1. + finally + if winnr() != curWinNr + exec curWinNr.'wincmd w' + endif + let &eventignore = _eventignore + endtry + return winnr +endfunction + +" Save/Restore window settings {{{ +function! genutils#SaveWindowSettings() + call genutils#SaveWindowSettings2('SaveWindowSettings', 1) +endfunction + +function! genutils#RestoreWindowSettings() + call genutils#RestoreWindowSettings2('SaveWindowSettings') +endfunction + + +function! genutils#ResetWindowSettings() + call genutils#ResetWindowSettings2('SaveWindowSettings') +endfunction + +function! genutils#SaveWindowSettings2(id, overwrite) + if genutils#ArrayVarExists("t:winSettings", a:id) && ! a:overwrite + return + endif + + let t:winSettings{a:id} = [] + call add(t:winSettings{a:id}, genutils#NumberOfWindows()) + call add(t:winSettings{a:id}, &lines) + call add(t:winSettings{a:id}, &columns) + call add(t:winSettings{a:id}, winnr()) + let i = 1 + while winbufnr(i) != -1 + call add(t:winSettings{a:id}, winheight(i)) + call add(t:winSettings{a:id}, winwidth(i)) + let i = i + 1 + endwhile + "let g:savedWindowSettings = t:winSettings{a:id} " Debug. +endfunction + +function! genutils#RestoreWindowSettings2(id) + " Calling twice fixes most of the resizing issues. This seems to be how the + " :mksession with "winsize" in 'sesionoptions' seems to work. + call s:RestoreWindowSettings2(a:id) + call s:RestoreWindowSettings2(a:id) +endfunction + +function! s:RestoreWindowSettings2(id) + "if ! exists("t:winSettings" . a:id) + if ! genutils#ArrayVarExists("t:winSettings", a:id) + return + endif + + let nWindows = t:winSettings{a:id}[0] + if nWindows != genutils#NumberOfWindows() + unlet t:winSettings{a:id} + return + endif + let orgLines = t:winSettings{a:id}[1] + let orgCols = t:winSettings{a:id}[2] + let activeWindow = t:winSettings{a:id}[3] + let mainWinSizeSame = (orgLines == &lines && orgCols == &columns) + + let winNo = 1 + let i = 4 + while i < len(t:winSettings{a:id}) + let height = t:winSettings{a:id}[i] + let width = t:winSettings{a:id}[i+1] + let height = (mainWinSizeSame ? height : + \ ((&lines * height + (orgLines / 2)) / orgLines)) + let width = (mainWinSizeSame ? width : + \ ((&columns * width + (orgCols / 2)) / orgCols)) + if winheight(winnr()) != height + exec winNo'resize' height + endif + if winwidth(winnr()) != width + exec 'vert' winNo 'resize' width + endif + let winNo = winNo + 1 + let i = i + 2 + endwhile + + " Restore the current window. + call genutils#MoveCursorToWindow(activeWindow) + "unlet g:savedWindowSettings +endfunction + + +function! genutils#ResetWindowSettings2(id) + if genutils#ArrayVarExists("t:winSettings", a:id) + unlet t:winSettings{a:id} + endif +endfunction + +" Save/Restore window settings }}} + +" Save/Restore selection {{{ + +function! genutils#SaveVisualSelection(id) + let curmode = mode() + if curmode == 'n' + normal! gv + endif + let s:vismode{a:id} = mode() + let s:firstline{a:id} = line("'<") + let s:lastline{a:id} = line("'>") + let s:firstcol{a:id} = col("'<") + let s:lastcol{a:id} = col("'>") + if curmode !=# s:vismode{a:id} + exec "normal \" + endif +endfunction + +function! genutils#RestoreVisualSelection(id) + if mode() !=# 'n' + exec "normal \" + endif + if exists('s:vismode{id}') + exec 'normal' s:firstline{a:id}.'gg'.s:firstcol{a:id}.'|'. + \ s:vismode{a:id}.(s:lastline{a:id}-s:firstline{a:id}).'j'. + \ (s:lastcol{a:id}-s:firstcol{a:id}).'l' + endif +endfunction +" Save/Restore selection }}} + +function! genutils#CleanupFileName(fileName) + return genutils#CleanupFileName2(a:fileName, '') +endfunction + +function! genutils#CleanupFileName2(fileName, win32ProtectedChars) + let fileName = substitute(a:fileName, '^\s\+\|\s\+$', '', 'g') + + " Expand relative paths and paths containing relative components (takes care + " of ~ also). + if ! genutils#PathIsAbsolute(fileName) + let fileName = fnamemodify(fileName, ':p') + endif + + " I think we can have UNC paths on UNIX, if samba is installed. + if genutils#OnMS() && (match(fileName, '^//') == 0 || + \ match(fileName, '^\\\\') == 0) + let uncPath = 1 + else + let uncPath = 0 + endif + + " Remove multiple path separators. + if has('win32') + if a:win32ProtectedChars != '' + let fileName=substitute(fileName, '\\['.a:win32ProtectedChars.']\@!', '/', + \ 'g') + else + let fileName=substitute(fileName, '\\', '/', 'g') + endif + elseif genutils#OnMS() + " On non-win32 systems, the forward-slash is not supported, so leave + " back-slash. + let fileName=substitute(fileName, '\\\{2,}', '\', 'g') + endif + let fileName=substitute(fileName, '/\{2,}', '/', 'g') + + " Remove ending extra path separators. + let fileName=substitute(fileName, '/$', '', '') + let fileName=substitute(fileName, '\\$', '', '') + + " If it was an UNC path, add back an extra slash. + if uncPath + let fileName = '/'.fileName + endif + + if genutils#OnMS() + let fileName=substitute(fileName, '^[A-Z]:', '\L&', '') + + " Add drive letter if missing (just in case). + if !uncPath && match(fileName, '^/') == 0 + let curDrive = substitute(getcwd(), '^\([a-zA-Z]:\).*$', '\L\1', '') + let fileName = curDrive . fileName + endif + endif + return fileName +endfunction +"echo genutils#CleanupFileName('\\a///b/c\') +"echo genutils#CleanupFileName('C:\a/b/c\d') +"echo genutils#CleanupFileName('a/b/c\d') +"echo genutils#CleanupFileName('~/a/b/c\d') +"echo genutils#CleanupFileName('~/a/b/../c\d') + +function! genutils#OnMS() + return has('win32') || has('dos32') || has('win16') || has('dos16') || + \ has('win95') +endfunction + +function! genutils#PathIsAbsolute(path) + let absolute=0 + if has('unix') || genutils#OnMS() + if match(a:path, '^/') == 0 + let absolute=1 + endif + endif + if (! absolute) && genutils#OnMS() + if match(a:path, "^\\") == 0 + let absolute=1 + endif + endif + if (! absolute) && genutils#OnMS() + if match(a:path, "^[A-Za-z]:") == 0 + let absolute=1 + endif + endif + return absolute +endfunction + +function! genutils#PathIsFileNameOnly(path) + return (match(a:path, "\\") < 0) && (match(a:path, "/") < 0) +endfunction + +let s:mySNR = '' +function! s:SNR() + if s:mySNR == '' + let s:mySNR = matchstr(expand(''), '\d\+_\zeSNR$') + endif + return s:mySNR +endfun + + +"" --- START save/restore position. {{{ + +function! genutils#SaveSoftPosition(id) + let b:sp_startline_{a:id} = getline(".") + call genutils#SaveHardPosition(a:id) +endfunction + +function! genutils#RestoreSoftPosition(id) + 0 + call genutils#RestoreHardPosition(a:id) + let stLine = b:sp_startline_{a:id} + if getline('.') !=# stLine + if ! search('\V\^'.escape(stLine, "\\").'\$', 'W') + call search('\V\^'.escape(stLine, "\\").'\$', 'bW') + endif + endif + call genutils#MoveCurLineToWinLine(b:sp_winline_{a:id}) +endfunction + +function! genutils#ResetSoftPosition(id) + unlet b:sp_startline_{a:id} +endfunction + +" A synonym for genutils#SaveSoftPosition. +function! genutils#SaveHardPositionWithContext(id) + call genutils#SaveSoftPosition(a:id) +endfunction + +" A synonym for genutils#RestoreSoftPosition. +function! genutils#RestoreHardPositionWithContext(id) + call genutils#RestoreSoftPosition(a:id) +endfunction + +" A synonym for genutils#ResetSoftPosition. +function! genutils#ResetHardPositionWithContext(id) + call genutils#ResetSoftPosition(a:id) +endfunction + +function! genutils#SaveHardPosition(id) + let b:sp_col_{a:id} = virtcol(".") + let b:sp_lin_{a:id} = line(".") + " Avoid accounting for wrapped lines. + let _wrap = &l:wrap + try + setl nowrap + let b:sp_winline_{a:id} = winline() + finally + let &l:wrap = _wrap + endtry +endfunction + +function! genutils#RestoreHardPosition(id) + " This doesn't take virtual column. + "call cursor(b:sp_lin_{a:id}, b:sp_col_{a:id}) + " Vim7 generates E16 if line number is invalid. + " TODO: Why is this leaving cursor on the last-but-one line when the + " condition meets? + execute ((line('$') < b:sp_lin_{a:id}) ? line('$') : + \ b:sp_lin_{a:id}) + "execute b:sp_lin_{a:id} + execute ((line('$') < b:sp_lin_{a:id}) ? line('$') : + \ b:sp_lin_{a:id}) + "execute b:sp_lin_{a:id} + execute "normal!" b:sp_col_{a:id} . "|" + call genutils#MoveCurLineToWinLine(b:sp_winline_{a:id}) +endfunction + +function! genutils#ResetHardPosition(id) + unlet b:sp_col_{a:id} + unlet b:sp_lin_{a:id} + unlet b:sp_winline_{a:id} +endfunction + +function! genutils#GetLinePosition(id) + return b:sp_lin_{a:id} +endfunction + +function! genutils#GetColPosition(id) + return b:sp_col_{a:id} +endfunction + +function! genutils#IsPositionSet(id) + return exists('b:sp_col_' . a:id) +endfunction + +"" --- END save/restore position. }}} + + + +"" +"" --- START: Notify window close -- {{{ +"" + +let s:notifyWindow = {} +function! genutils#AddNotifyWindowClose(windowTitle, functionName) + let bufName = a:windowTitle + + let s:notifyWindow[bufName] = a:functionName + + "let g:notifyWindow = s:notifyWindow " Debug. + + " Start listening to events. + aug NotifyWindowClose + au! + au WinEnter * :call genutils#CheckWindowClose() + au BufEnter * :call genutils#CheckWindowClose() + aug END +endfunction + +function! genutils#RemoveNotifyWindowClose(windowTitle) + let bufName = a:windowTitle + + if has_key(s:notifyWindow, bufName) + call remove(s:notifyWindow, bufName) + if len(s:notifyWindow) == 0 + "unlet g:notifyWindow " Debug. + + aug NotifyWindowClose + au! + aug END + endif + endif +endfunction + +function! genutils#CheckWindowClose() + if !exists('s:notifyWindow') + return + endif + + " Now iterate over all the registered window titles and see which one's are + " closed. + for nextWin in keys(s:notifyWindow) + if bufwinnr(s:FindBufferForName(nextWin)) == -1 + let funcName = s:notifyWindow[nextWin] + " Remove this entry as these are going to be processed. The caller can add + " them back if needed. + unlet s:notifyWindow[nextWin] + "call input("cmd: " . cmd) + call call(funcName, [nextWin]) + endif + endfor +endfunction + +"function! NotifyWindowCloseF(title) +" call input(a:title . " closed") +"endfunction +" +"function! RunNotifyWindowCloseTest() +" call input("Creating three windows, 'ABC', 'XYZ' and 'b':") +" split ABC +" split X Y Z +" split b +" redraw +" call genutils#AddNotifyWindowClose("ABC", "NotifyWindowCloseF") +" call genutils#AddNotifyWindowClose("X Y Z", "NotifyWindowCloseF") +" call input("notifyWindow: " . string(s:notifyWindow)) +" au NotifyWindowClose WinEnter +" call input("Added notifications for 'ABC' and 'XYZ'\n". +" \ "Now closing the windows, you should see ONLY two notifications:") +" quit +" quit +" quit +"endfunction + +"" +"" --- END: Notify window close -- }}} +"" + +" TODO: For large ranges, the cmd can become too big, so make it one cmd per +" line. +function! genutils#ShowLinesWithSyntax() range + " This makes sure we start (subsequent) echo's on the first line in the + " command-line area. + " + echo '' + + let cmd = '' + let prev_group = ' x ' " Something that won't match any syntax group. + + let show_line = a:firstline + let isMultiLine = ((a:lastline - a:firstline) > 1) + while show_line <= a:lastline + let cmd = '' + let length = strlen(getline(show_line)) + let column = 1 + + while column <= length + let group = synIDattr(synID(show_line, column, 1), 'name') + if group != prev_group + if cmd != '' + let cmd = cmd . "'|" + endif + let cmd = cmd . 'echohl ' . (group == '' ? 'NONE' : group) . "|echon '" + let prev_group = group + endif + let char = strpart(getline(show_line), column - 1, 1) + if char == "'" + let char = "'."'".'" + endif + let cmd = cmd . char + let column = column + 1 + endwhile + + try + exec cmd."\n'" + catch + echo '' + endtry + let show_line = show_line + 1 + endwhile + echohl NONE +endfunction + + +function! genutils#AlignWordWithWordInPreviousLine() + "First go to the first col in the word. + if getline('.')[col('.') - 1] =~ '\s' + normal! w + else + normal! "_yiw + endif + let orgVcol = virtcol('.') + let prevLnum = prevnonblank(line('.') - 1) + if prevLnum == -1 + return + endif + let prevLine = getline(prevLnum) + + " First get the column to align with. + if prevLine[orgVcol - 1] =~ '\s' + " column starts from 1 where as index starts from 0. + let nonSpaceStrInd = orgVcol " column starts from 1 where as index starts from 0. + while prevLine[nonSpaceStrInd] =~ '\s' + let nonSpaceStrInd = nonSpaceStrInd + 1 + endwhile + else + if strlen(prevLine) < orgVcol + let nonSpaceStrInd = strlen(prevLine) - 1 + else + let nonSpaceStrInd = orgVcol - 1 + endif + + while prevLine[nonSpaceStrInd - 1] !~ '\s' && nonSpaceStrInd > 0 + let nonSpaceStrInd = nonSpaceStrInd - 1 + endwhile + endif + let newVcol = nonSpaceStrInd + 1 " Convert to column number. + + if orgVcol > newVcol " We need to reduce the spacing. + let sub = strpart(getline('.'), newVcol - 1, (orgVcol - newVcol)) + if sub =~ '^\s\+$' + " Remove the excess space. + exec 'normal! ' . newVcol . '|' + exec 'normal! ' . (orgVcol - newVcol) . 'x' + endif + elseif orgVcol < newVcol " We need to insert spacing. + exec 'normal! ' . orgVcol . '|' + exec 'normal! ' . (newVcol - orgVcol) . 'i ' + endif +endfunction + +function! genutils#ShiftWordInSpace(dir) + if a:dir == 1 " forward. + " If currently on ... + if getline(".")[col(".") - 1] == " " + let move1 = 'wf ' + else + " If next col is a + "if getline(".")[col(".") + 1] + let move1 = 'f ' + endif + let removeCommand = "x" + let pasteCommand = "bi " + let move2 = 'w' + let offset = 0 + else " backward. + " If currently on ... + if getline(".")[col(".") - 1] == " " + let move1 = 'w' + else + let move1 = '"_yiW' + endif + let removeCommand = "hx" + let pasteCommand = 'h"_yiwEa ' + let move2 = 'b' + let offset = -3 + endif + + let savedCol = col(".") + exec "normal!" move1 + let curCol = col(".") + let possible = 0 + " Check if there is a space at the end. + if col("$") == (curCol + 1) " Works only for forward case, as expected. + let possible = 1 + elseif getline(".")[curCol + offset] == " " + " Remove the space from here. + exec "normal!" removeCommand + let possible = 1 + endif + + " Move back into the word. + "exec "normal!" savedCol . "|" + if possible == 1 + exec "normal!" pasteCommand + exec "normal!" move2 + else + " Move to the original location. + exec "normal!" savedCol . "|" + endif +endfunction + + +function! genutils#CenterWordInSpace() + let line = getline('.') + let orgCol = col('.') + " If currently on ... + if line[orgCol - 1] == " " + let matchExpr = ' *\%'. orgCol . 'c *\w\+ \+' + else + let matchExpr = ' \+\(\w*\%' . orgCol . 'c\w*\) \+' + endif + let matchInd = match(line, matchExpr) + if matchInd == -1 + return + endif + let matchStr = matchstr(line, matchExpr) + let nSpaces = strlen(substitute(matchStr, '[^ ]', '', 'g')) + let word = substitute(matchStr, ' ', '', 'g') + let middle = nSpaces / 2 + let left = nSpaces - middle + let newStr = '' + while middle > 0 + let newStr = newStr . ' ' + let middle = middle - 1 + endwhile + let newStr = newStr . word + while left > 0 + let newStr = newStr . ' ' + let left = left - 1 + endwhile + + let newLine = strpart(line, 0, matchInd) + let newLine = newLine . newStr + let newLine = newLine . strpart (line, matchInd + strlen(matchStr)) + silent! keepjumps call setline(line('.'), newLine) +endfunction + +function! genutils#MapAppendCascaded(lhs, rhs, mapMode) + + " Determine the map mode from the map command. + let mapChar = strpart(a:mapMode, 0, 1) + + " Check if this is already mapped. + let oldrhs = maparg(a:lhs, mapChar) + if oldrhs != "" + let self = oldrhs + else + let self = a:lhs + endif + "echomsg a:mapMode . "oremap" . " " . a:lhs . " " . self . a:rhs + exec a:mapMode . "oremap" a:lhs self . a:rhs +endfunction + +" smartSlash simply says whether to depend on shellslash and ArgLead for +" determining path separator. If it shouldn't depend, it will always assume +" that the required pathsep is forward-slash. +function! genutils#UserFileComplete(ArgLead, CmdLine, CursorPos, smartSlash, + \ searchPath) + let glob = '' + let opathsep = "\\" + let npathsep = '/' + if exists('+shellslash') && ! &shellslash && a:smartSlash && + \ stridx(a:ArgLead, "\\") != -1 + let opathsep = '/' + let npathsep = "\\" + endif + if a:searchPath !=# '' + for nextPath in split(a:searchPath, genutils#CrUnProtectedCharsPattern(',')) + let nextPath = genutils#CleanupFileName(nextPath) + let matches = glob(nextPath.'/'.a:ArgLead.'*') + if matches !~# '^\_s*$' + let matches = s:FixPathSep(matches, opathsep, npathsep) + let nextPath = substitute(nextPath, opathsep, npathsep, 'g') + let matches = substitute(matches, '\V'.escape(nextPath.npathsep, "\\"), + \ '', 'g') + let glob = glob . matches . "\n" + endif + endfor + else + let glob = s:FixPathSep(glob(a:ArgLead.'*'), opathsep, npathsep) + endif + " FIXME: Need an option to control if ArgLead should also be returned or + " not. + return glob."\n".a:ArgLead +endfunction + +command! -complete=file -nargs=* GUDebugEcho :echo +function! genutils#UserFileExpand(fileArgs) + return substitute(genutils#GetVimCmdOutput( + \ 'GUDebugEcho ' . a:fileArgs), '^\_s\+\|\_s\+$', '', 'g') +endfunction + +function! s:FixPathSep(matches, opathsep, npathsep) + let matches = a:matches + let matches = substitute(matches, a:opathsep, a:npathsep, 'g') + let matches = substitute(matches, "\\([^\n]\\+\\)", '\=submatch(1).'. + \ '(isdirectory(submatch(1)) ? a:npathsep : "")', 'g') + return matches +endfunction + +function! genutils#GetVimCmdOutput(cmd) + let v:errmsg = '' + let output = '' + let _z = @z + let _shortmess = &shortmess + try + set shortmess= + redir @z + silent exec a:cmd + catch /.*/ + let v:errmsg = substitute(v:exception, '^[^:]\+:', '', '') + finally + redir END + let &shortmess = _shortmess + if v:errmsg == '' + let output = @z + endif + let @z = _z + endtry + return output +endfunction + +function! genutils#OptClearBuffer() + " Go as far as possible in the undo history to conserve Vim resources. + let _modifiable = &l:modifiable + let _undolevels = &undolevels + try + setl modifiable + set undolevels=-1 + silent! keepjumps 0,$delete _ + finally + let &undolevels = _undolevels + let &l:modifiable = _modifiable + endtry +endfunction + + +"" START: Sorting support. {{{ +"" + +" +" Comapare functions. +" + +function! genutils#CmpByLineLengthNname(line1, line2, ...) + let direction = (a:0?a:1:1) + let cmp = genutils#CmpByLength(a:line1, a:line2, direction) + if cmp == 0 + let cmp = genutils#CmpByString(a:line1, a:line2, direction) + endif + return cmp +endfunction + +function! genutils#CmpByLength(line1, line2, ...) + let direction = (a:0?a:1:1) + let len1 = strlen(a:line1) + let len2 = strlen(a:line2) + return direction * (len1 - len2) +endfunction + +" Compare first by name and then by length. +" Useful for sorting Java imports. +function! genutils#CmpJavaImports(line1, line2, ...) + let direction = (a:0?a:1:1) + " FIXME: Simplify this. + if stridx(a:line1, '.') == -1 + let pkg1 = '' + let cls1 = substitute(a:line1, '.* \(^[ ]\+\)', '\1', '') + else + let pkg1 = substitute(a:line1, '.*import\s\+\(.*\)\.[^. ;]\+.*$', '\1', '') + let cls1 = substitute(a:line1, '^.*\.\([^. ;]\+\).*$', '\1', '') + endif + if stridx(a:line2, '.') == -1 + let pkg2 = '' + let cls2 = substitute(a:line2, '.* \(^[ ]\+\)', '\1', '') + else + let pkg2 = substitute(a:line2, '.*import\s\+\(.*\)\.[^. ;]\+.*$', '\1', '') + let cls2 = substitute(a:line2, '^.*\.\([^. ;]\+\).*$', '\1', '') + endif + + let cmp = genutils#CmpByString(pkg1, pkg2, direction) + if cmp == 0 + let cmp = genutils#CmpByLength(cls1, cls2, direction) + endif + return cmp +endfunction + +function! genutils#CmpByString(line1, line2, ...) + let direction = (a:0?a:1:1) + if a:line1 < a:line2 + return -direction + elseif a:line1 > a:line2 + return direction + else + return 0 + endif +endfunction + +function! genutils#CmpByStringIgnoreCase(line1, line2, ...) + let direction = (a:0?a:1:1) + if a:line1 ? a:line2 + return direction + else + return 0 + endif +endfunction + +function! genutils#CmpByNumber(line1, line2, ...) + let direction = (a:0 ? a:1 :1) + let num1 = a:line1 + 0 + let num2 = a:line2 + 0 + + if num1 < num2 + return -direction + elseif num1 > num2 + return direction + else + return 0 + endif +endfunction + +function! genutils#QSort(cmp, direction) range + call s:QSortR(a:firstline, a:lastline, a:cmp, a:direction, + \ 's:BufLineAccessor', 's:BufLineSwapper', '') +endfunction + +function! genutils#QSort2(start, end, cmp, direction, accessor, swapper, context) + call s:QSortR(a:start, a:end, a:cmp, a:direction, a:accessor, a:swapper, + \ a:context) +endfunction + +" The default swapper that swaps lines in the current buffer. +function! s:BufLineSwapper(line1, line2, context) + let str2 = getline(a:line1) + silent! keepjumps call setline(a:line1, getline(a:line2)) + silent! keepjumps call setline(a:line2, str2) +endfunction + +" The default accessor that returns lines from the current buffer. +function! s:BufLineAccessor(line, context) + return getline(a:line) +endfunction + +" The default mover that moves lines from one place to another in the current +" buffer. +function! s:BufLineMover(from, to, context) + let line = getline(a:from) + exec a:from.'d_' + call append(a:to, line) +endfunction + +" +" Sort lines. QSortR() is called recursively. +" +function! s:QSortR(start, end, cmp, direction, accessor, swapper, context) + if a:end > a:start + let low = a:start + let high = a:end + + " Arbitrarily establish partition element at the midpoint of the data. + let midStr = {a:accessor}(((a:start + a:end) / 2), a:context) + + " Loop through the data until indices cross. + while low <= high + + " Find the first element that is greater than or equal to the partition + " element starting from the left Index. + while low < a:end + let result = {a:cmp}({a:accessor}(low, a:context), midStr, a:direction) + if result < 0 + let low = low + 1 + else + break + endif + endwhile + + " Find an element that is smaller than or equal to the partition element + " starting from the right Index. + while high > a:start + let result = {a:cmp}({a:accessor}(high, a:context), midStr, a:direction) + if result > 0 + let high = high - 1 + else + break + endif + endwhile + + " If the indexes have not crossed, swap. + if low <= high + " Swap lines low and high. + call {a:swapper}(high, low, a:context) + let low = low + 1 + let high = high - 1 + endif + endwhile + + " If the right index has not reached the left side of data must now sort + " the left partition. + if a:start < high + call s:QSortR(a:start, high, a:cmp, a:direction, a:accessor, a:swapper, + \ a:context) + endif + + " If the left index has not reached the right side of data must now sort + " the right partition. + if low < a:end + call s:QSortR(low, a:end, a:cmp, a:direction, a:accessor, a:swapper, + \ a:context) + endif + endif +endfunction + +function! genutils#BinSearchForInsert(start, end, line, cmp, direction) + return genutils#BinSearchForInsert2(a:start, a:end, a:line, a:cmp, + \ a:direction, 's:BufLineAccessor', '') +endfunction + +function! genutils#BinSearchForInsert2(start, end, line, cmp, direction, + \ accessor, context) + let start = a:start - 1 + let end = a:end + while start < end + let middle = (start + end + 1) / 2 + " Support passing both Funcref's as well as names. + if type(a:cmp) == 2 + if type(a:accessor) == 2 + let result = a:cmp(a:accessor(middle, a:context), a:line, a:direction) + else + let result = a:cmp({a:accessor}(middle, a:context), a:line, a:direction) + endif + else + if type(a:accessor) == 2 + let result = {a:cmp}(a:accessor(middle, a:context), a:line, a:direction) + else + let result = {a:cmp}({a:accessor}(middle, a:context), a:line, a:direction) + endif + endif + if result < 0 + let start = middle + else + let end = middle - 1 + endif + endwhile + return start +endfunction + +function! genutils#BinSearchList(list, start, end, item, cmp) + let start = a:start - 1 + let end = a:end + while start < end + let middle = (start + end + 1) / 2 + let result = call(a:cmp, [get(a:list, middle), a:item]) + if result < 0 + let start = middle + else + let end = middle - 1 + endif + endwhile + return start +endfunction + +function! genutils#BinInsertSort(cmp, direction) range + call genutils#BinInsertSort2(a:firstline, a:lastline, a:cmp, a:direction, + \ 's:BufLineAccessor', 's:BufLineMover', '') +endfunction + +function! genutils#BinInsertSort2(start, end, cmp, direction, accessor, mover, context) + let i = a:start + 1 + while i <= a:end + let low = s:BinSearchToAppend2(a:start, i, {a:accessor}(i, a:context), + \ a:cmp, a:direction, a:accessor, a:context) + " Move the object. + if low < i + call {a:mover}(i, low - 1, a:context) + endif + let i = i + 1 + endwhile +endfunction + +function! s:BinSearchToAppend(start, end, line, cmp, direction) + return s:BinSearchToAppend2(a:start, a:end, a:line, a:cmp, a:direction, + \ 's:BufLineAccessor', '') +endfunction + +function! s:BinSearchToAppend2(start, end, line, cmp, direction, accessor, + \ context) + let low = a:start + let high = a:end + while low < high + let mid = (low + high) / 2 + let diff = {a:cmp}({a:accessor}(mid, a:context), a:line, a:direction) + if diff > 0 + let high = mid + else + let low = mid + 1 + if diff == 0 + break + endif + endif + endwhile + return low +endfunction + +""" END: Sorting support. }}} + + +" Eats character if it matches the given pattern. +" +" Originally, +" From: Benji Fisher +" Date: Mon, 25 Mar 2002 15:05:14 -0500 +" +" Based on Bram's idea of eating a character while type to expand an +" abbreviation. This solves the problem with abbreviations, where we are +" left with an extra space after the expansion. +" Ex: +" inoreabbr \stdout\ System.out.println("");=genutils#EatChar('\s') +function! genutils#EatChar(pat) + let c = nr2char(getchar()) + "call input('Pattern: '.a:pat.' '. + " \ ((c =~ a:pat) ? 'Returning empty' : 'Returning: '.char2nr(c))) + return (c =~ a:pat) ? '' : c +endfun + + +" Can return a spacer from 0 to 80 characters width. +let s:spacer= " ". + \ " " +function! genutils#GetSpacer(width) + return strpart(s:spacer, 0, a:width) +endfunction + +function! genutils#SilentSubstitute(pat, cmd) + call genutils#SaveHardPosition('SilentSubstitute') + let _search = @/ + try + let @/ = a:pat + keepjumps silent! exec a:cmd + finally + let @/ = _search + call genutils#RestoreHardPosition('SilentSubstitute') + call genutils#ResetHardPosition('SilentSubstitute') + endtry +endfunction + +function! genutils#SilentDelete(arg1, ...) + " For backwards compatibility. + if a:0 + let range = a:arg1 + let pat = a:1 + else + let range = '' + let pat = a:arg1 + endif + let _search = @/ + call genutils#SaveHardPosition('SilentDelete') + try + let @/ = pat + keepjumps silent! exec range'g//d _' + finally + let @/ = _search + call genutils#RestoreHardPosition('SilentDelete') + call genutils#ResetHardPosition('SilentDelete') + endtry +endfunction + +" START: genutils#Roman2Decimal {{{ +let s:I = 1 +let s:V = 5 +let s:X = 10 +let s:L = 50 +let s:C = 100 +let s:D = 500 +let s:M = 1000 + +function! s:Char2Num(c) + " A bit of magic on empty strings + if a:c == "" + return 0 + endif + exec 'let n = s:' . toupper(a:c) + return n +endfun + +function! genutils#Roman2Decimal(str) + if a:str !~? '^[IVXLCDM]\+$' + return a:str + endif + let sum = 0 + let i = 0 + let n0 = s:Char2Num(a:str[i]) + let len = strlen(a:str) + while i < len + let i = i + 1 + let n1 = s:Char2Num(a:str[i]) + " Magic: n1=0 when i exceeds len + if n1 > n0 + let sum = sum - n0 + else + let sum = sum + n0 + endif + let n0 = n1 + endwhile + return sum +endfun +" END: genutils#Roman2Decimal }}} + + +" BEGIN: Relative path {{{ +function! genutils#CommonPath(path1, path2, ...) + let path1 = genutils#CleanupFileName(a:path1) . ((a:0 > 0 ? a:1 : 0) ? '/' : '') + let path2 = genutils#CleanupFileName(a:path2) . ((a:0 > 1 ? a:2 : 0) ? '/' : '') + return genutils#CommonString(path1, path2) +endfunction + +function! genutils#CommonString(str1, str2) + let str1 = a:str1 + let str2 = a:str2 + if str1 == str2 + return str1 + endif + let n = 0 + while str1[n] == str2[n] + let n = n+1 + endwhile + return strpart(str1, 0, n) +endfunction + +function! genutils#RelPathFromFile(srcFile, tgtFile) + return genutils#RelPathFromDir(fnamemodify(a:srcFile, ':h'), a:tgtFile) +endfunction + +function! genutils#RelPathFromDir(srcDir, tgtFile) + let cleanDir = genutils#CleanupFileName(a:srcDir).'/' + let cleanFile = genutils#CleanupFileName(a:tgtFile) + let cmnPath = genutils#CommonPath(cleanDir, cleanFile, 1) + let relDirFromCmnPath = strpart(cleanDir, strlen(cmnPath)) + if relDirFromCmnPath == '/' " This means the file is under the srcDir. + let relDirFromCmnPath = '' + endif + let relFileFromCmnPath = strpart(cleanFile, strlen(cmnPath)) + return substitute(relDirFromCmnPath, '[^/]\+', '..', 'g') . + \ relFileFromCmnPath +endfunction + +" END: Relative path }}} + + +" BEGIN: Persistent settings {{{ +if ! exists("g:genutilsNoPersist") || ! g:genutilsNoPersist + " Make sure the '!' option to store global variables that are upper cased are + " stored in viminfo file. + " Make sure it is the first option, so that it will not interfere with the + " 'n' option ("Todd J. Cosgrove" (todd dot cosgrove at softechnics dot + " com)). + " Also take care of empty value, when 'compatible' mode is on (Bram + " Moolenar) + if &viminfo == '' + set viminfo=!,'20,"50,h + else + set viminfo^=! + endif +endif + +function! genutils#PutPersistentVar(pluginName, persistentVar, value) + if ! exists("g:genutilsNoPersist") || ! g:genutilsNoPersist + let globalVarName = s:PersistentVarName(a:pluginName, a:persistentVar) + exec 'let ' . globalVarName . " = '" . a:value . "'" + endif +endfunction + +function! genutils#GetPersistentVar(pluginName, persistentVar, default) + if ! exists("g:genutilsNoPersist") || ! g:genutilsNoPersist + let globalVarName = s:PersistentVarName(a:pluginName, a:persistentVar) + if (exists(globalVarName)) + exec 'let value = ' . globalVarName + exec 'unlet ' . globalVarName + else + let value = a:default + endif + return value + else + return default + endif +endfunction + +function! s:PersistentVarName(pluginName, persistentVar) + return 'g:GU_' . toupper(a:pluginName) . '_' . toupper(a:persistentVar) +endfunction +" END: Persistent settings }}} + + +" FileChangedShell handling {{{ +if !exists('s:fcShellPreFuncs') + let s:fcShellPreFuncs = {} +endif + +function! genutils#AddToFCShellPre(funcName) + let s:fcShellPreFuncs[a:funcName] = a:funcName +endfunction + +function! genutils#RemoveFromFCShellPre(funcName) + if has_key(s:fcShellPreFuncs, a:funcName) + unlet s:fcShellPreFuncs[a:funcName] + endif +endfunction + +let s:defFCShellInstalled = 0 +function! genutils#DefFCShellInstall() + if ! s:defFCShellInstalled + aug DefFCShell + au! + au FileChangedShell * nested call genutils#DefFileChangedShell() + aug END + endif + let s:defFCShellInstalled = s:defFCShellInstalled + 1 +endfunction + +function! genutils#DefFCShellUninstall() + if s:defFCShellInstalled <= 0 + return + endif + let s:defFCShellInstalled = s:defFCShellInstalled - 1 + if ! s:defFCShellInstalled + aug DefFCShell + au! + aug END + endif +endfunction + +function! genutils#DefFileChangedShell() + let autoread = s:InvokeFuncs(s:fcShellPreFuncs) + let bufNo = expand('') + 0 + let fName = expand('') + let msg = '' + let v:fcs_choice = '' + if getbufvar(bufNo, '&modified') + let v:fcs_choice = 'ask' + elseif ! autoread + let v:fcs_choice = 'ask' + else + let v:fcs_choice = 'reload' + endif + return +endfunction + +function! s:InvokeFuncs(funcList) + let autoread = &autoread + if len(a:funcList) != 0 + for nextFunc in keys(a:funcList) + let result = call(nextFunc, []) + if result != -1 + let autoread = autoread || result + endif + endfor + endif + return autoread +endfunction +" FileChangedShell handling }}} + + +" Sign related utilities {{{ +function! genutils#CurLineHasSign() + let signs = genutils#GetVimCmdOutput('sign place buffer=' . bufnr('%'), 1) + return (match(signs, + \ 'line=' . line('.') . '\s\+id=\d\+\s\+name=VimBreakPt') != -1) +endfunction + +function! genutils#ClearAllSigns() + let signs = genutils#GetVimCmdOutput('sign place buffer=' . bufnr('%'), 1) + let curIdx = 0 + let pat = 'line=\d\+\s\+id=\zs\d\+\ze\s\+name=VimBreakPt' + let id = 0 + while curIdx != -1 && curIdx < strlen(signs) + let id = matchstr(signs, pat, curIdx) + if id != '' + exec 'sign unplace ' . id . ' buffer=' . bufnr('%') + endif + let curIdx = matchend(signs, pat, curIdx) + endwhile +endfunction +" }}} + +let s:UNPROTECTED_CHAR_PRFX = '\%(\%([^\\]\|^\)\\\%(\\\\\)*\)\@ 0?1:0) + let regex = s:UNPROTECTED_CHAR_PRFX + let chars = a:chars + if strlen(chars) > 1 + let chars = '['.chars.']' + endif + if capture + let chars = '\('.chars.'\)' + endif + return regex.chars +endfunction + +function! genutils#PromptForElement(array, default, msg, skip, useDialog, + \ nCols) + let nCols = a:nCols + let index = 0 + let line = "" + let element = "" + let optionsMsg = "" + let colWidth = &columns / nCols - 1 " Leave a margin of one column as a gap. + let curCol = 1 + let nElements = len(a:array) + let newArray = [] " Without the skip element. + if index(a:array, a:skip) != -1 + let nElements = nElements - 1 + endif + for element in a:array + if element ==# a:skip + continue + endif + call add(newArray, element) + let element = strpart(index." ", 0, 4) . element + let eleColWidth = (strlen(element) - 1) / colWidth + 1 + " Fill up the spacer for the rest of the partial column. + let element = element . genutils#GetSpacer( + \ eleColWidth * (colWidth + 1) - strlen(element) - 1) + let wouldBeLength = strlen(line) + strlen(element) + 1 + if wouldBeLength > (curCol * (colWidth + eleColWidth)) && + \ wouldBeLength > &columns + let splitLine = 2 " Split before adding the new element. + elseif curCol == nCols + let splitLine = 1 " Split after adding the new element. + else + let splitLine = 0 + endif + if splitLine == 2 + if strlen(line) == &columns + " Remove the last space as it otherwise results in an extra empty line + " on the screen. + let line = strpart(line, 0, strlen(line) - 1) + endif + let optionsMsg = optionsMsg . line . "\n" + let line = element . ' ' + let curCol = strlen(element) / (colWidth + 1) + else + let line = line . element . ' ' + if splitLine == 1 + if strlen(line) == &columns + " Remove the last space as it otherwise results in an extra empty line + " on the screen. + let line = strpart(line, 0, strlen(line) - 1) + endif + let curCol = 0 " Reset col count. + let optionsMsg = optionsMsg . line . "\n" + let line = "" + endif + endif + let curCol = curCol + 1 + let index = index + 1 + endfor + " Finally if there is anything left in line, then append that too. + if line.'' != '' + let optionsMsg = optionsMsg . line . "\n" + let line = "" + endif + + " Default index or empty string. + let default = '' + if type(a:default) == 0 + let default = a:default + elseif a:default.'' != '' + let default = index(a:array, a:default) + endif + if a:default == -1 + let default = '' + endif + + while !exists("selectedElement") + if a:useDialog + let s:selection = inputdialog(optionsMsg . a:msg, default) + else + let s:selection = input(optionsMsg . a:msg, default) + endif + if s:selection.'' == '' + let selectedElement = '' + let s:selection = -1 + else + let s:selection = (s:selection !~# '^\d\+$') ? -1 : (s:selection + 0) + if s:selection >= 0 && s:selection < nElements + let selectedElement = newArray[s:selection] + else + echohl ERROR | echo "\nInvalid selection, please try again" | + \ echohl NONE + endif + endif + echo "\n" + endwhile + return selectedElement +endfunction + +let s:selection = -1 +function! genutils#GetSelectedIndex() + return s:selection +endfunction + +" Always match() with 'ignorecase' and 'smartcase' off. +function! s:Match(expr, pat, start) + let _ic = &ignorecase + let _scs = &smartcase + let result = -1 + try + set noignorecase + set nosmartcase + let result = match(a:expr, a:pat, a:start) + finally + let &ignorecase = _ic + let &smartcase = _scs + endtry + return result +endfunction + +" Restore cpo. +let &cpo = s:save_cpo +unlet s:save_cpo + +" vim6:fdm=marker et diff --git a/.vim/autoload/perforce.vim b/.vim/autoload/perforce.vim new file mode 100755 index 0000000..460ef9c --- /dev/null +++ b/.vim/autoload/perforce.vim @@ -0,0 +1,3715 @@ +" perforce.vim: Please see plugin/perforce.vim + +" Make sure line-continuations won't cause any problem. This will be restored +" at the end +let s:save_cpo = &cpo +set cpo&vim + + +""" BEGIN: Initializations {{{ + +" Determine the script id. +function! s:MyScriptId() + map xx xx + let s:sid = maparg("xx") + unmap xx + return substitute(s:sid, "xx$", "", "") +endfunction +let s:myScriptId = s:MyScriptId() +delfunction s:MyScriptId " This is not needed anymore. + +""" BEGIN: One-time initialization of some script variables {{{ +let s:lastMsg = '' +let s:lastMsgGrp = 'None' +" Indicates the current recursion level for executing p4 commands. +let s:recLevel = 0 + +if genutils#OnMS() && match(&shell, '\') != -1 + " When using cygwin bash with native vim, p4 gets confused by the PWD, which + " is in cygwin style. + let s:p4CommandPrefix = "unset PWD && " +else + let s:p4CommandPrefix = "" +endif + +" Special characters in a filename that are not acceptable in a filename (as a +" window title) on windows. +let s:specialChars = '\([*:?"<>|]\)' +let s:specialCharsMap = { + \ '*': 'S', + \ ':': 'C', + \ '?': 'Q', + \ '"': 'D', + \ '<': 'L', + \ '>': 'G', + \ '|': 'P', + \ } + +" +" A lot of metadata on perforce command syntax and handling. +" + +let s:p4KnownCmds = split('add,admin,annotate,branch,branches,change,changes,' . + \ 'client,clients,counter,counters,delete,depot,depots,describe,diff,' . + \ 'diff2,dirs,edit,filelog,files,fix,fixes,flush,fstat,get,group,' . + \ 'groups,have,help,info,integ,integrate,integrated,job,jobs,jobspec,' . + \ 'label,labels,labelsync,lock,logger,login,monitor,obliterate,opened,' . + \ 'passwd,print,protect,rename,reopen,resolve,resolved,revert,review,' . + \ 'reviews,set,submit,sync,triggers,typemap,unlock,user,users,verify,' . + \ 'where,workspaces,', ',') +" Add some built-in commands to this list. +let s:builtinCmds = split('vdiff,vdiff2,exec,', ',') +let s:allCommands = s:p4KnownCmds + s:builtinCmds +let s:p4KnownCmdsCompStr = '' + +" Map between the option and the commands that reqire us to pass an argument +" with this option. +let s:p4OptCmdMap = {} +let s:p4OptCmdMap['b'] = split('diff2,integrate', ',') +let s:p4OptCmdMap['c'] = split('add,delete,edit,fix,fstat,integrate,lock,' . + \ 'opened,reopen,r[ver],review,reviews,submit,unlock', ',') +let s:p4OptCmdMap['e'] = ['jobs'] +let s:p4OptCmdMap['j'] = ['fixes'] +let s:p4OptCmdMap['l'] = ['labelsync'] +let s:p4OptCmdMap['m'] = split('changes,filelog,jobs', ',') +let s:p4OptCmdMap['o'] = ['print'] +let s:p4OptCmdMap['s'] = split('changes,integrate', ',') +let s:p4OptCmdMap['t'] = split('add,client,edit,label,reopen', ',') +let s:p4OptCmdMap['O'] = ['passwd'] +let s:p4OptCmdMap['P'] = ['passwd'] +let s:p4OptCmdMap['S'] = ['set'] + +" These built-in options require us to pass an argument. These options start +" with a '+'. +let s:biOptCmdMap = {} +let s:biOptCmdMap['c'] = ['diff'] + +" Map the commands with short name to their long versions. +let s:shortCmdMap = {} +let s:shortCmdMap['p'] = 'print' +let s:shortCmdMap['d'] = 'diff' +let s:shortCmdMap['e'] = 'edit' +let s:shortCmdMap['a'] = 'add' +let s:shortCmdMap['r'] = 'revert' +let s:shortCmdMap['g'] = 'get' +let s:shortCmdMap['o'] = 'open' +let s:shortCmdMap['d2'] = 'diff2' +let s:shortCmdMap['h'] = 'help' + + +" NOTE: The current file is used as the default argument, only when the +" command is not one of the s:askUserCmds and it is not one of +" s:curFileNotDefCmds or s:nofileArgsCmds. +" For these commands, we don't need to default to the current file, as these +" commands can work without any arguments. +let s:curFileNotDefCmds = split('change,changes,client,files,integrate,job,' . + \ 'jobs,jobspec,labels,labelsync,opened,resolve,submit,user,', ',') +" For these commands, we need to ask user for the argument, as we can't assume +" the current file is the default. +let s:askUserCmds = split('admin,branch,counter,depot,fix,group,label,', ',') +" A subset of askUserCmds, that should use a more generic prompt. +let s:genericPromptCmds = split('admin,counter,fix,', ',') +" Commands that essentially display a list of files. +let s:filelistCmds = split('files,have,integrate,opened,', ',') +" Commands that work with a spec. +let s:specCmds = split('branch,change,client,depot,group,job,jobspec,label,' . + \ 'protect,submit,triggers,typemap,user,', ',') +" Out of the above specCmds, these are the only commands that don't +" support '-o' option. Consequently we have to have our own template. +let s:noOutputCmds = ['submit'] +" The following are used only to create a specification, not to view them. +" Consequently, they don't accept a '-d' option to delete the spec. +let s:specOnlyCmds = split('jobspec,submit,', ',') +" These commands might change the fstat of files, requiring an update on some +" or all the buffers loaded into vim. +"let s:statusUpdReqCmds = 'add,delete,edit,get,lock,reopen,revert,sync,unlock,' +"" For these commands we need to call :checktime, as the command might have +"" changed the state of the file. +"let s:checktimeReqCmds = 'edit,get,reopen,revert,sync,' +" For these commands, we can even set 'autoread' along with doing a :checktime. +let s:autoreadCmds = split('edit,get,reopen,revert,sync,', ',') +" These commands don't expect filename arguments, so no special processing for +" file expansion. +let s:nofileArgsCmds = split('branch,branches,change,client,clients,counters,' . + \ 'depot,depots,describe,dirs,group,groups,help,info,job,jobspec,label,' . + \ 'logger,passwd,protect,rename,review,triggers,typemap,user,users,', ',') +" For these commands, the output should not be set to perforce type. +let s:ftNotPerforceCmds = split('diff,diff2,print,vdiff,vdiff2', ',') +" Allows navigation keys in the command window. +let s:navigateCmds = ['help'] +" These commands accept a '-m' argument to limit the list size. +let s:limitListCmds = split('filelog,jobs,changes,', ',') +" These commands take the diff option -dx. +let s:diffCmds = split('describe,diff,diff2,', ',') +" The following commands prefer dialog output. If the output exceeds +" g:p4MaxLinesInDialog, we should switch to showing the output in a window. +let s:dlgOutputCmds = + \ split('add,delete,edit,get,lock,reopen,revert,sync,unlock,', ',') + +" If there is a confirm message, then PFIF() will prompt user before +" continuing with the run. +let s:confirmMsgs{'revert'} = "Reverting file(s) will overwrite any edits to " . + \ "the files(s)\n Do you want to continue?" +let s:confirmMsgs{'submit'} = "This will commit the changelist to the depot." . + \ "\n Do you want to continue?" + +" Settings that are not directly exposed to the user. These can be accessed +" using the public API. +" Refresh the contents of perforce windows, even if the window is already open. +let s:refreshWindowsAlways = 1 + +" List of the global variable names of the user configurable settings. +let s:settings = split('ClientRoot,CmdPath,Presets,' . + \ 'DefaultOptions,DefaultDiffOptions,EnableMenu,EnablePopupMenu,' . + \ 'UseExpandedMenu,UseExpandedPopupMenu,EnableRuler,RulerWidth,' . + \ 'DefaultListSize,EnableActiveStatus,OptimizeActiveStatus,' . + \ 'ASIgnoreDefPattern,ASIgnoreUsrPattern,PromptToCheckout,' . + \ 'CheckOutDefault,UseGUIDialogs,MaxLinesInDialog,SortSettings,' . + \ 'TempDir,SplitCommand,UseVimDiff2,EnableFileChangedShell,' . + \ 'BufHidden,Depot,Autoread,UseClientViewMap,DefaultPreset', ',') +let s:settingsCompStr = '' + +let s:helpWinName = 'P4\ help' + +" Unprotected space. +let s:SPACE_AS_SEP = genutils#CrUnProtectedCharsPattern(' ') +let s:EMPTY_STR = '^\_s*$' + +if !exists('s:p4Client') || s:p4Client =~# s:EMPTY_STR + let s:p4Client = $P4CLIENT +endif +if !exists('s:p4User') || s:p4User =~# s:EMPTY_STR + if exists("$P4USER") && $P4USER !~# s:EMPTY_STR + let s:p4User = $P4USER + elseif genutils#OnMS() && exists("$USERNAME") + let s:p4User = $USERNAME + elseif exists("$LOGNAME") + let s:p4User = $LOGNAME + elseif exists("$USERNAME") " Happens if you are on cygwin too. + let s:p4User = $USERNAME + else + let s:p4User = '' + endif +endif +if !exists('s:p4Port') || s:p4Port =~# s:EMPTY_STR + let s:p4Port = $P4PORT +endif +let s:p4Password = $P4PASSWD + +let s:CM_RUN = 'run' | let s:CM_FILTER = 'filter' | let s:CM_DISPLAY = 'display' +let s:CM_PIPE = 'pipe' + +let s:changesExpr = "matchstr(getline(\".\"), '" . + \ '^Change \zs\d\+\ze ' . "')" +let s:branchesExpr = "matchstr(getline(\".\"), '" . + \ '^Branch \zs[^ ]\+\ze ' . "')" +let s:labelsExpr = "matchstr(getline(\".\"), '" . + \ '^Label \zs[^ ]\+\ze ' . "')" +let s:clientsExpr = "matchstr(getline(\".\"), '" . + \ '^Client \zs[^ ]\+\ze ' . "')" +let s:usersExpr = "matchstr(getline(\".\"), '" . + \ '^[^ ]\+\ze <[^@>]\+@[^>]\+> ([^)]\+)' . "')" +let s:jobsExpr = "matchstr(getline(\".\"), '" . + \ '^[^ ]\+\ze on ' . "')" +let s:depotsExpr = "matchstr(getline(\".\"), '" . + \ '^Depot \zs[^ ]\+\ze ' . "')" +let s:describeExpr = 's:DescribeGetCurrentItem()' +let s:filelogExpr = 's:GetCurrentDepotFile(line("."))' +let s:groupsExpr = 'expand("")' + +let s:fileBrowseExpr = 's:ConvertToLocalPath(s:GetCurrentDepotFile(line(".")))' +let s:openedExpr = s:fileBrowseExpr +let s:filesExpr = s:fileBrowseExpr +let s:haveExpr = s:fileBrowseExpr +let s:integrateExpr = s:fileBrowseExpr +" Open in describe window should open the local file. +let s:describeOpenItemExpr = s:fileBrowseExpr + +" If an explicit handler is defined, then it will override the default rule of +" finding the command with the singular form. +let s:filelogItemHandler = "s:printHdlr" +let s:changesItemHandler = "s:changeHdlr" +let s:openedItemHandler = "s:OpenFile" +let s:describeItemHandler = "s:OpenFile" +let s:filesItemHandler = "s:OpenFile" +let s:haveItemHandler = "s:OpenFile" + +" Define handlers for built-in commands. These have no arguments, they will +" use the existing parsed command-line vars. Set s:errCode on errors. +let s:builtinCmdHandler{'vdiff'} = 's:VDiffHandler' +let s:builtinCmdHandler{'vdiff2'} = 's:VDiff2Handler' +let s:builtinCmdHandler{'exec'} = 's:ExecHandler' + +let s:vdiffCounter = 0 + +" A stack of contexts. +let s:p4Contexts = [] + +" Cache of client view mappings, with client name as the key. +let s:fromDepotMapping = {} +let s:toDepotMapping = {} + +aug Perforce | aug END " Define autocommand group. +call genutils#AddToFCShellPre('perforce#FileChangedShell') + +""" END: One-time initialization of some script variables }}} + +""" END: Initializations }}} + + +""" BEGIN: Command specific functions {{{ + +function! s:printHdlr(scriptOrigin, outputType, ...) + let retVal = call('perforce#PFIF', [a:scriptOrigin + 1, a:outputType, 'print'] + \ +a:000) + + if s:StartBufSetup() + let undo = 0 + " The first line printed by p4 for non-q operation causes vim to misjudge + " the filetype. + if getline(1) =~# '//[^#]\+#\d\+ - ' + setlocal modifiable + let firstLine = getline(1) + silent! 1delete _ + endif + + set ft= + doautocmd filetypedetect BufNewFile + " If automatic detection doesn't work... + if &ft == "" + let &ft=s:GuessFileTypeForCurrentWindow() + endif + + if exists('firstLine') + silent! 1put! =firstLine + setlocal nomodifiable + endif + + call s:EndBufSetup() + endif + return retVal +endfunction + +function! s:describeHdlr(scriptOrigin, outputType, ...) + if !a:scriptOrigin + call call('s:ParseOptionsIF', [1, line('$'), 1, a:outputType, 'describe']+a:000) + endif + " If -s doesn't exist, and user doesn't intent to see a diff, then let us + " add -s option. In any case he can press enter on the to see + " it later. + if index(s:p4CmdOptions, '-s') == -1 && + \ s:indexMatching(s:p4CmdOptions, '^-d.\+$') == -1 + call add(s:p4CmdOptions, '-s') + let s:p4WinName = s:MakeWindowName() " Adjust window name. + endif + + let retVal = perforce#PFIF(2, a:outputType, 'describe') + if s:StartBufSetup() && getline(1) !~# ' - no such changelist' + call s:SetupFileBrowse() + if index(s:p4CmdOptions, '-s') != -1 + setlocal modifiable + silent! 2,$g/^Change \d\+ by \|\%$/ + \ call append(line('.')-1, ['', "\t", '']) + setlocal nomodifiable + else + call s:SetupDiff() + endif + + call s:EndBufSetup() + endif + return retVal +endfunction + +function! s:diffHdlr(scriptOrigin, outputType, ...) + if !a:scriptOrigin + call call('s:ParseOptionsIF', [1, line('$'), 1, a:outputType, 'diff']+a:000) + endif + + " If a change number is specified in the diff, we need to handle it + " ourselves, as p4 doesn't understand this. + let changeOptIdx = index(s:p4CmdOptions, '++c') + let changeNo = '' + if changeOptIdx != -1 " If a change no. is specified. + let changeNo = s:p4CmdOptions[changeOptIdx+1] + call s:PushP4Context() + try + call extend(s:p4Options, ['++T', '++N'], 0) " Testmode. + let retVal = perforce#PFIF(2, a:outputType, 'diff') " Opens window. + if s:errCode == 0 + setlocal modifiable + exec '%PF ++f opened -c' changeNo + endif + finally + let cntxtStr = s:PopP4Context() + endtry + else + " Any + option is treated like a signal to run external diff. + let externalDiffOptExists = (s:indexMatching(s:p4CmdOptions, '^+\S\+$') != -1) + if externalDiffOptExists + if len(s:p4Arguments) > 1 + return s:SyntaxError('External diff options can not be used with multiple files.') + endif + let needsPop = 0 + try + let _p4Options = copy(s:p4Options) + call insert(s:p4Options, '++T', 0) " Testmode, just open the window. + let retVal = perforce#PFIF(2, 0, 'diff') + let s:p4Options = _p4Options + if s:errCode != 0 + return + endif + call s:PushP4Context() | let needsPop = 1 + PW print -q + if s:errCode == 0 + setlocal modifiable + let fileName = s:ConvertToLocalPath(s:p4Arguments[0]) + call s:PeekP4Context() + " Gather and process only external options. + " Sample: + " '-x +width=10 -du -y +U=20 -z -a -db +tabsize=4' + " to + " '--width=10 -U 20 --tabsize=4' + let diffOpts = [] + for opt in s:p4CmdOptions + if opt =~ '^+' + call add(diffOpts, substitute(opt, '^+\([^= ]\+\)=\(.*\)$', + \ '\=(strlen(submatch(1)) > 1 ? '. + \ '("--".submatch(1).'. + \ '(submatch(2) != "" ? "=".submatch(2) : "")) : '. + \ '("-".submatch(1).'. + \ '(submatch(2) != "" ? " ".submatch(2) : "")))', + \ 'g')) + endif + endfor + if getbufvar(bufnr('#'), '&ff') ==# 'dos' + setlocal ff=dos + endif + silent! exec '%!'. + \ genutils#EscapeCommand('diff', diffOpts+['--', '-', fileName], + \ '') + if v:shell_error > 1 + call s:EchoMessage('Error executing external diff command. '. + \ 'Verify that GNU (or a compatible) diff is in your path.', + \ 'ERROR') + return '' + endif + call genutils#SilentSubstitute("\$", '%s///') + call genutils#SilentSubstitute('^--- -', '1s;;--- '. + \ s:ConvertToDepotPath(fileName)) + 1 + endif + finally + setlocal nomodifiable + if needsPop + call s:PopP4Context() + endif + endtry + else + let retVal = perforce#PFIF(2, exists('$P4DIFF') ? 5 : a:outputType, 'diff') + endif + endif + + if s:StartBufSetup() + call s:SetupDiff() + + if changeNo != '' && getline(1) !~# 'ile(s) not opened on this client\.' + setl modifiable + call genutils#SilentSubstitute('#.*', '%s///e') + call s:SetP4ContextVars(cntxtStr) " Restore original diff context. + call perforce#PFIF(1, 0, '-x', '-', '++f', '++n', 'diff') + setl nomodifiable + endif + + call s:EndBufSetup() + endif + return retVal +endfunction + +function! s:diff2Hdlr(scriptOrigin, outputType, ...) + if !a:scriptOrigin + call call('s:ParseOptionsIF', [1, line('$'), 1, a:outputType, 'diff2']+a:000) + endif + + let s:p4Arguments = s:GetDiff2Args() + + let retVal = perforce#PFIF(2, exists('$P4DIFF') ? 5 : a:outputType, 'diff2') + if s:StartBufSetup() + call s:SetupDiff() + + call s:EndBufSetup() + endif + return retVal +endfunction + +function! s:passwdHdlr(scriptOrigin, outputType, ...) + if !a:scriptOrigin + call call('s:ParseOptionsIF', [1, line('$'), 1, a:outputType, 'passwd']+a:000) + endif + + let oldPasswd = "" + if index(s:p4CmdOptions, '-O') == -1 + let oldPasswd = input('Enter old password: ') + " FIXME: Handle empty passwords. + call add(add(s:p4CmdOptions, '-O'), oldPasswd) + endif + let newPasswd = "" + if index(s:p4CmdOptions, '-P') == -1 + while 1 + let newPasswd = input('Enter new password: ') + if (input('Re-enter new password: ') != newPasswd) + call s:EchoMessage("Passwords don't match", 'Error') + else + " FIXME: Handle empty passwords. + call add(add(s:p4CmdOptions, '-P'), newPasswd) + break + endif + endwhile + endif + let retVal = perforce#PFIF(2, a:outputType, 'passwd') + return retVal +endfunction + +" Only to avoid confirming for -n and -a options. +function! s:revertHdlr(scriptOrigin, outputType, ...) + if !a:scriptOrigin + call call('s:ParseOptionsIF', [1, line('$'), 1, + \ a:outputType, 'passwd']+a:000) + endif + + if index(s:p4CmdOptions, '-n') != -1 || index(s:p4CmdOptions, '-a') != -1 + call add(s:p4Options, '++y') + endif + let retVal = perforce#PFIF(2, a:outputType, 'revert') + return retVal +endfunction + +function! s:changeHdlrImpl(outputType) + let _p4Arguments = s:p4Arguments + " If argument(s) is not a number... + if len(s:p4Arguments) != 0 && s:indexMatching(s:p4Arguments, '^\d\+$') == -1 + let s:p4Arguments = [] " Let a new changelist be created. + endif + let retVal = perforce#PFIF(2, a:outputType, 'change') + let s:p4Arguments = _p4Arguments + if s:errCode == 0 && s:indexMatching(s:p4Arguments, '^\d\+$') == -1 + \ && (s:StartBufSetup() || s:commandMode ==# s:CM_FILTER) + if len(s:p4Arguments) != 0 + if search('^Files:\s*$', 'w') && line('.') != line('$') + + + call s:PushP4Context() + try + call call('perforce#PFrangeIF', [line("."), line("$"), 1, 0]+ + \ s:p4Options+['++f', 'opened', '-c', 'default']+ + \ s:p4Arguments) + finally + call s:PopP4Context() + endtry + + if s:errCode == 0 + call genutils#SilentSubstitute('^', '.,$s//\t/e') + call genutils#SilentSubstitute('#\d\+ - \(\S\+\) .*$', + \ '.,$s//\t# \1/e') + endif + endif + endif + + call s:EndBufSetup() + setl nomodified + if len(s:p4Arguments) != 0 && &cmdheight > 1 + " The message about W and WQ must have gone by now. + redraw | call perforce#LastMessage() + endif + else + " Save the filelist in this changelist so that we can update their status + " later. + if search('Files:\s*$', 'w') + let b:p4OrgFilelist = getline(line('.')+1, line('$')) + endif + endif + return retVal +endfunction + +function! s:changeHdlr(scriptOrigin, outputType, ...) + if !a:scriptOrigin + call call('s:ParseOptionsIF', [1, line('$'), 1, a:outputType, 'change']+a:000) + endif + let retVal = s:changeHdlrImpl(a:outputType) + if s:StartBufSetup() + command! -buffer -nargs=* PChangeSubmit :call call('W', + \ [0]+b:p4Options+['submit']+split(, '\s')) + + call s:EndBufSetup() + endif + return retVal +endfunction + +" Create a template for submit. +function! s:submitHdlr(scriptOrigin, outputType, ...) + if !a:scriptOrigin + call call('s:ParseOptionsIF', [1, line('$'), 1, a:outputType, 'submit']+a:000) + endif + + if index(s:p4CmdOptions, '-c') != -1 + " Non-interactive. + let retVal = perforce#PFIF(2, a:outputType, 'submit') + else + call s:PushP4Context() + try + " This is done just to get the :W and :WQ commands defined properly and + " open the window with a proper name. The actual job is done by the call + " to s:changeHdlrImpl() which is then run in filter mode to avoid the + " side-effects (such as :W and :WQ getting overwritten etc.) + call extend(s:p4Options, ['++y', '++T'], 0) " Don't confirm, and testmode. + call perforce#PFIF(2, 0, 'submit') + if s:errCode == 0 + call s:PeekP4Context() + let s:p4CmdOptions = [] " These must be specific to 'submit'. + let s:p4Command = 'change' + let s:commandMode = s:CM_FILTER | let s:filterRange = '.' + let retVal = s:changeHdlrImpl(a:outputType) + setlocal nomodified + if s:errCode != 0 + return + endif + endif + finally + call s:PopP4Context() + endtry + + if s:StartBufSetup() + command! -buffer -nargs=* PSubmitPostpone :call call('W', + \ [0]+b:p4Options+['change']+split(, '\s')) + set ft=perforce " Just to get the cursor placement right. + call s:EndBufSetup() + endif + + if s:errCode + call s:EchoMessage("Error creating submission template.", 'Error') + endif + endif + return s:errCode +endfunction + +function! s:resolveHdlr(scriptOrigin, outputType, ...) + if !a:scriptOrigin + call call('s:ParseOptionsIF', [1, line('$'), 1, a:outputType, 'resolve']+a:000) + endif + + if (s:indexMatching(s:p4CmdOptions, '^-a[fmsty]$') == -1) && + \ (index(s:p4CmdOptions, '-n') == -1) + return s:SyntaxError("Interactive resolve not implemented (yet).") + endif + let retVal = perforce#PFIF(2, a:outputType, 'resolve') + return retVal +endfunction + +function! s:filelogHdlr(scriptOrigin, outputType, ...) + let retVal = call('perforce#PFIF', [a:scriptOrigin + 1, a:outputType, 'filelog']+a:000) + + if s:StartBufSetup() + " No meaning for delete. + silent! nunmap D + silent! delcommand PItemDelete + command! -range -buffer -nargs=0 PFilelogDiff + \ :call s:FilelogDiff2(, ) + vnoremap D :PFilelogDiff + command! -buffer -nargs=0 PFilelogPrint :call perforce#PFIF(0, 0, 'print', + \ GetCurrentItem()) + nnoremap p :PFilelogPrint + command! -buffer -nargs=0 PFilelogSync :call FilelogSyncToCurrentItem() + nnoremap S :PFilelogSync + command! -buffer -nargs=0 PFilelogDescribe + \ :call FilelogDescribeChange() + nnoremap C :PFilelogDescribe + + call s:EndBufSetup() + endif +endfunction + +function! s:clientsHdlr(scriptOrigin, outputType, ...) + let retVal = call('perforce#PFIF', [a:scriptOrigin + 1, a:outputType, 'clients']+a:000) + + if s:StartBufSetup() + command! -buffer -nargs=0 PClientsTemplate + \ :call perforce#PFIF(0, 0, '++A', 'client', '-t', GetCurrentItem()) + nnoremap P :PClientsTemplate + + call s:EndBufSetup() + endif + return retVal +endfunction + +function! s:changesHdlr(scriptOrigin, outputType, ...) + let retVal = call('perforce#PFIF', [a:scriptOrigin + 1, a:outputType, 'changes']+a:000) + + if s:StartBufSetup() + command! -buffer -nargs=0 PItemDescribe + \ :call PChangesDescribeCurrentItem() + command! -buffer -nargs=0 PChangesSubmit + \ :call ChangesSubmitChangeList() + nnoremap S :PChangesSubmit + command! -buffer -nargs=0 PChangesOpened + \ :if getline('.') =~# " \\*pending\\* '" | + \ call perforce#PFIF(1, 0, 'opened', '-c', GetCurrentItem()) | + \ endif + nnoremap o :PChangesOpened + command! -buffer -nargs=0 PChangesDiff + \ :if getline('.') =~# " \\*pending\\* '" | + \ call perforce#PFIF(0, 0, 'diff', '++c', GetCurrentItem()) | + \ else | + \ call perforce#PFIF(0, 0, 'describe', (('DefaultDiffOptions') + \ =~ '^\s*$' ? '-dd' : ('DefaultDiffOptions')), + \ GetCurrentItem()) | + \ endif + nnoremap d :PChangesDiff + command! -buffer -nargs=0 PItemOpen + \ :if getline('.') =~# " \\*pending\\* '" | + \ call perforce#PFIF(0, 0, 'change', GetCurrentItem()) | + \ else | + \ call perforce#PFIF(0, 0, 'describe', '-dd', GetCurrentItem()) | + \ endif + + call s:EndBufSetup() + endif +endfunction + +function! s:labelsHdlr(scriptOrigin, outputType, ...) + let retVal = call('perforce#PFIF', [a:scriptOrigin + 1, a:outputType, 'labels']+a:000) + + if s:StartBufSetup() + command! -buffer -nargs=0 PLabelsSyncClient + \ :call LabelsSyncClientToLabel() + nnoremap S :PLabelsSyncClient + command! -buffer -nargs=0 PLabelsSyncLabel + \ :call LabelsSyncLabelToClient() + nnoremap C :PLabelsSyncLabel + command! -buffer -nargs=0 PLabelsFiles :call perforce#PFIF(0, 0, '++n', 'files', + \ '//...@'. GetCurrentItem()) + nnoremap I :PLabelsFiles + command! -buffer -nargs=0 PLabelsTemplate :call perforce#PFIF(0, 0, '++A', + \ 'label', '-t', GetCurrentItem()) + nnoremap P :PLabelsTemplate + + call s:EndBufSetup() + endif + return retVal +endfunction + +function! s:helpHdlr(scriptOrigin, outputType, ...) + call genutils#SaveWindowSettings2("PerforceHelp", 1) + " If there is a help window already open, then we need to reuse it. + let helpWin = bufwinnr(s:helpWinName) + let retVal = call('perforce#PFIF', [a:scriptOrigin + 1, a:outputType, 'help']+a:000) + + if s:StartBufSetup() + command! -buffer -nargs=0 PHelpSelect + \ :call perforce#PFIF(0, 0, 'help', expand("")) + nnoremap :PHelpSelect + nnoremap K :PHelpSelect + nnoremap <2-LeftMouse> :PHelpSelect + call genutils#AddNotifyWindowClose(s:helpWinName, s:myScriptId . + \ "RestoreWindows") + if helpWin == -1 " Resize only when it was not already visible. + exec "resize " . 20 + endif + redraw | echo + \ "Press /K/<2-LeftMouse> to drilldown on perforce help keywords." + + call s:EndBufSetup() + endif + return retVal +endfunction + +" Built-in command handlers {{{ +function! s:VDiffHandler() + let nArgs = len(s:p4Arguments) + if nArgs > 2 + return s:SyntaxError("vdiff: Too many arguments.") + endif + + let firstFile = '' + let secondFile = '' + if nArgs == 2 + let firstFile = s:p4Arguments[0] + let secondFile = s:p4Arguments[1] + elseif nArgs == 1 + let secondFile = s:p4Arguments[0] + else + let secondFile = s:EscapeFileName(s:GetCurFileName()) + endif + if firstFile == '' + let firstFile = s:ConvertToDepotPath(secondFile) + endif + call s:VDiffImpl(firstFile, secondFile, 0) +endfunction + +function! s:VDiff2Handler() + if len(s:p4Arguments) > 2 + return s:SyntaxError("vdiff2: Too many arguments") + endif + + let s:p4Arguments = s:GetDiff2Args() + call s:VDiffImpl(s:p4Arguments[0], s:p4Arguments[1], 1) +endfunction + +function! s:VDiffImpl(firstFile, secondFile, preferDepotPaths) + let firstFile = a:firstFile + let secondFile = a:secondFile + + if a:preferDepotPaths || s:PathRefersToDepot(firstFile) + let firstFile = s:ConvertToDepotPath(firstFile) + let tempFile1 = s:MakeTempName(firstFile) + else + let tempFile1 = firstFile + endif + if a:preferDepotPaths || s:PathRefersToDepot(secondFile) + let secondFile = s:ConvertToDepotPath(secondFile) + let tempFile2 = s:MakeTempName(secondFile) + else + let tempFile2 = secondFile + endif + if firstFile =~# s:EMPTY_STR || secondFile =~# s:EMPTY_STR || + \ (tempFile1 ==# tempFile2) + return s:SyntaxError("diff requires two distinct files as arguments.") + endif + + let s:vdiffCounter = s:vdiffCounter + 1 + + if s:IsDepotPath(firstFile) + let s:p4Command = 'print' + let s:p4CmdOptions = ['-q'] + let s:p4WinName = tempFile1 + let s:p4Arguments = [firstFile] + call perforce#PFIF(2, 0, 'print') + if s:errCode != 0 + return + endif + else + let v:errmsg = '' + silent! exec 'split' firstFile + if v:errmsg != "" + return s:ShowVimError("Error opening file: ".firstFile."\n".v:errmsg, '') + endif + endif + diffthis + let w:p4VDiffWindow = s:vdiffCounter + wincmd K + + " CAUTION: If there is a buffer or window local value, then this will get + " overridden, but it is OK. + if exists('t:p4SplitCommand') + let _splitCommand = t:p4SplitCommand + endif + let t:p4SplitCommand = 'vsplit' + let _splitright = &splitright + set splitright + try + if s:IsDepotPath(secondFile) + let s:p4Command = 'print' + let s:p4CmdOptions = ['-q'] + let s:p4WinName = tempFile2 + let s:p4Arguments = [secondFile] + call perforce#PFIF(2, 0, 'print') + if s:errCode != 0 + return + endif + else + let v:errmsg = '' + silent! exec 'vsplit' secondFile + if v:errmsg != "" + return s:ShowVimError("Error opening file: ".secondFile."\n".v:errmsg, '') + endif + endif + finally + if exists('_splitCommand') + let t:p4SplitCommand = _splitCommand + else + unlet t:p4SplitCommand + endif + let &splitright = _splitright + endtry + diffthis + let w:p4VDiffWindow = s:vdiffCounter + wincmd _ +endfunction + +" Returns a fileName in the temp directory that is unique for the branch and +" revision specified in the fileName. +function! s:MakeTempName(filePath) + let depotPath = s:ConvertToDepotPath(a:filePath) + if depotPath =~# s:EMPTY_STR + return '' + endif + let tmpName = s:_('TempDir') . '/' + let branch = s:GetBranchName(depotPath) + if branch !~# s:EMPTY_STR + let tmpName = tmpName . branch . '-' + endif + let revSpec = s:GetRevisionSpecifier(depotPath) + if revSpec !~# s:EMPTY_STR + let tmpName = tmpName . substitute(strpart(revSpec, 1), '/', '_', 'g') . '-' + endif + return tmpName . fnamemodify(substitute(a:filePath, '\\*#\d\+$', '', ''), + \ ':t') +endfunction + +function! s:ExecHandler() + if len(s:p4Arguments) != 0 + echo join(s:p4Arguments, ' ') + let cmdHasBang = 0 + if s:p4Arguments[0] =~# '^!' + let cmdHasBang = 1 + " FIXME: Pipe itself needs to be escaped, and they could be chained. + let cmd = genutils#EscapeCommand(substitute(s:p4Arguments[0], '^!', '', + \ ''), s:p4Arguments[1:], s:p4Pipe) + else + let cmd = join(s:p4Arguments, ' ') + endif + let cmd = genutils#Escape(cmd, '#%!') + try + exec (cmdHasBang ? '!' : '').cmd + catch + let v:errmsg = substitute(v:exception, '^[^:]\+:', '', '') + call s:ShowVimError(v:errmsg, v:throwpoint) + endtry + endif +endfunction + +" Built-in command handlers }}} + +""" END: Command specific functions }}} + + +""" BEGIN: Helper functions {{{ + +" Open a file from an alternative codeline. +" If mode == 0, first file is opened and all other files are added to buffer +" list. +" If mode == 1, the files are not really opened, the list is just returned. +" If mode == 2, it behaves the same as mode == 0, except that the file is +" split opened. +" If there are no arguments passed, user is prompted to enter. He can then +" enter a codeline followed by a list of filenames. +" If only one argument is passed, it is assumed to be the codeline and the +" current filename is assumed (user is not prompted). +function! perforce#PFOpenAltFile(mode, ...) " {{{ + let argList = copy(a:000) + if a:0 < 2 + if a:0 == 0 + " Prompt for codeline string (codeline optionally followed by filenames). + let codelineStr = s:PromptFor(0, s:_('UseGUIDialogs'), + \ "Enter the alternative codeline string: ", '') + if codelineStr =~# s:EMPTY_STR + return "" + endif + let argList = split(codelineStr, s:SPACE_AS_SEP) + endif + if len(argList) == 1 + call add(argList, s:EscapeFileName(s:GetCurFileName())) + endif + endif + + let altFileNames = call('s:PFGetAltFiles', ['']+argList) + if a:mode == 0 || a:mode == 2 + let firstFile = 1 + for altFileName in altFileNames + if firstFile + execute ((a:mode == 0) ? ":edit " : ":split ") . altFileName + let firstFile = 0 + else + execute ":badd " . altFileName + endif + endfor + else + return join(altFileNames, ' ') + endif +endfunction " }}} + +" Interactively change the port/client/user. {{{ +function! perforce#SwitchPortClientUser() + let p4Port = s:PromptFor(0, s:_('UseGUIDialogs'), "Port: ", s:_('p4Port')) + let p4Client = s:PromptFor(0, s:_('UseGUIDialogs'), "Client: ", s:_('p4Client')) + let p4User = s:PromptFor(0, s:_('UseGUIDialogs'), "User: ", s:_('p4User')) + call perforce#PFSwitch(1, p4Port, p4Client, p4User) +endfunction + +" No args: Print presets and prompt user to select a preset. +" Number: Select that numbered preset. +" port [client] [user]: Set the specified settings. +function! perforce#PFSwitch(updateClientRoot, ...) + if a:0 == 0 || match(a:1, '^\d\+$') == 0 + let selPreset = '' + let presets = split(s:_('Presets'), ',') + if a:0 == 0 + if len(presets) == 0 + call s:EchoMessage("No presets to select from.", 'Error') + return + endif + + let selPreset = genutils#PromptForElement(presets, -1, + \ "Select the setting: ", -1, s:_('UseGUIDialogs'), 1) + else + let index = a:1 + 0 + if index >= len(presets) + call s:EchoMessage("Not that many presets.", 'Error') + return + endif + let selPreset = presets[index] + endif + if selPreset == '' + return + endif + let argList = split(selPreset, s:SPACE_AS_SEP) + else + if a:0 == 1 + let argList = split(a:1, ' ') + else + let argList = a:000 + endif + endif + call call('s:PSwitchHelper', [a:updateClientRoot]+argList) + + " Loop through all the buffers and invalidate the filestatuses. + let lastBufNr = bufnr('$') + let i = 1 + while i <= lastBufNr + if bufexists(i) && getbufvar(i, '&buftype') == '' + call s:ResetFileStatusForBuffer(i) + endif + let i = i + 1 + endwhile +endfunction + +function! s:PSwitchHelper(updateClientRoot, ...) + let p4Port = a:1 + let p4Client = s:_('p4Client') + let p4User = s:_('p4User') + if a:0 > 1 + let p4Client = a:2 + endif + if a:0 > 2 + let p4User = a:3 + endif + if ! s:SetPortClientUser(p4Port, p4Client, p4User) + return + endif + + if a:updateClientRoot + if s:p4Port !=# 'P4CONFIG' + call s:GetClientInfo() + else + let g:p4ClientRoot = '' " Since the client is chosen dynamically. + endif + endif +endfunction + +function! s:SetPortClientUser(port, client, user) + if s:p4Port ==# a:port && s:p4Client ==# a:client && s:p4User ==# a:user + return 0 + endif + + let s:p4Port = a:port + let s:p4Client = a:client + let s:p4User = a:user + let s:p4Password = '' + return 1 +endfunction + +function! perforce#PFSwitchComplete(ArgLead, CmdLine, CursorPos) + return substitute(s:_('Presets'), ',', "\n", 'g') +endfunction +" port/client/user }}} + +function! s:PHelpComplete(ArgLead, CmdLine, CursorPos) + if s:p4KnownCmdsCompStr == '' + let s:p4KnownCmdsCompStr = join(s:p4KnownCmds, "\n") + endif + return s:p4KnownCmdsCompStr. + \ "simple\ncommands\nenvironment\nfiletypes\njobview\nrevisions\n". + \ "usage\nviews\n" +endfunction + +" Handler for opened command. +function! s:OpenFile(scriptOrigin, outputType, fileName) " {{{ + if filereadable(a:fileName) + if a:outputType == 0 + let curWin = winnr() + let bufNr = genutils#FindBufferForName(a:fileName) + let winnr = bufwinnr(bufNr) + if winnr != -1 + exec winnr.'wincmd w' + else + wincmd p + endif + if curWin != winnr() && &previewwindow + wincmd p " Don't use preview window. + endif + " Avoid loosing temporary buffers accidentally. + if winnr() == curWin || getbufvar('%', '&bufhidden') != '' + split + endif + if winbufnr(winnr()) != bufNr + if bufNr != -1 + exec "buffer" bufNr | " Preserves cursor position. + else + exec "edit " . a:fileName + endif + endif + else + exec "pedit " . a:fileName + endif + else + call perforce#PFIF(0, a:outputType, 'print', a:fileName) + endif +endfunction " }}} + +function! s:DescribeGetCurrentItem() " {{{ + if getline(".") ==# "\t" + let [changeHdrLine, col] = searchpos('^Change \zs\d\+ by ', 'bnW') + if changeHdrLine != 0 + let changeNo = matchstr(getline(changeHdrLine), '\d\+', col-1) + let _modifiable = &l:modifiable + try + setlocal modifiable + call genutils#SaveHardPosition('DescribeGetCurrentItem') + exec changeHdrLine.',.PF ++f describe' s:_('DefaultDiffOptions') changeNo + call genutils#RestoreHardPosition('DescribeGetCurrentItem') + call genutils#ResetHardPosition('DescribeGetCurrentItem') + finally + let &l:modifiable = _modifiable + endtry + call s:SetupDiff() + endif + return "" + endif + return s:GetCurrentDepotFile(line('.')) +endfunction " }}} + +function! s:getCommandItemHandler(outputType, command, args) " {{{ + let itemHandler = "" + if exists("s:{a:command}ItemHandler") + let itemHandler = s:{a:command}ItemHandler + elseif match(a:command, 'e\?s$') != -1 + let handlerCmd = substitute(a:command, 'e\?s$', '', '') + if exists('*s:{handlerCmd}Hdlr') + let itemHandler = 's:' . handlerCmd . 'Hdlr' + else + let itemHandler = 'perforce#PFIF' + endif + endif + if itemHandler ==# 'perforce#PFIF' + return "call perforce#PFIF(1, " . a:outputType . ", '" . handlerCmd . "', " . + \ a:args . ")" + elseif itemHandler !~# s:EMPTY_STR + return 'call ' . itemHandler . '(0, ' . a:outputType . ', ' . a:args . ')' + endif + return itemHandler +endfunction " }}} + +function! s:OpenCurrentItem(outputType) " {{{ + let curItem = s:GetOpenItem(a:outputType) + if curItem !~# s:EMPTY_STR + let commandHandler = s:getCommandItemHandler(a:outputType, b:p4Command, + \ "'" . curItem . "'") + if commandHandler !~# s:EMPTY_STR + exec commandHandler + endif + endif +endfunction " }}} + +function! s:GetCurrentItem() " {{{ + if exists("b:p4Command") && exists("s:{b:p4Command}Expr") + exec "return " s:{b:p4Command}Expr + endif + return "" +endfunction " }}} + +function! s:GetOpenItem(outputType) " {{{ + " For non-preview open. + if exists("b:p4Command") && a:outputType == 0 && + \ exists("s:{b:p4Command}OpenItemExpr") + exec "return " s:{b:p4Command}OpenItemExpr + endif + return s:GetCurrentItem() +endfunction " }}} + +function! s:DeleteCurrentItem() " {{{ + let curItem = s:GetCurrentItem() + if curItem !~# s:EMPTY_STR + let answer = s:ConfirmMessage("Are you sure you want to delete " . + \ curItem . "?", "&Yes\n&No", 2, "Question") + if answer == 1 + let commandHandler = s:getCommandItemHandler(2, b:p4Command, + \ "'-d', '" . curItem . "'") + if commandHandler !~# s:EMPTY_STR + exec commandHandler + endif + if v:shell_error == "" + call perforce#PFRefreshActivePane() + endif + endif + endif +endfunction " }}} + +function! s:LaunchCurrentFile() " {{{ + if g:p4FileLauncher =~# s:EMPTY_STR + call s:ConfirmMessage("There was no launcher command configured to launch ". + \ "this item, use g:p4FileLauncher to configure." , "OK", 1, "Error") + return + endif + let curItem = s:GetCurrentItem() + if curItem !~# s:EMPTY_STR + exec 'silent! !'.g:p4FileLauncher curItem + endif +endfunction " }}} + +function! s:FilelogDiff2(line1, line2) " {{{ + let line1 = a:line1 + let line2 = a:line2 + if line1 == line2 + if line2 < line("$") + let line2 = line2 + 1 + elseif line1 > 1 + let line1 = line1 - 1 + else + return + endif + endif + + let file1 = s:GetCurrentDepotFile(line1) + if file1 !~# s:EMPTY_STR + let file2 = s:GetCurrentDepotFile(line2) + if file2 !~# s:EMPTY_STR && file2 != file1 + " file2 will be older than file1. + exec "call perforce#PFIF(0, 0, \"" . (s:_('UseVimDiff2') ? 'vdiff2' : 'diff2') . + \ "\", file2, file1)" + endif + endif +endfunction " }}} + +function! s:FilelogSyncToCurrentItem() " {{{ + let curItem = s:GetCurrentItem() + if curItem !~# s:EMPTY_STR + let answer = s:ConfirmMessage("Do you want to sync to: " . curItem . " ?", + \ "&Yes\n&No", 2, "Question") + if answer == 1 + call perforce#PFIF(1, 2, 'sync', curItem) + endif + endif +endfunction " }}} + +function! s:ChangesSubmitChangeList() " {{{ + let curItem = s:GetCurrentItem() + if curItem !~# s:EMPTY_STR + let answer = s:ConfirmMessage("Do you want to submit change list: " . + \ curItem . " ?", "&Yes\n&No", 2, "Question") + if answer == 1 + call perforce#PFIF(0, 0, '++y', 'submit', '-c', curItem) + endif + endif +endfunction " }}} + +function! s:LabelsSyncClientToLabel() " {{{ + let curItem = s:GetCurrentItem() + if curItem !~# s:EMPTY_STR + let answer = s:ConfirmMessage("Do you want to sync client to the label: " . + \ curItem . " ?", "&Yes\n&No", 2, "Question") + if answer == 1 + let retVal = call('perforce#PFIF', [1, 1, 'sync', + \ '//".s:_('Depot')."/...@'.curItem]) + return retVal + endif + endif +endfunction " }}} + +function! s:LabelsSyncLabelToClient() " {{{ + let curItem = s:GetCurrentItem() + if curItem !~# s:EMPTY_STR + let answer = s:ConfirmMessage("Do you want to sync label: " . curItem . + \ " to client " . s:_('p4Client') . " ?", "&Yes\n&No", 2, "Question") + if answer == 1 + let retVal = perforce#PFIF(1, 1, 'labelsync', '-l', curItem) + return retVal + endif + endif +endfunction " }}} + +function! s:FilelogDescribeChange() " {{{ + let changeNo = matchstr(getline("."), ' change \zs\d\+\ze ') + if changeNo !~# s:EMPTY_STR + exec "call perforce#PFIF(0, 1, 'describe', changeNo)" + endif +endfunction " }}} + +function! s:SetupFileBrowse() " {{{ + " For now, assume that a new window is created and we are in the new window. + exec "setlocal includeexpr=P4IncludeExpr(v:fname)" + + " No meaning for delete. + silent! nunmap D + silent! delcommand PItemDelete + command! -buffer -nargs=0 PFileDiff :call perforce#PFIF(0, 1, 'diff', + \ GetCurrentDepotFile(line("."))) + nnoremap D :PFileDiff + command! -buffer -nargs=0 PFileProps :call perforce#PFIF(1, 0, 'fstat', '-C', + \ GetCurrentDepotFile(line("."))) + nnoremap P :PFileProps + command! -buffer -nargs=0 PFileLog :call perforce#PFIF(1, 0, 'filelog', + \ GetCurrentDepotFile(line("."))) + command! -buffer -nargs=0 PFileEdit :call perforce#PFIF(1, 2, 'edit', + \ GetCurrentItem()) + nnoremap I :PFileEdit + command! -buffer -bar -nargs=0 PFileRevert :call perforce#PFIF(1, 2, 'revert', + \ GetCurrentItem()) + nnoremap R :PFileRevert \| PFRefreshActivePane + command! -buffer -nargs=0 PFilePrint + \ :if getline('.') !~# '(\%(u\|ux\)binary)$' | + \ call perforce#PFIF(0, 0, 'print', + \ substitute(GetCurrentDepotFile(line('.')), '#[^#]\+$', '', ''). + \ '#'. + \ ((getline(".") =~# '#\d\+ - delete change') ? + \ matchstr(getline('.'), '#\zs\d\+\ze - ') - 1 : + \ matchstr(getline('.'), '#\zs\d\+\ze - ')) + \ ) | + \ else | + \ echo 'PFilePrint: Binary file... ignored.' | + \ endif + nnoremap p :PFilePrint + command! -buffer -nargs=0 PFileGet :call perforce#PFIF(1, 2, 'sync', + \ GetCurrentDepotFile(line("."))) + command! -buffer -nargs=0 PFileSync :call perforce#PFIF(1, 2, 'sync', + \ GetCurrentItem()) + nnoremap S :PFileSync + command! -buffer -nargs=0 PFileChange :call perforce#PFIF(0, 0, 'change', + \ GetCurrentChangeNumber(line("."))) + nnoremap C :PFileChange + command! -buffer -nargs=0 PFileLaunch :call LaunchCurrentFile() + nnoremap A :PFileLaunch +endfunction " }}} + +function! s:SetupDiff() " {{{ + setlocal ft=diff +endfunction " }}} + +function! s:SetupSelectItem() " {{{ + nnoremap D :PItemDelete + nnoremap O :PItemOpen + nnoremap :PItemDescribe + nnoremap <2-LeftMouse> :PItemDescribe + command! -buffer -nargs=0 PItemDescribe :call OpenCurrentItem(1) + command! -buffer -nargs=0 PItemOpen :call OpenCurrentItem(0) + command! -buffer -nargs=0 PItemDelete :call DeleteCurrentItem() + cnoremap =GetCurrentItem() +endfunction " }}} + +function! s:RestoreWindows(dummy) " {{{ + call genutils#RestoreWindowSettings2("PerforceHelp") +endfunction " }}} + +function! s:NavigateBack() " {{{ + call s:Navigate('u') + if line('$') == 1 && getline(1) == '' + call s:NavigateForward() + endif +endfunction " }}} + +function! s:NavigateForward() " {{{ + call s:Navigate("\") +endfunction " }}} + +function! s:Navigate(key) " {{{ + let _modifiable = &l:modifiable + try + setlocal modifiable + " Use built-in markers as Vim takes care of remembering and restoring them + " during the undo/redo. + normal! mt + + silent! exec "normal" a:key + + if line("'t") > 0 && line("'t") <= line('$') + normal! `t + endif + finally + let &l:modifiable = _modifiable + endtry +endfunction " }}} + +function! s:GetCurrentChangeNumber(lineNo) " {{{ + let line = getline(a:lineNo) + let changeNo = matchstr(line, ' - \S\+ change \zs\S\+\ze (') + if changeNo ==# 'default' + let changeNo = '' + endif + return changeNo +endfunction " }}} + +function! s:PChangesDescribeCurrentItem() " {{{ + let currentChangeNo = s:GetCurrentItem() + if currentChangeNo !~# s:EMPTY_STR + call perforce#PFIF(0, 1, 'describe', '-s', currentChangeNo) + endif +endfunction " }}} + +" {{{ +function! perforce#PFSettings(...) + if s:_('SortSettings ') + if exists("s:sortedSettings") + let settings = s:sortedSettings + else + let settings = sort(s:settings) + let s:sortedSettings = settings + endif + else + let settings = s:settings + endif + if a:0 > 0 + let selectedSetting = a:1 + else + let selectedSetting = genutils#PromptForElement(settings, -1, + \ "Select the setting: ", -1, 0, 3) + endif + if selectedSetting !~# s:EMPTY_STR + let oldVal = s:_(selectedSetting) + if a:0 > 1 + let newVal = a:2 + echo 'Current value for' selectedSetting.': "'.oldVal.'" New value: "'. + \ newVal.'"' + else + let newVal = input('Current value for ' . selectedSetting . ' is: ' . + \ oldVal . "\nEnter new value: ", oldVal) + endif + if newVal != oldVal + let g:p4{selectedSetting} = newVal + call perforce#Initialize(1) + endif + endif +endfunction + +function! perforce#PFSettingsComplete(ArgLead, CmdLine, CursorPos) + if s:settingsCompStr == '' + let s:settingsCompStr = join(s:settings, "\n") + endif + return s:settingsCompStr +endfunction +" }}} + +function! s:MakeRevStr(ver) " {{{ + let verStr = '' + if a:ver =~# '^[#@&]' + let verStr = a:ver + elseif a:ver =~# '^[-+]\?\d\+\>\|^none\>\|^head\>\|^have\>' + let verStr = '#' . a:ver + elseif a:ver !~# s:EMPTY_STR + let verStr = '@' . a:ver + endif + return verStr +endfunction " }}} + +function! s:GetBranchName(fileName) " {{{ + if s:IsFileUnderDepot(a:fileName) + " TODO: Need to run where command at this phase. + elseif stridx(a:fileName, '//') == 0 + return matchstr(a:fileName, '^//[^/]\+/\zs[^/]\+\ze') + else + return '' + endif +endfunction " }}} + +function! s:GetDiff2Args() + let p4Arguments = s:p4Arguments + if len(p4Arguments) < 2 + if len(p4Arguments) == 0 + let file = s:EscapeFileName(s:GetCurFileName()) + else + let file = p4Arguments[0] + endif + let ver1 = s:PromptFor(0, s:_('UseGUIDialogs'), "Version1? ", '') + let ver2 = s:PromptFor(0, s:_('UseGUIDialogs'), "Version2? ", '') + let p4Arguments = [file.s:MakeRevStr(ver1), file.s:MakeRevStr(ver2)] + endif + return p4Arguments +endfunction + +function! perforce#ToggleCheckOutPrompt(interactive) + aug P4CheckOut + au! + if g:p4PromptToCheckout + let g:p4PromptToCheckout = 0 + else + let g:p4PromptToCheckout = 1 + au FileChangedRO * nested :call CheckOutFile() + endif + aug END + if a:interactive + echomsg "PromptToCheckout is now " . ((g:p4PromptToCheckout) ? "enabled." : + \ "disabled.") + endif +endfunction + +function! perforce#PFDiffOff(diffCounter) + " Cycle through all windows and turn off diff options for the specified diff + " run, or all, if none specified. + let curWinNr = winnr() + let eventignore = &eventignore + set eventignore=all + try + let i = 1 + while winbufnr(i) != -1 + try + exec i 'wincmd w' + if ! exists('w:p4VDiffWindow') + continue + endif + + if a:diffCounter == -1 || w:p4VDiffWindow == a:diffCounter + call genutils#CleanDiffOptions() + unlet w:p4VDiffWindow + endif + finally + let i = i + 1 + endtry + endwhile + finally + " Return to the original window. + exec curWinNr 'wincmd w' + let &eventignore = eventignore + endtry +endfunction +""" END: Helper functions }}} + + +""" BEGIN: Middleware functions {{{ + +" Filter contents through p4. +function! perforce#PW(fline, lline, scriptOrigin, ...) range + if a:scriptOrigin != 2 + call call('s:ParseOptions', [a:fline, a:lline, 0, '++f'] + a:000) + else + let s:commandMode = s:CM_FILTER + endif + setlocal modifiable + let retVal = perforce#PFIF(2, 5, s:p4Command) + return retVal +endfunction + +" Generate raw output into a new window. +function! perforce#PFRaw(...) + call call('s:ParseOptions', [1, line('$'), 0] + a:000) + + let retVal = s:PFImpl(1, 0) + return retVal +endfunction + +function! s:W(quitWhenDone, commandName, ...) + call call('s:ParseOptionsIF', [1, line('$'), 0, 5, a:commandName]+a:000) + if index(s:p4CmdOptions, '-i') == -1 + call insert(s:p4CmdOptions, '-i', 0) + endif + let retVal = perforce#PW(1, line('$'), 2) + if s:errCode == 0 + setl nomodified + if a:quitWhenDone + close + else + if s:p4Command ==# 'change' || s:p4Command ==# 'submit' + " Change number fixed, or files added/removed. + if s:FixChangeNo() || search('^Change \d\+ updated, ', 'w') + silent! undo + if search('^Files:\s*$', 'w') + let b:p4NewFilelist = getline(line('.')+1, line('$')) + if !exists('b:p4OrgFilelist') + let filelist = b:p4NewFilelist + else + " Find outersection. + let filelist = copy(b:p4NewFilelist) + call filter(filelist, 'index(b:p4OrgFilelist, v:val)==-1') + call extend(filelist, filter(copy(b:p4OrgFilelist), 'index(b:p4NewFilelist, v:val)==-1')) + endif + let files = map(filelist, 's:ConvertToLocalPath(v:val)') + call s:ResetFileStatusForFiles(files) + endif + silent! redo + endif + endif + endif + else + call s:FixChangeNo() + endif +endfunction + +function! s:FixChangeNo() + if search('^Change \d\+ created', 'w') || + \ search('^Change \d\+ renamed change \d\+ and submitted', 'w') + let newChangeNo = matchstr(getline('.'), '\d\+\ze\%(created\|and\)') + let _undolevels=&undolevels + try + let allLines = getline(1, line('$')) + silent! undo + " Make the below changes such a way that they can't be undo. This in a + " way, forces Vim to create an undo point, so that user can later + " undo and see these changes, with proper change number and status + " in place. This has the side effect of loosing the previous undo + " history, which can be considered desirable, as otherwise the user + " can undo this change and back to the new state. + set undolevels=-1 + if search("^Change:\t\%(new\|\d\+\)$") + silent! keepjumps call setline('.', "Change:\t" . newChangeNo) + " If no Date is present (happens for new changelists) + if !search("^Date:\t", 'w') + call append('.', ['', "Date:\t".strftime('%Y/%m/%d %H:%M:%S')]) + endif + endif + if search("^Status:\tnew$") + silent! keepjumps call setline('.', "Status:\tpending") + endif + setl nomodified + let &undolevels=_undolevels + silent! 0,$delete _ + call setline(1, allLines) + setl nomodified + call s:PFSetupForSpec() + finally + let &undolevels=_undolevels + endtry + let b:p4Command = 'change' + let b:p4CmdOptions = ['-i'] + return 1 + else + return 0 + endif +endfunction + +function! s:ParseOptionsIF(fline, lline, scriptOrigin, outputType, commandName, + \ ...) " range + " There are multiple possibilities here: + " - scriptOrigin, in which case the commandName contains the name of the + " command, but the varArgs also may contain it. + " - commandOrigin, in which case the commandName may actually be the + " name of the command, or it may be the first argument to p4 itself, in + " any case we will let p4 handle the error cases. + if index(s:allCommands, a:commandName) != -1 && a:scriptOrigin + call call('s:ParseOptions', [a:fline, a:lline, a:outputType] + a:000) + " Add a:commandName only if it doesn't already exist in the var args. + " Handles cases like "PF help submit" and "PF -c change changeno#", + " where the commandName need not be at the starting and there could be + " more than one valid commandNames (help and submit). + if s:p4Command != a:commandName + call call('s:ParseOptions', + \ [a:fline, a:lline, a:outputType, a:commandName] + a:000) + endif + else + call call('s:ParseOptions', + \ [a:fline, a:lline, a:outputType, a:commandName] + a:000) + endif +endfunction + +" PFIF {{{ +" The commandName may not be the perforce command when it is not of script +" origin (called directly from a command), but it should be always command +" name, when it is script origin. +" scriptOrigin: An integer indicating the origin of the call. +" 0 - Originated directly from the user, so should redirect to the specific +" command handler (if exists), after some basic processing. +" 1 - Originated from the script, continue with the full processing (makes +" difference in some command parsing). +" 2 - Same as 1 but, avoid processing arguments (they are already processing +" by the caller). +function! perforce#PFIF(scriptOrigin, outputType, commandName, ...) + return call('perforce#PFrangeIF', [1, line('$'), a:scriptOrigin, a:outputType, + \ a:commandName]+a:000) +endfunction + +function! perforce#PFrangeIF(fline, lline, scriptOrigin, outputType, + \ commandName, ...) + if a:scriptOrigin != 2 + call call('s:ParseOptionsIF', [a:fline, a:lline, + \ a:scriptOrigin, a:outputType, a:commandName]+a:000) + endif + if ! a:scriptOrigin + " Save a copy of the arguments so that the refresh can invoke this exactly + " as it was before. + let s:userArgs = [a:fline, a:lline, a:scriptOrigin, a:outputType, a:commandName] + \ +a:000 + endif + + + let outputOptIdx = index(s:p4Options, '++o') + if outputOptIdx != -1 + " Get the argument followed by ++o. + let s:outputType = get(s:p4Options, outputIdx + 1) + 0 + endif + " If this command prefers a dialog output, then just take care of + " it right here. + if index(s:dlgOutputCmds, s:p4Command) != -1 + let s:outputType = 2 + endif + if ! a:scriptOrigin + if exists('*s:{s:p4Command}Hdlr') + return s:{s:p4Command}Hdlr(1, s:outputType, a:commandName) + endif + endif + + let modifyWindowName = 0 + let dontProcess = (index(s:p4Options, '++n') != -1) + let noDefaultArg = (index(s:p4Options, '++N') != -1) + " If there is a confirm message for this command, then first prompt user. + let dontConfirm = (index(s:p4Options, '++y') != -1) + if exists('s:confirmMsgs{s:p4Command}') && ! dontConfirm + let option = s:ConfirmMessage(s:confirmMsgs{s:p4Command}, "&Yes\n&No", 2, + \ "Question") + if option == 2 + let s:errCode = 2 + return '' + endif + endif + + if index(s:limitListCmds, s:p4Command) != -1 && + \ index(s:p4CmdOptions, '-m') == -1 && s:_('DefaultListSize') > -1 + call extend(s:p4CmdOptions, ['-m', s:_('DefaultListSize')], 0) + let modifyWindowName = 1 + endif + if index(s:diffCmds, s:p4Command) != -1 && + \ s:indexMatching(s:p4CmdOptions, '^-d.*$') == -1 + \ && s:_('DefaultDiffOptions') !~# s:EMPTY_STR + " FIXME: Avoid split(). + call extend(s:p4CmdOptions, split(s:_('DefaultDiffOptions'), '\s'), 0) + let modifyWindowName = 1 + endif + + " Process p4Arguments, unless explicitly not requested to do so, or the '-x' + " option to read arguments from a file is given. + let unprotectedSpecPat = genutils#CrUnProtectedCharsPattern('&#@') + if ! dontProcess && ! noDefaultArg && len(s:p4Arguments) == 0 && + \ index(s:p4Options, '-x') == -1 + if (index(s:askUserCmds, s:p4Command) != -1 && + \ index(s:p4CmdOptions, '-i') == -1) || + \ index(s:p4Options, '++A') != -1 + if index(s:genericPromptCmds, s:p4Command) != -1 + let prompt = 'Enter arguments for ' . s:p4Command . ': ' + else + let prompt = "Enter the " . s:p4Command . " name: " + endif + let additionalArg = s:PromptFor(0, s:_('UseGUIDialogs'), prompt, '') + if additionalArg =~# s:EMPTY_STR + if index(s:genericPromptCmds, s:p4Command) != -1 + call s:EchoMessage('Arguments required for '. s:p4Command, 'Error') + else + call s:EchoMessage(substitute(s:p4Command, "^.", '\U&', '') . + \ " name required.", 'Error') + endif + let s:errCode = 2 + return '' + endif + let s:p4Arguments = [additionalArg] + elseif ! dontProcess && + \ index(s:curFileNotDefCmds, s:p4Command) == -1 && + \ index(s:nofileArgsCmds, s:p4Command) == -1 + let s:p4Arguments = [s:EscapeFileName(s:GetCurFileName())] + let modifyWindowName = 1 + endif + elseif ! dontProcess && s:indexMatching(s:p4Arguments, unprotectedSpecPat) != -1 + let branchModifierSpecified = 0 + let unprotectedAmp = genutils#CrUnProtectedCharsPattern('&') + if s:indexMatching(s:p4Arguments, unprotectedAmp) != -1 + let branchModifierSpecified = 1 + " CAUTION: We make sure the view mappings are generated before + " s:PFGetAltFiles() gets invoked, otherwise the call results in a + " recursive |sub-replace-special| and corrupts the mappings. + call s:CondUpdateViewMappings() + endif + + " This is like running substitute(v:val, 'pat', '\=expr', 'g') on each + " element. + " Pattern is (the start of line or series of non-space chars) followed by + " an unprotected [#@&] with a revision/codeline specifier. + " Expression is a concatenation of each of: + " (?:) + " + " (?:) + call map(s:p4Arguments, "substitute(v:val, '". + \ '\(^\|\%(\S\)\+\)\('.unprotectedSpecPat.'\)\%(\2\)\@!\([-+]\?\d\+\|\S\+\)'."', ". + \ "'\\=s:ExpandRevision(submatch(1), submatch(2), submatch(3))', 'g')") + if s:errCode != 0 + return '' + endif + + if branchModifierSpecified + call map(s:p4Arguments, + \ '(v:val =~ unprotectedAmp ? s:ApplyBranchSpecs(v:val, unprotectedAmp) : v:val)') + endif + " Unescape them, as user is required to escape them to avoid the above + " processing. + call map(s:p4Arguments, "genutils#UnEscape(v:val, '&@')") + + let modifyWindowName = 1 + endif + + let testMode = 0 + if index(s:p4Options, '++T') != -1 + let testMode = 1 " Dry run, opens the window. + elseif index(s:p4Options, '++D') != -1 + let testMode = 2 " Debug. Opens the window and displays the command. + endif + + let oldOptLen = len(s:p4Options) + " Remove all the built-in options. + call filter(s:p4Options, "v:val !~ '".'++\S\+\%(\s\+[^-+]\+\|\s\+\)\?'."'") + if len(s:p4Options) != oldOptLen + let modifyWindowName = 1 + endif + if index(s:diffCmds, s:p4Command) != -1 + " Remove the dummy option, if exists (see |perforce-default-diff-format|). + call filter(s:p4CmdOptions, 'v:val != "-d"') + let modifyWindowName = 1 + endif + + if s:p4Command ==# 'help' + " Use simple window name for all the help commands. + let s:p4WinName = s:helpWinName + elseif modifyWindowName + let s:p4WinName = s:MakeWindowName() + endif + + " If the command is a built-in command, then don't pass it to external p4. + if exists('s:builtinCmdHandler{s:p4Command}') + let s:errCode = 0 + return {s:builtinCmdHandler{s:p4Command}}() + endif + + let specMode = 0 + if index(s:specCmds, s:p4Command) != -1 + if index(s:p4CmdOptions, '-d') == -1 + \ && index(s:p4CmdOptions, '-o') == -1 + \ && index(s:noOutputCmds, s:p4Command) == -1 + call add(s:p4CmdOptions, '-o') + endif + + " Go into specification mode only if the user intends to edit the output. + if ((s:p4Command ==# 'submit' && index(s:p4CmdOptions, '-c') == -1) || + \ (index(s:specOnlyCmds, s:p4Command) == -1 && + \ index(s:p4CmdOptions, '-d') == -1)) && + \ s:outputType == 0 + let specMode = 1 + endif + endif + + let navigateCmd = 0 + if index(s:navigateCmds, s:p4Command) != -1 + let navigateCmd = 1 + endif + + let retryCount = 0 + let retVal = '' + " FIXME: When is "not clearing" (value 2) useful at all? + let clearBuffer = (testMode != 2 ? ! navigateCmd : 2) + " CAUTION: This is like a do..while loop, but not exactly the same, be + " careful using continue, the counter will not get incremented. + while 1 + let retVal = s:PFImpl(clearBuffer, testMode) + + " Everything else in this loop is for password support. + if s:errCode == 0 + break + else + if retVal =~# s:EMPTY_STR + let retVal = getline(1) + endif + " FIXME: Works only with English as the language. + if retVal =~# 'Perforce password (P4PASSWD) invalid or unset.' + let p4Password = inputsecret("Password required for user " . + \ s:_('p4User') . ": ", s:p4Password) + if p4Password ==# s:p4Password + break + endif + let s:p4Password = p4Password + else + break + endif + endif + let retryCount = retryCount + 1 + if retryCount > 2 + break + endif + endwhile + + if s:errCode == 0 && index(s:autoreadCmds, s:p4Command) != -1 + call s:ResetFileStatusForFiles(s:p4Arguments) + endif + + if s:errCode != 0 + return retVal + endif + + call s:SetupWindow(specMode) + + return retVal +endfunction + +function! s:SetupWindow(specMode) + if s:StartBufSetup() + " If this command has a handler for the individual items, then enable the + " item selection commands. + if s:getCommandItemHandler(0, s:p4Command, '') !~# s:EMPTY_STR + call s:SetupSelectItem() + endif + + if index(s:ftNotPerforceCmds, s:p4Command) == -1 + setlocal ft=perforce + endif + + if index(s:filelistCmds, s:p4Command) != -1 + call s:SetupFileBrowse() + endif + + if s:NewWindowCreated() + if a:specMode + " It is not possible to have an s:p4Command which is in s:allCommands + " and still not be the actual intended command. + command! -buffer -nargs=* W :call call('W', + \ [0]+b:p4Options+[b:p4Command]+b:p4CmdOptions+split(, + \ '\s')) + command! -buffer -nargs=* WQ :call call('W', + \ [1]+b:p4Options+[b:p4Command]+b:p4CmdOptions+split(, + \ '\s')) + call s:EchoMessage("When done, save " . s:p4Command . + \ " spec by using :W or :WQ command. Undo on errors.", 'None') + call s:PFSetupForSpec() + else " Define q to quit the read-only perforce windows (David Fishburn) + nnoremap q q + endif + endif + + if index(s:navigateCmds, s:p4Command) != -1 + nnoremap :call NavigateBack() + nnoremap :call NavigateBack() + nnoremap :call NavigateForward() + endif + + call s:EndBufSetup() + endif +endfunction + +function! s:ExpandRevision(fName, revType, revSpec) + return (a:fName==''?s:EscapeFileName(s:GetCurFileName()):a:fName). + \ a:revType. + \ (a:revSpec=~'^[-+]'?s:AdjustRevision(a:fName, a:revSpec):a:revSpec) +endfunction + +function! s:ApplyBranchSpecs(arg, unprotectedAmp) + " The first arg will be filename, followed by multiple specifiers. + let toks = split(a:arg, a:unprotectedAmp) + " FIXME: Handle "&" in the filename. + " FIXME: Handle other revision specifiers occuring in any order + " (e.g., &branch#3). + " Ex: c:/dev/docs/Triage/Tools/Machine\ Configurations\ &\ Codeline\ Requirements.xls + if len(toks) == 0 || toks[0] == '' + return a:arg + endif + let fname = remove(toks, 0) + " Reduce filename according to the branch tokens. + for altBranch in toks + let fname = s:PFGetAltFiles("&", altBranch, fname)[0] + endfor + return fname +endfunction + +function! perforce#PFComplete(ArgLead, CmdLine, CursorPos) + if s:p4KnownCmdsCompStr == '' + let s:p4KnownCmdsCompStr = join(s:p4KnownCmds, "\n") + endif + if a:CmdLine =~ '^\s*P[FW] ' + let argStr = strpart(a:CmdLine, matchend(a:CmdLine, '^\s*PF ')) + let s:p4Command = '' + if argStr !~# s:EMPTY_STR + if exists('g:p4EnableUserFileExpand') + let _p4EnableUserFileExpand = g:p4EnableUserFileExpand + endif + try + " WORKAROUND for :redir broken, when called from here. + let g:p4EnableUserFileExpand = 0 + exec 'call s:ParseOptionsIF(-1, -1, 0, 0, ' . + \ genutils#CreateArgString(argStr, s:SPACE_AS_SEP).')' + finally + if exists('_p4EnableUserFileExpand') + let g:p4EnableUserFileExpand = _p4EnableUserFileExpand + else + unlet g:p4EnableUserFileExpand + endif + endtry + endif + if s:p4Command ==# '' || s:p4Command ==# a:ArgLead + return s:p4KnownCmdsCompStr."\n".join(s:builtinCmds, "\n") + endif + else + let userCmd = substitute(a:CmdLine, '^\s*P\(.\)\(\w*\).*', '\l\1\2', '') + if strlen(userCmd) < 3 + if !has_key(s:shortCmdMap, userCmd) + throw "Perforce internal error: no map found for short command: ". + \ userCmd + endif + let userCmd = s:shortCmdMap[userCmd] + endif + let s:p4Command = userCmd + endif + if s:p4Command ==# 'help' + return s:PHelpComplete(a:ArgLead, a:CmdLine, a:CursorPos) + endif + if index(s:nofileArgsCmds, s:p4Command) != -1 + return '' + endif + + " FIXME: Can't set command-line from user function. + "let argLead = genutils#UserFileExpand(a:ArgLead) + "if argLead !=# a:ArgLead + " let cmdLine = strpart(a:CmdLine, 0, a:CursorPos-strlen(a:ArgLead)) . + " \ argLead . strpart(a:CmdLine, a:CursorPos) + " exec "normal! \e'".cmdLine."'\" + " call setcmdpos(a:CursorPos+(strlen(argLead) - strlen(a:ArgLead))) + " return '' + "endif + if a:ArgLead =~ '^//'.s:_('Depot').'/' + " Get directory matches. + let dirMatches = s:GetOutput('dirs', a:ArgLead, "\n", '/&') + " Get file matches. + let fileMatches = s:GetOutput('files', a:ArgLead, '#\d\+[^'."\n".']\+', '') + if dirMatches !~ s:EMPTY_STR || fileMatches !~ s:EMPTY_STR + return dirMatches.fileMatches + else + return '' + endif + endif + return genutils#UserFileComplete(a:ArgLead, a:CmdLine, a:CursorPos, 1, '') +endfunction + +function! s:GetOutput(p4Cmd, arg, pat, repl) + let matches = perforce#PFIF(0, 4, a:p4Cmd, a:arg.'*') + if s:errCode == 0 + if matches =~ 'no such file(s)' + let matches = '' + else + let matches = substitute(substitute(matches, a:pat, a:repl, 'g'), + \ "\n\n", "\n", 'g') + endif + endif + return matches +endfunction +" PFIF }}} + +""" START: Adopted from Tom's perforce plugin. {{{ + +"--------------------------------------------------------------------------- +" Produce string for ruler output +function! perforce#RulerStatus() + if exists('b:p4RulerStr') && b:p4RulerStr !~# s:EMPTY_STR + return b:p4RulerStr + endif + if !exists('b:p4FStatDone') || !b:p4FStatDone + return '' + endif + + "let b:p4RulerStr = '[p4 ' + let b:p4RulerStr = '[' + if exists('b:p4RulerErr') && b:p4RulerErr !~# s:EMPTY_STR + let b:p4RulerStr = b:p4RulerStr . b:p4RulerErr + elseif !exists('b:p4HaveRev') + let b:p4RulerStr = '' + elseif b:p4Action =~# s:EMPTY_STR + if b:p4OtherOpen =~# s:EMPTY_STR + let b:p4RulerStr = b:p4RulerStr . 'unopened' + else + let b:p4RulerStr = b:p4RulerStr . b:p4OtherOpen . ':' . b:p4OtherAction + endif + else + if b:p4Change ==# 'default' || b:p4Change =~# s:EMPTY_STR + let b:p4RulerStr = b:p4RulerStr . b:p4Action + else + let b:p4RulerStr = b:p4RulerStr . b:p4Action . ':' . b:p4Change + endif + endif + if exists('b:p4HaveRev') && b:p4HaveRev !~# s:EMPTY_STR + let b:p4RulerStr = b:p4RulerStr . ' #' . b:p4HaveRev . '/' . b:p4HeadRev + endif + + if b:p4RulerStr !~# s:EMPTY_STR + let b:p4RulerStr = b:p4RulerStr . ']' + endif + return b:p4RulerStr +endfunction + +function! s:GetClientInfo() + let infoStr = '' + call s:PushP4Context() + try + let infoStr = perforce#PFIF(0, 4, 'info') + if s:errCode != 0 + return s:ConfirmMessage((v:errmsg != '') ? v:errmsg : infoStr, 'OK', 1, + \ 'Error') + endif + finally + call s:PopP4Context(0) + endtry + let g:p4ClientRoot = genutils#CleanupFileName(s:StrExtract(infoStr, + \ '\CClient root: [^'."\n".']\+', 13)) + let s:p4Client = s:StrExtract(infoStr, '\CClient name: [^'."\n".']\+', 13) + let s:p4User = s:StrExtract(infoStr, '\CUser name: [^'."\n".']\+', 11) +endfunction + +" Get/refresh filestatus for the specified buffer with optimizations. +function! perforce#GetFileStatus(buf, refresh) + if ! type(a:buf) " If number. + let bufNr = (a:buf == 0) ? bufnr('%') : a:buf + else + let bufNr = bufnr(a:buf) + endif + + " If it is not a normal buffer, then ignore it. + if getbufvar(bufNr, '&buftype') != '' || bufname(bufNr) == '' + return "" + endif + if bufNr == -1 || (!a:refresh && s:_('OptimizeActiveStatus') && + \ getbufvar(bufNr, "p4FStatDone")) + return "" + endif + + " This is an optimization by restricting status to the files under the + " client root only. + if !s:IsFileUnderDepot(expand('#'.bufNr.':p')) + return "" + endif + + return s:GetFileStatusImpl(bufNr) +endfunction + +function! s:ResetFileStatusForFiles(files) + for file in a:files + let bufNr = genutils#FindBufferForName(file) + if bufNr != -1 + " FIXME: Check for other tabs also. + if bufwinnr(bufNr) != -1 " If currently visible. + call perforce#GetFileStatus(bufNr, 1) + else + call s:ResetFileStatusForBuffer(bufNr) + endif + endif + endfor +endfunction + +function! s:ResetFileStatusForBuffer(bufNr) + " Avoid proliferating this buffer variable. + if getbufvar(a:bufNr, 'p4FStatDone') != 0 + call setbufvar(a:bufNr, 'p4FStatDone', 0) + endif +endfunction + +"--------------------------------------------------------------------------- +" Obtain file status information +function! s:GetFileStatusImpl(bufNr) + if bufname(a:bufNr) == "" + return '' + endif + let fileName = fnamemodify(bufname(a:bufNr), ':p') + let bufNr = a:bufNr + " If the filename matches with one of the ignore patterns, then don't do + " status. + if s:_('ASIgnoreDefPattern') !~# s:EMPTY_STR && + \ match(fileName, s:_('ASIgnoreDefPattern')) != -1 + return '' + endif + if s:_('ASIgnoreUsrPattern') !~# s:EMPTY_STR && + \ match(fileName, s:_('ASIgnoreUsrPattern')) != -1 + return '' + endif + + call setbufvar(bufNr, 'p4RulerStr', '') " Let this be reconstructed. + + " This could very well be a recursive call, so we should save the current + " state. + call s:PushP4Context() + try + let fileStatusStr = perforce#PFIF(1, 4, 'fstat', fileName) + call setbufvar(bufNr, 'p4FStatDone', '1') + + if s:errCode != 0 + call setbufvar(bufNr, 'p4RulerErr', "") + return '' + endif + finally + call s:PopP4Context(0) + endtry + + if match(fileStatusStr, ' - file(s) not in client view\.') >= 0 + call setbufvar(bufNr, 'p4RulerErr', "") + " Required for optimizing out in future runs. + call setbufvar(bufNr, 'p4HeadRev', '') + return '' + elseif match(fileStatusStr, ' - no such file(s).') >= 0 + call setbufvar(bufNr, 'p4RulerErr', "") + " Required for optimizing out in future runs. + call setbufvar(bufNr, 'p4HeadRev', '') + return '' + else + call setbufvar(bufNr, 'p4RulerErr', '') + endif + + call setbufvar(bufNr, 'p4HeadRev', + \ s:StrExtract(fileStatusStr, '\CheadRev [0-9]\+', 8)) + "call setbufvar(bufNr, 'p4DepotFile', + " \ s:StrExtract(fileStatusStr, '\CdepotFile [^'."\n".']\+', 10)) + "call setbufvar(bufNr, 'p4ClientFile', + " \ s:StrExtract(fileStatusStr, '\CclientFile [^'."\n".']\+', 11)) + call setbufvar(bufNr, 'p4HaveRev', + \ s:StrExtract(fileStatusStr, '\ChaveRev [0-9]\+', 8)) + let headAction = s:StrExtract(fileStatusStr, '\CheadAction [^[:space:]]\+', + \ 11) + if headAction ==# 'delete' + call setbufvar(bufNr, 'p4Action', '') + call setbufvar(bufNr, 'p4Change', '') + else + call setbufvar(bufNr, 'p4Action', + \ s:StrExtract(fileStatusStr, '\Caction [^[:space:]]\+', 7)) + call setbufvar(bufNr, 'p4OtherOpen', + \ s:StrExtract(fileStatusStr, '\CotherOpen0 [^[:space:]@]\+', 11)) + call setbufvar(bufNr, 'p4OtherAction', + \ s:StrExtract(fileStatusStr, '\CotherAction0 [^[:space:]@]\+', 13)) + call setbufvar(bufNr, 'p4Change', + \ s:StrExtract(fileStatusStr, '\Cchange [^[:space:]]\+', 7)) + endif + + return fileStatusStr +endfunction + +function! s:StrExtract(str, pat, pos) + let part = matchstr(a:str, a:pat) + let part = strpart(part, a:pos) + return part +endfunction + +function! s:AdjustRevision(file, adjustment) + let s:errCode = 0 + let revNum = a:adjustment + if revNum =~# '[-+]\d\+' + let revNum = substitute(revNum, '^+', '', '') + if getbufvar(a:file, 'p4HeadRev') =~# s:EMPTY_STR + " If fstat is not done yet, do it now. + call perforce#GetFileStatus(a:file, 1) + if getbufvar(a:file, 'p4HeadRev') =~# s:EMPTY_STR + call s:EchoMessage("Current revision is not available. " . + \ "To be able to use negative revisions, see help on " . + \ "'perforce-active-status'.", 'Error') + let s:errCode = 1 + return -1 + endif + endif + let revNum = getbufvar(a:file, 'p4HaveRev') + revNum + if revNum < 1 + call s:EchoMessage("Not that many revisions available. Try again " . + \ "after running PFRefreshFileStatus command.", 'Error') + let s:errCode = 1 + return -1 + endif + endif + return revNum +endfunction + +"--------------------------------------------------------------------------- +" One of a set of functions that returns fields from the p4 fstat command +function! s:IsCurrent() + let revdiff = b:p4HeadRev - b:p4HaveRev + if revdiff == 0 + return 0 + else + return -1 + endif +endfunction + +function! s:CheckOutFile() + if ! g:p4PromptToCheckout || ! s:IsFileUnderDepot(expand('%:p')) + return + endif + " If we know that the file is deleted from the depot, don't prompt. + if exists('b:p4Action') && b:p4Action == '' + return + endif + + if filereadable(expand("%")) && ! filewritable(expand("%")) + let option = s:ConfirmMessage("Readonly file, do you want to checkout " . + \ "from perforce?", "&Yes\n&No\n&Cancel", s:_('CheckOutDefault'), + \ "Question") + if option == 1 + call perforce#PFIF(1, 2, 'edit') + if ! s:errCode + edit + call perforce#GetFileStatus(expand('') + 0, 1) + endif + elseif option == 3 + call s:CancelEdit(0) + endif + endif +endfunction + +function! s:CancelEdit(stage) + aug P4CancelEdit + au! + if a:stage == 0 + au CursorMovedI nested :call CancelEdit(1) + au CursorMoved nested :call CancelEdit(1) + elseif a:stage == 1 + stopinsert + silent undo + setl readonly + endif + aug END +endfunction + +function! perforce#FileChangedShell() + let bufNr = expand("") + 0 + if s:_('EnableActiveStatus') + call s:ResetFileStatusForBuffer(bufNr) + endif + let autoread = -1 + if index(s:autoreadCmds, s:currentCommand) != -1 + let autoread = s:_('Autoread') + if autoread + call setbufvar(bufNr, '&readonly', 0) + endif + endif + return autoread +endfunction +""" END: Adapted from Tom's perforce plugin. }}} + +""" END: Middleware functions }}} + + +""" BEGIN: Infrastructure {{{ + +" Assumes that the arguments are already parsed and are ready to be used in +" the script variables. +" Low level interface with the p4 command. +" clearBuffer: If the buffer contents should be cleared before +" adding the new output (See s:GotoWindow). +" testMode (number): +" 0 - Run normally. +" 1 - testing, ignore. +" 2 - debugging, display the command-line instead of the actual output.. +" Returns the output if available. If there is any error, the error code will +" be available in s:errCode variable. +function! s:PFImpl(clearBuffer, testMode) " {{{ + try " [-2f] + + let s:errCode = 0 + let p4Options = s:GetP4Options() + let fullCmd = s:CreateFullCmd(s:MakeP4ArgList(p4Options, 0)) + " Save the name of the current file. + let p4OrgFileName = s:GetCurFileName() + + let s:currentCommand = '' + " Make sure all the already existing changes are detected. We don't have + " s:currentCommand set here, so the user will get an appropriate prompt. + try + checktime + catch + " FIXME: Ignore error for now. + endtry + + " If the output has to be shown in a window, position cursor appropriately, + " creating a new window if required. + let v:errmsg = "" + " Ignore outputType in this case. + if s:commandMode != s:CM_PIPE && s:commandMode != s:CM_FILTER + if s:outputType == 0 || s:outputType == 1 + " Only when "clear with undo" is selected, we optimize out the call. + if s:GotoWindow(s:outputType, + \ (!s:refreshWindowsAlways && (a:clearBuffer == 1)) ? + \ 2 : a:clearBuffer, p4OrgFileName, 0) != 0 + return s:errCode + endif + endif + endif + + let output = '' + if s:errCode == 0 + if ! a:testMode + let s:currentCommand = s:p4Command + if s:_('EnableFileChangedShell') + call genutils#DefFCShellInstall() + endif + + try + if s:commandMode ==# s:CM_RUN + " Only when "clear with undo" is selected, we optimize out the call. + if s:refreshWindowsAlways || + \ ((!s:refreshWindowsAlways && (a:clearBuffer == 1)) && + \ (line('$') == 1 && getline(1) =~ '^\s*$')) + " If we are placing the output in a new window, then we should + " avoid system() for performance reasons, imagine doing a + " 'print' on a huge file. + " These two outputType's correspond to placing the output in a + " window. + if s:outputType != 0 && s:outputType != 1 + let output = s:System(fullCmd) + else + exec '.call s:Filter(fullCmd, 1)' + let output = '' + endif + endif + elseif s:commandMode ==# s:CM_FILTER + exec s:filterRange . 'call s:Filter(fullCmd, 1)' + elseif s:commandMode ==# s:CM_PIPE + exec s:filterRange . 'call s:Filter(fullCmd, 2)' + endif + " Detect any new changes to the loaded buffers. + " CAUTION: This actually results in a reentrant call back to this + " function, but our Push/Pop mechanism for the context should take + " care of it. + try + checktime + catch + " FIXME: Ignore error for now. + endtry + finally + if s:_('EnableFileChangedShell') + call genutils#DefFCShellUninstall() + endif + let s:currentCommand = '' + endtry + elseif a:testMode != 1 + let output = fullCmd + endif + + let v:errmsg = "" + " If we have non-null output, then handling it is still pending. + if output !~# s:EMPTY_STR + let echoGrp = 'NONE' " The default + let maxLinesInDlg = s:_('MaxLinesInDialog') + if s:outputType == 2 && maxLinesInDlg != -1 + " Count NLs. + let nNLs = 0 + let nlIdx = 0 + while 1 + let nlIdx = stridx(output, "\n", nlIdx+1) + if nlIdx != -1 + let nNLs += 1 + if nNLs > maxLinesInDlg + break + endif + else + break + endif + endwhile + if nNLs > maxLinesInDlg + " NOTE: Keep in sync with that at the start of the function. + if s:GotoWindow(s:outputType, + \ (!s:refreshWindowsAlways && (a:clearBuffer == 1)) ? + \ 2 : a:clearBuffer, p4OrgFileName, 0) == 0 + let s:outputType = 0 + else + let s:outputType = 3 + let echoGrp = 'WarningMsg' + endif + endif + endif + " If the output has to be shown in a dialog, bring up a dialog with the + " output, otherwise show it in the current window. + if s:outputType == 0 || s:outputType == 1 + silent! put! =output + 1 + elseif s:outputType == 2 + call s:ConfirmMessage(output, "OK", 1, "Info") + elseif s:outputType == 3 + call s:EchoMessage(output, echoGrp) + elseif s:outputType == 4 + " Do nothing we will just return it. + endif + endif + endif + return output + + finally " [+2s] + call s:InitWindow(p4Options) + endtry +endfunction " }}} + +function! s:NewWindowCreated() + if (s:outputType == 0 || s:outputType == 1) && s:errCode == 0 && + \ (s:commandMode ==# s:CM_RUN) + return 1 + else + return 0 + endif +endfunction + +function! s:setBufSetting(opt, set) + let optArg = matchstr(b:p4Options, '\%(\S\)\@$", '%s///e') + setlocal nomodifiable + setlocal nomodified + + if s:outputType == 1 + wincmd p + endif + endif +endfunction + +" External command execution {{{ + +function! s:System(fullCmd) + return s:ExecCmd(a:fullCmd, 0) +endfunction + +function! s:Filter(fullCmd, mode) range + " For command-line, we need to protect '%', '#' and '!' chars, even if they + " are in quotes, to avoid getting expanded by Vim before invoking external + " cmd. + let fullCmd = genutils#Escape(a:fullCmd, '%#!') + exec a:firstline.",".a:lastline. + \ "call s:ExecCmd(fullCmd, a:mode)" +endfunction + +function! s:ExecCmd(fullCmd, mode) range + let v:errmsg = '' + let output = '' + try + " Assume the shellredir is set correctly to capture the error messages. + if a:mode == 0 + let output = system(a:fullCmd) + elseif a:mode == 1 + silent! exec a:firstline.",".a:lastline."!".a:fullCmd + else + silent! exec a:firstline.",".a:lastline."write !".a:fullCmd + endif + + call s:CheckShellError(output, s:outputType) + return output + catch /^Vim\%((\a\+)\)\=:E/ " 48[2-5] + let v:errmsg = substitute(v:exception, '^[^:]\+:', '', '') + call s:CheckShellError(output, s:outputType) + catch /^Vim:Interrupt$/ + let s:errCode = 1 + let v:errmsg = 'Interrupted' + catch " Ignore. + endtry +endfunction + +function! s:EvalExpr(expr, def) + let result = a:def + if a:expr !~# s:EMPTY_STR + exec "let result = " . a:expr + endif + return result +endfunction + +function! s:GetP4Options() + let addOptions = [] + + " If there are duplicates, perfore takes the first option, so let + " s:p4Options or b:p4Options come before g:p4DefaultOptions. + " LIMITATATION: We choose either s:p4Options or b:p4Options only. But this + " shouldn't be a big issue as this feature is meant for executing more + " commands on the p4 result windows only. + if len(s:p4Options) != 0 + call extend(addOptions, s:p4Options) + elseif exists('b:p4Options') && len(b:p4Options) != 0 + call extend(addOptions, b:p4Options) + endif + + " FIXME: avoid split here. + call extend(addOptions, split(s:_('DefaultOptions'), ' ')) + + let p4Client = s:p4Client + let p4User = s:p4User + let p4Port = s:p4Port + try + if s:p4Port !=# 'P4CONFIG' + if s:_('CurPresetExpr') !~# s:EMPTY_STR + let preset = s:EvalExpr(s:_('CurPresetExpr'), '') + if preset ~= s:EMPTY_STR + call perforce#PFSwitch(0, preset) + endif + endif + + if s:_('p4Client') !~# s:EMPTY_STR && index(addOptions, '-c') == -1 + call add(add(addOptions, '-c'), s:_('p4Client')) + endif + if s:_('p4User') !~# s:EMPTY_STR && index(addOptions, '-u') == -1 + call add(add(addOptions, '-u'), s:_('p4User')) + endif + if s:_('p4Port') !~# s:EMPTY_STR && index(addOptions, '-p') == -1 + call add(add(addOptions, '-p'), s:_('p4Port')) + endif + " Don't pass password with '-P' option, it will be too open (ps will show + " it up). + let $P4PASSWD = s:p4Password + else + endif + finally + let s:p4Client = p4Client + let s:p4User = p4User + let s:p4Port = p4Port + endtry + + return addOptions +endfunction + +function! s:CreateFullCmd(argList) + let fullCmd = genutils#EscapeCommand(s:p4CommandPrefix.s:_('CmdPath'), a:argList, + \ s:p4Pipe) + let g:p4FullCmd = fullCmd + return fullCmd +endfunction + +" Generates a command string as the user typed, using the script variables. +function! s:MakeP4ArgList(p4Options, useBufLocal) + if a:useBufLocal && exists('b:p4Command') + let p4Command = b:p4Command + else + let p4Command = s:p4Command + endif + if a:useBufLocal && exists('b:p4CmdOptions') + let p4CmdOptions = b:p4CmdOptions + else + let p4CmdOptions = s:p4CmdOptions + endif + if a:useBufLocal && exists('b:p4Arguments') + let p4Arguments = b:p4Arguments + else + let p4Arguments = s:p4Arguments + endif + let cmdList = a:p4Options+[p4Command]+p4CmdOptions+p4Arguments + " Remove the protection from the characters that we treat specially (Note: # + " and % are treated specially by Vim command-line itself, and the + " back-slashes are removed even before we see them.) + call map(cmdList, "genutils#UnEscape(v:val, '&')") + return cmdList +endfunction + +" In case of outputType == 4, it assumes the caller wants to see the output as +" it is, so no error message is given. The caller is expected to check for +" error code, though. +function! s:CheckShellError(output, outputType) + if (v:shell_error != 0 || v:errmsg != '') && a:outputType != 4 + let output = "There was an error executing external p4 command.\n" + if v:errmsg != '' + let output = output . "\n" . "errmsg = " . v:errmsg + endif + " When commandMode ==# s:CM_RUN, the error message may already be there in + " the current window. + if a:output !~# s:EMPTY_STR + let output = output . "\n" . a:output + elseif a:output =~# s:EMPTY_STR && + \ (s:commandMode ==# s:CM_RUN && line('$') == 1 && col('$') == 1) + let output = output . "\n\n" . + \ "Check if your 'shellredir' option captures error messages." + endif + call s:ConfirmMessage(output, "OK", 1, "Error") + endif + let s:errCode = v:shell_error + return v:shell_error +endfunction + +" External command execution }}} + +" Push/Pop/Peek context {{{ +function! s:PushP4Context() + call add(s:p4Contexts, s:GetP4ContextVars()) +endfunction + +function! s:PeekP4Context() + return s:PopP4ContextImpl(1, 1) +endfunction + +function! s:PopP4Context(...) + " By default carry forward error. + return s:PopP4ContextImpl(0, (a:0 ? a:1 : 1)) +endfunction + +function! s:NumP4Contexts() + return len(s:p4Contexts) +endfunction + +function! s:PopP4ContextImpl(peek, carryFwdErr) + let nContexts = len(s:p4Contexts) + if nContexts <= 0 + echoerr "PopP4Context: Contexts stack is empty" + return + endif + let context = s:p4Contexts[-1] + if !a:peek + call remove(s:p4Contexts, nContexts-1) + endif + + call s:SetP4ContextVars(context, a:carryFwdErr) + return context +endfunction + +" Serialize p4 context variables. +function! s:GetP4ContextVars() + return [s:p4Options , s:p4Command , s:p4CmdOptions , s:p4Arguments , + \ s:p4Pipe , s:p4WinName , s:commandMode , s:filterRange , + \ s:outputType , s:errCode, s:userArgs] +endfunction + +" De-serialize p4 context variables. +function! s:SetP4ContextVars(context, ...) + let carryFwdErr = 0 + if a:0 && a:1 + let carryFwdErr = s:errCode + endif + + let [s:p4Options, s:p4Command, s:p4CmdOptions, s:p4Arguments, s:p4Pipe, + \ s:p4WinName, s:commandMode, s:filterRange, s:outputType, s:errCode, + \ s:userArgs] = a:context + let s:errCode = s:errCode + carryFwdErr +endfunction +" Push/Pop/Peek context }}} + +""" BEGIN: Argument parsing {{{ +function! s:ResetP4ContextVars() + " Syntax is: + " PF | + " Ex: PF -c hari integrate -b branch -s + let s:p4Options = [] + let s:p4Command = "" + let s:p4CmdOptions = [] + let s:p4Arguments = [] + let s:p4Pipe = [] + let s:p4WinName = "" + " commandMode: + " run - Execute p4 using system() or its equivalent. + " filter - Execute p4 as a filter for the current window contents. Use + " commandPrefix to restrict the filter range. + " display - Don't execute p4. The output is already passed in. + let s:commandMode = "run" + let s:filterRange = "" + let s:outputType = 0 + let s:errCode = 0 + + " Special variable to keep track of full user arguments. + let s:userArgs = [] +endfunction +call s:ResetP4ContextVars() " Let them get initialized the first time. + +" Parses the arguments into 4 parts, "options to p4", "p4 command", +" "options to p4 command", "actual arguments". Also generates the window name. +" outputType (string): +" 0 - Execute p4 and place the output in a new window. +" 1 - Same as above, but use preview window. +" 2 - Execute p4 and show the output in a dialog for confirmation. +" 3 - Execute p4 and echo the output. +" 4 - Execute p4 and return the output. +" 5 - Execute p4 no output expected. Essentially same as 4 when the current +" commandMode doesn't produce any output, just for clarification. +function! s:ParseOptions(fline, lline, outputType, ...) " range + call s:ResetP4ContextVars() + let s:outputType = a:outputType + if a:0 == 0 + return + endif + + let s:filterRange = a:fline . ',' . a:lline + let i = 1 + let prevArg = "" + let curArg = "" + let s:pendingPipeArg = '' + while i <= a:0 + try " Just for the sake of loop variables. [-2f] + + if s:pendingPipeArg !~# s:EMPTY_STR + let curArg = s:pendingPipeArg + let s:pendingPipeArg = '' + elseif len(s:p4Pipe) == 0 + let curArg = a:000[i-1] + " The user can't specify a null string on the command-line, this is an + " argument originating from the script, so just ignore it (just for + " the sake of convenience, see PChangesDiff for a possibility). + if curArg == '' + continue + endif + let pipeIndex = match(curArg, '\\\@' + let curItem = s:GetCurrentItem() + if curItem !~# s:EMPTY_STR + let curArg = curItem + endif + endif + + " As we use custom completion mode, the filename meta-sequences in the + " arguments will not be expanded by Vim automatically, so we need to + " expand them manually here. On the other hand, this provides us control + " on what to expand, so we can avoid expanding perforce file revision + " numbers as buffernames (escaping is no longer required by the user on + " the commandline). + let fileRev = '' + let fileRevIndex = match(curArg, '#\(-\?\d\+\|none\|head\|have\)$') + if fileRevIndex != -1 + let fileRev = strpart(curArg, fileRevIndex) + let curArg = strpart(curArg, 0, fileRevIndex) + endif + if curArg != '' && (!exists('g:p4EnableUserFileExpand') || + \ g:p4EnableUserFileExpand) + let curArg = genutils#UserFileExpand(curArg) + endif + if fileRev != '' + let curArg = curArg.fileRev + endif + + if curArg =~# '^|' || len(s:p4Pipe) != 0 + call add(s:p4Pipe, curArg) + continue + endif + + if ! s:IsAnOption(curArg) " If not an option. + if s:p4Command =~# s:EMPTY_STR && + \ index(s:allCommands, curArg) != -1 + " If the previous one was an option to p4 that takes in an argument. + if prevArg =~# '^-[cCdHLpPux]$' || prevArg =~# '^++o$' " See :PH usage. + call add(s:p4Options, curArg) + if prevArg ==# '++o' && (curArg == '0' || curArg == 1) + let s:outputType = curArg + endif + else + let s:p4Command = curArg + endif + else " Argument is not a perforce command. + if s:p4Command =~# s:EMPTY_STR + call add(s:p4Options, curArg) + else + let optArg = 0 + " Look for options that have an argument, so we can collect this + " into p4CmdOptions instead of p4Arguments. + if len(s:p4Arguments) == 0 && s:IsAnOption(prevArg) + " We could as well just check for the option here, but combining + " this with the command name will increase the accuracy of finding + " the starting point for p4Arguments. + if (prevArg[0] ==# '-' && has_key(s:p4OptCmdMap, prevArg[1]) && + \ index(s:p4OptCmdMap[prevArg[1]], s:p4Command) != -1) || + \ (prevArg =~# '^++' && has_key(s:biOptCmdMap, prevArg[2]) && + \ index(s:biOptCmdMap[prevArg[2]], s:p4Command) != -1) + let optArg = 1 + endif + endif + + if optArg + call add(s:p4CmdOptions, curArg) + else + call add(s:p4Arguments, curArg) + endif + endif + endif + else + if len(s:p4Arguments) == 0 + if s:p4Command =~# s:EMPTY_STR + if curArg =~# '^++[pfdr]$' + if curArg ==# '++p' + let s:commandMode = s:CM_PIPE + elseif curArg ==# '++f' + let s:commandMode = s:CM_FILTER + elseif curArg ==# '++r' + let s:commandMode = s:CM_RUN + endif + continue + endif + call add(s:p4Options, curArg) + else + call add(s:p4CmdOptions, curArg) + endif + else + call add(s:p4Arguments, curArg) + endif + endif + " The "-x -" option requires it to act like a filter. + if s:p4Command =~# s:EMPTY_STR && prevArg ==# '-x' && curArg ==# '-' + let s:commandMode = s:CM_FILTER + endif + + finally " [+2s] + if s:pendingPipeArg =~# s:EMPTY_STR + let i = i + 1 + endif + let prevArg = curArg + endtry + endwhile + + if index(s:p4Options, '-d') == -1 + let curDir = s:EvalExpr(s:_('CurDirExpr'), '') + if curDir !=# '' + call add(add(s:p4Options, '-d'), s:EscapeFileName(curDir)) + endif + endif + let s:p4WinName = s:MakeWindowName() +endfunction + +function! s:IsAnOption(arg) + if a:arg =~# '^-.$' || a:arg =~# '^-d\%([cnsubw]\|\d\+\)*$' || + \ a:arg =~# '^-a[fmsty]$' || a:arg =~# '^-s[ader]$' || + \ a:arg =~# '^-qu$' || a:arg =~# '^+' + return 1 + else + return 0 + endif +endfunction + +function! s:CleanSpaces(str) + " Though not complete, it is enough to just say, + " "spaces that are not preceded by \'s". + return substitute(substitute(a:str, '^ \+\|\%(\\\@ or // prefix from fileName. +function! s:StripRemotePath(fileName) + "return substitute(a:fileName, '//\%('.s:_('Depot').'\|'.s:_('p4Client').'\)', '', '') + return substitute(a:fileName, '//\%('.s:_('Depot').'\)', '', '') +endfunction + +" Client view translation {{{ +" Convert perforce file wildcards ("*", "..." and "%[1-9]") to a Vim string +" regex (see |pattern.txt|). Returns patterns that work when "very nomagic" +" is set. +let s:p4Wild = {} +function! s:TranlsateP4Wild(p4Wild, rhsView) + let strRegex = '' + if a:rhsView + if a:p4Wild[0] ==# '%' + let pos = s:p4WildMap[a:p4Wild[1]] + else + let pos = s:p4WildCount + endif + let strRegex = '\'.pos + else + if a:p4Wild ==# '*' + let strRegex = '\(\[^/]\*\)' + elseif a:p4Wild ==# '...' + let strRegex = '\(\.\*\)' + elseif a:p4Wild[0] ==# '%' + let strRegex = '\(\[^/]\*\)' + let s:p4WildMap[a:p4Wild[1]] = s:p4WildCount + endif + endif + let s:p4WildCount = s:p4WildCount + 1 + return strRegex +endfunction + +" Convert perforce file regex (containing "*", "..." and "%[1-9]") to a Vim +" string regex. No error checks for now, for simplicity. +function! s:TranslateP4FileRegex(p4Regex, rhsView) + let s:p4WildCount = 1 + " Note: We don't expect backslashes in the views, so no special handling. + return substitute(a:p4Regex, + \ '\(\*\|\%(\.\)\@ nWindows + if winbufnr(winnr()) == curBufnr " Error creating buffer itself. + close + elseif bufname('%') == s:p4WinName + " This should even close the window. + silent! exec "bwipeout " . bufnr('%') + endif + endif + endif + return 0 +endfunction + +function! s:BufConflictError(cmdCompleted) + return s:ShowVimError('This perforce command resulted in matching an '. + \ 'existing buffer. To prevent any demage this could cause '. + \ 'the command will be aborted at this point.'. + \ (a:cmdCompleted ? ("\nHowever the command completed ". + \ (s:errCode ? 'un' : ''). 'successfully.') : ''), '') +endfunction + +function! s:EditP4WinName(preview, nWindows) + let fatal = 0 + let bug = 0 + let exception = '' + let pWindowWasOpen = (genutils#GetPreviewWinnr() != -1) + " Some patterns can cause problems. + let _wildignore = &wildignore + try + set wildignore= + exec (a:preview?'p':'').'edit' s:p4WinName + catch /^Vim\%((\a\+)\)\=:E303/ + " This is a non-fatal error. + let bug = 1 | let exception = v:exception + let stack = v:throwpoint + catch /^Vim\%((\a\+)\)\=:\%(E77\|E480\)/ + let bug = 1 | let exception = v:exception | let fatal = 1 + let stack = v:throwpoint + catch + let exception = v:exception | let fatal = 1 + let stack = v:throwpoint + finally + let &wildignore = _wildignore + endtry + if fatal + call s:ShowVimError(exception, '') + endif + if bug + echohl ERROR + echomsg "Please report this error message:" + echomsg "\t".exception + echomsg + echomsg "with the following information:" + echomsg "\ts:p4WinName:" s:p4WinName + echomsg "\tCurrent stack:" stack + echohl NONE + endif + " For non preview operation, or for preview window operation when the preview + " window is not already visible, we expect the number of windows to go up. + if !a:preview || (a:preview && !pWindowWasOpen) + if a:nWindows >= genutils#NumberOfWindows() + let s:errCode = 1 + endif + endif +endfunction + +function! s:MakeWindowName(...) + " Let only the options that are explicitly specified appear in the window + " name. + if a:0 > 0 + let cmdStr = a:1 + else + let cmdStr = 'p4 '.join(s:MakeP4ArgList(s:p4Options, 0), ' ') + endif + let winName = cmdStr + "let winName = genutils#DeEscape(winName) + " HACK: Work-around for some weird handling of buffer names that have "..." + " (the perforce wildcard) at the end of the filename or in the middle + " followed by a space. The autocommand is not getting triggered to clean + " the buffer. If we append another character to this, I observed that the + " autocommand gets triggered. Using "/" instead of "'" would probably be + " more appropriate, but this is causing unexpected FileChangedShell + " autocommands on certain filenames (try "PF submit ../..." e.g.). There + " is also another issue with "..." (anywhere) getting treated as ".." + " resulting in two names matching the same buffer( + " "p4 diff ../.../*.java" and "p4 submit ../.../*.java" e.g.). This + " could also change the name of the buffer during the :cd operations + " (though applies only to spec buffers). + "let winName = substitute(winName, '\.\.\%( \|$\)\@=', '&/', 'g') + "let winName = substitute(winName, '\.\.\%( \|$\)\@=', "&'", 'g') + let winName = substitute(winName, '\.\.\.', '..,', 'g') + " The intention is to do the substitute only on systems like windoze that + " don't allow all characters in the filename, but I can't generalize it + " enough, so as a workaround I a just assuming any system supporting + " 'shellslash' option to be a windoze like system. In addition, cygwin + " vim thinks that it is on Unix and tries to allow all characters, but + " since the underlying OS doesn't support it, we need the same treatment + " here also. + if exists('+shellslash') || has('win32unix') + " Some characters are not allowed in a filename on windows so substitute + " them with something else. + let winName = substitute(winName, s:specialChars, + \ '\="[" . s:specialCharsMap[submatch(1)] . "]"', 'g') + "let winName = substitute(winName, s:specialChars, '\\\1', 'g') + endif + " Finally escape some characters again. + let winName = genutils#Escape(winName, " #%\t") + if ! exists('+shellslash') " Assuming UNIX environment. + let winName = substitute(winName, '\\\@ nested :W + aug END +endfunction + +function! perforce#WipeoutP4Buffers(...) + let testMode = 1 + if a:0 > 0 && a:1 ==# '++y' + let testMode = 0 + endif + let i = 1 + let lastBuf = bufnr('$') + let cleanedBufs = '' + while i <= lastBuf + if bufexists(i) && expand('#'.i) =~# '\TestParseOptions() +function! s:TestParseOptions(commandName, ...) range + call call('s:ParseOptionsIF', [a:firstline, a:lastline, 0, 0, a:commandName]+ + \ a:000) + call s:DebugP4Status() +endfunction + +function! s:DebugP4Status() + echo "p4Options :" . join(s:p4Options, ' ') . ":" + echo "p4Command :" . s:p4Command . ":" + echo "p4CmdOptions :" . join(s:p4CmdOptions, ' ') . ":" + echo "p4Arguments :" . join(s:p4Arguments, ' ') . ":" + echo "p4Pipe :" . join(s:p4Pipe, ' ') . ":" + echo "p4WinName :" . s:p4WinName . ":" + echo "outputType :" . s:outputType . ":" + echo "errCode :" . s:errCode . ":" + echo "commandMode :" . s:commandMode . ":" + echo "filterRange :" . s:filterRange . ":" + echo "Cmd :" . s:CreateFullCmd(s:MakeP4ArgList([''], 0)) . ":" +endfunction + +"function! s:TestPushPopContexts() +" let s:p4Options = ["options1"] +" let s:p4Command = "command1" +" let s:p4CmdOptions = ["cmdOptions1"] +" let s:p4Arguments = ["arguments1"] +" let s:p4WinName = "winname1" +" call s:PushP4Context() +" +" let s:p4Options = ["options2"] +" let s:p4Command = "command2" +" let s:p4CmdOptions = ["cmdOptions2"] +" let s:p4Arguments = ["arguments2"] +" let s:p4WinName = "winname2" +" call s:PushP4Context() +" +" call s:ResetP4ContextVars() +" echo "After reset: " . s:CreateFullCmd(s:MakeP4ArgList([''], 0)) +" call s:PopP4Context() +" echo "After pop1: " . s:CreateFullCmd(s:MakeP4ArgList([''], 0)) +" call s:PopP4Context() +" echo "After pop2: " . s:CreateFullCmd(s:MakeP4ArgList([''], 0)) +"endfunction + +""" END: Testing }}} + +""" BEGIN: Experimental API {{{ + +function! perforce#PFGet(var) + return {a:var} +endfunction + +function! perforce#PFSet(var, val) + let {a:var} = a:val +endfunction + +function! perforce#PFCall(func, ...) + let result = call(a:func, a:000) + return result +endfunction + +function! perforce#PFEval(expr) + exec "let result = ".a:expr + return result +endfunction + +""" END: Experimental API }}} + +function! perforce#Initialize(initMenu) " {{{ + +" User Options {{{ + +if g:p4ClientRoot != '' + let g:p4ClientRoot = genutils#CleanupFileName(g:p4ClientRoot) +endif +if type(g:p4DefaultListSize) == 0 + let g:p4DefaultListSize = string(g:p4DefaultListSize) +endif +if g:p4FileLauncher == '' && genutils#OnMS() + let g:p4FileLauncher = "start rundll32 SHELL32.DLL,ShellExec_RunDLL" +endif +if g:p4DefaultPreset != -1 && + \ g:p4DefaultPreset.'' !~# s:EMPTY_STR + call perforce#PFSwitch(1, g:p4DefaultPreset) +endif + + +" Assume the user already has the preferred rulerformat set (which is anyway +" going to be done through the .vimrc file which should have been sourced by +" now). +if g:p4EnableRuler + " Take care of rerunning this code, as the reinitialization can happen any + " time. + if !exists("s:orgRulerFormat") + let s:orgRulerFormat = &rulerformat + else + let &rulerformat = s:orgRulerFormat + endif + + if &rulerformat != "" + if match(&rulerformat, '^%\d\+') == 0 + let orgWidth = substitute(&rulerformat, '^%\(\d\+\)(.*$', + \ '\1', '') + let orgRuler = substitute(&rulerformat, '^%\d\+(\(.*\)%)$', '\1', '') + else + let orgWidth = strlen(&rulerformat) " Approximate. + let orgRuler = &rulerformat + endif + else + let orgWidth = 20 + let orgRuler = '%l,%c%V%=%5(%p%%%)' + endif + let &rulerformat = '%' . (orgWidth + g:p4RulerWidth) . '(%{' . + \ 'perforce#RulerStatus()}%=' . orgRuler . '%)' +else + if exists("s:orgRulerFormat") + let &rulerformat = s:orgRulerFormat + else + set rulerformat& + endif +endif + +aug P4Active + au! + if g:p4EnableActiveStatus + au BufRead * call perforce#GetFileStatus(expand('') + 0, 0) + endif +aug END + +" User Options }}} + +if a:initMenu +runtime! perforce/perforcemenu.vim +let v:errmsg = '' +endif + +let g:p4PromptToCheckout = ! g:p4PromptToCheckout +call perforce#ToggleCheckOutPrompt(0) + +endfunction " s:Initialize }}} + +""" END: Infrastructure }}} + +" Do some initializations. +if g:p4DefaultPreset != -1 && + \ g:p4DefaultPreset.'' !~# s:EMPTY_STR + call perforce#PFSwitch(0, g:p4DefaultPreset) +endif + +aug P4ClientRoot + au! + if g:p4ClientRoot =~# s:EMPTY_STR || s:p4Client =~# s:EMPTY_STR || + \ s:p4User =~# s:EMPTY_STR + if s:_('EnableActiveStatus') + " If Vim is still starting up (construct suggested by Eric Arnold). + if bufnr("$") == 1 && !bufloaded(1) + au VimEnter * call GetClientInfo() | au! P4ClientRoot + else + call s:GetClientInfo() + endif + else + let g:p4ClientRoot = fnamemodify(".", ":p") + endif + endif +aug END + +call perforce#Initialize(0) + +" WORKAROUND for :redir broken, when called from completion function... just +" make sure this is initialized early. +call genutils#MakeArgumentString() + +" Restore cpo. +let &cpo = s:save_cpo +unlet s:save_cpo + +" vim6:fdm=marker et sw=2 diff --git a/.vim/autoload/perforceutils.vim b/.vim/autoload/perforceutils.vim new file mode 100755 index 0000000..2009413 --- /dev/null +++ b/.vim/autoload/perforceutils.vim @@ -0,0 +1,260 @@ +" perforceutils.vim: Please see perforce/perforceutils.vim + +" Make sure line-continuations won't cause any problem. This will be restored +" at the end +let s:save_cpo = &cpo +set cpo&vim + +" Determine the script id. +function! s:MyScriptId() + map xx xx + let s:sid = maparg("xx") + unmap xx + return substitute(s:sid, "xx$", "", "") +endfunction +let s:myScriptId = s:MyScriptId() +delfunction s:MyScriptId " This is not needed anymore. + +" CAUTION: Don't assume the existence of plugin/perforce.vim (or any other +" plugins) at the time this file is sourced. + +" DiffLink {{{ + +" Open the source line for the current line from the diff. +function! perforceutils#DiffOpenSrc(preview) " {{{ + let s:EMPTY_STR = perforce#PFEval('s:EMPTY_STR') + if perforce#PFEval('s:GetCurrentItem()') !~# s:EMPTY_STR + PItemOpen + endif + call genutils#SaveHardPosition('DiffOpenSrc') + " Move to the end of next line (if possible), so that the search will work + " correctly when the cursor is ON the header (should find the current line). + normal $ + let filePat = '\zs[^#]\+\%(#\d\+\)\=\ze\%( ([^)]\+)\)\=' + let diffHdr = '^diff \%(-\S\+\s\+\)*' + " Search backwards to find the header for this diff (could contain two + " depot files or one depot file with or without a local file). + if search('^==== '.filePat.'\%( - '.filePat.'\)\= ====', 'bW') + let firstFile = matchstr(getline('.'), '^==== \zs'.filePat. + \ '\%( - \| ====\)') + let secondFile = matchstr(getline('.'), ' - '.filePat.' ====', + \ strlen(firstFile)+5) + let foundHeader = 1 + + " GNU diff header. + elseif search('^--- '.filePat.'.*\n\_^+++ '.filePat, 'bW') + let firstFile = matchstr(getline(line('.')-1), '^--- \zs.\{-}\ze\t') + let secondFile = matchstr(getline('.'), '^+++ \zs.\{-}\ze\t') + let foundHeader = 1 + + " Another GNU diff header, for default output (typically for -r option). + elseif search(diffHdr.filePat.' '.filePat, 'bW') + exec substitute(substitute(getline('.'), + \ diffHdr.'\('.filePat.'\) \('.filePat.'\)', + \ ":::let firstFile = '\\1' | let secondFile = '\\2'", ''), + \ '^.*:::', '', '') + let foundHeader = 1 + else + let foundHeader = 0 + endif + if foundHeader + call genutils#RestoreHardPosition('DiffOpenSrc') + if firstFile =~# s:EMPTY_STR + return + elseif secondFile =~# s:EMPTY_STR + " When there is only one file, then it is treated as the secondFile. + let secondFile = firstFile + let firstFile = '' + endif + + " Search for the start of the diff segment. We could be in default, + " context or unified mode. Determine context, stLine and offset. + if search('^\d\+\%(,\d\+\)\=[adc]\d\+\%(,\d\+\)\=$', 'bW') " default. + let segStLine = line('.') + let segHeader = getline('.') + call genutils#RestoreHardPosition('DiffOpenSrc') + let context = 'depot' + let regPre = '^' + if getline('.') =~# '^>' + let context = 'local' + let regPre = '[cad]' + if search('^---$', 'bW') && line('.') > segStLine + let segStLine = line('.') + endif + endif + let stLine = matchstr(segHeader, regPre.'\zs\d\+\ze') + call genutils#RestoreHardPosition('DiffOpenSrc') + let offset = line('.') - segStLine - 1 + elseif search('\([*-]\)\1\1 \d\+,\d\+ \1\{4}', 'bW') " context. + if getline('.') =~# '^-' + let context = 'local' + else + let context = 'depot' + endif + let stLine = matchstr(getline('.'), '^[*-]\{3} \zs\d\+\ze,') + let segStLine = line('.') + call genutils#RestoreHardPosition('DiffOpenSrc') + let offset = line('.') - segStLine - 1 + elseif search('^@@ -\=\d\+,\d\+ +\=\d\+,\d\+ @@$', 'bW') " unified + let segStLine = line('.') + let segHeader = getline('.') + call genutils#RestoreHardPosition('DiffOpenSrc') + let context = 'local' + let sign = '+' + if getline('.') =~# '^-' + let context = 'depot' + let sign = '-' + endif + let stLine = matchstr(segHeader, ' '.sign.'\zs\d\+\ze,\d\+') + let _ma = &l:modifiable + try + setl modifiable + " Count the number of lines that come from the other side (those lines + " that start with an opposite sign). + let _ss = @/ | let @/ = '^'.substitute('-+', sign, '', '') | + \ let offOffset = matchstr(genutils#GetVimCmdOutput( segStLine.',.s//&/'), + \ '\d\+\ze substitutions\? on \d\+ lines\?') + 0 | let @/ = _ss + if offOffset > 0 + silent! undo + call genutils#RestoreHardPosition('DiffOpenSrc') + endif + let offset = line('.') - segStLine - 1 - offOffset + finally + let &l:modifiable = _ma + endtry + else " Not inside a diff context, just use 1. + let context = 'local' + let stLine = 1 + let offset = 0 + endif + + try + if context ==# 'depot' && firstFile =~# s:EMPTY_STR + " Assume previous revision as the "before" file if none specified. + if perforce#PFCall('s:IsDepotPath', secondFile) && secondFile =~# '#\d\+' + let depotRev = s:GetFileRevision(secondFile) + if depotRev == '' + return + endif + let firstFile = substitute(secondFile, '#\d\+', '', '').'#'.(depotRev-1) + else + return + endif + endif + if context ==# 'local' + let file = secondFile + else + let file = firstFile + endif + " If the path refers to a depot file, check if the local file is currently + " open in Vim and if so has the same version number as the depot file. + if context ==# 'local' && perforce#PFCall('s:IsDepotPath', file) + let localFile = perforce#PFCall('s:ConvertToLocalPath', file) + let bufNr = bufnr(localFile) + 0 + if bufNr != -1 + let haveRev = getbufvar(bufNr, 'p4HaveRev') + let depotRev = s:GetFileRevision(file) + if haveRev == depotRev + let file = localFile + endif + " else " We could also try to run 'fstat' command and open up the file. + endif + endif + if perforce#PFCall('s:IsDepotPath', file) + let refresh = perforce#PFGet('s:refreshWindowsAlways') + try + call perforce#PFSet('s:refreshWindowsAlways', 0) + call perforce#PFIF(1, a:preview, 'print', file) + finally + call perforce#PFSet('s:refreshWindowsAlways', refresh) + endtry + let offset = offset + 1 " For print header. + else + call perforce#PFCall('s:OpenFile', 1, a:preview, genutils#CleanupFileName(file)) + endif + if perforce#PFEval('s:errCode') == 0 + if a:preview + wincmd P + endif + mark ' + exec (stLine + offset) + if a:preview + " Also works as a work-around for the buffer not getting scrolled. + normal! z. + wincmd p + endif + endif + catch + call perforce#PFCall('s:EchoMessage', v:exception, 'Error') + endtry + endif +endfunction " }}} + +function! perforceutils#SetupDiffLink() + command! -buffer -nargs=0 PDiffLink :PFDiffLink + command! -buffer -nargs=0 PDiffPLink :PFDiffPLink + nnoremap O :PDiffLink + nnoremap :PDiffPLink +endfunction + +function! s:GetFileRevision(depotPath) + let rev = matchstr(a:depotPath, '#\zs\d\+$') + return (rev !~# s:EMPTY_STR) ? rev + 0 : '' +endfunction + +" DiffLink }}} + +" ShowConflicts {{{ +function! perforceutils#ShowConflicts() + let _splitright = &splitright + set splitright + try + let curFile = expand('%:p') + "exec 'split' curFile.'.Original' + exec 'edit' curFile.'.Original' + silent! exec 'read' curFile + silent! 1delete _ + call genutils#SilentSubstitute('^==== THEIRS \_.\{-}\%(^<<<<$\)\@=', '%s///e') + call genutils#SilentDelete('\%(^>>>> ORIGINAL \|^==== THEIRS\|^==== YOURS \|^<<<<$\)') + call genutils#SetupScratchBuffer() + setlocal nomodifiable + diffthis + + exec 'vsplit' curFile.'.Theirs' + silent! exec 'read' curFile + 1delete _ + call genutils#SilentSubstitute('^>>>> ORIGINAL \_.\{-}\%(^==== THEIRS \)\@=', '%s///e') + call genutils#SilentSubstitute('^==== YOURS \_.\{-}\%(^<<<<$\)\@=', '%s///e') + call genutils#SilentDelete('\%(^>>>> ORIGINAL \|^==== THEIRS\|^==== YOURS \|^<<<<$\)') + call genutils#SetupScratchBuffer() + setlocal nomodifiable + diffthis + + exec 'vsplit' curFile.'.Yours' + silent! exec 'read' curFile + 1delete _ + call genutils#SilentSubstitute('^>>>> ORIGINAL \_.\{-}\%(^==== YOURS \)\@=', '%s///e') + call genutils#SilentDelete('\%(^>>>> ORIGINAL \|^==== THEIRS\|^==== YOURS \|^<<<<$\)') + call genutils#SetupScratchBuffer() + setlocal buftype= + setlocal nomodified + exec "au Perforce BufWriteCmd :call SaveYours('".curFile."')" + diffthis + finally + let &splitright = _splitright + endtry +endfunction + +function! s:SaveYours(orgFile) + if confirm('Do you want to accept the changes in "'.expand("%:p:t").'"?', + \ "&Yes\n&No", 2, "Question") == 1 + exec 'w!' a:orgFile + endif +endfunction +" ShowConflicts }}} + +" Restore cpo. +let &cpo = s:save_cpo +unlet s:save_cpo + +" vim6:fdm=marker et sw=2 diff --git a/.vim/doc/perforce.txt b/.vim/doc/perforce.txt new file mode 100755 index 0000000..32d6985 --- /dev/null +++ b/.vim/doc/perforce.txt @@ -0,0 +1,2441 @@ +*perforce.txt* A feature Rich Perforce SCM Integration for Vim. + Requires Vim 6.0 + Last Change: 01-Sep-2006 @ 16:54 + Since Version: 1.4 + Revision: 1.3.0 + Plugin Version: 4.1 + Author: Hari Krishna Dara (hari_vim at yahoo dot com) + + *perforce-introduction* + *perforce-plugin* +This is a fairly complete integration with the perforce version control system +for the most commonly used operations, including many administrative commands. +It includes a great menu that is modelled very close to the p4win (the perforce +GUI client) and is quite extensive. + +============================================================================== +OVERVIEW *perforce-overview* + +|perforce-installation| How to install the plugin. + +|perforce-filetype| Perforce filetype plugin and setting it up for p4. + +|perforce-settings| An explanation of how to configure the plugin, for + both the perforce related parameters and the plugin + customizations, such as enabling menu. + +|perforce-ruler| Setting up the ruler to show an active status of the + file. + +|perforce-syntax| Perforce syntax plugin. + +|perforce-help| Browse the p4 help conveniently from within vim. + +|perforce-reinitialize| Describes how to reinitialize the plugin to load the + latest settings from the environment without + restarting vim. + +|perforce-commands| A description of various commands that can be + issued on Vim command-line. + +|perforce-revisions| Specifying perforce revisions conveniently. + +|perforce-interactive-commands| + How to execute interactive perforce commands. + +|perforce-forms| How to edit the perforce forms (specifications) from + within vim. + +|perforce-submit| Special handling of submit command. + +|perforce-list-commands| Commands that can be used in a list of items window. + +|perforce-extensions| Some useful extensions in the form of new options + and commands. + +|perforce-misc-commands| Some additional useful commands. + +|perforce-special-commands| Some useful commands and mappings. + +|perforce-utils| Additional utilties (perforce/perforceutils.vim). + +|perforce-API| New API provided by the plugin (experimental). + +|perforce-tips| Some useful tips. + +|perforce-limitations| Current limitations of the plugin. + +|perforce-troubleshooting| Some notes how to trouble shoot problems. + +|perforce-changes| A change list for current version from previous + versions. + +|perforce-known-issues| A list of known bugs. + +|perforce-wishlist| Wishlist items that may be worth doing for a future + version. + +|perforce-bugreporting| Reporting bugs. + +|perforce-acknowledgements| Acknowledgements. + +============================================================================== + + *perforce-installation* + +To install, place the perforce.vim script in one of the runtime plugin +directories along with the genutils.vim scripts that it depends on. This +typically is .vim/plugin or vimfiles/plugin directory under your home directory. +To get the latest versions of these plugins, goto the following webplages: + perforce.vim: http://www.vim.org/script.php?script_id=240 + genutils.vim: http://www.vim.org/script.php?script_id=197 + +The distribution contains the following files: + - plugin/perforce.vim + - Thin interface to the plugin (autoload/perforce.vim). + - autoload/perforce.vim + - The perforce plugin itself loaded on demand. + - ftplugin/perforce.vim + - The perforce filetype plugin file. See below on how to configure the + perforce filetype. + - syntax/perforce.vim. + - Syntax definition file for perforce forms. + - doc/perforce.txt. + - The online help file (this file). + - perforce/perforcemenu.vim. + - Additional module to install and configure a menu for the plugin. + See |perforce-menu|. + - perforce/perforcebugrep.vim. + - An utility script that can be used to generate useful information + while sending bugreports about the plugin. See + |perforce-bugreporting|. + - perforce/perforceutils.vim. + - Thin interface to autoload/perforceutils.vim + - autoload/perforceutils.vim + - Additional misc. utilities loaded on demand. + +If you obtained the plugin as a zip archive, + - Just extract it in your runtime directory. + - Start a new instance or go to an existing instance of vim. + - Execute: +> + :helpt /doc +< + This should generate the help tags for the perforce plugin help. + - Setup perforce filetype as described in |perforce-filetype| section. + - Make sure p4 command is in your path or set |p4CmdPath| property. + - Set the |p4ClientRoot| property and additionally any other + |perforce-settings| that you like. + - Optionally set 'cmdheight' property to at least 2 to avoid seeing the more + prompt for most of the messages the plugin gives. + - The plugin requires that you have Vim 7.0 version installed. + - If you want to enable the perforceutils.vim plugin, add the following line + to your vimrc: +> + runtime perforce/perforceutils.vim +< + - If you want to enable the menu, add the following line to your vimrc, at + the end of perforce configuration lines: +> + runtime perforce/perforcemenu.vim +< + - The core of the plugin is autoloaded by Vim the first time a perforce + command needs to be executed. This improves the startup time of Vim + considerably in some situations. If you want the plugin to be loaded + immediately, you can force an autoload by adding the following command to + your vimrc: +> + PFIntialize +< + +Note: It is important to make sure your 'shellredir' properly set such that, vim +captures both the standard output and standard error messages from the external +perforce command. If this is not correctly set, the plugin will not be able to +show the error messages generated by p4 command to you. + +Also note that on Windows, if you use a UNIX like shell for your 'shell' +setting, the plugin will not work correctly unless the 'shellslash' option is +set. + + *loaded_perforce* +Later, if you need to temporarily disable the plugin without needing to remove +the files, you can set the loaded_perforce variable in your vimrc. You can also +set the no_perforce_maps to disable just the mappings defined in this plugin or +no_plugin_maps to disable mappings in all the plugins (provided they all honor +this setting), in your vimrc. +============================================================================== + + *perforce-filetype* + *perforce-ftplugin* +The package comes with a perforce ftplugin (|filetype-plugin|) which sets a +few text mode options suitable for editing the forms and a syntax plugin to +colorize the spec files. The ftplugin also positions the cursor at the +appropriate line based on the type of spec you are editing. When you open +perforce forms from within vim using |perforce-interactive-commands|, you +don't have to do anything special for this to work, but if you want the same +to work while editing forms using p4 command directly, then you need to add +the following lines in your scripts.vim: +> + if getline(1) =~ '^# A Perforce ' + setfiletype perforce + endif +< +If you do not have this file already, then you need to create it under +vimfiles or .vim directory (or anywhere in your 'rtp'). For details see help +on |new-filetype-scripts|. Note that you also need to enable filetype plugins +for this to work, see |filetype-plugin| for more information. + +============================================================================== + + *perforce-settings* + *perforce-customizing* +The plugin allows a lot of customization by reading some global variables. You +can define a set of global variables in your startup script (.vimrc/_vimrc) and +they are automatically read by the plugin during the Vim startup and are used +for customizing the behavior. + +Note that all the setting names are case sensitive and the boolean settings can +be set to the value 0 to disable and any non-zero number (for the matter of +fact, any string will also do) to enable. All the settings can be changed at +runtime without restarting vim; see |perforce-reinitialize|. + +The settings can also be changed at runtime, see |perforce-reinitialize|. + +Here is a list of all the options: + |p4CmdPath|, |p4DefaultPreset|, |p4DefaultOptions|, |p4ClientRoot|, + |p4EnableRuler|, |p4RulerWidth|, |p4EnableActiveStatus|, + |p4ASIgnoreDefPattern|, |p4ASIgnoreUsrPattern|, |p4OptimizeActiveStatus|, + |p4UseGUIDialogs|, |p4PromptToCheckout|, |p4DefaultListSize|, + |p4DefaultDiffOptions|, |p4EnableMenu|, |p4UseExpandedMenu|, + |p4EnablePopupMenu|, |p4UseExpandedPopupMenu|, |p4Presets|, + |p4MaxLinesInDialog|, |p4CheckOutDefault|, |p4SortSettings|, |p4TempDir|, + |p4SplitCommand|, |p4UseVimDiff2|, |p4EnableFileChangedShell|, + |p4HideOnBufHidden|, |p4Depot|, |p4Autoread|, |p4FileLauncher|, + |p4CurPresetExpr|, |p4CurDirExpr|, |p4UseClientViewMap| + + *perforce-command-path* + *p4CmdPath* +The plugin executes the external p4 command for all its operations. By default +the command is found in the PATH, but if it doesn't exist in the path you can +still specify the location by using the p4CmdPath variable. +> + :let g:p4CmdPath = '' +< + Example: > + :let g:p4CmdPath = '~/bin/perforce/bin/p4' +< + *perforce-presets* + *p4Presets* +This is a useful setting when you work with multiple perforce installations at +the same time. This allows you to predefine a set of configurations that you +can switch between without needing to restart vim or manually enter the +details while using the |PFSwitch| command. Set this variable in your vimrc +with a comma separated list of settings. Each setting should be of +the form (separated with one or more spaces): +> + +< +You can include as many of these specifications as you want, just separate them +with commas. +> + :let g:p4Presets = 'port1 client1 user1,port2 client2 user2' +< +Once set, you can use the |PFSwitch| command in one of three ways to choose the +setting. + + 1. If you know the index of the setting to which you want to + switch to (starting with 0), just pass that as an argument to the + command as +> + " Switch to the third setting. + :PFSwitch 2 +< + 2. If you don't know the index, just invoke the command without any + arguments. You will be prompted to enter the index after displaying + a list of available settings. + + 3. If you have expanded menu enabled (see |p4UseExpandedMenu| or + |p4UseExpandedPopupMenu|), then you can choose the desired setting + to switch to using the "Settings" sub-menu under "Perforce" group. + + *perforce-P4CONFIG* + *P4CONFIG* +As a special case, you can have one of the specifications as "P4CONFIG", +allowing you to switch to the P4CONFIG feature of external p4 command +interactively, see |perforce-dynamic-client|. +> + :let g:p4Presets = 'p1 c1 u1,P4CONFIG,p2 c2 u2' +< +Setting port field to "P4CONFIG" also has the same effect. This results in +plugin not passing the options for port, client and user explicitly such that p4 +can determine them based on the P4CONFIG file. However you can still override +them by explicitly specifying the corresponding option to PF command. + + *p4DefaultPreset* +If you don't have the P4CLIENT, P4USER and P4PORT environment variables set, you +can use this setting to initialize the plugin with corresponding values. The +format for the value is exactly same as a single entry in |p4Presets| setting. +In fact, you can also set this to an index in the |p4Presets|. +> + :let g:p4DefaultPreset = ' ' +< + Example: > + :let g:p4DefaultPreset = 'localhost:1666 hari_client hari' + :let g:p4DefaultPreset = 2 " Start with the second setting in p4Presets. +< +Note that the plugin automatically chooses some defaults for the above based on +your environment, if you don't explicitly set them in your startup script. + + *perforce-default-options* + *perforce-common-options* + *p4DefaultOptions* +If you need to pass in additional arguments every time the external p4 command +is run, you can use the following setting. The arguments are passed to p4 +literally. For the set of options that you can specify here, see perforce help +for "usage" (use ":PH usage" command, see |perforce-help|) +> + :let g:p4DefaultOptions = '' +< + Example: > + :let g:p4DefaultOptions = '-H hkrishna' +< + *perforce-buffer-local-options* +In addition you can also use the buffer local variables "b:p4Options", +"b:p4Client", "b:p4User", "b:p4Port" and "b:clientRoot" to override the global +options (see ":PH usage" for option list) at a buffer level. The plugin +automatically sets these options whenever you create a new perforce result +window such that any future commands originating from the same window +automatically inherit them. This is especially useful when you temporarily +switch to a different client/user by providing one at command-line, and later do +more operations on the results (provided the given client/user is valid on the +current m/c). > + + :PF -c hari_tmp opened + :PFileDiff +> +The PFileDiff will automatically use the "hari_tmp" client as b:p4Client is set +to that value. You can also use b:p4Options to set any option that is accepted +in the global options section of PF command (see |perforce-global-options|), +however for the client view mapping to work correctly, you need to have +b:p4Client and b:clientRoot set. > + + :let b:p4Options = '-H my_host' +< +All commands executed when this buffer is active will automatically inherit +these global options. + +NOTE: Currently the b:clientRoot variable needs to be set manually. + + *perforce-client-root* + *p4ClientRoot* +The client root is required for certain commands (it is same as what you +specify in the Root: property in the client settings), so if not specified and +if the |p4EnableActiveStatus| setting is enabled, the plugin will run the "p4 +info" command to read the client root setting from the perforce server. But +this will introduce a short delay in autoload time (especially if you are +talking to a perforce server that is installed out side your network). To avoid +this, use the following setting to specify the client root. +> + :let g:p4ClientRoot = '' +< + Example: > + :let g:p4ClientRoot = 'c:/dev' +< +If |p4EnableActiveStatus| is disabled, the current directory is used as the +default. + + *perforce-gui-dialogs* + *p4UseGUIDialogs* +By default the plugin uses console dialogs for some of the prompts. This is +convenient as you can then use the input-history and expression register +|quote=|. But if you like, you can enable this setting to force using GUI +dialogs for all the prompts. +> + :let g:p4UseGUIDialogs = 1 +< + + *perforce-automatic-checkout* + *p4PromptToCheckout* +The plugin by default prompts you to checkout, when you start editing a +readonly file under the client root. You can disable this behavior by using the +following setting: +> + :let g:p4PromptToCheckout = 0 +< +Note that you can still manually checkout (or edit) the file even when this +option is disabled by using "PE" or "PF edit" command. + +When the checkout prompt is given, you have the option saying Yes, No or Cancel. +When accidental changes to the buffer bring up the checkout prompt, you can +select Cancel to revert the state of the buffer as much as possible (you may +still have to press sometimes). One advantage of selecting Cancel in these +cases is that the next time you start making a genuine change, you will get the +checkout prompt as expected (otherwise, you will only get this prompt once). + + *perforce-list-size* + *perforce-default-list-size* + *p4DefaultListSize* +When you execute "changes", "jobs" and "filelog" perforce commands, the number +of entries is limited to 100 to avoid generating a large volume of data. But you +can change the value to whatever you like: +> + :let g:p4DefaultListSize = 1000 +< +To disabling it completely (show the entire list) set it to a negative number: +> + :let g:p4DefaultListSize = -1 +< + + *perforce-default-diff-options* + *p4DefaultDiffOptions* +You can set the default diff options to be passed to all the "diff" and "diff2" +operations (both direct and indirect execution of these commands), by using the +following setting: +> + :let g:p4DefaultDiffOptions = '-dwbu5' +< +For the options that you can set here, see the help for "diff" or "diff2" by +running the "PH diff" or "PH diff2" command. + +Note, this setting can't be used to specify options to the external diff +program. + + *perforce-menu* + *p4EnableMenu* +The distribution comes with an additional module called perforcemenu.vim to +install a Perforce sub-menu on the main or PopUp menu. By default the menu is +not added as many people (including myself) don't use menus (I have the entire +menu bar disabled). Use the following setting to enable the Perforce sub-menu: +> + :let g:p4EnableMenu = 1 +< +The above setting will create a very basic menu with the most needed commands. +This makes it easy to use shortcut keys if you have the |winaltkeys| correctly +configured. To enable a more full featured menu, see |p4UseExpandedMenu|. + + *perforce-expanded-menu* + *p4UseExpandedMenu* +By default the |p4EnableMenu| option creates a full-featured menu that is +modelled closely after the p4Win utility, which comes with perforce. But you can +disable this and have only a basic menu with the most commonly used set of +commands (this was the default for older versions of the plugin). Use the +following setting in your startup script: +> + :let g:p4UseExpandedMenu = 0 +< +If you want a basic menu on the main menu (for the ease of using the shortcut +keys), then you can consider having the full-featured version on the popup +menu, see |p4EnablePopupMenu| and |p4UseExpandedPopupMenu| settings. + + *perforce-popup-menu* + *p4EnablePopupMenu* +This is similar to |p4EnableMenu| except that enabling this option, adds a +Perforce sub-menu on the PopUp menu instead of the main menu. +> + :let g:p4EnablePopupMenu = 1 +< + *perforce-expanded-popup-menu* + *p4UseExpandedPopupMenu* +This is similar to |p4UseExpandedMenu| except that enabling this option, adds a +more full-featured Perforce sub-menu on the PopUp menu. +> + :let g:p4UseExpandedPopupMenu = 1 +< + *loaded_perforcemenu* +Note: If you never use the menu features of the plugin, consider setting +"loaded_perforcemenu" to a non-zero value, to avoid getting this module sourced. + + *perforce-max-lines-in-dialog* + *p4MaxLinesInDialog* +Commands that use a dialog box to show the result (such as |PEdit|) assume +that the messages generated by the perforce command are only a few lines. But +depending on the arguments (e.g., "PEdit ..." and there are many files under +the current directory ...), there can be too many lines to display in a dialog +so the display mode is automatically switched to a new window instead of the +dialog. Though the default limit is 1, which helps to draw your attention for +the conditions that you normally expect a one line result (e.g., you checkout a +file and someone else already checked out the file), you can change it by +setting the following line: +> + :let g:p4MaxLinesInDialog = +< + Example: +> + :let g:p4MaxLinesInDialog = 5 +< + *p4CheckOutDefault* +When you start modifying a read-only file, the plugin prompts you to checkout +the file from perforce. Set this option to 1 to make the default option to +"Yes" and 2 for "No". The default is 2 to avoid accidentally checking out a +file. > + + :let g:p4CheckOutDefault =