mirror of
				git://git.code.sf.net/p/zsh/code
				synced 2025-10-31 06:00:54 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			336 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			336 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
| # Description
 | |
| # ===========
 | |
| #
 | |
| # Change to a recently used directory recorded in a file so that the
 | |
| # recent file list persists across sessions.
 | |
| #
 | |
| # To use this system,
 | |
| #
 | |
| #   autoload -Uz chpwd_recent_dirs cdr add-zsh-hook
 | |
| #   add-zsh-hook chpwd chpwd_recent_dirs
 | |
| #
 | |
| # (add-zsh-hook appeared in zsh version 4.3.4.)  This ensures that all
 | |
| # directories you change to interactively are registered.  The
 | |
| # chpwd_recent_dirs function does some guesswork to see if you are "really"
 | |
| # changing directory permanently, see below.
 | |
| #
 | |
| # The argument to cdr is a number corresponding to the Nth most recently
 | |
| # changed-to directory starting at 1 for the immediately preceding
 | |
| # directory (the current directory is remembered but is not offered as a
 | |
| # destination).  You can use directory arguments if you set the
 | |
| # recent-dirs-default style, see below; however, it should be noted
 | |
| # that if you do you gain nothing over using cd directly (the recent
 | |
| # directory list is updated in either case).
 | |
| #
 | |
| # If the argument is omitted, 1 is assumed.
 | |
| #
 | |
| # Completion is available if compinit has been run; menu selection is
 | |
| # recommended, using
 | |
| #
 | |
| #   zstyle ':completion:*:*:cdr:*:*' menu selection
 | |
| #
 | |
| # and also the verbose style to ensure the directory is shown (this
 | |
| # is on by default).
 | |
| #
 | |
| # Options
 | |
| # =======
 | |
| #
 | |
| # "cdr -l" lists the numbers and the corresponding directories in
 | |
| # abbreviated form (i.e. with "~" substitution reapplied), one per line.
 | |
| # The directories here are not quoted (this would only be an issue if a
 | |
| # directory name contained a newline).  This is used by the completion
 | |
| # system.
 | |
| #
 | |
| # "cdr -r" sets the parameter "reply" to the current set of directories.
 | |
| #
 | |
| # "cdr -e" allows you to edit the list of directories, one per line.  The
 | |
| # list can be edited to any extent you like; no sanity checking is
 | |
| # performed.  Completion is available.  No quoting is necessary (except for
 | |
| # newlines, where I have in any case no sympathy); directories are in
 | |
| # unabbreviated from and contain an absolute path, i.e. they start with /
 | |
| # (and only /).  Usually the first entry should be left as the current
 | |
| # directory.
 | |
| #
 | |
| # "cdr -p 'pattern'" prunes anything matching the given extended glob
 | |
| # pattern from the directory list.  The match is against the fully
 | |
| # expanded directory path and the full string must match (use wildcards
 | |
| # at the ends if needed).  If output is going to a terminal, the
 | |
| # function will print the new list for the user to confrim; this can be
 | |
| # skipped by giving -P instead of -p.
 | |
| #
 | |
| # Details of directory handling
 | |
| # =============================
 | |
| #
 | |
| # Recent directories are saved to a file immediately and hence are
 | |
| # preserved across sessions.  Note currently no file locking is applied:
 | |
| # the list is updated immediately on interactive commands and nowhere else
 | |
| # (unlike history), and it is assumed you are only going to change
 | |
| # directory in one window at once.  This is not safe on shared accounts,
 | |
| # but in any case the system has limited utility when someone else is
 | |
| # changing to a different set of directories behind your back.
 | |
| #
 | |
| # To make this a little safer, only directory changes instituted from the
 | |
| # command line, either directly or indirectly through shell function calls
 | |
| # (but not through subshells, evals, traps, completion functions and the
 | |
| # like) are saved.  This works best in versions of the shell from 4.3.11
 | |
| # which has facilities to check the evaluation context.  Shell functions
 | |
