#compdef p4 -value-,P4CLIENT,-default- -value-,P4PORT,-default- -value-,P4MERGE,-default- -value-,P4USER,-default- # Increasingly loosely based on _cvs version 1.17. # Styles, tags and contexts # ========================= # # If the `verbose' style is set (it is assumed by default), verbose # descriptions are provided for many completed quantities derived # dynamically such as subcommand names, labels, changes -- in fact, # just about anything for which Perforce itself produces a verbose, # one-line description. It may be turned off in the context of each # subcommand e.g. # zstyle ':completion:*:p4-labelsync:*' verbose false # or for a particular tag, e.g. changes, # zstyle ':completion:*:changes' verbose false # or just for top-level completion (i.e. up to and including completion # of the subcommand): # zstyle ':completion:*:p4:*' verbose false # or for p4 as a whole, # zstyle ':completion:*:p4(-*|):*' verbose false # This is actually handled by the `_describe' function underneath the # Perforce completion system; it's mentioned here as verbosity adds # significantly to a lot of the Perforce completions. # # Note that completing change numbers is not very useful if `verbose' is # turned off. There is no speed advantage for turning it off, either. # (Changes are also known as changelists or changesets. The functions # and tags here all consistently use `changes'.) # # The style `max' can be set to a number which limits how many # possibilities can be shown when selecting changes or jobs. This is # handled within Perforce, so the completion code may limit the number even # further. If not set explicitly, the value is taken to be 20 to avoid a # huge database being output. Set it to a larger number if necessary. # # The style `all-files' is used to tell the completion system to # complete any file in a given context. This is for use in places # where it would, for example, only complete files opened for editing. # See the next section for more. # # The style `depot-files' tells the system to complete files by asking # Perforce for a list where it would otherwise complete files locally by # the standard mechanism --- basically any time you don't use // notation # and there is no restriction e.g. to opened files only. There is likely # to be a significant speed penalty for this; it is turned off by default # in all contexts. The advantage is that it cuts out files not maintained # by Perforce. (Again, note this is a style, not a tag.) Contexts # where this might be particularly useful include p4-diff or p4-diff2. # # The tags depot-files and depot-dirs also exist; they are used whenever # the system is completing files or directories by asking Perforce # to list them, rather than by using normal file completion. # # The tag subdirs is used to complete the special `...' which tells # Perforce to search all subdirectories. Hence you can turn this # feature off by suitably manipulating your tags. # # Completion of files and their revisions # ======================================= # # File completion handles @ and # suffixes. If the filename is completed, # typing @ or # removes the space which was automatically added. # The context used has `at-suffix' or `hash-suffix' in the position # before the tag to indicate suffix completion (as always, ^Xh will # show you all possible contexts). This makes it possible # to select changes, dates, labels and clients using the tag-order # style. For example, # zstyle ':completion:*:p4-*:at-suffix:*' tag-order changes '*' # will force all completion after `@' to show changes first. Executing # _next_tags (usually ^x^n) will cycle between that and the remaining # tags (dates, labels, clients). # # A # is automatically quoted when handled in this way; if the file is # typed by hand or the completion didn't finish (e.g. you typed a character # in the middle of menu completion), you probably need to type `\#' by # hand. The problem is that the completion system uses extended globbing # and hence a pattern of the form `filename#' always matches `filename' # (since e# matches any number of e's including one). Hence this can look # like an expansion which expands to `filename'. # # After @, you can complete changes (note the use of the style `max' # above), labels, clients or even dates, while after `#' you can # complete numeric revisions or the special revision names head, none, # have. These are available whether or not you completed the filename; if # the file doesn't exist, numeric revisions won't work, but the rest will # (though what Perforce will do with the resulting command is another matter). # # In addition, when completing after `file@', only changes specific to `file' # will be shown (exactly the list of changes Perforce shows from the # command `p4 changes file'). If this doesn't work, chances are that # `file' does not exist. Having a multi-directory match (literal `...') # in `file' should work fine, since `p4 changes' recognises all normal # Perforce file syntax. # # Some perforce commands allow you to specify a range of revisions or # changes as `file@1,@2' or `file#1,#2'. Currently, the second part of the # revision range can always be completed (whether the command accepts them # or not), but the comma after the first part of the range is only added # automatically if the documentation suggests the command accepts ranges at # that point. This is an auto-removable suffix, so it will disappear if # you hit space or return. Typing a `#' at this point will insert a # backslash, as before. The # and @ are never added automatically; you # have to select one by hand. # # File completion for some functions is restricted by the Perforce # status of the file; for example, `p4 opened' only completes opened # files (surprised?) However, you can set the style (N.B. not tag) # all-files; so, for example, you can turn off the limit in this case by # zstyle ':completion:*:p4-opened:*' all-files true # Normally the file-patterns style would be used to control matching, # but as the file types are not selected by globbing it doesn't work here # However, if you set the all-files style, all filename completion is done # by the standard mechanism; in this case, the file-patterns style works # as usual. The style ignored-patterns is available in any case, even # without all-files; this is therefore generally the one to use. # # With `p4 diff', the shell will spot if you have used an option that # allows you to diff unopened files (such as -f) and in that case offer # all files; otherwise, it just offers opened files. # # Completion of changes # ===================== # # There is various extra magic available any time change numbers # are completed, regardless of how this was reached, i.e. # `p4 fixes -c ...' and `p4 diff filename.c@...' are treated the same way. # Note, however, these only work if you are at the point where a change # number would be completed. # # Firstly, as mentioned above there is a maximum for the number of # changes which will be shown, given by the style max, or defaulting to 20. # Only the most recent changes will be shown. This is to avoid a speed # penalty or clumsy output. If a positive numeric argument is given # when changes are being completed, the maximum is set (unconditionally) # to that number instead. # # It is also possible to give a negative numeric prefix to a listing widget # (i.e. typically whatever is bound to ^D). If there is already a change # number on the line, e.g. from cycling through a menu of choices, the full # description for that change is shown in the format of a completion # listing. [TODO: this could be made configurable with a style.] # # It may be necessary to abandon the current completion attempt before # typing this to force the completion system to display the new text. # Replacing delete-char-or-list with the following user defined widget # (create with `zle -N ...') will force this for any negative prefix argument. # (( ${NUMERIC:-0} < 0 )) && (( CURSOR = CURSOR )) # zle delete-char-or-list # # Completion of jobs # ================= # # Completing jobs uses the same logic for the numeric prefix as completing # changes: a positive prefix changes the maximum number of jobs which # will be shown, and a negative prefix when listing shows the full # text for the job whose name is currently inserted on the command line. # In this case, the entire text of the word being completed is assumed # to constitute the job name (which is almost certainly correct). # # Completion of dates # =================== # # In a file revision specification it is possible to give a date # in the form file@YYYY/MM/DD:hh:mm:ss, which may be completed. This # is ever so slightly less silly than it sounds. Any component entered # by hand with the appropriate suffix will be ignored; any component # completed will be set to the current value. Hence you can easily # specify, say, one month ago by using the completed value for all # components except the month and setting that to one less. The shell # will also happily append the appropriate suffix if you try to complete # after anything which is already the appropriate width. (Perforce # supports two-digit years, but these are confusing and no longer # particularly useful as they refer to the twentieth century, so # the shell does not.) # # Calls to p4 # =========== # # Much of the information from Perforce is provided by calls to p4 # commands. This is done via the _call_program interface, as described # in the zshcompletesys manual page. Hence a suitable context with the # `command' style allows the user to take control of this call. # The tags used are the name of the p4 command, or in the case of # calls to help subcommands, `help-'. Note that if the # value of the style begins with `-', the arguments to the perforce # command are appended to the remaining words of the style before calling # the command. # # Programmes taking p4-style arguments # ==================================== # # It is possible to use the _perforce completion with other commands # which behave like a subcommand of p4 by setting the service type # to p4-. For example, # compdef _perforce p4cvsmap=p4-files # says that the command `p4cvsmap' takes arguments like `p4 files'. # Often the options will be different; if this is a problem, you # will need to write your own completer which loads _perforce and # calls its functions directly. # # TODO # ==== # # No mechanism is provided for completely ignoring certain files not # handled by Perforce as with .cvsignore. This could be done ad hoc. # However, the ignored-patterns style and the parameter $fignore are # of course applied as usual, so setting ignored-patterns for the # context `:completion:*:p4[-:]*' should work. _perforce() { # rely on localoptions setopt nonomatch local p4cmd==p4 match mbegin mend integer i if [[ $service = -value-* ]]; then # Completing parameter value. # Some of these --- in particular P4PORT --- don't need # the perforce server. case $compstate[parameter] in (P4PORT) _perforce_hosts_ports ;; (P4CLIENT) _perforce_clients ;; (P4MERGE) _command_names -e ;; (P4USER) _users ;; esac # We do not handle values anywhere else. return fi if [[ $p4cmd = '=p4' ]]; then _message "p4 executable not found: completion not available" return fi if (( ! ${#_perforce_cmd_list} )); then (( ${+_perforce_cmd_list} )) || typeset -ga _perforce_cmd_list local hline # Output looks like command-namedescription in words... # Ignore blank lines and the heading line beginning `Perforce...' # Just gets run once, then cached, so don't bother optimising # this to a grossly unreadable parameter substitution. _call_program help-commands p4 help commands | while read -A hline; do (( ${#hline} < 2 )) && continue [[ $hline[1] = (#i)perforce ]] && continue _perforce_cmd_list+=("${hline[1]}:${hline[2,-1]}") done fi # If we are given a service of the form p4-cmd, treat this # as if it was after `p4 cmd'. This provides an easy way in # for scripts and functions that emulate the behaviour of # p4 subcommands. Note we don't shorten the command line arguments. if [[ $service = p4-(#b)(*) ]]; then local curcontext="$curcontext" if (( $+functions[_perforce_cmd_${match[1]}] )); then curcontext="${curcontext%:*:*}:p4-$match[1]:" _perforce_cmd_${match[1]} else _message "unhandled _perforce service: $service" fi fi # We need to try and check if we are before or after the # subcommand, since some of the options with arguments, in particular -c, # work differently. It didn't work if I just added '*::...' to the # end of the arguments list, anyway. for (( i = 2; i < CURRENT; i++ )); do if [[ $words[i] = -[cCdHLpPux] ]]; then # word with following argument (( i++ )) elif [[ $words[i] != -* ]]; then break fi done if (( i >= CURRENT )); then _arguments -s : \ '-c+[client]:client:_perforce_clients' \ '-C+[charset]:charset:_perforce_charsets' \ '-d+[current directory]:directory:_path_files -g "*(/)"' \ '-H+[hostname]:host:_hosts' \ '-G[python output]' \ '-L+[message language]:language: ' \ '-p+[server port]:port:_ports' \ '-P+[password on server]:password: ' \ '-s[output script tags]' \ '-u+[user]:user name:_users' \ '-x+[filename or -]:file:_perforce_files_or_minus' \ '1:perforce command:_perforce_commands' else (( i-- )) (( CURRENT -= i )) shift $i words _perforce_command_args fi } # # Command and argument dispatchers # (( $+functions[_perforce_commands] )) || _perforce_commands() { _describe -t p4-commands 'Perforce command' _perforce_cmd_list } (( $+functions[_perforce_command_args] )) || _perforce_command_args() { local curcontext="$curcontext" cmd=${words[1]} if (( $+functions[_perforce_cmd_$cmd] )); then curcontext="${curcontext%:*:*}:p4-${cmd}:" _perforce_cmd_$cmd else _message "unhandled perforce command: $cmd" fi } # # Helper functions # (( $+functions[_perforce_branches] )) || _perforce_branches() { local bline match mbegin mend local -a bl bl=(${${${(f)"$(_call_program branches p4 branches 2>/dev/null)"}##Branch }/ /:}) [[ $#bl -eq 1 && $bl[1] = '' ]] && bl=() (( $#bl )) && _describe -t branches 'Perforce branch' bl } (( $+functions[_perforce_changes] )) || _perforce_changes() { local cline match mbegin mend max ctype num comma file local -a cl cstatus zstyle -s ":completion:${curcontext}:" max max if [[ ${NUMERIC:-0} -lt 0 && -z $compstate[insert] ]]; then # Not inserting (i.e. just listing) and given a negative # prefix argument. Instead of listing possible completions, # show the full description for the change number on the line at # the moment. [[ $PREFIX = (|*[^[:digit:]])(#b)(<->) ]] && num+=$match[1] [[ $SUFFIX = (#b)(<->)* ]] && num+=$match[1] if [[ -n $num ]]; then _message -r "$(_call_program describe p4 describe $num)" return 0 fi elif [[ ${NUMERIC:-0} -gt 0 ]]; then max=$NUMERIC fi # Hack: assume the arguments we want are at the end. while [[ $argv[-1] = -t? ]]; do case $argv[-1] in # Change embedded in filename; extract that and remove # the corresponding prefix. Remove possible `#'s, too, # in case we are looking at a range. (-tf) file=${${(Q)PREFIX}%%[\#@]*} compset -P '*@' ;; # Changes already submitted (-ts) cstatus=(-s submitted) ctype="submitted " ;; # Changes still pending (-tp) cstatus=(-s pending) ctype="pending " ;; # Range allowed: append comma and supply rules for # removing and handling subsequent `#'. (-tR) comma=(-S, -R _perforce_file_suffix) esac argv=($argv[1,-2]) done # Limit to the 20 most recent changes by default to avoid huge # output. cl=( ${${${${(f)"$(_call_program changes p4 changes -m ${max:-20} $cstatus \$file)"}##Change\ }//\ on\ /:}/\ by\ /\ } "default:change not yet numbered") [[ $#cl -eq 1 && $cl[1] = '' ]] && cl=() _describe -t changes "${ctype}change" cl $comma } (( $+functions[_perforce_charsets] )) || _perforce_charsets() { local expl _wanted charset expl 'character set' \ compadd eucjp iso8859-1 shiftjis utf8 winansi } (( $+functions[_perforce_clients] )) || _perforce_clients() { local cline match mbegin mend local -a slash cl # Are we completing after an @, or a client view in a filespec? if ! compset -P '*@'; then compset -P '//' && slash=(-S/ -q) fi cl=(${${${(f)"$(_call_program clients p4 clients)"}##Client\ }/\ /:}) [[ $#cl -eq 1 && $cl[1] = '' ]] && cl=() _describe -t clients 'Perforce client' cl $slash } (( $+functions[_perforce_counters] )) || _perforce_counters() { local cline match mbegin mend local -a cl cl=(${${${(f)"$(_call_program counters p4 counters)"}/\ /:}/\=/current value}) [[ $#cl -eq 1 && $cl[1] = '' ]] && cl=() _describe -t counters 'Perforce counter' cl } (( $+functions[_perforce_counter_values] )) || _perforce_counter_values() { if [[ -n $words[CURRENT-1] ]]; then local value="$(_call_program counter p4 counter $words[CURRENT-1] 2>/dev/null)" if [[ -n $value ]]; then # No space. This allows stuff like incarg and decarg. compstate[insert]=1 _wanted value expl 'counter value' compadd $value fi fi } (( $+functions[_perforce_dates] )) || _perforce_dates() { # Only useful in a file spec after `@'. compset -P '*@' # Date/time now in format required by Perforce. local now="$(date +%Y:%m:%d:%T)" name prefix local -a nowarray offer opts matchpats suffixes names nowarray=(${(s.:.)now}) names=( year month day\ of\ month hour minute second) suffixes=( / / : : : '' ) integer i prefix=${(Q)PREFIX} for (( i = 6; i >= 1; i-- )); do # Match from the most specific back. # The following is one of those occasions where zsh # substitution skips to the right answer without ever # passing through the real world on the way. if [[ $prefix = *${(j.*.)~suffixes[1,i-1]}* ]]; then (( i > 1 )) && compset -P "*$suffixes[i-1]" # If what's there already is the right length, # just accept it and add the suffix. prefix=${(Q)PREFIX} if [[ ${#prefix} = ${#nowarray[i]} ]]; then offer=($prefix) else offer=($nowarray[i]) fi [[ -n $suffixes[i] ]] && opts=(-S $suffixes[i] -q) name=$names[i] break fi done _describe -t dates $name offer $opts } (( $+functions[_perforce_depots] )) || _perforce_depots() { local dline match mbegin mend max local -a dl dl=(${${${(f)"$(_call_program depots p4 depots)"}##Depot\ }/\ /:}) [[ $#dl -eq 1 && $dl[1] = '' ]] && dl=() _describe -t depots 'depot name' dl } (( $+functions[_perforce_files_or_minus] )) || _perforce_files_or_minus() { _alternative 'minus:minus sign:(-)' 'files:file name:_files' } (( $+functions[_perforce_file_suffix] )) || _perforce_file_suffix() { # Used with compadd -R to handle @ or # after a file name. # Differs from compadd -r '...' in that it quotes `#' if typed. [[ $1 = 1 ]] || return if [[ $LBUFFER[-1] = [\ ,] ]]; then if [[ $KEYS = '#' ]]; then if [[ $LBUFFER[-1] = , ]]; then # Range: no suffix removal but add a backslash LBUFFER+=\\ else # Suffix removal with an added backslash LBUFFER="$LBUFFER[1,-2]\\" fi elif [[ $KEYS = (*[^[:print:]]*|[[:blank:]\;\&\|]) || \ ( $KEYS = @ && $LBUFFER[-1] = ' ' ) ]] ; then # Normal suffix removal LBUFFER="$LBUFFER[1,-2]" fi fi } # # Helper functions for the helper function _perforce_files. These files # are low-level enough that they don't handle tags; this is done # by the _alternative handler in _perforce_files. # (( $+functions[_perforce_integrated_files] )) || _perforce_integrated_files() { local pfx=${(Q)PREFIX} type local -a files compset -P '*/' files=(${${${(f)"$(_call_program integrated p4 integrated \"\$pfx\*\$\{\(Q\)SUFFIX\}\" 2>/dev/null)"}%\#*}##*/}) [[ $#files -eq 1 && $files[1] = '' ]] && files=() compadd "$@" -a files } (( $+functions[_perforce_opened_files] )) || _perforce_opened_files() { local pfx=${(Q)PREFIX} type local -a files compset -P '*/' files=(${${${(f)"$(_call_program opened p4 opened \"\$pfx\*\$\{\(Q\)SUFFIX\}\" 2>/dev/null)"}%\#*}##*/}) [[ $#files -eq 1 && $files[1] = '' ]] && files=() compadd "$@" -a files } (( $+functions[_perforce_resolved_files] )) || _perforce_resolved_files() { local pfx=${(Q)PREFIX} type local -a files compset -P '*/' files=(${${${(f)"$(_call_program resolved p4 resolved \"\$pfx\*\$\{\(Q\)SUFFIX\}\" 2>/dev/null)"}%\#*}##*/}) [[ $#files -eq 1 && $files[1] = '' ]] && files=() compadd "$@" -a files } (( $+functions[_perforce_subdirs] )) || _perforce_subdirs() { # This has no other function than to offer to add the `...' used # by Perforce to indicate a recursive search of directories. # Bit pathetic, really. compset -P '*/' compadd "$@" '...' } (( $+functions[_perforce_depot_dirs] )) || _perforce_depot_dirs() { # Normal completion of directories in depots local pfx=${(Q)PREFIX} expl local -a files compset -P '*/' files=(${"${(f)$(_call_program dirs p4 dirs \"\$pfx\*\$\{\(Q\)SUFFIX\}\" 2>/dev/null)}"##*/}) [[ $#files -eq 1 && $files[1] = '' ]] && files=() compadd "$@" -S / -q -a files } (( $+functions[_perforce_depot_files] )) || _perforce_depot_files() { # Normal completion of files in depots local pfx=${(Q)PREFIX} expl local -a files compset -P '*/' files=(${${${(f)"$(_call_program files p4 files \"\$pfx\*\$\{\(Q\)SUFFIX\}\" 2>/dev/null)"}%\#*}##*/}) [[ $#files -eq 1 && $files[1] = '' ]] && files=() compadd "$@" -R _perforce_file_suffix -a files } (( $+functions[_perforce_client_dirs] )) || _perforce_client_dirs() { # This is a slightly odd addition which isn't often necessary. # When completing directories in a client specification, Perforce # doesn't tell you about intermediate directories which are in # the client, but not in the depot. (Well... sometimes. I've # had some odd results with this. I suspect there may be a bug # but I don't really know enough to be sure.) # # For example, if my view contains # //depot/branches/rev1.2/... //pws_client/branches/rev1.2/... # then `p4 dirs "//pws_client/*"' won't mention the `branches' # directory because the view actually starts lower down. So # we add it by hand when necessary. # # We don't want to waste time on this, since it's not the usual # case, so we cache the results where necessary. This means # recording all the clients that we can later ask about if necessary. # To flush the cache, `unset _perforce_client_list _perforce_client_dirs'. if (( ! ${+_perforce_client_list} )); then # Retrieve the list of clients. typeset -gA _perforce_client_list local -a tmplist local tmpelt tmplist=(${${${(f)"$(_call_program clients p4 clients)"}##Client\ }%%\ *}) [[ $#tmplist -eq 1 && $tmplist[1] = '' ]] && tmplist=() for tmpelt in $tmplist; do _perforce_client_list[$tmpelt]=1 done fi # See if the first path element is a client. Very often it # will actually be a depot, so we test this as quickly as possible. local client=${${PREFIX##//}%%/*} [[ -z ${_perforce_client_list[$client]} ]] && return 1 local oldifs=$IFS IFS= type dir line dirs (( ${+_perforce_client_dirs} )) || typeset -gA _perforce_client_dirs if (( ${+_perforce_client_dirs[$client]} )); then # Already cached, although may be empty. dirs=${_perforce_client_dirs[$client]} else # We need to look at the View stanza of the client record # to see what directories exist in the client view. _call_program client "p4 client -o $client" 2>/dev/null | while read line; do case $line in ([[:blank:]]##) type= ;; ((#b)([[:alpha:]]##):*) type=${match[1]} ;; (*) if [[ $type = View ]]; then dir=${${line##[[:blank:]]##//*[[:blank:]]//$client}%%/...(/*|)} if [[ $#dir -gt 1 ]]; then dirs+="${dirs:+ }${(q)dir##/}" fi fi ;; esac done fi (( ${#dirs} )) || return 1 # Turn our string of space-separated backquoted elements into an array. dirs=(${(z)dirs}) # Get the current prefix also as an array of elements compset -P '//[^/]##/' pfx=(${(s./.)${(Q)PREFIX}}) local -a ndirs local match mbegin mend # Check matching path segments while (( ${#pfx} > 1 )); do ndirs=() for dir in $dirs; do if [[ $dir = $pfx/(#b)(*) ]]; then ndirs+=($match[1]) fi done (( ${#ndirs} )) || return 1 dirs=($ndirs) shift pfx compset -P '[^/]' done compadd -S / -q "$@" -- ${dirs%%/*} } (( $+functions[_perforce_files] )) || _perforce_files() { local pfx fline expl opt match mbegin mend range type local -a files types local dodirs unmaintained # Suffix operations can modify context local curcontext="$curcontext" while (( $# )); do if [[ $1 = -t(#b)(?) ]]; then case $match[1] in (d) dodirs=-/ ;; (u) unmaintained=1 ;; (i) types+=(integrated) ;; (o) types+=(opened) ;; (r) types+=(resolved) ;; (R) range="-tR" ;; esac fi shift done # Remove the quotes present in the word on the command line, # since we will treat this as a literal string from now on. # We might get into problems with characters recognised as # special by p4 files and p4 dirs, but worry about that later. pfx=${(Q)PREFIX} if [[ -prefix *@ ]]; then # Modify context to indicate we are in a suffix. curcontext="${curcontext%:*}:at-suffix" # Check for existing range syntax [[ $PREFIX = *[@\#]*,* ]] && range= # After @ you can specify changes, clients, labels or dates. # Note we don't remove the prefix here; we leave it to the # subcommand. This is in case it needs information from # the prefix; _perforce_changes uses this to limit the # output to relevant changes. _alternative \ "changes:change:_perforce_changes $range -tf" \ clients:client:_perforce_clients \ labels:label:_perforce_labels \ 'dates:date (+ time):_perforce_dates' elif [[ -prefix *\# ]]; then # Modify context to indicate we are in a suffix. curcontext="${curcontext%:*}:hash-suffix" # Check for existing range syntax [[ $PREFIX = *[@\#]*,* ]] && range= # Remove longest possible tail match to get name --- this # automatically handles filenames in ranges e.g. `foo#1,#3'. # (Note the compset removes the maximum possible head match, # so we only complete the second part of the range in that case.) _perforce_revisions $range elif [[ $PREFIX = //* ]]; then # This specifies files already handled by Perforce, so there's # no point trying to look for unmaintained files. Assume # the user knows what they're doing. local -a altfiles if [[ $PREFIX = //[^/]# ]]; then # Complete //clientname spec. Don't complete non-directories... # I don't actually know if they are valid here. altfiles+=("clients:Perforce client:_perforce_clients") else local donefiles=1 if [[ -z $dodirs ]]; then if [[ ${#types} -gt 0 ]] && ! zstyle -t ":completion:${curcontext}:" all-files; then for type in $types; do altfiles+=("$type-files:$type file:_perforce_${type}_files") done else altfiles+=("depot-files:file in depot:_perforce_depot_files") fi fi # Intermediate directories in a client view. # See function for notes. altfiles+=("client-dirs:client directory:_perforce_client_dirs") fi altfiles+=("depot-dirs:directory in depot:_perforce_depot_dirs" "subdirs:subdirectory search:_perforce_subdirs") _alternative $altfiles elif [[ -n $unmaintained && -z $dodirs ]]; then # a la _cvs_nonentried_files: directories are never maintained, # so skip 'em. Unmaintained files can't be integrated, opened # or resolved, so treat as exclusive (just as well, since # this bit's messy). local MATCH MBEGIN MEND local -a omitpats match=() : ${PREFIX:#(#b)(*/)(*)} pfx="$match[1]" pfx=${(e)~pfx} omitpats=( ${${${${(f)"$(_call_program files p4 files \"\$pfx\*\$\{\(Q\)SUFFIX\}\" 2>/dev/null)"}%\#*}##*/}//(#m)[][*?()<|^~#\\]/\\$MATCH} ) [[ $#omitpats -eq 1 && $omitpats[1] = '' ]] && omitpats=() if (( ${#omitpats} )); then _path_files -g "*~(*/|)(${(j:|:)~omitpats})(D.)" else _path_files fi # Don't handle suffixes for non-entried files elif (( ${#types} )) && ! zstyle -t ":completion:${curcontext}:" all-files then local -a altfiles for type in $types; do altfiles+=("$type-files:$type file:_perforce_${type}_files") done altfiles+=("depot-dirs:directory in depot:_perforce_depot_dirs" "subdirs:subdirectory search:_perforce_subdirs") _alternative $altfiles elif zstyle -t ":completion:${curcontext}:" depot-files; then local -a altfiles if [[ -z $dodirs ]]; then altfiles+=("depot-files:file in depot:_perforce_depot_files") fi altfiles+=("depot-dirs:directory in depot:_perforce_depot_dirs" "subdirs:subdirectory search:_perforce_subdirs") _alternative $altfiles else # Look locally. _alternative \ "files:file:_path_files -R _perforce_file_suffix $dodirs" \ "subdirs:subdirectory search:_perforce_subdirs" fi } # # Remaining helpers for other types of Perforce metadata. # (( $+functions[_perforce_filetypes] )) || _perforce_filetypes() { local -a values if compset -P '*+*'; then # That second `*' is deliberate --- only complete the last # letter since we can have a whole string of them. values=( "m:always set modtime on client" "w:always writeable on client" "x:set exec bit on client" "k:full RCS keyword expansion" "k:RCS expansion only for Id, Header" "l:exclusive open, disallow multiple opens" "C:server stores compress file per revision" "D:server stores deltas in RCS format" "F:server stores full file per revision" "S:server stores only head revision") _describe -t file-modifiers 'Perforce file modifier' values else values=( "text:text, translate newlines" "binary:raw bytes" "symlink:symbolic link" "apple:Mac resource + data" "unicode:text, translate newlines, store as UTF-8") _describe -t file-types 'Perforce file type' values -S+ -q fi } (( $+functions[_perforce_groups] )) || _perforce_groups() { _describe -t groups 'Perforce group' $(_call_program groups p4 groups) } (( $+functions[_perforce_hosts_ports] )) || _perforce_hosts_ports() { if compset -P '*:'; then _ports else # is this -q-able? _hosts -S : fi } (( $+functions[_perforce_jobs] )) || _perforce_jobs() { local jline match mbegin mend max local -a jl zstyle -s ":completion:${curcontext}:" max max if [[ ${NUMERIC:-0} -lt 0 && -z $compstate[insert] ]]; then # Not inserting (i.e. just listing) and given a negative # prefix argument. Instead of listing possible completions, # show the full description for the job which is on the line at # the moment. _message -r "$(_call_program jobs p4 jobs -e \"Job=\$PREFIX\$SUFFIX\" -l 2>/dev/null)" return 0 elif [[ ${NUMERIC:-0} -gt 0 ]]; then max=$NUMERIC fi _call_program jobs p4 jobs -m ${max:-20} | while read jline; do if [[ $jline = (#b)([^[:blank:]]##)' '[^[:blank:]]##' '(*) ]]; then jl+=("${match[1]}:${match[2]}") fi done _describe -t jobs 'Perforce job' jl } (( $+functions[_perforce_jobviews] )) || _perforce_jobviews() { # Jobviews (see `p4 help jobview') are ways of interrogating the # jobs/fixes database. It's basically either a set of strings, # or a set of key=value pairs, or some combination, separated # by various logical operators. The `=' could be a comparison, # but we don't currently bother with that here; it's a bit cumbersome # to complete. local line type oldifs=$IFS IFS= key value slash=/ local match mbegin mend # This is simply to split out two space-delimited words a backreferences. local m2words m2words='(#b)[[:blank:]]##([[:alnum:]]##)[[:blank:]]##([^[:blank:]]##)' local -a valuespec local -A p4fields p4values # All the characters which can separate multiple match attempts. # Ignore up to the last one. We don't try to complete these. compset -P '*[[:blank:]\^\&\|\(\)]' # According to the manual, `p4 jobspec' requires admin privileges. # If this is true even of `p4 jobspec -o', we are a bit screwed. _call_program jobspec p4 jobspec -o 2>/dev/null | while read line; do case $line in ([[:blank:]]##) type= ;; ((#b)([[:alpha:]]##):*) type=${match[1]} ;; (*) case $type in # This stanza tells us all the allowed fields. (Fields) if [[ $line = [[:blank:]]##<->${~m2words}* ]] then p4fields[${(L)match[1]}]=${match[2]} fi ;; # This stanza gives allowed values for the `select' types. (Values) if [[ $line = ${~m2words}* ]]; then p4values[${(L)match[1]}]=${match[2]} fi ;; esac ;; esac done IFS=$oldifs if (( ! ${#p4fields} )); then # We didn't get anything; add the defaults. p4fields=( date date description text job word status select user word ) p4values=( status open/suspended/closed ) fi for key in ${(k)p4fields}; do if [[ -n ${p4values[$key]} ]]; then valuespec+=("${key}:${p4fields[$key]}:(${p4values[$key]//$slash/ })") elif [[ $key = job ]]; then # Nothing special for jobs; add our own completion. valuespec+=("${key}:Perforce job:_perforce_jobs") elif [[ $key = user ]]; then # Nothing provided for user; add our own completion. valuespec+=("${key}:user:_users") else valuespec+=("${key}:${p4fields[$key]}: ") fi done _values 'Job specification parameter' $valuespec } (( $+functions[_perforce_labels] )) || _perforce_labels() { local lline match mbegin mend local -a ll # May be completing after `@'. compset -P '*@' _call_program labels p4 labels | while read lline; do if [[ $lline = (#b)'Label '([^[:blank:]]##)' '(*) ]]; then ll+=("${match[1]}:${match[2]}") fi done _describe -t labels 'Perforce label' ll } (( $+functions[_perforce_revisions] )) || _perforce_revisions() { # Doesn't handle standard completion options; requires space # in front if used as action in _arguments. local rline match mbegin mend comma expl pfx local -a rl if [[ $1 = -tR ]]; then # handle ranges comma=(-S, -R _perforce_file_suffix) shift fi # Beware of @foo,#bar; stupid but probably valid. pfx=${${(Q)PREFIX}%%[\#@]*} compset -P '*\#' # Numerical revision numbers, possibly with text. if [[ -z $PREFIX || $PREFIX = <-> ]]; then # always allowed (same as none) rl+=(0) _call_program filelog 'p4 filelog $pfx' 2>/dev/null | while read rline; do if [[ $rline = (#b)'... #'(<->)' change '(*) ]]; then rl+=("${match[1]}:${match[2]}") fi done fi # Non-numerical (special) revision names. if [[ -z $PREFIX || $PREFIX != <-> ]]; then rl+=('head:head revision' 'none:empty revision' 'have:current synced revision') fi _describe -t revisions 'revision' rl $comma } (( $+functions[_perforce_statuses] )) || _perforce_statuses() { # Perforce statuses are usually limited to a set of values # given by the jobspec. local jline match mbegin mend local -a statuses _call_program jobspec p4 jobspec -o | while read jline; do if [[ $jline = (#b)Status[[:blank:]]##(*/*) ]]; then statuses=(${(s./.)match[1]}) break fi done if (( !${#statuses} )); then # Couldn't find anything from the jobspec; add defaults. statuses=(closed open suspended) fi _describe -t statuses 'job status' statuses return 1 } (( $+functions[_perforce_variables] )) || _perforce_variables() { local line match mbegin mend expl local -a vars _call_program help-environment p4 help environment | while IFS= read line; do if [[ $line = $'\t'(#b)([A-Z][A-Z0-9_]##)* ]]; then vars+=($match[1]) fi done _wanted variable expl 'environment variable' compadd -S= -q $vars } # # Completions for p4 commands # (( $+functions[_perforce_cmd_add] )) || _perforce_cmd_add() { _arguments -s : \ '-c+[select by change]:change:_perforce_changes -tp' \ '-t+[set file type]:file type:_perforce_filetypes' \ '*:file:_perforce_files -tu' } (( $+functions[_perforce_cmd_admin] )) || _perforce_cmd_admin() { if (( CURRENT == 2 )); then local -a adcmds adcmds=( "checkpoint:checkpoint, save copy of journal file" "journal:save and truncate journal file" "stop:stop the server") _describe -t commands 'Perforce admin command' adcmds elif [[ $words[2] == (checkpoint|journal) ]]; then shift words (( CURRENT-- )) _arguments -s : \ '-z[gzip journal file]' \ '1::journal file prefix: ' fi } (( $+functions[_perforce_cmd_annotate] )) || _perforce_cmd_annotate() { # If you don't have this, it's new in release 2002.2. _arguments -s : \ '-a[all, show both added and deleted lines]' \ '-q[quiet, suppress one-line file header]' \ '*::file:_perforce_files -tR' } (( $+functions[_perforce_cmd_branch] )) || _perforce_cmd_branch() { _arguments -s : \ '(-o)-f[force operation by superuser]' \ '(-o -i)-d[delete branch]' \ '(-d -i -f)-o[write specification to standard output]' \ '(-d -o)-i[read specification from standard input]' \ '(-i)*::branch name:_perforce_branches' } (( $+functions[_perforce_cmd_branches] )) || _perforce_cmd_branches() { # No arguments. _arguments -s : } (( $+functions[_perforce_cmd_change] )) || _perforce_cmd_change() { _arguments -s : \ '(-o)-f[allow force by superuser]' \ '-s[joblist includes the fix status]' \ '(-o -i)-d[describe newly created pending change]' \ '(-d -i -f)-o[output specification to standard output]' \ '(-d -o)-i[read specification from standard input]' \ '(-i)1::change:_perforce_changes -tp' } (( $+functions[_perforce_cmd_changes] )) || _perforce_cmd_changes() { _arguments -s : \ '-i[include integrated changes]' \ '-l[long output]' \ '-c[select by client]:client:_perforce_clients' \ '-m[most recent N changes]:max changes: ' \ '-s[select by status]:status:(pending submitted)' \ '-u[select by user]:user:_users' \ '*::file:_perforce_files -tR' } (( $+functions[_perforce_cmd_client] )) || _perforce_cmd_client() { _arguments -s : \ '-f[force modification by superuser]' \ '-t[use template]:template client:_perforce_clients' \ '(-o -i -t)-d[delete client]' \ '(-d -i -f)-o[print to standard output]' \ '(-d -o -t)-i[read from standard input]' \ '1::file:_perforce_clients' } (( $+functions[_perforce_cmd_clients] )) || _perforce_cmd_clients() { # No arguments. _arguments -s : } (( $+functions[_perforce_cmd_counter] )) || _perforce_cmd_counter() { _arguments -s : \ '-d[delete counter]' \ '-f[force setting of internal counter]' \ '1:counter:_perforce_counters' \ '(-d)2::numeric value:_perforce_counter_values' } (( $+functions[_perforce_cmd_counters] )) || _perforce_cmd_counters() { # No arguments _arguments -s : } (( $+functions[_perforce_cmd_delete] )) || _perforce_cmd_delete() { _arguments -s : \ '-c[select change for deletion]:change:_perforce_changes -tp' \ '*::file:_perforce_files' } (( $+functions[_perforce_cmd_depot] )) || _perforce_cmd_depot() { _arguments -s : \ '-d[delete depot]' \ '-o[print to stdout]' \ '-i[read name from stdin]' \ '(-i)*::depot name:_perforce_depots' } (( $+functions[_perforce_cmd_depots] )) || _perforce_cmd_depots() { # No arguments _arguments -s : } (( $+functions[_perforce_cmd_describe] )) || _perforce_cmd_describe() { _arguments -s : \ '-d-[select diff option]:diff option:((b\:ignore\ blanks c\:context n\:RCS s\:summary u\:unified w\:ignore\ all\ whitespace))' \ '-s[short form]' \ '*::change:_perforce_changes' } (( $+functions[_perforce_cmd_diff] )) || _perforce_cmd_diff() { local limit [[ ${words[(I)-(f|sd|se)]} -eq 0 ]] && limit=" -to" _arguments -s : \ '-d-[select diff option]:diff option:((b\:ignore\ blanks c\:context n\:RCS s\:summary u\:unified w\:ignore\ all\ whitespace))' \ '-f[diff every file]' \ '(-sd -se -sr)-sa[opened files, different or missing]' \ '(-sa -se -sr)-sd[unopened files, missing]' \ '(-sa -sd -sr)-se[unopened files, different]' \ '(-sa -sd -se)-sr[opened files, same as depot]' \ '-t[include non-text files]' \ "*::file:_perforce_files$limit" } (( $+functions[_perforce_cmd_diff2] )) || _perforce_cmd_diff2() { _arguments -s : \ '-b[specify branch view]:branch name:_perforce_branches' \ '-d-[select diff option]:diff option:((b\:ignore\ blanks c\:context n\:RCS s\:summary u\:unified w\:ignore\ all\ whitespace))' \ '-q[only list different files]' \ '-t[include non-text files]' \ '-u[use patch-friendly output]' \ '1::first file:_perforce_files' \ '2::second file:_perforce_files' } (( $+functions[_perforce_cmd_dirs] )) || _perforce_cmd_dirs() { _arguments -s : \ '-C[only dirs on current client]' \ '-D[include dirs with deleted files]' \ '-H[only dirs on the `have'\'' list]' \ '*::directory:_perforce_files -td' } (( $+functions[_perforce_cmd_edit] )) || _perforce_cmd_edit() { _arguments -s : \ '-c[set change for edit]:change:_perforce_changes -tp' \ '-t[set filetype]:filetype:_perforce_filetypes' \ '*::file:_perforce_files' } (( $+functions[_perforce_cmd_filelog] )) || _perforce_cmd_filelog() { _arguments -s : \ '-i[follow branches]' \ '-l[long output, full change text]' \ '-m[set maximum number of revisions to show]:max revisions: ' \ '-t[include time with date]' \ '*::file:_perforce_files' } (( $+functions[_perforce_cmd_files] )) || _perforce_cmd_files() { _arguments -s : \ '-a[display all revisions in given range]' \ '*::file:_perforce_files -tR' } (( $+functions[_perforce_cmd_fix] )) || _perforce_cmd_fix() { _arguments -s : \ '-d[delete the fix]' \ '-s[set job status]:status:_perforce_statuses' \ '1::-c required:(-c)' \ '2::change:_perforce_changes' \ '3::job:_perforce_jobs' } (( $+functions[_perforce_cmd_fixes] )) || _perforce_cmd_fixes() { _arguments -s : \ '-i[include integrated changes]' \ '-j[select by job]:job:_perforce_jobs' \ '-c[select by change]:change:_perforce_changes' \ '*::fixed file:_perforce_files -tR' } (( $+functions[_perforce_cmd_flush] )) || _perforce_cmd_flush() { _arguments -s : \ '-f[force resynchronisation]' \ '-n[show operations but don'\''t perform them]' \ '*::file:_perforce_files -tR' } (( $+functions[_perforce_cmd_fstat] )) || _perforce_cmd_fstat() { _arguments -s : \ '-c+[select by change]:change:_perforce_changes -ts' \ '-C[select mapped files]' \ '-H[select synced files]' \ '-W[select opened files]' \ '-l[include fileSize, possibly slow]' \ '-P[output clientFile in full Perforce syntax]' \ '-s[shorten, no client-related data]' \ '*::file:_perforce_files' } (( $+functions[_perforce_cmd_group] )) || _perforce_cmd_group() { _arguments -s : \ '-d[delete group]' \ '-o[output to stdout]' \ '-i[read from stdin]' \ '1::perforce group:_perforce_groups' } (( $+functions[_perforce_cmd_groups] )) || _perforce_cmd_groups() { _arguments -s : \ '1::user name:_users' } (( $+functions[_perforce_cmd_have] )) || _perforce_cmd_have() { _perforce_files } (( $+functions[_perforce_cmd_help] )) || _perforce_cmd_help() { local hline if (( ! ${#_perforce_help_list} )); then (( ${+_perforce_help_list} )) || typeset -ga _perforce_help_list # All commands have help. _perforce_help_list=($_perforce_cmd_list) _call_program help p4 help | while read -A hline; do if [[ $hline[1] = p4 && $hline[2] = help ]]; then _perforce_help_list+=("$hline[3]:${hline[4,-1]}") fi done fi _describe -t help-options 'Perforce help option' _perforce_help_list } (( $+functions[_perforce_cmd_info] )) || _perforce_cmd_info() { # No arguments _arguments -s : } (( $+functions[_perforce_cmd_integrate] )) || _perforce_cmd_integrate() { local range # If -s is present, the first normal argument can't have revRange. [[ ${words[(I)-s]} -eq 0 ]] && range=" -tR" _arguments -s : \ '-b[select branch]:branch:_perforce_branches' \ '-c[select change for integration]:change:_perforce_changes -tp' \ '-f[force reintegration]' \ '-d[reintegrated deleted files]' \ '-h[integrate to revision had on client]' \ '-i[integrate if no common file base]' \ '-n[no action, dummy run]' \ '-r[reverse direction of integration with branch]' \ '-s[select source]:source file:_perforce_files -tR' \ '-t[propagate type changes]' \ '-v[leave newly branched files uncopied till sync]' \ "1:file:_perforce_files$range" \ '*::file:_perforce_files' } (( $+functions[_perforce_cmd_integrated] )) || _perforce_cmd_integrated() { _perforce_files -ti } (( $+functions[_perforce_cmd_job] )) || _perforce_cmd_job() { _arguments -s : \ '(-d -o -i)-f[force setting of readonly fields]' \ '(-f -o -i)-d[delete job]' \ '(-f -d -i)-o[print to stdout]' \ '(-d -o)-i[read from stdin]' \ '(-i)1::job:_perforce_jobs' } (( $+functions[_perforce_cmd_jobs] )) || _perforce_cmd_jobs() { _arguments -s : \ '-e[select by jobview]:jobview:_perforce_jobviews' \ '-i[included integrated changes]' \ '-l[long output, full job descriptions]' \ '-r[reverse order of job names]' \ '-m[limit to most recent N jobs]:most recent jobs: ' \ '(-e -i -l -m)-R[]' \ '*::file:_perforce_files -tR' } (( $+functions[_perforce_cmd_jobspec] )) || _perforce_cmd_jobspec() { _arguments -s : \ '-i[read form from stdin]' \ '-o[write form from to stdout]' } (( $+functions[_perforce_cmd_label] )) || _perforce_cmd_label() { _arguments -s : \ '-f[force operation]' \ '-t+[copy template]:template: ' \ '(-o -i -t)-d[delete label]' \ '(-d -f -i)-o[write to standard output]' \ '(-o -d -t)-i[read from standard input]' \ '*::label:_perforce_labels' } (( $+functions[_perforce_cmd_labels] )) || _perforce_cmd_labels() { _arguments -s : \ '1::file or revisions which must contain label:_perforce_files -tR' } (( $+functions[_perforce_cmd_labelsync] )) || _perforce_cmd_labelsync() { _arguments -s : \ '-a[add files to label]' \ '-d[delete files from label]' \ '-n[no effect, dummy run]' \ '-l[specify label]:label:_perforce_labels' \ '*::file:_perforce_files -tR' } (( $+functions[_perforce_cmd_lock] )) || _perforce_cmd_lock() { _arguments -s : \ '-c[select by change]:change:_perforce_changes -tp' \ '*::file:_perforce_files -to' } (( $+functions[_perforce_cmd_logger] )) || _perforce_cmd_logger() { _arguments -s : \ '-c[limit by counter no]:number: ' \ '-t[use counter instead of logger]:counter:_perforce_counters' } (( $+functions[_perforce_cmd_obliterate] )) || _perforce_cmd_obliterate() { _message "obliterate is dangerous: you're on your own here." } (( $+functions[_perforce_cmd_opened] )) || _perforce_cmd_opened() { _arguments -s : \ '-a[list for all clients]' \ '-c+[select by change]:change:_perforce_changes -tp' \ '*::file:_perforce_files -to' } (( $+functions[_perforce_cmd_passwd] )) || _perforce_cmd_passwd() { _arguments -s : : \ '-O[explicit old password]:old password: ' \ '-P[explicit new password]:new password: ' \ '1::user name:_users' } (( $+functions[_perforce_cmd_print] )) || _perforce_cmd_print() { _arguments -s : \ '-a[display all revisions in a range]' \ '-o[Select output file]:output file:_files' \ '-q[Suppress header]' \ '*::file:_perforce_files -tR' } (( $+functions[_perforce_cmd_protect] )) || _perforce_cmd_protect() { _arguments -s : \ '-o[write spec to stdout]' \ '-i[read spec from stdin]' } (( $+functions[_perforce_cmd_reopen] )) || _perforce_cmd_reopen() { _arguments -s : \ '-c+[select change to reopen on]:change:_perforce_changes -tp' \ '-t+[set file type]:file type:_perforce_filetypes' \ '*::file:_perforce_files -to' } (( $+functions[_perforce_cmd_resolve] )) || _perforce_cmd_resolve() { _arguments -s : \ '-a-[select automatic merge type]:automation type:((f\:force\ acceptance m\:skip\ conflicts s\:safe t\:use\ theirs y\:use\ yours))' \ '-d-[select diff option]:diff option:((b\:ignore\ blanks w\:ignore\ all\ whitespace))' \ '-f[force re-resolution]' \ '-n[no action, just list]' \ '-t[force textual merge on binary files]' \ '-v[verbose, mark all changes]' \ '*::file:_perforce_files -to' } (( $+functions[_perforce_cmd_resolved] )) || _perforce_cmd_resolved() { _perforce_files -tr } (( $+functions[_perforce_cmd_revert] )) || _perforce_cmd_revert() { _arguments -s : \ '-a[revert unaltered files]' \ '-c[limit reversions to change]:change:_perforce_changes -tp' \ '-n[no action, show effect only]' \ '*::file:_perforce_files -to' } (( $+functions[_perforce_cmd_review] )) || _perforce_cmd_review() { _arguments -s : \ '-c[select change for counter]:change:_perforce_changes -ts' \ '-t[limit change number by counter]:counter:_perforce_counters' } (( $+functions[_perforce_cmd_reviews] )) || _perforce_cmd_reviews() { _arguments -s : \ '-c[show users by change]:change:_perforce_changes -ts' \ '*::file:_perforce_files' } (( $+functions[_perforce_cmd_set] )) || _perforce_cmd_set() { # Only works under Windoze but maybe we are on Cygwin. _arguments -s : \ '-s[set for whole system]' \ '-S[set for specified service]:service: ' \ "*::environment variable:_perforce_variables" } (( $+functions[_perforce_cmd_submit] )) || _perforce_cmd_submit() { _arguments -s : \ '-r[files open for add or edit remain open]' \ '-s[include fix status in list]' \ '(-s -i)-c[submit specific change]:change:_perforce_changes -tp' \ '(-c)-i[read change spec from stdin]' \ '*::file:_perforce_files -to -tr' \ } (( $+functions[_perforce_cmd_sync] )) || _perforce_cmd_sync() { _arguments -s : \ '-f[force resynchronisation]' \ '-n[show operations but don'\''t perform them]' \ '*::file:_perforce_files -tR' } (( $+functions[_perforce_cmd_triggers] )) || _perforce_cmd_triggers() { _arguments -s : \ '-o[output form to stdout]' \ '-i[read from from stdin]' } (( $+functions[_perforce_cmd_typemap] )) || _perforce_cmd_typemap() { _arguments -s : \ '-o[output table to stdout]' \ '-i[read table from stdin]' } (( $+functions[_perforce_cmd_unlock] )) || _perforce_cmd_unlock() { _arguments -s : \ '-c[non-default change to unlock]:change:_perforce_changes -tp' \ '-f[allow superuser to unlock any file]' \ '*::file:_perforce_files' } (( $+functions[_perforce_cmd_user] )) || _perforce_cmd_user() { _arguments -s : \ '(-o)-f[force edit by superuser]' \ '(-o -i)-d[delete user]' \ '(-o -d)-i[read form from stdin]' \ '(-f -i -d)-o[write form to stdout]' \ '(-i)1::username:_users' } (( $+functions[_perforce_cmd_users] )) || _perforce_cmd_users() { _arguments -s : \ '*::username:_users' } (( $+functions[_perforce_cmd_verify] )) || _perforce_cmd_verify() { _arguments -s : \ '-q[operate quietly]' \ '-u[compute and save digest if missing]' \ '-v[compute and save all digets]' \ '*::file:_perforce_files -tR' } (( $+functions[_perforce_cmd_where] )) || _perforce_cmd_where() { _perforce_files } _perforce "$@"