X-Git-Url: http://git.iain.cx/?a=blobdiff_plain;f=.vim%2Fautoload%2Fperforce.vim;fp=.vim%2Fautoload%2Fperforce.vim;h=460ef9c42ea2e4e8ad922fce919ce7424fc4ad93;hb=d0d885039e0b7f2242d6db6eb2ba36da132f1b94;hp=0000000000000000000000000000000000000000;hpb=85808cfafda2e74a99654d9a940291563b13cd85;p=profile.git 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