diff options
-rw-r--r-- | ChangeLog | 6 | ||||
-rw-r--r-- | Completion/Unix/Command/_darcs | 508 |
2 files changed, 507 insertions, 7 deletions
diff --git a/ChangeLog b/ChangeLog index 208599166..fca1bb845 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2005-04-06 Peter Stephenson <pws@csr.com> + + * Carlos Phillips <carlos.phillips@mail.mcgill.ca>: + private email: Completion/Unix/Command/_darcs: much improved + completion for darcs. + 2005-04-05 Wayne Davison <wayned@users.sourceforge.net> * unposted: Completion/Unix/Command/_rsync: Made --address diff --git a/Completion/Unix/Command/_darcs b/Completion/Unix/Command/_darcs index 7aa57643b..fe5c9842f 100644 --- a/Completion/Unix/Command/_darcs +++ b/Completion/Unix/Command/_darcs @@ -1,11 +1,505 @@ #compdef darcs -if (($CURRENT == 2)); then - # We're completing the first word after "darcs" -- the command. - _wanted command expl 'darcs command' \ - compadd -- $( darcs --commands ) +# EXTENDED_GLOB is required fr pattern backreferences. +setopt EXTENDED_GLOB + +local DARCS=$words[1] + +# test whether to hide short options from completion +autoload is-at-least +local hide_short +if zstyle -s ":completion:${curcontext}" hide-shortopts hide_short; then + case $hide_short in + true|yes|on|1) hide_short='!' ;; + *) hide_short='' ;; + esac else - # Find the options/files/URL/etc. for the current command by using darcs. - _wanted args expl 'arg for darcs command' \ - compadd -- $( darcs ${words[2]} --list-option ) + is-at-least 4.1 || hide_short='!' fi + + + +_darcs_main() { +# Based on section 6.8 of _A User's Guide to the Z-Shell_ by Peter Stephenson. +# Also based on the tla completion module by Jason McCarty. How do I credit +# this? +local DARCS=$words[1] +local arguments +local curcontext="$curcontext" + +if (( CURRENT > 2 )); then + local cmd=${words[2]} + local var_cmd=cmd_${cmd//-/_} + curcontext="${curcontext%:*:*}:darcs-${cmd}:" + (( CURRENT-- )) + shift words + + local short long arg desc action + short=() + long=() + arg=() + desc=() + action=() + arguments=() + + # Collect all help lines which have a leading space. + local input + input=(${(f)"$($DARCS $cmd -h)"}) + input=(${input:#[^\ ]*}) + local i + for (( i=1 ; i <= ${#input} ; i++ )); do + # Assumption: the the argument descriptions from `darcs cmd -h` + # have the following format: + # [spaces]<-f>[spaces][--flag]<=<spaces>argument>[spaces][description] + [[ "$input[i]" = (#b)' '#(-?|)' '#([^' ']#|)' '#(--[^=' ']#)(=([^' ']#)|)' '#(*) ]] \ + || _message -e messages "cannot parse command help output." || return 1 + + short[i]="$match[1]" + long[i]="$match[3]" + arg[i]="$match[5]" + desc[i]="$match[6]" + desc[i]="${${desc[i]//\[/\\[}//\]/\\]}" # escape brackets + + case $arg[i] in + DIRECTORY) + action[i]='_files -/' ;; + FILE|FILENAME|IDFILE|KEYS) + action[i]='_files' ;; + USERNAME) + action[i]='_users' ;; + EMAIL|FROM) + action[i]='_email_addresses' ;; + *) + action[i]='' ;; + esac + done + + # Compute the exludes for _arguments + + local excluded short_excluded long_excluded k + + for (( i=1 ; i <= ${#input} ; i++ )); do + excluded=() + for opt (${=excludes[$long[i]]}); do + k=$long[(i)$opt] + excluded=($excluded $short[k] $long[k]) + done + + # Generate arguments for _arguments. + # Make long and short options mutually exclusive. + short_excluded=($long[i] $excluded) + long_excluded=($short[i] $excluded) + [ $short[i] ] && arguments=("$arguments[@]" + "${hide_short}(${short_excluded})${short[i]}[${desc[i]}]${arg[i]:+:$arg[i]:$action[i]}") + [ $long[i] ] && arguments=("$arguments[@]" + "(${long_excluded})${long[i]}${arg[i]:+=}[${desc[i]}]${arg[i]:+:$arg[i]:$action[i]}") + done + + arguments=("$arguments[@]" "${(@P)var_cmd-*:FILE:_files}") +else + local hline + local -a cmdlist + _call_program help-commands darcs --help | while read -A hline; do + (( ${#hline} < 2 )) && continue + [[ $hline[1] == darcs ]] && continue + [[ $hline[1] == Usage: ]] && continue + [[ $hline[1] == Use ]] && continue + cmdlist=( $cmdlist "${hline[1]}:${hline[2,-1]}" ) + done + arguments=(':commands:(($cmdlist))') +fi + +_arguments -S -A '-*' \ + "$arguments[@]" +} + + + +# +# Command argument definitions +# +local -a cmd_initialize cmd_get +cmd_initialize=() +cmd_get=(':repository:_files -/' ':new repository name:_files -/') + +local -a cmd_add cmd_remove cmd_move cmd_replace +cmd_add=('*:new files:_darcs_new_file_or_tree') +cmd_remove=('*:existing controlled files:_darcs_controlled_files -e') +cmd_move=('*:existing controlled files:_darcs_controlled_files -e') +cmd_replace=(':old token:' ':new token:' '*:existing controlled files:_darcs_controlled_files -e') + +local -a cmd_record cmd_pull cmd_push cmd_send cmd_apply +cmd_record=('*:controlled files:_darcs_controlled_files') +cmd_pull=(':repository:_darcs_repository_or_tree') +cmd_push=(':repository:_darcs_repository_or_tree') +cmd_send=(':repository:_darcs_repository_or_tree') +cmd_apply=(':patch file:_files') + +local -a cmd_whatsnew cmd_changes +cmd_whatsnew=('*:controlled files:_darcs_controlled_files') +cmd_changes=('*:controlled files:_darcs_controlled_files') + +local -a cmd_tag cmd_setpref cmd_check cmd_optimize +cmd_tag=() +cmd_setpref=(':preference key:(test predist boringfile binaries)' ':value:_files') +cmd_check=() +cmd_optimize=() + +local -a cmd_amend_record cmd_rollback cmd_unrecord cmd_unpull cmd_revert cmd_unrevert +cmd_amend_record=('*:controlled files:_darcs_controlled_files') +cmd_rollback=() +cmd_unrecord=() +cmd_unpull=() +cmd_revert=('*:controlled files:_darcs_controlled_files') +cmd_unrevert=() + +local -a cmd_diff cmd_annotate +cmd_diff=('*:controlled files:_darcs_controlled_files') +cmd_annotate=('*:controlled files:_darcs_controlled_files') + +local -a cmd_resolve cmd_dist cmd_trackdown cmd_repair +cmd_resolve=() +cmd_dist=() +cmd_trackdown=(':initialization:' ':command:') +cmd_repair=() + + +# +# Completion functions +# + +(( $+functions[_darcs_new_files] )) || +_darcs_new_files () { + local -a new_files + local -a local_files + local in_tree_head in_tree_tail + _darcs_make_tree_path in_tree_head in_tree_tail || return 1 + new_files=(${(f)"$(cd $(_darcs_absolute_tree_root)/$in_tree_head; $DARCS whatsnew -sl .)"}) + new_files=(${${new_files:#[^a]*}//a /}) + + local_files=() + for file ($new_files); do + [[ $file:h = $in_tree_head && $file:t = ${in_tree_tail}* ]] && local_files+=$file:t + done + + compset -P '*/' + _description new_files expl "new files" + compadd "$expl[@]" "$local_files[@]" +} + + + + +# _darcs_controlled_files [-e|r] [-f|d] +# +# Adds controlled files to the completion. Can take either +# -e or -r as flags which respectively only add the existing +# files or the deleted files. Can take either -f or -d which +# respectively add only the files or directories. +(( $+functions[_darcs_controlled_files] )) || +_darcs_controlled_files() { + local abs_root=$(_darcs_absolute_tree_root) + local only_removed only_existing only_files only_dirs + zparseopts -E \ + 'r=only_removed' 'e=only_existing' \ + 'f=only_files' 'd=only_dirs' + + local in_tree_head in_tree_tail + _darcs_make_tree_path in_tree_head in_tree_tail + local recorded_dir="$abs_root/_darcs/current/$in_tree_head" + local -a controlled_files controlled_dirs existing_files existing_dirs + local -a dir_display_strs removed_dir_display_strs + controlled_files=${(z)$(print $recorded_dir/$in_tree_tail*(.:t))} + controlled_dirs=${(z)$(print $recorded_dir/$in_tree_tail*(/:t))} + existing_files=() existing_dirs=() + removed_files=() removed_dirs=() + dir_display_strs=() removed_dir_display_strs=() + local dir file + for dir ($controlled_dirs); do + if [[ -e $abs_root/$in_tree_head/$dir ]]; then + existing_dirs+="$dir" + dir_display_strs+="$dir/" + else + removed_dirs+="$dir" + removed_dir_display_strs+="$dir/" + fi + done + for file ($controlled_files); do + if [[ -e $abs_root/$in_tree_head/$file ]]; then + existing_files+="$file" + else + removed_files+="$file" + fi + done + + compset -P '*/' + if (( ! ${#only_removed} )); then + _description controlled_files expl "existing revision controlled files" + (( ! ${#only_dirs} )) && compadd "$expl[@]" $existing_files + (( ! ${#only_files} )) \ + && compadd "$expl[@]" -q -S / -d dir_display_strs -a -- existing_dirs + fi + if (( ! ${#only_existing} )); then + _description removed_files expl "removed revision controlled files" + (( ! ${#only_dirs} )) && compadd "$expl[@]" $removed_files + (( ! ${#only_files} )) \ + && compadd "$expl[@]" -q -S / -d removed_dir_display_strs -a -- removed_dirs + fi +} + +(( $+functions[_darcs_repositories] )) || +_darcs_repositories() { + local local_repos_path="$(_darcs_absolute_tree_root)/_darcs/prefs/repos" + local global_repos_path="$HOME/.darcs/repos" + local -a local_repos global_repos + local -a global_repos + [[ -e $local_repos_path ]] && cat $local_repos_path | read -A local_repos + [[ -e $global_repos_path ]] && cat $global_repos_path | read -A global_repos + local_repos=${local_repos:# #} + global_repos=${global_repos:# #} + _description repositories expl "repositories" + (( ${#local_repos} )) && compadd "$expl[@]" -- "$local_repos[@]" + (( ${#global_repos} )) && compadd "$expl[@]" -- "$global_repos[@]" +} + + + +# Combination completion functions + +(( $+functions[_darcs_new_file_or_tree] )) || +_darcs_new_file_or_tree() { + local base_dir=$( cd ${$(_darcs_repodir):-.}; pwd -P) + [[ -z $(_darcs_absolute_tree_root $base_dir) ]] && return 1 + local -a ignored_files + ignored_files=(_darcs) + _alternative 'newfiles:new file:_darcs_new_files' \ + "directories:tree:_path_files -/ -W$base_dir -Fignored_files" +} + +(( $+functions[_darcs_repository_or_tree] )) || +_darcs_repository_or_tree() { + local -a ignored_files + ignored_files=(_darcs) + _alternative 'repository:repository:_darcs_repositories' \ + "directories:directories:_path_files -/ -Fignored_files" +} + + +# +# Mutually exclusive options +# + +typeset -A excludes +excludes=( +# Output + --summary '--no-summary' + --no-summary '--summary' + --context ' --xml-output --human-readable --unified' + --xml-output '--context --human-readable --unified' + --human-readable '--context --xml-output --unified' + --unified '--context --xml-output --human-readable ' + +# Verbosity + --verbose ' --quiet --standard-verbosity' + --quiet '--verbose --standard-verbosity' + --standard-verbosity '--verbose --quiet ' + +# Traversal + --recursive '--not-recursive' + --not-recursive '--recursive' + --look-for-adds '--dont-look-for-adds' + --dont-look-for-adds '--look-for-adds' + +# Pattern + --from-match ' --from-patch --from-tag' + --from-patch '--from-match --from-tag' + --from-tag '--from-patch --from-match ' + --to-match ' --to-patch -to-tag' + --to-patch '--to-match -to-tag' + --to-tag '--to-match --to-patch ' + +# Repository Properties + --plain-pristine-tree '--no-pristine-tree' + --no-pristine-tree '--plain-pristine-tree' + --parial '--complete' + --complete '--partial' + --compress '--dont-compress' + --dont-compress '--compress' + --set-default '--no-set-default' + --no-set-default '--set-default' + +# Logs + --edit-long-comment '--skip-long-comment --leave-test-directory' + --skip-long-comment '--edit-long-comment --leave-test-directory' + --prompt-long-comment '--edit-long-comment --skip-long-comment' + +# Security + --sign ' --sign-as --sign-ssl --dont-sign' + --sign-as '--sign --sign-ssl --dont-sign' + --sign-ssl '--sign --sign-as --dont-sign' + --dont-sign '--sign --sign-as --sign-ssl ' + --verify ' --verify-ssl --no-verify' + --verify-ssl '--verify --no-verify' + --no-verify '--verify --verify-ssl ' + --apply-as '--apply-as-myself' + --apply-as-myself '--apply-as' + +# Conflicts + --mark-conflicts '--allow-conflicts --no-resolve-conflicts --dont-allow-conflicts' + --allow-conflicts '--mark-conflicts --no-resolve-conflicts --dont-allow-conflicts' + --no-resolve-conflicts '--mark-conflicts --allow-conflicts --dont-allow-conflicts' + --dont-allow-conflicts '--mark-conflicts --allow-conflicts --no-resolve-conflicts ' + +# Test + --test '--no-test' + --no-test '--test' + --leave-test-directory '--remove-test-directory' + --remove-test-directory '--leave-test-directory' + +# Misc + --force '--no-force' + --no-force '--force' + --ask-deps '--no-ask-deps' + --no-ask-deps '--ask-deps' + --date-trick '--no-date-trick' + --no-date-trick '--date-trick' + --set-scripts-executable '--dont-set-scripts-executable' + --dont-set-scripts-executable '--set-scripts-executable' +) + + + +# +# Utility functions +# + +# _darcs_make_tree_path in_tree_head_name in_tree_tail_name path +# Set in_tree_head_name in_tree_tail_name to the corresponding path +# parts from inside the current darcs tree. +_darcs_make_tree_path () { + [[ -z $3 || $3 = '.' ]] && 3=${PREFIX:-./} + local _in_tree=$(_darcs_path_from_root ${$(_darcs_repodir):-.}/$3) + [[ -z $_in_tree ]] && return 1 + 4='' 5='' + if [[ ${3[-1]} = / ]]; then + 4=$_in_tree + else + 4=$_in_tree:h + [[ $_in_tree:t != . ]] && 5=$_in_tree:t + fi + set -A "$1" "$4" + set -A "$2" "$5" +} + +_darcs_repodir () { + local index=$words[(i)--repodir*] + if (( index < CURRENT )); then + if [[ $words[$index] = --repodir ]]; then + (( index++ )) + print $words[$index] + else + print ${words[$index]#*=} + fi + fi +} + +_darcs_absolute_tree_root () { + local root=$(_darcs_repodir) + [[ -z $root ]] && root=$(pwd -P) + while [[ ! $root -ef / ]]; do + [[ -d $root/_darcs ]] && break + root="$root/.." + done + [[ $root -ef / ]] || print $(cd $root; pwd -P) +} + +_darcs_tree_root () { + local abs=$(_darcs_absolute_tree_root) + local rel=$(_darcs_relative_path $abs $(pwd -P)) + [[ -n $abs ]] && print $rel +} + +# _darcs_paths_from_root name paths +# Sets name to the paths relative to the darcs tree root. +# If no argument is given then the current directory +# is assumed. +_darcs_paths_from_root () { + local name=$1 + abs=$(_darcs_absolute_tree_root) + [[ -z $abs ]] && set -A "$name" && return 1 + shift + 1=${1:=$PWD} + local -a subpaths + _darcs_filter_for_subpaths subpaths $abs $* + local i + for (( i=1; i<=${#subpaths}; i++ )); do + [[ $subpaths[$i] != '.' ]] && subpaths[$i]="./$subpaths[$i]" + done + set -A "$name" "$subpaths[@]" +} + +_darcs_path_from_root() { + local path + _darcs_paths_from_root path $1 + [[ -n $path ]] && print "$path" +} + +# _darcs_filter_for_in_dir name dir paths +# Sets name to the relative paths from dir to the given paths which +# traverse dir. It ignores paths which are not in dir. +_darcs_filter_for_subpaths () { + local name=$1 dir=$2 + shift 2 + local p rel + local -a accepted_paths + accepted_paths=() + for p; do + rel=$(_darcs_path_difference $p $dir) + [[ -n $rel ]] && accepted_paths+="$rel" + done + set -A "$name" "$accepted_paths[@]" +} + +# _darcs_path_difference p1 p2 +# Print the path from p2 to p1. If p2 is not an ancestor of p1 then it +# prints a blank string. If they point to the same directory then it returns +# a single period. p2 needs to be a directory path. +_darcs_path_difference () { + local diff=$(_darcs_relative_path $1 $2) + [[ ${diff%%/*} != .. ]] && print $diff || return 1 +} + + +# Print the a relative path from the second directory to the first, +# defaulting the second directory to $PWD if none is specified. +# Taken from the zsh mailing list. +_darcs_relative_path () { + 2=${2:=$PWD} + [[ -d $2 && -d $1:h ]] || return 1 + [[ ! -d $1 ]] && 3=$1:t 1=$1:h + 1=$(cd $1; pwd -P) + 2=$(cd $2; pwd -P) + [[ $1 -ef $2 ]] && print ${3:-.} && return + + local -a cur abs + cur=(${(s:/:)2}) # Split 'current' directory into cur + abs=(${(s:/:)1} $3) # Split target directory into abs + + # Compute the length of the common prefix, or discover a subdiretory: + integer i=1 + while [[ i -le $#abs && $abs[i] == $cur[i] ]] + do + ((++i > $#cur)) && print ${(j:/:)abs[i,-1]} && return + done + + 2=${(j:/:)cur[i,-1]/*/..} # Up to the common prefix directory and + 1=${(j:/:)abs[i,-1]} # down to the target directory or file + + print $2${1:+/$1} +} + +# Code to make sure _darcs is run when we load it +_darcs_main "$@" + + + |