summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Stephenson <pws@users.sourceforge.net>2010-07-09 14:47:48 +0000
committerPeter Stephenson <pws@users.sourceforge.net>2010-07-09 14:47:48 +0000
commit5da6530d831ea8a00943b39359d535ea59996894 (patch)
tree2a4fcd827214fa4fd2a8d172341a07cb8b76cda0
parent924f40b772d7c222ffe8db502fb88cc897fd9902 (diff)
downloadzsh-5da6530d831ea8a00943b39359d535ea59996894.tar.gz
zsh-5da6530d831ea8a00943b39359d535ea59996894.tar.xz
zsh-5da6530d831ea8a00943b39359d535ea59996894.zip
28065 plus unposted zsh.mdd:
add cdr and related functions and docs
-rw-r--r--ChangeLog11
-rw-r--r--Doc/Zsh/contrib.yo268
-rw-r--r--Functions/Chpwd/.distfiles8
-rw-r--r--Functions/Chpwd/_cdr57
-rw-r--r--Functions/Chpwd/cdr311
-rw-r--r--Functions/Chpwd/chpwd_recent_add24
-rw-r--r--Functions/Chpwd/chpwd_recent_dirs49
-rw-r--r--Functions/Chpwd/chpwd_recent_filehandler42
-rw-r--r--Src/zsh.mdd2
9 files changed, 768 insertions, 4 deletions
diff --git a/ChangeLog b/ChangeLog
index 59fc30125..96fb1f6f9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2010-07-09  Peter Stephenson  <pws@csr.com>
+
+	* 28065: Doc/Zsh/contrib.yo, Functions/Chpwd/.distfiles,
+	Functions/Chpwd/cdr, Functions/Chpwd/_cdr,
+	Functions/Chpwd/chpwd_recent_add,
+	Functions/Chpwd/chpwd_recent_dirs,
+	Functions/Chpwd/chpwd_recent_filehandler, plus Src/zsh.mdd
+	(not posted): add cdr function, tools, and documentation.
+
 2010-06-30  Clint Adams  <clint@zsh.org>
 
 	* 27998, 28061, 28062: Functions/Newuser/zsh-newuser-install:
@@ -13343,5 +13352,5 @@
 
 *****************************************************
 * This is used by the shell to define $ZSH_PATCHLEVEL
-* $Revision: 1.5019 $
+* $Revision: 1.5020 $
 *****************************************************
diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index ac0d32d19..73bbb521a 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -11,6 +11,7 @@ such as shell functions, look for comments in the function source files.
 
 startmenu()
 menu(Utilities)
+menu(Recent Directories)
 menu(Version Control Information)
 menu(Prompt Themes)
 menu(ZLE Functions)
@@ -21,7 +22,7 @@ menu(User Configuration Functions)
 menu(Other Functions)
 endmenu()
 
-texinode(Utilities)(Version Control Information)()(User Contributions)
+texinode(Utilities)(Recent Directories)()(User Contributions)
 sect(Utilities)
 
 subsect(Accessing On-Line Help)
@@ -317,7 +318,270 @@ functions to be executed.
 )
 enditem()
 
