Garrett Hildebrand's vi Page

vi Intro

I use vi as my editor-of-choice. I recall when I was first learning vi, about 1990, I was moving off of another editor (which shall go unnamed) when a friend made the remark, "you should only _have_ to learn one editor in your lifetime. There are simply too many other things to worry about." It is not likely that I shall spend any more time on Emacs, but you never know.

Regarding the vi versus emacs debate, I like this letter by Tim O'Reilly: http://www.oreilly.com/pub/a/oreilly/ask_tim/1999/unix_editor.html.

So, what will you get from reading this Web page? Well, besides the usual pointers to things written by other poeple, I have some interesting tips and tricks, and one really "geek chic" macro to share.

vi Books

A Guide to vi is the book I used to learn vi, but is unfortunately out of print. Perhaps you can find it in a used bookstore somewhere:

A Guide to vi
Visual Editing on the UNIX System

By Dan Sonnenschein 2nd Edition 1987
Prentice Hall, Englewood Cliffs, NJ
ISBN: 0-13-371311-3 025
180 pages, $29.95


Learning the vi editor
6th edition, November 1998
ISBN 1-56592-426-6.

Editions 1 through 5 were authored by Linda Lamb, and these are now out of print. Arnold Robbins name appears with Linda Lamb on the 6th edition's list of authors, but Tim O'Reilly helped too: "While I don't have an author credit on the cover, I actually wrote a large part of the fourth and fifth editions, including the opening paean to the virtues of vi." -- Tim O'Reilly, Jun 21 1999, in response to an "ask Tim" email.


vi Help

vi (pronounced vee-eye, as most people think of it), is a unix-based program which has a certain guaranteed subset of functionality. What this means is that help for vi should be useful as help for other programs--such as Elvis and Vim--which are based upon vi. Here are some Web-based helpers to using it:

Vim Help

vi Articles

In what used to be SunWorld I read an article on vi which has disappeared from the Web are some good tips on using buffers. This has remained lost until Jonathan Cohen <JonathanC@BooksonTape.com> found an updated version of it at itWorld.com with a publishing date of 10/02/2001 (The article was written well before this date, however). See
Grasping more of the vi editor at http://www.itworld.com/Comp/2378/swol-0397-unix101/
There is a short and simple article on vi by Dru Lavigne <genisis@istar.ca> which has a nice beginners summary of the basic and often-used commands, but note that the suggested :!! cal should actually be !! cal. See
Using the vi Editor at http://www.onlamp.com/pub/a/bsd/2001/10/25/FreeBSD_Basics.html

Elvis

Elvis is a DOS-based version of vi; actually, it is an emulation. I find it to be satisfactory for DOS. Here are a few things you might be interested in:

Vim

Vim is reportedly an improved version of vi. If you prefer a GUI interface on a Windows platform, use VIM (like Elvis, it runs on Intel PCs as well as UNIX). I began to experiment with it in 1997 and find it to be quite satisfactory, but I still use what comes with UNIX as my vi editor. I was pointed to it by Sandy Herring, who says, "... a BIG improvement of the UNIX editor vi." I have not been quite so sucked up into the vim hype, but I'd be lost without it on Windows platforms.



Using vi with other Tools

To use vi as your command line history editor at the Unix shell, you need to tell the shell that this is your preference.

At the bash Shell prompt: set -o vi

At the Korn Shell prompt: set -o vi

At the tcsh Shell prompt: bindkey -v

To get into the vi editor to look at or modify your history, simply type <ESC>k and then you move up or down in the stack with j and k. Note that searches should be done with ? when you first go in, because you are at the bottom, not the top.

To use vi as your editor for the Pine email tool, simply go to the configuration setup (M for main menu, S for setup, C for configuration, then use w to search for 'editor') and set the editor to vi. In the .pinerc file it will appear thus:

