vi macros and tricks

The mappings shown under headings (1)-(4) below all appear in my EXINIT variable (see end of this file), though while trying them out, you might want to give them as bottom-line commands.  (Or, once you are using the first one, as lines executed using that mapping.)  They work in the two versions of vi I have used until recently:  the old BSD vi, and the vi on SUN unix.  I have recently begun using vim on linux, and have supplemented my comments with notes on how these mappings and their use have to be modified in this environment. 

In the descriptions below, I show non-printing characters as they appear in vi; e.g. ^M, ^V, ^[, etc..  To actually use these mappings, you need to replace these non-printing characters by the characters they represent.  (To enter ^M in a file or on the bottom line, use the sequence of two keystrokes  ^V^M ; i.e., CTR-V CTR-M.  Similarly, to get ^[ use  ^V^[  and to get  ^V  itself, use  ^V^V .)  The particular characters or strings that you map are unimportant, of course; you can substitute others of your choice for my ^O, ^E, etc..  In vim, it is also possible to enter in one's command-lines escape-sequences consisting of ordinary characters and representing the above, e.g., <C-O> for ^O , <C-M> or <CR> for ^M, and <C-]> or <ESC> for ^].

(1)    map ^O "ayy:@a^M
      ( map <C-O> "ayy:@a<CR>   in vim)

When this has been executed, the keystroke  ^O  given in screen mode causes whatever line the cursor is currently on to be executed as a bottom-line command. 

Thus, one can work out complicated search commands, substitutions, mappings, etc., edit them with vi, and try them out and fine-tune them until one has what one wants.  Even for commands that don't require great ingenuity, it may be useful to have a bank of them in a file for repeated use.  For instance, in TeX files I am writing, I keep a bank of around 60 pattern-search commands to look for various errors in formatting I have found myself making over the years. 

I also use it for sending e-mail: I keep mail related to various topics in separate files, and to send such e-mail, I will generally draft it at the end of the file, preceded by a line   +,$w !mail  whoever@wherever,  which I execute when it is ready. 

In LaTeXing my preprints, I like to keep the source files in one directory, Papers/p, and auxiliary matter (dvi files and log files, journals' style class files, etc.) in a "utilities" subdirectory, Papers/p/u. I edit the source files with Papers/p as my current directory, and when I have a version ready to LaTeX, I execute with  ^O  a line  w|!cd u;latex ../%  which I keep in the file.  This spawns a shell which moves to the "utilities" directory, LaTeXs the file from there (accessing the style class files there if needed), and puts in that directory the various associated files; including the dvi file, to which I keep an instance of xdvi pointed. 

How does the  ^O  mapping work? The first part,  "ayy , yanks the content of the current line to buffer "a".  The second part is the bottom-line command  :@a  (terminated by a carriage return,  ^M ), which executes the contents of buffer "a". 

If you precede the keystroke  ^O  by a positive integer n, this will similarly yank and execute the next n lines.  (However, with SUN vi, there is a glitch:  If the n lines in question include a line with a global command,  address g/pattern/command,  then the lines after that one are ignored.  This does not happen when a script file is executed in  ex  mode; it seems to be a bug associated with visual mode.) 

Many of the commands to which I apply the  ^O  trick are themselves commands to map keystroke combinations; and this introduces another complication:  the distinction between the interpretation of characters in the command to do the mapping, and in the mapping itself.  In particular, if the desired action involves a pipeline character |,  this must appear in the command as ^V|,  otherwise when one executes the command,  vi  will interpret the command to mean that only what appears before the pipeline is part of the mapping command, and that what follows is a second command. 

There is a further complication in  vim,  where for reasons I don't understand, two ^V's are needed before a pipeline in a mapping command is executed using ^O (although one suffices in one's VIMINIT variable, or in a script file that one sources).  Moreover, if the command contains a carriage-return ^M or an escape character ^[, these must appear in lines to be executed by ^O as ^V^M and as ^V^[. Hence in reading the points that follow, remember:

(For the cases of ^V^M and ^V^[, an alternative is to replace former with the escape sequence <C-M> or <CR> and the latter with <C-[> or <ESC>.  But I know no way to get a mapping whose effect contains a pipeline than to input it with ^V^V|.  Note again that the above rule does not apply if these commands are executed in another way, such as a sourced command script or one's VIMINIT variable.)

(2)    map =m :'x+kw^V|'w,'ymo'z-^M

This allows one to move a block of lines from one point in the file to another:  one gives the line above the block the mark  x  (i.e., one puts the cursor on that line and types  mx  in screen mode), gives the last line of the block the mark  y , and gives the line above which one wants to block to be moved the mark  z , then types  =m  (still in screen mode), and the move is made.  After the move, the position from which the block was moved is still marked  x , so one can revisit it with the movement command  'x  (and perhaps select some nearby material that one also wants to move).  The beginning and end of the moved block are marked  w  and   y , so that one can also find them easily. 

(3)    map ^^ :w^V|e#^M

This causes the keystroke  ^^  to be equivalent to the bottom-line command  w|e# , i.e., write the current file and return to the previous file.  (Note: the keystroke  ^^  involves hitting three keys simultaneously:  CTRL, CAP and 6.) 

(4)    map! ^E ^[Ea

This causes the keystroke  ^E , when given in insert mode, to jump to the next end-of-word location, and reenter insert mode there.  So if I want to change  They say  to  They are saying , I enter insert mode at the end of  They , type  " are", hit  ^E , and type  ing.  To skip two words, hit  ^E^E , etc. 

(5)  A trick for checking matched parentheses. 

I don't like using the "showmatch" option in vi; it slows down my typing and is distracting.  But when I have finished writing or editing some mathematical work, I want to know that parentheses match.  To do this, I keep the string  ([{  at the beginning of the file, and  }])  at the end.  I put the cursor on one of these characters, say the  (  at the beginning, and type the keystroke  % .  If left and right parentheses  ( ... ) match within the file, the cursor will move (after a second or so) to the  )  at the end.  If there is an unmatched  )  in the file, it will instead find that.  If, on the other hand, there is an unmatched  ( , it will not go anywhere.  In that case, one can find the unmatched  (  by going to the other end of the file, putting the cursor on the  )  there, and again typing  % . 

Of course, the above  ([{  and  }])  have to be kept from being treated as part of the file; this can easily be done by putting them in comment lines.  (Mathematical writing also occasionally uses unmatched parentheses; e.g., in naming half-open intervals  (a, b].  Such cases, if they are few, can be kludged by matching these symbols with commented-out parentheses and brackets.  But if they are common in a file, it is probably simplest to forego using, on that file, the part of this search that handles those symbols.)

In vim, at least in the version on the system I am on, the above matching trick occasionally fails, in peculiar circumstances which I don't understand, involving double-quotes between the parentheses.  E.g., in a file you are editing with vim, try typing in the 6-character string  ("("))  (double-quotes and all), put the cursor on the first or last symbol of that string and hit %; and see what happens.  There are still stranger example involving the interaction of double quotes and newlines. (Fortunately, in the source files of my math papers, double quotes almost never occur.) 

(6)  When you're not sure what changes you have made

Sometimes I discover that I have suspended a vi session hours or days earlier, and perhaps later edited the same file, so that the existing file and the buffer of the suspended session may each embody changes that the other does not.  The first thing I do on re-opening the suspended session is hit  ^G  (or equivalently, give the bottom line command  :file ).  If the information that is then shown on the bottom line does not contain the word  [Modified],  I know that I wrote all changes before suspending the vi session, and can safely quit this instance of vi.  If it does say  [Modified],  I will give the bottom-line command  :w !diff % - | less.  This gives a comparison between the contents of the buffer (the "-" argument of "diff") and the current state of the file (the "%" argument), allowing one to compare these, and decide what parts, if any, of one version to copy into the other. 

(The above trick is less necessary when using vim, where the "undo" command u can be applied recursively; but it is still helpful at times.)

(7)  Getting where you want with a global. 

It is easy to give a global command that will, say, go to each line matching  pattern1,  find the next line after it that matches  pattern2, and collect those lines for you:  g/pattern1//pattern2/ya X  will collect the desired lines in buffer  x.  You can then put them into the file with the on-screen command  "xp  or the bottom-line command  :pu x  and examine them.

But what if you want to capture in this way the second occurrence of  pattern2  after each occurrence of  pattern1,  or more generally, the first occurrence of  pattern3  after the first occurrence of  pattern2  after each occurrence of  pattern1?  The obvious sorts of commands won't work: e.g.,  g/pattern1//pattern2/|/pattern3/ya  X  will write to the screen the occurrence of  pattern2  after each occurrence of  pattern1  (which you did not want, but is generally not a problem), and then yank to buffer  x  the occurrence of  pattern3  following that same occurrence of  pattern1,  not following the intermediate occurrence of  pattern2

A little change that gives the desired behavior is to add a semicolon after  /pattern2/  in this command.  The semicolon causes vi to reset the location of "." (the "current line") from the line found by the "global" command to the line just found; so that the next search is relative to that line, rather than relative to the line found by the global.  An additional change, which will get rid of the unintended behavior of writing the occurrences of  pattern2  to the screen, is to give it, instead something harmless to do that you won't notice -- e.g., marking each line found by the  pattern2  search as location "a".  Thus, we get the command g/pattern1//pattern2/;ka|/pattern3/ya X , which does what we wanted to start with.  (The one unintended side-effect is that at the end of the command, the last location found by the search for  pattern2  is marked as location "a".  So you just have to be sure that you hadn't previously marked as "a" some location you didn't want to lose.)

Needless to say, yanking isn't the only thing you may want to use this trick for.  The thing to remember is that in a global command, pattern-searches are relative to the lines found in the original global search, unless a semicolon is used within the global command to re-set that location.

Entering mappings (1)-(4) in one's  EXINIT.

Below is a simplified version of the line in my .login file that contains the mapping commands described in points (1)-(4) above.  (Simplified in that it leaves out other settings and mappings that are either well-known, or of less general interest.  In the same file I have a line setting my VIMINIT, similar but adapted to vim.) 
     setenv EXINIT 'map ^O "ayy:@a^M|map ^^ :w^V|e#^M|map! ^E ^[Ea|'"map =m :'x+kw^V|'w,'ymo'z-^M"
(Observe that the first part of the command, which contains the mappings described under (1), (3) and (4) above, is enclosed in single-quotes, '...', while the mapping described under (2), placed at the end, is enclosed in double-quotes, "...".  This is because mapping (1) itself contains a double-quote, and hence can't be quoted using double-quotes, while (2) involves several single-quotes, hence needs double-quotes to quote it.) 

If you copy this line into your own .login file, remember to replace strings  ^M  etc. with the corresponding genuine control-characters.  Once you have made this addition to your .login file, these vi mappings will be in effect in all future login sessions.  You can get them to take effect in your current session by giving the command  source .login .