-texinode(Version Control Information)(Prompt Themes)(Utilities)(User Contributions)
+texinode(Recent Directories)(Version Control Information)(Utilities)(User
+Contributions)
+cindex(recent directories, maintaining list of)
+cindex(directories, maintaining list of recent)
+findex(cdr)
+findex(_cdr)
+findex(chpwd_recent_add)
+findex(chpwd_recent_dirs)
+findex(chpwd_recent_filehandler)
+sect(Remembering Recent Directories)
+
+The function tt(cdr) allows you to change the working directory to a
+previous working directory from a list maintained automatically.  It is
+similar in concept to the directory stack controlled by the tt(pushd),
+tt(popd) and tt(dirs) builtins, but is more configurable, and as it stores
+all entries in files it is maintained across sessions and (by default)
+between terminal emulators in the current session.  (The tt(pushd)
+directory stack is not actually modified or used by tt(cdr) unless you
+configure it to do so as described in the configuration section below.)
+
+subsect(Installation)
+
+The system works by means of a hook function that is called every time the
+directory changes.  To install the system, autoload the required functions
+and use the tt(add-zsh-hook) function described above:
+
+example(autoload -Uz chpwd_recent_dirs cdr add-zsh-hook
+add-zsh-hook chpwd chpwd_recent_dirs)
+
+Now every time you change directly interactively, no matter which
+command you use, the directory to which you change will be remembered
+in most-recent-first order.
+
+subsect(Use)
+
+All direct user interaction is via the tt(cdr) function.
+
+The argument to cdr is a number var(N) corresponding to the var(N)th most
+recently changed-to directory.  1 is the immediately preceeding directory;
+the current directory is remembered but is not offered as a destination.
+Note that if you have multiple windows open 1 may refer to a directory
+changed to in another window; you can avoid this by having per-terminal
+files for storing directory as described for the
+tt(recent-dirs-file) style below.
+
+If you set the tt(recent-dirs-default) style described below tt(cdr)
+will behave the same as tt(cd) if given a non-numeric argument, or more
+than one argument.  The recent directory list is updated just the same
+however you change directory.
+
+If the argument is omitted, 1 is assumed.  This is similar to tt(pushd)'s
+behaviour of swapping the two most recent directories on the stack.
+
+Completion for the argument to tt(cdr) is available if compinit has been
+run; menu selection is recommended, using:
+
+example(zstyle ':completion:*:*:cdr:*:*' menu selection)
+
+to allow you to cycle through recent directories; the order is preserved,
+so the first choice is the most recent directory before the current one.
+The verbose style is also recommended to ensure the directory is shown; this
+style is on by default so no action is required unless you have changed it.
+
+subsect(Options)
+
+The behaviour of tt(cdr) may be modified by the following options.
+
+startitem()
+item(tt(-l))(
+lists the numbers and the corresponding directories in
+abbreviated form (i.e. with tt(~) 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.
+)
+item(tt(-r))(
+sets the variable tt(reply) to the current set of directories.  Nothing
+is printed and the directory is not changed.
+)
+item(tt(-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 tt(/).
+Usually the first entry should be left as the current directory.
+)
+enditem()
+
+subsect(Configuration)
+
+Configuration is by mean of the styles mechanism that should be familiar
+from completion; if not, see the description of the tt(zstyle) command in
+ifzman(see zmanref(zshmodules))\
+ifnzman(noderef(The zsh/zutil Module)).  The context for setting styles
+should be tt(':chpwd:*') in case the meaning of the context is extended in
+future, for example:
+
+example(zstyle ':chpwd:*' recent-dirs-max 0)
+
+sets the value of the tt(recent-dirs-max) style to 0.  In practice the
+style name is specific enough that a context of '*' should be fine.
+
+An exception is tt(recent-dirs-insert), which is used exclusively by the
+completion system and so has the usual completion system context
+(tt(':completion:*') if nothing more specific is needed), though again
+tt('*') should be fine in practice.
+
+startitem()
+item(tt(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.
+)
+item(tt(recent-dirs-file))(
+The file where the list of directories is saved.  The default
+is tt(${ZDOTDIR:-$HOME}/.chpwd-recent-dirs), i.e. this is in your
+home directory unless you have set the variable tt(ZDOTDIR) to point
+somewhere else.  Directory names are saved in tt($')var(...)tt(') 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 tt(+) can appear in the list to
+indicate the default file should be read at that point.  This allows
+effects like the following:
+
+example(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.
+)
+item(tt(recent-dirs-insert))(
+Used by completion.  If tt(recent-dirs-default) is true, then setting
+this to tt(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.
+
+If the value of the style is tt(always), then only recent directories will
+be completed; in that case, use the tt(cd) command when you want to
+complete other directories.
+
+If the value is tt(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 tt(both) then both sets of completions are
+presented; the usual tag mechanism can be used to distinguish results, with
+recent directories tagged as tt(recent-dirs).  Note that the recent
+directories inserted are abbreviated with directory names where appropriate.
+)
+item(tt(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.
+)
+item(tt(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:
+
+startitem()
+item(tt(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 diretories; 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.
+)
+item(tt(pattern:var(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,
+tt('pattern:/tmp(|/*)') stops tt(/tmp) or its descendants from being
+added.  The tt(EXTENDED_GLOB) option is always turned on for
+these patterns.
+)
+enditem()
+)
+item(tt(recent-dirs-pushd))(
+If set to true, tt(cdr) will use tt(pushd) instead of tt(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.
+)
+enditem()
+
+subsect(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 tt(zsh_directory_name) containing the following code,
+tt(~[1]) will refer to the most recent directory other than $PWD, and so on.
+This also includes completion.
+
+example(if [[ $1 = n ]]; then
+  if [[ $2 = <-> ]]; then
+    # Recent directory
+    typeset -ga reply
+    autoload -Uz cdr
+    cdr -r
+    if [[ -n ${reply[$2]} ]]; then
+      reply=LPAR()${reply[$2]}RPAR()
+      return 0
+    else
+      reply=LPAR()RPAR()
+      return 1
+    fi
+  fi
+elif [[ $1 = c ]]; then
+  if [[ $PREFIX = <-> || -z $PREFIX ]]; then
+    typeset -a keys values
+    values=LPAR()${${(f)"$+LPAR()cdr -l+RPAR()"}/ ##/:}RPAR()
+    keys=LPAR()${values%%:*}RPAR()
+    _describe -t dir-index 'recent directory index' \
+      values keys -V unsorted -S']'
+    return
+  fi
+fi
+return 1)
+
+subsect(Details of directory handling)
+
+This section is for the curious or confused; most users will not
+need to know this information.
+
+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.  Shell functions should use tt(cd -q) or tt(pushd -q) to
+avoid side effects if the change to the directory is to be invisible at the
+command line.  See the contents of the function tt(chpwd_recent_dirs) for
+more details.
+
+texinode(Version Control Information)(Prompt Themes)(Recent Directories)(User Contributions)
 sect(Gathering information from version control systems)
 cindex(version control utility)
 
diff --git a/Functions/Chpwd/.distfiles b/Functions/Chpwd/.distfiles
new file mode 100644
index 000000000..39ccd830c
--- /dev/null
+++ b/Functions/Chpwd/.distfiles
@@ -0,0 +1,8 @@
+DISTFILES_SRC='
+.distfiles
+cdr
+_cdr
+chpwd_recent_add
+chpwd_recent_dirs
+chpwd_recent_filehandler
+'
diff --git a/Functions/Chpwd/_cdr b/Functions/Chpwd/_cdr
new file mode 100644
index 000000000..2f52ff54b
--- /dev/null
+++ b/Functions/Chpwd/_cdr
@@ -0,0 +1,57 @@
+#compdef cdr
+
+local expl insert_string
+integer default insert
+
+zstyle -t ':chpwd:' recent-dirs-default && default=1
+if (( default )); then
+  zstyle -s ':completion:${curcontext}' recent-dirs-insert insert_string
+  case $insert_string in
+    (both)
+    insert=4
+    ;;
+
+    (fallback)
+    insert=3
+    ;;
+
+    (always)
+    insert=2
+    ;;
+
+    ([tT]*|1|[yY]*)
+    insert=1
+    ;;
+
+    (*)
+    insert=0
+  esac
+fi
+
+# See if we should fall back to cd completion.
+if [[ default -ne 0 && insert -lt 2 && \
+  ( CURRENT -ne 2 || (-n $words[2] && $words[2] != <->) ) ]]; then
+  $_comps[cd] "$@"
+  return
+fi
+
+local -a values keys
+
+if (( insert )); then
+  # insert the actual directory, not the number
+  values=(${${(f)"$(cdr -l)"}##<-> ##})
+  # Suppress the usual space suffix, since there's no further argument
+  # and it's useful to be able to edit the directory e.g. add /more/stuff.
+  if _wanted -V recent-dirs expl 'recent directory' compadd -S '' -Q -a values
+  then
+    (( insert == 4 )) || return 0
+  fi
+
+  (( insert >= 3 )) || return
+  $_comps[cd] "$@"
+else
+  values=(${${(f)"$(cdr -l)"}/ ##/:})
+  keys=(${values%%:*})
+
+  _describe -t dir-index 'recent directory index' values keys -V unsorted
+fi
diff --git a/Functions/Chpwd/cdr b/Functions/Chpwd/cdr
new file mode 100644
index 000000000..551710499
--- /dev/null
+++ b/Functions/Chpwd/cdr
@@ -0,0 +1,311 @@
+# 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 preceeding
+# 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.
+#
+# 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 diretories; 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
+local opt dir
+local -aU dirs
+
+while getopts "elr" opt; do
+  case $opt in
+    (e)
+    edit=1
+    ;;
+
+    (l)
+    list=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
+  local compcontext='directories:directory:_path_files -/'
+IFS='
+' vared reply || return 1
+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 -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
diff --git a/Functions/Chpwd/chpwd_recent_add b/Functions/Chpwd/chpwd_recent_add
new file mode 100644
index 000000000..49ec8d1c6
--- /dev/null
+++ b/Functions/Chpwd/chpwd_recent_add
@@ -0,0 +1,24 @@
+# Helper for chpwd_recent_dirs.
+# Add a directory to the reply array unless we're skipping it.
+# If skipping, return non-zero status.
+
+local pat
+local add=$1
+local -a prune patterns
+
+zstyle -a ':chpwd:' recent-dirs-prune prune
+if (( ${#prune} )); then
+  patterns=(${${prune:#^pattern:*}##pattern:})
+fi
+
+for pat in $patterns; do
+  if [[ $add =~ ${~pat} ]]; then
+    return 1
+  fi
+done
+
+if [[ ${prune[(I)parent]} -ne 0 && $add = $reply[1]/* ]]; then
+  # replace
+  reply=($reply[2,-1])
+fi
+reply=($add $reply)
diff --git a/Functions/Chpwd/chpwd_recent_dirs b/Functions/Chpwd/chpwd_recent_dirs
new file mode 100644
index 000000000..10a6b4e34
--- /dev/null
+++ b/Functions/Chpwd/chpwd_recent_dirs
@@ -0,0 +1,49 @@
+# Save the current directory in the stack of most recently used directories.
+
+emulate -L zsh
+setopt extendedglob
+
+local -aU reply
+integer changed
+
+autoload -Uz chpwd_recent_filehandler chpwd_recent_add
+
+# Don't save if this is not interactive, or is in a subshell.
+# Don't save if this is not being called directly from the top level
+# of the shell via a straightforward sequence of shell functions.
+# So this is called
+#  - on any straightforward cd or pushd (including AUTO_CD)
+#  - any time cd or pushd is called from a function invoked directly
+#    or indirectly from the command line, e.g. if pushd is a function
+#    fixing the order of directories that got broken years ago
+# but it is not called any time
+#  - the shell is not interactive
+#  - we forked
+#  - we are being eval'd, including for some special purpose such
+#    as a style
+#  - we are not called from the top-level command loop, for example
+#    we are in a completion function (which is called from zle
+#    when the main top-level command interpreter isn't running)
+#  - obviously, when cd -q is in use (that's what the option is for).
+#
+# For compatibility with older shells, skip this test if $ZSH_EVAL_CONTEXT
+# isn't set.  This will never be the case inside a shell function when
+# the variable is implemented.
+if [[ ! -o interactive  || $ZSH_SUBSHELL -ne 0 || \
+  ( -n $ZSH_EVAL_CONTEXT && \
+  $ZSH_EVAL_CONTEXT != toplevel(:[a-z]#func|)# ) ]]; then
+  return
+fi
+
+chpwd_recent_filehandler
+
+if [[ $reply[1] != $PWD ]]; then
+  if [[ -n $OLDPWD && $reply[1] != $OLDPWD ]]; then
+    # The first time we change directory we probably don't have
+    # the initial directory.
+    chpwd_recent_add $OLDPWD && changed=1
+  fi
+  chpwd_recent_add $PWD && changed=1
+
+  (( changed )) && chpwd_recent_filehandler $reply
+fi
diff --git a/Functions/Chpwd/chpwd_recent_filehandler b/Functions/Chpwd/chpwd_recent_filehandler
new file mode 100644
index 000000000..b80e7f681
--- /dev/null
+++ b/Functions/Chpwd/chpwd_recent_filehandler
@@ -0,0 +1,42 @@
+# With arguments, output those files to the recent directory file.
+# With no arguments, read in the directories from the file into $reply.
+#
+# Handles recent-dirs-file and recent-dirs-max styles.
+
+emulate -L zsh
+setopt extendedglob
+
+integer max
+local file
+local -a files
+local default=${ZDOTDIR:-$HOME}/.chpwd-recent-dirs
+
+if zstyle -a ':chpwd:' recent-dirs-file files; then
+  files=(${files//(#s)+(#e)/$default})
+fi
+if (( ${#files} == 0 )); then
+  files=($default)
+fi
+
+zstyle -s ':chpwd:' recent-dirs-max max || max=20
+
+if (( $# )); then
+  if (( max > 0 && ${#argv} > max )); then
+    argv=(${argv[1,max]})
+  fi
+  # Quote on write.
+  # Use $'...' quoting... this fixes newlines and other nastiness.
+  print -rl ${(qqqq)argv} >${files[1]}
+else
+  typeset -g reply
+  # Unquote on read.
+  reply=()
+  for file in $files; do
+    [[ -r $file ]] || continue
+    reply+=(${(Q)${(f)"$(<$file)"}})
+    if (( max > 0 && ${#reply} >= max )); then
+      reply=(${reply[1,max]})
+      break
+    fi
+  done
+fi
diff --git a/Src/zsh.mdd b/Src/zsh.mdd
index 51e68fc7c..537aa4d8e 100644
--- a/Src/zsh.mdd
+++ b/Src/zsh.mdd
@@ -2,7 +2,7 @@ name=zsh/main
 link=static
 load=yes
 # load=static should replace use of alwayslink
-functions='Functions/Exceptions/* Functions/Misc/* Functions/MIME/* Functions/Prompts/* Functions/VCS_Info/* Functions/VCS_Info/Backends/*'
+functions='Functions/Chpwd/* Functions/Exceptions/* Functions/Misc/* Functions/MIME/* Functions/Prompts/* Functions/VCS_Info/* Functions/VCS_Info/Backends/*'
 
 nozshdep=1
 alwayslink=1