From 5da6530d831ea8a00943b39359d535ea59996894 Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Fri, 9 Jul 2010 14:47:48 +0000 Subject: 28065 plus unposted zsh.mdd: add cdr and related functions and docs --- ChangeLog | 11 +- Doc/Zsh/contrib.yo | 268 +++++++++++++++++++++++++- Functions/Chpwd/.distfiles | 8 + Functions/Chpwd/_cdr | 57 ++++++ Functions/Chpwd/cdr | 311 +++++++++++++++++++++++++++++++ Functions/Chpwd/chpwd_recent_add | 24 +++ Functions/Chpwd/chpwd_recent_dirs | 49 +++++ Functions/Chpwd/chpwd_recent_filehandler | 42 +++++ Src/zsh.mdd | 2 +- 9 files changed, 768 insertions(+), 4 deletions(-) create mode 100644 Functions/Chpwd/.distfiles create mode 100644 Functions/Chpwd/_cdr create mode 100644 Functions/Chpwd/cdr create mode 100644 Functions/Chpwd/chpwd_recent_add create mode 100644 Functions/Chpwd/chpwd_recent_dirs create mode 100644 Functions/Chpwd/chpwd_recent_filehandler diff --git a/ChangeLog b/ChangeLog index 59fc30125..96fb1f6f9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2010-07-09 Peter Stephenson + + * 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 * 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: +# 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 | ] +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 -- cgit 1.4.1