1#compdef p4 p4d -value-,P4CLIENT,-default- -value-,P4PORT,-default- -value-,P4MERGE,-default- -value-,P4USER,-default-
2
3# Maintainer: Peter Stephenson <pws@csr.com>.
4
5# Increasingly loosely based on _cvs version 1.17.
6# Completions currently based on Perforce release 2016.1.
7
8# Styles, tags and contexts
9# =========================
10#
11# If the `verbose' style is set (it is assumed by default), verbose
12# descriptions are provided for many completed quantities derived
13# dynamically such as subcommand names, labels, changes -- in fact,
14# just about anything for which Perforce itself produces a verbose,
15# one-line description.  It may be turned off in the context of each
16# subcommand e.g.
17#   zstyle ':completion:*:p4-labelsync:*' verbose false
18# or for a particular tag, e.g. changes,
19#   zstyle ':completion:*:changes' verbose false
20# or just for top-level completion (i.e. up to and including completion
21# of the subcommand):
22#   zstyle ':completion:*:p4:*' verbose false
23# or for p4 as a whole,
24#   zstyle  ':completion:*:p4(-*|):*' verbose false
25# This is actually handled by the `_describe' function underneath the
26# Perforce completion system; it's mentioned here as verbosity adds
27# significantly to a lot of the Perforce completions.
28#
29# Note that completing change numbers is not very useful if `verbose' is
30# turned off.  There is no speed advantage for turning it off, either.
31# (Changes are also known as changelists or changesets.  The functions
32# and tags here all consistently use `changes'.)
33#
34# The style `max' can be set to a number which limits how many
35# possibilities can be shown when selecting changes or jobs.  This is
36# handled within Perforce, so the completion code may limit the number even
37# further.  If not set explicitly, the value is taken to be 20 to avoid a
38# huge database being output.  Set it to a larger number if necessary.
39# Setting it explicitly to zero removes the maximum.  Because you see only
40# the most recent, changes and jobs are shown in the order given by
41# Perforce without further sorting.
42#
43# Completion of jobs can also be controlled by the `jobview' style.
44# This uses the standard Perforce JobView syntax, and is applied
45# in connection with the `max' style.  In other words,
46# if you set
47#   zstyle ':completion:*:p4-*:jobs' max 0
48#   zstyle ':completion:*:p4-*:jobs' jobview 'user=pws'
49# then jobs to be completed will be those from the output of
50#   p4 jobs -e 'user=pws'
51# i.e. those assigned to Perforce user `pws'.
52#
53# Completion of changes can be controlled by the `changes' style.
54# This takes additional arguments to be passed to `p4 changes'.
55# An obvious example is:
56#   zstyle ':completion:*:p4-*:changes' changes -u $USER
57# to limit changes to the present user.
58#
59# The style `all-files' is used to tell the completion system to
60# complete any file in a given context.  This is for use in places
61# where it would, for example, only complete files opened for editing.
62# See the next section for more.
63#
64# The style `depot-files' tells the system to complete files by asking
65# Perforce for a list where it would otherwise complete files locally by
66# the standard mechanism --- basically any time you don't use // notation
67# and there is no restriction e.g. to opened files only.  There is likely
68# to be a significant speed penalty for this; it is turned off by default
69# in all contexts.  The advantage is that it cuts out files not maintained
70# by Perforce.  (Again, note this is a style, not a tag.)  Contexts
71# where this might be particularly useful include p4-diff or p4-diff2.
72#
73# The tags depot-files and depot-dirs also exist; they are used whenever
74# the system is completing files or directories by asking Perforce
75# to list them, rather than by using normal file completion.
76#
77# The tag subdirs is used to complete the special `...' which tells
78# Perforce to search all subdirectories.  Hence you can turn this
79# feature off by suitably manipulating your tags.
80#
81# The function will usually try to limit the files it lists by
82# context; for example, to just opened files.  By default it does
83# this by retrieving the complete list from Perforce and then
84# relying on the completion system to do the matching.  If this is
85# slow, it is possible to set the style "glob", in which case the
86# matching is done within Perforce, potentially reducing the amount of
87# searching of Perforce's internal database.  The tag used for
88# this is the same as the command used to retrieve the file name:
89# integrated, opened, resolved, dirs, files.  The disadvantage
90# of doing the matching within Perforce is that no matcher specification
91# is applied; for example, it's not possible to match a_u.c against
92# admin_utils.c.
93#
94# Actually, a hybrid strategy is used when the glob style is not set: the
95# directory is passed literally to Perforce, but the file or directory
96# being matched is passed as "*", so that matching on the contents of the
97# directory is performed by the completion system.
98#
99# Experiment suggests that the glob style isn't usually needed: only
100# "p4 integrated" is likely to be significantly slowed if no limiting
101# pattern is applied, and completing only integrated files is uncommon.
102#
103# Completion of files and their revisions
104# =======================================
105#
106# File completion handles @ and # suffixes.  If the filename is completed,
107# typing @ or # removes the space which was automatically added.
108# The context used has `at-suffix' or `hash-suffix' in the position
109# before the tag to indicate suffix completion (as always, ^Xh will
110# show you all possible contexts).  This makes it possible
111# to select changes, dates, labels and clients using the tag-order
112# style.  For example,
113#    zstyle ':completion:*:p4-*:at-suffix:*' tag-order changes '*'
114# will force all completion after `@' to show changes first.  Executing
115# _next_tags (usually ^x^n) will cycle between that and the remaining
116# tags (dates, labels, clients).  I recommend, at least, keeping labels
117# later than changes since the former are less useful and can take a long
118# time to complete.
119#
120# A # is automatically quoted when handled in this way; if the file is
121# typed by hand or the completion didn't finish (e.g. you typed a character
122# in the middle of menu completion), you probably need to type `\#' by
123# hand.  The problem is that the completion system uses extended globbing
124# and hence a pattern of the form `filename#' always matches `filename'
125# (since e# matches any number of e's including one).  Hence this can look
126# like an expansion which expands to `filename'.
127#
128# After @, you can complete changes (note the use of the style `max'
129# above), labels, clients or even dates, while after `#' you can
130# complete numeric revisions or the special revision names head, none,
131# have.  These are available whether or not you completed the filename; if
132# the file doesn't exist, numeric revisions won't work, but the rest will
133# (though what Perforce will do with the resulting command is another matter).
134#
135# In addition, when completing after `file@', only changes specific to `file'
136# will be shown (exactly the list of changes Perforce shows from the
137# command `p4 changes file').  If this doesn't work, chances are that
138# `file' does not exist.  Having a multi-directory match (literal `...')
139# in `file' should work fine, since `p4 changes' recognises all normal
140# Perforce file syntax.
141#
142# Some perforce commands allow you to specify a range of revisions or
143# changes as `file@1,@2' or `file#1,#2'.  Currently, the second part of the
144# revision range can always be completed (whether the command accepts them
145# or not), but the comma after the first part of the range is only added
146# automatically if the documentation suggests the command accepts ranges at
147# that point.  This is an auto-removable suffix, so it will disappear if
148# you hit space or return.  Typing a `#' at this point will insert a
149# backslash, as before.  The # and @ are never added automatically; you
150# have to select one by hand.
151#
152# Perforce allows change and revision numbers to be preceded by =, <, <=, >
153# or >=.  See `p4 help undoc' for details.  (In particular, `=' is
154# an extremely useful shortcut when integrating single changes.)
155# This syntax is handled, but currently the < and > must be quoted
156# with a backslash, not by any other mechanism.  For example,
157#   p4 files myfile@\>=3<TAB>
158# will complete a change number.  The valid syntax where the second
159# change or revision in a range does not have the @ or # in front
160# (for example `file@32183,32185') is not currently handled; the @
161# must be repeated.
162#
163# File completion for some functions is restricted by the Perforce
164# status of the file; for example, `p4 opened' only completes opened
165# files (surprised?)  However, you can set the style (N.B. not tag)
166# `all-files'; so, for example, you can turn off the limit in this case by
167#   zstyle ':completion:*:p4-opened:*' all-files true
168# Normally the `file-patterns' style would be used to control matching,
169# but as the file types are not selected by globbing it doesn't work here
170# However, if you set the all-files style, all filename completion is done
171# by the standard mechanism; in this case, the `file-patterns' style works
172# as usual.  The style `ignored-patterns' is available in any case, even
173# without `all-files'; this is therefore generally the one to use.
174#
175# The style `whole-path' allows you complete the entire path to a file
176# at once.  This is useful in cases such as opened files where the
177# list of files is likely to be short but may include files with
178# widely different paths.  As with the `glob' style, the tag is the
179# Perforce disposition of the file: integrated, opened, resolved, dirs,
180# files.  For example, with
181#   zstyle ':completion:*:p4-revert:*:opened' whole-path true
182# completion after `p4 revert' will offer you the entire depot path
183# to a file rather than just part of the path at once (with the
184# usual methods of disambiguation).  Directory completion is turned
185# off during a `whole-path' completion.  The `whole-path' style can
186# also take the value `absolute'; this means that an initial `/'
187# activates `whole-path' completion, otherwise a relative file path
188# will be completed in the normal way.  For example, with
189#   zstyle ':completion:*:p4-revert:*:opened' whole-path absolute
190# then after `p4 revert <TAB>' you are offered open files in the
191# current directory plus directories; after `p4 revert /<TAB>' you
192# are offered all open files in depot syntax.
193#
194# With `p4 diff', the shell will spot if you have used an option that
195# allows you to diff unopened files (such as -f) and in that case offer
196# all files; otherwise, it just offers opened files.
197#
198# Completion of changes
199# =====================
200#
201# There is various extra magic available any time change numbers
202# are completed, regardless of how this was reached, i.e.
203# `p4 fixes -c ...' and `p4 diff filename.c@...' are treated the same way.
204# Note, however, these only work if you are at the point where a change
205# number would be completed.
206#
207# Firstly, as mentioned above there is a maximum for the number of
208# changes which will be shown, given by the style max, or defaulting to 20.
209# Only the most recent changes will be shown.  This is to avoid a speed
210# penalty or clumsy output.  If a positive numeric argument is given
211# when changes are being completed, the maximum is set (unconditionally)
212# to that number instead.
213#
214# It is also possible to give a negative numeric prefix to a listing widget
215# (i.e. typically whatever is bound to ^D).  If there is already a change
216# number on the line, e.g. from cycling through a menu of choices, the full
217# description for that change is shown in the format of a completion
218# listing.  [TODO: this could be made configurable with a style.]
219#
220# It may be necessary to abandon the current completion attempt before
221# typing this to force the completion system to display the new text.
222# Replacing delete-char-or-list with the following user defined widget
223# (create with `zle -N ...') will force this for any negative prefix argument.
224#    (( ${NUMERIC:-0} < 0 )) && (( CURSOR = CURSOR ))
225#    zle delete-char-or-list
226#
227# Completion of jobs
228# =================
229#
230# Completing jobs uses the same logic for the numeric prefix as completing
231# changes: a positive prefix changes the maximum number of jobs which
232# will be shown, and a negative prefix when listing shows the full
233# text for the job whose name is currently inserted on the command line.
234# In this case, the entire text of the word being completed is assumed
235# to constitute the job name (which is almost certainly correct).
236#
237# Completion of dates
238# ===================
239#
240# In a file revision specification it is possible to give a date
241# in the form file@YYYY/MM/DD:hh:mm:ss, which may be completed.  This
242# is ever so slightly less silly than it sounds.  Any component entered
243# by hand with the appropriate suffix will be ignored; any component
244# completed will be set to the current value.  Hence you can easily
245# specify, say, one month ago by using the completed value for all
246# components except the month and setting that to one less.  The shell
247# will also happily append the appropriate suffix if you try to complete
248# after anything which is already the appropriate width.  (Perforce
249# supports two-digit years, but these are confusing and no longer
250# particularly useful as they refer to the twentieth century, so
251# the shell does not.)
252#
253# Calls to p4
254# ===========
255#
256# Much of the information from Perforce is provided by calls to p4
257# commands.  This is done via the _call_program interface, as described
258# in the zshcompsys manual page.  Hence a suitable context with the
259# `command' style allows the user to take control of this call.
260# The tags used are the name of the p4 command, or in the case of
261# calls to help subcommands, `help-<subcommand>'.  Note that if the
262# value of the style begins with `-', the arguments to the perforce
263# command are appended to the remaining words of the style before calling
264# the command.
265#
266# Programmes taking p4-style arguments
267# ====================================
268#
269# It is possible to use the _perforce completion with other commands
270# which behave like a subcommand of p4 by setting the service type
271# to p4-<subcommand>.  For example,
272#   compdef _perforce p4cvsmap=p4-files
273# says that the command `p4cvsmap' takes arguments like `p4 files'.
274# Often the options will be different; if this is a problem, you
275# will need to write your own completer which loads _perforce and
276# calls its functions directly.  You can add -global to the end
277# of the service to say that the command also handles global
278# Perforce options, comme ca:
279#   compdef _perforce p4reopen=p4-job-global
280#
281# Anything more complicated should be modelled on one of the
282# _perforce_cmd_* handlers below.  To get this to work, the full
283# set of _perforce functions must be loaded; because of the way
284# autoloading works, a trick is required:  call "_perforce -l" which
285# causes the function to be executed, loading all the associated
286# functions as a side effect, but tells _perforce to return without
287# generating any completions.  For example, here is the completion
288# for my `p4desc' function which is an enhanced version of `p4 describe'
289# (without any handling for global Perforce arguments):
290#
291#  #compdef p4desc
292#
293#  _perforce -l
294#
295#  _arguments -s : \
296#    '-d-[select diff option]:diff option:((b\:ignore\ blanks c\:context d\:basic\ diff n\:RCS s\:summary u\:unified w\:ignore\ all\ whitespace))' \
297#    '-s[short form]' \
298#    '-j[select by job]:job:_perforce_jobs' \
299#    '*::change:_perforce_changes'
300#
301# To add handling of global options to this, see the end of the _perforce
302# function below.  Something like:
303#
304#   local -a _perforce_global_options _perforce_option_dispatch
305#   if _perforce_global_options; then
306#     _arguments -s : $_perforce_option_dispatch \
307#       '<other stuff>'
308#   fi
309#
310# TODO
311# ====
312#
313# No mechanism is provided for completely ignoring certain files not
314# handled by Perforce as with .cvsignore.  This could be done ad hoc.
315# However, the ignored-patterns style and the parameter $fignore are
316# of course applied as usual, so setting ignored-patterns for the
317# context `:completion:*:p4[-:]*' should work.
318
319_perforce() {
320  # rely on localoptions
321  setopt nonomatch
322  local p4cmd==p4 match mbegin mend
323  integer _perforce_cmd_ind
324
325  # Localise variables used at different levels of the hierarchy.
326  local _perforce_exclude_change
327
328  if [[ $1 = -l ]]; then
329    # Run to load _perforce and associated functions but do
330    # nothing else.
331    return
332  fi
333
334  if [[ $service = -value-* ]]; then
335    # Completing parameter value.
336    # Some of these --- in particular P4PORT --- don't need
337    # the perforce server.
338    case $compstate[parameter] in
339      (P4PORT)
340      _perforce_hosts_ports
341      ;;
342
343      (P4CLIENT)
344      _perforce_clients
345      ;;
346
347      (P4MERGE)
348      _command_names -e
349      ;;
350
351      (P4USER)
352      _perforce_users
353      ;;
354    esac
355    # We do not handle values anywhere else.
356    return
357  fi
358
359  if [[ $p4cmd = '=p4' ]]; then
360    _message "p4 executable not found: completion not available"
361    return
362  fi
363
364  # If we are at or after the command word, remember the
365  # global arguments to p4 as we will need to pass these down
366  # when generating completion lists.
367  # This is both an array and a function, but luckily I never
368  # get confused...
369  local -a _perforce_global_options
370  local -a _perforce_option_dispatch
371
372  # If we are given a service of the form p4-cmd, treat this
373  # as if it was after `p4 cmd'.  This provides an easy way in
374  # for scripts and functions that emulate the behaviour of
375  # p4 subcommands.  Note we don't shorten the command line arguments.
376  if [[ $service = p4-(#b)(*) ]]; then
377    local curcontext="$curcontext"
378    local p4cmd=$words[1] cmd=$match[1] gbl
379
380    if [[ $cmd = (#b)(*)-global ]]; then
381      # Handles global options.
382      cmd=$match[1]
383      _perforce_global_options && gbl=1
384    fi
385    if (( $+functions[_perforce_cmd_$cmd] )); then
386      curcontext="${curcontext%:*:*}:p4-${cmd}:"
387      if [[ -n $gbl ]]; then
388	# We are handling global Perforce options as well as the
389	# arguments to the specific command.
390	# To handle the latter, we need the command name, plus
391	# all the arguments for the command with the global options
392	# removed.  The function _perforce_service_dispatch handles
393	# this by unshifting the command ($p4cmd) into words,
394	# then dispatching for the Perforce subcommand $cmd.
395	#
396	# Has anyone noticed this is getting rather complicated?
397	_arguments -s : $_perforce_option_dispatch \
398	"*::p4-$cmd arguments: _perforce_service_dispatch $p4cmd $cmd"
399      else
400	_perforce_cmd_$cmd
401      fi
402      # Don't try to do full command handling.
403      return
404    else
405      _message "unhandled _perforce service: $service"
406      return 1
407    fi
408  fi
409
410  if [[ $service = p4d ]]; then
411    _arguments -s : \
412      '-d[run as daemon]' \
413      '-f[run as single threaded server]' \
414      '-i[run for inetd using sockets]' \
415      '-q[suppress startup message]' \
416      '-s[run as NT service]' \
417      '-xi[switch server database to unicode mode and quit]' \
418      '-xu[run database upgrade and quit]' \
419      '-c[run command and exit]:command of some sort: ' \
420      '-Id[specify description]:description: ' \
421      '-In[specify unique name]:name: ' \
422      '-jc[checkpoint, save and truncate journal]::optional prefix: ' \
423      '-jd[checkpoint, not saving journal]::optional file:_files' \
424      '-jj[save and truncate journal]::optional prefix: ' \
425      '-jr[incremental restore from checkpoint/journal]:'\
426'file:_files:file:_files' \
427      '-z[gzip checkpoint and journal files]' \
428      '-h[show help]' \
429      '-V[print server version]' \
430      '-A[set audit log ($P4AUDIT)]:audit file:_files' \
431      '-J[set journal file ($P4JOURNAL) or "off"]:journal file:_files' \
432      '-L[set error log ($P4LOG or stderr)]:error log:_files' \
433      '-p[set port ($P4PORT o perforce:1666)]:port:_perforce_hosts_ports' \
434      '-r[set root directory ($P4ROOT)]:root directory:_path_files -g "*(/)"' \
435      '-v[debug level]:level: '
436  elif _perforce_global_options; then
437    _arguments -s : $_perforce_option_dispatch \
438    '1:perforce command:_perforce_commands'
439  else
440    (( _perforce_cmd_ind-- ))
441    (( CURRENT -= _perforce_cmd_ind ))
442    shift $_perforce_cmd_ind words
443    _perforce_command_args
444  fi
445}
446
447
448#
449# Command and argument dispatchers
450#
451
452# Front end to _call_program to add in the global arguments
453# passed to p4.  The first argument is the tag, the remaining
454# arguments are passed to p4.  Typically the tag is the same
455# as the first p4 argument.
456(( $+functions[_perforce_call_p4] )) ||
457_perforce_call_p4() {
458  local cp_tag=$1
459  shift
460  # This is for our own use for parsing, and we need English output,
461  # so...
462  local +x P4LANGUAGE
463  _call_program $cp_tag command p4 "${_perforce_global_options[@]}" "$@"
464}
465
466
467# The list of commands is cached in _perforce_cmd_list, but we
468# only generate it via this function when we need it.
469(( $+functions[_perforce_gen_cmd_list] )) ||
470_perforce_gen_cmd_list() {
471  (( ${+_perforce_cmd_list} )) || typeset -ga _perforce_cmd_list
472  local hline line match mbegin mend
473  # Output looks like <tab>command-name<space>description in words...
474  # Ignore blank lines and the heading line beginning `Perforce...'
475  # Just gets run once, then cached, so don't bother optimising
476  # this to a grossly unreadable parameter substitution.
477  _perforce_cmd_list=()
478  _perforce_call_p4 help-commands help commands | while read -A hline; do
479    if (( ${#hline} < 2 )); then
480      if (( ${#_perforce_cmd_list} )); then
481	# Ignore comments after the main list of commands, separate by blank
482	# line.
483	break
484      else
485	continue
486      fi
487    fi
488    [[ $hline[1] = (#i)perforce ]] && continue
489    _perforce_cmd_list+=("${hline[1]}:${hline[2,-1]}")
490  done
491  # Also cache the server version for nefarious purposes.
492  _perforce_call_p4 info info | while read line; do
493    if [[ $line = (#b)"Server version: "*/*/(<->.<->)(.[^/]|)/*" "* ]]; then
494      _perforce_server_version=$match[1]
495    fi
496  done
497
498  # Unsupported commands: we could look through p4 help undoc, I suppose.
499  # I can't be bothered to check the date when they appeared any more,
500  # but let's at least check they're not already there.
501  local -a unsup
502  unsup=(
503    "attribute:set attributes for open file (EXPERIMENTAL)"
504    "dbschema:report meta database information"
505    "export:extract journal or checkpoint records"
506    "interchanges:report changes not yet integrated between branches"
507    "replicate:poll for journal changes and apply to another server"
508    "spec:allows limited changes to form specifications (admin)"
509  )
510  for line in $unsup; do
511    if [[ ${_perforce_cmd_list[(r)${line%%:*}:*]} = '' ]]; then
512      _perforce_cmd_list+=($line)
513    fi
514  done
515}
516
517
518(( $+functions[_perforce_commands] )) ||
519_perforce_commands() {
520  (( ${#_perforce_cmd_list} )) || _perforce_gen_cmd_list
521  _describe -t p4-commands 'Perforce command' _perforce_cmd_list
522}
523
524
525(( $+functions[_perforce_command_args] )) ||
526_perforce_command_args() {
527  local curcontext="$curcontext" cmd=${words[1]}
528  if (( $+functions[_perforce_cmd_$cmd] )); then
529    curcontext="${curcontext%:*:*}:p4-${cmd}:"
530    _perforce_cmd_$cmd
531  else
532    _message "unhandled perforce command: $cmd"
533  fi
534}
535
536
537(( $+functions[_perforce_service_dispatch] )) ||
538_perforce_service_dispatch() {
539  # Put the original command name back, then dispatch for
540  # our Perforce handler.
541  words=($1 "$words[@]")
542  (( CURRENT++ ))
543  _perforce_cmd_$2
544}
545
546
547#
548# Helper functions
549#
550
551(( $+functions[_perforce_global_options] )) ||
552_perforce_global_options() {
553  # Options with arguments we need to pass down when calling
554  # p4 from completers.  There are no options without arguments
555  # we need to pass.  (Don't pass down -L language since we
556  # parse based on English output.)
557  local argopts_pass="cCdHpPu"
558  # Other options which have arguments but we shouldn't pass down.
559  # There are some debugging options, but they tend to get used
560  # with the argument in the same word as the option, in which
561  # case they will be handled OK anyway.
562  local argopts_ignore="Lx"
563
564  # The options we support in the form for _arguments.
565  # This is here for modularity and convenience, but note that since the
566  # actual dispatch takes place later, this is not local to this
567  # function and so must be made local in the caller.
568  _perforce_option_dispatch=(
569    '-c+[client]:client:_perforce_clients' \
570    '-C+[charset]:charset:_perforce_charsets' \
571    '-d+[current directory]:directory:_path_files -g "*(/)"' \
572    '-H+[hostname]:host:_perforce_hosts' \
573    '-G[python output]' \
574    '-L+[message language]:language: ' \
575    '-p+[server port]:port:_perforce_hosts_ports' \
576    '-P+[password on server]:password: ' \
577    '-s[output script tags]' \
578    '-u+[user]:user name:_perforce_users' \
579    '-x+[filename or -]:file:_perforce_files_or_minus' \
580    '-z+[select output format]:output format:(tag)'
581  )
582
583  integer i
584
585  # We need to try and check if we are before or after the
586  # subcommand, since some of the options with arguments, in particular -c,
587  # work differently.  It didn't work if I just added '*::...' to the
588  # end of the arguments list, anyway.
589  for (( i = 2; i < CURRENT; i++ )); do
590    if [[ $words[i] = -[$argopts_pass$argopts_ignore] ]]; then
591      # word with following argument --- check this
592      # is less than the current word, else we are completing
593      # this and shouldn't pass it down
594      if [[ $(( i + 1 )) -lt $CURRENT && \
595	$words[i] = -[$argopts_pass] ]]; then
596	_perforce_global_options+=(${words[i,i+1]})
597      fi
598      (( i++ ))
599    elif [[ $words[i] = -[$argopts_pass]* ]]; then
600      # word including argument which we want to keep
601      _perforce_global_options+=(${words[i]})
602    elif [[ $words[i] != -* ]]; then
603      break
604    fi
605  done
606
607  (( _perforce_cmd_ind = i ))
608  (( _perforce_cmd_ind >= CURRENT ))
609}
610
611(( $+functions[_perforce_branches] )) ||
612_perforce_branches() {
613  local bline match mbegin mend
614  local -a bl
615  bl=(${${${${(f)"$(_perforce_call_p4 branches branches 2>/dev/null)"}##Branch }//:/\\:}/ /:})
616  [[ $#bl -eq 1 && $bl[1] = '' ]] && bl=()
617  (( $#bl )) && _describe -t branches 'Perforce branch' bl
618}
619
620
621(( $+functions[_perforce_changes] )) ||
622_perforce_changes() {
623  local cline match mbegin mend max ctype num comma file
624  local -a cl cstatus amax xargs
625
626  zstyle -s ":completion:${curcontext}:changes" max max || max=20
627  zstyle -a ":completion:${curcontext}:changes" changes xargs
628  if [[ ${NUMERIC:-0} -lt 0 && -z $compstate[insert] ]]; then
629    # Not inserting (i.e. just listing) and given a negative
630    # prefix argument.  Instead of listing possible completions,
631    # show the full description for the change number on the line at
632    # the moment.
633    [[ $PREFIX = (|*[^[:digit:]])(#b)(<->) ]] && num+=$match[1]
634    [[ $SUFFIX = (#b)(<->)* ]] && num+=$match[1]
635    if [[ -n $num ]]; then
636      _message -r "$(_perforce_call_p4 describe describe $num)"
637      return 0
638    fi
639  elif [[ ${NUMERIC:-0} -gt 0 ]]; then
640    max=$NUMERIC
641  fi
642
643  (( max )) && amax=(-m $max)
644
645  # Hack: assume the arguments we want are at the end.
646  while [[ $argv[-1] = -t? ]]; do
647    case $argv[-1] in
648      # Change embedded in filename; extract that and remove
649      # the corresponding prefix.  Remove possible `#'s, too,
650      # in case we are looking at a range.
651      (-tf)
652      file=${${(Q)PREFIX}%%[\#@]*}
653      compset -P '*@(|\\\<|\\\>)(|=)'
654      ;;
655
656      # Changes already submitted
657      (-ts)
658      cstatus=(-s submitted)
659      ctype="submitted "
660      ;;
661
662      # Changes still pending
663      (-tp)
664      cstatus=(-s pending)
665      ctype="pending "
666      ;;
667
668      # Changes still pending and on the current client.
669      # Many uses of pending changes must be on the current client
670      # to be meaningful, in particular submitting a change or
671      # associating a file or operation with a particular change.
672      (-tc)
673      # Don't like this way of extracting the client to use,
674      # but I don't see a simpler one.  We do at least make sure
675      # we call p4 with the same arguments as we will use with p4 changes.
676      cstatus=(-s pending -c "$(_perforce_call_p4 client client -o |
677awk '/^Client:/ { print $2 }')")
678      ctype="local pending "
679      ;;
680
681      # Changes that were shelved
682      (-tS)
683      cstatus=(-s shelved)
684      ctype="shelved "
685      ;;
686
687      # Range allowed: append comma and supply rules for
688      # removing and handling subsequent `#'.
689      (-tR)
690      comma=(-S, -R _perforce_file_suffix)
691    esac
692    argv=($argv[1,-2])
693  done
694  # Limit to the 20 most recent changes by default to avoid huge
695  # output.
696  cl=(
697${${${${${(f)"$(_perforce_call_p4 changes changes $amax $xargs $cstatus \$file)"}##Change\ }//:/\\:}//\ on\ /:}/\ by\ /\ }
698  )
699  # "default" can't have shelved files in it...
700  [[ $ctype = shelved* ]] || cl+=("default:change not yet numbered")
701  [[ $#cl -eq 1 && $cl[1] = '' ]] && cl=()
702  _describe -t changes "${ctype}change" cl -V changes-unsorted $comma
703}
704
705
706(( $+functions[_perforce_charsets] )) ||
707_perforce_charsets() {
708  local expl
709  _wanted charset expl 'character set' \
710    compadd eucjp iso8859-1 shiftjis utf8 winansi
711}
712
713
714(( $+functions[_perforce_clients] )) ||
715_perforce_clients() {
716  local -a slash cl
717
718  # Are we completing after an @, or a client view in a filespec?
719  if ! compset -P '*@'; then
720    compset -P '//' && slash=(-S/ -q)
721  fi
722
723  cl=(${${${${(f)"$(_perforce_call_p4 clients clients)"}##Client\ }//:/\\:}/\ /:})
724  [[ $#cl -eq 1 && $cl[1] = '' ]] && cl=()
725  _describe -t clients 'Perforce client' cl $slash
726}
727
728
729(( $+functions[_perforce_counters] )) ||
730_perforce_counters() {
731  local cline match mbegin mend
732  local -a cl
733
734  cl=(${${${${(f)"$(_perforce_call_p4 counters counters)"}//:/\\:}/\ /:}/\=/current value})
735  [[ $#cl -eq 1 && $cl[1] = '' ]] && cl=()
736  _describe -t counters 'Perforce counter' cl
737}
738
739
740(( $+functions[_perforce_counter_values] )) ||
741_perforce_counter_values() {
742  if [[ -n $words[CURRENT-1] ]]; then
743    local value="$(_perforce_call_p4 counter counter $words[CURRENT-1] 2>/dev/null)"
744    if [[ -n $value ]]; then
745      # No space.  This allows stuff like incarg and decarg.
746      compstate[insert]=1
747      _wanted value expl 'counter value' compadd $value
748    fi
749  fi
750}
751
752
753(( $+functions[_perforce_dates] )) ||
754_perforce_dates() {
755  # Only useful in a file spec after `@'.
756  compset -P '*@(|\\\<|\\\>)(|=)'
757
758  # Date/time now in format required by Perforce.
759  local now="$(date +%Y:%m:%d:%T)" name prefix
760  local -a nowarray offer opts matchpats suffixes names
761  nowarray=(${(s.:.)now})
762
763  names=(   year month day\ of\ month hour minute second)
764  suffixes=(  /    /    :               :     :     ''  )
765
766  integer i
767  prefix=${(Q)PREFIX}
768  for (( i = 6; i >= 1; i-- )); do
769    # Match from the most specific back.
770    # The following is one of those occasions where zsh
771    # substitution skips to the right answer without ever
772    # passing through the real world on the way.
773    if [[ $prefix = *${(j.*.)~suffixes[1,i-1]}* ]]; then
774      (( i > 1 )) && compset -P "*$suffixes[i-1]"
775      # If what's there already is the right length,
776      # just accept it and add the suffix.
777      prefix=${(Q)PREFIX}
778      if [[ ${#prefix} = ${#nowarray[i]} ]]; then
779	offer=($prefix)
780      else
781	offer=($nowarray[i])
782      fi
783      [[ -n $suffixes[i] ]] && opts=(-S $suffixes[i] -q)
784      name=$names[i]
785      break
786    fi
787  done
788
789  _describe -t dates $name offer $opts
790}
791
792
793(( $+functions[_perforce_dbtables] )) ||
794_perforce_dbtables() {
795  local -a tables
796  tables=(archmap bodtext change changex counters depot domain have integ
797    integed ixtext label locks resolve rev revcx revdx revhx revsx trigger
798    user view working)
799  _describe -t db-table "DB table" tables
800}
801
802
803(( $+functions[_perforce_depots] )) ||
804_perforce_depots() {
805  local dline match mbegin mend
806  local -a dl
807
808  dl=(${${${${(f)"$(_perforce_call_p4 depots depots)"}##Depot\ }//:/\\:}/\ /:})
809  [[ $#dl -eq 1 && $dl[1] = '' ]] && dl=()
810  _describe -t depots 'depot name' dl
811}
812
813
814(( $+functions[_perforce_files_or_minus] )) ||
815_perforce_files_or_minus() {
816  _alternative 'minus:minus sign:(-)' 'files:file name:_files'
817}
818
819
820(( $+functions[_perforce_file_suffix] )) ||
821_perforce_file_suffix() {
822  # Used with compadd -R to handle @ or # after a file name.
823  # Differs from compadd -r '...' in that it quotes `#' if typed.
824  [[ $1 = 1 ]] || return
825
826  if [[ $LBUFFER[-1] = [\ ,] ]]; then
827    if [[ $KEYS = '#' ]]; then
828      if [[ $LBUFFER[-1] = , ]]; then
829	# Range: no suffix removal but add a backslash
830	LBUFFER+=\\
831	  else
832	# Suffix removal with an added backslash
833	LBUFFER="$LBUFFER[1,-2]\\"
834      fi
835    elif [[ $KEYS = (*[^[:print:]]*|[[:blank:]\;\&\|]) || \
836      ( $KEYS = @ && $LBUFFER[-1] = ' ' ) ]] ; then
837      # Normal suffix removal
838      LBUFFER="$LBUFFER[1,-2]"
839    fi
840  elif [[ $LBUFFER[-1] = / ]]; then
841    # Normal suffix removal for directories.
842    if [[ $KEYS = (*[^[:print:]]*|[[:blank:]\;\&\|/]) ]]; then
843      LBUFFER="$LBUFFER[1,-2]"
844    fi
845  fi
846}
847
848
849# Helper function for the helper function for the helper functions
850# for the helper function _perforce_files.
851#
852# Check if we should do whole-path completion.
853# The argument is the Perforce disposition of files are looking at.
854_perforce_whole_path() {
855  local wp
856
857  zstyle -s ":completion:${curcontext}:$1" whole-path wp
858  case $wp in
859    (true|yes|on|1)
860    return 0
861    ;;
862
863    (absolute)
864    [[ ${(Q)PREFIX} = /* ]] && return 0
865    ;;
866  esac
867
868  return 1
869}
870
871#
872# Helper function for the helper function _perforce_retreive_files
873# for the helper functions for the helper function _perforce files.
874#
875# This code retrieves the list of files with a glob filter and
876# possibly a line filter specified by the caller.  The line filter
877# must match the entire line.
878#
879# Result returned in $files.
880#
881(( $+functions[_perforce_filter_files] )) ||
882_perforce_filter_files() {
883  local call="$1"
884  local globfilter="$2"
885  local linefilter="$3"
886  local line
887
888  if [[ -n $linefilter ]]; then
889    files=(
890      ${${${(f)"$(_perforce_call_p4 $call $call $globfilter 2>/dev/null)"}:#${~linefilter}}%%\#*}
891    )
892  else
893    files=(
894      ${${(f)"$(_perforce_call_p4 $call $call $globfilter 2>/dev/null)"}%%\#*}
895    )
896  fi
897}
898
899#
900# Helper function for the helper functions for the helper function
901# _perforce_files.  This is common code to retrieve a list of files
902# from Perforce.
903#
904# First argument is the p4 subcommand used to list the files.
905# This is also used as a tag for the style to decide whether
906# to limit the list within Perforce. It may have a colon
907# followed by a shell pattern to match lines to be excluded
908# from the match.
909#
910# Remaining arguments are additional arguments to compadd.
911#
912(( $+functions[_perforce_retrieve_files] )) ||
913_perforce_retrieve_files() {
914  local pfx exclude
915  local -a files match mbegin mend
916
917  if [[ $1 = (#b)([^:]##):(*) ]]; then
918    1=$match[1]
919    exclude=$match[2]
920  fi
921
922  if _perforce_whole_path $1; then
923    _perforce_filter_files $1 '' $exclude
924  elif zstyle -t ":completion:${curcontext}:$1" glob; then
925    # Limit the list by using Perforce to glob the pattern.
926    # This may be faster, but won't use matcher specs etc.
927    pfx=${(Q)PREFIX}
928    compset -P '*/'
929    _perforce_filter_files $1 '"$pfx*${(Q)SUFFIX}"' $exclude
930    files=(${files##*/})
931  else
932    # We need to limit the list to a directory.
933    if [[ $PREFIX = */* ]]; then
934      pfx="${(Q)${PREFIX%/*}}/*"
935    else
936      pfx="*"
937    fi
938    compset -P '*/'
939    _perforce_filter_files $1 '$pfx' $exclude
940    files=(${files##*/})
941  fi
942  [[ $#files -eq 1 && $files[1] = '' ]] && files=()
943  shift
944  compadd "$@" -a files
945}
946
947
948#
949# Helper functions for the helper function _perforce_files.  These files
950# are low-level enough that they don't handle tags; this is done
951# by the _alternative handler in _perforce_files.
952#
953
954(( $+functions[_perforce_integrated_files] )) ||
955_perforce_integrated_files() {
956  local pfx=${(Q)PREFIX} type
957  local -a files
958
959  _perforce_retrieve_files integrated "$@"
960}
961
962
963(( $+functions[_perforce_opened_files] )) ||
964_perforce_opened_files() {
965  local csuf
966
967  if [[ -n $_perforce_exclude_change ]]; then
968    if [[ $_perforce_exclude_change = default ]]; then
969      csuf=":* default change [^#]#"
970    else
971      csuf=":* change $_perforce_exclude_change [^#]#"
972    fi
973  fi
974
975  _perforce_retrieve_files opened$csuf "$@"
976}
977
978
979(( $+functions[_perforce_resolved_files] )) ||
980_perforce_resolved_files() {
981  _perforce_retrieve_files resolved "$@"
982}
983
984
985# This has no other function than to offer to add the `...' used
986# by Perforce to indicate a recursive search of directories.
987# Bit pathetic, really.
988#
989# This has been causing odd effects with prefixes so is turned off.
990#(( $+functions[_perforce_subdirs] )) ||
991#_perforce_subdirs() {
992#  if [[ $PREFIX = */* ]]; then
993#    local dir=${PREFIX%%[^/]#}
994#    compadd "$@" -J subdirs -p $dir ...
995#  else
996#    compadd "$@" -J subdirs ...
997#  fi
998#}
999
1000
1001(( $+functions[_perforce_depot_dirs] )) ||
1002_perforce_depot_dirs() {
1003  # Normal completion of directories in depots
1004
1005  _perforce_retrieve_files dirs "$@" -S / -q
1006}
1007
1008
1009(( $+functions[_perforce_depot_files] )) ||
1010_perforce_depot_files() {
1011  # Normal completion of files in depots
1012
1013  _perforce_retrieve_files files "$@" -R _perforce_file_suffix
1014}
1015
1016
1017(( $+functions[_perforce_client_dirs] )) ||
1018_perforce_client_dirs() {
1019  # This is a slightly odd addition which isn't often necessary.
1020  # When completing directories in a client specification, Perforce
1021  # doesn't tell you about intermediate directories which are in
1022  # the client, but not in the depot.  (Well... sometimes.  I've
1023  # had some odd results with this.  I suspect there may be a bug
1024  # but I don't really know enough to be sure.)
1025  #
1026  # For example, if my view contains
1027  #   //depot/branches/rev1.2/...   //pws_client/branches/rev1.2/...
1028  # then `p4 dirs "//pws_client/*"' won't mention the `branches'
1029  # directory because the view actually starts lower down.  So
1030  # we add it by hand when necessary.
1031  #
1032  # We don't want to waste time on this, since it's not the usual
1033  # case, so we cache the results where necessary.  This means
1034  # recording all the clients that we can later ask about if necessary.
1035  # To flush the cache, `unset _perforce_client_list _perforce_client_dirs'.
1036  if (( ! ${+_perforce_client_list} )); then
1037    # Retrieve the list of clients.
1038    typeset -gA _perforce_client_list
1039    local -a tmplist
1040    local tmpelt
1041    tmplist=(${${${(f)"$(_perforce_call_p4 clients clients)"}##Client\ }%%\ *})
1042    [[ $#tmplist -eq 1 && $tmplist[1] = '' ]] && tmplist=()
1043    for tmpelt in $tmplist; do
1044      _perforce_client_list[$tmpelt]=1
1045    done
1046  fi
1047
1048  # See if the first path element is a client.  Very often it
1049  # will actually be a depot, so we test this as quickly as possible.
1050  local client=${${PREFIX##//}%%/*}
1051  [[ -z ${_perforce_client_list[$client]} ]] && return 1
1052
1053  local oldifs=$IFS IFS= type dir line dirs
1054
1055  (( ${+_perforce_client_dirs} )) || typeset -gA _perforce_client_dirs
1056
1057  if (( ${+_perforce_client_dirs[$client]} )); then
1058    # Already cached, although may be empty.
1059    dirs=${_perforce_client_dirs[$client]}
1060  else
1061    # We need to look at the View stanza of the client record
1062    # to see what directories exist in the client view.
1063    _perforce_call_p4 client "client -o $client" 2>/dev/null | while read line
1064    do
1065      case $line in
1066	([[:blank:]]##)
1067	type=
1068	;;
1069
1070	((#b)([[:alpha:]]##):*)
1071	type=${match[1]}
1072	;;
1073
1074	(*)
1075	if [[ $type = View ]]; then
1076	  dir=${${line##[[:blank:]]##//*[[:blank:]]//$client}%%/...(/*|)}
1077	  if [[ $#dir -gt 1 ]]; then
1078	    dirs+="${dirs:+ }${(q)dir##/}"
1079	  fi
1080	fi
1081	;;
1082      esac
1083    done
1084  fi
1085
1086  (( ${#dirs} )) || return 1
1087
1088  # Turn our string of space-separated backquoted elements into an array.
1089  dirs=(${(z)dirs})
1090  # Get the current prefix also as an array of elements
1091  compset -P '//[^/]##/'
1092  pfx=(${(s./.)${(Q)PREFIX}})
1093
1094  local -a ndirs
1095  local match mbegin mend
1096  # Check matching path segments
1097  while (( ${#pfx} > 1 )); do
1098    ndirs=()
1099    for dir in $dirs; do
1100      if [[ $dir = $pfx/(#b)(*) ]]; then
1101	ndirs+=($match[1])
1102      fi
1103    done
1104    (( ${#ndirs} )) || return 1
1105    dirs=($ndirs)
1106    shift pfx
1107    compset -P '[^/]'
1108  done
1109  compadd -S / -q "$@" -- ${dirs%%/*}
1110}
1111
1112(( $+functions[_perforce_files] )) ||
1113_perforce_files() {
1114  local pfx fline expl opt match mbegin mend range type
1115  local -a files types
1116
1117  local dodirs unmaintained
1118  # Suffix operations can modify context
1119  local curcontext="$curcontext"
1120  # Used to inhibit directory completion
1121  local nodirs
1122
1123  while (( $# )); do
1124    if [[ $1 = -t(#b)(?) ]]; then
1125      case $match[1] in
1126	(d)
1127	dodirs=-/
1128	;;
1129
1130	(u)
1131	unmaintained=1
1132	;;
1133
1134	(i)
1135	types+=(integrated)
1136	;;
1137
1138	(o)
1139	types+=(opened)
1140	;;
1141
1142	(r)
1143	types+=(resolved)
1144	;;
1145
1146	(R)
1147	range="-tR"
1148	;;
1149      esac
1150    fi
1151    shift
1152  done
1153
1154  # Remove the quotes present in the word on the command line,
1155  # since we will treat this as a literal string from now on.
1156  # We might get into problems with characters recognised as
1157  # special by p4 files and p4 dirs, but worry about that later.
1158  pfx=${(Q)PREFIX}
1159
1160  if [[ -prefix *@ ]]; then
1161    # Modify context to indicate we are in a suffix.
1162    curcontext="${curcontext%:*}:at-suffix"
1163    # Check for existing range syntax
1164    [[ $PREFIX = *[@\#]*,* ]] && range=
1165    # After @ you can specify changes, clients, labels or dates.
1166    # Note we don't remove the prefix here; we leave it to the
1167    # subcommand.  This is in case it needs information from
1168    # the prefix; _perforce_changes uses this to limit the
1169    # output to relevant changes.
1170    _alternative \
1171      "changes:change:_perforce_changes $range -tf" \
1172      clients:client:_perforce_clients \
1173      "labels:label:_perforce_labels -tf" \
1174      'dates:date (+ time):_perforce_dates'
1175  elif [[ -prefix *\# ]]; then
1176    # Modify context to indicate we are in a suffix.
1177    curcontext="${curcontext%:*}:hash-suffix"
1178    # Check for existing range syntax
1179    [[ $PREFIX = *[@\#]*,* ]] && range=
1180    # Remove longest possible tail match to get name --- this
1181    # automatically handles filenames in ranges e.g. `foo#1,#3'.
1182    # (Note the compset removes the maximum possible head match,
1183    # so we only complete the second part of the range in that case.)
1184    _perforce_revisions $range
1185  elif [[ $PREFIX = //* ]]; then
1186    # This specifies files already handled by Perforce, so there's
1187    # no point trying to look for unmaintained files.  Assume
1188    # the user knows what they're doing.
1189    local -a altfiles
1190    integer whole_path
1191
1192    for type in $types; do
1193      _perforce_whole_path $type && whole_path=1
1194    done
1195
1196    # If we're doing whole-path completion, and the user starts
1197    # a completion early, assume they want just those files,
1198    # rather than a client spec.  This isn't necessarily the case,
1199    # but there's an excellent chance it does fit the user's intention
1200    # in a case where it's not really worth adding a special option.
1201    # A client list can be huge and they're not actually used very
1202    # often to refer to files.  In fact, this whole completion
1203    # probably ought to be optional (you can do it with tags if
1204    # you really want).
1205    if [[ $PREFIX = //[^/]# && $whole_path -eq 0 ]]; then
1206      # Complete //clientname or //depot spec.
1207      # Don't complete non-directories...
1208      # I don't actually know if they are valid here.
1209      altfiles+=("clients:Perforce client:_perforce_clients"
1210	"depots:Perforce depot:_perforce_depots")
1211    else
1212      local donefiles=1
1213      if [[ -z $dodirs ]]; then
1214	if [[ ${#types} -gt 0 ]] &&
1215	  ! zstyle -t ":completion:${curcontext}:" all-files; then
1216	  for type in $types; do
1217	    altfiles+=("$type-files:$type file:_perforce_${type}_files")
1218	  done
1219	  (( whole_path )) && nodirs=1
1220	else
1221	  altfiles+=("depot-files:file in depot:_perforce_depot_files")
1222	fi
1223      fi
1224      if [[ -z $nodirs ]]; then
1225	# Intermediate directories in a client view.
1226	# See function for notes.
1227	altfiles+=("client-dirs:client directory:_perforce_client_dirs")
1228      fi
1229    fi
1230    altfiles+=("depot-dirs:directory in depot:_perforce_depot_dirs"
1231#      "subdirs:subdirectory search:_perforce_subdirs"
1232    )
1233    _alternative $altfiles
1234  elif [[ -n $unmaintained ]]; then
1235    # As directories are always umaintained, but may contain files
1236    # we want to add, we'll always complete directories here.  That's
1237    # neater than the alternative of excluding them here and requesting
1238    # them separately in the caller.  The only client for this
1239    # branch is currently 'p4 add'.
1240    #
1241    # Unmaintained files can't be integrated, opened
1242    # or resolved, so treat as exclusive to other options (just as well, since
1243    # this bit's messy).
1244    local MATCH MBEGIN MEND
1245    local -a omitpats
1246
1247    match=()
1248    : ${PREFIX:#(#b)(*/)(*)}
1249    pfx="$match[1]"
1250    pfx=${(e)~pfx}
1251    # Exclude both files already known to perforce, plus
1252    # those opened.  There will be some overlap but we need
1253    # to exclude files that are already opened for add.
1254    omitpats=(
1255      ${${${${(f)"$(_perforce_call_p4 files files \"\$pfx\*\$\{\(Q\)SUFFIX\}\" 2>/dev/null)"}%\#*}##*/}//(#m)[][*?()<|^~#\\]/\\$MATCH}
1256      ${${${${(f)"$(_perforce_call_p4 opened opened \"\$pfx\*\$\{\(Q\)SUFFIX\}\" 2>/dev/null)"}%\#*}##*/}//(#m)[][*?()<|^~#\\]/\\$MATCH}
1257	)
1258
1259    [[ $#omitpats -eq 1 && $omitpats[1] = '' ]] && omitpats=()
1260    if (( ${#omitpats} )); then
1261      _path_files -g "*~(*/|)(${(j:|:)~omitpats})(D)"
1262    else
1263      _path_files
1264    fi
1265    # Don't handle suffixes for non-entried files
1266  elif (( ${#types} )) && ! zstyle -t ":completion:${curcontext}:" all-files
1267    then
1268    local -a altfiles
1269
1270    for type in $types; do
1271      altfiles+=("$type-files:$type file:_perforce_${type}_files")
1272      _perforce_whole_path $type && nodirs=1
1273    done
1274
1275    if [[ -z $nodirs ]]; then
1276#      altfiles+=("subdirs:subdirectory search:_perforce_subdirs")
1277      if zstyle -t ":completion:${curcontext}:" depot-files; then
1278	altfiles+=("depot-dirs:directory in depot:_perforce_depot_dirs")
1279      else
1280	altfiles+=("directories:directory:_path_files -/")
1281      fi
1282    fi
1283    _alternative $altfiles
1284  elif zstyle -t ":completion:${curcontext}:" depot-files; then
1285    local -a altfiles
1286    if [[ -z $dodirs ]]; then
1287      altfiles+=("depot-files:file in depot:_perforce_depot_files")
1288    fi
1289    altfiles+=("depot-dirs:directory in depot:_perforce_depot_dirs"
1290#      "subdirs:subdirectory search:_perforce_subdirs"
1291    )
1292    _alternative $altfiles
1293  else
1294    # Look locally.
1295#   _alternative \
1296#      "files:file:_path_files -R _perforce_file_suffix $dodirs" \
1297#      "subdirs:subdirectory search:_perforce_subdirs"
1298    _path_files -R _perforce_file_suffix $dodirs
1299  fi
1300}
1301
1302
1303#
1304# Remaining helpers for other types of Perforce metadata.
1305#
1306
1307(( $+functions[_perforce_filetypes] )) ||
1308_perforce_filetypes() {
1309  local -a values
1310  if compset -P '*+*'; then
1311    # That second `*' is deliberate --- only complete the last
1312    # letter since we can have a whole string of them.
1313    values=(
1314      "m:always set modtime on client"
1315      "w:always writeable on client"
1316      "x:set exec bit on client"
1317      "k:full RCS keyword expansion"
1318      "ko:RCS expansion only for Id, Header"
1319      "l:exclusive open, disallow multiple opens"
1320      "C:server stores compress file per revision"
1321      "D:server stores deltas in RCS format"
1322      "F:server stores full file per revision"
1323      "S:server stores only head revision"
1324      "X:externally archived file"
1325    )
1326    _describe -t file-modifiers 'Perforce file modifier' values
1327  else
1328    values=(
1329      "text:text, translate newlines"
1330      "binary:raw bytes"
1331      "symlink:symbolic link"
1332      "apple:Mac resource + data"
1333      "unicode:text, translate newlines, store as UTF-8")
1334    _describe -t file-types 'Perforce file type' values -S+ -q
1335  fi
1336}
1337
1338
1339(( $+functions[_perforce_fstat_fields] )) ||
1340_perforce_fstat_fields() {
1341  local sep
1342  if [[ $argv[-1] = -tv ]]; then
1343    # jobview, space is separator
1344    sep=' '
1345  else
1346    sep=','
1347  fi
1348  local -a values
1349  # yes, "phew", sorry.
1350  # output from "p4 help fstat" gives fields like
1351  #    digest               -- MD5 digest (fingerprint)
1352  # etc. etc.
1353  values=(
1354    ${${${${(M)${(f)"$(_perforce_call_p4 help-fstat help fstat)"}:#[[:blank:]]#[a-zA-Z]##(|\#)[[:blank:]]##--*}##[[:blank:]]#}:#fstat *}//[[:blank:]]##--[[:blank:]]##/:}
1355  )
1356  compset -P '*[,[:blank:]]'
1357  _describe -t fstat-fields 'Perforce fstat fields' values -S, -q
1358}
1359
1360
1361(( $+functions[_perforce_groups] )) ||
1362_perforce_groups() {
1363  local -a values
1364  values=($(_perforce_call_p4 groups groups))
1365  _describe -t groups 'Perforce group' values
1366}
1367
1368
1369(( $+functions[_perforce_hosts] )) ||
1370_perforce_hosts() {
1371  local expl host
1372  # Completion for p4 -H; other forms of host completion
1373  # go through _perforce_hosts_ports.
1374  # From Felix: if the client specifies a hostname, there's
1375  # no point using any other host, since it won't work.
1376  host=$(_perforce_call_p4 client client -o |
1377    awk '$1 ~ /^Host:/ {print $2}' )
1378  if [[ -n $host ]]; then
1379    _wanted hosts expl host compadd "$@" $host
1380  else
1381    _hosts
1382  fi
1383}
1384
1385
1386(( $+functions[_perforce_hosts_ports] )) ||
1387_perforce_hosts_ports() {
1388  if compset -P '*:'; then
1389    _ports
1390    local expl
1391    _wanted ports expl port compadd "$@" 1666
1392  else
1393    # is this -q-able?
1394    _hosts -S :
1395  fi
1396}
1397
1398(( $+functions[_perforce_jobs] )) ||
1399_perforce_jobs() {
1400  # Optional argument is jobview for limiting jobs.
1401  local jline match mbegin mend max jobview
1402  local -a jl amax ajobview
1403
1404  zstyle -s ":completion:${curcontext}:jobs" max max || max=20
1405  # Hack: if there is a job view, it is at the end.
1406  # This is nasty, it's really unnecessarily difficult to
1407  # pass arguments within completion functions...
1408  if [[ $argv[-2] = -e ]]; then
1409    ajobview=(-e "${(q)argv[-1]}")
1410    argv=("${(@)argv[1,-3]}")
1411  elif zstyle -s ":completion:${curcontext}:jobs" jobview jobview; then
1412    ajobview=(-e "'$jobview'")
1413  elif [[ -n $PREFIX ]]; then
1414    # Extra quotes for the benefit of _call_program which does an "eval".
1415    ajobview=(-e "'job=$PREFIX\\*$SUFFIX'")
1416  fi
1417  if [[ ${NUMERIC:-0} -lt 0 && -z $compstate[insert] ]]; then
1418  # Not inserting (i.e. just listing) and given a negative
1419  # prefix argument.  Instead of listing possible completions,
1420  # show the full description for the job which is on the line at
1421  # the moment.
1422    _message -r "$(_perforce_call_p4 jobs jobs -e \"'Job=\$PREFIX\$SUFFIX'\" -l 2>/dev/null)"
1423    return 0
1424  elif [[ ${NUMERIC:-0} -gt 0 ]]; then
1425    max=$NUMERIC
1426  fi
1427
1428  (( max )) && amax=(-m $max)
1429
1430  _perforce_call_p4 jobs jobs $ajobview $amax | while read jline; do
1431    if [[ $jline = (#b)([^[:blank:]]##)' '[^[:blank:]]##' '(*) ]]; then
1432      jl+=("${match[1]}:${match[2]}")
1433    fi
1434  done
1435  _describe -t jobs 'Perforce job' jl -V jobs-unsorted
1436}
1437
1438(( $+functions[_perforce_jobviews] )) ||
1439_perforce_jobviews() {
1440  # Jobviews (see `p4 help jobview') are ways of interrogating the
1441  # jobs/fixes database.  It's basically either a set of strings,
1442  # or a set of key=value pairs, or some combination, separated
1443  # by various logical operators.  The `=' could be a comparison,
1444  # but we don't currently bother with that here; it's a bit cumbersome
1445  # to complete.
1446  local line type oldifs=$IFS IFS= key value slash=/
1447  local match mbegin mend
1448  # This is simply to split out two space-delimited words a backreferences.
1449  local m2words
1450  m2words='(#b)[[:blank:]]##([[:alnum:]]##)[[:blank:]]##([^[:blank:]]##)'
1451
1452  local -a valuespec
1453  local -A p4fields p4values
1454
1455  # All the characters which can separate multiple match attempts.
1456  # Ignore up to the last one.  We don't try to complete these.
1457  compset -P '*[[:blank:]\^\&\|\(\)]'
1458
1459  # According to the manual, `p4 jobspec' requires admin privileges.
1460  # If this is true even of `p4 jobspec -o', we are a bit screwed.
1461  _perforce_call_p4 jobspec jobspec -o 2>/dev/null | while read line; do
1462    case $line in
1463      ([[:blank:]]##)
1464      type=
1465      ;;
1466
1467      ((#b)([[:alpha:]]##):*)
1468      type=${match[1]}
1469      ;;
1470
1471      (*)
1472      case $type in
1473	# This stanza tells us all the allowed fields.
1474	(Fields)
1475	if [[ $line = [[:blank:]]##<->${~m2words}* ]]; then
1476	  p4fields[${(L)match[1]}]=${match[2]}
1477	fi
1478	;;
1479
1480	# This stanza gives allowed values for the `select' types.
1481	(Values)
1482	if [[ $line = ${~m2words}* ]]; then
1483	  p4values[${(L)match[1]}]=${match[2]}
1484	fi
1485	;;
1486      esac
1487
1488      ;;
1489    esac
1490  done
1491
1492  IFS=$oldifs
1493
1494  if (( ! ${#p4fields} )); then
1495    # We didn't get anything; add the defaults.
1496    p4fields=(
1497      date		date
1498      description	text
1499      job		word
1500      status		select
1501      user		word
1502    )
1503    p4values=(
1504	    status	open/suspended/closed
1505	)
1506  fi
1507
1508  for key in ${(k)p4fields}; do
1509    if [[ -n ${p4values[$key]} ]]; then
1510      valuespec+=("${key}:${p4fields[$key]}:(${p4values[$key]//$slash/ })")
1511    elif [[ $key = job ]]; then
1512      # Nothing special for jobs; add our own completion.
1513      valuespec+=("${key}:Perforce job:_perforce_jobs")
1514    elif [[ $key = user ]]; then
1515      # Nothing provided for user; add our own completion.
1516      valuespec+=("${key}:user:_perforce_users")
1517    else
1518      valuespec+=("${key}:${p4fields[$key]}: ")
1519    fi
1520  done
1521
1522  _values 'Job specification parameter' $valuespec
1523}
1524
1525(( $+functions[_perforce_labels] )) ||
1526_perforce_labels() {
1527    local lline file
1528    local -a ll match mbegin mend
1529
1530    if [[ $argv[-1] = -tf ]]; then
1531      argv=($argv[1,-2])
1532      # Completing after `@'.
1533      file=${${(Q)PREFIX}%%@*}
1534      compset -P '*@(|\\\<|\\\>)(|=)'
1535    fi
1536
1537    ll=(${${(f)"$(_perforce_call_p4 labels labels ${file:+\$file})"}//(#b)Label\ ([^[:blank:]]##)\ (*)/$match[1]:$match[2]})
1538    _describe -t labels 'Perforce label' ll
1539}
1540
1541
1542(( $+functions[_perforce_revisions] )) ||
1543_perforce_revisions() {
1544  # Doesn't handle standard completion options; requires space
1545  # in front if used as action in _arguments.
1546
1547  local rline match mbegin mend comma expl pfx
1548  local -a rl
1549
1550  if [[ $1 = -tR ]]; then
1551    # handle ranges
1552    comma=(-S, -R _perforce_file_suffix)
1553	shift
1554  fi
1555
1556  # Beware of @foo,#bar; stupid but valid.
1557  pfx=${${(Q)PREFIX}%%[\#@]*}
1558  compset -P '*\#(|\\\<|\\\>)(|=)'
1559
1560  # Numerical revision numbers, possibly with text.
1561  if [[ -z $PREFIX || $PREFIX = <-> ]]; then
1562    # always allowed (same as none)
1563    rl+=(0)
1564    _perforce_call_p4 filelog 'filelog $pfx' 2>/dev/null | while read rline; do
1565      if [[ $rline = (#b)'... #'(<->)' change '(*) ]]; then
1566	rl+=("${match[1]}:${match[2]}")
1567      fi
1568    done
1569  fi
1570  # Non-numerical (special) revision names.
1571  if [[ -z $PREFIX || $PREFIX != <-> ]]; then
1572    rl+=('head:head revision' 'none:empty revision'
1573      'have:current synced revision')
1574  fi
1575  _describe -t revisions 'revision' rl -V revisions-unsorted $comma
1576}
1577
1578
1579(( $+functions[_perforce_statuses] )) ||
1580_perforce_statuses() {
1581  # Perforce statuses are usually limited to a set of values
1582  # given by the jobspec.
1583  local jline match mbegin mend
1584  local -a statuses
1585
1586  _perforce_call_p4 jobspec jobspec -o | while read jline; do
1587    if [[ $jline = (#b)Status[[:blank:]]##(*/*) ]]; then
1588      statuses=(${(s./.)match[1]})
1589      break
1590    fi
1591  done
1592  if (( !${#statuses} )); then
1593    # Couldn't find anything from the jobspec; add defaults.
1594    statuses=(closed open suspended)
1595  fi
1596  _describe -t statuses 'job status' statuses
1597}
1598
1599
1600(( $+functions[_perforce_submit_options] )) ||
1601_perforce_submit_options() {
1602  local -a soptions
1603  soptions=('submitunchanged:submit all open files (default)'
1604	    'revertunchanged:revert unchanged files'
1605	    'leaveunchanged:move unchanged files to default changelist')
1606  soptions+=(${^${soptions//:/+reopen:}}", leave submitted open")
1607  _describe -t submit-options 'submit option' soptions
1608}
1609
1610
1611(( $+functions[_perforce_pids] )) ||
1612_perforce_pids() {
1613  local -a ul
1614
1615  ul=(${${${${(f)"$(_perforce_call_p4 monitor monitor show 2>/dev/null)"}# *}//:/\\:}/\ /:})
1616  [[ $#ul -eq 1 && $ul[1] = '' ]] && ul=()
1617  _describe -t id 'process ID' ul
1618}
1619
1620
1621(( $+functions[_perforce_users] )) ||
1622_perforce_users() {
1623  local -a ul
1624
1625  ul=(${${${(f)"$(_perforce_call_p4 users users)"}//:/\\:}/\ /:})
1626  [[ $#ul -eq 1 && $ul[1] = '' ]] && ul=()
1627  _describe -t users 'Perforce user' ul
1628}
1629
1630
1631(( $+functions[_perforce_users_or_groups] )) ||
1632_perforce_users_or_groups() {
1633    _alternative 'groups:Perforce group:_perforce_groups' \
1634      'users:Perforce user:_perforce_users'
1635}
1636
1637(( $+functions[_perforce_variables] )) ||
1638_perforce_variables() {
1639  local line match mbegin mend expl
1640  local -a vars
1641
1642  _perforce_call_p4 help-environment help environment | while IFS= read line
1643  do
1644    if [[ $line = $'\t'(#b)([A-Z][A-Z0-9_]##)* ]]; then
1645      vars+=($match[1])
1646    fi
1647  done
1648
1649  _wanted variable expl 'environment variable' compadd -S= -q $vars
1650}
1651
1652#
1653# Completions for p4 commands
1654#
1655
1656(( $+functions[_perforce_cmd_add] )) ||
1657_perforce_cmd_add() {
1658  _arguments -s : \
1659    '-c[add files to change]:change:_perforce_changes -tc' \
1660    '-d[reopen files for add]' \
1661    '-f[allow filenames with wild cards]' \
1662    '-I[do not perform ignore checking]' \
1663    '-n[preview add]' \
1664    '-t[set file type]:file type:_perforce_filetypes' \
1665    '*:file:_perforce_files -tu'
1666}
1667
1668
1669(( $+functions[_perforce_cmd_admin] )) ||
1670_perforce_cmd_admin() {
1671  if (( CURRENT == 2 )); then
1672    local -a adcmds
1673    adcmds=(
1674      "checkpoint:checkpoint, save copy of journal file"
1675      "dbstat:db tables"
1676      "journal:save and truncate journal file"
1677      "logstat:report sizes of log files"
1678      "stop:stop the server")
1679    _describe -t commands 'Perforce admin command' adcmds
1680  else
1681    case $words[2] in
1682      (checkpoint|journal)
1683      shift words
1684      (( CURRENT-- ))
1685      _arguments -s : \
1686        '-z[gzip journal file]' \
1687        '1::journal file prefix: '
1688      ;;
1689
1690      (dbstat)
1691      shift words
1692      (( CURRENT -- ))
1693      _arguments -s : \
1694        '-s[show sizes]'
1695    esac
1696  fi
1697}
1698
1699
1700(( $+functions[_perforce_cmd_annotate] )) ||
1701_perforce_cmd_annotate() {
1702  # New in release 2002.2.
1703  # -c was new in about 2003.2.
1704  _arguments -s : \
1705    '-a[include deleted files and lines]' \
1706    '-c[output change numbers]' \
1707    '-d-[select whitespace option]:whitespace option:((
1708b\:ignore\ whitespace\ changes
1709w\:ignore\ whitespace
1710l\:ignore\ line\ endings))' \
1711    '-i[follow branches]' \
1712    '-I[follow all integrations]' \
1713    '-q[suppress one-line header]' \
1714    '-t[display binary files]' \
1715    '-u[output user and date]' \
1716    '*::file:_perforce_files -tR'
1717}
1718
1719
1720(( $+functions[_perforce_cmd_attribute] )) ||
1721_perforce_cmd_attribute() {
1722  # This is currently (2005.1) an unsupported command.
1723  # See "p4 help undoc".
1724  local limit
1725  # If -f is present, search unopened files, else don't
1726  [[ ${words[(I)-f]} -eq 0 ]] && limit=" -to"
1727  _arguments -s : \
1728    '-n[attribute name]:name: ' \
1729    '-v[attribute value]:value: ' \
1730    '-e[use hex value]' \
1731    '-f[set attribute on submitted file]' \
1732    '-p[propagate attribute when opened]' \
1733    '(-v)-i[read attribute from standard input]' \
1734    "*::file:_perforce_files$limit"
1735}
1736
1737
1738(( $+functions[_perforce_cmd_branch] )) ||
1739_perforce_cmd_branch() {
1740  _arguments -s : \
1741    '(-o -S -P)-f[force operation]' \
1742    '(-o -i -S -P)-d[delete branch]' \
1743    '(-d -i -f)-o[write branch spec to standard output]' \
1744    '(-d -o -S -P)-i[read branch spec from standard input]' \
1745    '(-f -d -i)-S[expose internally generated mapping]:stream: ' \
1746    '(-f -d -i)-P[treat stream as a child of parent stream]:parent stream: ' \
1747    '(-i)*::branch name:_perforce_branches'
1748}
1749
1750
1751(( $+functions[_perforce_cmd_branches] )) ||
1752_perforce_cmd_branches() {
1753  _arguments -s : \
1754    '(-E)-e[list branches that match pattern]:pattern: ' \
1755    '(-e)-E[list branches that match case-insensitive pattern]:case-insensitive pattern: ' \
1756    '-u[list branches owned by user]:user:_perforce_users' \
1757    '-m[limit output to max branches]:max branches: ' \
1758    '-t[display time and date]'
1759}
1760
1761
1762(( $+functions[_perforce_cmd_change] )) ||
1763_perforce_cmd_change() {
1764  local ctype
1765  # Unless forcing or outputting, we don't
1766  # complete committed changes since they can't be altered.
1767  # If deleting and not forcing, the change must be on the current client.
1768  if [[ ${words[(I)-*(f|o)*]} -eq 0 ]]; then
1769    if [[ ${words[(I)-d]} -gt 0 ]]; then
1770      ctype=" -tc"
1771    else
1772      ctype=" -tp"
1773    fi
1774  fi
1775  _arguments -s : \
1776    '-f[force update of change]' \
1777    '-s[include fix status in job list]' \
1778    '(-u -I -o -i -t -U)-d[delete change]' \
1779    '(-u -d -o -i -t -U --serverid)-o[write change spec to the standard output]' \
1780    '(-O -I -d -o -i -t -U --serverid)-i[read change spec from the standard input]' \
1781    '(-s -d -o -i --serverid)-t[set type of change]:type:(public restricted)' \
1782    '-U[set user of empty change]:user:_perforce_users' \
1783    '-O[change is original number before submit]' \
1784    '-I[change is number of Identity field]' \
1785    '-u[force update of submitted change]' \
1786    '(-s -u -O -I -o -i -t -U)--serverid[specify server]:server ID: ' \
1787    "(-i)1::change:_perforce_changes$ctype"
1788}
1789
1790
1791(( $+functions[_perforce_cmd_changelist] )) ||
1792_perforce_cmd_changelist() {
1793  _perforce_cmd_change "$@"
1794}
1795
1796
1797(( $+functions[_perforce_cmd_changes] )) ||
1798_perforce_cmd_changes() {
1799  _arguments -s : \
1800    '-i[include integrated changes]' \
1801    '-t[display time and date]' \
1802    '-l[display full change text]' \
1803    '-L[display truncated change text]' \
1804    '-f[view restricted changes]' \
1805    '-c[display changes submitted by client]:client:_perforce_clients' \
1806    '-e[display changes above this change]:change:_perforce_changes' \
1807    '-m[limit to max changes]:max changes: ' \
1808    '-s[limit output to changes with status]:status:(pending shelved submitted)' \
1809    '-u[display changes owned by user]:user:_perforce_users' \
1810    '*::file:_perforce_files -tR'
1811}
1812
1813
1814(( $+functions[_perforce_cmd_changelists] )) ||
1815_perforce_cmd_changelists() {
1816  _perforce_cmd_changes "$@"
1817}
1818
1819
1820(( $+functions[_perforce_cmd_clean] )) ||
1821_perforce_cmd_clean() {
1822  _arguments -s : \
1823    '-e[clean modified files]' \
1824    '-a[clean added files]' \
1825    '-d[clean deleted files]' \
1826    '-I[do not perform ignore checking]' \
1827    '-l[output relative paths]' \
1828    '-n[preview clean]' \
1829    '*:file:_perforce_files -tu'
1830}
1831
1832
1833
1834(( $+functions[_perforce_cmd_client] )) ||
1835_perforce_cmd_client() {
1836  _arguments -s : \
1837    '-f[force update of client]' \
1838    '-Fs[force delete with shelved changes]' \
1839    '(-t -o -S -c -s -i)-d[delete client]' \
1840    '(-f -d -Fs -s -i --serverid)-o[write client spec to standard output]' \
1841    '(-t -d -Fs -i --serverid)-S[create new client dedicated to stream]:stream: ' \
1842    '(-d -Fs -o -c -i --serverid)-s[switch client view without opening editor]' \
1843    '(-t -d -Fs -o -S -c -s --serverid)-i[read client spec from standard input]' \
1844    '-t[use client as template]:client:_perforce_clients' \
1845    '(-f -t -d -Fs -s -i --serverid)-c[yield client spec for stream at moment change was recorded]:change:_perforce_changes -ts' \
1846    '--serverid[specify server]:server ID: ' \
1847    '1::file:_perforce_clients'
1848}
1849
1850
1851(( $+functions[_perforce_cmd_clients] )) ||
1852_perforce_cmd_clients() {
1853  _arguments -s : \
1854    '-t[display time and date]' \
1855    '-u[list clients owned by user]:user:_perforce_users' \
1856    '(-E)-e[list clients that match pattern]:pattern: ' \
1857    '(-e)-E[list clients that match case-insensitive pattern]:case-insensitive pattern: ' \
1858    '-m[limit to max clients]:max clients: ' \
1859    '-S[limit output to clients dedicated to stream]:stream: ' \
1860    '-U[list unloaded clients]' \
1861    '(-s)-a[display all clients]' \
1862    '(-a)-s[display clients dedicated to server]:server ID: '
1863}
1864
1865
1866(( $+functions[_perforce_cmd_copy] )) ||
1867_perforce_cmd_copy() {
1868  local -a fileargs
1869  if [[ ${words[(I)-b*]} -ne 0 ]]; then
1870    if [[ ${words[(I)-*s*]} -eq 0 ]]; then
1871      # with -b and no -s, all files are to-files (but -s may come later)
1872      fileargs=('*::to file:_perforce_files -tR')
1873    else
1874      # with -b and -s we have one from-file and any number of to-files
1875      fileargs=('*::to file:_perforce_files')
1876    fi
1877  elif [[ ${words[(I)-(S|P)]} -ne 0 ]]; then
1878      fileargs=('*::file:_perforce_files -tR')
1879  else
1880    # with no -b we have one from-file and one to-file
1881    fileargs=('1::from file:_perforce_files -tR'
1882              '2::to file:_perforce_files')
1883  fi
1884  _arguments -s : \
1885    '-b[use branch view'\''s source and target]:branch:_perforce_branches' \
1886    '-s[select source file, use branch view as target]:source file:_perforce_files -tR' \
1887    '-r[reverse direction of copy]' \
1888    '-c[open files in change]:change:_perforce_changes -tc' \
1889    '-f[force creation of extra revisions]' \
1890    '-n[preview copy]' \
1891    '-m[limit copy to max files]:max files: ' \
1892    '-q[suppress normal output messages]' \
1893    '-v[do not modify client files]' \
1894    '(-b -s)-S[copy from stream to its parent]:stream: ' \
1895    '(-b -s)-P[generate branch view using a parent stream]:parent stream: ' \
1896    '(-b -s)-F[copy against stream'\''s expected flow]' \
1897    $fileargs
1898}
1899
1900(( $+functions[_perforce_cmd_counter] )) ||
1901_perforce_cmd_counter() {
1902  _arguments -s : \
1903    '-d[delete counter]' \
1904    '-f[set or delete internal counter]' \
1905    '-i[increment counter by 1]' \
1906    '-m[allow multiple operations]' \
1907    '1:counter:_perforce_counters' \
1908    '(-d -i)2::numeric value:_perforce_counter_values'
1909}
1910
1911
1912(( $+functions[_perforce_cmd_counters] )) ||
1913_perforce_cmd_counters() {
1914  _arguments -s : \
1915    '-e[list counters that match pattern]:pattern: ' \
1916    '-m[limit to max counters]:max counters: '
1917}
1918
1919
1920(( $+functions[_perforce_cmd_cstat] )) ||
1921_perforce_cmd_cstat() {
1922  _arguments -s : \
1923  '*::file:_perforce_files'
1924}
1925
1926
1927(( $+functions[_perforce_cmd_dbschema] )) ||
1928_perforce_cmd_dbschema() {
1929  if [[ $PREFIX = *:* ]]; then
1930    _message 'table version'
1931  else
1932    _perforce_dbtables
1933  fi
1934}
1935
1936
1937(( $+functions[_perforce_cmd_dbstat] )) ||
1938_perforce_cmd_dbstat() {
1939  _arguments -s : \
1940  '(-s)-h[histogram of leaf pages in DB table]' \
1941  '(-s)-a[all tables]' \
1942  '(-h -a)-s[report sizes of tables]' \
1943  '(-s -a)*::DB table:_perforce_dbtable'
1944}
1945
1946
1947(( $+functions[_perforce_cmd_delete] )) ||
1948_perforce_cmd_delete() {
1949  _arguments -s : \
1950    '-c[delete files for change]:change:_perforce_changes -tc' \
1951    '-n[preview delete]' \
1952    '-k[perform delete on server]' \
1953    '-v[delete unsynced files]' \
1954    '*::file:_perforce_files'
1955}
1956
1957
1958(( $+functions[_perforce_cmd_depot] )) ||
1959_perforce_cmd_depot() {
1960  _arguments -s : \
1961    '(-t -o -i)-d[delete depot]' \
1962    '(-t -o -i)-f[force delete]' \
1963    '(-d -o -i)-t[insert value into type]:type: ' \
1964    '(-t -d -i -f)-o[write depot spec to standard output]' \
1965    '(-t -d -o -f)-i[read depot spec from standard input]' \
1966    '(-i)*::depot name:_perforce_depots'
1967}
1968
1969
1970(( $+functions[_perforce_cmd_depots] )) ||
1971_perforce_cmd_depots() {
1972  # No arguments
1973  _arguments -s :
1974}
1975
1976
1977(( $+functions[_perforce_cmd_describe] )) ||
1978_perforce_cmd_describe() {
1979  _arguments -s : \
1980    '-d-[diff options]:diff options:((
1981n\:RCS
1982c\:context
1983s\:summary
1984u\:unified
1985b\:ignore\ whitespace\ changes
1986w\:ignore\ whitespace
1987l\:ignore\ line\ endings))' \
1988    '-s[omit diffs]' \
1989    '-S[list shelved files]' \
1990    '-f[force display of restricted change]' \
1991    '-O[change is original number before submit]' \
1992    '-I[change is number of Identity field]' \
1993    '-m[limit output to max files]:max files: ' \
1994    '*::change:_perforce_changes'
1995}
1996
1997
1998(( $+functions[_perforce_cmd_diff] )) ||
1999_perforce_cmd_diff() {
2000  local limit
2001  [[ ${words[(I)-(f|sd|se|sl)]} -eq 0 ]] && limit=" -to"
2002  _arguments -s : \
2003    '-d-[diff options]:diff options:((
2004n\:RCS
2005c\:context
2006s\:summary
2007u\:unified
2008b\:ignore\ whitespace\ changes
2009w\:ignore\ whitespace
2010l\:ignore\ line\ endings))' \
2011    '-f[diff every file]' \
2012    '-m[limit output to max files]:max files: ' \
2013    '-Od[limit output to files that differ]' \
2014    '-s-[filter options]:filter options:((
2015a\:list\ opened\ files\ that\ differ\ from\ depot
2016b\:list\ modified\ integrated\ files
2017d\:list\ unopened\ missing\ files
2018e\:list\ unopened\ files\ that\ differ\ from\ depot
2019l\:list\ all\ unopened\ files\ with\ status
2020r\:list\ opened\ files\ that\ do\ not\ differ\ from\ depot))' \
2021    '-t[diff binary files]' \
2022    "*::file:_perforce_files$limit"
2023}
2024
2025
2026(( $+functions[_perforce_cmd_diff2] )) ||
2027_perforce_cmd_diff2() {
2028  _arguments -s : \
2029    '-b[use branch view'\''s source and target]:branch:_perforce_branches' \
2030    '-d-[diff options]:diff options:((
2031n\:RCS
2032c\:context
2033s\:summary
2034u\:unified
2035b\:ignore\ whitespace\ changes
2036w\:ignore\ whitespace
2037l\:ignore\ line\ endings))' \
2038    '-Od[limit output to files that differ]' \
2039    '-q[omit identical files]' \
2040    '-t[diff binary files]' \
2041    '-u[use GNU diff -u format]' \
2042    '(-b)-S[use generated branch view from stream]:stream: ' \
2043    '(-b)-P[use generated branch view from parent stream]:parent stream: ' \
2044    '1::from file:_perforce_files' \
2045    '2::to file:_perforce_files'
2046}
2047
2048
2049(( $+functions[_perforce_cmd_dirs] )) ||
2050_perforce_cmd_dirs() {
2051  _arguments -s : \
2052    '-C[list only directories in current client]' \
2053    '-D[include directories with only deleted files]' \
2054    '-H[list directories with synced files]' \
2055    '-S[limit output to depot directories mapped to stream'\''s client]:stream: ' \
2056    '*::directory:_perforce_files -td'
2057}
2058
2059
2060(( $+functions[_perforce_cmd_edit] )) ||
2061_perforce_cmd_edit() {
2062  _arguments -s : \
2063    '-c[edit files for change]:change:_perforce_changes -tc' \
2064    '-t[specify filetype]:filetype:_perforce_filetypes' \
2065    '-n[preview edit]' \
2066    '-k[edit files on server]' \
2067    '*::file:_perforce_files'
2068}
2069
2070
2071(( $+functions[_perforce_cmd_export] )) ||
2072_perforce_cmd_export() {
2073  _arguments -s : \
2074    '(-j)-c[specify checkpoint number (/ position)]:checkpoint number: ' \
2075    '(-c)-j[specify journal number (/ position)]:journal number: ' \
2076    '(-j)-f[reformat non-textual data types]' \
2077    '(-j)-l[specify number of lines]:number of lines: ' \
2078    '(-j)-F[specify filter]:filter pattern: ' \
2079    '(-c)-r[raw format]' \
2080    '-J[specify file prefix]:file prefix: ' \
2081    '-T[space-separated list of tables not to export]'
2082}
2083
2084
2085(( $+functions[_perforce_cmd_filelog] )) ||
2086_perforce_cmd_filelog() {
2087  _arguments -s : \
2088    '-c[display files at change]:change:_perforce_changes -ts' \
2089    '-i[include inherited file history]' \
2090    '-h[display file content history]' \
2091    '-t[display time and date]' \
2092    '-l[display full change text]' \
2093    '-L[display truncated change text]' \
2094    '-m[display max number of revisions]:max revisions: ' \
2095    '-p[do not follow content of promoted task streams]' \
2096    '-s[display shortened form]' \
2097    '*::file:_perforce_files'
2098}
2099
2100
2101(( $+functions[_perforce_cmd_files] )) ||
2102_perforce_cmd_files() {
2103  _arguments -s : \
2104    '-a[display all revisions in range]' \
2105    '-A[display files in archive depots]' \
2106    '-e[do not display deleted, purged or archived files]' \
2107    '-m[limit output to max files]:max files: ' \
2108    '-U[display files in unload depot]' \
2109    '*::file:_perforce_files -tR'
2110}
2111
2112
2113(( $+functions[_perforce_cmd_fix] )) ||
2114_perforce_cmd_fix() {
2115  local job
2116  local -a jobs
2117
2118  if [[ -n $words[(R)-d] && -n $words[(R)-c] && -n $words[$words[(i)-c]+1] ]]
2119  then
2120    # Deleting a fix from a change.  We can find out which fixes
2121    # are present.
2122    local -a jobs
2123    jobs=(${${(f)"$(_perforce_call_p4 fixes fixes -c $words[$words[(i)-c]+1])"}%" fixed by change "*})
2124    if (( ${#jobs} )); then
2125      jobs=("Job="${^jobs})
2126      job=" -e \"${(j.|.)jobs}\""
2127    fi
2128  fi
2129
2130  _arguments -s : \
2131    '-d[delete fix]' \
2132    '-s[set status]:status:_perforce_statuses' \
2133    '-c[display jobs fixed by change]:change:_perforce_changes -ts' \
2134    "*::job:_perforce_jobs$job"
2135}
2136
2137
2138(( $+functions[_perforce_cmd_fixes] )) ||
2139_perforce_cmd_fixes() {
2140  _arguments -s : \
2141    '-j[list fixes for job]:job:_perforce_jobs' \
2142    '-c[list fixes for change]:change:_perforce_changes -tR' \
2143    '-i[include integrated changes]' \
2144    '-m[limit output to max fixes]:max fixes: ' \
2145    '*::fixed file:_perforce_files -tR'
2146}
2147
2148
2149(( $+functions[_perforce_cmd_flush] )) ||
2150_perforce_cmd_flush() {
2151  _arguments -s : \
2152    '-f[force resynchronisation]' \
2153    '-L[use full depot syntax, including revision number]' \
2154    '-n[preview flush]' \
2155    '-N[preview flush with summary]' \
2156    '-q[suppress normal output messages]' \
2157    '-r[reopen moved files in new location]' \
2158    '-m[limit sync to max files]:max files: ' \
2159    '*::file:_perforce_files -tR'
2160}
2161
2162
2163(( $+functions[_perforce_cmd_fstat] )) ||
2164_perforce_cmd_fstat() {
2165  local Oattr Aattr
2166  if [[ ${_perforce_cmd_list[(r)attribute:*]} != '' ]]; then
2167    # Unsupported feature, try not to show if not present
2168    Oattr=' a\:output\ attributes d\:output\ digest e\:output\ values\ in\ hex'
2169    Aattr='-A[restrict attributes by pattern]:attribute pattern: '
2170  fi
2171  _arguments -s : \
2172    $Aattr \
2173    '-F[list only files satisfying filter]:filter:_perforce_fstat_fields -tv' \
2174    '-L[use full depot syntax, including revision number]' \
2175    '-T[return specified fields]:output field:_perforce_fstat_fields' \
2176    '-m[limits output to max files]:max files: ' \
2177    '-r[sort output in reverse order]' \
2178    '-c[display files modified by or after change]:change:_perforce_changes -ts' \
2179    '-e[list files modified by change]:change:_perforce_changes -ts' \
2180    "-O-[output options]:output options:((
2181f\:all\ revisions
2182l\:fileSize\ and\ digest
2183p\:local\ file\ path
2184r\:pending\ integration
2185s\:exclude\ local\ path
2186$Oattr))" \
2187    '-R-[restrict files]:file restrictions:((
2188c\:mapped\ in\ client
2189h\:synced\ to\ client
2190n\:opened\ not\ at\ head\ revision
2191o\:opened
2192r\:resolved
2193s\:shelved
2194u\:unresolved))' \
2195    '-S-[sort order]:sort by:((
2196t\:filetype
2197d\:date
2198r\:head\ revision
2199h\:have\ revision
2200s\:filesize))' \
2201    '-U[display info about unload files in unload depot]' \
2202    '-C[limit output to mapped files (-Rc)]' \
2203    '-H[limit output to synced files (-Rh)]' \
2204    '-W[limit output to opened files (-Ro)]' \
2205    '-l[output fileSize and digest (-Ol)]' \
2206    '-P[output local file paths (-Op)]' \
2207    '-s[exclude local file paths (-Os)]' \
2208    '*::file:_perforce_files'
2209}
2210
2211
2212(( $+functions[_perforce_cmd_grep] )) ||
2213_perforce_cmd_grep() {
2214  _arguments -s : \
2215    '-e[search pattern]:pattern: ' \
2216    '-a[search all revisions]' \
2217    '-i[case insensitive match]' \
2218    '-n[display matching line number]' \
2219    '-v[display files with non-matching lines]' \
2220    '-F[interpret pattern as fixed string]' \
2221    '-G[interpret pattern as regexp]' \
2222    '-L[display non-matching files]' \
2223    '-l[display matching files]' \
2224    '-s[suppress errors on long lines]' \
2225    '-t[search binary files]' \
2226    '-A[display N lines of trailing context]:lines: ' \
2227    '-B[display N lines of leading context]:lines: ' \
2228    '-C[display N lines of output context]:lines: ' \
2229    '*::file:_perforce_files -tR'
2230}
2231
2232
2233(( $+functions[_perforce_cmd_group] )) ||
2234_perforce_cmd_group() {
2235  _arguments -s : \
2236    '-d[delete group]' \
2237    '-o[write group spec to standard output]' \
2238    '-i[read group spec from standard input]' \
2239    '(-o -A)-a[allow owner to modify group]' \
2240    '(-a -d)-A[allow admin user to add new group]' \
2241    '1::perforce group:_perforce_groups'
2242}
2243
2244
2245(( $+functions[_perforce_cmd_groups] )) ||
2246_perforce_cmd_groups() {
2247  _arguments -s : \
2248    '-i[display indirect membership by subgroups]' \
2249    '-m[limit output to max groups]:max groups: ' \
2250    '-v[display group data]' \
2251    '(-u -o)-g[display group with name]:group:_perforce_groups' \
2252    '(-g -o)-u[display all groups for user]:user:_perforce_users' \
2253    '(-g -u)-o[display all groups for owner]:owner:_perforce_users' \
2254    '(-g -u -o)1::user or group name:_perforce_users_or_groups'
2255}
2256
2257
2258(( $+functions[_perforce_cmd_have] )) ||
2259_perforce_cmd_have() {
2260  _perforce_files
2261}
2262
2263
2264(( $+functions[_perforce_cmd_help] )) ||
2265_perforce_cmd_help() {
2266  local hline
2267  if (( ! ${#_perforce_help_list} )); then
2268    (( ${+_perforce_help_list} )) || typeset -ga _perforce_help_list
2269    # All commands have help.
2270    (( ${#_perforce_cmd_list} )) || _perforce_gen_cmd_list
2271    _perforce_help_list=($_perforce_cmd_list)
2272    _perforce_call_p4 help help | while read -A hline; do
2273      if [[ $hline[1] = p4 && $hline[2] = help ]]; then
2274        _perforce_help_list+=("$hline[3]:${hline[4,-1]}")
2275      fi
2276    done
2277    if [[ -z ${_perforce_help_list[(r)undoc:*]} ]]; then
2278      _perforce_help_list+=("undoc:help for otherwise undocumented features")
2279    fi
2280  fi
2281  _describe -t help-options 'Perforce help option' _perforce_help_list
2282}
2283
2284
2285(( $+functions[_perforce_cmd_info] )) ||
2286_perforce_cmd_info() {
2287  _arguments -s : \
2288    '-s[short output]'
2289}
2290
2291
2292(( $+functions[_perforce_cmd_integrate] )) ||
2293_perforce_cmd_integrate() {
2294  local range
2295  # If -s is present, the first normal argument can't have revRange.
2296  [[ ${words[(I)-s]} -eq 0 ]] && range=" -tR"
2297  _arguments -s : \
2298    '-b[use branch view'\''s source and target]:branch:_perforce_branches' \
2299    '(-r)-s[select source file, use branch view as target]:source file:_perforce_files -tR' \
2300    '-f[force integration]' \
2301    '-O-[output more information]:output options:((
2302b\:show\ base\ revision\ for\ merge
2303r\:show\ scheduled\ resolves))' \
2304    '-R-[specify resolve schedule]:schedule:((
2305b\:branch\ resolves
2306d\:delete\ resolves
2307s\:skip\ cherry-picked\ revisions\ already\ integrated))' \
2308    '-Di[retain revisions of deleted files]' \
2309    '-h[leave files at revision currently synced]' \
2310    '-m[limit integration to max files]:max files: ' \
2311    '-n[preview integration]' \
2312    '-q[suppress normal output messages]' \
2313    '-c[open in change]:change:_perforce_changes -tc' \
2314    '-v[do not modify client files]' \
2315    '-r[reverse direction of mapping]' \
2316    '-S[use generated branch view from stream]:stream: ' \
2317    '-P[use generated branch view from parent stream]:parent stream: ' \
2318    "1:file:_perforce_files$range" \
2319    '*::file:_perforce_files'
2320}
2321
2322
2323(( $+functions[_perforce_cmd_integ] )) ||
2324_perforce_cmd_integ() {
2325  _perforce_cmd_integrate "$@"
2326}
2327
2328(( $+functions[_perforce_cmd_integrated] )) ||
2329_perforce_cmd_integrated() {
2330  _arguments -s : \
2331    '-r[reverse mapping in branch view]' \
2332    '-b[list files integrated from branch view]:branch:_perforce_branches' \
2333    '*::file:_perforce_files -ti'
2334}
2335
2336
2337# interchanges is an unsupported but useful command that reports
2338# changes that haven't been integrated between source and target;
2339# see "p4 help undoc".
2340(( $+functions[_perforce_cmd_interchanges] )) ||
2341_perforce_cmd_interchanges() {
2342  local -a fileargs
2343  if [[ ${words[(I)-b*]} -ne 0 ]]; then
2344    if [[ ${words[(I)-*s*]} -eq 0 ]]; then
2345      # with -b and no -s, all files are to-files (but -s may come later)
2346      fileargs=('*::to file:_perforce_files -tR')
2347    else
2348      # with -b and -s we have one from-file and any number of to-files
2349      fileargs=('*::to file:_perforce_files')
2350    fi
2351  elif [[ ${words[(I)-(S|P)]} -ne 0 ]]; then
2352      fileargs=('*::file:_perforce_files -tR')
2353  else
2354    # with no -b we have one from-file and one to-file
2355    fileargs=('1::from file:_perforce_files -tR'
2356              '2::to file:_perforce_files')
2357  fi
2358  _arguments -s : \
2359    '-f[list files that require integration]' \
2360    '-l[display full change text]' \
2361    '-t[display time and date]' \
2362    '(-S -P)-b[use branch view'\''s source and target]:branch:_perforce_branches' \
2363    '(-S -P)-s[select source file, use branch view as target]:source file:_perforce_files -tR' \
2364    '-u[limit files submitted by user]:user:_perforce_users' \
2365    '-r[reverse mapping direction]' \
2366    '-S[use generated branch view from stream]:stream: ' \
2367    '-P[use generated branch view from parent stream]:parent stream: ' \
2368    '-F[ignore stream'\''s expected flow]' \
2369    $fileargs
2370}
2371
2372
2373(( $+functions[_perforce_cmd_istat] )) ||
2374_perforce_cmd_istat() {
2375  _arguments -s : \
2376    '-a[show status of integration in both directions]' \
2377    '-c[assume cache is stale]' \
2378    '-r[show status of integration from parent]' \
2379    '-s[show cached state without refreshing stale data]' \
2380    '1::stream: '
2381}
2382
2383
2384(( $+functions[_perforce_cmd_job] )) ||
2385_perforce_cmd_job() {
2386  _arguments -s : \
2387    '(-d -o -i)-f[force setting of readonly fields]' \
2388    '(-f -o -i)-d[delete job]' \
2389    '(-f -d -i)-o[write job spec to standard output]' \
2390    '(-d -o)-i[read job spec from standard input]' \
2391    '(-i)1::job:_perforce_jobs'
2392}
2393
2394
2395(( $+functions[_perforce_cmd_jobs] )) ||
2396_perforce_cmd_jobs() {
2397  _arguments -s : \
2398    '-e[list jobs matching parameter]::_perforce_jobviews' \
2399    '-i[include integrated changes]' \
2400    '-l[display full job text]' \
2401    '-m[limit output to max jobs]:max jobs: ' \
2402    '-r[sort in reverse order]' \
2403    '(-e -i -l -m)-R[rebuild jobs table]' \
2404    '*::file:_perforce_files -tR'
2405}
2406
2407
2408(( $+functions[_perforce_cmd_jobspec] )) ||
2409_perforce_cmd_jobspec() {
2410  _arguments -s : \
2411    '-i[read form from stdin]' \
2412    '-o[write form from to stdout]'
2413}
2414
2415
2416(( $+functions[_perforce_cmd_key] )) ||
2417_perforce_cmd_key() {
2418  local -a keyargs
2419  if [[ ${words[(I)-(d|i)]} -ne 0 ]]; then
2420    keyargs=('1::name: ')
2421  elif [[ ${words[(I)-m]} -ne 0 ]]; then
2422    keyargs=('*::name value pairs: ')
2423  else
2424    keyargs=('1::name: ' '2::value: ')
2425  fi
2426  _arguments -s : \
2427    '(-i -m)-d[delete key]' \
2428    '(-d -m)-i[increment key value by 1]' \
2429    '(-d -i)-m[allow multiple operations]' \
2430    $keyargs
2431}
2432
2433
2434(( $+functions[_perforce_cmd_keys] )) ||
2435_perforce_cmd_keys() {
2436  _arguments -s : \
2437    '-e[list keys that match pattern]:pattern: ' \
2438    '-m[limit output to max keys]:max keys: '
2439}
2440
2441
2442(( $+functions[_perforce_cmd_label] )) ||
2443_perforce_cmd_label() {
2444  _arguments -s : \
2445    '-f[force operation]' \
2446    '-t[copy view and options from label]:label:_perforce_labels' \
2447    '(-o -i -t)-d[delete label]' \
2448    '(-d -f -i -g)-o[write label spec to standard output]' \
2449    '(-o -d -t)-i[read label spec from standard input]' \
2450    '-g[update global label]' \
2451    '*::label:_perforce_labels'
2452}
2453
2454
2455(( $+functions[_perforce_cmd_labels] )) ||
2456_perforce_cmd_labels() {
2457  _arguments -s : \
2458    '-t[display time and date]' \
2459    '-u[list labels owned by user]:user:_perforce_users' \
2460    '(-E)-e[list labels that match pattern]:pattern: ' \
2461    '(-e)-E[list labels that match case-insensitive pattern]:case-insensitive pattern: ' \
2462    '-m[limit output to max labels]:max labels: ' \
2463    '(-s)-a[display all labels]' \
2464    '(-a)-s[display labels from server]:server ID: ' \
2465    '-U[list unloaded labels]' \
2466    '1::file or revisions which must contain label:_perforce_files -tR'
2467}
2468
2469
2470(( $+functions[_perforce_cmd_labelsync] )) ||
2471_perforce_cmd_labelsync() {
2472  _arguments -s : \
2473    '-l[specify label]:label:_perforce_labels' \
2474    '-a[add files to label]' \
2475    '-d[delete files from label]' \
2476    '-n[preview labelsync]' \
2477    '-q[suppress normal output messages]' \
2478    '-g[update global label]' \
2479    '*::file:_perforce_files -tR'
2480}
2481
2482
2483(( $+functions[_perforce_cmd_list] )) ||
2484_perforce_cmd_list() {
2485  _arguments -s : \
2486    '-l[use temporary list name]:list name: ' \
2487    '(-C)-d[delete list]' \
2488    '-C[limit files to client]' \
2489    '-M[forward list to master server]' \
2490    '*::file:_perforce_files -tR'
2491}
2492
2493
2494(( $+functions[_perforce_cmd_license] )) ||
2495_perforce_cmd_license() {
2496  _arguments -s : \
2497    '-o[write license to stdout]' \
2498    '-i[read license from stdin]'
2499}
2500
2501
2502(( $+functions[_perforce_cmd_lock] )) ||
2503_perforce_cmd_lock() {
2504  _arguments -s : \
2505    '-c[lock files for change]:change:_perforce_changes -tc' \
2506    '-g[lock files globally]' \
2507    '*::file:_perforce_files -to'
2508}
2509
2510
2511(( $+functions[_perforce_cmd_lockstat] )) ||
2512_perforce_cmd_lockstat() {
2513  _message 'no arguments'
2514}
2515
2516
2517(( $+functions[_perforce_cmd_logger] )) ||
2518_perforce_cmd_logger() {
2519  _arguments -s : \
2520    '-c[list events after sequence]:sequence: ' \
2521    '-t[list events after counter]:counter:_perforce_counters'
2522}
2523
2524
2525
2526(( $+functions[_perforce_cmd_login] )) ||
2527_perforce_cmd_login() {
2528  _arguments -s : \
2529    '-a[issue ticket on all host machines]' \
2530    '-h[issue ticket on host]:host: ' \
2531    '-p[display ticket, do not store]' \
2532    '-r[forward login to server]:remote spec: ' \
2533    '(-a -p -h)-s[display status of current ticket]' \
2534    '(-s)1::user:_perforce_users'
2535}
2536
2537
2538(( $+functions[_perforce_cmd_logout] )) ||
2539_perforce_cmd_logout() {
2540  _arguments -s : \
2541    '-a[invalidate ticket on server]'
2542}
2543
2544
2545(( $+functions[_perforce_cmd_logstat] )) ||
2546_perforce_cmd_logstat() {
2547  _message 'no arguments'
2548}
2549
2550
2551(( $+functions[_perforce_cmd_logtail] )) ||
2552_perforce_cmd_logtail() {
2553  _arguments -s : \
2554    '-b[specify block size, default 8192]:block size: ' \
2555    '-s[specify start offset]:offset: ' \
2556    '-m[specify max blocks]:max blocks: '
2557}
2558
2559
2560(( $+functions[_perforce_cmd_merge] )) ||
2561_perforce_cmd_merge() {
2562  local -a fileargs
2563  if [[ ${words[(I)--from]} -ne 0 ]]; then
2564    fileargs=('1:to file:_perforce_files -tR')
2565  else
2566    fileargs=('1:from file:_perforce_files -tR'
2567              '2:to file:_perforce_files')
2568  fi
2569  _arguments -s : \
2570    '-F[merge against stream'\''s expected flow]' \
2571    '-Ob[show base revision for merge]' \
2572    '-q[suppress normal output messages]' \
2573    '--from[merge from stream other than the parent stream]:stream: ' \
2574    '-m[limit merge to max files]:max files: ' \
2575    '-n[preview merge]' \
2576    '-c[open in change]:change:_perforce_changes -tc' \
2577    $fileargs
2578}
2579
2580
2581(( $+functions[_perforce_cmd_monitor] )) ||
2582_perforce_cmd_monitor() {
2583  if (( CURRENT > 2 )); then
2584    case $words[2] in
2585      (show)
2586      shift words
2587      (( CURRENT-- ))
2588      _arguments -s : \
2589        '-a[show command arguments]' \
2590        '-e[show command environment]' \
2591        '-l[long output format]'
2592      ;;
2593
2594      (terminate)
2595      _perforce_pids
2596      ;;
2597
2598      (clear)
2599      _alternative 'pids:pid:_perforce_pids' 'all:all processes:(all)'
2600      ;;
2601
2602      (*)
2603      _message "no such monitor command: $words[1]"
2604      ;;
2605    esac
2606  else
2607    local expl
2608    _wanted monitor-command expl 'monitor command' compadd show terminate clear
2609  fi
2610}
2611
2612
2613(( $+functions[_perforce_cmd_move] )) ||
2614_perforce_cmd_move() {
2615  _arguments -s : \
2616    '-c[reopen in change]:change:_perforce_changes -tc' \
2617    '-f[force move]' \
2618    '-t[specify new file type]:filetype:_perforce_filetypes' \
2619    '-n[preview move]' \
2620    '-k[perform move on server]' \
2621    '1::from file:_perforce_files -to' \
2622    '2::to file:_perforce_files -tu'
2623}
2624
2625
2626(( $+functions[_perforce_cmd_obliterate] )) ||
2627_perforce_cmd_obliterate() {
2628    if [[ ${words[(I)-y]} -gt 0 ]]; then
2629      _message \
2630": don't complete after -y; run obliterate without, then add the -y"
2631    else
2632      _arguments -s : \
2633        '-y[actually perform the operation]' \
2634        '*::file:_perforce_files -tR'
2635    fi
2636}
2637
2638
2639(( $+functions[_perforce_cmd_opened] )) ||
2640_perforce_cmd_opened() {
2641  # You might think you could check for files opened on another
2642  # client, and hence the -c completion should have the argument
2643  # -tp, but currently Perforce doesn't allow that, so -tc is correct.
2644  # This is true even if -a is also given.
2645  _arguments -s : \
2646    '-a[list files for all clients]' \
2647    '-c[list files opened in change]:change:_perforce_changes -tc' \
2648    '-C[list files open in client]:client:_perforce_clients' \
2649    '-u[list files opened by user]:user name:_perforce_users' \
2650    '-m[limit output to max files]:max files: ' \
2651    '-s[short output]' \
2652    '-x[list exclusive files]' \
2653    '-g[list files opened on Commit Server]' \
2654    '*::file:_perforce_files -to'
2655}
2656
2657
2658(( $+functions[_perforce_cmd_passwd] )) ||
2659_perforce_cmd_passwd() {
2660  _arguments -s : \
2661    '-O[explicit old password]:old password: ' \
2662    '-P[explicit new password]:new password: ' \
2663    '1::user name:_perforce_users'
2664}
2665
2666
2667(( $+functions[_perforce_cmd_ping] )) ||
2668_perforce_cmd_ping() {
2669  _arguments -s : \
2670    '-c[specify count of messages]:count of messages: ' \
2671    '-t[specify total time of test]:time in seconds: ' \
2672    '-i[specify iterations for test]:number of iterations: ' \
2673    '-f[transmit continuously without waiting for responses]' \
2674    '-p[specify pause between tests]:pause in seconds: ' \
2675    '-s[specify send size]:send size in octets: ' \
2676    '-r[specify receive size]:receive size in octets: '
2677}
2678
2679
2680(( $+functions[_perforce_cmd_populate] )) ||
2681_perforce_cmd_populate() {
2682  local -a fileargs
2683  if [[ ${words[(I)-b*]} -ne 0 ]]; then
2684    if [[ ${words[(I)-*s*]} -eq 0 ]]; then
2685      # with -b and no -s, all files are to-files (but -s may come later)
2686      fileargs=('*::to file:_perforce_files -tR')
2687    else
2688      # with -b and -s we have one from-file and any number of to-files
2689      fileargs=('*::to file:_perforce_files')
2690    fi
2691  elif [[ ${words[(I)-(S|P)]} -ne 0 ]]; then
2692      fileargs=('*::file:_perforce_files -tR')
2693  else
2694    # with no -b we have one from-file and one to-file
2695    fileargs=('1::from file:_perforce_files -tR'
2696              '2::to file:_perforce_files')
2697  fi
2698  _arguments -s : \
2699    '(-S -P)-b[use branch view'\''s source and target]:branch:_perforce_branches' \
2700    '(-S -P)-s[select source file, use branch view as target]:source file:_perforce_files -tR' \
2701    '-r[reverse mapping direction]' \
2702    '-S[use generated branch view from stream]:stream: ' \
2703    '-P[use generated branch view from parent stream]:parent stream: ' \
2704    '-d[description for submitted change]:description: ' \
2705    '-f[force deleted files to branch into target]' \
2706    '-n[preview populate]' \
2707    '-o[display files created by populate]' \
2708    '-m[limit max actions]:max actions: ' \
2709    $fileargs
2710}
2711
2712
2713(( $+functions[_perforce_cmd_print] )) ||
2714_perforce_cmd_print() {
2715  _arguments -s : \
2716    '-a[print all revisions in range]' \
2717    '-A[print files in archive depots]' \
2718    '-k[suppress keyword expansion]' \
2719    '-o[redirect output to file]:file:_files' \
2720    '-q[suppress header]' \
2721    '-m[limit max files]:max files: ' \
2722    '-U[print files in unload depot]:unload file:_perforce_files' \
2723    '*::file:_perforce_files -tR'
2724}
2725
2726
2727(( $+functions[_perforce_cmd_protect] )) ||
2728_perforce_cmd_protect() {
2729  _arguments -s : \
2730    '-o[write protection table to standard output]' \
2731    '-i[read protection table from standard input]'
2732}
2733
2734
2735(( $+functions[_perforce_cmd_protects] )) ||
2736_perforce_cmd_protects() {
2737  _arguments -s : \
2738    '(-g -u)-a[display protection lines for all users]' \
2739    '(-a -u)-g[display protection lines for group]:perforce group:_perforce_groups' \
2740    '(-a -g)-u[display protection lines for user]:perforce user:_perforce_users' \
2741    '-h[display protection lines for host]:host:_perforce_hosts' \
2742    '-m[report single word summary]' \
2743    '*:file:_perforce_files'
2744}
2745
2746
2747(( $+functions[_perforce_cmd_prune] )) ||
2748_perforce_cmd_prune() {
2749  _arguments -s : \
2750    '-y[execute prune]' \
2751    '-S[stream to prune]:stream: '
2752}
2753
2754
2755(( $+functions[_perforce_cmd_pull] )) ||
2756_perforce_cmd_pull() {
2757  _arguments -s : \
2758    '-i[repeat as specified]:seconds between repeats: ' \
2759    '-u[retrieve file content rather than journal]' \
2760    '-p[display information about pending transfers]' \
2761    '-J[specify prefix for journal file]:journal file prefix: '
2762}
2763
2764
2765(( $+functions[_perforce_cmd_reconcile] )) ||
2766_perforce_cmd_reconcile() {
2767  _arguments -s : \
2768    '-n[preview reconcile]' \
2769    '-c[open files for change]:change:_perforce_changes -tc' \
2770    '-e[open modified files for edit]' \
2771    '-a[open new files for add]' \
2772    '-d[open removed files for delete]' \
2773    '-f[reformat filenames with wildcard characters]' \
2774    '-I[do not perform ignore checking]' \
2775    '-l[output relative paths]' \
2776    '-m[check file modification times]' \
2777    '-w[force client files to be updated to match depot]' \
2778    '-k[reconcile have list with client]' \
2779    '*:file:_perforce_files -tu'
2780}
2781
2782
2783(( $+functions[_perforce_cmd_rec] )) ||
2784_perforce_cmd_rec() {
2785  _perforce_cmd_reconcile "$@"
2786}
2787
2788
2789(( $+functions[_perforce_cmd_rename] )) ||
2790_perforce_cmd_rename() {
2791  _perforce_cmd_move "$@"
2792}
2793
2794
2795(( $+functions[_perforce_cmd_reopen] )) ||
2796_perforce_cmd_reopen() {
2797  # Assume user doesn't want to reopen to same changelist.
2798  integer pos=${words[(I)-c]}
2799  if (( pos )); then
2800    _perforce_exclude_change=${words[pos+1]}
2801  elif [[ -n ${words[(R)-c?*]} ]]; then
2802    _perforce_exclude_change=${${words[(R)-c?*]}##-c}
2803  fi
2804
2805  _arguments -s : \
2806    '-c[reopen files for change]:change:_perforce_changes -tc' \
2807    '-t[specify new file type]:filetype:_perforce_filetypes' \
2808    '*::file:_perforce_files -to'
2809}
2810
2811
2812(( $+functions[_perforce_cmd_replicate] )) ||
2813_perforce_cmd_replicate() {
2814  _arguments -s : \
2815    '-i[specify interval in seconds]:interval: ' \
2816    '-j[specify journal number (/ position)]:journal number: ' \
2817    '-J[specify file prefix]:file prefix: ' \
2818    '-k[keep pipe open]' \
2819    '-o[specify output file]:output file:_files' \
2820    '-R[reconnect on failure, needs -i]' \
2821    '-s[specify file to track state]:state file:_files' \
2822    '-T[space-separate list of tables not to transfer]' \
2823    '-x[terminate when journal rotates]' \
2824    '*::->_command'
2825}
2826
2827
2828(( $+functions[_perforce_cmd_resolve] )) ||
2829_perforce_cmd_resolve() {
2830  _arguments -s : \
2831    '-A-[limit resolve attempts]:resolve attempts:((
2832a\:resolve\ attributes
2833b\:resolve\ file\ branching
2834c\:resolve\ file\ content\ changes
2835d\:resolve\ file\ deletions
2836m\:resolve\ moved\ and\ renamed\ files
2837t\:resolve\ filetype\ changes
2838Q\:resolve\ charset\ changes
2839))' \
2840    '-a-[set automatic resolve]:resolve:((
2841s\:skip\ files\ that\ need\ merging
2842m\:skip\ files\ with\ conflicts
2843f\:accept\ merged\ files\ with\ conflicts
2844t\:use\ theirs
2845y\:use\ yours))' \
2846    '-d-[control whitespace merging]:whitespace option:((
2847b\:ignore\ whitespace\ changes
2848w\:ignore\ whitespace\ altogether
2849l\:ignores\ line\ endings))' \
2850    '-f[re-resolve files]' \
2851    '-n[preview resolve]' \
2852    '-N[preview resolve with summary]' \
2853    '-o[display base file name and revision for merge]' \
2854    '-t[force textual merge]' \
2855    '-v[insert markers for all changes]' \
2856    '-c[limit resolve to change]:change:_perforce_changes -tc' \
2857    '*::file:_perforce_files -to'
2858}
2859
2860
2861(( $+functions[_perforce_cmd_resolved] )) ||
2862_perforce_cmd_resolved() {
2863  _arguments -s : \
2864    '-o[report revision used as base for resolve]' \
2865    '*::file:_perforce_files -tr'
2866}
2867
2868
2869(( $+functions[_perforce_cmd_revert] )) ||
2870_perforce_cmd_revert() {
2871  _arguments -s : \
2872    '-a[revert open unchanged files]' \
2873    '-n[preview revert]' \
2874    '-k[mark files as reverted on server]' \
2875    '-w[delete new files]' \
2876    '-c[revert files opened in change]:change:_perforce_changes -tc' \
2877    '-C[specify client]:client:_perforce_clients' \
2878    '*::file:_perforce_files -to'
2879}
2880
2881
2882(( $+functions[_perforce_cmd_review] )) ||
2883_perforce_cmd_review() {
2884  _arguments -s : \
2885    '-c[specify change]:change:_perforce_changes -ts' \
2886    '-t[specify counter]:counter:_perforce_counters'
2887}
2888
2889
2890(( $+functions[_perforce_cmd_reviews] )) ||
2891_perforce_cmd_reviews() {
2892  _arguments -s : \
2893    '-c[limit files submitted in change]:change:_perforce_changes -ts' \
2894    '-C[limit files opened in client]:client:_perforce_clients' \
2895    '*::file:_perforce_files'
2896}
2897
2898
2899(( $+functions[_perforce_cmd_set] )) ||
2900_perforce_cmd_set() {
2901  # Only works under Windoze but maybe we are on Cygwin.
2902  _arguments -s : \
2903    '-q[remove origin]' \
2904    '-s[set for whole system]' \
2905    '-S[specify service]:service: ' \
2906    "*::environment variable:_perforce_variables"
2907}
2908
2909
2910(( $+functions[_perforce_cmd_shelve] )) ||
2911_perforce_cmd_shelve() {
2912  _arguments -s : \
2913    '-i[read change spec from standard input]' \
2914    '(-i)-c[shelve files in change]:change:_perforce_changes -tc' \
2915    '-f[overwrite existing shelved files]' \
2916    '-r[replace shelved files in change]' \
2917    '-a[handle unchanged files]:option:(submitunchanged leaveunchanged)' \
2918    '(-p -a -i -r)-d[delete shelved files]' \
2919    '-p[promote shelved change to commit server]' \
2920    '(-i -r)*::file:_perforce_files -to'
2921}
2922
2923
2924(( $+functions[_perforce_cmd_status] )) ||
2925_perforce_cmd_status() {
2926  _arguments -s : \
2927    '-c[list files in change]:change:_perforce_changes -tc' \
2928    '-A[list all new, modified, and removed files]' \
2929    '-e[list modified files]' \
2930    '-a[list new files]' \
2931    '-d[list removed files]' \
2932    '-f[reformat filenames with wildcard characters]' \
2933    '-s[summarize output for new files]' \
2934    '-I[do not perform ignore checking]' \
2935    '-m[check file modification times]' \
2936    '-k[reconcile have list with client]' \
2937    '*:file:_perforce_files -tuo'
2938}
2939
2940
2941(( $+functions[_perforce_cmd_sizes] )) ||
2942_perforce_cmd_sizes() {
2943  _arguments -s : \
2944    '-a[list all revisions in range]' \
2945    '-b[specify blocksize]:blocksize in bytes: ' \
2946    '(-H)-h[print sizes in human-readable form (GiB)]' \
2947    '(-h)-H[print sizes in human-readable form (GB)]' \
2948    '-m[limit max files]:max files: ' \
2949    '-s[sum the file sizes]' \
2950    '-S[display sizes for shelved files]' \
2951    '-z[omit lazy copies]' \
2952    '(-z -S)-A[display files in archive depots]' \
2953    '-U[display sizes for unload files]' \
2954    '*:file:_perforce_files -tR'
2955}
2956
2957
2958# TODO Add more logic for subcommands
2959#p4 stream edit
2960#p4 stream resolve [-a<flag>] [-n] [-o]
2961#p4 stream revert
2962(( $+functions[_perforce_cmd_stream] )) ||
2963_perforce_cmd_stream() {
2964  _arguments -s : \
2965    '(-o -v)-d[delete stream]' \
2966    '(-f)-o[write stream spec to standard output]' \
2967    '(-o -v)-P[insert value into parent field]:parent stream: ' \
2968    '(-o -v)-t[insert value into type field]:type: ' \
2969    '(-o -v)-i[read stream spec from standard input]' \
2970    '(-o -v)-f[force modification]' \
2971    '(-f)-v[expose client view]' \
2972    '1:stream name: '
2973}
2974
2975
2976(( $+functions[_perforce_cmd_streams] )) ||
2977_perforce_cmd_streams() {
2978  _arguments -s : \
2979    '-F[limit files to pattern]:file pattern: ' \
2980    '-T[limit fields to list]:field list: ' \
2981    '-m[limit max streams]:max streams: ' \
2982    '-U[list unloaded task streams]' \
2983    '*:stream path: '
2984}
2985
2986
2987(( $+functions[_perforce_cmd_spec] )) ||
2988_perforce_cmd_spec() {
2989  _arguments -s : \
2990    '-d[delete a custom spec]' \
2991    '-i[read spec from stdin]' \
2992    '-o[write spec to stdout]' \
2993    "*::spec type:(branch change client depot group job
2994label spec trigger typemap user)"
2995}
2996
2997
2998# TODO Figure out how --parallel will work
2999#p4 submit -i [-r -s -f option] --parallel=threads=N[,batch=N][,min=N]
3000(( $+functions[_perforce_cmd_submit] )) ||
3001_perforce_cmd_submit() {
3002  _arguments -s : \
3003    '(-s -d -e -i)-c[submit change]:change:_perforce_changes -tc' \
3004    '(-r -s -f -d -c -i --noretransfer)-e[submit shelved change]:change:_perforce_changes -tS' \
3005    '(-s -c -e -i --noretransfer)-d[specify description]:description: ' \
3006    '(-d -c -e --noretransfer)-i[read change spec from standard input]' \
3007    '-f[override submit option]:submit option:_perforce_submit_options' \
3008    '-r[reopen submitted files]' \
3009    '(-d -c)-s[include fix status in list]' \
3010    '--parallel[parallel file transfer options]:parallel options: ' \
3011    '--noretransfer[do not re-transfer submitted files]:no re-transfer?:(0 1)' \
3012    '*::file:_perforce_files -to -tr'
3013}
3014
3015
3016# TODO Figure out how --parallel will work
3017#--parallel=threads=N[,batch=N][,batchsize=N][,min=N][,minsize=N]
3018(( $+functions[_perforce_cmd_sync] )) ||
3019_perforce_cmd_sync() {
3020  _arguments -s : \
3021    '-f[force resynchronisation]' \
3022    '-L[use full depot syntax]' \
3023    '-n[preview sync]' \
3024    '-N[preview sync with summary]' \
3025    '(-s -p)-k[update server without syncing files]' \
3026    '(-f -k -r -s)-p[sync client without updating server]' \
3027    '-q[suppress normal output messages]' \
3028    '(-s -p)-r[reopen moved files in new location]' \
3029    '(-f -k -r -p)-s[do not clobber modified files]' \
3030    '-m[limit max files to sync]:max files: ' \
3031    '--parallel[parallel file transfer options]:parallel options: ' \
3032    '*::file:_perforce_files -tR'
3033}
3034
3035
3036(( $+functions[_perforce_cmd_tag] )) ||
3037_perforce_cmd_tag() {
3038  _arguments -s : \
3039    '-d[delete association between label and files]' \
3040    '-n[preview tag]' \
3041    '-g[update global label]' \
3042    '-U[create label with autoreload option]' \
3043    '-l[specify label]:label:_perforce_labels' \
3044    '*::file:_perforce_files -tR'
3045}
3046
3047
3048(( $+functions[_perforce_cmd_tickets] )) ||
3049_perforce_cmd_tickets() {
3050  # No arguments.
3051  _arguments -s :
3052}
3053
3054
3055(( $+functions[_perforce_cmd_triggers] )) ||
3056_perforce_cmd_triggers() {
3057  _arguments -s : \
3058    '-o[output form to stdout]' \
3059    '-i[read from stdin]'
3060}
3061
3062
3063(( $+functions[_perforce_cmd_typemap] )) ||
3064_perforce_cmd_typemap() {
3065  _arguments -s : \
3066    '-o[output table to stdout]' \
3067    '-i[read table from stdin]'
3068}
3069
3070
3071(( $+functions[_perforce_cmd_unlock] )) ||
3072_perforce_cmd_unlock() {
3073  _arguments -s : \
3074    '-s[unlock files from shelved change]:change:_perforce_changes -tS' \
3075    '-c[unlock files from change]:change:_perforce_changes -tc' \
3076    '-x[unlock exclusive files]' \
3077    '-f[unlock files owned by other users]' \
3078    '*::file:_perforce_files'
3079}
3080
3081
3082(( $+functions[_perforce_cmd_unshelve] )) ||
3083_perforce_cmd_unshelve() {
3084  _arguments -s : \
3085    '-s[unshelve files from change]:change:_perforce_changes -tS' \
3086    '-c[unshelve files to change]:change:_perforce_changes -tc' \
3087    '-f[force clobbering of writeable files]' \
3088    '-n[preview unshelve]' \
3089    '-b[use branch view for unshelve]:branch:_perforce_branches' \
3090    '-S[use generated branch view from stream]:stream: ' \
3091    '-P[use generated branch view from parent stream]:parent stream: ' \
3092    '*::file, pattern allowed:_perforce_files'
3093}
3094
3095
3096(( $+functions[_perforce_cmd_update] )) ||
3097_perforce_cmd_update() {
3098  _arguments -s : \
3099    '-f[force resynchronisation]' \
3100    '-L[use full depot syntax]' \
3101    '-n[preview update]' \
3102    '-N[preview update with summary]' \
3103    '-q[suppress normal output messages]' \
3104    '-m[limit max files to update]:max files: ' \
3105    '*::file:_perforce_files -tR'
3106}
3107
3108
3109(( $+functions[_perforce_cmd_user] )) ||
3110_perforce_cmd_user() {
3111  _arguments -s : \
3112    '(-o -i)-d[delete user]' \
3113    '(-f -i -d)-o[write user spec to standard output]' \
3114    '(-o -d)-i[read user spec from standard input]' \
3115    '(-o)-f[force edit of user]' \
3116    '(-i)1::username:_perforce_users'
3117}
3118
3119
3120(( $+functions[_perforce_cmd_users] )) ||
3121_perforce_cmd_users() {
3122  _arguments -s : \
3123    '-m[limit output to max users]:max users: ' \
3124    '-a[output service and operator users]' \
3125    '-l[long output]' \
3126    '-r[list only replica users]' \
3127    '-c[list only central server users]' \
3128    '*::username:_perforce_users'
3129}
3130
3131
3132(( $+functions[_perforce_cmd_verify] )) ||
3133_perforce_cmd_verify() {
3134  _arguments -s : \
3135    '-m[limit revisions]:max revisions: ' \
3136    '-q[operate quietly]' \
3137    '-u[compute and save digest if missing]' \
3138    '-v[compute and save all digets]' \
3139    '-z[skip duplicates]' \
3140    '*::file:_perforce_files -tR'
3141}
3142
3143
3144(( $+functions[_perforce_cmd_where] )) ||
3145_perforce_cmd_where() {
3146  _perforce_files
3147}
3148
3149
3150(( $+functions[_perforce_cmd_workspace] )) ||
3151_perforce_cmd_workspace() {
3152  _perforce_cmd_client "$@"
3153}
3154
3155
3156(( $+functions[_perforce_cmd_workspaces] )) ||
3157_perforce_cmd_workspaces() {
3158  _perforce_cmd_clients "$@"
3159}
3160
3161
3162_perforce "$@"
3163