From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 25085 invoked by alias); 22 Jun 2010 19:31:27 -0000 Mailing-List: contact zsh-workers-help@zsh.org; run by ezmlm Precedence: bulk X-No-Archive: yes List-Id: Zsh Workers List List-Post: List-Help: X-Seq: 28054 Received: (qmail 411 invoked from network); 22 Jun 2010 19:31:14 -0000 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on f.primenet.com.au X-Spam-Level: X-Spam-Status: No, score=-2.6 required=5.0 tests=BAYES_00,RCVD_IN_DNSWL_LOW, SPF_HELO_PASS autolearn=ham version=3.3.1 Received-SPF: none (ns1.primenet.com.au: domain at csr.com does not designate permitted sender hosts) Date: Tue, 22 Jun 2010 20:30:58 +0100 From: Peter Stephenson To: "Zsh Hackers' List" Subject: "cdr" function for recent directories etc. Message-ID: <20100622203058.7b08775e@pws-pc> Organization: Cambridge Silicon Radio X-Mailer: Claws Mail 3.7.6 (GTK+ 2.18.9; x86_64-redhat-linux-gnu) Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="MP_/RzBtJ1yDU+fQFs9=ePl2RRO" X-OriginalArrivalTime: 22 Jun 2010 19:31:05.0527 (UTC) FILETIME=[74FF8C70:01CB1241] X-Scanned-By: MailControl A_09_40_00 (www.mailcontrol.com) on 10.71.0.128 --MP_/RzBtJ1yDU+fQFs9=ePl2RRO Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7bit Content-Disposition: inline Here's my contribution to the functions available for tracking directories across sessions. It includes a chpwd hook function that maintains a list of recently used directories (in order) in a file, and the function "cdr" that uses it; then there are a couple of helper functions. Configurable completion is provided so you can decide how you want to complete the directories in the list. If you define or add to zsh_directory_name as indicated in the comments you can use ~[1] etc. to refer to the directories and completion after ~[ will work. There are numerous styles to configure various aspects, including the file where the directories are saved, so you can tie this to a particular session as detected in ~/.zshrc. See comments at the top of cdr for documentation. Very roughly, it's like a more configurable pushd with a persistent stack, and most-recent-first ordering (like pushd used to have until the mid nineties at which point I had to introduce a function front end). Note a couple of aspects work best with the latest version of the shell, not yet released (I've said 4.3.11 but the current top of tree is OK). I'm posting it here rather than zsh-users in case anyone wants to make tweaks to it before it goes further. It's fairly straightforward shell code. pws Member of the CSR plc group of companies. CSR plc registered in England and Wales, registered number 4187346, registered office Churchill House, Cambridge Business Park, Cowley Road, Cambridge, CB4 0WZ, United Kingdom --MP_/RzBtJ1yDU+fQFs9=ePl2RRO Content-Type: text/plain Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="cdr" # 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; # see below. 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. # # 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 --MP_/RzBtJ1yDU+fQFs9=ePl2RRO Content-Type: text/plain Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="_cdr" #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)"}##<-> ##}) if _wanted -V recent-dirs expl 'recent directory' compadd -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 --MP_/RzBtJ1yDU+fQFs9=ePl2RRO Content-Type: text/plain Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="chpwd_recent_add" # 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) --MP_/RzBtJ1yDU+fQFs9=ePl2RRO Content-Type: text/plain Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="chpwd_recent_dirs" # 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 --MP_/RzBtJ1yDU+fQFs9=ePl2RRO Content-Type: text/plain Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="chpwd_recent_filehandler" # 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 zstyle -s ':chpwd:' recent-dirs-file file || file=${ZDOTDIR:-$HOME}/.chpwd-recent-dirs if (( $# )); then zstyle -s ':chpwd:' recent-dirs-max max || max=20 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} > $file elif [[ -f $file ]]; then typeset -g reply # Unquote on read. reply=(${(Q)${(f)"$(<$file)"}}) else typeset -g reply reply=() fi --MP_/RzBtJ1yDU+fQFs9=ePl2RRO--