home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


Book HomeLearning the vi EditorSearch this book

7.3. Saving Commands

Often you type the same long phrases over and over in a file. vi and ex have a number of different ways of saving long sequences of commands, both in command mode and in insert mode. When you call up one of these saved sequences to execute it, all you do is type a few characters (or even only one), and the entire sequence is executed as if you had entered the whole sequence of commands one by one.

7.3.1. Word Abbreviation

You can define abbreviations that vi will automatically expand into the full text whenever you type the abbreviation in insert mode. To define an abbreviation, use the ex command:

:ab abbr phrase

abbr is an abbreviation for the specified phrase. The sequence of characters that make up the abbreviation will be expanded in insert mode only if you type it as a full word; abbr will not be expanded within a word.

Suppose in the file practice you want to enter text that contains a frequently recurring phrase such as a difficult product or company name. The command:

:ab imrc International Materials Research Center
abbreviates International Materials Research Center to the initials imrc. Now whenever you type imrc in insert mode, imrc expands to the full text.

Keystrokes Results
ithe imrc Figure

Abbreviations expand as soon as you press a non-alphanumeric character (e.g., punctuation), a space, a carriage return, or ESC (returning to command mode). When you are choosing abbreviations, choose combinations of characters that don't ordinarily occur while you are typing text. If you create an abbreviation that ends up expanding in places where you don't want it to, you can disable the abbreviation by typing:

:unab abbr

To list your currently defined abbreviations, type:

:ab

The characters that compose your abbreviation cannot also appear at the end of your phrase. For example, if you issue the command:

:ab PG This movie is rated PG

you'll get the message "No tail recursion," and the abbreviation won't be set. The message means that you have tried to define something that will expand itself repeatedly, creating an infinite loop. If you issue the command:

:ab PG the PG rating system

you may or may not produce an infinite loop, but in either case you won't get a warning message. For example, when the above command was tested on a System V version of UNIX, the expansion worked. Circa 1990 on a Berkeley version, the abbreviation expanded repeatedly, like this:

the the the the the ...

until a memory error occurred and vi quit.

When tested, we obtained the following results on these vi versions:

Solaris 2.6 vi
The tail recursive version is not allowed, while the version with the name in the middle of the expansion only expands once.

nvi 1.79
Both versions exceed an internal expansion limit, the expansion stops, and nvi produces an error message.

elvis 2.0
The tail recursive version runs infinitely until the editor is interrupted. The version with the name in the middle eventually stops expanding, but without any error message.

vim 5.0 and 5.1
Both forms are detected and only expand once.

vile 7.4 and 8.0
Both forms are detected and only expand once.

We recommend that you avoid repeating your abbreviation as part of the defined phrase.

7.3.2. Using the map Command

While you're editing, you may find that you are using a command sequence frequently, or you may occasionally use a very complex command sequence. To save yourself keystrokes, or the time that it takes to remember the sequence, you can assign the sequence to an unused key by using the map command.

The map command acts a lot like ab except that you define a macro for vi's command mode instead of for insert mode.

:map x sequence
Define character x as a sequence of editing commands.

:unmap x
Disable the sequence defined for x.

:map
List the characters that are currently mapped.

Before you can start creating your own maps, you need to know the keys not used in command mode that are available for user-defined commands:

Letters
g K q V v

Control keys
^A ^K ^O ^W ^X

Symbols
_ * \ =

NOTE: The = is used by vi if Lisp mode is set, and to do text formatting by several of the clones. In many modern versions of vi, the _ is equivalent to the ^ command, and elvis and vim have a "visual mode" that uses the v, V, and ^V keys. The moral is to test your version carefully.

Depending on your terminal, you may also be able to associate map sequences with special function keys.

With maps you can create simple or complex command sequences. As a simple example, you could define a command to reverse the order of words. In vi, with the cursor as shown:

you can the scroll page

the sequence to put the after scroll would be dwelp: delete word, dw; move to the end of next word, e; move one space to the right, l; put the deleted word there, p. Saving this sequence:

:map v dwelp

enables you to reverse the order of two words at any time in the editing session with the single keystroke v.

7.3.3. Protecting Keys from Interpretation by ex