# Specifies the program invoked by ^_ in the Composer,
# or the "enable-alternate-editor-implicitly" feature.
editor=vi
Also, the following features should be enabled, or added to the .pinerc features list (e.g., features=)
enable-alternate-editor-cmd,
enable-alternate-editor-implicitly
This way, when you go from the header of the email to the body--where Pico would normally be invoked, you will go right into vi.

vi Tricks

There are a few things which are not obvious from reading the man pages, FAQs, quick-reference guides, and so-on, which are too cute not to mention as anything else but a trick. In here I've listed some I've come to like.

But first I'll mention a few things. :g/search-string/ can be placed in front of several ex commands, and ex underlies vi. The two most common commands I'd use here would be m and d, with 'd' to delete lines which are matched up, and 'm' used to move the matched lines to somewhere. As an example of the latter, :g/X-/m0 will move all lines which contain 'X-' to the top of the file. But the trick here is that the 'g' may be replaced with a 'v' to cause an inverse effect. In other words, all lines which do not match are affected by the command.

Also, many people use the 'u' command to undo. But few use the 'U' command. The difference is that 'u' undoes the last change, but 'U' undoes all changes made to the current line (so long as you are still on it).

Command String Description of Action
:v/./d or :g/^$/d Delete all empty lines. Add match for blanks if required.
:%s/^$/^M Add a new blank line to all existing blank lines. ^M is control-v, control-m
:v/^flort/s/^t&/ Insert a tab before any line that does _not_ with 'flort'. ^t is actually a tab character
:/flort/,/flortx/d Delete from the first occurance of 'flort' to the first occurance of 'flortx'.
:/flort/:r filex Find the first occurance of 'flort' and insert under that line the contents of 'filex'
:/flort/:r !date Find the first occurance of 'flort' and insert under that line the output of the shell command 'date' (or any other shell command)
:g/./join One of my favorite discoveries. Joins each pair of lines.
:v/^str/-1join Created from a question by John Lenning
:v/^...........$/d Created by John Lenning while working on Puzzle #35 ("They've got it all") of Matt Gaffney's Weekly Crossword Contest. Answers in the grid provided clues to the first and last names of 3 people who's collective names were pangrammatic. One was an ex-NHL allstar for 11 letters. John copied a list of players from a web page and pasted them into a vi session, then after removing white space used this command to delete all lines that did not have eleven letters. My version of that was to take the cleaned-up file and use "awk 'BEGIN { FS=RS } {print length, $0}' scratchfile | sort -n" (awk puts length numbers in front of each line) but I think John's is much more elegant.
:v/./.,/./-1join Replace two or more blank lines with one blank line. :v is like :g except that the regex (which is /./ in this case, or match a character) is inverted for the global scope. Blank lines do not match.
:.,/^$/g/./.,/^$/j This has the same scope as !} (e.g., a paragraph) but it allows the joining of all lines in it. 'j' is the same as 'join' and is an ex command. Since !} takes you out to the shell, we do this odder format to get to the ex command underlying vi.
:.,/^$/-1j If you liked the previous :.,/^$/g/./.,/^$/j, you'll really like this shorter, alternative form, which essentially does the same thing. Adding the -1 fixes a problem the new form introduces, which is to strip out the white space as well. The minus one leaves it there. I store it in my .exrc file as map =j :.,/^$/-1j^M using control-v control-m to get the ^M.
:.,$g/./.,/^$/-1j Here we have taken the "join paragraph lines" formula in its longer form of :.,/^$/g/./.,/^$/j and modified the file-scope from paragraph to current position to end-of-file (assuming there is a blank line at the end of the last paragraph). All paragraphs delimited by white-space lines will become one-line paragraphs separated by the white space.
:.,+2j Join the current line with the next 2 (3 lines joined).
:g/.*/.,+2j Like the previous join but global. The /.*/ matches all lines, and every 3 lines are joined. If you have no blank lines, you can simply use /./ instead. Inspired by a question from JJ at the University of Alcala, Spain, in August of 2006.
o%s/.<esc>x9p

a/&_<esc>"add:@a

