Zsh tricks you may not know
10 fresh zsh tricks you may not know...

You probably know M-. to insert the last argument of the previous line.
Sometimes, you want to insert a different argument.  There are a few options:
Use history expansion, e.g. !:-2 for the third word on the line before
(use TAB to expand it if you are not sure), or use M-. with a prefix argument:
M-2 M-.

Much nicer however is:

autoload -Uz copy-earlier-word
zle -N copy-earlier-word
bindkey "^[m" copy-earlier-word


Then, M-m will copy the last word of the current line, then the
second last word, etc.  But with M-. you can go back in lines too!
Thus:

% echo a b c
% echo 1 2 3
% echo <M-.><M-.><M-m>
% echo b


Man, I wish I knew that earlier!
Sometimes, you want to combine a few previously typed lines into one:

% vi foo.c
% gcc -o foo foo.c
% gdb --args foo -v


Repeating these three commands all the time gets annoying (even if you
know about C-o, it’s messy if you run other commands in between).

So, lets combine them into one line.  Are you moving your hand to
your mouse for copy&pasting?  Bzzzt!

We can use history expansion as well:

% !-3<TAB>; !-2<TAB>; !-1<TAB>
% vi foo.c; gcc -o foo foo.c; gdb --args foo -v


If you don’t see the numbers easily, you could do !vi<TAB> and so on.
I always wanted to use C-r for this, however, thus I defined:

autoload -Uz narrow-to-region
function _history-incremental-preserving-pattern-search-backward
{
  local state
  MARK=CURSOR  # magick, else multiple ^R don't work
  narrow-to-region -p "$LBUFFER${BUFFER:+>>}" -P "${BUFFER:+<<}$RBUFFER" -S state
  zle end-of-history
  zle history-incremental-pattern-search-backward
  narrow-to-region -R state
}
zle -N _history-incremental-preserving-pattern-search-backward
bindkey "^R" _history-incremental-preserving-pattern-search-backward
bindkey -M isearch "^R" history-incremental-pattern-search-backward
bindkey "^S" history-incremental-pattern-search-forward


Now C-r will work in a recursive way.  It looks like this:

% <C-r>vi<RET>;
% vi foo.c; <C-r>gcc
% vi foo.c; >>gcc -o foo foo.c<< <RET>;<C-r>gdb
% vi foo.c; gcc -o foo foo.c; >>gdb --args foo -v<< <RET>
% vi foo.c; gcc -o foo foo.c; gdb --args foo -v


Since C-r is not very useful if you already typed something, I feel
this redefinition is quite neat.
I have defined $WORDCHARS to exclude / since I usually don’t
want C-w or M-DEL to remove whole paths.  But sometimes I do, thus:

function _backward_kill_default_word() {
  WORDCHARS='*?_-.[]~=/&;!#$%^(){}<>' zle backward-kill-word
}
zle -N backward-kill-default-word _backward_kill_default_word
bindkey '\e=' backward-kill-default-word   # = is next to backspace

You probably heard of prompts like : juno ~/src/zsh ; (or just
; if you are hardcore-minimalist) which have the benefit that you
can copy the whole line in your terminal emulator and just paste it
to run it again:

: juno ~/src/zsh ; grep -r bindkey .
: juno ~/src/zsh ; : juno ~/src/zsh ; grep -r bindkey .


The : will nicely gobble up (almost) everything until the ;.
But the paste keeps repeating the prompt, which is ugly, and I
don’t like to have the : and ; in my prompt really.  That’s why
I had the IMHO ingenious idea to set my prompt to:

juno src/zsh% 


… which actually is:

juno src/zsh%␣


That is, the last character is a Unicode non-breaking
space (U+00A0).
Which will look like a plain space and behave like one.
Except that we can bindkey it to clear the input buffer:

nbsp=$'\u00A0'
bindkey -s $nbsp '^u'


Now, pasting the prompt will make it remove itself.  Great!
Growing up with bash, I never was fond of zsh’s menu-select widget.
I want TAB to complete as much as possible, and if I ever press TAB
again, it should display the completions and just let me type on.

However, sometimes, I’d like to use the menu-select widget, e.g.
if the files have prefixes that make selection hard (Maildirs, anyone?)
or consist of weird special chars only.