Note that when defining a map, you cannot simply type certain keys, such as RETURN, ESC, BACKSPACE, and DELETE as part of the command to be mapped, because these keys already have meaning within ex. If you want to include one of these keys as part of the command sequence, you must escape the normal meaning by preceding the key with CTRL-V. The keystroke ^V appears in the map as the ^ character. Characters following the ^V also do not appear as you expect. For example, a carriage return appears as ^M, escape as ^[, backspace as ^H, and so on.

On the other hand, if you want to use a control character as the character to be mapped, in most cases all you have to do is hold down the CTRL key and press the letter key at the same time. So, for example, all you need to do in order to map ^A is to type:

:map CTRL-A sequence

There are, however, three control characters that must be escaped with a ^V. They are ^T, ^W, and ^X. So, for example, if you want to map ^T, you must type:

:map CTRL-V CTRL-T sequence

The use of CTRL-V applies to any ex command, not just a map command. This means that you can type a carriage return in an abbreviation or a substitution command. For example, the abbreviation:

:ab 123 one^Mtwo^Mthree

expands to this:

one
two
three

(Here we show the sequence CTRL-V RETURN as ^M, the way it would appear on your screen.)

You can also globally add lines at certain locations. The command:

:g/^Section/s//As you recall, in^M&/

inserts, before all lines beginning with the word Section, a phrase on a separate line. The & restores the search pattern.

Unfortunately, one character always has special meaning in ex commands, even if you try to quote it with CTRL-V. Recall that the vertical bar (|) has special meaning as a separator of multiple ex commands. You cannot use a vertical bar in insert mode maps.

Now that you've seen how to use CTRL-V to protect certain keys inside ex commands, you're ready to define some powerful map sequences.

7.3.4. Complex Mapping Example

Assume that you have a glossary with entries like this:

map - an ex command which allows you to associate
a complex command sequence with a single key.

You would like to convert this glossary list to troff format, so that it looks like this:

.IP "map" 10 n
An ex command...

The best way to define a complex map is to do the edit once manually, writing down each keystroke that you have to type. Then recreate these keystrokes as a map. You want to:

  1. Insert the MS macro for an indented paragraph at the beginning of the line. Insert the first quotation mark as well (I.IP ").

  2. Press ESC to terminate insert mode.

  3. Move to the end of the first word (e) and add a second quotation mark, followed by a space and the size of the indent (a" 10n).

  4. Press RETURN to insert a new line.

  5. Press ESC to terminate insert mode.

  6. Remove the hyphen and two surrounding spaces (3x) and capitalize the next word (~).

That will be quite an editing chore if you have to repeat it more than just a few times.

With :map you can save the entire sequence so that it can be re-executed with a single keystroke:

:map g I.IP "^[ea" 10n^M^[3x~

Note that you have to "quote" both the ESC and RETURN characters with CTRL-V. ^[ is the sequence that appears when you type CTRL-V followed by ESC. ^M is the sequence shown when you type CTRL-V RETURN.

Now, simply typing g will perform the entire series of edits. At a slow baud rate you can actually see the edits happening individually. At a fast baud rate it will seem to happen by magic.

Don't be discouraged if your first attempt at key mapping fails. A small error in defining the map can give very different results from the ones you expect. Type u to undo the edit, and try again.

7.3.5. More Examples of Mapping Keys

These examples will give you an idea of the clever shortcuts possible when defining keyboard maps:

  1. Add text whenever you move to the end of a word:

    :map e ea

    Most of the time, the only reason you want to move to the end of a word is to add text. This map sequence puts you in insert mode automatically. Note that the mapped key, e, has meaning in vi. You're allowed to map a key that is already used by vi, but the key's normal function will be unavailable as long as the map is in effect. This isn't so bad in this case, since the E command is often identical to e.

  2. Transpose two words:

    :map K dwElp

    We discussed this sequence earlier in the chapter, but now you need to use E (assume here, and in the remaining examples, that the e command is mapped to ea). Remember that the cursor begins on the first of the two words. Unfortunately, because of the l command, this sequence (and the earlier version) doesn't work if the two words are at the end of a line: during the sequence, the cursor ends up at the end of the line, and l cannot move further right. Here's a better solution:

    :map K dwwP

    You could also use W instead of w.

  3. Save a file and edit the next one in a series:

    :map q :w^M:n^M

    Notice that you can map keys to ex commands, but be sure to finish each ex command with a carriage return. This sequence makes it easy to move from one file to the next and is useful when you've opened many short files with one vi command. Mapping the letter q helps you remember that the sequence is similar to a "quit."

  4. Put troff emboldening codes around a word:

    :map v i\fB^[e\fP^[

    This sequence assumes that the cursor is at the beginning of the word. First, you enter insert mode, then you type the code for the bold font. In map commands, you don't need to type two backslashes to produce one backslash. Next, you return to command mode by typing a "quoted" ESC. Finally, you append the closing troff code at the end of the word, and you return to command mode. Notice that when we appended to the end of the word, we didn't need to use ea, since this sequence is itself mapped to the single letter e. This shows you that map sequences are allowed to contain other mapped commands. (The ability to use nested map sequences is controlled by vi's remap option, which is normally enabled.)

  5. Put troff emboldening codes around a word, even when the cursor is not at the beginning of the word:

    :map V lbi\fB^[e\fP^[

    This sequence is the same as the previous one, except that it uses lb to handle the additional task of positioning the cursor at the beginning of the word. The cursor might be in the middle of the word, so you want to move to the beginning with the b command. But if the cursor were already at the beginning of the word, the b command would move the cursor to the previous word instead. To guard against that case, type an l before moving back with b, so that the cursor never starts on the first letter of the word. You can define variations of this sequence by replacing the b with B and the e with Ea. In all cases, though, the l command prevents this sequence from working if the cursor is at the end of a line. (You could append a space to get around this.)

  6. Repeatedly find and remove parentheses from around a word or phrase: [31]

    [31]From the article by Walter Zintz, in UNIX World, April 1990.

    :map = xf)xn

    This sequence assumes that you first found an open parenthesis, by typing /( followed by RETURN.

    If you choose to remove the parentheses, then use the map command: delete the open parenthesis with x, find the closing one with f), delete it with x, and then repeat your search for an open parenthesis with n.

    If you don't want to remove the parentheses (for example, if they're being used correctly), then don't use the map command: press n instead to find the next open parenthesis.

    You could also modify the map sequence above to handle matching pairs of quotes.

  7. Place C/C++ comments around an entire line:

    :map g I/* ^[A */^[

    This sequence inserts /* at the line's beginning and appends */ at the line's end. You could also map a substitute command to do the same thing:

    :map g :s;.*;/* & */;^M

    Here, you match the entire line (with .*), and when you replay it (with &), you surround the line with the comment symbols. Note the use of semicolon delimiters, to avoid having to escape the / in the comment.

  8. Safely repeat a long insertion:

    :map ^J :set wm=0^M.:set wm=10^M

    We mentioned in Chapter 2, that vi occasionally has difficulty repeating long insertions of text when wrapmargin is set. This map command is a useful workaround. It temporarily turns off the wrapmargin (by setting it to 0), gives the repeat command, and then restores the wrapmargin. Note that a map sequence can combine ex and vi commands.

In the previous example, even though ^J is a vi command (it moves the cursor down a line), this key is safe to map because it's really the same as the j command. There are many keys that either perform the same tasks as other keys or that are rarely used. However, you should be familiar with the vi commands before you boldly disable their normal use by using them in map definitions.

7.3.6. Mapping Keys for Insert Mode

Normally, maps apply only to command mode—after all, in insert mode, keys stand for themselves and shouldn't be mapped as commands. However, by adding an exclamation mark (!) to the map command, you can force it to override the ordinary meaning of a key and produce the map in insert mode. This feature is useful when you find yourself in insert mode but need to escape briefly to command mode, run a command, and then return to insert mode.

For example, suppose you just typed a word but forgot to italicize it (or place quotes around it, etc.). You can define this map:

:map! + ^[bi<I>^[ea</I>

Now, when you type a + at the end of a word, you will surround the word with HTML italicization codes. The + won't show up in the text.

The sequence above escapes to command mode (^[), backs up to insert the first code (bi<I>), escapes again (^[), and moves ahead to append the second code (ea</I>). Since the map sequence begins and ends in insert mode, you can continue entering text after marking the word.

Here's another example. Suppose that you've been typing your text, and you realize that the previous line should have ended with a colon. You can correct that by defining this map sequence:[32]

[32]From an article by Walter Zintz, in UNIX World, April 1990.

:map! % ^[kA:^[jA

Now, if you type a % anywhere along your current line, you'll append a colon to the end of the previous line. This command escapes to command mode, moves up a line, and appends the colon (^[kA:). The command then escapes again, moves down to the line you were on, and leaves you in insert mode (^[jA).

Note that we wanted to use uncommon characters (% and +) for the previous map commands. When a character is mapped for insert mode, you can no longer type that character as text.

To reinstate a character for normal typing, use the command:

:unmap! x

where x is the character that was previously mapped for insert mode. (Although vi will expand x on the command line as you type it, making it look like you are unmapping the expanded text, it will correctly unmap the character.)

Insert-mode mapping is often more appropriate for tying character strings to special keys that you wouldn't otherwise use. It is especially useful with programmable function keys.

7.3.7. Mapping Function Keys

Many terminals have programmable function keys (which are faithfully emulated by today's terminal emulators on bitmapped workstations). You can usually set up these keys to print whatever character or characters you want using a special setup mode on the terminal. However, keys programmed using a terminal's setup mode only work on that terminal; they may also limit the action of programs that want to set up those function keys themselves.

ex allows you to map function keys by number, using the syntax:

:map #1 commands

for function key number 1, and so on. (It can do this because the editor has access to the entry for that terminal found in either the terminfo or termcap database and knows the escape sequence normally put out by the function key.)

As with other keys, maps apply by default to command mode, but by using the map! commands as well, you can define two separate values for a function key—one to be used in command mode, the other in insert mode. For example, if you are an HTML user, you might want to put font-switch codes on function keys. For example:

:map #1 i<I>^[
:map! #1 <I>

If you are in command mode, the first function key will enter insert mode, type in the three characters <I>, and return to command mode. If you are already in insert mode, the key will simply type the three-character HTML code.

NOTE: If function keys have been redefined in the terminal's setup mode, the #n syntax might not work since the function keys no longer put out the expected control or escape sequence as described in its terminal database entry. You will need to examine the terminfo source (or termcap entry) for your terminal and check the definitions for the function keys. In addition, there are some terminals whose function keys perform only local actions and don't actually send any characters to the computer. Such function keys can't be mapped.

The terminal capabilities k1, k2 through k0 describe the first ten function keys. The capabilities l1, l2 through l0 describe the remaining function keys. Using your terminal's setup mode, you can change the control or escape sequence output by the function key to correspond with the terminfo or termcap entry. (For more information, see termcap & terminfo, published by O'Reilly & Associates.)

If the sequence contains ^M, which is a carriage return, press CTRL-M. For instance, in order to have function key 1 available for mapping, the terminal database entry for your terminal must have a definition of k1, such as:

k1=^A@^M

In turn, the definition:

^A@^M

must be what is output when you press that key.

To see what the function key puts out, use the od (octal dump) command with the -c option (show each character). You will need to press RETURN after the function key, and then CTRL-D to get od to print the information. For example:

$ od -c
^[[[A
^D
0000000 033   [   [   A  \n
0000005

Here, the function key sent Escape, two left brackets, and an A.

7.3.9. Mapping Multiple Input Keys

Mapping multiple key strokes is not restricted just to function keys. You can also map sequences of regular keystrokes. This can help make it easier to enter certain kinds of text, such as SGML or HTML.

Here are some :map commands, thanks to Jerry Peek, co-author of O'Reilly's Learning the UNIX Operating System, which make it easier to enter SGML markup. (The lines beginning with a double quote are comments. This is discussed below in Section 7.4.4.)

" ADR: need this
:set noremap
" bold:
map! =b </emphasis>^[F<i<emphasis role=bold>
map =B i<emphasis role=bold>^[
map =b a</emphasis>^[
" Move to end of next tag:
map! =e ^[f>a
map =e f>
" footnote (tacks opening tag directly after cursor in text-input mode):
map! =f <footnote>^M<para>^M</para>^M</footnote>^[kO
" Italics ("emphasis"):
map! =i </emphasis>^[F<i<emphasis>
map =I i<emphasis>^[
map =i a</emphasis>^[
" paragraphs:
map! =p ^[jo<para>^M</para>^[O
map =P O<para>^[
map =p o</para>^[
" less-than:
map! *l &lt;
...

Using these commands, to enter a footnote you would enter insert mode, and type =f. vi would then insert the opening and closing tags, and leave you in insert mode between them:

All the world's a stage.<footnote>
<para>
_
</para>
</footnote>

Needless to say, these macros proved quite useful during the development of this book.



Library Navigation Links

Copyright © 2003 O'Reilly & Associates. All rights reserved.