The zsh line editor is probably the first part of the shell you ever used, when you started typing in commands. Even the most basic shells, such as sh, provide some kind of editing capability, although in that case probably just what the system itself does --- enter characters, delete the last character, delete the entire line. Most shells you're likely to use nowadays do quite a lot more. With zsh you can even extend the set of editor commands using shell functions.
The zsh line editor is usually abbreviated to `zle'. Normally it fires itself up for any interative shell; you don't have to do anything special until you decide you need to change its behaviour. If everything looks OK and you're not interested in how zle is started up, skip to the next subsection.
Nowadays, zle lives in its own loadable module, zsh/zle
, which saves
all the overhead of having an editor if the shell isn't interactive.
However, you normally won't need to worry about that; I'll say more
about modules in chapter 7, but the shell knows when you
need zle and gives you it automatically. Usually the module is in a
directory with a name like `/usr/local/lib/zsh/4.0.4/zsh/zle.so
',
where the `4.0.4
' is the shell's version number, the same as the
value of the parameter $ZSH_VERSION
, and everything after that apart
from the suffix `.so
' is the module name. The suffix may be `.sl
'
(HP-UX) or `.dll
' (Cygwin), but `.so
' is by far the most common
form. It differs because zsh keeps the same convention for dynamically
loadable libraries, or `shared objects' in UNIX-speak, as the operating
system.
If the shell is badly installed, you sometimes see error
messages that it, or a command such as `bindkey', couldn't be loaded.
That means the shell couldn't find `zsh/zle
' anywhere in the module
load path, the array $module_path
. Then you need to complain to
your system administrator. If you've just compiled zsh and are having
this problem, it's because you have to install the modules before you
run the shell, even if the shell itself isn't installed. You can do
that by saying `make install.modules
'. Then the compiled zsh should
run from where it is.
Note that unlike bash's line editor, readline, which is an entirely
separate library, zle is an integral part of the shell. Hence you
configure it by sticking commands in your .zshrc
--- as it's
only useful for an interactive shell, only /etc/zshrc
and .zshrc
make sense for this purpose.
One tip if you're looking at the zsh manual using info, either with the
command of that name or \C-h i
within Emacs, which I find
the most convenient way: the entry for zle is called `Zsh Line Editor',
in full, not just `Zle'. Have fun looking for `Shell Builtin Commands'
(not `Builtins') while you're at it.
As with any editor later than ed
, you can move around the line and
change it using various `keystrokes', in other words one or more sets of
keys you type at once. For example, the keystroke to move back a word
is (maybe) ESC b
. This means you first hit the escape key; nothing
happens yet. Then you hit `b', and the cursor instantly jumps back to
the start of the word. (I'll have more to say on what zle thinks is a
`word' --- it's not necessarily the same as what the rest of the shell
thinks is a word.)
It will probably help if I introduce the shell's way of describing
keystrokes right away; then when you need to enter them you can just
copy them straight in. The escape key is `\e
', so that keystroke
would be `\eb
'. Other common keystrokes include holding down the
control key, probably near the bottom left of the keyboard, and typing
another key at the same time. The simplest way of indicate a control
key is just to put `^
' in front; so for example `^x^x
' means
hold down control, and press `x' twice with control still held down.
It has exactly the same effect as `^X^X
'. (You may find each time
you do that it takes you to the start of the line and back to where you
were.)
I've already introduced the weasel word `maybe' to try to avoid lying. This is because actually zle has two modes of operation, one (the default) like Emacs, the other like vi. If you don't know either of those venerable UNIX editors, I suggest you stick to Emacs mode, since it tends to interfere a little less with what you're doing, and furthermore completion is a little easier. Completion is an offshoot of zle behaviour which is described in chapter 6 (which, you will notice, is longer than this one).
If you normally use vi, you may have one or both of the environment
variables $EDITOR
or $VISUAL
set to `vi
'. (It all works the
same way if you use `vim
' instead, or any editor that happens to
contain `vi
' such as `elvis
'.) In that case, zle will start up
in its `vi
' mode, where the keystrokes are rather different.
That's why you might have found that `\eb
' didn't do what I said,
even though you had made no attempt to configure zle. You can make zle
always use either emacs or vi mode by putting either
bindkey -eor
bindkey -vin your
.zshrc
. This is just one of many uses of bindkey
.
If you're not familiar with this use of the word `bind', it just means
`make a keystroke execute a particular editor command'. Commands have
long-winded names with hyphens which give you quite a good description
of what they do, such as `backward-delete-char
'. Normal keys which
correspond to printable characters are usually `bound to
self-insert
', a perverse way of saying they do what you expect
and show up the character you typed. However, you can actually bind
them to something else. In vi command mode, this is perfectly normal.
Actually, if you use a windowing system, you might want to say
`bindkey -me
', which binds a whole set of `meta' keys. In X
Windows, one of the keys on your keyboard, possibly ALT
, may be
designated a `meta' key which has a special effect similar to the
control key. Bindings with the meta key held down are described a bit
like they are in Emacs, `\M-b
'. (You can specify control keys
similarly, in fact, like `\C-x
', but `^x
' is shorter.) Using
the `-m
' option to bindkey
tells zsh that wherever it binds an
escape sequence like `\eb
', it should also bind the corresponding
meta sequence like `\M-b
'. Emacs always ties these together, but
zsh doesn't --- you can rebind them separately. and if you want both
sequences to be bound to a new command, you have to bind them both
explicitly.
You need to be careful with `bindkey -m
', however; the shell can't
tell whether you are typing a character with the top bit set, or
executing a command. This is likely to become worse as the UTF-8
encoding for characters becomes more popular, since a non-ASCII
character then consists of a whole series of bytes with the top bit
set.
If you are interested in binding function keys, you may already have
found the key sequences they send apparently don't make any sense; see
the section below for more information. This will
introduce the function called zkbd
which can make the process less
painful. The function also helps with `meta' and `ALT' keys.
I'm going to concentrate on Emacs mode for various reasons: firstly, because I use it myself; secondly, because the most likely reason for you using vi mode is that you are already familiar with vi and don't need to be told how it works; thirdly, because most of the commands are the same in both modes, just bound differently; and finally because if you don't already know vi, you will quite likely find vi editing mode rather counterintuitive and difficult to use.
However, here are a few remarks on it just to get it out of the way. Like the real vi editor, there are two basic modes, insert mode, where you type in text, and command mode, where the same keystrokes which insert characters are bound instead to editing commands. Unlike the real vi, the line editor starts in insert mode on every new command you edit. This means you can often simply type a line up to the `return' at the end and forget you are in vi mode at all.
To enter command mode, you hit `escape', again just like normal vi. At
this point you are in the magic world of vi commands, where typing an
ordinary character can have any effect whatsoever. However, the
bindings are similar to normal vi, so `h
' and `l
' move left and
right. When you want to insert more text, you can use any of the normal
vi commands which allow you to do that, such as `i
' (vi-insert
) or
`a
' (vi-add-next
).
Apart from the separate command and insert modes and the completely
different set of key bindings, there is no basic difference between
Emacs mode and vi mode. You can bind keys in both the vi modes --- they
don't have to correspond to self-insert
in insert mode. Below,
I'll describe `keymaps', a complete set of descriptions for what all the
keys will do (less impressive than it sounds, since a lot of keys may be
set to `undefined-key
, which means they don't do anything useful),
and you will see how to change the behaviour in both modes.
If you know Emacs or vi, you will very soon find out how to do simple
commands like moving the cursor, going up and down the history list, and
deleting and copying words. If you don't, you should read the zshzle
manual page for a concise description of what it can do. Here is a
summary for Emacs mode.
You can move forwards and backwards along the line using the cursor keys. The are a variety of different conventions as to what keystrokes the cursor keys produce. You might naively expect that pressing, say, cursor right, sends a signal along the lines of `cursor right' to the application. Unfortunately, there is no such character in the ASCII character set, so programmes which read input as a string of characters like zsh have to be given an arbitrary string of characters. (It's different for programmes which understand other forms of input, like windowing systems.)
The two most common conventions for cursor keys are where the up key
sends `\e[A
' and the other three the same with B
, C
and
D
at the end, and the convention where the `[
' is replaced by an
`O
' (uppercase letter `O'). In old versions of zsh, the only
convention supported was the first of those two. The second, and any
other convention, were not supported at all and you had to bind the keys
yourself. This was done by something like:
bindkey "\eOA" up-line-or-history bindkey "\eOB" down-line-or-history bindkey "\eOC" forward-char bindkey "\eOD" backward-charThe shell tries harder now, and provided your system has the correct information about your terminal (zsh uses an old system called `termcap' which has largely been superseded by another called `terminfo') you should be lucky. If the shell thinks your keys are too perverse --- in particular, if the keystroke it wants to bind the function too is already defined by zsh --- you will still have to do it by hand. The list above should serve as a template.
Instead of the cursor keys, traditional Emacs keys are available: ^b
and ^f
for backward and forward, ^p
and ^n
for previous line
and next line, so you can continue even if the cursor keys don't work.
Moving longer distances is done by \eb
and \ef
for a word
backwards or forwards (or, as you saw, \M-b
and \M-f
), and
^a
and ^e
for the start and the end of the line. That just
about exhausts the ones you will use the most frequently.
For deleting, backspace or the delete key will delete backwards. There
is an eternal battle over these keys owing to the fact that on PC
keyboards the key at the top left of the central keyboard section is
`backspace' which is the character ^h
, while on traditional UNIX
keyboards it is `delete' which is the character 127, often written as
^?
(which zsh also understands). When you are in the system's own
primitive line editing mode, as with sh (unless your sh is really bash),
only one of these is `bound', although it's not really a key binding,
it's a translation made by the system's terminal driver, and it's
usually the wrong one. Hence you often find the system prints `^h
'
on the screen when you want it to delete. You can change the key using
stty erase '^h'but zsh protects you from all that --- both
^h
(backspace) and
^?
(delete) will delete backwards one character. Note, by the way,
that zsh doesn't understand smart names for any keystrokes -- if you try
to bind a key called `backspace
' zsh will bind a command to that
sequence of characters, not a key of that name. See comments on
`bindkey -s
' for when something like this might even be useful.
To confuse matters further, the key often marked as `Delete' on a 101- or
102-key PC keyboard in the group of 6 above the cursor keys is
completely different again, and probably doesn't send either of those
sequences. On my keyboard it sends the sequence `\e[3~
'. I find it
convenient to have this delete the next charater, which is its tradional
role in the PC world, which I do by
bindkey '\e[3~' delete-charHowever, the tradtional Emacs way of deleting the next character is to use `
^d
', which zsh binds for you by default. If you look at the
binding, which you can do by not giving bindkey an editor command to
bind,
% bindkey '^d' delete-char-or-listyou'll see it doesn't quite do what I suggested. The `
-or-list
'
part is for completion, and you'll find out about it in the next
chapter. The first shell I know of to have this odd combination was
tcsh.
Since I enjoy confusion, I might as well point out that usually ^d
has another use, which is to tell the terminal driver you've reached the
end of a file. In the case of, say, a file on a disk, the system knows
this by itself, but if you are supplying a stream of characters, the
only way of telling it is to send a special character. The default is
usually ^d
. You will notice that if you type `^d
' at the start
of the line, you see the message
zsh: use 'exit' to exit.That's because zsh recognises the
^d
as end-of-file in that
position. By default the shell warns you; you can turn this off by
setting the option IGNORE_EOF
. You can tell the system you don't
ever want to send an end-of-file in this way with stty
, again: the
following are equivalent in Linux but your system way want one or the
other:
stty eof '^-' stty eof undefRemember that
stty
is not part of the shell; it's a way of
controlling the state of the system's terminal driver. This means it
survives as long as the terminal or terminal window is still connected,
even if you start a new shell or exit one that isn't the login shell.
By the way, if you need to refer to a character by its number, the
easiest way is probably to use the syntax `\x??
', where the `??
'
are the two hex digits for the key. In the case of delete, it is
`\x7f
'. You can confirm this by:
% bindkey '\x7f' "^?" backward-delete-char
You can delete larger areas with `\ed
' to delete the next word and
`\e^h
' or `\e^?
' (escape followed by delete backwards) to delete
the previous word. `^u
' usually removes the entire line, before and
after the cursor --- this is not like Emacs, where ^u
introduces
digit arguments as I will describe in the next subsection. It is,
however, like another of those primitive editing commands the terminal
driver itself provides, this one being known to stty
as `kill
'.
The most common use of this outside zsh is for deleting your password
when you login, when you know you've typed it wrong but can't see how
many !@?*! characters you've typed, and maybe can't rely on the terminal
agreeing with you as to which of ^h
or ^?
will delete a single
one.
Strictly speaking, all the keystrokes in the previous paragraph perform
a `kill' (zsh-speak, not to be confused with the stty
`kill') rather
than a `delete' (or deletion, as we used to say when we had a distinct
between nouning and verbing). The difference is the same as in Emacs
--- `killed' text is saved for later `yanking' back somewhere else,
which you do with the ^y
key, whereas `deleted' text as with ^?
and ^d
is gone forever. This is what everyone not brought up under
Emacs calls `cut' and `paste' (although since Emacs dates back
to the seventies, it could be everyone else that's wrong). Another
feature borrowed from Emacs is that if you do multiple `kills' without
any other editing in between, the killed text is joined together and you
can yank it all back in one go. I will say more when I talk about point
and mark (another Emacs idea).
Actually, even deleted text isn't gone forever: zsh has an Emacs-like
editing history, and you can undo the previous commands on the line.
This is usually bound to ^xu
and ^x^u
, and there is shorter
binding which is described rather confusingly as `^_
' ---
confusingly, because on all not-completely-spaced-out keyboards I've
ever used you actually generate that sequence by holding down control
and pressing the `/
' key. Zsh doesn't use ^z
by default and, if
you are used to Windows, that is another suitable binding for undo
.
Zsh scores in one way over Emacs --- it also has `redo
', not
bound by default. This means that if you undo to much, you can put back
what you just undid by repeatedly using the redo
command.
Unlike completion, zle
doesn't have many options associated with it;
most of the control is done by key bindings and builtin commands. Only
two are really useful; both control beeps. The option beep
can be
unset to tell the shell never to make a noise on an error; the option
histbeep
can be unset to disable beeps only in the case of trying to
go back before the first or forward after the last history entry.
The not-very-useful options are zle
and singlelinezle
. The
former controls whether zle is active at all and isn't that useful
because it's usually on automatically whenever you need it, in other
words in interative shells, and off whenever you don't. It's sometimes
useful to test via `[[ -o zle ]]
', however; this lets you make a
function do something cleverer in an interative shell.
The option singlelinezle
restricts editing to one line; if it gets
too long, it will be truncated and a `$
' printed where the missing
bits are. It's only there for compatibility with ksh and as a
safeguard if your terminal is really screwed up, though even in that
case zsh tries to guess whether everything it needs is available.
Other functions that affect zle include the history functions. These
were described back in chapter 2; once you've set it off,
searching through the history works basically the same way in zle as
with the `!
' history commands.
The `minibuffer' is yet another Emacs concept; it is a prompt that
appears just under the command line for you to enter some edit required
by the editor itself. Usually, it comes and goes as it pleases and you
don't need to think about it. The most common uses are entering text
for searches, and entering a command which isn't bound to a string.
That's yet another Emacs feature: \ex
prompts you to enter the name
of a command. Luckily, since the names tend to be rather long,
completion is available. So typing `echo foo<ESC>xba<TAB>w<TAB>
' ends
up with:
% echo foo execute: backward-wordand hitting return executes that function, taking you to the start of the
foo
; you might be able to think of easier ways of doing that.
This does provide a way of running commands you don't often use.
(I hope my notation isn't too confusing. I write things like <TAB>
when I'm showing a single character you hit, to make it stand out from
the surrounding text. However, when I'm not showing text being entered,
I would write that as `\t
', which is how you would enter the
character into a key sequence to be bound, or a string to be printed.)
The minibuffer only handles a very limited set of editing commands.
Typing one it doesn't understand usually exits whatever you were trying
to do with the minibuffer, then executes the keystroke. However, in
this particular case, it won't let you exit until you have finished
typing a command; your only other option is to abort. The usual zle
abort character is ^g
, `send-break
'. This is different from the
more drastic ^c
, which sends the shell itself an interrupt signal.
Quite often they have the same effect in zle, however. (You'll notice
^c
is actually `bound to undefined-key
', in other words zle
doesn't consider it does anything. However, the terminal driver
probably causes it to send an interrupt, and zle does respond to that.)
Another feature useful with rare commands is `where-is
'. Surprise!
it's not bound by default, so typing `<ESC>xwhere-is
' is then the way
of runing it. Then you type another editor command at the `Where
is:
' prompt, and the shell will tell you what keystrokes, if any, are
bound to it. You can also simply use grep
on the output of
bindkey
, which, with no arguments, lists all bindings.
Many commands can be repeated by giving them a numeric prefix or digit
argument. For example, at the end of a long line of text, type
`<ESC>4<ESC>b
'. The `<ESC>b
' on its own would take you one word
backwards. The `<ESC>4
' passes it the number four and it moves four
words backwards. Generally speaking, this works any time it make sense
to repeat a command. It works for self-insert
, too, just repeatedly
inserting the character. If it doesn't work, the prefix argument is
simply ignored.
You can build up long or negative arguments by repeating both the \e
and the digit or `-
' after it; for example, `<ESC>-<ESC>1<ESC>0
'
specifies minus ten. It varies from command to command how useful
negative numbers are, but they generally switch from backwards to
forwards or similar: `<ESC>-<ESC>4<ESC>\f
' is a pointless way of
executing the same as `<ESC>4<ESC>b
'.
The shell also has Emacs' `universal-argument
' feature, but it's not
bound by default --- in Emacs it is \C-u
, but as we've seen that's
already in use. This is an alternative to all those escapes. If you
bind the command to a keystroke (it's absolutely pointless as a shortcut
otherwise), and type that key, then an option minus followed by any
digits are remembered as a prefix. The next keystroke which is not one
of those is then executed as a command, with the prefix formed by the
number typed after universal-argument
.
For example, on my keyboard, the key F12
sends the key sequence
`\e[[24~
' --- see below for how to find out what functions keys
send. Hence I use
bindkey '\e[[24~' universal-argumentThen if I hit the characters
F12
, 4
, 0
, a
, a row of
forty `a's is inserted onto the command line. I'm not claiming this
example is particularly useful.
Words are handled a bit differently in zsh from the way they are in most editors. First, there is a difference between what Emacs mode and vi mode consider words. That is to say, there is a difference between the functions bound by default in those modes; you can use the same functions in either mode by rebinding the keys.
In both vi and Emacs modes, the same logic about words applies whether you are moving forward or backward a number of words, or deleting or killing them; the same amount of text is removed when killing as the cursor would move in the other case.
In vi mode, words are basically the same as what vi considers words to
be: a sequence of alphanumeric characters together with underscores ---
essentially, characters that can occur in identifiers, and in fact
that's how zsh internally recognises vi `word characters'. There is one
slight oddity about vi's wordwise behaviour, however, which you can
easily see if you type `/a/filename/path/
', leave insert mode with
ESC
, and use `w
' or `b
' to go forward or backward by words
over it. It alternates between moving over the characters in a word,
and the characters in the separator `/
'.
In Emacs, however, it is done a bit differently. The vi `word
characters' are always considered parts of a word, but there is a
parameter $WORDCHARS
which gives a string of characters which are
also part of a word. This is perhaps opposite to what you would
expect; given that alphanumerics are always part of a word, you might
expect there to be a parameter to which you add characters you don't
want to be part of a word. But it's not like that.
Also unlike vi, jumping a word always means jumping to a word character at the start of a word. There is no extra `turn' used up in jumping over the non-word characters.
The default value for $WORDCHARS
is
*?_-.[]~=/&;!#$%^(){}<>i.e. pretty much everything and the kitchen sink. Usually, therefore, you will want to remove characters which you don't want to be considered parts of words; `
-
', `/
' and `.
' are particularly likely
possibilities. If you want to remove individual characters, you can do
it with some pattern matching trickery (next chapter):
% WORDCHARS=${WORDCHARS//[&.;]} % print $WORDCHARS *?_-[]~=/!#$%^(){}<>shows that the operation has removed those three characters in the group, i.e. `
&
', `.
' and `;
', from $WORDCHARS
. The
`//
' indicates a global substitution: any of the characters in the
square brackets is replaced by nothing.
Many other line editors, even those like readline
with Emacs
bindings, behave as if only identifier characters were part of a word,
i.e. as if $WORDCHARS
was empty. This is very easy to do with a
zle shell function. Recent versions of zsh supply the functions
`bash-forward-word
', `bash-kill-word
', and a set of other
similar ones, for you to bind to keys in order to have that behaviour.
Other behaviours are also possible by writing functions; for example,
you can jump over real shell words (i.e. individual command arguments)
by using some more substitution trickery, or you can consider only
space-delimited words (though that's not so far from what you get with
$WORDCHARS
by adding `"`'\@
').
Another useful concept from Emacs is that of regions and marks. In
Emacs-speak `point' is where the cursor is and `mark' is somewhere where
you leave a mark to come back to later. The command to set the mark at
the current point is `^@
' as in Emacs, a hieroglyphic which usually
means holding down the control key and pressing the space key. On some
systems, such as the limited version of telnet
provided with a
well-known non-UNIX-based windowing system, you can't send this
sequence, and you need to bind a different sequence to
set-mark-command
. One possibility is `\e
' (escape followed by
space), as in MicroEMACS. (Some X Windows configurations
don't allow ^@
to work in an xterm, either, though that is usually
fixable.)
To continue with Emacs language, the region between point and mark is
described simply as `the region'. In zsh, you can't have this
highlighted, as you might be used to with editors running directly under
windowing systems, so the easiest way to find out the ends of the region
is with ^x^x
, exchange-point-and-mark
, which I mentioned before
--- mark, by default, is left at the beginning of the line, hence the
behaviour you saw above.
Various editing commands --- usually those with `region
' in the name
--- operate on this. The most usual are those which kill or copy the
region. Annoyingly, kill-region
isn't bound --- in Emacs, it's
^w
, but zsh follows the tradition of having that bound to
backward-kill-word
, even though that's also available as the
traditional Emacs binding \e^?
. So it's probably useful to rebind
it. To copy the region, the usual binding `\ew
' works.
You then `yank' back the text copied or killed at another point with
`^y
'. The shell implements the `kill ring' feature, which means if
you perform a yank, then type `<ESC>y
' (yank-pop
) repeatedly, the
shell cycles back through previously killed or copied text, so that you
have more available than just the last one.
Zle has access to the lines saved in the shell's history, as described in `Setting up history' in chapter 2. There are essentially three ways of retrieving bits of the history: moving back through it line by line, searching back for a matching line, and extracting individual words from the history. In fact, the first two are pretty similar, and there are hybrid commands which allow you to move back step by step but still matching only particular lines.
The simplest behaviour is what you get with the normal cursor key
bindings, `up-line-or-history
' and `down-line-or-history
'.
If you are in text which fits into a single line (which may be a
continuation line, i.e. it has a new prompt in the form given by
$PS2
at the start of the line), this replaces the entire line with
the line before or after in the history. The history is not circular,
it has a beginning and an end. The beginning is the first line still
remembered by the shell (i.e. $HISTSIZE
lines back, taking into
account that the actual number of lines present will be
modified by the effects of any special history options you have set to
remove unwanted lines); the end is the line you are typing. You can use
\e<
and \e>
to go to the first and last line in the history.
The last phrase sounds trivial but isn't quite. Type `echo This is the
last line
', go back a few lines with the up arrow, and then back down
to the end, and you will see what I mean --- the shell has remembered
the line you were typing, even though it hadn't been entered, so you can
scroll up and down the history and still come back to it.
Of course, you can edit any of the earlier history lines, and hit `return' so that they are executed --- that's the whole point of being able to scroll back through the history. What is maybe not so obvious is that the shell will remember changes you make to these lines, too, until you hit `return'.
For example, type `echo this is the last line
' at a new shell
prompt, but don't hit return. Now hit the up arrow once, and edit the
previous line to say `echo this is the previous line
'. If you
scroll down and up, you will see that the shell has kept both of those
lines. When you decide which one to use and hit return, that line is
executed and added to the end of the history, and any changes to
previous lines in the history are forgotten.
Sometimes you don't want to add a new line to history, instead
re-execute a whole series of earlier commands one by one. This can be
done with ^o
, accept-line-and-down-history
. When you
hit ^o
on a line in the history, that is executed, and the line
after it in the history is shown. So you just need to keep hitting it
to keep executing the commands.
There are two other similar commands I don't use as much,
infer-next-history
, bound to ^x^n
, and
accept-and-infer-next-history
, not bound by default. `Inferring'
the next history means that the shell looks at what is in the current
line, whatever its provenance --- you might just have typed it, for
example --- and looks back in the history for a matching line; the
`inferred' next history line is the one following that line. In the
first case, you are simply shown that line; in the second case, the
current line is executed first, then you are shown the inferred line.
Feel free to drop me a line if you find this is the best thing since
sliced bread.
One slight confusion about the history is that it can be hard to
remember quite where you are in it, for example, if you were editing a
line and had to scroll back to look for something else. In cases like
this, \e>
is your friend, as it takes you the last line. Also,
whenever you hit return, you are guaranteed to be at the end of the
history, even if you were editing a line some back in the history,
unlike certain other systems (though accept-line-and-down-history
can emulate those). So it's usually not too hard to stay unconfused
about what you're editing.
Zsh has the commands you would expect to search through the history, i.e. where you hit a search key and then type in the words to search for. However, it also has other features, probably more used by the zsh community, where the search is based on some feature of the current line, in particular the first word or the line up to the cursor position. These typically enable you to search backwards more quickly, since you don't need to tell the shell what you are looking for.
Ordinary searching
The standard search commands, by which I mean the ones your are probably most familiar with from ordinary text editors (if either Emacs or vi can be so called), are designed to make Emacs and vi users feel at home.
In Emacs mode, you have incremental search: ^r
to search backwards
--- this is usually what you want, since you usually start at the end
--- and ^s
to search forwards. Note that ^s
is another
keystroke which is often intercepted by the terminal driver; in this
case, it usually freezes output to the terminal until you type ^q
to turn it back on. If you don't like this, you can either use
`stty stop
' and `stty start
' to change the characters, or simply
`unsetopt flowcontrol
' to turn that feature off altogether.
However, the command bound to ^s
,
history-incremental-search-forward
, is also bound to ^xs
, so you
can use that instead.
As in Emacs, for each character that you type, incremental search takes you to the nearest history entry that matches all the characters, until the match fails. Typing the search keystroke again at any point takes you to the next match for the characters in the minibuffer.
In vi command mode, the keystrokes available by default are the familiar
`/
' and `?
'. There are various differences from vi, however.
First of all, it is `/
' that searches backwards --- this is the one
you will use more often. Secondly, you can't search for regular
expressions (patterns); the only exception is that the character `^
'
at the start anchors the search to the start of a line. Everything else
is just a plain string.
The other two standard vi search keystrokes are also present: `n
'
searches for the next match of the current string, and `N
' does the
same but reverses the direction of the search.
Search for the first word
The next sort of search is probably the most commonly used, but is only
bound in Emacs mode: \ep
and \en
search forward or backward for
the next history line with the same first word as the current line. So
often to reuse a command you will type just the command name itself, and
hit \ep
until the command line you want appears. These commands are
called simply `history-search-backward
' and
`history-search-forward
'; the name doesn't really describe the
function all that well.
Prefix searching
Finally, you can search backwards for a line which has the entire
starting point up to the cursor position the same as the current line.
This gives you a little more control than
history-search-
direction. The corresponding commands,
history-beginning-search-backward
and
history-beginning-search-forward
, are not bound by default. I find
it useful to have them bound to ^xp
and ^xn
since this is
similar to the initial-word searches:
bindkey '^xp' history-beginning-search-backward bindkey '^xn' history-beginning-search-forward
Other search commands based on functions
Search commands are one of the types most often customised by writing
shell functions. Some are supplied with the latest versions of the
shell; have a look in the ZLE section of the zshcontrib
manual page.
You should find the functions themselves installed somewhere in your
$fpath
, typically
/usr/local/share/zsh/$ZSH_VERSION/functionsor in the subdirectory
Zle
of that directory, depending how your
version of zsh was installed. If the shell was pre-installed, the most
likely location is
/usr/share/zsh/$ZSH_VERSION/functions/ZleThese should guide you in writing your own.
One point to note is that when called from a function the
history-search-
direction and
history-incremental-search-
direction can take a string argument
specifying what to search for. In the first case, this is just a one
off search, while in the second, you remain in incremental search and
the string is used to prime the minibuffer, so you can edit it. I will
later say much more about writing zle functions, but calling a search
command from a user-defined editing function is as simple as:
zle history-search-backward search-stringand you can test the return status to see if the search succeeded.
Sometimes instead of editing a previous line you just want to extract a
word from it into the current line. This is particularly easy if the
word is the last on the line, and the line isn't far back in the
history: just hit \e.
repeatedly, and the shell will cycle through
the last word on previous lines. You can give this a prefix argument to
pick the Nth from last word on the line just above the last line
you picked a word from. As you can tell from the description, this gets
a little hairy; version 4.1 of the shell will probably provide a
slightly more flexible version.
Although it's strictly not to do with the history, you can copy the
previous word on the current line with copy-prev-word
, which for
some reason is bound to \e^_
, escape followed (probably) by control
and slash. I have this bound to \e=
instead (in some versions of
ksh that key sequence is taken by the equivalent of list-choices
).
This copies words delimited by whitespace, but you can copy what the
shell would see as the previous complete argument by using
copy-prev-shell-word
instead. This isn't bound by default, as it is
newer than the other one, but it is arguably more useful.
Sometimes you want to complete a word from the history; this is possible using the completion system, described in the next chapter.
There are two topics to cover under the heading of key bindings: first,
how to bind keys themselves, and secondly, keymaps and how to use them.
Manipulating both key bindings and keymaps is done with the bindkey
command. The first topic is the more immediately useful, so I'll start
with that.
You've already seen basic use of bindkey
to link editing commands to
a particular sequence of keys. You've seen the shorthand for naming
keys, with \e
being escape and ^x
the character x
pressed
while the control key is held down. I even said something about `meta'
key bindings.
Let me now launch into a little more detail. When you bind a key
sequence, which you do with `bindkey
key-sequence
editor-command', the key-sequence can consist of as many
characters as you like. It doesn't even matter (much) if some initial
set of the key sequence is already bound. For example, you can do,
bindkey '\eA' backward-word bindkey '\eAA' beginning-of-lineHere, I'll follow the shell documentation in referring to
\eA
as the
prefix of \eAA
.
This introduces two points. First, note that the binding for \eA
is
distinct from that for \ea
; you will see the latter still does
accept-and-hold
(in Emacs mode), which means it excutes the current
line, then gives it back to you for editing --- useful for doing a lot
of quite similar tasks. Meanwhile, \eA
takes you back a word.
This case sensitivity only applies to alphabetic characters which are a
complete key in their own right, not to those characters with the
control key held down --- ^x
and ^X
are identical. (You may
have found there are ways to bind both separately in Emacs when running
under a windowing system, since the windowing system can tell Emacs if
the shift key is held down with the others; it's not that simple if you
are using an ordinary terminal.)
If you entered both those bindkey
commands, you may notice that
there is a short pause before \eA
takes effect. That's because it's
waiting to see if you type another A
. If you do type the extra
A
during that pause, you will be taken to the beginning of the line
instead. That pause is how the shell decides whether to execute the
prefix on its own.
The time it waits is configurable and is given by the parameter
$KEYTIMEOUT
, which is the delay in hundredths of a second. The
default is 40, i.e. four tenths of a second. Its use is usually down
to personal preference; if you don't type very fast, you might want to
increase it, at the expense of a longer delay when you are waiting for
the prefix to be executed. If you are editing on a remote machine over
a very slow link, you also may need to increase it to be able to get
full key sequences which have such a prefix to work at all.
However, the shell only has this ambivalent behaviour if a prefix is
bound in its own right; if the initial key or keys don't mean anything
on their own, it will wait as long as you like for you to type a full
sequence which is bound. This is by far the normal case. The only
common example of a separately bound prefix is in vi insert mode, where
<ESC>
takes you back to command mode, while there may be other bindings
starting with \e
such as the cursor keys. We'll see below how you
can remove those if they offend your sense of vi purity. (Don't laugh,
vi users are strange.)
Note that if the whole sequence is not bound, after all, the shell will
abort as soon as it reads a complete key sequence which is no longer a
prefix. For example, if you type `\e[
', the chances are the shell
is waiting for more, but if you add a `/
', say, it will probably
decide you are being silly and abort. The next key you type then starts
a new sequence.
If you want to remove a key binding, you can simply bind it to something
else. Near all uses of the bindkey
and zle
commands are smart
about removing dead wood in such cases. However you can also use
`bindkey -r
key-sequence' to remove the binding explicitly.
You can also simply bind the sequence to the command undefined-key
;
this has exactly the same effect --- even down to pruning completely any
bindings for long sequences. For example, suppose you bind
`\e\C-x\C-x
' to a command, then to undefined-key
. All memory
that `\e\C-x\C-x
' was ever bound is removed; \e\C-x
will no longer be marked as a prefix key, unless you had some other binding
with that prefix.
You can remove all bindings starting with a given prefix by adding the
`-p
option. The example given in the manual,
bindkey -rpM viins '\e'(except it uses the equivalent form `
^[
') is amongst the most
useful, as it will remove the annoying delay after you type `\e
' to
enter vi command mode. The delay is there because the cursor keys
usually also start with \e
and the shell is waiting to see if you
actually typed one of those. So if you can make do without cursor keys
in vi insert mode you may want to consider this.
Note that any binding for the prefix itself is not removed. In this
example, \e
stays bound as it was in the viins
keymap,
presumably to vi-cmd-mode
.
All manipulations like this are specific to one particular keymap. You
need to repeat them with a different -M
... option argument,
which is described below, to have the same effect in other keymaps.
It's usually possible to bind the function keys on your keyboard, including the specially named ones such as `Home' and `Page Up'. It depends a good deal on how your windowing system or terminal driver handles them, but these days it's nearly always the case that a well set-up system will allow the function keys to send a string of characters to the terminal. To bind the keys you need to find out what that string is.
Luckily, you are usually aided by the fact that only the first character
of the string is `funny', i.e. does something other than insert a
character. So there is a trick for finding out what the sequence is.
In a shell window, hit ^v
(if you are using vi bindings, you will
need to be in insert mode), then the function key in question. You will
probably see a string like `^[OP
' --- this is what I get from the F1
key. A note in my .zshrc
suggests I used to get `\e[11~
', so be
prepared for something different, even if, like me, you are using a
standard xterm terminal emulator. A quick poll of terminal emulators on
this Linux/GNU/XFree86 system suggests these two possibilities are by
far the most popular.
You may even be able to get different sequences by holding down
shift or control as well (after pressing ^v
, of course). On my
keyboard, combining F1 with shift gives me `^[O2P
', with control
`^[O5P
' and with both `^[O6P
'. Again, your system may do
something completely different.
If you move the cursor back over that `^[
', you'll find it's a
single character --- you can position the cursor over the `^
', but
not the `[
'. This is zsh's way of inserting a real, live escape
character into the line. In fact, if you type
bindkey 'then
^v
, the function key, and the other single quote, you have a
perfectly acceptable way of binding the key on the command line.
Zsh is generally quite relaxed about your use of unprintable characters;
they may not show up correctly on your terminal, but the shell is able
to handle all single-byte characters. It doesn't yet have support for
those longer than a single byte, however.
You can also do the same in your .zshrc
; the shell will handle
strange characters in input without a murmur. You can also use the two
characters `^[
', which is just another way of entering an escape key.
However, the kosher thing to do is to turn it into `\e
'. For example,
bindkey '\e[OP' where-is # F1 bindkey '\e[O2P' universal-argument # shift F1and so on. Using this, you can give sensible meanings to `Home', `End', etc. Note the windowing system's sensible way of avoiding the problem with prefixes --- any extra characters are inserted before the final character, so the shell can easily tell when the sequence is complete without having to wait and see if there is more to follow.
There is a utility supplied with zsh called zkbd
which can help with
all of this by finding out and remembering the definitions for you. You
can probably use it simply by autoloading it and running it, as it is
usually installed with the other functions. It should be reasonably
self-explanatory, else consult the zshcontrib
manual.
If you are using X Windows and are educated enough, you can tinker with
your .Xdefaults
file to tweak how keys are interpreted by the
terminal emulator. For example, the following turns the backspace key
into a delete key in anything using a `VT100 widget', which is the basis
of xterm's usual mode of operation:
*VT100.Translations: #override \ <Key>BackSpace: string(0x7F)Part of the reason for showing this is that it makes zsh's key binding system look wonderfully streamlined by comparison. However, tinkering around at this level gives you very much more control over the use of key modifiers (shift, alt, meta, control, and maybe even super and hyper if you're lucky). This is far beyond the scope of this guide --- which I say, as you probably realise by now, to cover up for not knowing much about it. Here's another example from Oliver Kiddle, though; it uses control with the left-cursor key to send an escape sequence: insert
Ctrl<Key>Left: string(0x1b) string("[159q") \n\into the middle of the example above --- this shows how multiple definitions are handled. Modern xterms already send special escape sequences which you can investigate and bind to as I've described.
It's possible to assign an arbitrary string of characters to a key
sequence instead of an editor command by giving bindkey
the option
-s
. One of the good things about this is that the string of
characters are reinterpreted by zle, so they can contain active key
sequences. In the old days, this was quite often used as a basic form
of macro, to string together editor commands. For example, the
following is a simple way of moving backward two words by repeating the
Emacs mode bindings. I've used my F1 binding again; yours may be
completely different.
bindkey -s '\e[OP' '\eb\eb'It's not a good idea to bind a key sequence to another string which includes itself.
This method has the obvious drawback that if someone comes along and
rebinds `\eb
', then F1 will stop working, too. Nowadays, this sort
of task can be done much more flexibly and clearly by writing a
user-defined widget, which is described in a later section. So bindings
of this sort are going a little out of fashion. However, they do
provide quick shortcuts. Two from Oliver Kiddle:
bindkey -s '^[[072q' '^V^I' # Ctrl-Tab bindkey -s "\C-x\C-z" "\eqsuspend\n"You can also quite easily do some of the things you can do with global aliases.
Remember that `ordinary' characters can be rebound, too; they just usually happen to have a binding which makes them be inserted directly. As a particularly pointless example, consider:
bindkey -s secret 'Oh no!'If you type `
secret
' fast enough the letters are swallowed up and
`Oh no!
' appears instead. If you pause long enough anywhere in the
middle, the word is inserted just as normal. That's because all parts
of it can be interpreted as prefixes in their own right, so
$KEYTIMEOUT
applies at every intervening stage. Less pointlessly,
you could use this as a way of defining abbreviations.
So far, all I've said about keymaps is that there are three standard ones,
one for Emacs mode and two for vi mode, and that `bindkey -e
' and
`bindkey -v
' pick Emacs or vi insert mode bindings. There's no
simple way of picking vi command mode bindings, since that's not usually
directly available but entered by the vi-cmd-mode
command, usually
bound to \e
, in vi insert mode. (There is a `bindkey -a
', but
that doesn't pick the keymap for normal use; it's equivalent to, but
less clear than, `bindkey -M vicmd
'.)
Most handling of keymaps is done through bindkey
. The keymaps have
short names, emacs
, viins
and vicmd
, for use with
bindkey
. There is also a keymap .safe
which you don't usually
need but which never changes, so can be used if your experimentation has
completely ruined every other keymap. It only has bindings for
self-insert
(most keys) and accept-line
(^j
and ^m
), but
that's enough to enter commands.
The names are most useful in two places. First, you can use `bindkey
-M
keymap' to define keys in a particular map:
bindkey -M vicmd "\e[OA" up-line-or-historybinds the usual up-cursor key in
vicmd
mode, whatever keymap is
currently set. Actually, any version of the shell which
understands the -M
option probably has that bound already.
Secondly, you can force zle to use a particular keymap. This is done in
a slightly non-obvious way: zle always uses the keymap main
as the
current keymap (except when it's off in vi command mode, which is
handled a bit specially). To use your own, you need to make main
an
alias for that with `bindkey -A
'. The order after this is the same
as that after ln
: the existing keymap you want to refer to comes
first, then what you want to make an alias for it, in this case
main
. This means that
bindkey -A emacs mainhas the same effect as
bindkey -ebut is more explicit, if a little more baroque. Don't link
vicmd
to
main, since then you can't use viins
, which is bad. Note that
`bindkey -M emacs
' doesn't have this effect; it simply lists the
bindings in the emacs
keymap.
You can create your own keymaps, too. The easiest way is to copy an existing keymap, such as
bindkey -N mymap emacswhich creates (or replaces)
mymap
and initialises it with the
bindings from emacs
. Now you can use mymap
just like emacs
.
The bindings in each are completely separate. If you finish with a
keymap, you can remove it with `bindkey -D keymap
', although you'd
better make sure it's not linked to main
first.
You can omit `emacs
' to create an empty keymap; this might be
appropriate if your keymap is only going to be used in certain special
places and you want complete control on what goes into it. Currently
the shell isn't very good at letting you apply your own keymaps just in
certain places, however.
There are various other keymaps you might encounter, used in special
circumstances. If you list all keymaps, which is done by `bindkey
-l
', you may see listscroll
and menuselect
. These are used by
the new completion system, so if that isn't active, you probably
won't see them. They reside in the module zsh/complist
.
There will be more about their effects in chapter 6;
listscroll
allows you to move up and down completion lists which
more than fill the terminal window, and menuselect
allows you to
select items interactively from a displayed list. You can bind keys in
them as with any other keymap.
(In physics, the `advanced wave' is a hypothetical wave which moves backwards in time. Unfortunately, however useful it would be to meet deadlines, that's not what I meant by `advanced editing'.)
Here are are a few bits and pieces which go beyond ordinary line editing of shell commands. Although they haven't been widespread in shells up to now, I use all of them every day, so they aren't just for the postgraduate zsh scholar.
All Bourne-like shells allow you to edit continuation lines; that is, if
the shell can work out for sure that you haven't finished typing, it
will show you a new prompt, given by $PS2
, and allow you to continue
where you left off from the previous line. In zsh, you can even see
what it is the shell is waiting for. For a simple example, type
`array=
(first
' and then `return'. The shell is waiting for the
final parenthesis of the array, and prints `array>
' at you, unless
you have altered $PS2
. You can continue to add elements to the
array until you close the parentheses.
Shells derived from csh are less happy about continuation lines; historically, this is because they try to evaluate everything in one go, and became confused if they couldn't. The original csh didn't have a particularly sophisticated parser. For once, zsh doesn't have an option to match the csh behaviour; you just have to get used to the idea that things work in zsh.
Where zsh improves over other shells is that you aren't just limited to editing a single continuation line; you can actually edit a whole block of lines on screen as you would in a full screen editor --- although you can't scroll off the chunk of lines you're editing, which wouldn't make sense.
The easiest way of doing this is to hit escape before you type a newline at the point where you haven't finished typing. Actually, you can do this any time, even if the line so far is complete. For example,
% print This is line one<ESC><RET> print This is line twowhere those angle brackets at the end of the line means you type escape, then return. Nothing happens, and there is no new prompt; you just type blithely on. Hit return, unescaped this time, and both lines will be executed. Note there is no implicit backslash, or anything like that; when zsh reads the whole caboodle, that escaped carriage return became a real carriage return, just as the shell would have read it from a script.
This works because `\e\r
' is actually bound to the command
self-insert-unmeta
' which means `insert the character you get by
stripping the escape or top bit from what I just typed' --- in other
words, a literal carriage return. You would have got exactly the same
effect by typing ^v^j
, since the ^v
likewise escapes the
^j
(newline), as it does any other character.
(Aside for the terminally curious only: Why newline here and not
carriage return --- the `enter' key --- as you might expect? That's a
rather grotesque story. It turns out that for mostly historical reasons
UNIX terminal drivers like to swap newline and carriage return, so when
you type carriage return (sent both by that key and by ^m
, which is
the same as the character represented by \r
), it comes out as
newline (on most keyboards, just sent by ^j
, which is the same as
the character represented by \n
). It is the newline character which
is the one you `see' at the end of the line (by virtue of the fact it is
the end of the line). However, ^v
sees through this and if you type
^m
after it, it inserts a literal ^m
, which just looks like a
^m
because that's how zsh outputs it. So that's why that doesn't
work. Actually, self-insert-unmeta
would see the ^m
, too,
because that's what you get when you strip off the \e
, but it has a
little extra code to make UNIX users feel at home, and behaves as if it
were a newline. Normally, ^j
and ^m
are treated the same way
(accept-line
), but the literal characters have different behaviours.
If you're now very confused, just be thankful I haven't told you about
the additional contortions which go on when outputting a newline.)
It probably doesn't seem particularly useful yet, because all you've
done is miss out a new prompt. What makes it so is that you can now go
up and down between the two (or more) lines just using the cursor keys.
I'm assuming you haven't rebound the cursor keys, your terminal isn't a
dumb one which doesn't support cursor up, and the option
singlelinezle
isn't in effect --- just unset it if it is, you'll be
grateful later.
So for example, type
% if [[ true = false ]]; then<ESC><RET> print Fuzzy logic rules<ESC><RET> fiwhere I indented that second line just with spaces, because I usually do inside an `if'. There are no continuation prompts here, just the original
$PS1
; that's not a misprint. Now, before hitting return,
move up two lines, and edit false
to true
. You can see how this
can be useful. Entering functions at the command line is probably a more
typical example.
Suppose you've already gone through a few continuation lines in the
normal way with $PS2
's? You can't scroll back then, even though the
block hasn't yet been edited. There's a magic way of turning all those
continuation lines into a single block: the editor command
push-line-or-edit
. If you're not on a continuation line, it acts
like the normal push-line
command, which we'll meet below, but for
present purpose you use it when you are on a continuation line.
You are presented with a seamless block of text from the (redrawn)
prompt to the end which you can edit as one. It's quite reasonable to
bind push-line-or-edit
instead of push-line
, to either ^q
or
\eq
(in Emacs mode, which I will assume, as usual). Be careful with
^q
, though --- if the option flowcontrol
is set it will probably
be swallowed up by the terminal driver and not get through to the shell,
the same problem I mentioned above for ^s
.
I mentioned the vared
command in chapter 3; it uses the
normal line editor to editor a variable, typically a long one you don't
want to have to type in completely like $path
, although you need to
remember not to put the `$
' in front or the shell will
substitute it before vared
is run. However, since it's just a piece
of text like any other input, this, too, can have multiple lines, which
you enter in the same way --- and since a shell parameter can contain
anything at all, you have a pretty general purpose editor. The shell
function `zed
' is supplied with the shell and allows you to edit a
file using all the now-familiar commands. Since when editing files you don't
expect a carriage return to dump you out of the editor, just to insert a
new line, zed rebinds carriage return to self-insert-unmeta
(the
`-unmeta
' here is just to get the swapping behaviour of turning the
carriage return into a newline). To save and exit, you can type ^j
,
or, if your terminal does something odd with that, you can also use
^x^w
, which is designed to look like Emacs' way of writing a file.
If you look at zed
, you will see it has a few bells and whistles ---
for example, `zed -f
' allows you to edit a function --- but the code
to read a file into a parameter, edit the parameter, and write the
parameter back to the file is extremely simple; all the hard editing
code is already handled within vared
. Indeed, zed
is
essentially a completely general purpose editor, though it quickly
becomes inefficient with long files, particularly if they are larger
than a single screen; as you would expect, zle was written to cope
efficiently with short chunks of text.
It would probably be nice if you could make key bindings that only applied within vared by using a special keymap. That may happen one day.
By the way, note that you can edit arrays with vared and it will handle the different elements sensibly. As usual, whitespace separates elements; when it presents you with an array which contains whitespace within elements, vared will precede it with a backslash to show it isn't a separator. You can insert quoted spaces with backslashes yourself. Only whitespace characters need this quoting, and only backslashes work.
For example,
array=('one word' 'two or more words') vared arraypresents you with `
one\ word two\ or\ more\ words
'. If you add `
and\ some\ more.
', hit return, and type `print -l $array
' to show
one element per line you will see
one word two or more words and some more.Some older versions of the shell were less careful about spaces within elements.
The mysterious other use for push-line-or-edit
will now be
explained. Let's stick to push-line
, in fact, since I've already
dealt with the -or-edit
bit.
Type
print I was just in the directory(no newline). Oh dear, which directory were you just in? You don't want to interrupt the flow of text to find out. Hit `
\eq
'; the line
you've been typing disappears --- but don't worry, it hasn't gone.
Now type
dirsTwo things happen: that last line is executed, of course, showing the list of directories on the directory stack (your use of
pushd
and
popd
), but also the line you got rid of before has reappeared, so you
can continue to edit it.
You may not realise straight away quite how useful this is, but
I used it several times just while I was writing the previous
paragraph. For example, I was alternating directories between the zle
source code and the directory where I keep this guide, and I started
typing a `grep
' command before realising I was in the wrong
directory. All I need to do is type \eq
, then pushd
, to put me
where I want to be, and finish off the grep
.
The `buffer stack', which is the jargon for this mechanism, can go as
deep as you like. It's a last-in-first-out (LIFO) stack, so the line
pushed onto it by the most recently typed \eq
will reappear first,
followed by the back numbers in reverse order. You can even prime the
buffer stack from a function --- not necessarily a zle function, though
that works too --- with `print -z
command-line'.
You can pull something explicitly off the stack, if you want, by typing
\eg
, but that has the same effect as clearing the current line and
hitting return. You can of course push the same line multiple times: if
you need to do a whole series of things before executing it, just hit
\eq
again each time the line pops back up.
I lied a little bit, to avoid confusion. The cleverness of
push-line-or-edit
about multi-line buffers extends to the this
case, too. If you do a normal push-line
on a multi-line buffer,
only the current single line is pushed; the command to push the whole
lot, which is probably what you want, is push-input
. But if you
have push-line-or-edit
bound, you can forget the distinction, since
it will do that for you. If you've been paying attention you can work
out the following sequence (assuming \eq
has been rebound to
push-line-or-edit
):
% if [[ no = yes ]]; then then> print<ESC>q<ESC>qThe first
\eq
turns the two lines into a single buffer, then the
second pushes the whole lot onto the buffer stack. This saves a lot of
thinking about bindings. Hence I would recommend users of Emacs mode add
bindkey '\eq' push-line-or-editto their
.zshrc
and forget the distinctions.
We now come to the newest and most flexible part of zle, the ability to create new editing commands, as complicated as you like, using shell functions. This was originally introduced by Andrew Main (`Zefram') in zsh 3.1 and so is standard in all versions of zsh 4, although work goes on.
If you don't speak English as you first language, first of all, congratulations for getting this far. Secondly, you may think of `widget' only as a technical word applied to the object which realises some computational idea, like the thing that implements text editing in a window system, for example. However, to most English speakers, `widget' is a humorous word for an object, a bit like `whatyoumacallit' or `thingummybob', as in `where's that clever widget that undoes the foil and takes out the cork in one go'. Zsh's use has always seemed to me closer to the second, non-technical version, but I may be biased by the fact that the internal object introduced by Zefram to represent a widget, and never seen by the user, is called a `thingy', which I won't refer to again since you don't need to know.
Anyway, a `widget' is essentially what I've been calling an editor command up to now, something you can bind to a key sequence. The reason the more precise terminology is useful is that as soon as you have shell functions flying around, the word `command' is hopelessly non-specific, since functions are full of commands which may or may not be widgets. So I make no apology for using the word.
So now we are introducing a second type of widget: one which, instead of
something handled by code built into the shell, is handled by a function
written by the user. They are completely equivalent; bindkey
and
company don't care which it is. All you need to do to create a widget is
zle -N widget-name function-namethen widget-name can be used in
bindkey
, or
execute-named-cmd
, and the function function-name will be run.
If the widget-name
and function-name
are the same, which is
often the simplest thing to do, you just need one of them.
You can list the existing widgets by using `zle -l
', although often
`zle -lL
' is a better choice since the output format is then the
same as the form you would use to define the widget. If you see lots of
`zle -C
' widgets when you do that, ignore them for now; they are
completion widgets, handled a bit differently and described in
chapter 6.
Now you need to know what should go into the function.
The simplest thing you can do inside a function implementing a widget is call an existing function. So,
my-widget() { zle backward-word } zle -N my-widgetcreates a widget called
my-widget
which behaves in every respect
(except speed) like the builtin widget backward-word
. You can even
give it a prefix argument, which is passed down; \e3
then whatever
you bound the widget to (or \exmy-widget
) will go backward three words.
Suppose you wanted to pass your own prefix argument to
backward-word
, instead of what the user typed? Or suppose you want
to take account of the prefix argument, but do something different with
it? Both are possible.
Let's take the first of those. You can supply a prefix argument for
this command alone by putting -n
argument after the widget name
(note this is not where most options go).
my-widget() { zle backward-word -n 2 }This always goes backwards two words, overriding any numeric argument given by the user. (You can redefine the function without telling zle about it, by the way; zle just calls whatever function happens to be defined when the widget is run.) If you put just
-N
after the name
instead, it will cancel out any prefix given by the user, without
introducing a new one.
The other part of prefix handling --- intercepting the one the user
specified and maybe modifying it --- introduces one of the most important
parts of user-defined widgets. Zle provides various parameters which can
be read and often written to alter the behaviour of the editor or even
the text being edited. In this case, the parameter is $PREFIX
. For
example,
my-widget() { zle backward-word -n $(( ${NUMERIC:-1} * 2 )) }This uses an arithmetic substitution to provide an argument to
backward-word
which is twice what the user gave. Note that
${NUMERIC:-1}
notation, which is important: most of the time, you
don't give a numeric argument to a command at all, and in that case zle
naturally enough treats $NUMERIC
as if it wasn't set. This would
mess up the arithmetic substitution.
By the way, if you do make an error in a shell function, you won't see
it; you'll just get a beep, unless you've turned that off with setopt
nobeep
. The output from such functions is junked, since it would mess
up the display. So you should do any basic debugging before turning the
function into a widget, for example, stick a print
in front and run
it directly --- you can't execute widgets from outside the editor.
The following also works:
my-widget() { (( NUMERIC = ${NUMERIC:-1} * 2 )) zle backward-word }because you can alter
$NUMERIC
directly, and unless overridden by
the -n
argument it is used by any widgets called from the function.
If you called more widgets inside the function --- and you can call as
many as you like --- the same argument would apply to all the ones that
didn't have an explicit -n
or -N
.
Some widgets allow you to specify non-numeric arguments. At the moment
these are mainly search functions, which you can give an explicit search
string. Usually, however, you want to specify a new search string each
time. The most useful way of using this I can see is to provide an
initial argument for incremental search commands. Later, I'll show you
how you can read in characters in a similar fashion to Emacs mode's
^r
binding, history-incremental-search-backwards
.
There are some things you might want to do with the editor in a zle
function which wouldn't be useful executed directly from zle. One is to
cause an error in the same way as a normal widget does. You can do that
with `zle beep
'. However, this doesn't automatically stop your
function at that point; it's up to you to return from it.
It's possible to redefine a builtin widget just by declaring it with
`zle -N
' and defining the corresponding function. From now on, all
existing bindings which refer to that widget will cause yours to be run
instead of the builtin one. This happens because zle doesn't actually
care what a widget does until it is run. You can see this by using
bindkey
to define a key sequence to call an undefined widget such as
any-old-string
. The shell doesn't complain until you actually hit
the key sequence.
Sometimes, however, you want to be sure to call the builtin widget, even
if the behaviour has been redefined. You can do this by putting a
`.
' in front of the name of the widget; `zle
.up-line-or-history
' always calls the builtin widget usually referred
to as up-line-or-history
, even if the latter has been redefined.
One use for this is to rebind `accept-line
' to do something whenever
zle is about to pass a line up to the shell, but to accept the line
anyway: you write your own widget accept-line
, make sure it calls
`zle .accept-line
just before it finishes, and then use `zle -N
accept-line
. Here's a trivial but not entirely stupid example:
accept-line() { print -n "\e]2;Executing $BUFFER\a" zle .accept-line } zle -N accept-lineNow every time you hit return to execute a command, that
print
command will be executed first. As written, it puts `Executing
' and
then the contents of the command line (see below) into the title of your
xterm window, assuming it understands the usual xterm escape sequences.
In fact, this particular example is usually handled with the special
shell function (not zle function) `preexec
' which is passed a
command line about to be executed as an argument instead of in
$BUFFER
. There seems to be a side effect of rebinding
accept-line
that the return key stops working in the minibuffer
under some circumstances.
Note that to undo the fact that return executes your new widget, you
need to alias accept-line
back to .accept-line
:
zle -A .accept-line accept-lineIf you have trouble remembering the order, as with most alias or rename commands in zsh and UNIX generally, including
ln
and bindkey
-A
, the existing command, the one whose properties you want to keep,
comes first, while the new name for it comes second. Also, as with
those commands, it doesn't matter if the second name on the line
currently means something else; that will be replaced by the new
meaning. Afterwards, you don't need to worry about your own
accept-line
widget; zle handles the details of removing widgets when
they're no longer referred to. The function's still there, however,
since as far as the rest of the shell is concerned it's just an ordinary
shell function which you need to `unfunction
' to remove.
Do remember, however, not to delete a widget which redefines a basic internal widget by the obvious command
# Noooo! zle -D accept-linewhich stops the return key having any effect other than complaining there's no such widget. If you get into real trouble, `
\ex.accept-line
' should work, as you can use the `.
'-widgets
anywhere you can use any other except where they would redefine or
delete a `.
' widget. Use the `zle -A
' command above with the
extended-command form of `.accept-line
' to return to normality.
If you try to redefine or delete a `.
' widget, zle will tell you
it's protected. You can remove any other widget in this way, however, even
if it is still bound to a key sequence; you will then see an error if
you type that sequence.
One point to note about accept-line
is that the line isn't passed up
to zsh instantly, only when your own function exits. This is pretty
obvious when you think about it; zle is called from the main shell, and
if your own zle widget hasn't finished executing, the main shell hasn't
got control back yet. But it does mean, for example, that if you modify
the command line after a call to accept-line
or .accept-line
,
those changes are reflected in the line passed up to the shell:
# Noooo! to this one too. accept-line() { zle .accept-line BUFFER='Ha ha!' }This always returns the string `
Ha ha!
' to the main shell. This is
not particularly useful unless you are constructing a Samuel Beckett
shell for display at an installation in a Parisian art gallery.
The shell makes various parameters available for easy manipulation of
the command line. You've already seen $NUMERIC
. You may wonder
what happens if you have your own parmeter called $NUMERIC
; after
all, it's a fairly simple string to use as a name. The good news is you
don't need to worry; when the shell runs a zle function, it simply hides
any existing occurrences of a parameter and makes its special parameters
available. Then when it exits, the original parameter is reenabled.
So all you have to worry about is making sure you don't use these
special parameters for anything else while you are inside a zle widget.
There are four particularly common zle parameters.
First, there are three ways of referring to the text on the command
line: $BUFFER
is the entire line as a string, $LBUFFER
is the
line left of the cursor position, and $RBUFFER
is the line after
it including the character under the cursor, so that the division is
always at the point where the next inserted character would go. Any or
all of these may be empty, and $BUFFER
is always the string
$LBUFFER$RBUFFER
.
The necessary counterpart to these is $CURSOR
, which is the cursor
position with 1 being the first character. If you know how the shell
handles substrings in parameter substitutions, you will be able to see
that $LBUFFER
is $BUFFER[1,$CURSOR-1]
, while $RBUFFER
is
$BUFFER[$CURSOR,-1]
(unless you are using the option KSH_ARRAYS
for compatibility of indexes with ksh --- this isn't recommended for
implementing zle or completion widgets as it causes confusion with the
ones supplied with the shell).
The really useful thing about these is that they are modifiable. If you
modify $LBUFFER
or $RBUFFER
, then $BUFFER
and $CURSOR
will be modified appropriately; lengthening or shortening $LBUFFER
increases or decreases $CURSOR
. If you modify $BUFFER
, you may
need to set $CURSOR
yourself as the shell can't tell for sure where
the cursor should be. If you alter $CURSOR
, characters will be
moved between $LBUFFER
and $RBUFFER
, but $BUFFER
will remain
the same.
This makes tasks along the lines of basic movement and deletion commands extremely simple, often just a matter of pattern matching. However, it definitely pays to know about zsh's more sophisticated pattern matching and parameter substitution features, described in the next chapter. For example, if you start a widget function with
emulate -L zsh setopt extendedglob LBUFFER=${LBUFFER%%[^[:blank:]]##}then
$LBUFFER
contains the line left of the cursor stripped of all
the non-blank characters (usually anything except space or tab)
immediately to the left of the cursor.
This function uses the parameter substitution feature
`${
param%%
pattern}
' which removes the longest
match of pattern from the end of $
param. The `emulate
-L zsh
' ensures the shell options are set appropriately for the
function and makes all option settings local, and `setopt
extendedglob
' which turns on the extended pattern matching features; it
is this that makes the sequence `##
' appearing in the pattern mean
`at least one repetition of the previous pattern element'. The previous
pattern element is `anything except a blank character'. Hence, all
occurrences of non-blank characters are removed from the end of
$LBUFFER
.
If you want to move the cursor over those characters, you can tweak the function slightly:
emulate -L zsh setopt extendedglob chars=${(M)LBUFFER%%[^[:blank:]]##} (( CURSOR -= ${#chars} ))The string `
(M)
' has appeared at the start of the parameter
substitution. This is part of zsh's unique system of parameter flags;
this one means `insert the matched portion of the substitution'. In
other words, instead of returning $LBUFFER
stripped of non-blank
characters at the end, the substitution returns those very characters
which it would have stripped. To skip over them is now a simple matter
of decreasing $CURSOR
by the length of that string.
You'll find if you try these examples that they probably don't do quite what you want. In particular, they don't handle any blank characters found next to the non-blank ones which normal word-orientated functions do. However, you now have enough information to add tests for that yourself.
If you get more sophisticated, you can then add handling for
$NUMERIC
. Remember this isn't set unless the user gave it
explicitly, so it's up to you to treat it as 1 in that case.
A large fraction of what you are likely to want to do can be done with the parameters we've already met. Here are some hints as to how you might want to use some of the other parameters available. As always, for a complete list with rather less in the way of hints see the manual.
$KEYS
tells you the keys which were used to call the widget; it's
a string of those raw characters, not turned into the bindkey
format. In other words, if it was a single key (including possibly a
control key or a meta key), $KEYS
will just contain a single
character. So you can change the widget's behaviour for different
keys. Here's a very (very) simple function like self-insert
:
LBUFFER=$LBUFFER$KEYSNote this doesn't work very well with
\ex
extended command handling;
you just get the ^m
from the end of the line. You need to make sure
any widgets which use $KEYS
are sensibly bound. This also doesn't
handle numeric arguments to repeat characters; it's a fairly simple
exercise (particularly given zsh's `repeat
' loop) to add that.
$WIDGET
and $LASTWIDGET
tell you the name of the current widget
being executed and the one before that. These don't sound all that
useful at first hearing. However, you can use $WIDGET
together with
the fact that a widget doesn't need to have the same name as the
function that defines it. You can define
zle -N this-widget function zle -N that-widget functionand test
$WIDGET
inside function
to see if it contains
this-widget
or that-widget
. If these have a lot of shared code,
that is a considerable simplification without having to write extra
functions.
$LASTWIDGET
tends to be used for a slightly different purpose:
checking whether the last command to be executed was the same as the
current one, or maybe was just friendly with it. Here are edited
highlights of the function up-line-or-beginning-search
, a sort of
cross between up-line-or-search
and
history-beginning-search-backward
which has been added to the shell
distribution for 4.1
. If there are previous lines in the buffer, it
moves up through them; else if it's the first in a sequence of
calls to this function it remembers the cursor position and looks
backwards for a line with the same text from the start up to that point,
and puts the cursor at the end of the line; else if the same widget has
just been executed, it uses the old cursor position to search for
another match further back in the history.
if [[ $LBUFFER == *$'\n'* ]]; then zle .up-line-or-history __searching='' else if [[ $LASTWIDGET = $__searching ]]; then CURSOR=$__savecursor else __savecursor=$CURSOR fi __searching=$WIDGET zle .history-beginning-search-backward zle .end-of-line fiWe test
$__searching
instead of $WIDGET
directly to be able to
tell the case when we are moving lines instead of searching.
$__savecursor
gives the position for the backward search, after
which we put the cursor at the end of the line. The parameters
beginning `__
' aren't local to the function, because we need to test
them from the previous execution, so they have been given underscores in
front to try to distinguish them from other parameters which might be
around.
You'll see that the actual function supplied in the distribution is a little more complicated than this; for one thing, it uses styles set by the user to decide it's behaviour. Styles are described for use with completion widgets in chapter 6, but you can use them exactly the same way in zle functions.
The full version of up-line-or-beginning-search
uses another
parameter, $PREBUFFER
. This contains any text already absorbed by
zle
which you can no longer edit --- in other words, text read in
before the shell prompted with $PS2
for the remainder. Testing
`[[ -n $PREBUFFER ]]
' therefore effectively tests whether you are at
the $PS2
. You can use this to implement behaviour after the fashion
of push-line-or-edit
.
Every now and then you want the editor to do a sequence of operations with user input in the middle. This is usually done by a combination of two commands.
First, you may need to prompt the user in the minibuffer, just like
\ex
does. You can do this with `zle -R
'. Its basic function is
to redisplay the command line, flushing all the changes you have made in
your function so far, but you can give it a string argument which
appears in the minibuffer, just below the command line. You can give it
a list of other strings after that, which appear in a similar way to
lists of possible completions, but have no special significance to zle
in this case.
To get input back from the user, you can use `read -k
' which reads a
single key (not a sequence; no lookup takes place). This command is
always available in the shell, but in this case it is handled by zle
itself. The key is returned as a raw byte. Two facilities of
arithmetic evaluation are useful for handling this key: `#key
'
returns the ASCII code for the first character of $key
, while
`##
key' returns the ASCII code for key, which is in the
form that bindkey
would understand. For example,
read -k key if (( #key == ##\C-g )); then ...makes the use of arithmetic evaluation. The form on the left turns the first character in
$key
into a number, the second turns the literal
bindkey-style string \C-g
into a number (ASCII 7, since 1 to 26 are
just \C-a
to \C-z
). Don't confuse either of these forms with
`$#key
', which is the length of the string in the parameter, in this
case almost certainly 1 for a single byte; this form works both inside
and outside arithmetic substitution, the other forms only inside.
The `(( ... ))
' form is recommended for arithmetic substitutions
whenever possibly; you can do it with the basic `[[ ... ]]
' form,
since `-eq
' and similar tests treat both sides as arithmetic, though
you may need extra quoting; however, the only good reason I know for
doing that is to avoid using two types of condition syntax in the same
complex test.
These tricks are only really useful for quite complicated functions.
For an example, look at the function incremental-complete-word
supplied with the zsh source distribution. This function doesn't add to
clarity by using the form `#\\C-g
' instead of `##\C-g
'; it does
the same thing but the double backslash is very confusing, which is why
the other form was introduced.
transpose-words-about-point
This function is a variant on transpose-words
. It has various
twists. First, the words in question are always space-delimited,
neither shell words nor words in the $WORDCHARS
sense. This makes
it fairly predictable.
Second, it will transpose words about the current point (hence its name)
even if the character under the cursor is not a whitespace character. I
find this useful because I am eternally typing compound words a bit like
`function_name
' only to find that what I should have typed was
`name_function
'. Now I just position the cursor
over the underscore and execute this widget.
emulate -L zsh setopt extendedglob local match mbegin mend pat1 pat2 word1 word2 ws1 ws2 pat1=${LBUFFER%%(#b)([^[:blank:]]##)([[:blank:]]#)} word1=$match[1] ws1=$match[2] match=() pat2=${RBUFFER##(#b)(?[[:blank:]]#)([^[:blank:]]##)} ws2=$match[1] word2=$match[2] if [[ -n $word1 && -n $word2 ]]; then LBUFFER="$pat1$word2$ws1" RBUFFER="$ws2$word1$pat2" else zle beep fi
The only clever stuff here is the pattern matching. It makes a great
deal of use of `backreferences' an extended globbing feature which is
used in all forms of pattern matching including, as in this case,
parameter substitution. It will be described fully in the next
chapter. The key things to look for are the `(#b)
', which activates
backreferences if the option EXTENDED_GLOB
is turned on, the
parentheses following that, which mark out the bits you want to refer
to, and the references to elements of the array $match
, which store
those bits. The shell also sets $mbegin
and $mend
to give the
positions of the start and end of those matches, which is why those
parameters are made local; we want to preserve them from being seen
outside the function even though we don't actually use them.
You might also need to know about the `#
' characters: one after a
pattern means `zero or more repetitions', and two mean `one or more
repetitions'. Finally, `[:blank:]
' in a character class refers to
any blank character; when negated, as in the character class
`[^[:blank:]]
', it means any non-blank character. With the `#
's
we match a series blank or non-blank characters. Given that, you can
work out the rest of what's going on.
Here's a more sophisticated version of that. If you found the previous one heavy going. you probably don't want to look too closely at this.
emulate -L zsh setopt extendedglob local wordstyle blankpat wordpat1 wordpat2 local match mbegin mend pat1 pat2 word1 word2 ws1 ws2 zstyle -s ':zle:transpose-words-about-point' word-style wordstyle case $wordstyle in (shell) local bufwords # This splits the line into words as the shell understands them. bufwords=(${(z)LBUFFER}) wordpat1="${(q)bufwords[-1]}" # Take substring of RBUFFER to skip over first character, # which is the one under the cursor. bufwords=(${(z)RBUFFER[2,-1]}) wordpat2="${(q)bufwords[1]}" blankpat='[[:blank:]]#' ;; (space) blankpat='[[:blank:]]#' wordpat1='[^[:blank:]]##' wordpat2=$wordpat1 ;; (*) local wc=$WORDCHARS if [[ $wc = (#b)(?*)-(*) ]]; then # We need to bring any `-' to the front to avoid confusing # character classes... we get away with `]' since in zsh # this isn't a pattern character if it's quoted. wc=-$match[1]$match[2] fi # A blank is anything not in the character class consisting # of alphanumerics and the characters in $wc. # Quote $wc where necessary, because we don't want those # characters to be considered as pattern characters later on. blankpat="[^${(q)wc}a-zA-Z0-9]#" # and a word character is anything else. wordpat1="[${(q)wc}a-zA-Z0-9]##" wordpat2=$wordpat1 ;; esac # The eval makes any special characters in the parameters active. # In particular, we need the surrounding `[' s to be `real'. # This is why we quoted the wordpats in the `shell' option, where # they have to be treated as literal strings at this point. eval pat1='${LBUFFER%%(#b)('${wordpat1}')('${blankpat}')}' word1=$match[1] ws1=$match[2] match=() eval pat2='${RBUFFER##(#b)(?'${blankpat}')('${wordpat2}')}' ws2=$match[1] word2=$match[2] if [[ -n $word1 && -n $word2 ]]; then LBUFFER="$pat1$word2$ws1" RBUFFER="$ws2$word1$pat2" else zle beep fi
What has been added is the ability to use a style to define how the
shell finds a `word'. By default, words are the same as what the shell
usually thinks of as a word; this is handled by the branch of the case
statement which uses `$WORDCHARS
' and a little extra trickery to get
a pattern which matches the set of characters considered parts of a
word. We used the eval
's because it allowed us to have some bits of
$wordpat1
and friends active as pattern characters while others were
quoted.
This introduces two types of parameter expansion flags:
${(q)
param}
adds backslashes to quote special characters in
$
param, so that when the parameter appears after eval
the
result is just the original string. ${(z)
param}
splits the
parameter just as if it were a shell command line being split into a
command and words, so the result is an array; `z
' stands for
zsh-splitting or just zplitting as you fancy.
If you set
zstyle ':zle:*' word-style spaceyou get back to the behaviour of the original function.
Finally, if you replace `space
' with `shell
' in that zstyle
command, you will get words as they are split for normal use within the
shell; for example try
echo execute the widget 'between these' 'two quoted expressions'and the entire quoted expressions will be transposed. You may find that if you do this in the middle of a quoted expression, you don't get a sensible result; that's because the
(z)
-splitting doesn't
know what to do with the improperly completed quotes to its left and
right. Some versions of the shell have a bug (fixed in 4.0.5) that the
expressions which couldn't be split properly, because the quotes weren't
complete, have an extra space character at the end.
insert-numeric
Here's a widget which allows you to insert an ASCII character which you
know by number. I can't for the life of me remember where it came from,
but it's been lying around apparently for two and a half years (please
do email me if you think you wrote it, otherwise I'll assume I did).
You can give it a numeric prefix (that's the easy part of the function),
else it will prompt you for a number. If you type `x
' or `o
'
the number is treated as hexadecimal or octal, respectively, else as
decimal.
# Set up standard options. # Important for portability. emulate -L zsh # x must display in hexadecimal typeset -i 16 x if (( ${+NUMERIC} )); then # Numeric prefix given; just use that. x=$NUMERIC else # We need to read the ASCII code. local msg modes key mode=dec code char # Prompt for and read a base. integer base=10 zle -R "ASCII code (o -> oct, x -> hex) [$mode]: " read -k key case $key in (o) base=8 mode=oct zle -R "ASCII code [$mode]: " read -k key ;; (x) base=16 mode=hex zle -R "ASCII code [$mode]: " read -k key ;; esac # Now we are looking for numbers in that base. # Loop until newline or return. while [[ '#key' -ne '##\n' && '#key' -ne '##\r' ]]; do if [[ '#key' -eq '##^?' || '#key' -eq '##^h' ]]; then # Delete a character [[ -n $code ]] && code=${code[1,-2]} elif [[ ($mode == hex && $key != [0-9a-fA-f]) || ($mode == dec && $key != [0-9]) || ($mode == oct && $key != [0-7]) ]]; then # Character not in range, beep zle beep elif [[ '#key' -eq '##\C-g' ]]; then # Abort: returning 1 signals to zle that this # is an abnormal termination. return 1 else code="${code}${key}" fi char= if [[ -n $code ]]; then # Work out the character using the # numbers typed so far. (( x = ${base}#${code} )) if (( x > 255 )); then zle beep code=${code[1,-2]} [[ -n $code ]] && (( x = ${base}#${code} )) fi [[ -n $code ]] && eval char=\$\'\\x${x##???}\' fi # Prompt for any more digits, showing # the character as it would be inserted. zle -R "ASCII code [$mode]: $code${char:+ = $char}" read -k key || return 1 done # If aborted with no code, return [[ -z $code ]] && return 0 # Now we have the ASCII code. (( x = ${base}#${code} )) fi # Finally, if we have a single-byte character, # insert it to the left of the cursor if (( x < 0 || x > 255 )); then return 1 else eval LBUFFER=\$LBUFFER\$\'\\x${x##???}\' fi
This shows how to do interactive input. The `zle -R
's prompt the
user, while the `read -k
's accept a character at a time. As an
extra feature, while you are typing the number, the character that would
be inserted if you hit return is shown. The widget also handles
deletion with backspace or the (UNIX-style, not PC-style) delete key.
One blight on this is the way of turning the number in x into a
character, which is done by all those eval
s and backslashes. It
uses the feature that e.g. $'\x41'
is the character 0x41
(an
ASCII `A'). To use this, we must make sure the character (stored in x)
appears as hexadecimal, and following ksh zsh outputs hexadecimal
numbers as `16#41
' or similar. (The new option C_BASES
shows
hexadecimal numbers as 0x41 and similar, but here we need the plain
number in any case.) Hence we strip the `16#
' and construct our
$'\x41'
. Now we need to persuade the shell to interpret this as a
quoted string by passing it to eval
with the special characters
($
, \
, '
) quoted with a backslash so that they aren't
interpreted too early.
By the way, note that zsh only handles ordinary 8-bit characters at the moment. It doesn't matter if some do-gooder on your system has set things up to use UTF-8 (a UNIX-friendly version of the international standard for multi-byte characters, Unicode) to appeal to the international market, I'm afraid zsh is stuck with ISO 8859 and similar character sets for now.