It took me quite long to figure out how to enable menu-select
for certain widgets only.  The main problem was that the
option NO_ALWAYS_LAST_PROMPT disables the menu widget.  Thus, we
have to unset it locally:

zle -C complete-menu menu-select _generic
_complete_menu() {
  setopt localoptions alwayslastprompt
  zle complete-menu
}
zle -N _complete_menu
bindkey '^F' _complete_menu
bindkey -M menuselect '^F' accept-and-infer-next-history
bindkey -M menuselect '/'  accept-and-infer-next-history
bindkey -M menuselect '^?' undo
bindkey -M menuselect ' ' accept-and-hold
bindkey -M menuselect '*' history-incremental-search-forward


The latter keybindings make it a convenient file selector and
browser (using / and DEL).
One thing that zsh lacks is the ability to start an interactive
shell session from inside a shell script (e.g. rc(1) can do that).
Sometimes you want to spawn a shell that runs a command, but is
interactive after the program finished (e.g. when launched
from urxvt or tmux).

Luckily, I found zshi.
I have defined it as
a script to allow
execution from everywhere.  It needs some support from the .zshrc:

if [[ $1 == eval ]]; then
  shift
  ICMD="$@"
  set --
  zle-line-init() {
    BUFFER="$ICMD"
    zle accept-line
    zle -D zle-line-init
  }
  zle -N zle-line-init
fi


That solution is a bit more complex, but it allows you to press Up
(or run r) to execute the command again.
If you are watching series, you want to get to the next episode:

% mplayer foobar-S01-E23.mkv
% <Up><C-x a>
% mplayer foobar-S01-E24.mkv


zsh includes incarg, but it only works if you put the cursor on
the number.  This solution increments the last number, anywhere,
and knows about zero padding:

_increase_number() {
  local -a match mbegin mend
  [[ $LBUFFER =~ '([0-9]+)[^0-9]*$' ]] &&
    LBUFFER[mbegin,mend]=$(printf %0${#match[1]}d $((10#$match+${NUMERIC:-1})))
}
zle -N increase-number _increase_number
bindkey '^Xa' increase-number
bindkey -s '^Xx' '^[-^Xa'

I use Emacs keybindings, but sometimes I wish I had vi’s command mode.
Luckily, it’s just a C-x C-v away in the default configuration!
Heck, you may even go ahead and do:

bindkey '^[' vi-cmd-mode


… and i will put you back into Emacs mode again.
A great anti-feature of history expansion is when it fails:

% a carefully constructed command line !?gcc !?vim !?quux
zsh: no such event: gcc !<Up>
% a carefully constructed command line im !?quux


And your history expanders are gone.  Not so with this snippet:

function _recover_line_or_else() {
  if [[ -z $BUFFER && $CONTEXT = start && $zsh_eval_context = shfunc
        && -n $ZLE_LINE_ABORTED
        && $ZLE_LINE_ABORTED != $history[$((HISTCMD-1))] ]]; then
    LBUFFER+=$ZLE_LINE_ABORTED
    unset ZLE_LINE_ABORTED
  else
    zle .$WIDGET
  fi
}
zle -N up-line-or-history _recover_line_or_else
function _zle_line_finish() {
  ZLE_LINE_ABORTED=$BUFFER
}
zle -N zle-line-finish _zle_line_finish


This will keep the last line in all cases, allowing you to fix it:

% a carefully constructed command line !?gcc !?vim !?quux
zsh: no such event: gcc !<Up>
% a carefully constructed command line !?gcc !?vim !?quux

Renaming long file names sucks.  Many use graphical file managers for it.
I use imv (interactive mv):

imv() {
  local src dst
  for src; do
    [[ -e $src ]] || { print -u2 "$src does not exist"; continue }
    dst=$src
    vared dst
    [[ $src != $dst ]] && mkdir -p $dst:h && mv -n $src $dst
  done
}


It will even create the target directory if it doesn’t exist.
Bonus item: This is more for fun than serious use.  An
updating clock in your prompt:

_prompt_and_resched() { sched +1 _prompt_and_resched; zle && zle reset-prompt }
_prompt_and_resched
PS1="%D{%H:%M:%S} $PS1"


As usual, these things and many others are integrated in my .zshrc.  Enjoy your Z shell!

NP: Silly—EKG
