From ac62ad2b1fc73ef912ddc32b3cc81ace8d00c7f6 Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Mon, 3 Feb 2003 13:52:29 +0000 Subject: 18175: Completion for Perforce --- Completion/Unix/Command/_p4 | 1560 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1560 insertions(+) create mode 100644 Completion/Unix/Command/_p4 (limited to 'Completion/Unix/Command') diff --git a/Completion/Unix/Command/_p4 b/Completion/Unix/Command/_p4 new file mode 100644 index 000000000..43a776184 --- /dev/null +++ b/Completion/Unix/Command/_p4 @@ -0,0 +1,1560 @@ +#compdef p4 -value-,P4CLIENT,-default- -value-,P4PORT,-default- + +# Increasingly loosely based on _cvs version 1.17. + +# Styles, tags and contexts +# ========================= +# +# If the `verbose' style is set (it is assumed by default), verbose +# descriptions are provided for many completed quantities derived +# dynamically such as subcommand names, labels, changelists -- in fact, +# just about anything for which Perforce itself produces a verbose, +# one-line description. It may be turned off in the context of each +# subcommand e.g. +# zstyle ':completion:*:p4-labelsync:*' verbose false +# or for a particular tag, e.g. changelists, +# zstyle ':completeion:*:changelists' verbose false +# or just for top-level completion (i.e. up to and including completion +# of the subcommand): +# zstyle ':completion:*:p4:*' verbose false +# or for p4 as a whole, +# zstyle ':completion:*:p4(-*|):*' verbose false +# This is actually handled by the `_describe' function underneath the +# Perforce completion system; it's mentioned here as verbosity adds +# significantly to a lot of the Perforce completions. +# +# Note that completing changelists (which are just numbers) is not very +# useful if `verbose' is turned off. There is no speed advantage for +# turning it off, either. +# +# The style `max' can be set to a number which limits how many +# possibilities can be shown when selecting changelists or jobs. This is +# handled within Perforce, so the completion code may limit the number even +# further. If not set explicitly, the value is taken to be 20 to avoid a +# huge database being output. Set it to a larger number if necessary. +# +# The style `all-files' is used to tell the completion system to +# complete any file in a given context. This is for use in places +# where it would, for example, only complete files opened for editing. +# See the next section for more. +# +# The style `depot-files' tells the system to complete files by asking +# Perforce for a list where it would otherwise complete files locally by +# the standard mechanism --- basically any time you don't use // notation +# and there is no restriction e.g. to opened files only. There is likely +# to be a significant speed penalty for this; it is turned off by default +# in all contexts. The advantage is that it cuts out files not maintained +# by Perforce. (Again, note this is a style, not a tag.) Contexts +# where this might be particularly useful include p4-diff or p4-diff2. +# +# The tags depot-files and depot-dirs also exist; they are used whenever +# the system is completing files or directories by asking Perforce +# to list them, rather than by using normal file completion. +# +# The tag subdirs is used to complete the special `...' which tells +# Perforce to search all subdirectories. Hence you can turn this +# feature off by suitably manipulating your tags. +# +# Completion of files and their revisions +# ======================================= +# +# File completion handles @ and # suffixes. If the filename is completed, +# typing @ or # removes the space which was automatically added. +# +# A # is automatically quoted when handled in this way; if the file is +# typed by hand or the completion didn't finish (e.g. you typed a character +# in the middle of menu completion), you probably need to type `\#' by +# hand. The problem is that the completion system uses extended globbing +# and hence a pattern of the form `filename#' always matches `filename' +# (since e# matches any number of e's including one). Hence this can look +# like an expansion which expands to `filename'. +# +# After @, you can complete changelists (note the use of the style `max' +# above), labels or clients (but not dates), while after `#' you can +# complete numeric revisions or the special revision names head, none, +# have. These are available whether or not you completed the filename; if +# the file doesn't exist, numeric revisions won't work, but the rest will +# (though what Perforce will do with the resulting command is another matter). +# +# In addition, when completing after `file@', only changes specific to `file' +# will be shown (exactly the list of changes Perforce shows from the +# command `p4 changes file'). If this doesn't work, chances are that +# `file' does not exist. Having a multi-directory match (literal `...') +# in `file' should work fine, since `p4 changes' recognises all normal +# Perforce file syntax. +# +# Some perforce commands allow you to specify a range of revisions or +# changes as `file@1,@2' or `file#1,#2'. Currently, the second part of the +# revision range can always be completed (whether the command accepts them +# or not), but the comma after the first part of the range is only added +# automatically if the documentation suggests the command accepts ranges at +# that point. This is an auto-removable suffix, so it will disappear if +# you hit space or return. Typing a `#' at this point will insert a +# backslash, as before. +# +# File completion for some functions is restricted by the Perforce +# status of the file; for example, `p4 opened' only completes opened +# files (surprised?) However, you can set the style (N.B. not tag) +# all-files; so, for example, you can turn off the limit in this case by +# zstyle ':completion:*:p4-opened:*' all-files true +# Normally the file-patterns style would be used to control matching, +# but as the file types are not selected by globbing it doesn't work here +# However, if you set the all-files style, all filename completion is done +# by the standard mechanism; in this case, the file-patterns style works +# as usual. The style ignored-patterns is available in any case, even +# without all-files; this is therefore generally the one to use. +# +# With `p4 diff', the shell will spot if you have used an option that +# allows you to diff unopened files (such as -f) and in that case offer +# all files; otherwise, it just offers opened files. +# +# Completion of changes +# ===================== +# +# There is various extra magic available any time change numbers +# are completed, regardless of how this was reached, i.e. +# `p4 fixes -c ...' and `p4 diff filename.c@...' are treated the same way. +# Note, however, these only work if you are at the point where a change +# number would be completed. +# +# Firstly, as mentioned above there is a maximum for the number of +# changes which will be shown, given by the style max, or defaulting to 20. +# Only the most recent changes will be shown. This is to avoid a speed +# penalty or clumsy output. If a positive numeric argument is given +# when changes are being completed, the maximum is set (unconditionally) +# to that number instead. +# +# It is also possible to give a negative numeric prefix to a listing widget +# (i.e. typically whatever is bound to ^D). If there is already a change +# number on the line, e.g. from cycling through a menu of choices, the full +# description for that change is shown in the format of a completion +# listing. [TODO: this could be made configurable with a style.] +# +# It may be necessary to abandon the current completion attempt before +# typing this to force the completion system to display the new text. +# Replacing delete-char-or-list with the following user defined widget +# (create with `zle -N ...') will force this for any negative prefix argument. +# (( ${NUMERIC:-0} < 0 )) && (( CURSOR = CURSOR )) +# zle delete-char-or-list +# +# Completion of jobs +# ================= +# +# Completing jobs uses the same logic for the numeric prefix as completing +# changes: a positive prefix changes the maximum number of jobs which +# will be shown, and a negative prefix when listing shows the full +# text for the job whose name is currently inserted on the command line. +# In this case, the entire text of the word being completed is assumed +# to constitute the job name (which is almost certainly correct). +# +# Calls to p4 +# =========== +# +# Much of the information from Perforce is provided by calls to p4 +# commands. This is done via the _call_program interface, as described +# in the zshcompletesys manual page. Hence a suitable context with the +# `command' style allows the user to take control of this call. +# The tags used are the name of the p4 command, or in the case of +# calls to help subcommands, `help-'. Note that if the +# value of the style begins with `-', the arguments to the perforce +# command are appended to the remaining words of the style before calling +# the command. +# +# TODO +# ==== +# +# No mechanism is provided for completely ignoring certain files not +# handled by Perforce as with .cvsignore. This could be done ad hoc. +# However, the ignored-patterns style and the parameter $fignore are +# of course applied as usual, so setting ignored-patterns for the +# context `:completion:*:p4[-:]*' should work. + +_p4() { + # rely on localoptions + setopt nonomatch + local p4cmd==p4 + integer i + + if [[ $service = -value-* ]]; then + # Completing parameter value. + # Some of these --- in particular P4PORT --- don't need + # the perforce server. + case $compstate[parameter] in + (P4PORT) _p4_host_port + ;; + (P4CLIENT) _p4_clients + ;; + esac + # We do not handle values anywhere else. + return + fi + + if [[ $p4cmd = '=p4' ]]; then + _message "p4 excutable not found: completion not available" + return + fi + + if (( ! ${#_p4_cmd_list} )); then + (( ${+_p4_cmd_list} )) || typeset -ga _p4_cmd_list + local hline + # Output looks like command-namedescription in words... + # Ignore blank lines and the heading line beginning `Perforce...' + # Just gets run once, then cached, so don't bother optimising + # this to a grossly unreadable parameter substitution. + _call_program help-commands p4 help commands | while read -A hline; do + (( ${#hline} < 2 )) && continue + [[ $hline[1] = (#i)perforce ]] && continue + _p4_cmd_list+=("${hline[1]}:${hline[2,-1]}") + done + fi + + # We need to try and check if we are before or after the + # subcommand, since some of the options with arguments, in particular -c, + # work differently. It didn't work if I just added '*::...' to the + # end of the arguments list, anyway. + for (( i = 2; i < CURRENT; i++ )); do + if [[ $words[i] = -[cCdHLpPux] ]]; then + # word with following argument + (( i++ )) + elif [[ $words[i] != -* ]]; then + break + fi + done + + if (( i >= CURRENT )); then + _arguments -s : \ + '-c+[client]:client:_p4_clients' \ + '-C+[charset]:charset:_p4_charset' \ + '-d+[current directory]:directory:_path_files -g "*(/)"' \ + '-H+[hostname]:host:_hosts' \ + '-G[python output]' \ + '-L+[message language]:language: ' \ + '-p+[server port]:port:_ports' \ + '-P+[password on server]:password: ' \ + '-s[output script tags]' \ + '-u+[user]:user name:_users' \ + '-x+[filename or -]:file:_p4_file_or_Minus' \ + '1:perforce command:_p4_command' + else + (( i-- )) + (( CURRENT -= i )) + shift $i words + _p4_command_arg + fi +} + + +# +# Command and argument dispatchers +# + +(( $+functions[_p4_command] )) || +_p4_command() { + _describe -t p4-commands 'Perforce command' _p4_cmd_list +} + + +(( $+functions[_p4_command_arg] )) || +_p4_command_arg() { + local curcontext="$curcontext" cmd=${words[1]} + if (( $+functions[_p4_cmd_$cmd] )); then + curcontext="${curcontext%:*:*}:p4-${cmd}:" + _p4_cmd_$cmd + else + _message "unhandled perforce command: $cmd" + fi +} + + +# +# Helper functions +# + +(( $+functions[_p4_branches] )) || +_p4_branches() { + local bline match mbegin mend + local -a bl + bl=(${${${(f)"$(_call_program branches p4 branches 2>/dev/null)"}##Branch }/ /:}) + [[ $#bl -eq 1 && $bl[1] = '' ]] && bl=() + (( $#bl )) && _describe -t branches 'Perforce branch' bl +} + + +(( $+functions[_p4_changelist] )) || +_p4_changelist() { + local cline match mbegin mend max ctype num comma file + local -a cl cstatus + + zstyle -s ":completion:${curcontext}:" max max + if [[ ${NUMERIC:-0} -lt 0 && -z $compstate[insert] ]]; then + # Not inserting (i.e. just listing) and given a negative + # prefix argument. Instead of listing possible completions, + # show the full description for the change number on the line at + # the moment. + [[ $PREFIX = (|*[^[:digit:]])(#b)(<->) ]] && num+=$match[1] + [[ $SUFFIX = (#b)(<->)* ]] && num+=$match[1] + if [[ -n $num ]]; then + _message -r "$(_call_program describe p4 describe $num)" + return 0 + fi + elif [[ ${NUMERIC:-0} -gt 0 ]]; then + max=$NUMERIC + fi + + # Hack: assume the arguments we want are at the end. + while [[ $argv[-1] = -t? ]]; do + case $argv[-1] in + # Change embedded in filename; extract that and remove + # the corresponding prefix. Remove possible `#'s, too, + # in case we are looking at a range. + (-tf) file=${${(Q)PREFIX}%%[\#@]*} + compset -P '*@' + ;; + # Changes already submitted + (-ts) cstatus=(-s submitted) + ctype="submitted " + ;; + # Changes still pending + (-tp) + cstatus=(-s pending) + ctype="pending " + ;; + # Range allowed: append comma and supply rules for + # removing and handling subsequent `#'. + (-tR) comma=(-S, -R _p4_file_suffix) + esac + argv=($argv[1,-2]) + done + # Limit to the 20 most recent changes by default to avoid huge + # output. + cl=( +${${${${(f)"$(_call_program changes p4 changes -m ${max:-20} $cstatus \$file)"}##Change\ }//\ on\ /:}/\ by\ /\ } +"default:changelist not yet numbered") + [[ $#cl -eq 1 && $cl[1] = '' ]] && cl=() + _describe -t changelists "${ctype}changelist" cl $comma +} + + +(( $+functions[_p4_charset] )) || +_p4_charset() { + local expl + _wanted charset expl 'character set' \ + compadd eucjp iso8859-1 shiftjis utf8 winansi +} + + +(( $+functions[_p4_clients] )) || +_p4_clients() { + local cline match mbegin mend + local -a slash cl + + # Are we completing a client view in a filespec? + compset -P '//' && slash=(-S/ -q) + + cl=(${${${(f)"$(_call_program clients p4 clients)"}##Client\ }/\ /:}) + [[ $#cl -eq 1 && $cl[1] = '' ]] && cl=() + _describe -t clients 'Perforce client' cl $slash +} + + +(( $+functions[_p4_counters] )) || +_p4_counters() { + local cline match mbegin mend + local -a cl + + cl=(${${${(f)"$(_call_program counters p4 counters)"}/\ /:}/\=/current value}) + [[ $#cl -eq 1 && $cl[1] = '' ]] && cl=() + _describe -t counters 'Perforce counter' cl +} + + +(( $+functions[_p4_counter_value] )) || +_p4_counter_value() { + if [[ -n $words[CURRENT-1] ]]; then + local value="$(_call_program counter p4 counter $words[CURRENT-1] 2>/dev/null)" + if [[ -n $value ]]; then + # No space. This allows stuff like incarg and decarg. + compstate[insert]=1 + _wanted value expl 'counter value' compadd $value + fi + fi +} + + +(( $+functions[_p4_depots] )) || +_p4_depots() { + local dline match mbegin mend max + local -a dl + + dl=(${${${(f)"$(_call_program depots p4 depots)"}##Depot\ }/\ /:}) + [[ $#dl -eq 1 && $dl[1] = '' ]] && dl=() + _describe -t depots 'depot name' dl +} + + +(( $+functions[_p4_file_or_minus] )) || +_p4_file_or_minus() { + _alternative 'minus:minus sign:(-)' 'files:file name:_files' +} + + +(( $+functions[_p4_file_suffx] )) || +_p4_file_suffix() { + # Used with compadd -R to handle @ or # after a file name. + # Differs from compadd -r '...' in that it quotes `#' if typed. + [[ $1 = 1 ]] || return + + if [[ $LBUFFER[-1] = [\ ,] ]]; then + if [[ $KEYS = '#' ]]; then + if [[ $LBUFFER[-1] = , ]]; then + # Range: no suffix removal but add a backslash + LBUFFER+=\\ + else + # Suffix removal with an added backslash + LBUFFER="$LBUFFER[1,-2]\\" + fi + elif [[ $KEYS = (*[^[:print:]]*|[[:blank:]\;\&\|]) || \ + ( $KEYS = @ && $LBUFFER[-1] = ' ' ) ]] ; then + # Normal suffix removal + LBUFFER="$LBUFFER[1,-2]" + fi + fi +} + + +# +# Helper functions for the helper function _p4_files. These files +# are low-level enough that they don't handle tags; this is done +# by the _alternative handler in _p4_files. +# + +(( $+functions[_p4_integrated_files] )) || +_p4_integrated_files() { + local pfx=${(Q)PREFIX} type + local -a files + + compset -P '*/' + files=(${${${(f)"$(_call_program integrated p4 integrated \"\$pfx\*\$\{\(Q\)SUFFIX\}\" 2>/dev/null)"}%\#*}##*/}) + [[ $#files -eq 1 && $files[1] = '' ]] && files=() + compadd "$@" -a files +} + + +(( $+functions[_p4_opened_files] )) || +_p4_opened_files() { + local pfx=${(Q)PREFIX} type + local -a files + + compset -P '*/' + files=(${${${(f)"$(_call_program opened p4 opened \"\$pfx\*\$\{\(Q\)SUFFIX\}\" 2>/dev/null)"}%\#*}##*/}) + [[ $#files -eq 1 && $files[1] = '' ]] && files=() + compadd "$@" -a files +} + + +(( $+functions[_p4_resolved_files] )) || +_p4_resolved_files() { + local pfx=${(Q)PREFIX} type + local -a files + + compset -P '*/' + files=(${${${(f)"$(_call_program resolved p4 resolved \"\$pfx\*\$\{\(Q\)SUFFIX\}\" 2>/dev/null)"}%\#*}##*/}) + [[ $#files -eq 1 && $files[1] = '' ]] && files=() + compadd "$@" -a files +} + +(( $+functions[_p4_subdir_search] )) || +_p4_subdir_search() { + # This has no other function than to offer to add the `...' used + # by Perforce to indicate a recursive search of directories. + # Bit pathetic, really. + compset -P '*/' + compadd "$@" '...' +} + +(( $+functions[_p4_depot_dirs] )) || +_p4_depot_dirs() { + # Normal completion of directories in depots + local pfx=${(Q)PREFIX} expl + local -a files + + compset -P '*/' + files=(${"${(f)$(_call_program dirs p4 dirs \"\$pfx\*\$\{\(Q\)SUFFIX\}\" 2>/dev/null)}"##*/}) + [[ $#files -eq 1 && $files[1] = '' ]] && files=() + compadd "$@" -S / -q -a files +} + +(( $+functions[_p4_depot_files] )) || +_p4_depot_files() { + # Normal completion of files in depots + local pfx=${(Q)PREFIX} expl + local -a files + + compset -P '*/' + files=(${${${(f)"$(_call_program files p4 files \"\$pfx\*\$\{\(Q\)SUFFIX\}\" 2>/dev/null)"}%\#*}##*/}) + [[ $#files -eq 1 && $files[1] = '' ]] && files=() + compadd "$@" -R _p4_file_suffix -a files +} + +(( $+functions[_p4_client_dirs] )) || +_p4_client_dirs() { + # This is a slightly odd addition which isn't often necessary. + # When completing directories in a client specification, Perforce + # doesn't tell you about intermediate directories which are in + # the client, but not in the depot. (Well... sometimes. I've + # had some odd results with this. I suspect there may be a bug + # but I don't really know enough to be sure.) + # + # For example, if my view contains + # //depot/branches/rev1.2/... //pws_client/branches/rev1.2/... + # then `p4 dirs "//pws_client/*"' won't mention the `branches' + # directory because the view actually starts lower down. So + # we add it by hand when necessary. + # + # We don't want to waste time on this, since it's not the usual + # case, so we cache the results where necessary. This means + # recording all the clients that we can later ask about if necessary. + # To flush the cache, `unset _p4_client_list _p4_client_dirs'. + if (( ! ${+_p4_client_list} )); then + # Retrieve the list of clients. + typeset -gA _p4_client_list + local -a tmplist + local tmpelt + tmplist=(${${${(f)"$(_call_program clients p4 clients)"}##Client\ }%%\ *}) + [[ $#tmplist -eq 1 && $tmplist[1] = '' ]] && tmplist=() + for tmpelt in $tmplist; do + _p4_client_list[$tmpelt]=1 + done + fi + + # See if the first path element is a client. Very often it + # will actually be a depot, so we test this as quickly as possible. + local client=${${PREFIX##//}%%/*} + [[ -z ${_p4_client_list[$client]} ]] && return 1 + + local oldifs=$IFS IFS= type dir line dirs + + (( ${+_p4_client_dirs} )) || typeset -gA _p4_client_dirs + + if (( ${+_p4_client_dirs[$client]} )); then + # Already cached, although may be empty. + dirs=${_p4_client_dirs[$client]} + else + # We need to look at the View stanza of the client record + # to see what directories exist in the client view. + _call_program client "p4 client -o $client" 2>/dev/null | while read line; do + case $line in + ([[:blank:]]##) type= + ;; + ((#b)([[:alpha:]]##):*) type=${match[1]} + ;; + (*) if [[ $type = View ]]; then + dir=${${line##[[:blank:]]##//*[[:blank:]]//$client}%%/...(/*|)} + if [[ $#dir -gt 1 ]]; then + dirs+="${dirs:+ }${(q)dir##/}" + fi + fi + ;; + esac + done + fi + + (( ${#dirs} )) || return 1 + + # Turn our string of space-separated backquoted elements into an array. + dirs=(${(z)dirs}) + # Get the current prefix also as an array of elements + compset -P '//[^/]##/' + pfx=(${(s./.)${(Q)PREFIX}}) + + local -a ndirs + local match mbegin mend + # Check matching path segments + while (( ${#pfx} > 1 )); do + ndirs=() + for dir in $dirs; do + if [[ $dir = $pfx/(#b)(*) ]]; then + ndirs+=($match[1]) + fi + done + (( ${#ndirs} )) || return 1 + dirs=($ndirs) + shift pfx + compset -P '[^/]' + done + compadd -S / -q "$@" -- ${dirs%%/*} +} + +(( $+functions[_p4_files] )) || +_p4_files() { + local pfx fline expl opt match mbegin mend range type + local -a files types + + local dodirs unmaintained + + while (( $# )); do + if [[ $1 = -t(#b)(?) ]]; then + case $match[1] in + (d) dodirs=-/ + ;; + (u) unmaintained=1 + ;; + (i) types+=(integrated) + ;; + (o) types+=(opened) + ;; + (r) types+=(resolved) + ;; + (R) range="-tR" + ;; + esac + fi + shift + done + + # Remove the quotes present in the word on the command line, + # since we will treat this as a literal string from now on. + # We might get into problems with characters recognised as + # special by p4 files and p4 dirs, but worry about that later. + pfx=${(Q)PREFIX} + if [[ -prefix *@ ]]; then + # Check for existing range syntax + [[ $PREFIX = *[@\#]*,* ]] && range= + # After @ you can specify changelists, clients, labels or dates. + # Don't try to complete dates. + _alternative \ + "changelist:changelist:_p4_changelist $range -tf" \ + client:client:_p4_clients \ + label:label:_p4_labels + elif [[ -prefix *\# ]]; then + # Check for existing range syntax + [[ $PREFIX = *[@\#]*,* ]] && range= + # Remove longest possible tail match to get name --- this + # automatically handles filenames in ranges e.g. `foo#1,#3'. + # (Note the compset removes the maximum possible head match, + # so we only complete the second part of the range in that case.) + _p4_revisions $range + elif [[ $PREFIX = //* ]]; then + # This specifies files already handled by Perforce, so there's + # no point trying to look for unmaintained files. Assume + # the user knows what they're doing. + local -a altfiles + + if [[ $PREFIX = //[^/]# ]]; then + # Complete //clientname spec. Don't complete non-directories... + # I don't actually know if they are valid here. + altfiles+=("clients:Perforce client:_p4_clients") + else + local donefiles=1 + if [[ -z $dodirs ]]; then + if [[ ${#types} -gt 0 ]] && + ! zstyle -t ":completion:${curcontext}:" all-files; then + for type in $types; do + altfiles+=("$type-files:$type file:_p4_${type}_files") + done + else + altfiles+=("depot-files:file in depot:_p4_depot_files") + fi + fi + # Intermediate directories in a client view. + # See function for notes. + altfiles+=("client-dirs:client directory:_p4_client_dirs") + fi + altfiles+=("depot-dirs:directory in depot:_p4