| # should use cd -q or pushd -q to avoid side effects if the change to the
 | |
| # directory is to be invisible at the command line.  See the function
 | |
| # chpwd_recent_dirs for more details.
 | |
| #
 | |
| # Styles
 | |
| # ======
 | |
| #
 | |
| # Various styles are available.  The context for setting styles should be
 | |
| # ':chpwd:*' in case the meaning of the context is extended in future, for
 | |
| # example:
 | |
| #
 | |
| #   zstyle ':chpwd:*' recent-dirs-max 0
 | |
| #
 | |
| # although the style name is specific enough that a context of '*' should
 | |
| # be fine in practice.  The only exception is recent-dirs-insert, which is
 | |
| # used exclusively by the completion system and so has the usual completion
 | |
| # system context (':completion:*' if nothing more specific is needed,
 | |
| # though again '*' should be fine in practice).
 | |
| #
 | |
| #   recent-dirs-default
 | |
| #     If true, and the command is expecting a recent directory index, and
 | |
| #     either there is more than one argument or the argument is not an
 | |
| #     integer, then fall through to "cd".  This allows the lazy to use only
 | |
| #     one command for directory changing.  Completion recognises this, too;
 | |
| #     see recent-dirs-insert for how to control completion when this option
 | |
| #     is in use.
 | |
| #
 | |
| #   recent-dirs-file
 | |
| #     The file where the list of directories is saved.  The default
 | |
| #     is ${ZDOTDIR:-$HOME}/.chpwd-recent-dirs, i.e. this is in your
 | |
| #     home directory unless you have set ZDOTDIR to point somewhere else.
 | |
| #     Directory names are saved in $'...' quoted form, so each line
 | |
| #     in the file can be supplied directly to the shell as an argument.
 | |
| #
 | |
| #     The value of this style may be an array.  In this case, the first
 | |
| #     file in the list will always be used for saving directories while any
 | |
| #     other files are left untouched.  When reading the recent directory
 | |
| #     list, if there are fewer than the maximum number of entries in the
 | |
| #     first file, the contents of later files in the array will be appended
 | |
| #     with duplicates removed from the list shown.  The contents of the two
 | |
| #     files are not sorted together, i.e. all the entries in the first file
 | |
| #     are shown first.  The special value "+" can appear in the list to
 | |
| #     indicate the default file should be read at that point.  This allows
 | |
| #     effects like the following:
 | |
| #
 | |
| #       zstyle recent-dirs-file ':chpwd:*' ~/.chpwd-recent-dirs-${TTY##*/} +
 | |
| #
 | |
| #     Recent directories are read from a file numbered according to
 | |
| #     the terminal.  If there are insufficient entries the list
 | |
| #     is supplemented from the default file.
 | |
| #
 | |
| #   recent-dirs-insert
 | |
| #     Used by completion.  If recent-dirs-default is true, then setting
 | |
| #     this to true causes the actual directory, rather than its index, to
 | |
| #     be inserted on the command line; this has the same effect as using
 | |
| #     the corresponding index, but makes the history clearer and the line
 | |
| #     easier to edit.  With this setting, if part of an argument was
 | |
| #     already typed, normal directory completion rather than recent
 | |
| #     directory completion is done; this is because recent directory
 | |
| #     completion is expected to be done by cycling through entries menu
 | |
| #     fashion.  However, if the value of the style is "always", then only
 | |
| #     recent directories will be completed; in that case, use the cd
 | |
| #     command when you want to complete other directories.  If the value is
 | |
| #     "fallback", recent directories will be tried first, then normal
 | |
| #     directory completion is performed if recent directory completion
 | |
| #     failed to find a match.  Finally, if the value is "both" then both
 | |
| #     sets of completions are presented; the usual tag mechanism can be
 | |
| #     used to distinguish results, with recent directories tagged as
 | |
| #     "recent-dirs".  Note that the recent directories inserted are
 | |
