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