mirror of
				git://git.code.sf.net/p/zsh/code
				synced 2025-10-31 06:00:54 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			272 lines
		
	
	
	
		
			8.8 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			272 lines
		
	
	
	
		
			8.8 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
| #helper
 | |
| 
 | |
| # Utility function for in-path completion.
 | |
| # First argument should be an complist-option (e.g. -f, -/, -g). The other
 | |
| # arguments should be glob patterns, one per argument.
 | |
| #
 | |
| # E.g.: __path_files -g '*.tex' '*.texi'
 | |
| #
 | |
| # This is intended as a replacement for `complist -f', `complist -/', and
 | |
| # `complist -g ...' (but don't use it with other options).
 | |
| #
 | |
| # You may also give the `-W <spec>' option as with `compctl' and `complist',
 | |
| # but only as the first argument.
 | |
| #
 | |
| # This function also accepts an optional `-F <string>' option as its first
 | |
| # argument or just after the `-W <spec>'. This can be used to define file
 | |
| # name extension (a la `fignore'). Files with such an extension will not
 | |
| # be considered possible completions.
 | |
| #
 | |
| # This function behaves as if you have a matcher definition like:
 | |
| #   compctl -M 'r:|[-.,_/]=* r:|=* m:{a-z}={A-Z} m:-=_ m:.=,' \
 | |
| #              'm:{a-z}={A-Z} l:|=* r:|=*'
 | |
| # so you may want to modify this.
 | |
| 
 | |
| local nm prepaths str linepath realpath donepath patstr prepath testpath rest
 | |
| local tmp1 collect tmp2 suffixes i ignore
 | |
| 
 | |
| setopt localoptions nullglob rcexpandparam globdots extendedglob
 | |
| unsetopt markdirs globsubst shwordsplit nounset
 | |
| 
 | |
| # Get the optional `-W' option and its argument.
 | |
| if [[ "$1" = -W ]]; then
 | |
|   tmp1="$2"
 | |
|   if [[ "$tmp1[1]" = '(' ]]; then
 | |
|     prepaths=( $tmp1[2,-2]/ )
 | |
|   else
 | |
|     prepaths=( ${(P)${tmp1}} )
 | |