| #     abbreviated with directory names where appropriate.
 | |
| #
 | |
| #   recent-dirs-max
 | |
| #     The maximum number of directories to save to the file.  If
 | |
| #     this is zero or negative there is no maximum.  The default is 20.
 | |
| #     Note this includes the current directory, which isn't offered,
 | |
| #     so the highest number of directories you will be offered
 | |
| #     is one less than the maximum.
 | |
| #
 | |
| #   recent-dirs-prune
 | |
| #     This style is an array determining what directories should (or should
 | |
| #     not) be added to the recent list.  Elements of the array can include:
 | |
| #       parent
 | |
| #         Prune parents (more accurately, ancestors) from the recent list.
 | |
| #         If present, changing directly down by any number of directories
 | |
| #         causes the current directory to be overwritten.  For example,
 | |
| #         changing from ~pws to ~pws/some/other/dir causes ~pws not to be
 | |
| #         left on the recent directory stack.  This only applies to direct
 | |
| #         changes to descendant directories; earlier directories on the
 | |
| #         list are not pruned.  For example, changing from ~pws/yet/another
 | |
| #         to ~pws/some/other/dir does not cause ~pws to be pruned.
 | |
| #       pattern:<pattern>
 | |
| #         Gives a zsh pattern for directories that should not be
 | |
| #         added to the recent list (if not already there).  This element
 | |
| #         can be repeated to add different patterns.  For example,
 | |
| #         'pattern:/tmp(|/*)' stops /tmp or its descendants from being
 | |
| #         added.  The EXTENDED_GLOB option is always turned on for
 | |
| #         these patterns.
 | |
| #
 | |
| #   recent-dirs-pushd
 | |
| #     If set to true, cdr will use pushd instead of cd to change the
 | |
| #     directory, so the directory is saved on the directory stack.  As the
 | |
| #     directory stack is completely separate from the list of files saved
 | |
| #     by the mechanism used in this file there is no obvious reason to do
 | |
| #     this.
 | |
| #
 | |
| # Use with dynamic directory naming
 | |
| # =================================
 | |
| #
 | |
| # It is possible to refer to recent directories using the dynamic directory
 | |
| # name syntax that appeared in zsh version 4.3.7.  If you create and
 | |
| # autoload a function zsh_directory_name containing the following code,
 | |
| # ~[1] will refer to the most recent directory other than $PWD, and so on.
 | |
| # This also includes completion (version 4.3.11 is required for this to
 | |
| # work; previous versions needed the file _dynamic_directory_name to
 | |
| # be overloaded).
 | |
| #
 | |
| #   if [[ $1 = n ]]; then
 | |
| #     if [[ $2 = <-> ]]; then
 | |
| #       # Recent directory
 | |
| #       typeset -ga reply
 | |
| #       autoload -Uz cdr
 | |
| #       cdr -r
 | |
| #       if [[ -n ${reply[$2]} ]]; then
 | |
| #         reply=(${reply[$2]})
 | |
| #         return 0
 | |
| #       else
 | |
| #         reply=()
 | |
| #         return 1
 | |
| #       fi
 | |
| #     fi
 | |
| #   elif [[ $1 = c ]]; then
 | |
| #     if [[ $PREFIX = <-> || -z $PREFIX ]]; then
 | |
| #       typeset -a keys values
 | |
| #   
 | |
| #       values=(${${(f)"$(cdr -l)"}/ ##/:})
 | |
| #       keys=(${values%%:*})
 | |
| #   
 | |
| #       _describe -t dir-index 'recent directory index' \
 | |
| #         values keys -V unsorted -S']'
 | |
| #       return
 | |
| #     fi
 | |
| #   fi
 | |
| #   return 1
 | |
| 
 | |
| 
 | |
| emulate -L zsh
 | |
| setopt extendedglob
 | |
| 
 | |
| autoload -Uz chpwd_recent_filehandler chpwd_recent_add
 | |
| 
 | |
