The vi/ex Editor, Part 9: Take Charge with Macros | |
Text-Insertion Macros | |
What These Tools Do | |
Working Principles | |
Time for another exercise | |
Command-Submode Macros | |
:map Macros | |
Buffer Macros | |
:source Macros | |
Another Exercise | |
Write and Read Macros | |
Text-Insertion Macros | |
As befits an editor with all those built-in metacharacters that | |
operate while you are typing in text, there are two ways to create | |
your own macros for use during text insertion. Both can be useful | |
in the right circumstances, so you'll probably want to put them to | |
work at times. You may not have a choice -- often the .exrc file | |
that you may be given when you get a new Unix shell account has | |
some of these shorthand dodges built in. These two tools have as | |
many similarities as differences, so I will expound them in | |
parallel. | |
What These Tools Do | |
Both tools act only when you are in text-insertion submode of | |
screen-editing mode. Nonetheless, the commands that set them up | |
and manage them are line-mode commands like: | |
:ab ucb University of California at Berkeley | |
:map! } Control-[k2cc | |
The two example lines above will set up two shorthand forms that | |
you can use without further preliminaries. The first line provides | |
that whenever you type "ucb" as a separate word in your text, the | |
editor will replace it with "University of California at Berkeley". | |
It happens right on the spot, and without any special signal from | |
you. | |
The second line is for use if you frequently discover that what you | |
are typing has become a mess, and that the mess started back on the | |
previous line. With this shorthand form in effect, whenever there | |
is a "}" character in what you type in, the editor removes it and | |
instead acts as if you had typed in "Control-[k2cc". That is, the | |
Control-[ (generated by the "Escape" key on your keyboard) causes | |
the editor to escape from text-insertion to command mode, the "k" | |
causes the cursor to move up a line, and the "2cc" removes both of | |
the lines involved and puts you back in text-insertion mode, ready | |
to type in a replacement for those lines and continue on with your | |
text insertion. As with the previous tool, this happens as soon as | |
you type in the shorthand form, without any special action by you. | |
Note that whitespace separates either of these setup commands into | |
three parts. The first part, from the start of the line up to the | |
first stretch of whitespace, is just the command name. Part two, | |
between the first and second stretches of whitespace, is the short | |
form that you will type into the text. The third part, everything | |
following the second stretch of whitespace, is what the editor will | |
insert (and/or execute) when you type in the short form. Only the | |
first two stretches of whitespace are separators -- any later | |
stretches are integral components of part three. And whitespace | |
includes both space characters and tabs, in any mixture. | |
Working Principles | |
Now that you've seen what these two tools do, let's consider how | |
they work: | |
:abbreviate | |
(Shortest abbreviation is :ab). This tool acts when you type in a | |
certain character or string as a separate word, each end bounded by | |
whitespace, or a punctuation character, or the start or end of a | |
line, or the start or end of an insertion. As soon as the editor | |
sees that the abbreviation is a word by itself, it replaces that | |
abbreviation with the longer word or phrase you have set as | |
equivalent. | |
As an example, you might have declared "cat" as your abbreviation | |
for "felix domesticus". Then, wherever you type in a line such as | |
"the habits of the common cat include", the editor will promptly | |
change it to read "the habits of the common felix domesticus | |
include". But there will be no such change in words that happen to | |
include the string "cat" in them, such as "catamaran" or | |
"concatenation". Be careful with this, because while the word | |
"catlike" will not be changed, the word "cat-like" will be. | |
Neither a backslash (\) nor a control-V will quote an abbreviation | |
into a file as itself. Usually, the easiest way to insert an | |
abbreviation into your text is to escape from text-insertion | |
submode (back to command submode) in the middle of typing the | |
abbreviation, then re-enter text-insertion submode and type in the | |
rest of the abbreviation. If your abbreviation is only one | |
character long, though, you must fall back on typing the | |
abbreviation with a letter immediately before or after it, then | |
returning to command submode to erase the unwanted extra letter. | |
:map! | |
(No editor-accepted abbreviation). Very similar to the abbreviation | |
tool discussed above, but with three major differences: | |
The shorthand form defined with this command does not need to be | |
typed into your inserted text as a separate word in order to | |
operate. Even if it is embedded within another word, the short form | |
will disappear and its related text will be entered in its place. | |
This tool does not simply insert the related text into the file, as | |
the :abbreviate tool does; it acts as though the user had typed in | |
the related text instead of the short form. That is, if there is | |
an escape character in the related text, that escape will put the | |
editor back into command submode, and interpret any following | |
characters as screen-mode commands. (Unless one of those | |
characters returns you to text insertion submode -- then characters | |
following that insert-text command will be all be put into the | |
file, unless and until there is another escape character.) That | |
makes accidentally triggering this tool rather dangerous. | |
Quoting in a character or string that you've defined as a short | |
form via this command is simple. Type control-V before the | |
metacharacter, or the first character of the metastring, and into | |
the text it goes. Even that may not be required if you are dealing | |
with a metastring, and if the timeout option to the :set command is | |
still in its default state: turned on. In this case, all you need | |
to do is be sure that you take more than one full second to type in | |
the entire metastring, and it will have no meta effect. (This, by | |
the way, is one reason that this tool's metastrings should be short | |
-- so you can depend on being able to type one of them in less than | |
a full second when you do want the metavalue.) | |
Questions naturally arise regarding these tools. One frequent | |
query is why anyone would want to mess around with :abbreviate when | |
it seems much easier to do a general substitution command when the | |
document is complete. That is, instead of that abbreviation I set | |
up at the start of this explanation, just run a | |
:%s/\<ucb\>/University of California at Berkeley/g command | |
after all the text has been entered. There are several reasons to | |
use Vi's abbreviation feature instead: | |
It's not hard to unknowingly type in your abbreviated string where | |
you don't want it expanded, as in a direct quotation (Savio told | |
the students, "We don't want ucb to get the upper hand!"), or where | |
it has an entirely different meaning (Next, punch in the code: aQr | |
PxN ucb JHt.) Using the substitution command above, you would never | |
see that these sentences were being disfigured. But with | |
on-the-spot replacement the mistaken use would be right in your | |
face. | |
Lines can get very long when abbreviations are expanded, especially | |
when there are several abbreviations in one line. When you use a | |
general substitution command after text entry, there's no way to | |
know that certain lines have become unreasonably long. But with | |
the :abbreviate tool, you see the final length of each line as you | |
go along. And if you use the wrapmargin option to the :set | |
command, line breaks will generally be inserted after any | |
abbreviations have been expanded. | |
It's easy to forget to run a general substitution command. But a | |
:abbreviate command can be made automatic by putting it in a .exrc | |
file, and the user can see while typing whether it is or is not in | |
operation. | |
This tool can do more than save typing. For example, suppose a | |
technical writer is in the habit of typing "unix", while company | |
policy requires "UNIX(R)". Either a general substitution or the | |
:abbreviate tool will correct that writer's recurring errors, but | |
only the latter will continuously teach him that "unix" is not to | |
be used. As another example, consider a writer who begins far too | |
many sentences with the word "The", which makes for dull reading. A | |
:ab The DON'T OVERDO IT! command will ensure that every time this | |
writer types the word "The" at the start of the sentence, it will | |
promptly be transmuted into the billboard phrase "DON'T OVERDO | |
IT!", which can be backspaced over to insert a new sentence | |
beginning. Note that this will not be triggered by words such as | |
"These" or "Then", nor by the word "the" in the middle of a | |
sentence. | |
Another common question concerns precedence of metacharacters. You | |
can use most of the text-input metacharacters I've discussed | |
previously as short-form names in :map! commands. Suppose you did | |
use control-D as such a short form -- what would happen when you | |
typed control-D at the start of an autoindented line? Would it | |
wipe out the indentation or type in the phrase that :map! has | |
associated with it? Or if you used control-H as a short form? | |
When you subsequently typed a control-H during text entry, would | |
the cursor back up a space, or would type in a stored phrase? | |
The answer is that the :map! value would prevail. By preceding | |
either character with a control-V, you could type in in as itself, | |
but there would be no way to use the ordinary metavalue of either | |
character. If you were to map control-D followed immediately by | |
another control-D or two consecutive control-H characters or any | |
other doubling of an ordinary metacharacter, the situation would be | |
more complex. You could then type the two control-D or control-Hs | |
within one second to get the mapped text typed in, or you could | |
type control-D or control-H followed by a one-second pause to | |
invoke the ordinary metavalue. | |
Time for another exercise. | |
Suppose that you used control-D or control-H as a short form with | |
the :abbreviate command. Or suppose that you used some ordinary | |
character string as both an abbreviation and a mapping short form. | |
(The editor will allow you to do this.) What would happen when you | |
typed in this double-use short form during text insertion? This | |
exercise is straightforward enough that I expect most of you will | |
find the correct answer before you look at my solution. | |
Two final warnings. Do not try to define a non-alphanumeric | |
character or string as a short form with the :abbreviate command. | |
You probably will be able to do this -- the editor won't object -- | |
but when you try to use this abbreviation, nothing will happen. | |
And with either :abbreviate or :map!, do not put any metacharacter | |
as itself into the long-form string. Even if you manage to get it | |
into the string as itself, it will not go into your text that way. | |
What if you have forgotten what short forms you have set up, or are | |
uncertain as to whether some may have been set up for you via a | |
.exrc startup file? Well, you can query either tool ju st by | |
giving its setup command without any arguments. Here are examples | |
of those queries, with the responses you might receive from the | |
editor: | |
:ab | |
cat cat felix domesticus | |
wolf wolf canis lupus | |
:map! | |
{ { ^[o^I^IThe End^[ | |
} } ^[o^I^I-XXX-^[ | |
~ ~ (more to come)^[ | |
Note that each response line has at least three strings of printing | |
characters, separated by whitespace. It's that second string in a | |
line that is the short form; the string that when typed in will be | |
replaced by the last string shown. (Yes, in every example line | |
above the first string is identical to the second, but that isn't | |
always so.) The last string is what will be inserted and/or | |
executed. | |
So now you know what characters and strings will have to be quoted | |
in when you want to insert them as themselves. And if one or more | |
of those short forms is something you will be typing in so often | |
that you can't spare the time to quote it in each time you use it, | |
you can disable the metavalue for the rest of the present editing | |
session. Just give the command name for the tool that uses this | |
short form but precede it with "un", and as the only argument give | |
the short form you want to disable. For example, here are the | |
commands that will disable the first entry in each of the lists | |
above: | |
:unab cat | |
:unmap! { | |
Command-Submode Macros | |
It's common that a text editor has a facility that lets a user | |
create personalized commands, usually as macros built on existing | |
commands. The Vi/Ex editor has four such facilities -- something | |
for every need. While these facilities don't have the low-level | |
programmability of mock-Lisp, they can accomplish a lot to simplify | |
your editing, and you don't need to learn a programming language to | |
use them. | |
I'll be discussing each facility (or family) in its own section | |
below, because their structures are quite different. Nonetheless, | |
you can often combine them to go od effect, by using a macro of one | |
type to call a macro of a different type. | |
:map Macros | |
This is the editor tool that's closest to what most users think of | |
as a macro facility. It uses the command :map as its setup tool, | |
and the macros it creates operate when the user is in command | |
submode of screen-editing mode. Otherwise it works just the way | |
its very close relative, the :map! tool, works -- which I explained | |
in depth in the first half of this tutorial part, above. Consider | |
the three command lines below: | |
:map v :!wc -w %Control-M | |
:unmap v | |
:map | |
The first line sets up a macro that does a word count on the file I | |
am editing, as of the last write to storage, whenever I type the | |
letter v from command submode while I am screen editing. The | |
second unsets that macro, so that a v command no longer does | |
anything. The third displays a list of the :map macros that are | |
currently in effect. All this should be transparently plain to | |
readers who understand the :map! tool. Still, there are a few | |
points worth noting that are particularly applicable to the :map | |
side of the family. | |
Choosing a short-form for :map macros should not be difficult. | |
Half a dozen of the printing ASCII characters and many of the | |
control characters are not used as screen-editing commands or | |
addresses. Hardly any strings of two duplicate characters (such as | |
"DD" above) are in use, and most editor versions will let you map | |
such strings. You don't need to avoid duplicating your :map! short | |
forms because the name spaces are completely separate. That is, if | |
you use a particular character or string as a :map short-form and | |
also as a :map! short-form; for example: | |
:map }} :!wc -w %Control-M | |
:map! }} Control-[j0R | |
there is no conflict. The editor will allow both mappings, and | |
will use the correct long-form based on the context; whether you | |
typed }} from command or text-insertion submode. As the first | |
example above shows, your command string can include any of the | |
line-mode commands that can be invoked from screen mode, providing | |
you begin each one with a colon ":" as you would when invoking it | |
directly while in screen mode, and quote in a Control-M (the RETURN | |
character) to terminate the command. | |
Suppose that you ran the two following setup commands, either one | |
first: | |
:map Q 2dd | |
:map V 3jQ | |
The first command clearly provides that the Q command, which | |
ordinarily is the command that takes you out of screen mode and | |
into line mode, does not do that any more. Instead, it now deletes | |
two lines, and you now have no way to leave screen mode without | |
unmapping the "Q" character. But what does the new V do? | |
If you've left the :set command's remap option turned on, its | |
default value, then the V drops down three lines and then deletes | |
that third line and the one following. That is, when it comes to | |
the "Q" character in that mapping, it discovers that "Q" itself has | |
been mapped, and brings in the mapped value of "Q". But if you had | |
previously run a :se noremap command, then the editor would not | |
check for any mappings of the characters within a macro, and would | |
use the standard meaning of "Q" when it executed the "V" macro. So | |
then typing a "V" character would move you down three lines and | |
then put you into line-editing mode. (Yes, that means that while | |
you would no longer be able to execute the Q as itself directly, | |
your macros could still access it!) | |
Buffer Macros | |
There are limits to the amount of macro text you can store by | |
mapping it -- not as severe now as with earlier versions of the | |
editor, but still somewhat confining. To remedy that, the editor | |
offers a quite-similar tool with practically unlimited storage. It | |
involves those buffers where you store text pulled from your file, | |
for later reinsertion at various places. Specifically I mean the | |
twenty-six buffers named "a" through "z". | |
From screen-editing command sub-mode, you can type an at-sign "@" | |
followed by a letter of the alphabet, and the editor will take the | |
contents of the buffer with that letter-name and execute it as a | |
screen-mode command string. For example, if you have "0d3w" | |
(without the quotation marks) stored in named-buffer "k", then | |
typing @k will delete the first three words on the current line. | |
After you start using this method in your editing session, there's | |
an extra added convenience available: typing @@ will repeat the | |
last such buffer command you ran. | |
To put a command into a named buffer, get the line or lines of your | |
command into your file one way or another, then delete or yank them | |
into the buffer of your choice, as by: | |
"p3dd | |
:ya m | |
to delete a three-line macro into buffer "p" and yank a one-line | |
macro into buffer "m", respectively. You need not tell the editor | |
that you regard the contents of a buffer as a command macro until | |
you choose to execute it with a "@" command. In fact, you can use | |
a buffer's contents both ways, executing it as a command at one | |
moment and putting it back into your file as text the next. | |
One important difference from macros created by mapping: if you | |
need a linebreak character in a buffer macro, don't try to quote it | |
in. Instead, type it in the ordinary way, so that it forms a line | |
break between two lines of your macro text. And don't break a line | |
in your macro text for any other reason, because the linebreak | |
characte r that appears there will be treated as a command | |
character by the editor when you execute the buffer contents as an | |
editing command string. | |
:source Macros | |
Line-mode commands have a macro tool in this editor, too. Of | |
course you can insert most line-mode commands in the previous two | |
types of macros, but this tool is dedicated entirely to line-mode | |
commands, and can include even commands that can't be run | |
interactively from screen mode via a preceding colon. The only | |
line-mode commands that can't be run with this tool are the visual | |
and open commands. With this tool, you set up your macros by | |
putting their commands into one or more files, then invoke them | |
with command lines like: | |
:so /u/myname/commands.1 | |
Your command files should contain strictly line-mode commands, one | |
per line unless you separate them within the line by pipe "|" | |
characters, and should not have a colon before each command. The | |
other restrictions depend on how you plan to invoke your macro | |
files. Ideally you should give your source commands while you are | |
in line mode -- then the above limitations are all you will face. | |
But if you insist on invoking :source while in screen mode, there | |
are two other limitations: | |
Only the first line of your command file will execute. Due to the | |
editor restriction against running multi-line line-mode commands | |
while in screen mode, all lines after the first in your command | |
file will be silently discarded. | |
If your first command is not complete on the first line (for | |
instance, an append is not), even that command will not execute. | |
In this case the failure will not be silent. | |
Another Exercise. | |
So if you want to source in command files from within screen mode, | |
it's a very good idea to create one-line command files. But there | |
will be a few cases where multi-line command files will be a | |
worthwhile thing, even when you may be invoking them from screen | |
mode. Here's an easy exercise for you: come up with a specific | |
case in which a command file that you may source in from either | |
line or screen mode should nonetheless have more than one line. Of | |
course there are multiple possibilities here, so don't be disturbed | |
if the solution that occurs to you is not one of those I | |
arbitrarily chose for my answer. | |
When you really get into sourcing, you'll be pleased to know that | |
:source files can contain commands to call other :source files. | |
This is the basis for truly modular editor scripts, and for a raft | |
of rather tricky maneuvers. It also saves typing when you need to | |
invoke a source file from screen mode, but the list of commands is | |
simply too long to fit on one line: a single line in your initial | |
source file is long enough to call a very large number of other | |
source files, each with a single long line of commands. You will | |
probably find that invoking nested :source files from line mode | |
will turn off line mode's colon prompt, but you can turn it back on | |
again via a :se prompt command. | |
Write and Read Macros | |
The Vi/Ex editor has tools for running some or all of the lines in | |
the file you're editing through a program outside the editor, then | |
using the transformed lines to replace the original lines in your | |
file. It can also run a program with any or no input and insert the | |
program's output in your file, or write some or all of your file | |
lines as input to a program that may send its output anywhere. | |
And where is the macro capability in all this? Well, when you use | |
these tools you are not limited to standard Unix utilities as your | |
outside programs -- your own coding will do just as well. Compiled | |
or scripted, one line or a thousand, in a standard language like C | |
or Perl or in a specialized one such as Snobol; the rule is that if | |
your Unix system will execute it, the editor can pass it over your | |
text. | |
This tutorial is not going to get into writing these personal text | |
processors, in any language, so I will only be explaining how to | |
send your text in and/or out via editor tools. In the examples | |
below, I will suppose you have a text-processing program named | |
myhack that lives within your searchpath. | |
[Editor's note: One external program I use frequently reformats | |
paragraphs into nicely looking text blocks that are easier to read. | |
I use the program named reform, published on pages 320-321 in the | |
first edition of the famous book Programming Perl by Larry Wall and | |
Randal L. Schwartz. At first blush you may ask, why use such an | |
external program when I can simply set Vi's wrapmargin variable? | |
Of course, the answer is how do you easily reform paragraphs that | |
are already ragged, say due to the problem Walter posed above | |
(using find and replace to expand abbreviations, instead of | |
expanding abbreviations using the built-in Vi abbreviation macro | |
facility?] | |
Note that the command to execute the outside program should be | |
typed as you would type it at your shell prompt, because it will be | |
passed to the shell intact except for the addition of input and/or | |
output redirection. | |
If you want to take some (or all) of the lines out of your file, | |
use them as input to your outside program, then put the resulting | |
output in place of the original lines, you can use either a | |
line-mode or a screen-mode command to do it, as shown below: | |
:196,254 ! myhack -n6 | |
!L myhack -n6 | |
12!! myhack -n6 | |
!/^CHAPTER/- myhack -n6 | |
The line-mode command can be invoked from line mode, or from screen | |
mode by preceding it with a colon. In either case, you give an | |
address or address range, next the exclamation point, then | |
everything following until you type return is passed to the shell | |
as a command line. The line-mode command must have at least one | |
address because there is no default address for this command. But | |
the whitespace I show before and after the exclamation point is | |
permissible but not necessary; I put it in solely for readability. | |
Screen-mode command form is the exclamation point as the command | |
name, followed by the target address, then the outside command | |
(with arguments and/or whitespace as would be required or permitted | |
on your shell command line), ending when you hit the escape or | |
return key. As with the c d y commands, you can type two | |
consecutive exclamation points to send just the current line, and | |
use a count to send that number of lines as shown in my third | |
example command. The last example involves an extra escape | |
character -- at the end of a search pattern address, whether / or ? | |
based and including any + or - suffix, you must press the escape | |
key before you start typing the outside command. | |
You're not limited to just one outside program at a time. You can | |
pipeline two or more together as your shell permits, ordinarily | |
with the "|" character. (Because a | character and what follows it | |
will be passed to the shell, this editor command cannot appear in a | |
line-mode command string, including a :global string, unless it is | |
the last command in the string.) The final output of the pipeline | |
is what will go into your file. And you can undo the effect of the | |
outside command or pipeline, putting your file back the way it was, | |
with a u command. | |
You may not want your text to make a round trip, though. You may | |
want to send your text, as modified by your outside program, off to | |
some other destination, or you may want to pull some text into your | |
file that originated in your outside program, or was taken from | |
some outside source. In these cases, use the line-mode commands | |
that appear below: | |
:1,.w ! myhack -n6 > nufile | |
:217r ! myhack -n6 < oldfile | |
The first command above sends the initial lines from the file you | |
are editing as input to your myhack program, and redirects the | |
output to a file. It does not erase the affected lines from the | |
file you are editing. The second runs your myhack program using | |
the contents of another file as the input, then places the output | |
in the file you are editing, right after line 217. | |
Both line-mode commands are shown with addresses, but they are not | |
necessary. The default address for a :write command is the entire | |
file; for a :read command, right after the current line. The space | |
character just before the exclamation-point flag after each command | |
is absolutely essential; without it you would get something greatly | |
different from what you expected. | |
Usually there will be output redirection for the :write ! command, | |
and input redirection for the :read ! command, but not always. For | |
example, you may want to :read ! an outside command that generates | |
a pseudo-random number, using no input at all. When you do need | |
input or output, you can build the necessary redirection into your | |
outside program or you can put the redirection on the command line | |
as shown above, using your own shell's notation. | |
In The Next Installment of this Tutorial | |
I'll be putting the techniques I've taught so far to work, showing | |
how to set up the editor for special purposes. Your suggestions on | |
what special purposes to consider are welcome, of course. One | |
purpose that is already in my mind is an arrangement of the editor | |
for computerphobes: very simple, with beginner features such as | |
"stateless" editing, and fortified against common user errors. | |
SIDEBAR: The timeout Function | |
The :set command's timeout option seems arcane in purpose and | |
tricky to use, at least to some editor users. But it becomes | |
pretty plain when you know why and how it actually works. | |
Basically, when the timeout option is on (its default state) and | |
you type in a short form you've set up by a :map or :map! command, | |
you must type the entire short form in no more than one second. If | |
you miss that deadline, the editor will ignore the metavalue, and | |
take the characters you've typed at their face value. | |
This odd requirement serves a purpose; preventing deadlock. As an | |
example, suppose you have defined "DD" (without the quotation | |
marks) as a macro via the :map command, and have turned off the | |
timeout option. Now, while editing, you type a plain D command to | |
delete part of a line. When the editor receives this single "D" it | |
is uncertain what to do. Are you actually telling it to delete | |
that partial line? Or are you starting to type in your double-D | |
macro? The only way the editor can resolve this question is to | |
wait and see what character you type in next. But if you are | |
waiting to see the result of your deletion before you do any more | |
editing, the mutual wait will last indefinitely. With the timeout | |
option left turned on, the wait will only be a second or so before | |
the editor acts on your D command. | |
One moral of this story is to leave timeout on unless you have a | |
compelling reason to turn it off, and choose your macro names so | |
that you can easily type them in within the one-second limit. If | |
you are not particularly nimble fingered, or if other people may be | |
using your editor macros, then for practical purposes this means | |
either a single character or two repetitions of one character as in | |
my example above. (Some fussy versions of the editor will refuse to | |
map anything except a single character.) | |
Another moral is to avoid certain macro names, such as "jj" (again, | |
without the quotation marks). The standard address j is one that | |
you might want to type twice in rapid succession, to move directly | |
down two lines without the trouble of reaching away from the | |
central keyboard to hit the 2 key. But the user with a macro named | |
jj had better not move down too quickly via that method, or he/she | |
will accidentally invoke the macro of that name. | |
Finally, you should realize that the one-second count before timing | |
out is not hair-splittingly accurate. The design of the standard | |
Unix software clock means that the time-out interval may be a | |
little less or somewhat more than precisely one second. | |
Solutions | |
Back to the index |