|     [[ $#prepaths -eq 0 ]] && prepaths=( $tmp1/ )
 | |
|   fi
 | |
|   [[ $#prepaths -eq 0 ]] && prepaths=( '' )
 | |
|   shift 2
 | |
| else
 | |
|   prepaths=( '' )
 | |
| fi
 | |
| 
 | |
| # Get the optional `-F' option and its argument.
 | |
| if [[ "$1" = -F ]]; then
 | |
|   ignore=(-F "$2")
 | |
|   shift 2
 | |
| else
 | |
|   ignore=''
 | |
| fi
 | |
| 
 | |
| # str holds the whole string from the command line with a `*' between
 | |
| # the prefix and the suffix.
 | |
| 
 | |
| str="${PREFIX:q}*${SUFFIX:q}"
 | |
| 
 | |
| # We will first try normal completion called with `complist', but only if we
 | |
| # weren't given a `-F' option.
 | |
| 
 | |
| if [[ -z "$ignore" ]]; then
 | |
|   # First build an array containing the `-W' option, if there is any and we
 | |
|   # want to use it. We don't want to use it if the string from the command line
 | |
|   # is a absolute path or relative to the current directory.
 | |
| 
 | |
|   if [[ -z "$tmp1[1]" || "$str[1]" = [~/] || "$str" = (.|..)/* ]]; then
 | |
|     tmp1=()
 | |
|   else
 | |
|     tmp1=(-W "( $ppres )")
 | |
|   fi
 | |
| 
 | |
|   # Now call complist.
 | |
| 
 | |
|   nm=$NMATCHES
 | |
|   if [[ $# -eq 0 ]]; then
 | |
|     complist "$tmp1[@]" -f
 | |
|   elif [[ "$1" = -g ]]; then
 | |
|     complist "$tmp1[@]" -g "$argv[2,-1]"
 | |
|     shift
 | |
|   else
 | |
|     complist "$tmp1[@]" $1
 | |
|     shift
 | |
|   fi
 | |
| 
 | |
|   # If this generated any matches, we don't wnat to do in-path completion.
 | |
| 
 | |
|   [[ -nmatches nm ]] || return
 | |
| 
 | |
|   # No `-F' option, so we want to use `fignore'.
 | |
| 
 | |
|   ignore=(-F fignore)
 | |
| fi
 | |
| 
 | |
| # If we weren't given any file patterns as arguments, we trick ourselves
 | |
| # into believing that we were given the pattern `*'. This is just to simplify
 | |
| # the following code.
 | |
| 
 | |
| [[ -z "$1" ]] && 1='*'
 | |
| 
 | |
| # Now let's have a closer look at the string to complete.
 | |
| 
 | |
| if [[ "$str[1]" = \~ ]]; then
 | |
|   # It begins with `~', so remember anything before the first slash to be able
 | |
|   # to report it to the completion code. Also get an expanded version of it
 | |
|   # (in `realpath'), so that we can generate the matches. Then remove that
 | |
|   # prefix from the string to complete, set `donepath' to build the correct
 | |
|   # paths and make sure that the loop below is run only once with an empty
 | |
|   # prefix path by setting `prepaths'.
 | |
|   
 | |
|   linepath="${str%%/*}/"
 | |
|   eval realpath\=path
 | |
|   str="${str#*/}"
 | |
|   donepath=''
 | |
|   prepaths=( '' )
 | |
| else
 | |
|   # If the string does not start with a `~' we don't remove a prefix from the
 | |
|   # string.
 | |
| 
 | |
|   liniepath=''
 | |
|   realpath=''
 | |
| 
 | |
|   if [[ "$str[1]" = / ]]; then
 | |
|     # If it is a absolut path name, we remove the first slash and put it in
 | |
|     # `donepath' meaning that we treat it as the path that was already handled.
 | |
|     # Also, we don't use the paths from `-W'.
 | |
| 
 | |
|     str="$str[2,-1]"
 | |
|     donepath='/'
 | |
|     prepaths=( '' )
 | |
|   else
 | |
|     # The common case, we just use the string as it is, unless it begins with
 | |
|     # `./' or `../' in which case we don't use the paths from `-W'.
 | |
|     
 | |
|     [[ "$str" = (.|..)/* ]] && prepaths=( '' )
 | |
|     donepath=''
 | |
|   fi
 | |
| fi
 | |
| 
 | |
| # First we skip over all pathname components in `str' which really exist in
 | |
| # the file-system, so that `/usr/lib/l<TAB>' doesn't offer you `lib' and
 | |
| # `lib5'. Pathname components skipped this way are taken from `str' and added
 | |
| # to `donepath'.
 | |
| 
 | |
| while [[ "$str" = */* ]] do
 | |
|   [[ -e "$realpath$donepath${str%%/*}" ]] || break
 | |
|   donepath="$donepath${str%%/*}/"
 | |
|   str="${str#*/}"
 | |
| done
 | |
| 
 | |
| # Now build the glob pattern. As noted above, this function behaves as if
 | |
| # a global matcher with two matching specifications are given.
 | |
| 
 | |
| if [[ -matcher 1 ]]; then
 | |
|   patstr="$str:gs/,/*,/:gs/_/*_/:gs./.*/.:gs/-/*[-_]/:gs/./*[.,]/:gs-*[.,]*[.,]*/-../-:gs.**.*."
 | |
| else
 | |
|   patstr="${str%/*}/*${str##*/}*"
 | |
|   patstr="$patstr:gs./.*/.:gs.**.*."
 | |
| fi
 | |
| 
 | |
| # Finally, generate the matches. First we loop over all the paths from `-W'.
 | |
| # Note that in this loop `str' is used as a modifyable version of `patstr'
 | |
| # and `testpath' is a modifyable version of `donepath'.
 | |
| 
 | |
| for prepath in "$prepaths[@]"; do
 | |
|   str="$patstr"
 | |
|   testpath="$donepath"
 | |
| 
 | |
|   # The second loop tests the components of the path in `str' to get the
 | |
|   # possible matches.
 | |
| 
 | |
|   while [[ "$str" = */* ]] do
 | |
|     # `rest' is the pathname after the first slash that is left. In `tmp1'
 | |
|     # we get the globbing matches for the pathname component currently
 | |
|     # handled.
 | |
| 
 | |
|     rest="${str#*/}"
 | |
|     tmp1="${prepath}${realpath}${testpath}(#l)${str%%/*}(-/)"
 | |
|     tmp1=( $~tmp1 )
 | |
| 
 | |
|     if [[ $#tmp1 -eq 0 ]]; then
 | |
|       # If this didn't produce any matches, we don't need to test this path
 | |
|       # any further, so continue with the next `-W' path, if any.
 | |
| 
 | |
|       continue 2
 | |
|     elif [[ $#tmp1 -gt 1 ]]; then
 | |
|       # If it produced more than one match, we want to remove those which
 | |
|       # don't have possible following pathname components matching the 
 | |
|       # rest of the string we are completing. (The case with only one
 | |
|       # match is handled below.)
 | |
|       # In `collect' we will collect those of the produced pathnames that
 | |
|       # have a matching possible path-suffix. In `suffixes' we build an
 | |
|       # array containing strings build from the rest of the string to 
 | |
|       # complete and the glob patterns we were given as arguments.
 | |
| 
 | |
|       collect=()
 | |
|       suffixes=( $rest$@ )
 | |
|       suffixes=( "${(@)suffixes:gs.**.*.}" )
 | |
| 
 | |
|       # In the loop the prefixes from the `tmp1' array produced above and
 | |
|       # the suffixes we just built are used to produce possible matches
 | |
|       # via globbing.
 | |
| 
 | |
|       for i in $tmp1; do
 | |
|         tmp2=( $~i/(#l)$~suffixes )
 | |
|         [[ $#tmp2 -ne 0 ]] && collect=( $collect $i )
 | |
|       done
 | |
| 
 | |
|       # If this test showed that none of the matches from the glob in `tmp1'
 | |
|       # has a possible sub-path matching what's on the line, we give up and
 | |
|       # continue with the next `-W' path.
 | |
| 
 | |
|       if [[ $#collect -eq 0 ]]; then
 | |
|         continue 2
 | |
|       elif [[ $#collect -ne 1 ]]; then
 | |
|         # If we have more than one possible match, this means that the
 | |
| 	# pathname component currently handled is ambiguous, so we give
 | |
| 	# it to the completion code.
 | |
| 	# First we build the full path prefix in `tmp1'.
 | |
| 
 | |
|         tmp1="$prepath$realpath$testpath"
 | |
| 
 | |
| 	# Now produce all matching pathnames in `collect'.
 | |
| 
 | |
|         collect=( $~collect/(#l)$~suffixes )
 | |
| 
 | |
| 	# And then remove the common path prefix from all these matches.
 | |
| 
 | |
|         collect=( ${collect#$tmp1} )
 | |
| 
 | |
| 	# Finally, we add all these matches with the common (unexpanded)
 | |
| 	# pathprefix (the `-p' option), the path-prefix (the `-W' option)
 | |
| 	# to allow the completion code to test file type, and the path-
 | |
| 	# suffix (the `-s' option). We also tell the completion code that
 | |
| 	# these are file names and that `fignore' should be used as usual
 | |
| 	# (the `-f' and `-F' options).
 | |
| 
 | |
|         for i in $collect; do
 | |
|           compadd -p "$linepath$testpath" -W "$tmp1" -s "/${i#*/}" -f "$ignore[@]" -- "${i%%/*}"
 | |
|         done
 | |
| 
 | |
| 	# We have just finished handling all the matches from above, so we
 | |
| 	# can continue with the next `-W' path.
 | |
| 
 | |
| 	continue 2
 | |
|       fi
 | |
|       # We reach this point if only one of the path prefixes in `tmp1'
 | |
|       # has a existing path-suffix matching the string from the line.
 | |
|       # In this case we accept this match and continue with the next
 | |
|       # path-name component.
 | |
| 
 | |
|       tmp1=( "$collect[1]" )
 | |
|     fi
 | |
|     # This is also reached if the first globbing produced only one match
 | |
|     # in this case we just continue with the next pathname component, too.
 | |
| 
 | |
|     tmp1="$tmp1[1]"
 | |
|     testpath="$testpath${tmp1##*/}/"
 | |
|     str="$rest"
 | |
|   done
 | |
| 
 | |
|   # We are here if all pathname components except the last one (which is still
 | |
|   # not tested) are unambiguous. So we add matches with the full path prefix, 
 | |
|   # no path suffix, the `-W' we are currently handling, all the matches we
 | |
|   # can produce in this directory, if any.
 | |
| 
 | |
|   tmp1="$prepath$realpath$testpath"
 | |
|   suffixes=( $str$@ )
 | |
|   suffixes=( "${(@)suffixes:gs.**.*.}" )
 | |
|   tmp2=( $~tmp1(#l)$~suffixes )
 | |
|   compadd -p "$linepath$testpath" -W "$prepath$realpath$testpath" -f "$ignore[@]" -- ${tmp2#$tmp1}
 | |
| done
 |