JJ again. He wanted to be able to insert a character after a particular column (which is KNOWN to exist). What this does is build a statement like '%s/........./&_' on a new open line, which is then deleted into buffer 'a' and then executed via ex as '@a'. The number 9 is arbitrary (could be any column), and the underscore could be any character or string. But the columns (its global) must exist or you get a pattern matching error. This beats using a mouse, IMHO :-). the '.<esc>x9p' part puts '.' in the buffer and then '9p' pastes it on the line 9 times (you could just type them all in).
:g/rex/s/$/str/ (where 'rex' is short for 'regex'). On any line in the file which contains the regular expression specified then append to those lines only the string 'str'. Many variations on this formula can exist and be interesting.
:.!fmt Format the current line only (Unix)
!}fmt Format the current paragraph, (e.g., to next whitepspace; UNIX)
!3}fmt Format the current and next two paragraphs (or next n-1; UNIX)
:.,/regex/!fmt Format from the current line to matching regex, say, a word (UNIX)
!Gfmt Format everything from the current line to the end of the file (UNIX)
:%s g Do a previous substitution on a global basis. Great for when you forget the % (which is really 1$) the first time. Doh.
:.,$s g Do a previous substitution from the current line to the end of the file.
:s g Do a previous substitution globally on the current line only.
:%s Do a previous substitution on the first matching occurance of all lines, but not globally on the line.
:g/flort/s/foo/bar/g Replace all occurances of 'foo' with 'bar' BUT ONLY on lines which contain the string 'flort'.
xp Swap the location the character at the cursor and the one following.
ddp Swap the order of two lines.
uu Go to the location of the last change made. It is actually simply two undo commands, but it has that effect if you are elsewhere in the file than the last change made. This tip is from Victor Langeveld, who cautions, "'uu' doesn't work with vim: in vim you'd use 'u^R' with the default settings."
:g/.*/m0 Reverses the order of the lines in a file.
ma Mark a location you want to come back to with marker a (or b, or c, or d...). Use in conjunction with 'a (or 'b, 'c, 'd, etc...).
'a After moving to another part of the file, you type single-quote followed by the marker letter (a, b, c, d, etc.). to go back.
'' Typing in two single-quotes after a search or nG (where n is {1,2,3...n}) will return you to where you were before the search. Very handy for going off and double-checking something else in the file, then returning to where you are currently editing. Also, doing '''' (four single-quottes) as '' <pause> '' will allow you to toggle back-and-forth between the two locations, visually comparing. Doing an update of some kind at the searched-to location does not seem to affect this operation.
s/./\u&/g This makes all the characters on the current line upper-case. Replacing the '\u' with \l will do the reverse. I found this on Nick Gammon's vi page, but he uses s/\<./\u&/g. I found that dropping the '\<' seemed to have no effect in vi on SunOS.
:.! tr '[a-z]' '[A-Z]' Another way to make everything on the current line upper-case. Note, however, that this can be of the form !) or !} (i.e., !}tr '[a-z]' '[A-Z]') to change either the current sentence or the current paragraph.
. and & Simply typing in a period '.' after doing some sort of simple change such as r or x will cause the last change to be repeated. This is very useful for changing a numeral or letter in a column of text, by doing j. over and over again (go down one line in the same column and do the last one-character change again). The '&' repeats the last substitute using s/regex/replacement/ but remember :%s g to do it globally.
d0 This is not really a trick, yet it is amazing how many people are ignorant of it. It is more of a practice. I use d0 all the time to delete text prior to my cursor position and up to the start of the line. The analog is d$, or delete until end-of-line.
dG (and dnG) This also is not a trick, and like d0 it is amazing how many people are ignorant of it as novices. And in fact, I looked at three fairly good vi "cheat sheets" that came off of some pretty thorough vi web sites that did not mention it at all. But anyway, this will delete from the currrent line to the end of the file. Bam! Just like Emmeril adding spices to a dish. dnG deletes from the current cursor position to line 'n'. So d1G will delete to the top of the file.
ctd ... where 'd' is any delimiter. Use this for changing text between two quotes, for example. Place your cursor on the 1st character after the open quote, do ct", then type in the new text (such as a URL). Then hit <ESC> to pop out of change mode and back into edit mode. The trailing " will be preserved.
H:.,+22list If you execute this you will first move to the top line on the current screen, then all lines on the screen will have a dollar-sign '$' placed at the end, and you will be prompted by the message, [Hit return to continue]. When you hit return, (enter key), you will be back in normal mode again. Useful for seeing if there are trailing blanks at the end of the lines. To check just the line you are on, you can do :.list or simply go to the end of the line with $.
:.= and := :.= gives the current line number, := shows total lines in file. So does <ctrl>G.


vi macros and the .exrc file

Let's begin with my .exrc file, which gives me some interesting leverage, because it contains macros I use:

set showmode
set ignorecase
" set directory=/var/tmp
"
map , :n^M map =i :r $HOME/.include.headers^M
map =dd :!rm %^M^M:n^M
map K $a ^[Bi^M^[y$$pBhxi^M^[$ma^!`awc -m^Md0i(^[$a)^[kJkJ$
map =f !}fmt^M map =s :r ~/.signature^M
map =w :w^M:n^M
map =c >>d0:co.^Mk:s/./x/g^M40A x^[079lD:s/x//g^MJ
map V :w^M:!ispell -x %^M:e!^M^M

Before you start using the .exrc file, check out this advice from Sandy Herring <sandy@herring.org>:

"I set up my .exrc file and nothing new happened! I did some reading (using the links you provided) and discovered that my .profile exports values to EXINIT. I changed my .profile to not do that and now I have the extra functionality I wanted. It may be worthwhile mentioning that restriction."

Please note, these lines should *not* be cut-and-pasted into your .exrc file. Each should be entered with vi, so that the two- character sequences '^M' and '^[' which appear here are correctly created as the single control characters they really are. They are created as follows: each '^[' should be entered as control-v followed by the escape-key, and each '^M' should be entered as control-v followed by control-m (vi allows actual control characters to be entered in command lines as long as they are preceeded by control-v).

If you would like to save yourself all of the trouble of typing this in, you can simply bag my .exrc file in uuencoded format.

An explantion for each line of the sample .exrc:

set showmode
allows the mode of operation to be displayed in the lower, right-hand corner of the screen. For example, APPEND MODE appears when using the 'a' command, and INSERT MODE appears when using 'i'.

set ignorecase
causes all searches with '/' or '?' to be case insensitive. You can turn this off, if you need to, by simply entering ':set noignorecase'. More often than not, I find it better to start with case insensitivity as the default.

set directory.
This somewhat obscure directive is, in this case, setting the scratch area to the usual UNIX default: /var/tmp. I include it in my .exrc file so that I: a) won't forget the command, which what I used to do, scratching my head when I needed it and, b)make it easy to change to /tmp. You see, when vi starts up, it requires a scratch space larger than the size of the file being edited. If your /var file area is only twenty megabytes, and the file you need to look at is twenty-four, the only way around this is to switch over to /tmp for a bit. You need to set it in .exrc for it to work properly in this situation.
I keep this commented-out in my .exrc file ('"' is the comment character) so it has no effect unless I change it. Watch out using /tmp; that space is cleared out if the system crashes and reboots!


map =i :r $HOME/.include.headers^M
So =i simply reads in the following two lines from the file .include.headers:

    --------------------------- Begin Include ------------------------------
    --------------------------- E N D Include ------------------------------
    
to move to the next one. The '^M' is created by entering in <ctrl>v followed by <ctrl>m.
map , :n^M
allows me to vi a list of files, and simply enter ',' to move to the next one. The '^M' is created by entering in <ctrl>v followed by <ctrl>m.


map =w :w^M:n^M
is another one I use in a list of files. I get tired of doing ':w'<return> followed by ':n'<return>. Now, I just type in '=w' and I am on my way to the next file. I map all of my really baroque macros with an '=' lead-in, so that I don't trip them by accident.

map =f {!}fmt^M
is a really useful one, allowing the current paragraph to be reformatted quickly. It puts the cursor at the start of the paragraph, then formats to the end of the paragraph. If you need something other than the default, you can do it manually, as in '!}fmt -45', for example.

I recently found a way to put the cursor back at the end of the word you were on when you started the fmt, after being asked about that by Ola Rauer <Ola.Rauer@med.lu.se> This is it:

map F BEa /flort+x+y+z^[B"mdfzhpBx{!}fmt -68^M@m^MdfzxBE

Basically, it adds a tag (which we hope is unique: 'flortx+y+z') and then stores it in buffer m as /flortx+y+z which it can later use to find that spot again, deleting the added tag. I think I'd change it so it does not go to the beginning of the paragraph but rather begins the fmt from where you are, but that is not what I was being asked about.



map K $a ^[Bi^M^[y$$pxBhxi^M^[$xma^!`awc -m^Md0i(^[$a)^[kJkJ$
This is really my most bizarre macro. It assumes that the user's cursor is somewhere on a word for which the user wants to append ' (n)', where 'n' is the number of characters in the word, and it further assumes that this word is the last one on a line. As an example of what it would do, if the cursor were anywhere on the word 'flort', what would be returned is 'flort (6)', with the cursor resting on the ')' in command-mode when done.

Why have something like this? Well, let's just say that it has something to do with a prediliction for crossword puzzles and entering-in memorable words in a clues file. :-)

Before I explain how it works, let me say that 'ddPp' to duplicate a line (delete, paste upwards, paste downwards) nor 'Yp' will work in a global macro in the standard vi, so if you are wondering about the rather odd sequence of y$$a ^[pBhxi^M^[ you now have your answer, and if you don't get that, it is explained in two steps, below.

  • $a ^[Bi^M^[ Appends a space to the end of the line, and then moves back one WORD (B rather than b, for word), makes sure the cursor is on the beginning of the word in question, then goes into insert mode, putting it on a new line, then escapes back into command mode with the cursor on the 1st letter of the word.
  • y$$pxBhxi^M^[ is nothing more than a strange way to copy a line. Recall that the line in question is really a word which has a space appended to the end of it. What we do is y$ to get the contents of the line, then go to the end of it with $ and then with p paste itself on the end. xBhx removes the trailing space on each word. i^M^[ simply inserts a new line between the word and it's copy, and leaves us in command mode. This is all because Yp won't work, and the next work-around I thought of (i.e., ddPp) did not work in the macro, either. Anyone having a better idea please let me know.
  • $ma^!`awc -m^M moves the cursor to the end of the 2nd word copy and marks that location as "Mark 'a'", moves the cursor back to the beginning of the line (and the word), and executes a shell command of 'wc -m' from the start of the word to the end of it (the mark), which causes the word to be replaced by 5 spaces and a number representing the number of characters in the word. I should point out that using a scope of !`a simply makes sure the next lines do not get included in the mess, becaue a bug in ! causes the whole current line to get replaced.
  • d0i(^[$a)^[ deletes the spaces in front of the number, inserts an open-paren, then moves after the number and appends a close-paren.
  • kJkJ$ goes up one line to the original word, appends the line which has the '(n)', then moves up another line and re-appends the word with its parenthetical number to the original line from whence it came.

So, let's say I start with this line:

Execrable: VeryBad (7); Wretched (8)

and I want to add the word 'Detestable', so I have $a and I have

Execrable: VeryBad (7); Wretched (8); Detestable

so now I do the macro K and I wind-up with this:

Execrable: VeryBad (7); Wretched (8); Detestable (10)

Pretty neat, eh?! It is pure geek chic!

map =dd :!rm %^M^M:n^M
is used to delete the current file being edited. Sometimes, when I get a bunch of trash in some dump-point directory, I'll just do a 'vi *' and go through it with my ',' macro, then enter '=dd' to get rid of one I don't require anymore. The '=dd' macro also moves on to the next file automatically.


map =s :r ~/.signature^M
is used to read in your version of the .sig file (.signature, in my case). I often do not want my .sig file included in email I write, because most of my email goes to collegues who know quite well what my full name, email address, Web address, title and phone number are. So, by default, I do not include my .sig, but rather '=s' it in when I do desire it.

map =c 080i ^[$78hd0:s/  / /g^M
This macro is used for centering a single text line. I've been through several centering-macro iterations, and these are all captured in my Center Macro Hall of Fame. The one I am using nowadays was taken from the Sat, 18 Jan 2003 04:00:07 GMT vi FAQ, Part 2, section 6.1, "Silly vi Macros." :-) I'm told by Sandy Herring that in VIM you can get away with simply :ce80 to get the job done.

The =c center macro functions as follows:

080i ^[
Use '0' to go to column zero, '80i ' to insert 80 spaces before the LTBC's text (Line To Be Centered), escape-out of the insert.

$78hd0
Use '$' to go to the end of the line, then '78h' to move left 78 places, then 'd0' to trim leading white-space (this could be a problem if you are trying to center something longer than 78 columns! ;-)

:s/  / /g^M
Change all occurances of <space> <space> with <space> ' ' using ':s/  / /g' following by a <return> (or <enter>, or control-m... whatever :()

Wow! Wasn't that simple? That's a definate DFM (Dang Fine Macro!).

map V :w^M:!ispell -x %^M:e!^M^M
this is a pretty cool one I picked up off of the Net. I had been trying to get this one right on my own (with little success), as a part of making the switch from Pine to mh, which has no built-in spell-checking. I knew that if I were on UNIX and had the ispell command and a word dictionary available, then it should be no real problem. It turns out it wasn't, once I got a lead on the macro after searching around the Net for some new vi tricks.

As a last word, I'll mention that the dictionary is usually in /usr/lib/dict/words; that the interactive spelling checker can be used easily through the 'V' macro; that I haven't found anything useful being done with 'V' anyway (so I use it for "verify," or spell checking); and, finally, the -x option says to not create a backup file.

Other Tricks

Recovering Deleted Text

Ever make a change, then another, and regret the first one? If they are on the same line, a U will take care of that. But if you have deleted two lines, then discovered that you did not want to delete the first one, it is still available! There is a stack of buffers which accept deleted text, numbered 1-9. The first delete loads 1; the second delete moves 1 to 2 and loads 1; and the third delete moves 2 to 3, 1 to 2, then loads 1... and so-on. You can paste these lines back in by doing "np (that is, double-quote, number 1-9, letter 'p'), where 'n' is the number of the buffer you want (e.g., n deletes ago...), up to 9. Just doing, say, '2p' without the double-quote will paste the default buffer's contents twice, which is not going to help.

Recovering a File

If you execute :preserve a copy of your file--as it was at the last write--will be stored in a special location (/var/preserve/user-id on Sun Solaris, for example). You will also get an email about it, telling you it can be recovered from the command line. But you can also recover it in vi at any time by typing in :recover. So, let's say you do the preserve, then make a bunch of changes, then want to see what the original file looked like. Do a :w to write-out the current file, then a :recover to see the previous version, then a :e! % to revert to the changed copy you wrote out. Warning: once you do the recover, the preserved copy is removed from the system. But an obvious use of recover would be to allow you to revert without having to run a copy at the Unix shell prompt before you begin your edit session.

Reading in from the shell

I have another command I use quite frequently with vi, which is to read in something from the shell. While this is quite an obvious trick when one reads the command reference, I find that many folks miss this one. Use this whenever you want to include text which is the output of a command. Let's use, as an example, the case where one wants a copy of the current months calendar:

     :r !cal
or
     !!cal

Pretty simple, huh? Remember, this will replace the contents of the line it was executed on, so use it on a blank line if you don't want to replace something.

Appending the current line elsewhere

Here is one more I often use; the case is when I am editing a file and want one line to go on to the end of another file. First move to the line in question, then do the following:

     :.w >> filename

Writing out lines between marks

If you want to write out a file using the lines from one mark to another, there is an easy way suggested by John Burns (john.h.burns@boeing.invalid_address_for_spammers_remove_to_reply.com). Using the m mark command, and a pair of the twenty-six available markers (a to z), you can mark an area of the file you are in, say ma and mb, then write that section out:

     :'a,'bw filename

or

     :'a,'bw >> filename
to append.

Getting a chunk from one file to another

Often-times, one finds that one wants to take something from one file and put it into another. Let's say that one wants a paragraph from file foo to be put into file bar. Here are the steps:

  1. vi foo bar
  2. go to the paragraph you want to take, and position on the first character of the first line.
  3. "ay} is executed. This says, put into non-volitile buffer a what is yanked from the current cursor-position to the end-of-paragraph (denoted by the '}' character).
  4. type in , if you are using my .exrc file, or :n if you aren't.
  5. find the spot you want to insert the yanked stuff.
  6. type in "ap. That's it!

Context jumping

Ever went someplace in your file, but wanted then to go back to where you where? This is easy; just type two back-quotes, as in ``, and you will jump back to the previous context. Within the context of one window, H takes you to the home-line, L takes you to the last, and M to the middle of the window.

Creating a horizontal ruler

Ever want to know what character space you are on a line? What I do is to create a horizontal ruler, then store it in one of my non-volitile paste buffers for later use in the session.

  1. 7i.........!<esc> creates a seventy-character bar consisting of seven sets of '.........!'.
  2. "cdd removes the newly created bar to buffer c.
  3. Later, when you need the bar, just do "cp and it will appear, like this:.
.........!.........!.........!.........!.........!.........!.........!
If you have perl on your system, replace step one (7i.........!) with the following:

:r !perl -e 'for ($i=1;$i<8;$i+=1) {print ".........$i";} print ".........\n";'

you get this ruler instead!

.........1.........2.........3.........4.........5.........6.........7.........

Shell commands to operate on a line, range or paragraph

I mentioned use of the !}fmt command during the discussion of the .exrc file. Using fmt as an example again, here are the three common ways to use it:
  • :.!fmt to format one line.
  • !!fmt better way to format one line.
  • !}fmt to format one paragraph.
  • :.,$!fmt to format from the current line to the end of the document.
  • :.,/searchstring/!fmt to format from the current line to any occurance of searchstring.
Of course, any reasonable shell formatting command may be used in this way, and fmt itself may be used with options, such as fmt -60 to change from the default line length to sixty.

Getting the tcsh ^T command to work in vi

Some tcsh users have gotten to like the ^T command which swaps (transposes) the previous two characters. In vi, a single-character delete followed by a paste will transpose, but here we assume one is doing input and types two characters in backwards.

David Harnick-Shapiro of the University of California, Irvine ICS department <david@ics.uci.edu> makes the following suggestion:

:map! ^T ^[hxpa
which simply drops out of input mode, swaps the chars, and throws you back into input mode. Note that ^T is already used in vi, for tags, so you programmers might want to take up my convention of =t for this one.

Using abbr to convert DATE to the date in vi

Victor Langeveld <ltvic@mbfys.kun.nl> also wrote me about creating an abbreviation which maps onto a command and in effect acts like a macro, but in input mode. The effect is that as you are typing text in the input mode, whenever you type DATE the word is replaced with today's date, and you stay in input mode! Here it is:

:abbr DATE ^M^[:.!date '+\%a \%d \%h \%Y'^MkJA
Remember to preceed the three control characters (i.e., ^M, ^[ and ^M) with a ctrl-v. Victor has tested this only on FreeBSD. I have verified that it functions as expected on SunOS 5.7 and Solaris 2.5.0.

Creating on-the-fly commands based on file text

Something I've run into on the 'Net and which after a search I find exists in a number of places is this mapping:

:map! ^P ^[a. ^[hbmmi?\<^[2h"zdt.@z^Mywmx`mP xi

This is a clever little devil. What is happening here is that a command is being built in a buffer from text in the macro and text in the file, then is executed. Nowhere have I found a good explanation of this, so I decided to document it myself (I did not invent this mapping).

The bang sign after the map tells vi to only pay attention to control-P or control-N when you are in input mode, so for these to work you have to be typing in a word.

The escape invoked takes it out of input mode, puts it into append mode and adds a '.' after the word you were typing, then escapes out of that. h seems not required, but is intended to make sure you are on the word.

The b moves the cursor to the start of the word and marks the spot using mark 'm'. Back into input mode at the start of the word, we add in the string '?\<' and escape-out. "zdt. deletes what we just typed in, through the end of the word, to the period '.' and stores that result in buffer z.

@z executes what is in buffer z, so in this case we are searching backwards for a word which begins with what we were typing in before. Assuming that word is found, the 'yw' "yanks" the word and for some reason I can't figure out marks its location with mark 'x'. Next, the '`m' gets us back to where the word were were typing was and the 'P' pastes the word found on the search there, the ' ' moves the cursor forward to the period '.' left over from previous operations, the 'x' deletes the period, and we go back into input mode.

In summary,

:map! ^P ^[a. ^[hbmmi?\<^[2h"zdt.@z^Mywmx`mP xi

reads to me like:

:map! CONTROL-P ESCAPE hbmmi?\< ESCAPE 2h"zdt.@z^ CONTROL-M ywmx`mP xi
With the spaces before and after ESCAPE and CONTROL-M (return) added for clarity of reading.

Here is another version that is supposed to do the same sort of thing. In this case the author mapped it to control-Q(Ola Rauer <Ola.Rauer@med.lu.se> sent this to me, having found it at http://georg.f-451.net/elvis/#word_completion).

 
:map input ^Q ^[bmz?\<\@^Mnye`zPldea.^[bis^["zdt.@z

Scripts and vi

As has been seen with the format command, it is easy to pass out a line, a paragraph, a range of lines--or the whole document, to an external command for processing. The output of the command replaces the scope of the command. It is thus possible to create custom shell scripts for specific purposes.

The vi Search File Script

John Burns <john.h.burns@boeing.invalid_address_for_spammers_remove_to_reply.com> contributes this one. The .exrc file is loaded with a macro which, in John's .exrc file, is a control-A. This command causes the first three non-blank characters on the current line to be used as a regular expression for searching a template file. A match on a template for a function call, for example, could be used to replace the current line when programming.
.exrc
map ^A !! $HOME/.vi.script  ^M
$HOME/.vi.script
sed -e 's/^ *\(...\).*/\1/' >/tmp/b
grep -i ^`cat /tmp/b` $HOME/.vi.search.file | fmt -72 


Special Thanks

Special thanks to Sandy Herring, for proof reading, and testing; Pete Mastren, for his spiffy centering macro; Joshua Wright <jwright@jwu.edu> for the original centering macro; Victor Langeveld <ltvic@mbfys.kun.nl> for the tip about using uu to find the location of your last change, and for the DATE abbreviation idea; and John Burns (john.h.burns@boeing.invalid_address_for_spammers_remove_to_reply.com) for interesting tips, and suggesting that the .exrc file be available uuencoded. Thanks also to all the folks who have shared vi tips with me over the years.


Have a vi tip you'd like to share? Just drop me a line

This document may be found at: http://www.nacs.uci.edu/indiv/gdh/vi
Comments and suggestions welcome.
Last revised Wednesday, 16-Sep-2009 16:36:12 PDT .

Garrett Hildebrand -- gdh@uci.edu