| integer list set_reply i bad edit force_prune
 | |
| local opt dir prune
 | |
| local -aU dirs
 | |
| 
 | |
| while getopts "elp:P:r" opt; do
 | |
|   case $opt in
 | |
|     (e)
 | |
|     edit=1
 | |
|     ;;
 | |
| 
 | |
|     (l)
 | |
|     list=1
 | |
|     ;;
 | |
| 
 | |
|     ([pP])
 | |
|     prune=$OPTARG
 | |
|     edit=1
 | |
|     [[ $opt = P ]] && force_prune=1
 | |
|     ;;
 | |
| 
 | |
|     (r)
 | |
|     set_reply=1
 | |
|     ;;
 | |
| 
 | |
|     (*)
 | |
|     return 1
 | |
|     ;;
 | |
|   esac
 | |
| done
 | |
| shift $(( OPTIND - 1 ))
 | |
| 
 | |
| if (( set_reply )); then
 | |
|   typeset -ga reply
 | |
| else
 | |
|   local -a reply
 | |
| fi
 | |
| 
 | |
| if (( list || set_reply || edit )); then
 | |
|   (( $# )) && bad=1
 | |
| else
 | |
|   if [[ $#1 -eq 0 ]]; then
 | |
|     1=1
 | |
|   elif [[ $# -ne 1 || $1 != <-> ]]; then
 | |
|     if zstyle -t ':chpwd:' recent-dirs-default; then
 | |
|       cd "$@"
 | |
|       return
 | |
|     else
 | |
|       bad=1
 | |
|     fi
 | |
|   fi
 | |
| fi
 | |
| 
 | |
| if (( bad )); then
 | |
|   print "Usage: $0 [-l | -r | <dir-num> ]
 | |
| Use $0 -l or completion to see possible directories."
 | |
|   return 1
 | |
| fi
 | |
| 
 | |
| chpwd_recent_filehandler
 | |
| 
 | |
| if [[ $PWD != $reply[1] ]]; then
 | |
|   # When we first start we don't have the current directory.
 | |
|   # Add it now for consistency.
 | |
|   chpwd_recent_add $PWD && chpwd_recent_filehandler $reply
 | |
| fi
 | |
| 
 | |
| if (( edit )); then
 | |
|   if [[ -n $prune ]]; then
 | |
|     reply=(${reply:#$~prune})
 | |
|     if [[ force_prune -eq 0 && -t 1 ]]; then
 | |
|       print -nrl "New list:" $reply 'Accept? '
 | |
|       if ! read -q; then
 | |
| 	print
 | |
| 	return 1
 | |
|       fi
 | |
|       print
 | |
|     fi
 | |
|   else
 | |
|     local compcontext='directories:directory:_path_files -/'
 | |
|     IFS='
 | |
| ' vared reply || return 1
 | |
|   fi
 | |
|   chpwd_recent_filehandler $reply
 | |
| fi
 | |
| 
 | |
| # Skip current directory if present (may have been pruned).
 | |
| [[ $reply[1] = $PWD ]] && reply=($reply[2,-1])
 | |
| 
 | |
| if (( list )); then
 | |
|   dirs=($reply)
 | |
|   for (( i = 1; i <= ${#dirs}; i++ )); do
 | |
|     print -n ${(r.5.)i}
 | |
|     print -r ${(D)dirs[i]}
 | |
|   done
 | |
|   return
 | |
| fi
 | |
| 
 | |
| (( set_reply || edit )) && return
 | |
| 
 | |
| if (( $1 > ${#reply} )); then
 | |
|   print "Not enough directories ($(( ${#dirs} - 1)) possibilities)" >&2
 | |
|   return 1
 | |
| fi
 | |
| dir=${reply[$1]}
 | |
| 
 | |
| if zstyle -t ':chpwd:' recent-dirs-pushd; then
 | |
|   pushd -- $dir
 | |
| else
 | |
|   cd -- $dir
 | |
| fi
 |