Added Perforce plugin.
[profile.git] / .vim / autoload / perforce.vim
1 " perforce.vim: Please see plugin/perforce.vim
2
3 " Make sure line-continuations won't cause any problem. This will be restored
4 "   at the end
5 let s:save_cpo = &cpo
6 set cpo&vim
7
8
9 """ BEGIN: Initializations {{{
10
11 " Determine the script id.
12 function! s:MyScriptId()
13   map <SID>xx <SID>xx
14   let s:sid = maparg("<SID>xx")
15   unmap <SID>xx
16   return substitute(s:sid, "xx$", "", "")
17 endfunction
18 let s:myScriptId = s:MyScriptId()
19 delfunction s:MyScriptId " This is not needed anymore.
20
21 """ BEGIN: One-time initialization of some script variables {{{
22 let s:lastMsg = ''
23 let s:lastMsgGrp = 'None'
24 " Indicates the current recursion level for executing p4 commands.
25 let s:recLevel = 0
26
27 if genutils#OnMS() && match(&shell, '\<bash\>') != -1
28   " When using cygwin bash with native vim, p4 gets confused by the PWD, which
29   "   is in cygwin style.
30   let s:p4CommandPrefix = "unset PWD && "
31 else
32   let s:p4CommandPrefix = ""
33 endif
34
35 " Special characters in a filename that are not acceptable in a filename (as a
36 "   window title) on windows.
37 let s:specialChars = '\([*:?"<>|]\)' 
38 let s:specialCharsMap = {
39       \   '*': 'S',
40       \   ':': 'C',
41       \   '?': 'Q',
42       \   '"': 'D',
43       \   '<': 'L',
44       \   '>': 'G',
45       \   '|': 'P',
46       \ }
47
48 "
49 " A lot of metadata on perforce command syntax and handling.
50 "
51
52 let s:p4KnownCmds = split('add,admin,annotate,branch,branches,change,changes,' .
53       \ 'client,clients,counter,counters,delete,depot,depots,describe,diff,' .
54       \ 'diff2,dirs,edit,filelog,files,fix,fixes,flush,fstat,get,group,' .
55       \ 'groups,have,help,info,integ,integrate,integrated,job,jobs,jobspec,' .
56       \ 'label,labels,labelsync,lock,logger,login,monitor,obliterate,opened,' .
57       \ 'passwd,print,protect,rename,reopen,resolve,resolved,revert,review,' .
58       \ 'reviews,set,submit,sync,triggers,typemap,unlock,user,users,verify,' .
59       \ 'where,workspaces,', ',')
60 " Add some built-in commands to this list.
61 let s:builtinCmds = split('vdiff,vdiff2,exec,', ',')
62 let s:allCommands = s:p4KnownCmds + s:builtinCmds
63 let s:p4KnownCmdsCompStr = ''
64
65 " Map between the option and the commands that reqire us to pass an argument
66 "   with this option.
67 let s:p4OptCmdMap = {}
68 let s:p4OptCmdMap['b'] = split('diff2,integrate', ',')
69 let s:p4OptCmdMap['c'] = split('add,delete,edit,fix,fstat,integrate,lock,' .
70       \ 'opened,reopen,r[ver],review,reviews,submit,unlock', ',')
71 let s:p4OptCmdMap['e'] = ['jobs']
72 let s:p4OptCmdMap['j'] = ['fixes']
73 let s:p4OptCmdMap['l'] = ['labelsync']
74 let s:p4OptCmdMap['m'] = split('changes,filelog,jobs', ',')
75 let s:p4OptCmdMap['o'] = ['print']
76 let s:p4OptCmdMap['s'] = split('changes,integrate', ',')
77 let s:p4OptCmdMap['t'] = split('add,client,edit,label,reopen', ',')
78 let s:p4OptCmdMap['O'] = ['passwd']
79 let s:p4OptCmdMap['P'] = ['passwd']
80 let s:p4OptCmdMap['S'] = ['set']
81
82 " These built-in options require us to pass an argument. These options start
83 "   with a '+'.
84 let s:biOptCmdMap = {}
85 let s:biOptCmdMap['c'] = ['diff']
86
87 " Map the commands with short name to their long versions.
88 let s:shortCmdMap = {}
89 let s:shortCmdMap['p'] = 'print'
90 let s:shortCmdMap['d'] = 'diff'
91 let s:shortCmdMap['e'] = 'edit'
92 let s:shortCmdMap['a'] = 'add'
93 let s:shortCmdMap['r'] = 'revert'
94 let s:shortCmdMap['g'] = 'get'
95 let s:shortCmdMap['o'] = 'open'
96 let s:shortCmdMap['d2'] = 'diff2'
97 let s:shortCmdMap['h'] = 'help'
98
99
100 " NOTE: The current file is used as the default argument, only when the
101 "   command is not one of the s:askUserCmds and it is not one of
102 "   s:curFileNotDefCmds or s:nofileArgsCmds.
103 " For these commands, we don't need to default to the current file, as these
104 "   commands can work without any arguments.
105 let s:curFileNotDefCmds = split('change,changes,client,files,integrate,job,' .
106       \ 'jobs,jobspec,labels,labelsync,opened,resolve,submit,user,', ',')
107 " For these commands, we need to ask user for the argument, as we can't assume
108 "   the current file is the default.
109 let s:askUserCmds = split('admin,branch,counter,depot,fix,group,label,', ',')
110 " A subset of askUserCmds, that should use a more generic prompt.
111 let s:genericPromptCmds = split('admin,counter,fix,', ',')
112 " Commands that essentially display a list of files.
113 let s:filelistCmds = split('files,have,integrate,opened,', ',')
114 " Commands that work with a spec.
115 let s:specCmds = split('branch,change,client,depot,group,job,jobspec,label,' .
116       \ 'protect,submit,triggers,typemap,user,', ',')
117 " Out of the above specCmds, these are the only commands that don't
118 "   support '-o' option. Consequently we have to have our own template.
119 let s:noOutputCmds = ['submit']
120 " The following are used only to create a specification, not to view them.
121 "   Consequently, they don't accept a '-d' option to delete the spec.
122 let s:specOnlyCmds = split('jobspec,submit,', ',')
123 " These commands might change the fstat of files, requiring an update on some
124 "   or all the buffers loaded into vim.
125 "let s:statusUpdReqCmds = 'add,delete,edit,get,lock,reopen,revert,sync,unlock,'
126 "" For these commands we need to call :checktime, as the command might have
127 ""   changed the state of the file.
128 "let s:checktimeReqCmds = 'edit,get,reopen,revert,sync,'
129 " For these commands, we can even set 'autoread' along with doing a :checktime.
130 let s:autoreadCmds = split('edit,get,reopen,revert,sync,', ',')
131 " These commands don't expect filename arguments, so no special processing for
132 "   file expansion.
133 let s:nofileArgsCmds = split('branch,branches,change,client,clients,counters,' .
134       \ 'depot,depots,describe,dirs,group,groups,help,info,job,jobspec,label,' .
135       \ 'logger,passwd,protect,rename,review,triggers,typemap,user,users,', ',')
136 " For these commands, the output should not be set to perforce type.
137 let s:ftNotPerforceCmds = split('diff,diff2,print,vdiff,vdiff2', ',')
138 " Allows navigation keys in the command window.
139 let s:navigateCmds = ['help']
140 " These commands accept a '-m' argument to limit the list size.
141 let s:limitListCmds = split('filelog,jobs,changes,', ',')
142 " These commands take the diff option -dx.
143 let s:diffCmds = split('describe,diff,diff2,', ',')
144 " The following commands prefer dialog output. If the output exceeds
145 "   g:p4MaxLinesInDialog, we should switch to showing the output in a window.
146 let s:dlgOutputCmds =
147       \ split('add,delete,edit,get,lock,reopen,revert,sync,unlock,', ',')
148
149 " If there is a confirm message, then PFIF() will prompt user before
150 "   continuing with the run.
151 let s:confirmMsgs{'revert'} = "Reverting file(s) will overwrite any edits to " .
152       \ "the files(s)\n Do you want to continue?"
153 let s:confirmMsgs{'submit'} = "This will commit the changelist to the depot." .
154       \ "\n Do you want to continue?"
155
156 " Settings that are not directly exposed to the user. These can be accessed
157 "   using the public API.
158 " Refresh the contents of perforce windows, even if the window is already open.
159 let s:refreshWindowsAlways = 1
160
161 " List of the global variable names of the user configurable settings.
162 let s:settings = split('ClientRoot,CmdPath,Presets,' .
163       \ 'DefaultOptions,DefaultDiffOptions,EnableMenu,EnablePopupMenu,' .
164       \ 'UseExpandedMenu,UseExpandedPopupMenu,EnableRuler,RulerWidth,' .
165       \ 'DefaultListSize,EnableActiveStatus,OptimizeActiveStatus,' .
166       \ 'ASIgnoreDefPattern,ASIgnoreUsrPattern,PromptToCheckout,' .
167       \ 'CheckOutDefault,UseGUIDialogs,MaxLinesInDialog,SortSettings,' .
168       \ 'TempDir,SplitCommand,UseVimDiff2,EnableFileChangedShell,' .
169       \ 'BufHidden,Depot,Autoread,UseClientViewMap,DefaultPreset', ',')
170 let s:settingsCompStr = ''
171
172 let s:helpWinName = 'P4\ help'
173
174 " Unprotected space.
175 let s:SPACE_AS_SEP = genutils#CrUnProtectedCharsPattern(' ')
176 let s:EMPTY_STR = '^\_s*$'
177
178 if !exists('s:p4Client') || s:p4Client =~# s:EMPTY_STR
179   let s:p4Client = $P4CLIENT
180 endif
181 if !exists('s:p4User') || s:p4User =~# s:EMPTY_STR
182   if exists("$P4USER") && $P4USER !~# s:EMPTY_STR
183     let s:p4User = $P4USER
184   elseif genutils#OnMS() && exists("$USERNAME")
185     let s:p4User = $USERNAME
186   elseif exists("$LOGNAME")
187     let s:p4User = $LOGNAME
188   elseif exists("$USERNAME") " Happens if you are on cygwin too.
189     let s:p4User = $USERNAME
190   else
191     let s:p4User = ''
192   endif
193 endif
194 if !exists('s:p4Port') || s:p4Port =~# s:EMPTY_STR
195   let s:p4Port = $P4PORT
196 endif
197 let s:p4Password = $P4PASSWD
198
199 let s:CM_RUN = 'run' | let s:CM_FILTER = 'filter' | let s:CM_DISPLAY = 'display'
200 let s:CM_PIPE = 'pipe'
201
202 let s:changesExpr  = "matchstr(getline(\".\"), '" .
203       \ '^Change \zs\d\+\ze ' . "')"
204 let s:branchesExpr = "matchstr(getline(\".\"), '" .
205       \ '^Branch \zs[^ ]\+\ze ' . "')"
206 let s:labelsExpr   = "matchstr(getline(\".\"), '" .
207       \ '^Label \zs[^ ]\+\ze ' . "')"
208 let s:clientsExpr  = "matchstr(getline(\".\"), '" .
209       \ '^Client \zs[^ ]\+\ze ' . "')"
210 let s:usersExpr    = "matchstr(getline(\".\"), '" .
211       \ '^[^ ]\+\ze <[^@>]\+@[^>]\+> ([^)]\+)' . "')"
212 let s:jobsExpr     = "matchstr(getline(\".\"), '" .
213       \ '^[^ ]\+\ze on ' . "')"
214 let s:depotsExpr   = "matchstr(getline(\".\"), '" .
215       \ '^Depot \zs[^ ]\+\ze ' . "')"
216 let s:describeExpr = 's:DescribeGetCurrentItem()'
217 let s:filelogExpr  = 's:GetCurrentDepotFile(line("."))'
218 let s:groupsExpr   = 'expand("<cword>")'
219
220 let s:fileBrowseExpr = 's:ConvertToLocalPath(s:GetCurrentDepotFile(line(".")))'
221 let s:openedExpr   = s:fileBrowseExpr
222 let s:filesExpr    = s:fileBrowseExpr
223 let s:haveExpr     = s:fileBrowseExpr
224 let s:integrateExpr = s:fileBrowseExpr
225 " Open in describe window should open the local file.
226 let s:describeOpenItemExpr = s:fileBrowseExpr
227
228 " If an explicit handler is defined, then it will override the default rule of
229 "   finding the command with the singular form.
230 let s:filelogItemHandler = "s:printHdlr"
231 let s:changesItemHandler = "s:changeHdlr"
232 let s:openedItemHandler = "s:OpenFile"
233 let s:describeItemHandler = "s:OpenFile"
234 let s:filesItemHandler = "s:OpenFile"
235 let s:haveItemHandler = "s:OpenFile"
236
237 " Define handlers for built-in commands. These have no arguments, they will
238 "   use the existing parsed command-line vars. Set s:errCode on errors.
239 let s:builtinCmdHandler{'vdiff'} = 's:VDiffHandler' 
240 let s:builtinCmdHandler{'vdiff2'} = 's:VDiff2Handler' 
241 let s:builtinCmdHandler{'exec'} = 's:ExecHandler' 
242
243 let s:vdiffCounter = 0
244
245 " A stack of contexts.
246 let s:p4Contexts = []
247
248 " Cache of client view mappings, with client name as the key.
249 let s:fromDepotMapping = {}
250 let s:toDepotMapping = {}
251
252 aug Perforce | aug END " Define autocommand group.
253 call genutils#AddToFCShellPre('perforce#FileChangedShell')
254
255 """ END: One-time initialization of some script variables }}}
256
257 """ END: Initializations }}}
258
259
260 """ BEGIN: Command specific functions {{{
261
262 function! s:printHdlr(scriptOrigin, outputType, ...)
263   let retVal = call('perforce#PFIF', [a:scriptOrigin + 1, a:outputType, 'print']
264         \ +a:000)
265
266   if s:StartBufSetup()
267     let undo = 0
268     " The first line printed by p4 for non-q operation causes vim to misjudge
269     " the filetype.
270     if getline(1) =~# '//[^#]\+#\d\+ - '
271       setlocal modifiable
272       let firstLine = getline(1)
273       silent! 1delete _
274     endif
275
276     set ft=
277     doautocmd filetypedetect BufNewFile
278     " If automatic detection doesn't work...
279     if &ft == ""
280       let &ft=s:GuessFileTypeForCurrentWindow()
281     endif
282
283     if exists('firstLine')
284       silent! 1put! =firstLine
285       setlocal nomodifiable
286     endif
287
288     call s:EndBufSetup()
289   endif
290   return retVal
291 endfunction
292
293 function! s:describeHdlr(scriptOrigin, outputType, ...)
294   if !a:scriptOrigin
295     call call('s:ParseOptionsIF', [1, line('$'), 1, a:outputType, 'describe']+a:000)
296   endif
297   " If -s doesn't exist, and user doesn't intent to see a diff, then let us
298   "   add -s option. In any case he can press enter on the <SHOW DIFFS> to see
299   "   it later.
300   if index(s:p4CmdOptions, '-s') == -1 &&
301         \ s:indexMatching(s:p4CmdOptions, '^-d.\+$') == -1
302     call add(s:p4CmdOptions, '-s')
303     let s:p4WinName = s:MakeWindowName() " Adjust window name.
304   endif
305
306   let retVal = perforce#PFIF(2, a:outputType, 'describe')
307   if s:StartBufSetup() && getline(1) !~# ' - no such changelist'
308     call s:SetupFileBrowse()
309     if index(s:p4CmdOptions, '-s') != -1
310       setlocal modifiable
311       silent! 2,$g/^Change \d\+ by \|\%$/
312             \ call append(line('.')-1, ['', "\t<SHOW DIFFS>", ''])
313       setlocal nomodifiable
314     else
315       call s:SetupDiff()
316     endif
317
318     call s:EndBufSetup()
319   endif
320   return retVal
321 endfunction
322
323 function! s:diffHdlr(scriptOrigin, outputType, ...)
324   if !a:scriptOrigin
325     call call('s:ParseOptionsIF', [1, line('$'), 1, a:outputType, 'diff']+a:000)
326   endif
327
328   " If a change number is specified in the diff, we need to handle it
329   "   ourselves, as p4 doesn't understand this.
330   let changeOptIdx = index(s:p4CmdOptions, '++c')
331   let changeNo = ''
332   if changeOptIdx != -1 " If a change no. is specified.
333     let changeNo = s:p4CmdOptions[changeOptIdx+1]
334     call s:PushP4Context()
335     try
336       call extend(s:p4Options, ['++T', '++N'], 0) " Testmode.
337       let retVal = perforce#PFIF(2, a:outputType, 'diff') " Opens window.
338       if s:errCode == 0
339         setlocal modifiable
340         exec '%PF ++f opened -c' changeNo
341       endif
342     finally
343       let cntxtStr = s:PopP4Context()
344     endtry
345   else
346     " Any + option is treated like a signal to run external diff.
347     let externalDiffOptExists = (s:indexMatching(s:p4CmdOptions, '^+\S\+$') != -1)
348     if externalDiffOptExists
349       if len(s:p4Arguments) > 1
350         return s:SyntaxError('External diff options can not be used with multiple files.')
351       endif
352       let needsPop = 0
353       try
354         let _p4Options = copy(s:p4Options)
355         call insert(s:p4Options, '++T', 0) " Testmode, just open the window.
356         let retVal = perforce#PFIF(2, 0, 'diff')
357         let s:p4Options = _p4Options
358         if s:errCode != 0
359           return
360         endif
361         call s:PushP4Context() | let needsPop = 1
362         PW print -q
363         if s:errCode == 0
364           setlocal modifiable
365           let fileName = s:ConvertToLocalPath(s:p4Arguments[0])
366           call s:PeekP4Context()
367           " Gather and process only external options.
368           " Sample:
369           " '-x +width=10 -du -y +U=20 -z -a -db +tabsize=4'
370           "   to
371           " '--width=10 -U 20 --tabsize=4'
372           let diffOpts = []
373           for opt in s:p4CmdOptions
374             if opt =~ '^+'
375               call add(diffOpts, substitute(opt, '^+\([^= ]\+\)=\(.*\)$',
376                     \ '\=(strlen(submatch(1)) > 1 ? '.
377                     \     '("--".submatch(1).'.
378                     \      '(submatch(2) != "" ? "=".submatch(2) : "")) : '.
379                     \     '("-".submatch(1).'.
380                     \      '(submatch(2) != "" ? " ".submatch(2) : "")))',
381                     \ 'g'))
382             endif
383           endfor
384           if getbufvar(bufnr('#'), '&ff') ==# 'dos'
385             setlocal ff=dos
386           endif
387           silent! exec '%!'.
388                 \ genutils#EscapeCommand('diff', diffOpts+['--', '-', fileName],
389                 \ '')
390           if v:shell_error > 1
391             call s:EchoMessage('Error executing external diff command. '.
392                   \ 'Verify that GNU (or a compatible) diff is in your path.',
393                   \ 'ERROR')
394             return ''
395           endif
396           call genutils#SilentSubstitute("\<CR>$", '%s///')
397           call genutils#SilentSubstitute('^--- -', '1s;;--- '.
398                 \ s:ConvertToDepotPath(fileName))
399           1
400         endif
401       finally
402         setlocal nomodifiable
403         if needsPop
404           call s:PopP4Context()
405         endif
406       endtry
407     else
408       let retVal = perforce#PFIF(2, exists('$P4DIFF') ? 5 : a:outputType, 'diff')
409     endif
410   endif
411
412   if s:StartBufSetup()
413     call s:SetupDiff()
414
415     if changeNo != '' && getline(1) !~# 'ile(s) not opened on this client\.'
416       setl modifiable
417       call genutils#SilentSubstitute('#.*', '%s///e')
418       call s:SetP4ContextVars(cntxtStr) " Restore original diff context.
419       call perforce#PFIF(1, 0, '-x', '-', '++f', '++n', 'diff')
420       setl nomodifiable
421     endif
422
423     call s:EndBufSetup()
424   endif
425   return retVal
426 endfunction
427
428 function! s:diff2Hdlr(scriptOrigin, outputType, ...)
429   if !a:scriptOrigin
430     call call('s:ParseOptionsIF', [1, line('$'), 1, a:outputType, 'diff2']+a:000)
431   endif
432
433   let s:p4Arguments = s:GetDiff2Args()
434
435   let retVal = perforce#PFIF(2, exists('$P4DIFF') ? 5 : a:outputType, 'diff2')
436   if s:StartBufSetup()
437     call s:SetupDiff()
438
439     call s:EndBufSetup()
440   endif
441   return retVal
442 endfunction
443
444 function! s:passwdHdlr(scriptOrigin, outputType, ...)
445   if !a:scriptOrigin
446     call call('s:ParseOptionsIF', [1, line('$'), 1, a:outputType, 'passwd']+a:000)
447   endif
448
449   let oldPasswd = ""
450   if index(s:p4CmdOptions, '-O') == -1
451     let oldPasswd = input('Enter old password: ')
452     " FIXME: Handle empty passwords.
453     call add(add(s:p4CmdOptions, '-O'), oldPasswd)
454   endif
455   let newPasswd = ""
456   if index(s:p4CmdOptions, '-P') == -1
457     while 1
458       let newPasswd = input('Enter new password: ')
459       if (input('Re-enter new password: ') != newPasswd)
460         call s:EchoMessage("Passwords don't match", 'Error')
461       else
462         " FIXME: Handle empty passwords.
463         call add(add(s:p4CmdOptions, '-P'), newPasswd)
464         break
465       endif
466     endwhile
467   endif
468   let retVal = perforce#PFIF(2, a:outputType, 'passwd')
469   return retVal
470 endfunction
471
472 " Only to avoid confirming for -n and -a options.
473 function! s:revertHdlr(scriptOrigin, outputType, ...)
474   if !a:scriptOrigin
475     call call('s:ParseOptionsIF', [1, line('$'), 1,
476           \ a:outputType, 'passwd']+a:000)
477   endif
478
479   if index(s:p4CmdOptions, '-n') != -1 || index(s:p4CmdOptions, '-a') != -1
480     call add(s:p4Options, '++y')
481   endif
482   let retVal = perforce#PFIF(2, a:outputType, 'revert')
483   return retVal
484 endfunction
485
486 function! s:changeHdlrImpl(outputType)
487   let _p4Arguments = s:p4Arguments
488   " If argument(s) is not a number...
489   if len(s:p4Arguments) != 0 && s:indexMatching(s:p4Arguments, '^\d\+$') == -1
490     let s:p4Arguments = [] " Let a new changelist be created.
491   endif
492   let retVal = perforce#PFIF(2, a:outputType, 'change')
493   let s:p4Arguments = _p4Arguments
494   if s:errCode == 0 && s:indexMatching(s:p4Arguments, '^\d\+$') == -1
495         \ && (s:StartBufSetup() || s:commandMode ==# s:CM_FILTER)
496     if len(s:p4Arguments) != 0
497       if search('^Files:\s*$', 'w') && line('.') != line('$')
498         +
499         call s:PushP4Context()
500         try
501           call call('perforce#PFrangeIF', [line("."), line("$"), 1, 0]+
502                 \ s:p4Options+['++f', 'opened', '-c', 'default']+
503                 \ s:p4Arguments)
504         finally
505           call s:PopP4Context()
506         endtry
507
508         if s:errCode == 0
509           call genutils#SilentSubstitute('^', '.,$s//\t/e')
510           call genutils#SilentSubstitute('#\d\+ - \(\S\+\) .*$',
511                 \ '.,$s//\t# \1/e')
512         endif
513       endif
514     endif
515
516     call s:EndBufSetup()
517     setl nomodified
518     if len(s:p4Arguments) != 0 && &cmdheight > 1
519       " The message about W and WQ must have gone by now.
520       redraw | call perforce#LastMessage()
521     endif
522   else
523     " Save the filelist in this changelist so that we can update their status
524     " later.
525     if search('Files:\s*$', 'w')
526       let b:p4OrgFilelist = getline(line('.')+1, line('$'))
527     endif
528   endif
529   return retVal
530 endfunction
531
532 function! s:changeHdlr(scriptOrigin, outputType, ...)
533   if !a:scriptOrigin
534     call call('s:ParseOptionsIF', [1, line('$'), 1, a:outputType, 'change']+a:000)
535   endif
536   let retVal = s:changeHdlrImpl(a:outputType)
537   if s:StartBufSetup()
538     command! -buffer -nargs=* PChangeSubmit :call call('<SID>W',
539           \ [0]+b:p4Options+['submit']+split(<q-args>, '\s'))
540
541     call s:EndBufSetup()
542   endif
543   return retVal
544 endfunction
545
546 " Create a template for submit.
547 function! s:submitHdlr(scriptOrigin, outputType, ...)
548   if !a:scriptOrigin
549     call call('s:ParseOptionsIF', [1, line('$'), 1, a:outputType, 'submit']+a:000)
550   endif
551
552   if index(s:p4CmdOptions, '-c') != -1
553     " Non-interactive.
554     let retVal = perforce#PFIF(2, a:outputType, 'submit')
555   else
556     call s:PushP4Context()
557     try
558       " This is done just to get the :W and :WQ commands defined properly and
559       " open the window with a proper name. The actual job is done by the call
560       " to s:changeHdlrImpl() which is then run in filter mode to avoid the
561       " side-effects (such as :W and :WQ getting overwritten etc.)
562       call extend(s:p4Options, ['++y', '++T'], 0) " Don't confirm, and testmode.
563       call perforce#PFIF(2, 0, 'submit')
564       if s:errCode == 0
565         call s:PeekP4Context()
566         let s:p4CmdOptions = [] " These must be specific to 'submit'.
567         let s:p4Command = 'change'
568         let s:commandMode = s:CM_FILTER | let s:filterRange = '.'
569         let retVal = s:changeHdlrImpl(a:outputType)
570         setlocal nomodified
571         if s:errCode != 0
572           return
573         endif
574        endif
575     finally
576       call s:PopP4Context()
577     endtry
578
579     if s:StartBufSetup()
580       command! -buffer -nargs=* PSubmitPostpone :call call('<SID>W',
581             \ [0]+b:p4Options+['change']+split(<q-args>, '\s'))
582       set ft=perforce " Just to get the cursor placement right.
583       call s:EndBufSetup()
584     endif
585
586     if s:errCode
587       call s:EchoMessage("Error creating submission template.", 'Error')
588     endif
589   endif
590   return s:errCode
591 endfunction
592
593 function! s:resolveHdlr(scriptOrigin, outputType, ...)
594   if !a:scriptOrigin
595     call call('s:ParseOptionsIF', [1, line('$'), 1, a:outputType, 'resolve']+a:000)
596   endif
597
598   if (s:indexMatching(s:p4CmdOptions, '^-a[fmsty]$') == -1) &&
599         \ (index(s:p4CmdOptions, '-n') == -1)
600     return s:SyntaxError("Interactive resolve not implemented (yet).")
601   endif
602   let retVal = perforce#PFIF(2, a:outputType, 'resolve')
603   return retVal
604 endfunction
605
606 function! s:filelogHdlr(scriptOrigin, outputType, ...)
607   let retVal = call('perforce#PFIF', [a:scriptOrigin + 1, a:outputType, 'filelog']+a:000)
608
609   if s:StartBufSetup()
610     " No meaning for delete.
611     silent! nunmap <buffer> D
612     silent! delcommand PItemDelete
613     command! -range -buffer -nargs=0 PFilelogDiff
614           \ :call s:FilelogDiff2(<line1>, <line2>)
615     vnoremap <silent> <buffer> D :PFilelogDiff<CR>
616     command! -buffer -nargs=0 PFilelogPrint :call perforce#PFIF(0, 0, 'print',
617           \ <SID>GetCurrentItem())
618     nnoremap <silent> <buffer> p :PFilelogPrint<CR>
619     command! -buffer -nargs=0 PFilelogSync :call <SID>FilelogSyncToCurrentItem()
620     nnoremap <silent> <buffer> S :PFilelogSync<CR>
621     command! -buffer -nargs=0 PFilelogDescribe
622           \ :call <SID>FilelogDescribeChange()
623     nnoremap <silent> <buffer> C :PFilelogDescribe<CR>
624
625     call s:EndBufSetup()
626   endif
627 endfunction
628
629 function! s:clientsHdlr(scriptOrigin, outputType, ...)
630   let retVal = call('perforce#PFIF', [a:scriptOrigin + 1, a:outputType, 'clients']+a:000)
631
632   if s:StartBufSetup()
633     command! -buffer -nargs=0 PClientsTemplate
634           \ :call perforce#PFIF(0, 0, '++A', 'client', '-t', <SID>GetCurrentItem())
635     nnoremap <silent> <buffer> P :PClientsTemplate<CR>
636
637     call s:EndBufSetup()
638   endif
639   return retVal
640 endfunction
641
642 function! s:changesHdlr(scriptOrigin, outputType, ...)
643   let retVal = call('perforce#PFIF', [a:scriptOrigin + 1, a:outputType, 'changes']+a:000)
644
645   if s:StartBufSetup()
646     command! -buffer -nargs=0 PItemDescribe
647           \ :call <SID>PChangesDescribeCurrentItem()
648     command! -buffer -nargs=0 PChangesSubmit
649           \ :call <SID>ChangesSubmitChangeList()
650     nnoremap <silent> <buffer> S :PChangesSubmit<CR>
651     command! -buffer -nargs=0 PChangesOpened
652           \ :if getline('.') =~# " \\*pending\\* '" |
653           \    call perforce#PFIF(1, 0, 'opened', '-c', <SID>GetCurrentItem()) |
654           \  endif
655     nnoremap <silent> <buffer> o :PChangesOpened<CR>
656     command! -buffer -nargs=0 PChangesDiff
657           \ :if getline('.') =~# " \\*pending\\* '" |
658           \    call perforce#PFIF(0, 0, 'diff', '++c', <SID>GetCurrentItem()) |
659           \  else |
660           \    call perforce#PFIF(0, 0, 'describe', (<SID>('DefaultDiffOptions')
661           \                 =~ '^\s*$' ? '-dd' : <SID>('DefaultDiffOptions')),
662           \                   <SID>GetCurrentItem()) |
663           \  endif
664     nnoremap <silent> <buffer> d :PChangesDiff<CR>
665     command! -buffer -nargs=0 PItemOpen
666           \ :if getline('.') =~# " \\*pending\\* '" |
667           \    call perforce#PFIF(0, 0, 'change', <SID>GetCurrentItem()) |
668           \  else |
669           \    call perforce#PFIF(0, 0, 'describe', '-dd', <SID>GetCurrentItem()) |
670           \  endif
671
672     call s:EndBufSetup()
673   endif
674 endfunction
675
676 function! s:labelsHdlr(scriptOrigin, outputType, ...)
677   let retVal = call('perforce#PFIF', [a:scriptOrigin + 1, a:outputType, 'labels']+a:000)
678
679   if s:StartBufSetup()
680     command! -buffer -nargs=0 PLabelsSyncClient
681           \ :call <SID>LabelsSyncClientToLabel()
682     nnoremap <silent> <buffer> S :PLabelsSyncClient<CR>
683     command! -buffer -nargs=0 PLabelsSyncLabel
684           \ :call <SID>LabelsSyncLabelToClient()
685     nnoremap <silent> <buffer> C :PLabelsSyncLabel<CR>
686     command! -buffer -nargs=0 PLabelsFiles :call perforce#PFIF(0, 0, '++n', 'files',
687           \ '//...@'. <SID>GetCurrentItem())
688     nnoremap <silent> <buffer> I :PLabelsFiles<CR>
689     command! -buffer -nargs=0 PLabelsTemplate :call perforce#PFIF(0, 0, '++A',
690           \ 'label', '-t', <SID>GetCurrentItem())
691     nnoremap <silent> <buffer> P :PLabelsTemplate<CR>
692
693     call s:EndBufSetup()
694   endif
695   return retVal
696 endfunction
697
698 function! s:helpHdlr(scriptOrigin, outputType, ...)
699   call genutils#SaveWindowSettings2("PerforceHelp", 1)
700   " If there is a help window already open, then we need to reuse it.
701   let helpWin = bufwinnr(s:helpWinName)
702   let retVal = call('perforce#PFIF', [a:scriptOrigin + 1, a:outputType, 'help']+a:000)
703
704   if s:StartBufSetup()
705     command! -buffer -nargs=0 PHelpSelect
706           \ :call perforce#PFIF(0, 0, 'help', expand("<cword>"))
707     nnoremap <silent> <buffer> <CR> :PHelpSelect<CR>
708     nnoremap <silent> <buffer> K :PHelpSelect<CR>
709     nnoremap <silent> <buffer> <2-LeftMouse> :PHelpSelect<CR>
710     call genutils#AddNotifyWindowClose(s:helpWinName, s:myScriptId .
711           \ "RestoreWindows")
712     if helpWin == -1 " Resize only when it was not already visible.
713       exec "resize " . 20
714     endif
715     redraw | echo
716           \ "Press <CR>/K/<2-LeftMouse> to drilldown on perforce help keywords."
717
718     call s:EndBufSetup()
719   endif
720   return retVal
721 endfunction
722
723 " Built-in command handlers {{{
724 function! s:VDiffHandler()
725   let nArgs = len(s:p4Arguments)
726   if nArgs > 2
727     return s:SyntaxError("vdiff: Too many arguments.")
728   endif
729
730   let firstFile = ''
731   let secondFile = ''
732   if nArgs == 2
733     let firstFile = s:p4Arguments[0]
734     let secondFile = s:p4Arguments[1]
735   elseif nArgs == 1
736     let secondFile = s:p4Arguments[0]
737   else
738     let secondFile = s:EscapeFileName(s:GetCurFileName())
739   endif
740   if firstFile == ''
741     let firstFile = s:ConvertToDepotPath(secondFile)
742   endif
743   call s:VDiffImpl(firstFile, secondFile, 0)
744 endfunction
745
746 function! s:VDiff2Handler()
747   if len(s:p4Arguments) > 2
748     return s:SyntaxError("vdiff2: Too many arguments")
749   endif
750
751   let s:p4Arguments = s:GetDiff2Args()
752   call s:VDiffImpl(s:p4Arguments[0], s:p4Arguments[1], 1)
753 endfunction
754
755 function! s:VDiffImpl(firstFile, secondFile, preferDepotPaths)
756   let firstFile = a:firstFile
757   let secondFile = a:secondFile
758
759   if a:preferDepotPaths || s:PathRefersToDepot(firstFile)
760     let firstFile = s:ConvertToDepotPath(firstFile)
761     let tempFile1 = s:MakeTempName(firstFile)
762   else
763     let tempFile1 = firstFile
764   endif
765   if a:preferDepotPaths || s:PathRefersToDepot(secondFile)
766     let secondFile = s:ConvertToDepotPath(secondFile)
767     let tempFile2 = s:MakeTempName(secondFile)
768   else
769     let tempFile2 = secondFile
770   endif
771   if firstFile =~# s:EMPTY_STR || secondFile =~# s:EMPTY_STR ||
772         \ (tempFile1 ==# tempFile2)
773     return s:SyntaxError("diff requires two distinct files as arguments.")
774   endif
775
776   let s:vdiffCounter = s:vdiffCounter + 1
777
778   if s:IsDepotPath(firstFile)
779     let s:p4Command = 'print'
780     let s:p4CmdOptions = ['-q']
781     let s:p4WinName = tempFile1
782     let s:p4Arguments = [firstFile]
783     call perforce#PFIF(2, 0, 'print')
784     if s:errCode != 0
785       return
786     endif
787   else
788     let v:errmsg = ''
789     silent! exec 'split' firstFile
790     if v:errmsg != ""
791       return s:ShowVimError("Error opening file: ".firstFile."\n".v:errmsg, '')
792     endif
793   endif
794   diffthis
795   let w:p4VDiffWindow = s:vdiffCounter
796   wincmd K
797
798   " CAUTION: If there is a buffer or window local value, then this will get
799   " overridden, but it is OK.
800   if exists('t:p4SplitCommand')
801     let _splitCommand = t:p4SplitCommand
802   endif
803   let t:p4SplitCommand = 'vsplit'
804   let _splitright = &splitright
805   set splitright
806   try
807     if s:IsDepotPath(secondFile)
808       let s:p4Command = 'print'
809       let s:p4CmdOptions = ['-q']
810       let s:p4WinName = tempFile2
811       let s:p4Arguments = [secondFile]
812       call perforce#PFIF(2, 0, 'print')
813       if s:errCode != 0
814         return
815       endif
816     else
817       let v:errmsg = ''
818       silent! exec 'vsplit' secondFile
819       if v:errmsg != ""
820         return s:ShowVimError("Error opening file: ".secondFile."\n".v:errmsg, '')
821       endif
822     endif
823   finally
824     if exists('_splitCommand')
825       let t:p4SplitCommand = _splitCommand
826     else
827       unlet t:p4SplitCommand
828     endif
829     let &splitright = _splitright
830   endtry
831   diffthis
832   let w:p4VDiffWindow = s:vdiffCounter
833   wincmd _
834 endfunction
835
836 " Returns a fileName in the temp directory that is unique for the branch and
837 "   revision specified in the fileName.
838 function! s:MakeTempName(filePath)
839   let depotPath = s:ConvertToDepotPath(a:filePath)
840   if depotPath =~# s:EMPTY_STR
841     return ''
842   endif
843   let tmpName = s:_('TempDir') . '/'
844   let branch = s:GetBranchName(depotPath)
845   if branch !~# s:EMPTY_STR
846     let tmpName = tmpName . branch . '-'
847   endif
848   let revSpec = s:GetRevisionSpecifier(depotPath)
849   if revSpec !~# s:EMPTY_STR
850     let tmpName = tmpName . substitute(strpart(revSpec, 1), '/', '_', 'g') . '-'
851   endif
852   return tmpName . fnamemodify(substitute(a:filePath, '\\*#\d\+$', '', ''),
853         \ ':t')
854 endfunction
855
856 function! s:ExecHandler()
857   if len(s:p4Arguments) != 0
858     echo join(s:p4Arguments, ' ')
859     let cmdHasBang = 0
860     if s:p4Arguments[0] =~# '^!'
861       let cmdHasBang = 1
862       " FIXME: Pipe itself needs to be escaped, and they could be chained.
863       let cmd = genutils#EscapeCommand(substitute(s:p4Arguments[0], '^!', '',
864             \ ''), s:p4Arguments[1:], s:p4Pipe)
865     else
866       let cmd = join(s:p4Arguments, ' ')
867     endif
868     let cmd = genutils#Escape(cmd, '#%!')
869     try
870       exec (cmdHasBang ? '!' : '').cmd
871     catch
872       let v:errmsg = substitute(v:exception, '^[^:]\+:', '', '')
873       call s:ShowVimError(v:errmsg, v:throwpoint)
874     endtry
875   endif
876 endfunction
877
878 " Built-in command handlers }}}
879
880 """ END: Command specific functions }}}
881
882
883 """ BEGIN: Helper functions {{{
884
885 " Open a file from an alternative codeline.
886 " If mode == 0, first file is opened and all other files are added to buffer
887 "   list.
888 " If mode == 1, the files are not really opened, the list is just returned.
889 " If mode == 2, it behaves the same as mode == 0, except that the file is
890 "   split opened.
891 " If there are no arguments passed, user is prompted to enter. He can then
892 "   enter a codeline followed by a list of filenames.
893 " If only one argument is passed, it is assumed to be the codeline and the
894 "   current filename is assumed (user is not prompted).
895 function! perforce#PFOpenAltFile(mode, ...) " {{{
896   let argList = copy(a:000)
897   if a:0 < 2
898     if a:0 == 0
899       " Prompt for codeline string (codeline optionally followed by filenames).
900       let codelineStr = s:PromptFor(0, s:_('UseGUIDialogs'),
901             \ "Enter the alternative codeline string: ", '')
902       if codelineStr =~# s:EMPTY_STR
903         return ""
904       endif
905       let argList = split(codelineStr, s:SPACE_AS_SEP)
906     endif
907     if len(argList) == 1
908       call add(argList, s:EscapeFileName(s:GetCurFileName()))
909     endif
910   endif
911
912   let altFileNames = call('s:PFGetAltFiles', ['']+argList)
913   if a:mode == 0 || a:mode == 2
914     let firstFile = 1
915     for altFileName in altFileNames
916       if firstFile
917         execute ((a:mode == 0) ? ":edit " : ":split ") . altFileName
918         let firstFile = 0
919       else
920         execute ":badd " . altFileName
921       endif
922     endfor
923   else
924     return join(altFileNames, ' ')
925   endif
926 endfunction " }}}
927
928 " Interactively change the port/client/user. {{{
929 function! perforce#SwitchPortClientUser()
930   let p4Port = s:PromptFor(0, s:_('UseGUIDialogs'), "Port: ", s:_('p4Port'))
931   let p4Client = s:PromptFor(0, s:_('UseGUIDialogs'), "Client: ", s:_('p4Client'))
932   let p4User = s:PromptFor(0, s:_('UseGUIDialogs'), "User: ", s:_('p4User'))
933   call perforce#PFSwitch(1, p4Port, p4Client, p4User)
934 endfunction
935
936 " No args: Print presets and prompt user to select a preset.
937 " Number: Select that numbered preset.
938 " port [client] [user]: Set the specified settings.
939 function! perforce#PFSwitch(updateClientRoot, ...)
940   if a:0 == 0 || match(a:1, '^\d\+$') == 0
941     let selPreset = ''
942     let presets = split(s:_('Presets'), ',')
943     if a:0 == 0
944       if len(presets) == 0
945         call s:EchoMessage("No presets to select from.", 'Error')
946         return
947       endif
948
949       let selPreset = genutils#PromptForElement(presets, -1,
950             \ "Select the setting: ", -1, s:_('UseGUIDialogs'), 1)
951     else
952       let index = a:1 + 0
953       if index >= len(presets)
954         call s:EchoMessage("Not that many presets.", 'Error')
955         return
956       endif
957       let selPreset = presets[index]
958     endif
959     if selPreset == ''
960       return
961     endif
962     let argList = split(selPreset, s:SPACE_AS_SEP)
963   else
964     if a:0 == 1
965       let argList = split(a:1, ' ')
966     else
967       let argList = a:000
968     endif
969   endif
970   call call('s:PSwitchHelper', [a:updateClientRoot]+argList)
971
972   " Loop through all the buffers and invalidate the filestatuses.
973   let lastBufNr = bufnr('$')
974   let i = 1
975   while i <= lastBufNr
976     if bufexists(i) && getbufvar(i, '&buftype') == ''
977       call s:ResetFileStatusForBuffer(i)
978     endif
979     let i = i + 1
980   endwhile
981 endfunction
982
983 function! s:PSwitchHelper(updateClientRoot, ...)
984   let p4Port = a:1
985   let p4Client = s:_('p4Client')
986   let p4User = s:_('p4User')
987   if a:0 > 1
988     let p4Client = a:2
989   endif
990   if a:0 > 2
991     let p4User = a:3
992   endif
993   if ! s:SetPortClientUser(p4Port, p4Client, p4User)
994     return
995   endif
996
997   if a:updateClientRoot
998     if s:p4Port !=# 'P4CONFIG'
999       call s:GetClientInfo()
1000     else
1001       let g:p4ClientRoot = '' " Since the client is chosen dynamically.
1002     endif
1003   endif
1004 endfunction
1005
1006 function! s:SetPortClientUser(port, client, user)
1007   if s:p4Port ==# a:port && s:p4Client ==# a:client && s:p4User ==# a:user
1008     return 0
1009   endif
1010
1011   let s:p4Port = a:port
1012   let s:p4Client = a:client
1013   let s:p4User = a:user
1014   let s:p4Password = ''
1015   return 1
1016 endfunction
1017
1018 function! perforce#PFSwitchComplete(ArgLead, CmdLine, CursorPos)
1019   return substitute(s:_('Presets'), ',', "\n", 'g')
1020 endfunction
1021 " port/client/user }}}
1022
1023 function! s:PHelpComplete(ArgLead, CmdLine, CursorPos)
1024   if s:p4KnownCmdsCompStr == ''
1025     let s:p4KnownCmdsCompStr = join(s:p4KnownCmds, "\n")
1026   endif
1027   return s:p4KnownCmdsCompStr.
1028           \ "simple\ncommands\nenvironment\nfiletypes\njobview\nrevisions\n".
1029           \ "usage\nviews\n"
1030 endfunction
1031  
1032 " Handler for opened command.
1033 function! s:OpenFile(scriptOrigin, outputType, fileName) " {{{
1034   if filereadable(a:fileName)
1035     if a:outputType == 0
1036       let curWin = winnr()
1037       let bufNr = genutils#FindBufferForName(a:fileName)
1038       let winnr = bufwinnr(bufNr)
1039       if winnr != -1
1040         exec winnr.'wincmd w'
1041       else
1042         wincmd p
1043       endif
1044       if curWin != winnr() && &previewwindow
1045         wincmd p " Don't use preview window.
1046       endif
1047       " Avoid loosing temporary buffers accidentally.
1048       if winnr() == curWin || getbufvar('%', '&bufhidden') != ''
1049         split
1050       endif
1051       if winbufnr(winnr()) != bufNr
1052         if bufNr != -1
1053           exec "buffer" bufNr | " Preserves cursor position.
1054         else
1055           exec "edit " . a:fileName
1056         endif
1057       endif
1058     else
1059       exec "pedit " . a:fileName
1060     endif
1061   else
1062     call perforce#PFIF(0, a:outputType, 'print', a:fileName)
1063   endif
1064 endfunction " }}}
1065
1066 function! s:DescribeGetCurrentItem() " {{{
1067   if getline(".") ==# "\t<SHOW DIFFS>"
1068     let [changeHdrLine, col] = searchpos('^Change \zs\d\+ by ', 'bnW')
1069     if changeHdrLine != 0
1070       let changeNo = matchstr(getline(changeHdrLine), '\d\+', col-1)
1071       let _modifiable = &l:modifiable
1072       try
1073         setlocal modifiable
1074         call genutils#SaveHardPosition('DescribeGetCurrentItem')
1075         exec changeHdrLine.',.PF ++f describe' s:_('DefaultDiffOptions') changeNo
1076         call genutils#RestoreHardPosition('DescribeGetCurrentItem')
1077         call genutils#ResetHardPosition('DescribeGetCurrentItem')
1078       finally
1079         let &l:modifiable = _modifiable
1080       endtry
1081       call s:SetupDiff()
1082     endif
1083     return ""
1084   endif
1085   return s:GetCurrentDepotFile(line('.'))
1086 endfunction " }}}
1087
1088 function! s:getCommandItemHandler(outputType, command, args) " {{{
1089   let itemHandler = ""
1090   if exists("s:{a:command}ItemHandler")
1091     let itemHandler = s:{a:command}ItemHandler
1092   elseif match(a:command, 'e\?s$') != -1
1093     let handlerCmd = substitute(a:command, 'e\?s$', '', '')
1094     if exists('*s:{handlerCmd}Hdlr')
1095       let itemHandler = 's:' . handlerCmd . 'Hdlr'
1096     else
1097       let itemHandler = 'perforce#PFIF'
1098     endif
1099   endif
1100   if itemHandler ==# 'perforce#PFIF'
1101     return "call perforce#PFIF(1, " . a:outputType . ", '" . handlerCmd . "', " .
1102           \ a:args . ")"
1103   elseif itemHandler !~# s:EMPTY_STR
1104     return 'call ' . itemHandler . '(0, ' . a:outputType . ', ' . a:args . ')'
1105   endif
1106   return itemHandler
1107 endfunction " }}}
1108
1109 function! s:OpenCurrentItem(outputType) " {{{
1110   let curItem = s:GetOpenItem(a:outputType)
1111   if curItem !~# s:EMPTY_STR
1112     let commandHandler = s:getCommandItemHandler(a:outputType, b:p4Command,
1113           \ "'" . curItem . "'")
1114     if commandHandler !~# s:EMPTY_STR
1115       exec commandHandler
1116     endif
1117   endif
1118 endfunction " }}}
1119
1120 function! s:GetCurrentItem() " {{{
1121   if exists("b:p4Command") && exists("s:{b:p4Command}Expr")
1122     exec "return " s:{b:p4Command}Expr
1123   endif
1124   return ""
1125 endfunction " }}}
1126
1127 function! s:GetOpenItem(outputType) " {{{
1128   " For non-preview open.
1129   if exists("b:p4Command") && a:outputType == 0 &&
1130         \ exists("s:{b:p4Command}OpenItemExpr")
1131     exec "return " s:{b:p4Command}OpenItemExpr
1132   endif
1133   return s:GetCurrentItem()
1134 endfunction " }}}
1135
1136 function! s:DeleteCurrentItem() " {{{
1137   let curItem = s:GetCurrentItem()
1138   if curItem !~# s:EMPTY_STR
1139     let answer = s:ConfirmMessage("Are you sure you want to delete " .
1140           \ curItem . "?", "&Yes\n&No", 2, "Question")
1141     if answer == 1
1142       let commandHandler = s:getCommandItemHandler(2, b:p4Command,
1143             \ "'-d', '" . curItem . "'")
1144       if commandHandler !~# s:EMPTY_STR
1145         exec commandHandler
1146       endif
1147       if v:shell_error == ""
1148         call perforce#PFRefreshActivePane()
1149       endif
1150     endif
1151   endif
1152 endfunction " }}}
1153
1154 function! s:LaunchCurrentFile() " {{{
1155   if g:p4FileLauncher =~# s:EMPTY_STR
1156     call s:ConfirmMessage("There was no launcher command configured to launch ".
1157           \ "this item, use g:p4FileLauncher to configure." , "OK", 1, "Error")
1158     return
1159   endif
1160   let curItem = s:GetCurrentItem()
1161   if curItem !~# s:EMPTY_STR
1162     exec 'silent! !'.g:p4FileLauncher curItem
1163   endif
1164 endfunction " }}}
1165
1166 function! s:FilelogDiff2(line1, line2) " {{{
1167   let line1 = a:line1
1168   let line2 = a:line2
1169   if line1 == line2
1170     if line2 < line("$")
1171       let line2 = line2 + 1
1172     elseif line1 > 1
1173       let line1 = line1 - 1
1174     else
1175       return
1176     endif
1177   endif
1178
1179   let file1 = s:GetCurrentDepotFile(line1)
1180   if file1 !~# s:EMPTY_STR
1181     let file2 = s:GetCurrentDepotFile(line2)
1182     if file2 !~# s:EMPTY_STR && file2 != file1
1183       " file2 will be older than file1.
1184       exec "call perforce#PFIF(0, 0, \"" . (s:_('UseVimDiff2') ? 'vdiff2' : 'diff2') .
1185             \ "\", file2, file1)"
1186     endif
1187   endif
1188 endfunction " }}}
1189
1190 function! s:FilelogSyncToCurrentItem() " {{{
1191   let curItem = s:GetCurrentItem()
1192   if curItem !~# s:EMPTY_STR
1193     let answer = s:ConfirmMessage("Do you want to sync to: " . curItem . " ?",
1194           \ "&Yes\n&No", 2, "Question")
1195     if answer == 1
1196       call perforce#PFIF(1, 2, 'sync', curItem)
1197     endif
1198   endif
1199 endfunction " }}}
1200
1201 function! s:ChangesSubmitChangeList() " {{{
1202   let curItem = s:GetCurrentItem()
1203   if curItem !~# s:EMPTY_STR
1204     let answer = s:ConfirmMessage("Do you want to submit change list: " .
1205           \ curItem . " ?", "&Yes\n&No", 2, "Question")
1206     if answer == 1
1207       call perforce#PFIF(0, 0, '++y', 'submit', '-c', curItem)
1208     endif
1209   endif
1210 endfunction " }}}
1211
1212 function! s:LabelsSyncClientToLabel() " {{{
1213   let curItem = s:GetCurrentItem()
1214   if curItem !~# s:EMPTY_STR
1215     let answer = s:ConfirmMessage("Do you want to sync client to the label: " .
1216           \ curItem . " ?", "&Yes\n&No", 2, "Question")
1217     if answer == 1
1218       let retVal = call('perforce#PFIF', [1, 1, 'sync',
1219             \ '//".s:_('Depot')."/...@'.curItem])
1220       return retVal
1221     endif
1222   endif
1223 endfunction " }}}
1224
1225 function! s:LabelsSyncLabelToClient() " {{{
1226   let curItem = s:GetCurrentItem()
1227   if curItem !~# s:EMPTY_STR
1228     let answer = s:ConfirmMessage("Do you want to sync label: " . curItem .
1229           \ " to client " . s:_('p4Client') . " ?", "&Yes\n&No", 2, "Question")
1230     if answer == 1
1231       let retVal = perforce#PFIF(1, 1, 'labelsync', '-l', curItem)
1232       return retVal
1233     endif
1234   endif
1235 endfunction " }}}
1236
1237 function! s:FilelogDescribeChange() " {{{
1238   let changeNo = matchstr(getline("."), ' change \zs\d\+\ze ')
1239   if changeNo !~# s:EMPTY_STR
1240     exec "call perforce#PFIF(0, 1, 'describe', changeNo)"
1241   endif
1242 endfunction " }}}
1243
1244 function! s:SetupFileBrowse() " {{{
1245   " For now, assume that a new window is created and we are in the new window.
1246   exec "setlocal includeexpr=P4IncludeExpr(v:fname)"
1247
1248   " No meaning for delete.
1249   silent! nunmap <buffer> D
1250   silent! delcommand PItemDelete
1251   command! -buffer -nargs=0 PFileDiff :call perforce#PFIF(0, 1, 'diff',
1252         \ <SID>GetCurrentDepotFile(line(".")))
1253   nnoremap <silent> <buffer> D :PFileDiff<CR>
1254   command! -buffer -nargs=0 PFileProps :call perforce#PFIF(1, 0, 'fstat', '-C',
1255         \ <SID>GetCurrentDepotFile(line(".")))
1256   nnoremap <silent> <buffer> P :PFileProps<CR>
1257   command! -buffer -nargs=0 PFileLog :call perforce#PFIF(1, 0, 'filelog',
1258         \ <SID>GetCurrentDepotFile(line(".")))
1259   command! -buffer -nargs=0 PFileEdit :call perforce#PFIF(1, 2, 'edit',
1260         \ <SID>GetCurrentItem())
1261   nnoremap <silent> <buffer> I :PFileEdit<CR>
1262   command! -buffer -bar -nargs=0 PFileRevert :call perforce#PFIF(1, 2, 'revert',
1263         \ <SID>GetCurrentItem())
1264   nnoremap <silent> <buffer> R :PFileRevert \| PFRefreshActivePane<CR>
1265   command! -buffer -nargs=0 PFilePrint
1266         \ :if getline('.') !~# '(\%(u\|ux\)binary)$' |
1267         \   call perforce#PFIF(0, 0, 'print',
1268         \   substitute(<SID>GetCurrentDepotFile(line('.')), '#[^#]\+$', '', '').
1269         \   '#'.
1270         \   ((getline(".") =~# '#\d\+ - delete change') ?
1271         \    matchstr(getline('.'), '#\zs\d\+\ze - ') - 1 :
1272         \    matchstr(getline('.'), '#\zs\d\+\ze - '))
1273         \   ) |
1274         \ else |
1275         \   echo 'PFilePrint: Binary file... ignored.' |
1276         \ endif
1277   nnoremap <silent> <buffer> p :PFilePrint<CR>
1278   command! -buffer -nargs=0 PFileGet :call perforce#PFIF(1, 2, 'sync',
1279         \ <SID>GetCurrentDepotFile(line(".")))
1280   command! -buffer -nargs=0 PFileSync :call perforce#PFIF(1, 2, 'sync',
1281         \ <SID>GetCurrentItem())
1282   nnoremap <silent> <buffer> S :PFileSync<CR>
1283   command! -buffer -nargs=0 PFileChange :call perforce#PFIF(0, 0, 'change', 
1284         \ <SID>GetCurrentChangeNumber(line(".")))
1285   nnoremap <silent> <buffer> C :PFileChange<CR>
1286   command! -buffer -nargs=0 PFileLaunch :call <SID>LaunchCurrentFile()
1287   nnoremap <silent> <buffer> A :PFileLaunch<CR>
1288 endfunction " }}}
1289
1290 function! s:SetupDiff() " {{{
1291   setlocal ft=diff
1292 endfunction " }}}
1293
1294 function! s:SetupSelectItem() " {{{
1295   nnoremap <buffer> <silent> D :PItemDelete<CR>
1296   nnoremap <buffer> <silent> O :PItemOpen<CR>
1297   nnoremap <buffer> <silent> <CR> :PItemDescribe<CR>
1298   nnoremap <buffer> <silent> <2-LeftMouse> :PItemDescribe<CR>
1299   command! -buffer -nargs=0 PItemDescribe :call <SID>OpenCurrentItem(1)
1300   command! -buffer -nargs=0 PItemOpen :call <SID>OpenCurrentItem(0)
1301   command! -buffer -nargs=0 PItemDelete :call <SID>DeleteCurrentItem()
1302   cnoremap <buffer> <C-X><C-I> <C-R>=<SID>GetCurrentItem()<CR>
1303 endfunction " }}}
1304
1305 function! s:RestoreWindows(dummy) " {{{
1306   call genutils#RestoreWindowSettings2("PerforceHelp")
1307 endfunction " }}}
1308
1309 function! s:NavigateBack() " {{{
1310   call s:Navigate('u')
1311   if line('$') == 1 && getline(1) == ''
1312     call s:NavigateForward()
1313   endif
1314 endfunction " }}}
1315
1316 function! s:NavigateForward() " {{{
1317   call s:Navigate("\<C-R>")
1318 endfunction " }}}
1319
1320 function! s:Navigate(key) " {{{
1321   let _modifiable = &l:modifiable
1322   try
1323     setlocal modifiable
1324     " Use built-in markers as Vim takes care of remembering and restoring them
1325     "   during the undo/redo.
1326     normal! mt
1327
1328     silent! exec "normal" a:key
1329
1330     if line("'t") > 0 && line("'t") <= line('$')
1331       normal! `t
1332     endif
1333   finally
1334     let &l:modifiable = _modifiable
1335   endtry
1336 endfunction " }}}
1337
1338 function! s:GetCurrentChangeNumber(lineNo) " {{{
1339   let line = getline(a:lineNo)
1340   let changeNo = matchstr(line, ' - \S\+ change \zs\S\+\ze (')
1341   if changeNo ==# 'default'
1342     let changeNo = ''
1343   endif
1344   return changeNo
1345 endfunction " }}}
1346
1347 function! s:PChangesDescribeCurrentItem() " {{{
1348   let currentChangeNo = s:GetCurrentItem()
1349   if currentChangeNo !~# s:EMPTY_STR
1350     call perforce#PFIF(0, 1, 'describe', '-s', currentChangeNo)
1351   endif
1352 endfunction " }}}
1353
1354 " {{{
1355 function! perforce#PFSettings(...)
1356   if s:_('SortSettings ')
1357     if exists("s:sortedSettings")
1358       let settings = s:sortedSettings
1359     else
1360       let settings = sort(s:settings)
1361       let s:sortedSettings = settings
1362     endif
1363   else
1364     let settings = s:settings
1365   endif
1366   if a:0 > 0
1367     let selectedSetting = a:1
1368   else
1369     let selectedSetting = genutils#PromptForElement(settings, -1,
1370           \ "Select the setting: ", -1, 0, 3)
1371   endif
1372   if selectedSetting !~# s:EMPTY_STR
1373     let oldVal = s:_(selectedSetting)
1374     if a:0 > 1
1375       let newVal = a:2
1376       echo 'Current value for' selectedSetting.': "'.oldVal.'" New value: "'.
1377             \ newVal.'"'
1378     else
1379       let newVal = input('Current value for ' . selectedSetting . ' is: ' .
1380             \ oldVal . "\nEnter new value: ", oldVal)
1381     endif
1382     if newVal != oldVal
1383       let g:p4{selectedSetting} = newVal
1384       call perforce#Initialize(1)
1385     endif
1386   endif
1387 endfunction
1388
1389 function! perforce#PFSettingsComplete(ArgLead, CmdLine, CursorPos)
1390   if s:settingsCompStr == ''
1391     let s:settingsCompStr = join(s:settings, "\n")
1392   endif
1393   return s:settingsCompStr
1394 endfunction
1395 " }}}
1396
1397 function! s:MakeRevStr(ver) " {{{
1398   let verStr = ''
1399   if a:ver =~# '^[#@&]'
1400     let verStr = a:ver
1401   elseif a:ver =~# '^[-+]\?\d\+\>\|^none\>\|^head\>\|^have\>'
1402     let verStr = '#' . a:ver
1403   elseif a:ver !~# s:EMPTY_STR
1404     let verStr = '@' . a:ver
1405   endif
1406   return verStr
1407 endfunction " }}}
1408
1409 function! s:GetBranchName(fileName) " {{{
1410   if s:IsFileUnderDepot(a:fileName)
1411     " TODO: Need to run where command at this phase.
1412   elseif stridx(a:fileName, '//') == 0
1413     return matchstr(a:fileName, '^//[^/]\+/\zs[^/]\+\ze')
1414   else
1415     return ''
1416   endif
1417 endfunction " }}}
1418
1419 function! s:GetDiff2Args()
1420   let p4Arguments = s:p4Arguments
1421   if len(p4Arguments) < 2
1422     if len(p4Arguments) == 0
1423       let file = s:EscapeFileName(s:GetCurFileName())
1424     else
1425       let file = p4Arguments[0]
1426     endif
1427     let ver1 = s:PromptFor(0, s:_('UseGUIDialogs'), "Version1? ", '')
1428     let ver2 = s:PromptFor(0, s:_('UseGUIDialogs'), "Version2? ", '')
1429     let p4Arguments = [file.s:MakeRevStr(ver1), file.s:MakeRevStr(ver2)]
1430   endif
1431   return p4Arguments
1432 endfunction
1433
1434 function! perforce#ToggleCheckOutPrompt(interactive)
1435   aug P4CheckOut
1436   au!
1437   if g:p4PromptToCheckout
1438     let g:p4PromptToCheckout = 0
1439   else
1440     let g:p4PromptToCheckout = 1
1441     au FileChangedRO * nested :call <SID>CheckOutFile()
1442   endif
1443   aug END
1444   if a:interactive
1445     echomsg "PromptToCheckout is now " . ((g:p4PromptToCheckout) ? "enabled." :
1446           \ "disabled.")
1447   endif
1448 endfunction
1449
1450 function! perforce#PFDiffOff(diffCounter)
1451   " Cycle through all windows and turn off diff options for the specified diff
1452   " run, or all, if none specified.
1453   let curWinNr = winnr()
1454   let eventignore = &eventignore
1455   set eventignore=all
1456   try
1457     let i = 1
1458     while winbufnr(i) != -1
1459       try
1460         exec i 'wincmd w'
1461         if ! exists('w:p4VDiffWindow')
1462           continue
1463         endif
1464
1465         if a:diffCounter == -1 || w:p4VDiffWindow == a:diffCounter
1466           call genutils#CleanDiffOptions()
1467           unlet w:p4VDiffWindow
1468         endif
1469       finally
1470         let i = i + 1
1471       endtry
1472     endwhile
1473   finally
1474     " Return to the original window.
1475     exec curWinNr 'wincmd w'
1476     let &eventignore = eventignore
1477   endtry
1478 endfunction
1479 """ END: Helper functions }}}
1480
1481
1482 """ BEGIN: Middleware functions {{{
1483
1484 " Filter contents through p4.
1485 function! perforce#PW(fline, lline, scriptOrigin, ...) range
1486   if a:scriptOrigin != 2
1487     call call('s:ParseOptions', [a:fline, a:lline, 0, '++f'] + a:000)
1488   else
1489     let s:commandMode = s:CM_FILTER
1490   endif
1491   setlocal modifiable
1492   let retVal = perforce#PFIF(2, 5, s:p4Command)
1493   return retVal
1494 endfunction
1495
1496 " Generate raw output into a new window.
1497 function! perforce#PFRaw(...)
1498   call call('s:ParseOptions', [1, line('$'), 0] + a:000)
1499
1500   let retVal = s:PFImpl(1, 0)
1501   return retVal
1502 endfunction
1503
1504 function! s:W(quitWhenDone, commandName, ...)
1505   call call('s:ParseOptionsIF', [1, line('$'), 0, 5, a:commandName]+a:000)
1506   if index(s:p4CmdOptions, '-i') == -1
1507     call insert(s:p4CmdOptions, '-i', 0)
1508   endif
1509   let retVal = perforce#PW(1, line('$'), 2)
1510   if s:errCode == 0
1511     setl nomodified
1512     if a:quitWhenDone
1513       close
1514     else
1515       if s:p4Command ==# 'change' || s:p4Command ==# 'submit'
1516         " Change number fixed, or files added/removed.
1517         if s:FixChangeNo() || search('^Change \d\+ updated, ', 'w')
1518           silent! undo
1519           if search('^Files:\s*$', 'w')
1520             let b:p4NewFilelist = getline(line('.')+1, line('$'))
1521             if !exists('b:p4OrgFilelist')
1522               let filelist = b:p4NewFilelist
1523             else
1524               " Find outersection.
1525               let filelist = copy(b:p4NewFilelist)
1526               call filter(filelist, 'index(b:p4OrgFilelist, v:val)==-1')
1527               call extend(filelist, filter(copy(b:p4OrgFilelist), 'index(b:p4NewFilelist, v:val)==-1'))
1528             endif
1529             let files = map(filelist, 's:ConvertToLocalPath(v:val)')
1530             call s:ResetFileStatusForFiles(files)
1531           endif
1532           silent! redo
1533         endif
1534       endif
1535     endif
1536   else
1537     call s:FixChangeNo()
1538   endif
1539 endfunction
1540
1541 function! s:FixChangeNo()
1542   if search('^Change \d\+ created', 'w') ||
1543         \ search('^Change \d\+ renamed change \d\+ and submitted', 'w')
1544     let newChangeNo = matchstr(getline('.'), '\d\+\ze\%(created\|and\)')
1545     let _undolevels=&undolevels
1546     try
1547       let allLines = getline(1, line('$'))
1548       silent! undo
1549       " Make the below changes such a way that they can't be undo. This in a
1550       "   way, forces Vim to create an undo point, so that user can later
1551       "   undo and see these changes, with proper change number and status
1552       "   in place. This has the side effect of loosing the previous undo
1553       "   history, which can be considered desirable, as otherwise the user
1554       "   can undo this change and back to the new state.
1555       set undolevels=-1
1556       if search("^Change:\t\%(new\|\d\+\)$")
1557         silent! keepjumps call setline('.', "Change:\t" . newChangeNo)
1558         " If no Date is present (happens for new changelists)
1559         if !search("^Date:\t", 'w')
1560           call append('.', ['', "Date:\t".strftime('%Y/%m/%d %H:%M:%S')])
1561         endif
1562       endif
1563       if search("^Status:\tnew$")
1564         silent! keepjumps call setline('.', "Status:\tpending")
1565       endif
1566       setl nomodified
1567       let &undolevels=_undolevels
1568       silent! 0,$delete _
1569       call setline(1, allLines)
1570       setl nomodified
1571       call s:PFSetupForSpec()
1572     finally
1573       let &undolevels=_undolevels
1574     endtry
1575     let b:p4Command = 'change'
1576     let b:p4CmdOptions = ['-i']
1577     return 1
1578   else
1579     return 0
1580   endif
1581 endfunction
1582
1583 function! s:ParseOptionsIF(fline, lline, scriptOrigin, outputType, commandName,
1584       \ ...) " range
1585   " There are multiple possibilities here:
1586   "   - scriptOrigin, in which case the commandName contains the name of the
1587   "     command, but the varArgs also may contain it.
1588   "   - commandOrigin, in which case the commandName may actually be the
1589   "     name of the command, or it may be the first argument to p4 itself, in
1590   "     any case we will let p4 handle the error cases.
1591   if index(s:allCommands, a:commandName) != -1 && a:scriptOrigin
1592     call call('s:ParseOptions', [a:fline, a:lline, a:outputType] + a:000)
1593     " Add a:commandName only if it doesn't already exist in the var args.
1594     " Handles cases like "PF help submit" and "PF -c <client> change changeno#",
1595     "   where the commandName need not be at the starting and there could be
1596     "   more than one valid commandNames (help and submit).
1597     if s:p4Command != a:commandName
1598       call call('s:ParseOptions',
1599             \ [a:fline, a:lline, a:outputType, a:commandName] + a:000)
1600     endif
1601   else
1602     call call('s:ParseOptions',
1603           \ [a:fline, a:lline, a:outputType, a:commandName] + a:000)
1604   endif
1605 endfunction
1606
1607 " PFIF {{{
1608 " The commandName may not be the perforce command when it is not of script
1609 "   origin (called directly from a command), but it should be always command
1610 "   name, when it is script origin.
1611 " scriptOrigin: An integer indicating the origin of the call. 
1612 "   0 - Originated directly from the user, so should redirect to the specific
1613 "       command handler (if exists), after some basic processing.
1614 "   1 - Originated from the script, continue with the full processing (makes
1615 "       difference in some command parsing).
1616 "   2 - Same as 1 but, avoid processing arguments (they are already processing
1617 "       by the caller).
1618 function! perforce#PFIF(scriptOrigin, outputType, commandName, ...)
1619   return call('perforce#PFrangeIF', [1, line('$'), a:scriptOrigin, a:outputType,
1620         \ a:commandName]+a:000)
1621 endfunction
1622
1623 function! perforce#PFrangeIF(fline, lline, scriptOrigin, outputType,
1624       \ commandName, ...)
1625   if a:scriptOrigin != 2
1626     call call('s:ParseOptionsIF', [a:fline, a:lline,
1627           \ a:scriptOrigin, a:outputType, a:commandName]+a:000)
1628   endif
1629   if ! a:scriptOrigin
1630     " Save a copy of the arguments so that the refresh can invoke this exactly
1631     " as it was before.
1632     let s:userArgs = [a:fline, a:lline, a:scriptOrigin, a:outputType, a:commandName]
1633           \ +a:000
1634   endif
1635
1636
1637   let outputOptIdx = index(s:p4Options, '++o')
1638   if outputOptIdx != -1
1639     " Get the argument followed by ++o.
1640     let s:outputType = get(s:p4Options, outputIdx + 1) + 0
1641   endif
1642   " If this command prefers a dialog output, then just take care of
1643   "   it right here.
1644   if index(s:dlgOutputCmds, s:p4Command) != -1
1645     let s:outputType = 2
1646   endif
1647   if ! a:scriptOrigin
1648     if exists('*s:{s:p4Command}Hdlr')
1649       return s:{s:p4Command}Hdlr(1, s:outputType, a:commandName)
1650     endif
1651   endif
1652  
1653   let modifyWindowName = 0
1654   let dontProcess = (index(s:p4Options, '++n') != -1)
1655   let noDefaultArg = (index(s:p4Options, '++N') != -1)
1656   " If there is a confirm message for this command, then first prompt user.
1657   let dontConfirm = (index(s:p4Options, '++y') != -1)
1658   if exists('s:confirmMsgs{s:p4Command}') && ! dontConfirm
1659     let option = s:ConfirmMessage(s:confirmMsgs{s:p4Command}, "&Yes\n&No", 2,
1660           \ "Question")
1661     if option == 2
1662       let s:errCode = 2
1663       return ''
1664     endif
1665   endif
1666
1667   if index(s:limitListCmds, s:p4Command) != -1 &&
1668         \ index(s:p4CmdOptions, '-m') == -1 && s:_('DefaultListSize') > -1
1669     call extend(s:p4CmdOptions, ['-m', s:_('DefaultListSize')], 0)
1670     let modifyWindowName = 1
1671   endif
1672   if index(s:diffCmds, s:p4Command) != -1 &&
1673         \ s:indexMatching(s:p4CmdOptions, '^-d.*$') == -1
1674         \ && s:_('DefaultDiffOptions') !~# s:EMPTY_STR
1675     " FIXME: Avoid split().
1676     call extend(s:p4CmdOptions, split(s:_('DefaultDiffOptions'), '\s'), 0)
1677     let modifyWindowName = 1
1678   endif
1679
1680   " Process p4Arguments, unless explicitly not requested to do so, or the '-x'
1681   "   option to read arguments from a file is given.
1682   let unprotectedSpecPat = genutils#CrUnProtectedCharsPattern('&#@')
1683   if ! dontProcess && ! noDefaultArg && len(s:p4Arguments) == 0 &&
1684         \ index(s:p4Options, '-x') == -1
1685     if (index(s:askUserCmds, s:p4Command) != -1 &&
1686           \ index(s:p4CmdOptions, '-i') == -1) ||
1687           \ index(s:p4Options, '++A') != -1
1688       if index(s:genericPromptCmds, s:p4Command) != -1
1689         let prompt = 'Enter arguments for ' . s:p4Command . ': '
1690       else
1691         let prompt = "Enter the " . s:p4Command . " name: "
1692       endif
1693       let additionalArg = s:PromptFor(0, s:_('UseGUIDialogs'), prompt, '')
1694       if additionalArg =~# s:EMPTY_STR
1695         if index(s:genericPromptCmds, s:p4Command) != -1
1696           call s:EchoMessage('Arguments required for '. s:p4Command, 'Error')
1697         else
1698           call s:EchoMessage(substitute(s:p4Command, "^.", '\U&', '') .
1699                 \ " name required.", 'Error')
1700         endif
1701         let s:errCode = 2
1702         return ''
1703       endif
1704       let s:p4Arguments = [additionalArg]
1705     elseif ! dontProcess &&
1706           \ index(s:curFileNotDefCmds, s:p4Command) == -1 &&
1707           \ index(s:nofileArgsCmds, s:p4Command) == -1
1708       let s:p4Arguments = [s:EscapeFileName(s:GetCurFileName())]
1709       let modifyWindowName = 1
1710     endif
1711   elseif ! dontProcess && s:indexMatching(s:p4Arguments, unprotectedSpecPat) != -1
1712     let branchModifierSpecified = 0
1713     let unprotectedAmp = genutils#CrUnProtectedCharsPattern('&')
1714     if s:indexMatching(s:p4Arguments, unprotectedAmp) != -1
1715       let branchModifierSpecified = 1
1716       " CAUTION: We make sure the view mappings are generated before
1717       "   s:PFGetAltFiles() gets invoked, otherwise the call results in a
1718       "   recursive |sub-replace-special| and corrupts the mappings.
1719       call s:CondUpdateViewMappings()
1720     endif
1721
1722     " This is like running substitute(v:val, 'pat', '\=expr', 'g') on each
1723     "   element.
1724     " Pattern is (the start of line or series of non-space chars) followed by
1725     "   an unprotected [#@&] with a revision/codeline specifier.
1726     " Expression is a concatenation of each of:
1727     "   (<no filename specified>?<current file>:<specified file>)
1728     "   <revision specifier>
1729     "   (<offset specified>?<adjusted revision>:<specified revision>)
1730     call map(s:p4Arguments, "substitute(v:val, '".
1731           \ '\(^\|\%(\S\)\+\)\('.unprotectedSpecPat.'\)\%(\2\)\@!\([-+]\?\d\+\|\S\+\)'."', ".
1732           \ "'\\=s:ExpandRevision(submatch(1), submatch(2), submatch(3))', 'g')")
1733     if s:errCode != 0
1734       return ''
1735     endif
1736
1737     if branchModifierSpecified
1738       call map(s:p4Arguments, 
1739             \ '(v:val =~ unprotectedAmp ? s:ApplyBranchSpecs(v:val, unprotectedAmp) : v:val)')
1740     endif
1741     " Unescape them, as user is required to escape them to avoid the above
1742     " processing.
1743     call map(s:p4Arguments, "genutils#UnEscape(v:val, '&@')")
1744
1745     let modifyWindowName = 1
1746   endif
1747
1748   let testMode = 0
1749   if index(s:p4Options, '++T') != -1
1750     let testMode = 1 " Dry run, opens the window.
1751   elseif index(s:p4Options, '++D') != -1
1752     let testMode = 2 " Debug. Opens the window and displays the command.
1753   endif
1754
1755   let oldOptLen = len(s:p4Options)
1756   " Remove all the built-in options.
1757   call filter(s:p4Options, "v:val !~ '".'++\S\+\%(\s\+[^-+]\+\|\s\+\)\?'."'")
1758   if len(s:p4Options) != oldOptLen
1759     let modifyWindowName = 1
1760   endif
1761   if index(s:diffCmds, s:p4Command) != -1
1762     " Remove the dummy option, if exists (see |perforce-default-diff-format|).
1763     call filter(s:p4CmdOptions, 'v:val != "-d"')
1764     let modifyWindowName = 1
1765   endif
1766
1767   if s:p4Command ==# 'help'
1768     " Use simple window name for all the help commands.
1769     let s:p4WinName = s:helpWinName
1770   elseif modifyWindowName
1771     let s:p4WinName = s:MakeWindowName() 
1772   endif
1773
1774   " If the command is a built-in command, then don't pass it to external p4.
1775   if exists('s:builtinCmdHandler{s:p4Command}')
1776     let s:errCode = 0
1777     return {s:builtinCmdHandler{s:p4Command}}()
1778   endif
1779
1780   let specMode = 0
1781   if index(s:specCmds, s:p4Command) != -1
1782     if index(s:p4CmdOptions, '-d') == -1
1783           \ && index(s:p4CmdOptions, '-o') == -1
1784           \ && index(s:noOutputCmds, s:p4Command) == -1
1785       call add(s:p4CmdOptions, '-o')
1786     endif
1787
1788     " Go into specification mode only if the user intends to edit the output.
1789     if ((s:p4Command ==# 'submit' && index(s:p4CmdOptions, '-c') == -1) ||
1790           \ (index(s:specOnlyCmds, s:p4Command) == -1 &&
1791           \  index(s:p4CmdOptions, '-d') == -1)) &&
1792           \ s:outputType == 0
1793       let specMode = 1
1794     endif
1795   endif
1796   
1797   let navigateCmd = 0
1798   if index(s:navigateCmds, s:p4Command) != -1
1799     let navigateCmd = 1
1800   endif
1801
1802   let retryCount = 0
1803   let retVal = ''
1804   " FIXME: When is "not clearing" (value 2) useful at all?
1805   let clearBuffer = (testMode != 2 ? ! navigateCmd : 2)
1806   " CAUTION: This is like a do..while loop, but not exactly the same, be
1807   " careful using continue, the counter will not get incremented.
1808   while 1
1809     let retVal = s:PFImpl(clearBuffer, testMode)
1810
1811     " Everything else in this loop is for password support.
1812     if s:errCode == 0
1813       break
1814     else
1815       if retVal =~# s:EMPTY_STR
1816         let retVal = getline(1)
1817       endif
1818       " FIXME: Works only with English as the language.
1819       if retVal =~# 'Perforce password (P4PASSWD) invalid or unset.'
1820         let p4Password = inputsecret("Password required for user " .
1821               \ s:_('p4User') . ": ", s:p4Password)
1822         if p4Password ==# s:p4Password
1823           break
1824         endif
1825         let s:p4Password = p4Password
1826       else
1827         break
1828       endif
1829     endif
1830     let retryCount = retryCount + 1
1831     if retryCount > 2
1832       break
1833     endif
1834   endwhile
1835
1836   if s:errCode == 0 && index(s:autoreadCmds, s:p4Command) != -1
1837     call s:ResetFileStatusForFiles(s:p4Arguments)
1838   endif
1839
1840   if s:errCode != 0
1841     return retVal
1842   endif
1843
1844   call s:SetupWindow(specMode)
1845
1846   return retVal
1847 endfunction
1848
1849 function! s:SetupWindow(specMode)
1850   if s:StartBufSetup()
1851     " If this command has a handler for the individual items, then enable the
1852     " item selection commands.
1853     if s:getCommandItemHandler(0, s:p4Command, '') !~# s:EMPTY_STR
1854       call s:SetupSelectItem()
1855     endif
1856
1857     if index(s:ftNotPerforceCmds, s:p4Command) == -1
1858       setlocal ft=perforce
1859     endif
1860
1861     if index(s:filelistCmds, s:p4Command) != -1
1862       call s:SetupFileBrowse()
1863     endif
1864
1865     if s:NewWindowCreated()
1866       if a:specMode
1867         " It is not possible to have an s:p4Command which is in s:allCommands
1868         "         and still not be the actual intended command.
1869         command! -buffer -nargs=* W :call call('<SID>W',
1870               \ [0]+b:p4Options+[b:p4Command]+b:p4CmdOptions+split(<q-args>,
1871               \ '\s'))
1872         command! -buffer -nargs=* WQ :call call('<SID>W',
1873               \ [1]+b:p4Options+[b:p4Command]+b:p4CmdOptions+split(<q-args>,
1874               \ '\s'))
1875         call s:EchoMessage("When done, save " . s:p4Command .
1876               \ " spec by using :W or :WQ command. Undo on errors.", 'None')
1877         call s:PFSetupForSpec()
1878       else " Define q to quit the read-only perforce windows (David Fishburn)
1879         nnoremap <buffer> q <C-W>q
1880       endif
1881     endif
1882
1883     if index(s:navigateCmds, s:p4Command) != -1
1884       nnoremap <silent> <buffer> <C-O> :call <SID>NavigateBack()<CR>
1885       nnoremap <silent> <buffer> <BS> :call <SID>NavigateBack()<CR>
1886       nnoremap <silent> <buffer> <Tab> :call <SID>NavigateForward()<CR>
1887     endif
1888
1889     call s:EndBufSetup()
1890   endif
1891 endfunction
1892
1893 function! s:ExpandRevision(fName, revType, revSpec)
1894   return (a:fName==''?s:EscapeFileName(s:GetCurFileName()):a:fName).
1895         \ a:revType.
1896         \ (a:revSpec=~'^[-+]'?s:AdjustRevision(a:fName, a:revSpec):a:revSpec)
1897 endfunction
1898
1899 function! s:ApplyBranchSpecs(arg, unprotectedAmp)
1900   " The first arg will be filename, followed by multiple specifiers.
1901   let toks = split(a:arg, a:unprotectedAmp)
1902   " FIXME: Handle "&" in the filename.
1903   " FIXME: Handle other revision specifiers occuring in any order
1904   " (e.g., <file>&branch#3).
1905   " Ex: c:/dev/docs/Triage/Tools/Machine\ Configurations\ &\ Codeline\ Requirements.xls
1906   if len(toks) == 0 || toks[0] == ''
1907     return a:arg
1908   endif
1909   let fname = remove(toks, 0)
1910   " Reduce filename according to the branch tokens.
1911   for altBranch in toks
1912     let fname = s:PFGetAltFiles("&", altBranch, fname)[0]
1913   endfor
1914   return fname
1915 endfunction
1916
1917 function! perforce#PFComplete(ArgLead, CmdLine, CursorPos)
1918   if s:p4KnownCmdsCompStr == ''
1919     let s:p4KnownCmdsCompStr = join(s:p4KnownCmds, "\n")
1920   endif
1921   if a:CmdLine =~ '^\s*P[FW] '
1922     let argStr = strpart(a:CmdLine, matchend(a:CmdLine, '^\s*PF '))
1923     let s:p4Command = ''
1924     if argStr !~# s:EMPTY_STR
1925       if exists('g:p4EnableUserFileExpand')
1926         let _p4EnableUserFileExpand = g:p4EnableUserFileExpand
1927       endif
1928       try
1929         " WORKAROUND for :redir broken, when called from here.
1930         let g:p4EnableUserFileExpand = 0
1931         exec 'call s:ParseOptionsIF(-1, -1, 0, 0, ' .
1932               \ genutils#CreateArgString(argStr, s:SPACE_AS_SEP).')'
1933       finally
1934         if exists('_p4EnableUserFileExpand')
1935           let g:p4EnableUserFileExpand = _p4EnableUserFileExpand
1936         else
1937           unlet g:p4EnableUserFileExpand
1938         endif
1939       endtry
1940     endif
1941     if s:p4Command ==# '' || s:p4Command ==# a:ArgLead
1942       return s:p4KnownCmdsCompStr."\n".join(s:builtinCmds, "\n")
1943     endif
1944   else
1945     let userCmd = substitute(a:CmdLine, '^\s*P\(.\)\(\w*\).*', '\l\1\2', '')
1946     if strlen(userCmd) < 3
1947       if !has_key(s:shortCmdMap, userCmd)
1948         throw "Perforce internal error: no map found for short command: ".
1949               \ userCmd
1950       endif
1951       let userCmd = s:shortCmdMap[userCmd]
1952     endif
1953     let s:p4Command = userCmd
1954   endif
1955   if s:p4Command ==# 'help'
1956     return s:PHelpComplete(a:ArgLead, a:CmdLine, a:CursorPos)
1957   endif
1958   if index(s:nofileArgsCmds, s:p4Command) != -1
1959     return ''
1960   endif
1961
1962   " FIXME: Can't set command-line from user function.
1963   "let argLead = genutils#UserFileExpand(a:ArgLead)
1964   "if argLead !=# a:ArgLead
1965   "  let cmdLine = strpart(a:CmdLine, 0, a:CursorPos-strlen(a:ArgLead)) .
1966   "        \ argLead . strpart(a:CmdLine, a:CursorPos)
1967   "  exec "normal! \<C-\>e'".cmdLine."'\<CR>"
1968   "  call setcmdpos(a:CursorPos+(strlen(argLead) - strlen(a:ArgLead)))
1969   "  return ''
1970   "endif
1971   if a:ArgLead =~ '^//'.s:_('Depot').'/'
1972     " Get directory matches.
1973     let dirMatches = s:GetOutput('dirs', a:ArgLead, "\n", '/&')
1974     " Get file matches.
1975     let fileMatches = s:GetOutput('files', a:ArgLead, '#\d\+[^'."\n".']\+', '')
1976     if dirMatches !~ s:EMPTY_STR || fileMatches !~ s:EMPTY_STR
1977       return dirMatches.fileMatches
1978     else
1979       return ''
1980     endif
1981   endif
1982   return genutils#UserFileComplete(a:ArgLead, a:CmdLine, a:CursorPos, 1, '')
1983 endfunction
1984
1985 function! s:GetOutput(p4Cmd, arg, pat, repl)
1986   let matches = perforce#PFIF(0, 4, a:p4Cmd, a:arg.'*')
1987   if s:errCode == 0
1988     if matches =~ 'no such file(s)'
1989       let matches = ''
1990     else
1991       let matches = substitute(substitute(matches, a:pat, a:repl, 'g'),
1992             \ "\n\n", "\n", 'g')
1993     endif
1994   endif
1995   return matches
1996 endfunction
1997 " PFIF }}}
1998
1999 """ START: Adopted from Tom's perforce plugin. {{{
2000
2001 "---------------------------------------------------------------------------
2002 " Produce string for ruler output
2003 function! perforce#RulerStatus()
2004   if exists('b:p4RulerStr') && b:p4RulerStr !~# s:EMPTY_STR
2005     return b:p4RulerStr
2006   endif
2007   if !exists('b:p4FStatDone') || !b:p4FStatDone
2008     return ''
2009   endif
2010
2011   "let b:p4RulerStr = '[p4 '
2012   let b:p4RulerStr = '['
2013   if exists('b:p4RulerErr') && b:p4RulerErr !~# s:EMPTY_STR
2014     let b:p4RulerStr = b:p4RulerStr . b:p4RulerErr
2015   elseif !exists('b:p4HaveRev')
2016     let b:p4RulerStr = ''
2017   elseif b:p4Action =~# s:EMPTY_STR
2018     if b:p4OtherOpen =~# s:EMPTY_STR
2019       let b:p4RulerStr = b:p4RulerStr . 'unopened'
2020     else
2021       let b:p4RulerStr = b:p4RulerStr . b:p4OtherOpen . ':' . b:p4OtherAction
2022     endif
2023   else
2024     if b:p4Change ==# 'default' || b:p4Change =~# s:EMPTY_STR
2025       let b:p4RulerStr = b:p4RulerStr . b:p4Action
2026     else
2027       let b:p4RulerStr = b:p4RulerStr . b:p4Action . ':' . b:p4Change
2028     endif
2029   endif
2030   if exists('b:p4HaveRev') && b:p4HaveRev !~# s:EMPTY_STR
2031     let b:p4RulerStr = b:p4RulerStr . ' #' . b:p4HaveRev . '/' . b:p4HeadRev
2032   endif
2033
2034   if b:p4RulerStr !~# s:EMPTY_STR
2035     let b:p4RulerStr = b:p4RulerStr . ']'
2036   endif
2037   return b:p4RulerStr
2038 endfunction
2039
2040 function! s:GetClientInfo()
2041   let infoStr = ''
2042   call s:PushP4Context()
2043   try
2044     let infoStr = perforce#PFIF(0, 4, 'info')
2045     if s:errCode != 0
2046       return s:ConfirmMessage((v:errmsg != '') ? v:errmsg : infoStr, 'OK', 1,
2047             \ 'Error')
2048     endif
2049   finally
2050     call s:PopP4Context(0)
2051   endtry
2052   let g:p4ClientRoot = genutils#CleanupFileName(s:StrExtract(infoStr,
2053         \ '\CClient root: [^'."\n".']\+', 13))
2054   let s:p4Client = s:StrExtract(infoStr, '\CClient name: [^'."\n".']\+', 13)
2055   let s:p4User = s:StrExtract(infoStr, '\CUser name: [^'."\n".']\+', 11)
2056 endfunction
2057
2058 " Get/refresh filestatus for the specified buffer with optimizations.
2059 function! perforce#GetFileStatus(buf, refresh)
2060   if ! type(a:buf) " If number.
2061     let bufNr = (a:buf == 0) ? bufnr('%') : a:buf
2062   else
2063     let bufNr = bufnr(a:buf)
2064   endif
2065
2066   " If it is not a normal buffer, then ignore it.
2067   if getbufvar(bufNr, '&buftype') != '' || bufname(bufNr) == ''
2068     return ""
2069   endif
2070   if bufNr == -1 || (!a:refresh && s:_('OptimizeActiveStatus') &&
2071         \ getbufvar(bufNr, "p4FStatDone"))
2072     return ""
2073   endif
2074
2075   " This is an optimization by restricting status to the files under the
2076   "   client root only.
2077   if !s:IsFileUnderDepot(expand('#'.bufNr.':p'))
2078     return ""
2079   endif
2080
2081   return s:GetFileStatusImpl(bufNr)
2082 endfunction
2083
2084 function! s:ResetFileStatusForFiles(files)
2085   for file in a:files
2086     let bufNr = genutils#FindBufferForName(file)
2087     if bufNr != -1
2088       " FIXME: Check for other tabs also.
2089       if bufwinnr(bufNr) != -1 " If currently visible.
2090         call perforce#GetFileStatus(bufNr, 1)
2091       else
2092         call s:ResetFileStatusForBuffer(bufNr)
2093       endif
2094     endif
2095   endfor
2096 endfunction
2097
2098 function! s:ResetFileStatusForBuffer(bufNr)
2099   " Avoid proliferating this buffer variable.
2100   if getbufvar(a:bufNr, 'p4FStatDone') != 0
2101     call setbufvar(a:bufNr, 'p4FStatDone', 0)
2102   endif
2103 endfunction
2104
2105 "---------------------------------------------------------------------------
2106 " Obtain file status information
2107 function! s:GetFileStatusImpl(bufNr)
2108   if bufname(a:bufNr) == ""
2109     return ''
2110   endif
2111   let fileName = fnamemodify(bufname(a:bufNr), ':p')
2112   let bufNr = a:bufNr
2113   " If the filename matches with one of the ignore patterns, then don't do
2114   " status.
2115   if s:_('ASIgnoreDefPattern') !~# s:EMPTY_STR &&
2116         \ match(fileName, s:_('ASIgnoreDefPattern')) != -1
2117     return ''
2118   endif
2119   if s:_('ASIgnoreUsrPattern') !~# s:EMPTY_STR &&
2120         \ match(fileName, s:_('ASIgnoreUsrPattern')) != -1
2121     return ''
2122   endif
2123
2124   call setbufvar(bufNr, 'p4RulerStr', '') " Let this be reconstructed.
2125
2126   " This could very well be a recursive call, so we should save the current
2127   "   state.
2128   call s:PushP4Context()
2129   try
2130     let fileStatusStr = perforce#PFIF(1, 4, 'fstat', fileName)
2131     call setbufvar(bufNr, 'p4FStatDone', '1')
2132
2133     if s:errCode != 0
2134       call setbufvar(bufNr, 'p4RulerErr', "<ERROR>")
2135       return ''
2136     endif
2137   finally
2138     call s:PopP4Context(0)
2139   endtry
2140
2141   if match(fileStatusStr, ' - file(s) not in client view\.') >= 0
2142     call setbufvar(bufNr, 'p4RulerErr', "<Not In View>")
2143     " Required for optimizing out in future runs.
2144     call setbufvar(bufNr, 'p4HeadRev', '')
2145     return ''
2146   elseif match(fileStatusStr, ' - no such file(s).') >= 0
2147     call setbufvar(bufNr, 'p4RulerErr', "<Not In Depot>")
2148     " Required for optimizing out in future runs.
2149     call setbufvar(bufNr, 'p4HeadRev', '')
2150     return ''
2151   else
2152     call setbufvar(bufNr, 'p4RulerErr', '')
2153   endif
2154
2155   call setbufvar(bufNr, 'p4HeadRev',
2156         \ s:StrExtract(fileStatusStr, '\CheadRev [0-9]\+', 8))
2157   "call setbufvar(bufNr, 'p4DepotFile',
2158   "      \ s:StrExtract(fileStatusStr, '\CdepotFile [^'."\n".']\+', 10))
2159   "call setbufvar(bufNr, 'p4ClientFile',
2160   "      \ s:StrExtract(fileStatusStr, '\CclientFile [^'."\n".']\+', 11))
2161   call setbufvar(bufNr, 'p4HaveRev',
2162         \ s:StrExtract(fileStatusStr, '\ChaveRev [0-9]\+', 8))
2163   let headAction = s:StrExtract(fileStatusStr, '\CheadAction [^[:space:]]\+',
2164         \ 11)
2165   if headAction ==# 'delete'
2166     call setbufvar(bufNr, 'p4Action', '<Deleted>')
2167     call setbufvar(bufNr, 'p4Change', '')
2168   else
2169     call setbufvar(bufNr, 'p4Action',
2170           \ s:StrExtract(fileStatusStr, '\Caction [^[:space:]]\+', 7))
2171     call setbufvar(bufNr, 'p4OtherOpen',
2172           \ s:StrExtract(fileStatusStr, '\CotherOpen0 [^[:space:]@]\+', 11))
2173     call setbufvar(bufNr, 'p4OtherAction',
2174           \ s:StrExtract(fileStatusStr, '\CotherAction0 [^[:space:]@]\+', 13))
2175     call setbufvar(bufNr, 'p4Change',
2176           \ s:StrExtract(fileStatusStr, '\Cchange [^[:space:]]\+', 7))
2177   endif
2178
2179   return fileStatusStr
2180 endfunction
2181
2182 function! s:StrExtract(str, pat, pos)
2183   let part = matchstr(a:str, a:pat)
2184   let part = strpart(part, a:pos)
2185   return part
2186 endfunction
2187
2188 function! s:AdjustRevision(file, adjustment)
2189   let s:errCode = 0
2190   let revNum = a:adjustment
2191   if revNum =~# '[-+]\d\+'
2192     let revNum = substitute(revNum, '^+', '', '')
2193     if getbufvar(a:file, 'p4HeadRev') =~# s:EMPTY_STR
2194       " If fstat is not done yet, do it now.
2195       call perforce#GetFileStatus(a:file, 1)
2196       if getbufvar(a:file, 'p4HeadRev') =~# s:EMPTY_STR
2197         call s:EchoMessage("Current revision is not available. " .
2198               \ "To be able to use negative revisions, see help on " .
2199               \ "'perforce-active-status'.", 'Error')
2200         let s:errCode = 1
2201         return -1
2202       endif
2203     endif
2204     let revNum = getbufvar(a:file, 'p4HaveRev') + revNum
2205     if revNum < 1
2206       call s:EchoMessage("Not that many revisions available. Try again " .
2207             \ "after running PFRefreshFileStatus command.", 'Error')
2208       let s:errCode = 1
2209       return -1
2210     endif
2211   endif
2212   return revNum
2213 endfunction
2214
2215 "---------------------------------------------------------------------------
2216 " One of a set of functions that returns fields from the p4 fstat command
2217 function! s:IsCurrent()
2218   let revdiff = b:p4HeadRev - b:p4HaveRev
2219   if revdiff == 0
2220     return 0
2221   else
2222     return -1
2223   endif
2224 endfunction
2225
2226 function! s:CheckOutFile()
2227   if ! g:p4PromptToCheckout || ! s:IsFileUnderDepot(expand('%:p'))
2228     return
2229   endif
2230   " If we know that the file is deleted from the depot, don't prompt.
2231   if exists('b:p4Action') && b:p4Action == '<Deleted>'
2232     return
2233   endif
2234
2235   if filereadable(expand("%")) && ! filewritable(expand("%"))
2236     let option = s:ConfirmMessage("Readonly file, do you want to checkout " .
2237           \ "from perforce?", "&Yes\n&No\n&Cancel", s:_('CheckOutDefault'),
2238           \ "Question")
2239     if option == 1
2240       call perforce#PFIF(1, 2, 'edit')
2241       if ! s:errCode
2242         edit
2243         call perforce#GetFileStatus(expand('<abuf>') + 0, 1)
2244       endif
2245     elseif option == 3
2246       call s:CancelEdit(0)
2247     endif
2248   endif
2249 endfunction
2250
2251 function! s:CancelEdit(stage)
2252   aug P4CancelEdit
2253     au!
2254     if a:stage == 0
2255       au CursorMovedI <buffer> nested :call <SID>CancelEdit(1)
2256       au CursorMoved <buffer> nested :call <SID>CancelEdit(1)
2257     elseif a:stage == 1
2258       stopinsert
2259       silent undo
2260       setl readonly
2261     endif
2262   aug END
2263 endfunction
2264
2265 function! perforce#FileChangedShell()
2266   let bufNr = expand("<abuf>") + 0
2267   if s:_('EnableActiveStatus')
2268     call s:ResetFileStatusForBuffer(bufNr)
2269   endif
2270   let autoread = -1
2271   if index(s:autoreadCmds, s:currentCommand) != -1
2272     let autoread = s:_('Autoread')
2273     if autoread
2274       call setbufvar(bufNr, '&readonly', 0)
2275     endif
2276   endif
2277   return autoread
2278 endfunction
2279 """ END: Adapted from Tom's perforce plugin. }}}
2280
2281 """ END: Middleware functions }}}
2282
2283
2284 """ BEGIN: Infrastructure {{{
2285
2286 " Assumes that the arguments are already parsed and are ready to be used in
2287 "   the script variables.
2288 " Low level interface with the p4 command.
2289 " clearBuffer: If the buffer contents should be cleared before
2290 "     adding the new output (See s:GotoWindow).
2291 " testMode (number):
2292 "   0 - Run normally.
2293 "   1 - testing, ignore.
2294 "   2 - debugging, display the command-line instead of the actual output..
2295 " Returns the output if available. If there is any error, the error code will
2296 "   be available in s:errCode variable.
2297 function! s:PFImpl(clearBuffer, testMode) " {{{
2298   try " [-2f]
2299
2300   let s:errCode = 0
2301   let p4Options = s:GetP4Options()
2302   let fullCmd = s:CreateFullCmd(s:MakeP4ArgList(p4Options, 0))
2303   " Save the name of the current file.
2304   let p4OrgFileName = s:GetCurFileName()
2305
2306   let s:currentCommand = ''
2307   " Make sure all the already existing changes are detected. We don't have
2308   "     s:currentCommand set here, so the user will get an appropriate prompt.
2309   try
2310     checktime
2311   catch
2312     " FIXME: Ignore error for now.
2313   endtry
2314
2315   " If the output has to be shown in a window, position cursor appropriately,
2316   " creating a new window if required.
2317   let v:errmsg = ""
2318   " Ignore outputType in this case.
2319   if s:commandMode != s:CM_PIPE && s:commandMode != s:CM_FILTER
2320     if s:outputType == 0 || s:outputType == 1
2321       " Only when "clear with undo" is selected, we optimize out the call.
2322       if s:GotoWindow(s:outputType,
2323             \ (!s:refreshWindowsAlways && (a:clearBuffer == 1)) ?
2324             \  2 : a:clearBuffer, p4OrgFileName, 0) != 0
2325         return s:errCode
2326       endif
2327     endif
2328   endif
2329
2330   let output = ''
2331   if s:errCode == 0
2332     if ! a:testMode
2333       let s:currentCommand = s:p4Command
2334       if s:_('EnableFileChangedShell')
2335         call genutils#DefFCShellInstall()
2336       endif
2337
2338       try
2339         if s:commandMode ==# s:CM_RUN
2340           " Only when "clear with undo" is selected, we optimize out the call.
2341           if s:refreshWindowsAlways ||
2342                 \ ((!s:refreshWindowsAlways && (a:clearBuffer == 1)) &&
2343                 \  (line('$') == 1 && getline(1) =~ '^\s*$'))
2344             " If we are placing the output in a new window, then we should
2345             "   avoid system() for performance reasons, imagine doing a
2346             "   'print' on a huge file.
2347             " These two outputType's correspond to placing the output in a
2348             "   window.
2349             if s:outputType != 0 && s:outputType != 1
2350               let output = s:System(fullCmd)
2351             else
2352               exec '.call s:Filter(fullCmd, 1)'
2353               let output = ''
2354             endif
2355           endif
2356         elseif s:commandMode ==# s:CM_FILTER
2357           exec s:filterRange . 'call s:Filter(fullCmd, 1)'
2358         elseif s:commandMode ==# s:CM_PIPE
2359           exec s:filterRange . 'call s:Filter(fullCmd, 2)'
2360         endif
2361         " Detect any new changes to the loaded buffers.
2362         " CAUTION: This actually results in a reentrant call back to this
2363         "   function, but our Push/Pop mechanism for the context should take
2364         "   care of it.
2365         try
2366           checktime
2367         catch
2368           " FIXME: Ignore error for now.
2369         endtry
2370       finally
2371         if s:_('EnableFileChangedShell')
2372           call genutils#DefFCShellUninstall()
2373         endif
2374         let s:currentCommand = ''
2375       endtry
2376     elseif a:testMode != 1
2377       let output = fullCmd
2378     endif
2379
2380     let v:errmsg = ""
2381     " If we have non-null output, then handling it is still pending.
2382     if output !~# s:EMPTY_STR
2383       let echoGrp = 'NONE' " The default
2384       let maxLinesInDlg = s:_('MaxLinesInDialog')
2385       if s:outputType == 2 && maxLinesInDlg != -1
2386         " Count NLs.
2387         let nNLs = 0
2388         let nlIdx = 0
2389         while 1
2390           let nlIdx = stridx(output, "\n", nlIdx+1)
2391           if nlIdx != -1
2392             let nNLs += 1
2393             if nNLs > maxLinesInDlg
2394               break
2395             endif
2396           else
2397             break
2398           endif
2399         endwhile
2400         if nNLs > maxLinesInDlg
2401           " NOTE: Keep in sync with that at the start of the function.
2402           if s:GotoWindow(s:outputType,
2403                 \ (!s:refreshWindowsAlways && (a:clearBuffer == 1)) ?
2404                 \  2 : a:clearBuffer, p4OrgFileName, 0) == 0
2405             let s:outputType = 0
2406           else
2407             let s:outputType = 3
2408             let echoGrp = 'WarningMsg'
2409           endif
2410         endif
2411       endif
2412       " If the output has to be shown in a dialog, bring up a dialog with the
2413       "   output, otherwise show it in the current window.
2414       if s:outputType == 0 || s:outputType == 1
2415         silent! put! =output
2416         1
2417       elseif s:outputType == 2
2418         call s:ConfirmMessage(output, "OK", 1, "Info")
2419       elseif s:outputType == 3
2420         call s:EchoMessage(output, echoGrp)
2421       elseif s:outputType == 4
2422         " Do nothing we will just return it.
2423       endif
2424     endif
2425   endif
2426   return output
2427
2428   finally " [+2s]
2429     call s:InitWindow(p4Options)
2430   endtry
2431 endfunction " }}}
2432
2433 function! s:NewWindowCreated()
2434   if (s:outputType == 0 || s:outputType == 1) && s:errCode == 0 &&
2435         \ (s:commandMode ==# s:CM_RUN)
2436     return 1
2437   else
2438     return 0
2439   endif
2440 endfunction
2441
2442 function! s:setBufSetting(opt, set)
2443   let optArg = matchstr(b:p4Options, '\%(\S\)\@<!-'.a:opt.'\s\+\S\+')
2444   if optArg !~# s:EMPTY_STR
2445     let b:p4Options = substitute(b:p4Options, '\V'.optArg, '', '')
2446     let b:{a:set} = matchstr(optArg, '-'.a:opt.'\s\+\zs.*')
2447   endif
2448 endfunction
2449
2450 " These p4Options are frozen according to the current s:p4Options.
2451 function! s:InitWindow(p4Options)
2452   if s:NewWindowCreated()
2453     let b:p4Command = s:p4Command
2454     let b:p4Options = a:p4Options
2455     let b:p4CmdOptions = s:p4CmdOptions
2456     let b:p4Arguments = s:p4Arguments
2457     let b:p4UserArgs = s:userArgs
2458     " Separate -p port -c client -u user options and set them individually.
2459     " Leave the rest in the b:p4Options variable.
2460     call s:setBufSetting('c', 'p4Client')
2461     call s:setBufSetting('p', 'p4Port')
2462     call s:setBufSetting('u', 'p4User')
2463     " Remove any ^M's at the end (for windows), without corrupting the search
2464     " register or its history.
2465     call genutils#SilentSubstitute("\<CR>$", '%s///e')
2466     setlocal nomodifiable
2467     setlocal nomodified
2468  
2469     if s:outputType == 1
2470       wincmd p
2471     endif
2472   endif
2473 endfunction
2474
2475 " External command execution {{{
2476
2477 function! s:System(fullCmd)
2478   return s:ExecCmd(a:fullCmd, 0)
2479 endfunction
2480
2481 function! s:Filter(fullCmd, mode) range
2482   " For command-line, we need to protect '%', '#' and '!' chars, even if they
2483   "   are in quotes, to avoid getting expanded by Vim before invoking external
2484   "   cmd.
2485   let fullCmd = genutils#Escape(a:fullCmd, '%#!')
2486   exec a:firstline.",".a:lastline.
2487         \ "call s:ExecCmd(fullCmd, a:mode)"
2488 endfunction
2489
2490 function! s:ExecCmd(fullCmd, mode) range
2491   let v:errmsg = ''
2492   let output = ''
2493   try
2494     " Assume the shellredir is set correctly to capture the error messages.
2495     if a:mode == 0
2496       let output = system(a:fullCmd)
2497     elseif a:mode == 1
2498       silent! exec a:firstline.",".a:lastline."!".a:fullCmd
2499     else
2500       silent! exec a:firstline.",".a:lastline."write !".a:fullCmd
2501     endif
2502
2503     call s:CheckShellError(output, s:outputType)
2504     return output
2505   catch /^Vim\%((\a\+)\)\=:E/ " 48[2-5]
2506     let v:errmsg = substitute(v:exception, '^[^:]\+:', '', '')
2507     call s:CheckShellError(output, s:outputType)
2508   catch /^Vim:Interrupt$/
2509     let s:errCode = 1
2510     let v:errmsg = 'Interrupted'
2511   catch " Ignore.
2512   endtry
2513 endfunction
2514
2515 function! s:EvalExpr(expr, def)
2516   let result = a:def
2517   if a:expr !~# s:EMPTY_STR
2518     exec "let result = " . a:expr
2519   endif
2520   return result
2521 endfunction
2522
2523 function! s:GetP4Options()
2524   let addOptions = []
2525
2526   " If there are duplicates, perfore takes the first option, so let
2527   "   s:p4Options or b:p4Options come before g:p4DefaultOptions.
2528   " LIMITATATION: We choose either s:p4Options or b:p4Options only. But this
2529   "   shouldn't be a big issue as this feature is meant for executing more
2530   "   commands on the p4 result windows only.
2531   if len(s:p4Options) != 0
2532     call extend(addOptions, s:p4Options)
2533   elseif exists('b:p4Options') && len(b:p4Options) != 0
2534     call extend(addOptions, b:p4Options)
2535   endif
2536
2537   " FIXME: avoid split here.
2538   call extend(addOptions, split(s:_('DefaultOptions'), ' '))
2539
2540   let p4Client = s:p4Client
2541   let p4User = s:p4User
2542   let p4Port = s:p4Port
2543   try
2544     if s:p4Port !=# 'P4CONFIG'
2545       if s:_('CurPresetExpr') !~# s:EMPTY_STR
2546         let preset = s:EvalExpr(s:_('CurPresetExpr'), '')
2547         if preset ~= s:EMPTY_STR
2548           call perforce#PFSwitch(0, preset)
2549         endif
2550       endif
2551
2552       if s:_('p4Client') !~# s:EMPTY_STR && index(addOptions, '-c') == -1
2553         call add(add(addOptions, '-c'), s:_('p4Client'))
2554       endif
2555       if s:_('p4User') !~# s:EMPTY_STR && index(addOptions, '-u') == -1
2556         call add(add(addOptions, '-u'), s:_('p4User'))
2557       endif
2558       if s:_('p4Port') !~# s:EMPTY_STR && index(addOptions, '-p') == -1
2559         call add(add(addOptions, '-p'), s:_('p4Port'))
2560       endif
2561       " Don't pass password with '-P' option, it will be too open (ps will show
2562       "   it up).
2563       let $P4PASSWD = s:p4Password
2564     else
2565     endif
2566   finally
2567     let s:p4Client = p4Client
2568     let s:p4User = p4User
2569     let s:p4Port = p4Port
2570   endtry
2571   
2572   return addOptions
2573 endfunction
2574
2575 function! s:CreateFullCmd(argList)
2576   let fullCmd = genutils#EscapeCommand(s:p4CommandPrefix.s:_('CmdPath'), a:argList,
2577         \ s:p4Pipe)
2578   let g:p4FullCmd = fullCmd
2579   return fullCmd
2580 endfunction
2581
2582 " Generates a command string as the user typed, using the script variables.
2583 function! s:MakeP4ArgList(p4Options, useBufLocal)
2584   if a:useBufLocal && exists('b:p4Command')
2585     let p4Command = b:p4Command
2586   else
2587     let p4Command = s:p4Command
2588   endif
2589   if a:useBufLocal && exists('b:p4CmdOptions')
2590     let p4CmdOptions = b:p4CmdOptions
2591   else
2592     let p4CmdOptions = s:p4CmdOptions
2593   endif
2594   if a:useBufLocal && exists('b:p4Arguments')
2595     let p4Arguments = b:p4Arguments
2596   else
2597     let p4Arguments = s:p4Arguments
2598   endif
2599   let cmdList = a:p4Options+[p4Command]+p4CmdOptions+p4Arguments
2600   " Remove the protection from the characters that we treat specially (Note: #
2601   "   and % are treated specially by Vim command-line itself, and the
2602   "   back-slashes are removed even before we see them.)
2603   call map(cmdList, "genutils#UnEscape(v:val, '&')")
2604   return cmdList
2605 endfunction
2606
2607 " In case of outputType == 4, it assumes the caller wants to see the output as
2608 " it is, so no error message is given. The caller is expected to check for
2609 " error code, though.
2610 function! s:CheckShellError(output, outputType)
2611   if (v:shell_error != 0 || v:errmsg != '') && a:outputType != 4
2612     let output = "There was an error executing external p4 command.\n"
2613     if v:errmsg != ''
2614       let output = output . "\n" . "errmsg = " . v:errmsg
2615     endif
2616     " When commandMode ==# s:CM_RUN, the error message may already be there in
2617     "   the current window.
2618     if a:output !~# s:EMPTY_STR
2619       let output = output . "\n" . a:output
2620     elseif a:output =~# s:EMPTY_STR &&
2621           \ (s:commandMode ==# s:CM_RUN && line('$') == 1 && col('$') == 1)
2622       let output = output . "\n\n" .
2623             \ "Check if your 'shellredir' option captures error messages."
2624     endif
2625     call s:ConfirmMessage(output, "OK", 1, "Error")
2626   endif
2627   let s:errCode = v:shell_error
2628   return v:shell_error
2629 endfunction
2630
2631 " External command execution }}}
2632
2633 " Push/Pop/Peek context {{{
2634 function! s:PushP4Context()
2635   call add(s:p4Contexts, s:GetP4ContextVars())
2636 endfunction
2637
2638 function! s:PeekP4Context()
2639   return s:PopP4ContextImpl(1, 1)
2640 endfunction
2641
2642 function! s:PopP4Context(...)
2643   " By default carry forward error.
2644   return s:PopP4ContextImpl(0, (a:0 ? a:1 : 1))
2645 endfunction
2646
2647 function! s:NumP4Contexts()
2648   return len(s:p4Contexts)
2649 endfunction
2650
2651 function! s:PopP4ContextImpl(peek, carryFwdErr)
2652   let nContexts = len(s:p4Contexts)
2653   if nContexts <= 0
2654     echoerr "PopP4Context: Contexts stack is empty"
2655     return
2656   endif
2657   let context = s:p4Contexts[-1]
2658   if !a:peek
2659     call remove(s:p4Contexts, nContexts-1)
2660   endif
2661
2662   call s:SetP4ContextVars(context, a:carryFwdErr)
2663   return context
2664 endfunction
2665
2666 " Serialize p4 context variables.
2667 function! s:GetP4ContextVars()
2668   return [s:p4Options , s:p4Command , s:p4CmdOptions , s:p4Arguments ,
2669         \ s:p4Pipe , s:p4WinName , s:commandMode , s:filterRange ,
2670         \ s:outputType , s:errCode, s:userArgs]
2671 endfunction
2672
2673 " De-serialize p4 context variables.
2674 function! s:SetP4ContextVars(context, ...)
2675   let carryFwdErr = 0
2676   if a:0 && a:1
2677     let carryFwdErr = s:errCode
2678   endif
2679
2680   let [s:p4Options, s:p4Command, s:p4CmdOptions, s:p4Arguments, s:p4Pipe,
2681         \ s:p4WinName, s:commandMode, s:filterRange, s:outputType, s:errCode,
2682         \ s:userArgs] = a:context
2683   let s:errCode = s:errCode + carryFwdErr
2684 endfunction
2685 " Push/Pop/Peek context }}}
2686
2687 """ BEGIN: Argument parsing {{{
2688 function! s:ResetP4ContextVars()
2689   " Syntax is:
2690   "   PF <p4Options> <p4Command> <p4CmdOptions> <p4Arguments> | <p4Pipe>
2691   " Ex: PF -c hari integrate -b branch -s <fromFile> <toFile>
2692   let s:p4Options = []
2693   let s:p4Command = ""
2694   let s:p4CmdOptions = []
2695   let s:p4Arguments = []
2696   let s:p4Pipe = []
2697   let s:p4WinName = ""
2698   " commandMode:
2699   "   run - Execute p4 using system() or its equivalent.
2700   "   filter - Execute p4 as a filter for the current window contents. Use
2701   "            commandPrefix to restrict the filter range.
2702   "   display - Don't execute p4. The output is already passed in.
2703   let s:commandMode = "run"
2704   let s:filterRange = ""
2705   let s:outputType = 0
2706   let s:errCode = 0
2707
2708   " Special variable to keep track of full user arguments.
2709   let s:userArgs = []
2710 endfunction
2711 call s:ResetP4ContextVars() " Let them get initialized the first time.
2712
2713 " Parses the arguments into 4 parts, "options to p4", "p4 command",
2714 " "options to p4 command", "actual arguments". Also generates the window name.
2715 " outputType (string):
2716 "   0 - Execute p4 and place the output in a new window.
2717 "   1 - Same as above, but use preview window.
2718 "   2 - Execute p4 and show the output in a dialog for confirmation.
2719 "   3 - Execute p4 and echo the output.
2720 "   4 - Execute p4 and return the output.
2721 "   5 - Execute p4 no output expected. Essentially same as 4 when the current
2722 "       commandMode doesn't produce any output, just for clarification.
2723 function! s:ParseOptions(fline, lline, outputType, ...) " range
2724   call s:ResetP4ContextVars()
2725   let s:outputType = a:outputType
2726   if a:0 == 0
2727     return
2728   endif
2729
2730   let s:filterRange = a:fline . ',' . a:lline
2731   let i = 1
2732   let prevArg = ""
2733   let curArg = ""
2734   let s:pendingPipeArg = ''
2735   while i <= a:0
2736     try " Just for the sake of loop variables. [-2f]
2737
2738     if s:pendingPipeArg !~# s:EMPTY_STR
2739       let curArg = s:pendingPipeArg
2740       let s:pendingPipeArg = ''
2741     elseif len(s:p4Pipe) == 0
2742       let curArg = a:000[i-1]
2743       " The user can't specify a null string on the command-line, this is an
2744       "   argument originating from the script, so just ignore it (just for
2745       "   the sake of convenience, see PChangesDiff for a possibility).
2746       if curArg == ''
2747         continue
2748       endif
2749       let pipeIndex = match(curArg, '\\\@<!\%(\\\\\)*\zs|')
2750       if pipeIndex != -1
2751         let pipePart = strpart(curArg, pipeIndex)
2752         let p4Part = strpart(curArg, 0, pipeIndex)
2753         if p4Part !~# s:EMPTY_STR
2754           let curArg = p4Part
2755           let s:pendingPipeArg = pipePart
2756         else
2757           let curArg = pipePart
2758         endif
2759       endif
2760     else
2761       let curArg = a:000[i-1]
2762     endif
2763
2764     if curArg ==# '<pfitem>'
2765       let curItem = s:GetCurrentItem()
2766       if curItem !~# s:EMPTY_STR
2767         let curArg = curItem
2768       endif
2769     endif
2770
2771     " As we use custom completion mode, the filename meta-sequences in the
2772     "   arguments will not be expanded by Vim automatically, so we need to
2773     "   expand them manually here. On the other hand, this provides us control
2774     "   on what to expand, so we can avoid expanding perforce file revision
2775     "   numbers as buffernames (escaping is no longer required by the user on
2776     "   the commandline).
2777     let fileRev = ''
2778     let fileRevIndex = match(curArg, '#\(-\?\d\+\|none\|head\|have\)$')
2779     if fileRevIndex != -1
2780       let fileRev = strpart(curArg, fileRevIndex)
2781       let curArg = strpart(curArg, 0, fileRevIndex)
2782     endif
2783     if curArg != '' && (!exists('g:p4EnableUserFileExpand') ||
2784           \ g:p4EnableUserFileExpand)
2785       let curArg = genutils#UserFileExpand(curArg)
2786     endif
2787     if fileRev != ''
2788       let curArg = curArg.fileRev
2789     endif
2790
2791     if curArg =~# '^|' || len(s:p4Pipe) != 0
2792       call add(s:p4Pipe, curArg)
2793       continue
2794     endif
2795
2796     if ! s:IsAnOption(curArg) " If not an option.
2797       if s:p4Command =~# s:EMPTY_STR &&
2798             \ index(s:allCommands, curArg) != -1
2799         " If the previous one was an option to p4 that takes in an argument.
2800         if prevArg =~# '^-[cCdHLpPux]$' || prevArg =~# '^++o$' " See :PH usage.
2801           call add(s:p4Options, curArg)
2802           if prevArg ==# '++o' && (curArg == '0' || curArg == 1)
2803             let s:outputType = curArg
2804           endif
2805         else
2806           let s:p4Command = curArg
2807         endif
2808       else " Argument is not a perforce command.
2809         if s:p4Command =~# s:EMPTY_STR
2810           call add(s:p4Options, curArg)
2811         else
2812           let optArg = 0
2813           " Look for options that have an argument, so we can collect this
2814           " into p4CmdOptions instead of p4Arguments.
2815           if len(s:p4Arguments) == 0 && s:IsAnOption(prevArg)
2816             " We could as well just check for the option here, but combining
2817             " this with the command name will increase the accuracy of finding
2818             " the starting point for p4Arguments.
2819             if (prevArg[0] ==# '-' && has_key(s:p4OptCmdMap, prevArg[1]) &&
2820                   \ index(s:p4OptCmdMap[prevArg[1]], s:p4Command) != -1) ||
2821              \ (prevArg =~# '^++' && has_key(s:biOptCmdMap, prevArg[2]) &&
2822                   \ index(s:biOptCmdMap[prevArg[2]], s:p4Command) != -1)
2823               let optArg = 1
2824             endif
2825           endif
2826
2827           if optArg
2828             call add(s:p4CmdOptions, curArg)
2829           else
2830             call add(s:p4Arguments, curArg)
2831           endif
2832         endif
2833       endif
2834     else
2835       if len(s:p4Arguments) == 0
2836         if s:p4Command =~# s:EMPTY_STR
2837           if curArg =~# '^++[pfdr]$'
2838             if curArg ==# '++p'
2839               let s:commandMode = s:CM_PIPE
2840             elseif curArg ==# '++f'
2841               let s:commandMode = s:CM_FILTER
2842             elseif curArg ==# '++r'
2843               let s:commandMode = s:CM_RUN
2844             endif
2845             continue
2846           endif
2847           call add(s:p4Options, curArg)
2848         else
2849           call add(s:p4CmdOptions, curArg)
2850         endif
2851       else
2852         call add(s:p4Arguments,  curArg)
2853       endif
2854     endif
2855    " The "-x -" option requires it to act like a filter.
2856     if s:p4Command =~# s:EMPTY_STR && prevArg ==# '-x' && curArg ==# '-'
2857       let s:commandMode = s:CM_FILTER
2858     endif
2859
2860     finally " [+2s]
2861       if s:pendingPipeArg =~# s:EMPTY_STR
2862         let i = i + 1
2863       endif
2864       let prevArg = curArg
2865     endtry
2866   endwhile
2867
2868   if index(s:p4Options, '-d') == -1
2869     let curDir = s:EvalExpr(s:_('CurDirExpr'), '')
2870     if curDir !=# ''
2871       call add(add(s:p4Options, '-d'), s:EscapeFileName(curDir))
2872     endif
2873   endif
2874   let s:p4WinName = s:MakeWindowName()
2875 endfunction
2876
2877 function! s:IsAnOption(arg)
2878   if a:arg =~# '^-.$' || a:arg =~# '^-d\%([cnsubw]\|\d\+\)*$' ||
2879         \ a:arg =~# '^-a[fmsty]$' || a:arg =~# '^-s[ader]$' ||
2880         \ a:arg =~# '^-qu$' || a:arg =~# '^+'
2881     return 1
2882   else
2883     return 0
2884   endif
2885 endfunction
2886
2887 function! s:CleanSpaces(str)
2888   " Though not complete, it is enough to just say,
2889   "   "spaces that are not preceded by \'s".
2890   return substitute(substitute(a:str, '^ \+\|\%(\\\@<! \)\+$', '', 'g'),
2891         \ '\%(\\\@<! \)\+', ' ', 'g')
2892 endfunction
2893
2894 function! s:_(set)
2895   let set = a:set
2896   if set =~# '^\u'
2897     let set = 'p4'.set
2898   endif
2899   if exists('b:'.set)
2900     return b:{set}
2901   elseif exists('w:'.set)
2902     return w:{set}
2903   elseif exists('t:'.set)
2904     return t:{set}
2905   elseif exists('s:'.set)
2906     return s:{set}
2907   elseif exists('g:'.set)
2908     return g:{set}
2909   else
2910     echoerr 'No setting found for: ' set
2911   endif
2912 endfunction
2913
2914 function! s:indexMatching(list, pat)
2915   let i = 0
2916   for item in a:list
2917     if item =~ a:pat
2918       return i
2919     endif
2920     let i += 1
2921   endfor
2922   return -1
2923 endfunction
2924 """ END: Argument parsing }}}
2925
2926 """ BEGIN: Messages and dialogs {{{
2927 function! s:SyntaxError(msg)
2928   let s:errCode = 1
2929   call s:ConfirmMessage("Syntax Error:\n".a:msg, "OK", 1, "Error")
2930   return s:errCode
2931 endfunction
2932
2933 function! s:ShowVimError(errmsg, stack)
2934   call s:ConfirmMessage("There was an error executing a Vim command.\n\t" .
2935         \ a:errmsg.(a:stack != '' ? "\nCurrent stack: ".a:stack : ''), "OK", 1,
2936         \ "Error")
2937   echohl ErrorMsg | echomsg a:errmsg | echohl None
2938   if a:stack != ''
2939     echomsg "Current stack:" a:stack
2940   endif
2941   redraw " Cls, such that it is only available in the message list.
2942   let s:errCode = 1
2943   return s:errCode
2944 endfunction
2945
2946 function! s:EchoMessage(msg, type)
2947   let s:lastMsg = a:msg
2948   let s:lastMsgGrp = a:type
2949   redraw | exec 'echohl' a:type | echo a:msg | echohl NONE
2950 endfunction
2951
2952 function! s:ConfirmMessage(msg, opts, def, type)
2953   let s:lastMsg = a:msg
2954   let s:lastMsgGrp = 'None'
2955   if a:type ==# 'Error'
2956     let s:lastMsgGrp = 'Error'
2957   endif
2958   return confirm(a:msg, a:opts, a:def, a:type)
2959 endfunction
2960
2961 function! s:PromptFor(loop, useDialogs, msg, default)
2962   let result = ""
2963   while result =~# s:EMPTY_STR
2964     if a:useDialogs
2965       let result = inputdialog(a:msg, a:default)
2966     else
2967       let result = input(a:msg, a:default)
2968     endif
2969     if ! a:loop
2970       break
2971     endif
2972   endwhile
2973   return result
2974 endfunction
2975
2976 function! perforce#LastMessage()
2977   call s:EchoMessage(s:lastMsg, s:lastMsgGrp)
2978 endfunction
2979 """ END: Messages and dialogs }}}
2980
2981 """ BEGIN: Filename handling {{{
2982 " Escape all the special characters (as the user would if he typed the name
2983 "   himself).
2984 function! s:EscapeFileName(fName)
2985   " If there is a -d option existing, then it is better to use the full path
2986   "   name.
2987   if index(s:p4Options, '-d')  != -1
2988     let fName = fnamemodify(a:fName, ':p')
2989   else
2990     let fName = a:fName
2991   endif
2992   return genutils#Escape(fName, ' &|')
2993 endfunction
2994
2995 function! s:GetCurFileName()
2996   " When the current window itself is a perforce window, then carry over the
2997   " existing value.
2998   return (exists('b:p4OrgFileName') &&
2999         \              b:p4OrgFileName !~# s:EMPTY_STR) ?
3000         \             b:p4OrgFileName : expand('%:p')
3001 endfunction
3002
3003 function! s:GuessFileTypeForCurrentWindow()
3004   let fileExt = s:GuessFileType(b:p4OrgFileName)
3005   if fileExt =~# s:EMPTY_STR
3006     let fileExt = s:GuessFileType(expand("%"))
3007   endif
3008   return fileExt
3009 endfunction
3010
3011 function! s:GuessFileType(name)
3012   let fileExt = fnamemodify(a:name, ":e")
3013   return matchstr(fileExt, '\w\+')
3014 endfunction
3015
3016 function! s:IsDepotPath(path)
3017   if match(a:path, '^//'.s:_('Depot').'/') == 0
3018         " \ || match(a:path, '^//'. s:_('p4Client') . '/') == 0
3019     return 1
3020   else
3021     return 0
3022   endif
3023 endfunction
3024
3025 function! s:PathRefersToDepot(path)
3026   if s:IsDepotPath(a:path) || s:GetRevisionSpecifier(a:path) !~# s:EMPTY_STR
3027     return 1
3028   else
3029     return 0
3030   endif
3031 endfunction
3032
3033 function! s:GetRevisionSpecifier(fileName)
3034   return matchstr(a:fileName,
3035         \ '^\(\%(\S\|\\\@<!\%(\\\\\)*\\ \)\+\)[\\]*\zs[#@].*$')
3036 endfunction
3037
3038 " Removes the //<depot> or //<client> prefix from fileName.
3039 function! s:StripRemotePath(fileName)
3040   "return substitute(a:fileName, '//\%('.s:_('Depot').'\|'.s:_('p4Client').'\)', '', '')
3041   return substitute(a:fileName, '//\%('.s:_('Depot').'\)', '', '')
3042 endfunction
3043
3044 " Client view translation {{{
3045 " Convert perforce file wildcards ("*", "..." and "%[1-9]") to a Vim string
3046 "   regex (see |pattern.txt|). Returns patterns that work when "very nomagic"
3047 "   is set.
3048 let s:p4Wild = {}
3049 function! s:TranlsateP4Wild(p4Wild, rhsView)
3050   let strRegex = ''
3051   if a:rhsView
3052     if a:p4Wild[0] ==# '%'
3053       let pos = s:p4WildMap[a:p4Wild[1]]
3054     else
3055       let pos = s:p4WildCount
3056     endif
3057     let strRegex = '\'.pos
3058   else
3059     if a:p4Wild ==# '*'
3060       let strRegex = '\(\[^/]\*\)'
3061     elseif a:p4Wild ==# '...'
3062       let strRegex = '\(\.\*\)'
3063     elseif a:p4Wild[0] ==# '%'
3064       let strRegex = '\(\[^/]\*\)'
3065       let s:p4WildMap[a:p4Wild[1]] = s:p4WildCount
3066     endif
3067   endif
3068   let s:p4WildCount = s:p4WildCount + 1
3069   return strRegex
3070 endfunction
3071
3072 " Convert perforce file regex (containing "*", "..." and "%[1-9]") to a Vim
3073 "   string regex. No error checks for now, for simplicity.
3074 function! s:TranslateP4FileRegex(p4Regex, rhsView)
3075   let s:p4WildCount = 1
3076   " Note: We don't expect backslashes in the views, so no special handling.
3077   return substitute(a:p4Regex,
3078         \ '\(\*\|\%(\.\)\@<!\.\.\.\%(\.\)\@!\|%\([1-9]\)\)',
3079         \ '\=s:TranlsateP4Wild(submatch(1), a:rhsView)', 'g')
3080 endfunction
3081
3082 function! s:CondUpdateViewMappings()
3083   if s:_('UseClientViewMap') &&
3084         \ (!has_key(s:toDepotMapping, s:_("p4Client")) ||
3085         \  (len(s:toDepotMapping[s:_('p4Client')]) < 0))
3086     call perforce#UpdateViewMappings()
3087   endif
3088 endfunction
3089
3090 function! perforce#UpdateViewMappings()
3091   if s:_('p4Client') =~# s:EMPTY_STR
3092     return
3093   endif
3094   let view = ''
3095   call s:PushP4Context()
3096   try
3097     let view = substitute(perforce#PFIF(1, 4, '-c', s:_('p4Client'), 'client'),
3098           \ "\\_.*\nView:\\ze\n", '', 'g')
3099     if s:errCode != 0
3100       return
3101     endif
3102   finally
3103     call s:PopP4Context(0)
3104   endtry
3105   let fromDepotMapping = []
3106   let toDepotMapping = []
3107   for nextMap in reverse(split(view, "\n"))
3108     " We need to inverse the order of mapping such that the mappings that come
3109     "   later in the view take more priority.
3110     " Also, don't care about exclusionary mappings for simplicity (this could
3111     "   be considered a feature too).
3112     exec substitute(nextMap,
3113           \ '\s*-\?//'.s:_('Depot').'/\([^ ]\+\)\s*//'.s:_("p4Client").'/\(.\+\)',
3114           \ 'call add(fromDepotMapping, [s:TranslateP4FileRegex('.
3115           \ "'".'\1'."'".', 0), s:TranslateP4FileRegex('."'".'\2'."'".
3116           \ ', 1)])', '')
3117     exec substitute(nextMap,
3118           \ '\s*-\?//'.s:_('Depot').'/\([^ ]\+\)\s*//'.s:_("p4Client").'/\(.\+\)',
3119           \ 'call add(toDepotMapping, [s:TranslateP4FileRegex('.
3120           \ "'".'\2'."'".', 0), s:TranslateP4FileRegex('."'".'\1'."'".
3121           \ ', 1)])', '')
3122   endfor
3123   let s:fromDepotMapping[s:_('p4Client')] = fromDepotMapping
3124   let s:toDepotMapping[s:_('p4Client')] = toDepotMapping
3125 endfunction
3126
3127 function! P4IncludeExpr(path)
3128   return s:ConvertToLocalPath(a:path)
3129 endfunction
3130
3131 function! s:ConvertToLocalPath(path)
3132   let fileName = substitute(a:path, '^\s\+\|\s*#[^#]\+$', '', 'g')
3133   if s:IsDepotPath(fileName)
3134     if s:_('UseClientViewMap')
3135       call s:CondUpdateViewMappings()
3136       for nextMap in s:fromDepotMapping[s:_('p4Client')]
3137         let [lhs, rhs] = nextMap
3138         if fileName =~# '\V'.lhs
3139           let fileName = substitute(fileName, '\V'.lhs, rhs, '')
3140           break
3141         endif
3142       endfor
3143     endif
3144     if s:IsDepotPath(fileName)
3145       let fileName = s:_('ClientRoot') . s:StripRemotePath(fileName)
3146     endif
3147   endif
3148   return fileName
3149 endfunction
3150
3151 function! s:ConvertToDepotPath(path)
3152   " If already a depot path, just return it without any changes.
3153   if s:IsDepotPath(a:path)
3154     let fileName = a:path
3155   else
3156     let fileName = genutils#CleanupFileName(a:path)
3157     if s:IsFileUnderDepot(fileName)
3158       if s:_('UseClientViewMap')
3159         call s:CondUpdateViewMappings()
3160         for nextMap in s:toDepotMapping[s:_('p4Client')]
3161           let [lhs, rhs] = nextMap
3162           if fileName =~# '\V'.lhs
3163             let fileName = substitute(fileName, '\V'.lhs, rhs, '')
3164             break
3165           endif
3166         endfor
3167       endif
3168       if ! s:IsDepotPath(fileName)
3169         let fileName = substitute(fileName, '^'.s:_('ClientRoot'),
3170               \ '//'.s:_('Depot'), '')
3171       endif
3172     endif
3173   endif
3174   return fileName
3175 endfunction
3176 " Client view translation }}}
3177
3178 " Requires at least 2 arguments.
3179 " Returns a List of alternative filenames.
3180 function! s:PFGetAltFiles(protectedChars, codeline, ...)
3181   if a:0 == 0
3182     return []
3183   endif
3184
3185   let altCodeLine = a:codeline
3186
3187   let i = 1
3188   let altFiles = []
3189   while i <= a:0
3190     let fileName = a:000[i-1]
3191     let fileName = genutils#CleanupFileName2(fileName, a:protectedChars)
3192
3193     if altCodeLine ==# 'local' && s:IsDepotPath(fileName)
3194       let altFile = s:ConvertToLocalPath(fileName)
3195     elseif ! s:IsDepotPath(fileName)
3196       let fileName = s:ConvertToDepotPath(fileName)
3197
3198       if altCodeLine ==# s:_('Depot')
3199         " We do nothing, it is already converted to depot path.
3200         let altFile = fileName
3201       else
3202         " FIXME: Assumes that the current branch name has single path component.
3203         let altFile = substitute(fileName, '//'.s:_('Depot').'/[^/]\+',
3204               \ '//'.s:_('Depot').'/' . altCodeLine, "")
3205         let altFile = s:ConvertToLocalPath(altFile)
3206       endif
3207     endif
3208     call add(altFiles, altFile)
3209     let i = i + 1
3210   endwhile
3211   return altFiles
3212 endfunction
3213
3214 function! s:IsFileUnderDepot(fileName)
3215   let fileName = genutils#CleanupFileName(a:fileName)
3216   if fileName =~? '^\V'.s:_('ClientRoot')
3217     return 1
3218   else
3219     return 0
3220   endif
3221 endfunction
3222
3223 " This better take the line as argument, but I need the context of current
3224 "   buffer contents anyway...
3225 " I don't need to handle other revision specifiers here, as I won't expect to
3226 "   see them here (perforce converts them to the appropriate revision number). 
3227 function! s:GetCurrentDepotFile(lineNo)
3228   " Local submissions.
3229   let fileName = ""
3230   let line = getline(a:lineNo)
3231   if match(line, '//'.s:_('Depot').'/.*\(#\d\+\)\?') != -1
3232         " \ || match(line, '^//'. s:_('p4Client') . '/.*\(#\d\+\)\?') != -1
3233     let fileName = matchstr(line, '//[^/]\+/[^#]*\(#\d\+\)\?')
3234   elseif match(line, '\.\.\. #\d\+ .*') != -1
3235     " Branches, integrations etc.
3236     let fileVer = matchstr(line, '\d\+')
3237     call genutils#SaveHardPosition('Perforce')
3238     exec a:lineNo
3239     if search('^//'.s:_('Depot').'/', 'bW') == 0
3240       let fileName = ""
3241     else
3242       let fileName = substitute(s:GetCurrentDepotFile(line(".")), '#\d\+$', '',
3243             \ '')
3244       let fileName = fileName . "#" . fileVer
3245     endif
3246     call genutils#RestoreHardPosition('Perforce')
3247     call genutils#ResetHardPosition('Perforce')
3248   endif
3249   return fileName
3250 endfunction
3251 """ END: Filename handling }}}
3252
3253 """ BEGIN: Buffer management, etc. {{{
3254 " Must be followed by a call to s:EndBufSetup()
3255 function! s:StartBufSetup()
3256   " If the command created a new window, then only do setup.
3257   if !s:errCode
3258     if s:NewWindowCreated()
3259       if s:outputType == 1
3260         wincmd p
3261       endif
3262
3263       return 1
3264     endif
3265   endif
3266   return 0
3267 endfunction
3268
3269 function! s:EndBufSetup()
3270   if s:NewWindowCreated()
3271     if s:outputType == 1
3272       wincmd p
3273     endif
3274   endif
3275 endfunction
3276
3277 " Goto/Open window for the current command.
3278 " clearBuffer (number):
3279 "   0 - clear with undo.
3280 "   1 - clear with no undo.
3281 "   2 - don't clear
3282 function! s:GotoWindow(outputType, clearBuffer, p4OrgFileName, cmdCompleted)
3283   let bufNr = genutils#FindBufferForName(s:p4WinName)
3284   " NOTE: Precautionary measure to avoid accidentally matching an existing
3285   "   buffer and thus overwriting the contents.
3286   if bufNr != -1 && getbufvar(bufNr, '&buftype') == ''
3287     return s:BufConflictError(a:cmdCompleted)
3288   endif
3289
3290   " If there is a window for this buffer already, then we will just move
3291   "   cursor into it.
3292   let curBufnr = bufnr('%')
3293   let maxBufNr = bufnr('$')
3294   let bufWinnr = bufwinnr(bufNr)
3295   let nWindows = genutils#NumberOfWindows()
3296   let _eventignore = &eventignore
3297   try
3298     "set eventignore=BufRead,BufReadPre,BufEnter,BufNewFile
3299     set eventignore=all
3300     if a:outputType == 1 " Preview
3301       let alreadyOpen = 0
3302       try
3303         wincmd P
3304         " No exception, meaning preview window is already open.
3305         if winnr() == bufWinnr
3306           " The buffer is already visible in the preview window. We don't have
3307           " to do anything in this case.
3308           let alreadyOpen = 1
3309         endif
3310       catch /^Vim\%((\a\+)\)\=:E441/
3311         " Ignore.
3312       endtry
3313       if !alreadyOpen
3314         call s:EditP4WinName(1, nWindows)
3315         wincmd P
3316       endif
3317     elseif bufWinnr != -1
3318       call genutils#MoveCursorToWindow(bufWinnr)
3319     else
3320       exec s:_('SplitCommand')
3321       call s:EditP4WinName(0, nWindows)
3322     endif
3323     if s:errCode == 0
3324       " FIXME: If the name didn't originally match with a buffer, we expect
3325       "   the s:EditP4WinName() to create a new buffer, but there is a bug in
3326       "   Vim, that treats "..." in filenames as ".." resulting in multiple
3327       "   names matching the same buffer ( "p4 diff ../.../*.java" and
3328       "   "p4 submit ../.../*.java" e.g.). Though I worked around this
3329       "   particular bug by avoiding "..." in filenames, this is a good check
3330       "   in any case.
3331       if bufNr == -1 && bufnr('%') <= maxBufNr
3332         return s:BufConflictError(a:cmdCompleted)
3333       endif
3334       " For navigation.
3335       normal! mt
3336     endif
3337   catch /^Vim\%((\a\+)\)\=:E788/ " Happens during FileChangedRO.
3338     return 788 " E788
3339   catch
3340     return s:ShowVimError("Exception while opening new window.\n" . v:exception,
3341           \ v:throwpoint)
3342   finally
3343     let &eventignore = _eventignore
3344   endtry
3345   " We now have a new window created, but may be with errors.
3346   if s:errCode == 0
3347     setlocal noreadonly
3348     setlocal modifiable
3349     if s:commandMode ==# s:CM_RUN
3350       if a:clearBuffer == 1
3351         call genutils#OptClearBuffer()
3352       elseif a:clearBuffer == 0
3353         silent! 0,$delete _
3354       endif
3355     endif
3356
3357     let b:p4OrgFileName = a:p4OrgFileName
3358     call s:PFSetupBuf(expand('%'))
3359   else
3360     " Window is created but with an error. We might actually miss the cases
3361     "   where a preview operation when the preview window is already open
3362     "   fails, and so no additional windows are created, but detecting such
3363     "   cases could be error prone, so it is better to leave the buffer in
3364     "   this case, rather than making a mistake.
3365     if genutils#NumberOfWindows() > nWindows
3366       if winbufnr(winnr()) == curBufnr " Error creating buffer itself.
3367         close
3368       elseif bufname('%') == s:p4WinName
3369         " This should even close the window.
3370         silent! exec "bwipeout " . bufnr('%')
3371       endif
3372     endif
3373   endif
3374   return 0
3375 endfunction
3376
3377 function! s:BufConflictError(cmdCompleted)
3378   return s:ShowVimError('This perforce command resulted in matching an '.
3379         \ 'existing buffer. To prevent any demage this could cause '.
3380         \ 'the command will be aborted at this point.'.
3381         \ (a:cmdCompleted ? ("\nHowever the command completed ".
3382         \ (s:errCode ? 'un' : ''). 'successfully.') : ''), '')
3383 endfunction
3384
3385 function! s:EditP4WinName(preview, nWindows)
3386   let fatal = 0
3387   let bug = 0
3388   let exception = ''
3389   let pWindowWasOpen = (genutils#GetPreviewWinnr() != -1)
3390   " Some patterns can cause problems.
3391   let _wildignore = &wildignore
3392   try
3393     set wildignore=
3394     exec (a:preview?'p':'').'edit' s:p4WinName
3395   catch /^Vim\%((\a\+)\)\=:E303/
3396     " This is a non-fatal error.
3397     let bug = 1 | let exception = v:exception
3398     let stack = v:throwpoint
3399   catch /^Vim\%((\a\+)\)\=:\%(E77\|E480\)/
3400     let bug = 1 | let exception = v:exception | let fatal = 1
3401     let stack = v:throwpoint
3402   catch
3403     let exception = v:exception | let fatal = 1
3404     let stack = v:throwpoint
3405   finally
3406     let &wildignore = _wildignore
3407   endtry
3408   if fatal
3409     call s:ShowVimError(exception, '')
3410   endif
3411   if bug
3412     echohl ERROR
3413     echomsg "Please report this error message:"
3414     echomsg "\t".exception
3415     echomsg
3416     echomsg "with the following information:"
3417     echomsg "\ts:p4WinName:" s:p4WinName
3418     echomsg "\tCurrent stack:" stack
3419     echohl NONE
3420   endif
3421   " For non preview operation, or for preview window operation when the preview
3422   "   window is not already visible, we expect the number of windows to go up.
3423   if !a:preview || (a:preview && !pWindowWasOpen)
3424     if a:nWindows >= genutils#NumberOfWindows()
3425       let s:errCode = 1
3426     endif
3427   endif
3428 endfunction
3429
3430 function! s:MakeWindowName(...)
3431   " Let only the options that are explicitly specified appear in the window
3432   "   name.
3433   if a:0 > 0
3434     let cmdStr = a:1
3435   else
3436     let cmdStr = 'p4 '.join(s:MakeP4ArgList(s:p4Options, 0), ' ')
3437   endif
3438   let winName = cmdStr
3439   "let winName = genutils#DeEscape(winName)
3440   " HACK: Work-around for some weird handling of buffer names that have "..."
3441   "   (the perforce wildcard) at the end of the filename or in the middle
3442   "   followed by a space. The autocommand is not getting triggered to clean
3443   "   the buffer. If we append another character to this, I observed that the
3444   "   autocommand gets triggered. Using "/" instead of "'" would probably be
3445   "   more appropriate, but this is causing unexpected FileChangedShell
3446   "   autocommands on certain filenames (try "PF submit ../..." e.g.). There
3447   "   is also another issue with "..." (anywhere) getting treated as ".."
3448   "   resulting in two names matching the same buffer(
3449   "     "p4 diff ../.../*.java" and "p4 submit ../.../*.java" e.g.). This
3450   "   could also change the name of the buffer during the :cd operations
3451   "   (though applies only to spec buffers).
3452   "let winName = substitute(winName, '\.\.\%( \|$\)\@=', '&/', 'g')
3453   "let winName = substitute(winName, '\.\.\%( \|$\)\@=', "&'", 'g')
3454   let winName = substitute(winName, '\.\.\.', '..,', 'g')
3455   " The intention is to do the substitute only on systems like windoze that
3456   "   don't allow all characters in the filename, but I can't generalize it
3457   "   enough, so as a workaround I a just assuming any system supporting
3458   "   'shellslash' option to be a windoze like system. In addition, cygwin
3459   "   vim thinks that it is on Unix and tries to allow all characters, but
3460   "   since the underlying OS doesn't support it, we need the same treatment
3461   "   here also.
3462   if exists('+shellslash') || has('win32unix')
3463     " Some characters are not allowed in a filename on windows so substitute
3464     " them with something else.
3465     let winName = substitute(winName, s:specialChars,
3466           \ '\="[" . s:specialCharsMap[submatch(1)] . "]"', 'g')
3467     "let winName = substitute(winName, s:specialChars, '\\\1', 'g')
3468   endif
3469   " Finally escape some characters again.
3470   let winName = genutils#Escape(winName, " #%\t")
3471   if ! exists('+shellslash') " Assuming UNIX environment.
3472     let winName = substitute(winName, '\\\@<!\(\%(\\\\\)*\\[^ ]\)', '\\\1', 'g')
3473     let winName = escape(winName, "'~$`{\"")
3474   endif
3475   return winName
3476 endfunction
3477
3478 function! s:PFSetupBuf(bufName)
3479   call genutils#SetupScratchBuffer()
3480   let &l:bufhidden=s:_('BufHidden')
3481 endfunction
3482
3483 function! s:PFSetupForSpec()
3484   setlocal modifiable
3485   setlocal buftype=
3486   exec 'aug Perforce'.bufnr('%')
3487     au!
3488     au BufWriteCmd <buffer> nested :W
3489   aug END
3490 endfunction
3491
3492 function! perforce#WipeoutP4Buffers(...)
3493   let testMode = 1
3494   if a:0 > 0 && a:1 ==# '++y'
3495     let testMode = 0
3496   endif
3497   let i = 1
3498   let lastBuf = bufnr('$')
3499   let cleanedBufs = ''
3500   while i <= lastBuf
3501     if bufexists(i) && expand('#'.i) =~# '\<p4 ' && bufwinnr(i) == -1
3502       if testMode
3503         let cleanedBufs = cleanedBufs . ', ' . expand('#'.i)
3504       else
3505         silent! exec 'bwipeout' i
3506         let cleanedBufs = cleanedBufs + 1
3507       endif
3508     endif
3509     let i = i + 1
3510   endwhile
3511   if testMode
3512     echo "Buffers that will be wipedout (Use ++y to perform action):" .
3513           \ cleanedBufs
3514   else
3515     echo "Total Perforce buffers wipedout (start with 'p4 '): " . cleanedBufs
3516   endif
3517 endfunction
3518
3519 function! perforce#PFRefreshActivePane()
3520   if exists("b:p4UserArgs")
3521     call genutils#SaveSoftPosition('Perforce')
3522
3523     try
3524       silent! undo
3525       call call('perforce#PFrangeIF', b:p4UserArgs)
3526     catch
3527       call s:ShowVimError(v:exception, v:throwpoint)
3528     endtry
3529
3530     call genutils#RestoreSoftPosition('Perforce')
3531     call genutils#ResetSoftPosition('Perforce')
3532   endif
3533 endfunction
3534 """ END: Buffer management, etc. }}}
3535
3536 """ BEGIN: Testing {{{
3537 " Ex: PFTestCmdParse -c client -u user integrate -b branch -s source target1 target2
3538 command! -nargs=* -range=% -complete=file PFTestCmdParse
3539       \ :call <SID>TestParseOptions(<f-args>)
3540 function! s:TestParseOptions(commandName, ...) range
3541   call call('s:ParseOptionsIF', [a:firstline, a:lastline, 0, 0, a:commandName]+
3542         \ a:000)
3543   call s:DebugP4Status()
3544 endfunction
3545
3546 function! s:DebugP4Status()
3547   echo "p4Options :" . join(s:p4Options, ' ') . ":"
3548   echo "p4Command :" . s:p4Command . ":"
3549   echo "p4CmdOptions :" . join(s:p4CmdOptions, ' ') . ":"
3550   echo "p4Arguments :" . join(s:p4Arguments, ' ') . ":"
3551   echo "p4Pipe :" . join(s:p4Pipe, ' ') . ":"
3552   echo "p4WinName :" . s:p4WinName . ":"
3553   echo "outputType :" . s:outputType . ":"
3554   echo "errCode :" . s:errCode . ":"
3555   echo "commandMode :" . s:commandMode . ":"
3556   echo "filterRange :" . s:filterRange . ":"
3557   echo "Cmd :" . s:CreateFullCmd(s:MakeP4ArgList([''], 0)) . ":"
3558 endfunction
3559
3560 "function! s:TestPushPopContexts()
3561 "  let s:p4Options = ["options1"]
3562 "  let s:p4Command = "command1"
3563 "  let s:p4CmdOptions = ["cmdOptions1"]
3564 "  let s:p4Arguments = ["arguments1"]
3565 "  let s:p4WinName = "winname1"
3566 "  call s:PushP4Context()
3567 "
3568 "  let s:p4Options = ["options2"]
3569 "  let s:p4Command = "command2"
3570 "  let s:p4CmdOptions = ["cmdOptions2"]
3571 "  let s:p4Arguments = ["arguments2"]
3572 "  let s:p4WinName = "winname2"
3573 "  call s:PushP4Context()
3574 "
3575 "  call s:ResetP4ContextVars()
3576 "  echo "After reset: " . s:CreateFullCmd(s:MakeP4ArgList([''], 0))
3577 "  call s:PopP4Context()
3578 "  echo "After pop1: " . s:CreateFullCmd(s:MakeP4ArgList([''], 0))
3579 "  call s:PopP4Context()
3580 "  echo "After pop2: " . s:CreateFullCmd(s:MakeP4ArgList([''], 0))
3581 "endfunction
3582
3583 """ END: Testing }}}
3584
3585 """ BEGIN: Experimental API {{{
3586
3587 function! perforce#PFGet(var)
3588   return {a:var}
3589 endfunction
3590
3591 function! perforce#PFSet(var, val)
3592   let {a:var} = a:val
3593 endfunction
3594
3595 function! perforce#PFCall(func, ...)
3596   let result = call(a:func, a:000)
3597   return result
3598 endfunction
3599
3600 function! perforce#PFEval(expr)
3601   exec "let result = ".a:expr
3602   return result
3603 endfunction
3604
3605 """ END: Experimental API }}}
3606
3607 function! perforce#Initialize(initMenu) " {{{
3608
3609 " User Options {{{
3610
3611 if g:p4ClientRoot != ''
3612   let g:p4ClientRoot = genutils#CleanupFileName(g:p4ClientRoot)
3613 endif
3614 if type(g:p4DefaultListSize) == 0
3615   let g:p4DefaultListSize = string(g:p4DefaultListSize)
3616 endif
3617 if g:p4FileLauncher == '' && genutils#OnMS()
3618   let g:p4FileLauncher = "start rundll32 SHELL32.DLL,ShellExec_RunDLL"
3619 endif
3620 if g:p4DefaultPreset != -1 &&
3621       \ g:p4DefaultPreset.'' !~# s:EMPTY_STR
3622   call perforce#PFSwitch(1, g:p4DefaultPreset)
3623 endif
3624
3625
3626 " Assume the user already has the preferred rulerformat set (which is anyway
3627 "   going to be done through the .vimrc file which should have been sourced by
3628 "   now).
3629 if g:p4EnableRuler
3630   " Take care of rerunning this code, as the reinitialization can happen any
3631   "   time.
3632   if !exists("s:orgRulerFormat")
3633     let s:orgRulerFormat = &rulerformat
3634   else
3635     let &rulerformat = s:orgRulerFormat
3636   endif
3637
3638   if &rulerformat != ""
3639     if match(&rulerformat, '^%\d\+') == 0
3640       let orgWidth = substitute(&rulerformat, '^%\(\d\+\)(.*$',
3641             \ '\1', '')
3642       let orgRuler = substitute(&rulerformat, '^%\d\+(\(.*\)%)$', '\1', '')
3643     else
3644       let orgWidth = strlen(&rulerformat) " Approximate.
3645       let orgRuler = &rulerformat
3646     endif
3647   else
3648     let orgWidth = 20
3649     let orgRuler = '%l,%c%V%=%5(%p%%%)'
3650   endif
3651   let &rulerformat = '%' . (orgWidth + g:p4RulerWidth) .  '(%{' .
3652         \ 'perforce#RulerStatus()}%=' . orgRuler . '%)'
3653 else
3654   if exists("s:orgRulerFormat")
3655     let &rulerformat = s:orgRulerFormat
3656   else
3657     set rulerformat&
3658   endif
3659 endif
3660
3661 aug P4Active
3662   au!
3663   if g:p4EnableActiveStatus
3664     au BufRead * call perforce#GetFileStatus(expand('<abuf>') + 0, 0)
3665   endif
3666 aug END
3667
3668 " User Options }}}
3669
3670 if a:initMenu
3671 runtime! perforce/perforcemenu.vim
3672 let v:errmsg = ''
3673 endif
3674
3675 let g:p4PromptToCheckout = ! g:p4PromptToCheckout
3676 call perforce#ToggleCheckOutPrompt(0)
3677
3678 endfunction " s:Initialize }}}
3679
3680 """ END: Infrastructure }}}
3681  
3682 " Do some initializations.
3683 if g:p4DefaultPreset != -1 &&
3684       \ g:p4DefaultPreset.'' !~# s:EMPTY_STR
3685   call perforce#PFSwitch(0, g:p4DefaultPreset)
3686 endif