about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog6
-rw-r--r--Completion/Unix/Command/_darcs508
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 "$@"
+
+
+