# Boyd's favorite .zshrc # Please send suggestions to me at boyd-adamson@usa.net # REQUIRES zsh version >= 4.1 # $Id: other.zshrc 82 2005-01-24 02:46:39Z boyd $ # vim: set ai tw=74 fen fdc=4 fdm=marker: VIM modeline ###################################################################### # COMMENTS & DOCUMENTATION {{{1 ###################################################################### # NOTE!!!! Don't make changes to this file for a single machine. Use # .zshrc.local. See "Local Customisations" below. ###################################################################### # General {{{2 ###################################################################### # This file is designed on the assumption that zsh is NOT our login # shell. If it was then the env variable setting should be in the # .zsh_profile file, not the .zshrc. # I've made a special effort to keep the number of external calls to a # minimum, since with this file's predecessor (.bashrc) it was getting to # the point that it would take 5 seconds to fire up bash on an Sun Ultra # 10! As a result, there are some unusual constructs in here. There are # still some external calls left (e.g. calls to sed) but they are not in # loops and I doubt they make that much difference to performance. # ###################################################################### # Local Customisations {{{2 ###################################################################### # It's best not to change this file on a machine-by-machine basis. For # local extensions this file calls ~/.zshrc.local if it exists. It's # called twice... once before all processing with a single parameter ($1) # "pre" and once after all processing with a single parameter "post". # # In addition, a number of variables are set and are available for use by # the ~/.zshrc.local script. These variables are UNset before this file # exits, so they are not available once the shell is interactive. # # The variables set by this script are: # _debugging Manually set (see below) to "1" if # parts of this and called scripts # should be verbose in their output. # Unset otherwise. NOTE that some # non-interactive tools (e.g. rsync) # get confused by stuff being # printed at shell startup so # normally this should be OFF. # # _shell_is_interactive Set to "1" if this is an interactive # session, and unset otherwise. No # output should be produced if this # is set. # # _running_X Set to "1" if we are running under # X and appropriate stuff should be # done. NOTE that when the "pre" # call to ~/.zshrc.local is made, # _running_X is based on a VERY # simple test (is DISPLAY set?). By # the time the "post" call is made # _running_X reflects more thorough # probes for a usable X server. It's # best to wait for the "post" call # before using X since timeouts on # non-existant servers may cause # this script to appear to hang # ###################################################################### # TODO: {{{2 ###################################################################### # It seems that I'm constantly adding new features that leap-frog the # TODO list below, so this is really a SHOULDDO list, but for what it's # worth: # - Policy question: Should stuff in the /usr/local tree be used in # preference to stuff in system directories like /usr/bin? At the # moment the path has the /usr/local stuff earlier. That means that # a system-installed version of software installed there won't get # used. # ###################################################################### # Folding {{{2 ###################################################################### # The funny comments in this file, made from braces, like the ones before # this paragraph, are for vim's folding mode. They make navigation in the # file a lot easier Unfortunately they require vim version >= 6.0. See # ":help folding" in vim for more info. They should't affect any other # editor, just think of them as marking the start and end of sections. ###################################################################### ###################################################################### # Set up some variables for later {{{1 ###################################################################### # NOTE: If a variable is added here it should be unset at the end of the # file! # _debugging=1 # $- contains the options provided to the shell [[ $- == *i* ]] && _shell_is_interactive=1 [[ -n "$DISPLAY" ]] && _running_X=1 ###################################################################### # Make the first call on the local settings file {{{1 ###################################################################### [ -f $HOME/.zshrc.local ] && . $HOME/.zshrc.local pre ###################################################################### # PATH, MANPATH, LD_LIBRARY_PATH {{{1 ###################################################################### # Comments {{{2 # We do this first since we might need them later. # # LOCALPROGS allows programs to be installed in # /usr/local//{bin,man,sbin,lib} and still be found. OPTPROGS # does the same thing for /opt/ - mostly for Solaris. MYOPTPROGS # is the same thing for ~/opt, but I don't understamd why you'd install # stuff in your own home dir and then not want to run it! # # Here we add EVERY path we're likely to need, independant of OS. We will # strip the ones that don't exist later # # Variable setup {{{2 # I don't normally set these to a complete list since not all users need # all apps, but if either of the {LOCAL,OPT}PROGS variables is not set it # will be automatically filled below with all values from the disk. #LOCALPROGS="vim screen teTeX" #OPTPROGS="SUNWcluster SUNWmd" #MYOPTPROGS="" # Basic Entries PATH=/sbin:/usr/sbin:/usr/bin:/usr/dt/bin:$PATH # Solaris entries PATH=$PATH:/usr/ccs/bin:/usr/openwin/bin:/usr/sfw/bin:/usr/perl5/bin # SunCluster entries (inc DTK) PATH=$PATH:/usr/cluster/bin:/usr/cluster/lib/sc:/usr/cluster/dtk/bin # Tru64 entries PATH=$PATH:/usr/bin/X11:/usr/tcb/bin # MacOSX entries PATH=/sw/bin:/sw/sbin:/Developer/Tools:$PATH # My entries PATH=/usr/local/bin:$PATH # Late Solaris entries - definitely want these at the end PATH=$PATH:/usr/ucb MANPATH=/sw/share/man:/usr/share/man:/usr/dt/share/man MANPATH=$MANPATH:/usr/local/man:/usr/openwin/man MANPATH=$MANPATH:/usr/X11R6/man/:/usr/perl5/man MANPATH=$MANPATH:/usr/cluster/man:/usr/cluster/dtk/man:/usr/sfw/man LD_LIBRARY_PATH=/usr/local/lib:/usr/sfw/lib # Auto-add paths {{{2 # /usr/local {{{3 if [ -d /usr/local ]; then for PROG in ${LOCALPROGS:-$(cd /usr/local; echo *)} do if [ -d /usr/local/$PROG/bin ]; then PATH=/usr/local/$PROG/bin:$PATH fi if [ -d /usr/local/$PROG/sbin ]; then PATH=/usr/local/$PROG/sbin:$PATH fi if [ -d /usr/local/$PROG/man ]; then MANPATH=/usr/local/$PROG/man:$MANPATH fi if [ -d /usr/local/$PROG/lib ]; then LD_LIBRARY_PATH=/usr/local/$PROG/lib:$LD_LIBRARY_PATH fi done fi # /opt {{{3 if [ -d /opt ]; then for PROG in ${OPTPROGS:-$(cd /opt; echo *)} do if [ -d /opt/$PROG/bin ]; then PATH=/opt/$PROG/bin:$PATH fi if [ -d /opt/$PROG/sbin ]; then PATH=/opt/$PROG/sbin:$PATH fi if [ -d /opt/$PROG/man ]; then MANPATH=/opt/$PROG/man:$MANPATH fi if [ -d /opt/$PROG/lib ]; then LD_LIBRARY_PATH=/opt/$PROG/lib:$LD_LIBRARY_PATH fi done fi # $HOME/opt {{{3 # Don't do this if our home directory is /. This won't affect the # resulting PATH but it will make the loop below faster if [ -d $HOME/opt -a $HOME != "/" ]; then for PROG in ${MYOPTPROGS:-$(cd $HOME/opt; echo *)} do if [ -d $HOME/opt/$PROG/bin ]; then PATH=$HOME/opt/$PROG/bin:$PATH fi if [ -d $HOME/opt/$PROG/sbin ]; then PATH=$HOME/opt/$PROG/sbin:$PATH fi if [ -d $HOME/opt/$PROG/man ]; then MANPATH=$HOME/opt/$PROG/man:$MANPATH fi if [ -d $HOME/opt/$PROG/lib ]; then LD_LIBRARY_PATH=$HOME/opt/$PROG/lib:$LD_LIBRARY_PATH fi done fi unset PROG OPTPROGS LOCALPROGS # Clean up path-like variables {{{2 # Here we clean up the PATH and similar variables by removing non-existant # dirs and duplicates. # # This is made significantly easier in zsh by two features: typeset -T and # typeset -U. Together they allow us to treat PATH-like variables as # arrays, with builtin uniqueness # # We do this by declaring a function that cleans a variable. This function # is currently left declared for future use. TODO: Should we unset this? # pathclean: Clean up path-like variables {{{3 # arguments: name-of-var-to-clean # # Note that this function's parameter is the NAME of the variable to clean # up, not the contents of the variable. The variable is changed in-place. # This is the zsh equivalent to pass-by-reference! function pathclean() { # Variable Declarations local -i i=1 # loop var local thevar=$1 # The NAME of the variable we're cleaning thearr=${(L)1} # The NAME of the array associated with it # Clean up the PATH # Tie the variable to a corresponding array parameter # Can't do this to 'path' or 'manpath' if [[ ( $thevar == PATH ) || ( $thevar == MANPATH ) ]] then typeset -agU $thearr else typeset -agUT $thevar $thearr fi # Now loop through and remove non-existent directories (this gets a # bit hairy ) while (( i <= ${(P)#thearr} )) do if [[ ! -d ${${(P)thearr}[i]} ]] then eval "${thearr}[i]=()" continue fi (( i = i + 1 )) done } # Perform the cleaning {{{3 pathclean PATH pathclean MANPATH pathclean LD_LIBRARY_PATH export PATH MANPATH LD_LIBRARY_PATH ###################################################################### # Terminal setup {{{1 ###################################################################### # Set up the terminal (based on the TERM variable). Doesn't work properly # on solaris (esp. in CDE) 'cos tset is Bezerkeley :-( # TODO: I think there's a tset in /usr/ucb #if [[ "$_shell_is_interactive" == 1 && $OSTYPE != solaris* ]]; then if [[ "$_shell_is_interactive" == 1 ]]; then if whence tset &> /dev/null then eval $(SHELL=/bin/sh tset -r -s) fi fi # Automatic backspace/delete detection {{{2 # This nice piece of work is thanks to Peter Stephenson in zsh-users/8398. # It automatically sets the terminal driver up correctly to match the # first ^? or ^H we see. backward-delete-char-detect() { if (( #KEYS == ##\C-? )); then zmodload -i zsh/sched sched +00:00 "stty erase '^?'" elif (( #KEYS == ##\C-h )); then zmodload -i zsh/sched sched +00:00 "stty erase '^h'" fi zle -A .$WIDGET $WIDGET zle .$WIDGET } zle -N backward-delete-char backward-delete-char-detect zle -N vi-backward-delete-char backward-delete-char-detect ###################################################################### # Better X detection {{{1 ###################################################################### # Ok, we now have the PATH set, so we can be more ambitious (and therefore # reliable) about setting the _running_X variable. The general idea here # is that we want to check that the DISPLAY variable is not only set, but # set to a valid value. The easiest way to do this is to run an innocuous # X program, and check for exit status. Unfortunately, X applications have # RIDICULOUSLY long timeouts (e.g. 2 1/2 _MINUTES_ on solaris 8). We don't # want to have to wait that long if the DISPLAY variable points at a # non-existent host. So we ping first, which should be quicker. But then, # of course ping syntax has it's own issues... if (( _running_X )); then declare displayhost=${DISPLAY%:*} declare pingcmd case $OSTYPE in solaris*) pingcmd="ping $displayhost 1" ;; linux*) pingcmd="ping -c 1 $displayhost" ;; *) ;; # TODO insert other flavors of ping here esac # If displayhost is blank DISPLAY is probably ":0" so we shouldn't ping [[ -z "$displayhost" ]] && pingcmd="" (( _debugging )) && echo Thorough X tests.. if ! ( $pingcmd && xdpyinfo ) >/dev/null 2>&1; then # Either we can't ping the machine or xdpyinfo failed. Either way, # X is probably not going to work! (( _debugging )) && echo X seems to be broken! unset _running_X fi unset displayhost pingcmd fi ###################################################################### # Shell options {{{1 ###################################################################### # Get help like in bash unalias run-help &> /dev/null autoload run-help # General goodness setopt nobeep noclobber extended_glob transient_rprompt # For those old solaris implementations where it's not the default setopt autolist automenu # History related settings HISTFILE=~/.zsh_history HISTSIZE=5000 SAVEHIST=5000 setopt append_history hist_save_no_dups hist_ignore_dups hist_no_store setopt inc_append_history extended_history # This is handy, and needed for below setopt equals # This makes the prompt easier to understand below autoload -U colors colors # I can't stand not having the stat command. Unfortunatly, most UNIXs # don't have it. Zsh to the rescue! This module adds it as a builtin. zmodload zsh/stat &> /dev/null && alias stat='stat -Lors' # Ftp in a shell. Now THIS is a funky idea. Not sure if it's a good one, # but let's give it a try. #autoload -U zfinit #zfinit # This is a nice idea. Hope I can train myself to use it autoload -U zargs # Always wanted something like vared. This gives us it. autoload -U zed # If the helpfiles util has been run, use the resulting files. This makes # run-help better [[ -d ~/.zsh/helpfiles ]] && HELPDIR=~/.zsh/helpfiles # This is where I put my own functions [[ -d ~/.zsh/functions ]] && fpath=( ~/.zsh/functions $fpath ) # zstyle settings {{{2 # Completion function debugging settings. Comment out for noremal use zstyle ':completion:*' verbose yes zstyle ':completion:*:descriptions' format %B%d%b zstyle ':completion:*:messages' format %d zstyle ':completion:*:warnings' format 'No matches for: %d' zstyle ':completion:*' group-name '' # Customize the completion mechanism: # Make the kill command completion show me a list all the time so I can be # sure I'm killing the right command. This works even with 'kill # netscape' zstyle ':completion:*:*:kill:*' menu yes select zstyle ':completion:*:kill:*' force-list always #make expansion case insensitive # (lower to upper) #zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}' # (lower to upper AND upper to lower) #zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' ## case-insensitive,partial-word and then substring completion zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}' 'r:|[._-]=* r:|=*' \ 'l:|=* r:|=*' # The following lines were added by compinstall zstyle ':completion:*' list-colors '' zstyle ':completion:*' menu select=2 zstyle ':completion:*' select-prompt %SScrolling active: current selection at %p%s zstyle :compinstall filename '/.zshrc' autoload -U compinit compinit # End of lines added by compinstall # Key bindings {{{2 # Get things that way my fingers are pre-programmed bindkey -e # Fix ^U bindkey "\C-U" backward-kill-line # Love this in bash, lets get it here setopt interactive_comments bindkey "\M-#" pound-insert bindkey "\e#" pound-insert # not sure why zsh treats these differently # Make the menuselection turned on above even more interesting # Needs this, which would have been loaded when we performed our first # completion anyway zmodload -i zsh/complist bindkey -M menuselect '^o' accept-and-infer-next-history ###################################################################### # Prompt and other terminal settings {{{1 ###################################################################### # Set the prompt {{{2 # If it's an xterm then set the window title to reflect $PWD # If we're root, make the prompt red # Declare some variables to work with declare basicprompt='%(!..%n@)%m:%2~%(!.#.$)' declare settitle setcoloron setcoloroff # set up complicated prompt stuff based on terminal type case $TERM in dtterm*|*xterm*|screen*|linux) # These use the basic ANSI color sequences. #setcoloroff=$'%{\033[0m%}' # We can use the fg and bg associative arrays set up earlier by # the colors function setcoloroff="%{${fg[default]}${bg[default]}%}" if (( ! $UID )); then # we are root... make the prompt red #setcoloron=$'%{\033[0;31m%}' setcoloron="%{${fg[red]}%}" else # non-root, but I still like color setcoloron="%{${fg[blue]}%}" fi ;; *) # Nothing special here ;; esac PS1="${setcoloron}${basicprompt}${setcoloroff} " # Show me the exit status of the previous command if it's not 0 and the # time otherwise. Not sure about the time part. We'll see. #RPROMPT='%(0?.%D{%L:%M}.%B[exit status %?]%b)' RPROMPT='%(0?..%B[%?]%b)' if [[ $TERM == (*xterm*|dtterm*|screen*) ]]; then # These terminals have a title that can be set. # This magic came from one of the linux HOWTOs #PS1='\[\033]0;\h: \w\007\]\h:\W\$ ' case $TERM in *xterm*|dtterm*) xtermwindowtitle () { print -nP "\033]2;$@\a" } xtermicontitle () { print -nP "\033]1;$@\a" } xtermbothtitle () { print -nP "\033]0;$@\a" } preexec () { xtermwindowtitle "%m: ${(j:;:)${(f)1}}" xtermicontitle "%m: ${${(z)2}[(w)1]}" } precmd () { xtermwindowtitle '%m: %~' xtermicontitle '%n@%m: %2~' } ;; screen*) # for screen I want to set the window title to only # the hostname, and use the hardstatus for the path. # The escape sequences to set the hardstatus are the # same as those to set the title in xterm. # TODO haven't got to this yet #settitle='\[\033k\h\033\\\033]0;\w\007\]' screenhardstatus () { print -nP "\033]2;$@\a" } screenwindowtitle () { print -nP "\033k$@\033\\" } preexec () { # Since the window title will have the first word of the # command, and since my screen config has the hardstatus # following the window title, just show me the rest (up to # a point) screenhardstatus "${${(z)1}[2,10]}" if [[ -z "$STY" ]] then # Although the terminal type is screen, screen is not # directly controlling this shell, so we are probably # connected to a remote machine. Therefore, include # the hostname and the username. screenwindowtitle "%n@%m:${${(z)2}[1]}" else screenwindowtitle "${${(z)2}[1]}" fi } precmd () { if [[ -z "$STY" ]] then screenwindowtitle '%n@%m:%1~' else screenwindowtitle '%1~' fi screenhardstatus '%~' } ;; esac fi # Remove any exporting of PS1 since it looks hideous in other shells and # zsh will just re-read this file anyway typeset +x PS1 # set extglob back to how we found it unset basicprompt settitle setcoloron setcoloroff oldextglob ###################################################################### # X related settings {{{1 ###################################################################### # Perhaps these should be elsewhere, but I don't want to carry 23 files # with me when I move from machine to machine # if (( _running_X )); then # use .xmodmaprc if it's there if [[ -f ~/.xmodmaprc ]]; then (( _debugging )) && echo Loading keyboard mapping from ~/.xmodmaprc xmodmap ~/.xmodmaprc else # Fix "Help" key on a Sun {{{2 # Ok, the "Help" key on Sun keyboards drives me mental, since I'm # always hitting it instead of "Escape", so I remap it so that is # IS a second Escape key. if [[ $OSTYPE == solaris* ]]; then # if it's not already done if [ -n "$(xmodmap -pk | grep Help)" ]; then (( _debugging )) && echo "Remapping Help key to Escape!" xmodmap -e "keysym Help = Escape" > /dev/null 2>&1 fi fi # Turn off CAPSLOCK (I never use it deliberately) {{{2 # if it's not already done if [[ -n $(xmodmap | grep '^lock' | sed 's/^lock *//') ]]; then xmodmap -e "clear lock" (( _debugging )) && echo "Disabling Caps Lock" fi # }}} fi # End of no-modmaprc branch fi # End of X stuff ###################################################################### # Aliases {{{1 ###################################################################### # Less is MORE {{{2 unalias less &>/dev/null if whence less &> /dev/null then # without less, man is CRAPPO (esp. on Solaris!). export PAGER="less -si" else # We don't have less, but my fingers are ALWAYS typing it anyway. So # this prevents (even more) insanity. alias less=more fi # vi is good, but vim is better. {{{2 # I'm in the habit of typing 'vi' but if vim is there, I'd prefer that if whence vim &> /dev/null then if [[ $OSTYPE == solaris* && $TERM == xterm ]]; then # I'm probably using putty on a PC so to get colors going: alias vi='TERM=xtermc vim' else alias vi=vim fi export EDITOR==vim export VISUAL==vim else export EDITOR==vi export VISUAL==vi fi # If I have GNU ls then use color! {{{2 # TODO: This will break in the unfortunate circumstance when dircolors is # present, but the first ls in the path is not GNU ls. Should fix this. if whence dircolors &> /dev/null then eval $(dircolors --bourne-shell) alias ls='ls --color=auto ' fi # Disable globbing on some commands {{{2 # Some commands are better withou globbing. In many other shells it # doesn't matter, since a glob that doesn't match any files will be left # unchanged. In zsh this produces an error. It's safer that way, but # sometimes the glob commands are MEANT to be left alone. You can always # type the glob pattern, then use to expand it before pressing # enter. # Solaris 10 commands. These allow glob like matching on service names. whence svcs &> /dev/null && alias svcs='noglob svcs' whence svcadm &> /dev/null && alias svcadm='noglob svcadm' ###################################################################### # Functions {{{1 ###################################################################### # Solaris ps {{{2 # This function lets me use either the System V (-ef) or BSD (aux) style # switches on Solaris. It just runs the appropriate ps based on whether # there's a - there or not. # Been meaning to do this for a while, then came across one at # http://www.beaglebros.com/unix - so I stole it. I've since changed it, # but when do I stop crediting the inspiration? if [[ $OSTYPE == solaris* ]]; then function ps { if [ -n "$1" ]; then if [[ $1 == -* ]]; then /usr/bin/ps $@ else /usr/ucb/ps $@ fi else /usr/bin/ps $@ fi } fi # end solaris function # Improved exec {{{2 # This makes the exec command behave more like bash. By that I mean that I # can make some bone-headed typo, like 'exec zsg' and not kill my shell. # Only a first pass at this stage exec () { [[ -x =$1 ]] && builtin exec $@ } # sethostcolors {{{2 # Uses the ability of modern xterms to allow the colors to be set # dynamically from the client app. Try to choose a base background color # for each machine, then use slight variants on that color for the # background # TODO: make this integrate with the prompt color better to ensure # readability of the prompt. sethostcolors () { # Get a word to base the color choice on. local basename=${1:-$HOST} # Get the red, green, and blue components from the basename in some semi-reproducible fashion local hostnum=${${$(echo $basename | cksum )[0]}[2,4]} local red=${hostnum[1]} local green=${hostnum[2]} local blue=${hostnum[3]} local redcomp=$(( (red & 3) * 32 )) local greencomp=$(( (green & 3) * 32 )) local bluecomp=$(( (blue & 3) * 32 )) # Randomise them a bit local randomfactor=16 redcomp=$(( redcomp + ( ( RANDOM * 2 * randomfactor / 32768 ) - randomfactor ) )) greencomp=$(( greencomp + ( ( RANDOM * 2 * randomfactor / 32768 ) - randomfactor ) )) bluecomp=$(( bluecomp + ( ( RANDOM * 2 * randomfactor / 32768 ) - randomfactor ) )) if (( redcomp < 0 )); then redcomp=0; fi if (( greencomp < 0 )); then greencomp=0; fi if (( bluecomp < 0 )); then bluecomp=0; fi # Make sure we can read it! local fgcol if (( redcomp + 0.5 + greencomp * 0.86 + bluecomp * 0.18 < 150 )) then # the background is quite dark. Make the text white fgcol='#fff' else # make the text black fgcol='#000' fi redcomp=${(l:2::0:)$(( [##16] redcomp ))} greencomp=${(l:2::0:)$(( [##16] greencomp ))} bluecomp=${(l:2::0:)$(( [##16] bluecomp ))} local bgcol="rgb:$redcomp/$greencomp/$bluecomp" print -n "\e]10;$fgcol;$bgcol\a" } # And run it if it makes sense if [[ $TERM == *xterm ]] then sethostcolors fi ###################################################################### # Make the last call on the local settings file {{{1 ###################################################################### [ -e $HOME/.zshrc.local ] && . $HOME/.zshrc.local post ###################################################################### # Clean up {{{1 ###################################################################### unset _shell_is_interactive unset _running_X unset _debugging # Suppress a failure status if one of the above fails return 0