[net.emacs] VIP

sato@SU-Russell.ARPA (Masahiko Sato) (07/24/86)

As I received more requests for VIP (VI Package for GNU Emacs) than I
expected, I decided to post the code here.  [Sorry for much more people
who are not interested in vi.]

I post VIP in 3 parts.  Part 1, which you are now reading, is a short
introduction to VIP.  Part 2 is the main code of VIP.  Part 3 is an
example of customizing VIP.

I appreciate comments and bug reports from you.

** masahiko (sato@su-russell.arpa) **

-----

*1* Installing VIP

Suppose that you have a copy of VIP (which you can find in Part 2) in the
file vi.el and its full path name is .../vi.el.  Then include the line

	(load ".../vi.el")

in your .emacs file.  In this way, VIP will be loaded whenever you enter
Emacs.  Or, after you entered Emacs, you can load vi.el by the "load"
command.  Another way is to define the function "change-mode-to-vi" to
autoload from the file vi.el.

You can also compile vi.el and use vi.elc instead of vi.el.

*2* Modes in VIP

(From now on I assume that vi.el has been loaded.)  Loading vi.el has the
effect of globally binding "^Z" (control-Z) to the function 
"change-mode-to-vi". The default binding of "^Z" in GNU Emacs is 
"suspend-emacs", but, you can also call "suspend-emacs" by hitting "^X^Z".
Other than this, all the key bindings of Emacs remain the same after loading 
vi.el.

Now, if you hit "^Z" the function "change-mode-to-vi" will be called, and
you will be in vi-mode.  (Some major modes like shell-mode locally bind
"^Z" to some special functions.  In such cases, you can call 
"change-mode-to-vi" by the "execute-extended-command" which is invoked by
"ESC x".)

You can observe the change of mode by looking at the mode line.  For instance,
if the mode line was:

-----Emacs: *scratch*              (Lisp Interaction)----All------------------

Then it will change to:

-----Vi:    *scratch*              (Lisp Interaction)----All------------------

Thus the word "Emacs" in the mode line will change to "Vi".  This assumes
that the initial mode line contains the string "Emacs:".  (If the mode line
does not contain the string "Emacs:", which I think is rather rare, VIP
will use its own mode line to replace the mode line.)

You can go back to the original emacs-mode by hitting "^Z" in vi-mode.  Thus
"^Z" toggles between these two modes.

Note that modes in VIP exist orthogonally to modes in Emacs.  This means
that you can be in vi-mode and at the same time, say, shell-mode.  You can
then bind a function which is useful in shell-mode to some key in VIP's
vi-mode.

Vi-mode corresponds to vi's command mode.  You can change from vi-mode to
insert-mode (which corresponds to vi's insert mode) by usual vi command
keys like "i", "a", "o" ... etc.

In insert-mode, the mode line will look like this:

-----Insert *scratch*              (Lisp Interaction)----All------------------

You can exit from insert-mode by hitting "ESC" key as you do in vi.

That VIP has three modes may seem very complicated, but in fact it is not
so, since VIP is implemented so that you can do most editing remaining only
in the two modes for vi (that is vi-mode and insert-mode).

The figure below shows the transition of three modes in VIP.

		   ==== ^Z ==>		== i,o ... ==>
	emacs-mode		vi-mode			insert-mode
		   <=== ^Z ===		<=== ESC ====
		   
*3* Emacs-mode

You will be in this mode just after you loaded vi.el.  You can do all normal 
Emacs editing in this mode.  Note that the key "^Z" is globally bound to 
"change-mode-to-vi".

*4* Vi-mode

This mode corresponds to vi's command mode.  Most vi commands work as they
do in vi.  I explain the major differences from vi below.

-- limitations --

Ex commands are not supported.  The key ":" is used for other purpose.
(See below.)

-- modifications --

- Undoing.  You can repeat undoing by the "." key.  So, "u" will undo
a single change, while "u...", for instance, will undo 4 previous
changes.  Undo is undoable as in vi.  So the content of the buffer will
be the same before and after "uu".

- Some commands which change a small number of characters are executed
slightly differently.  Thus, if the point is at the beginning of a word
"foo" and you wished to change it to "bar" by hitting "cw", then VIP
will prompt you for a new word in the mini buffer by the prompt
"foo => ".  You can then enter "bar" followed by a <return> to complete
the command.  Before you enter a <return> you can abort the command by
hitting "^G".  In general, you can abort a partially formed command by
hitting "^G".

- Searching by "/" and "?".  Search string must be terminated by a <return>,
and not by a "ESC".  The string will be searched literally by default.  To
invoke a regular expression search, first execute the search command "/"
(or "?") with empty search string.  (I.e, do "/" followed by a <return>.)
A search for empty string will toggle the search mode between vanilla
search and regular expression search.  You cannot give an offset to the
search string.  (It is a limitation.)  By default, search will wrap around
the buffer as in vi.  You can change this by rebinding the varialbe
"vi-search-wrap-around".  See *6* for how to do this.

- ":" key is now used to do a pseudo incremental search.  Assume for instance
that your last search was "/foo", but you wanted to search "fox" forward
instead of "foo".  Then hitting ":" will give you "/foo" in the mini buffer.
You can change the last "o" to "x" and then give a <return> to search
"fox" forward.

- For those of you who cannot remember which of "z" followed by <return>,
"." and "-" do what.  You can also use "z" followed by "H", "M" and "L" to
place the current line in the Home (Middle, and Last) line of the window.

-- additions --

- Some vi commands which do not accept counts now accept counts.

"p" and "P".  Given counts, text will be yanked (in vi's sense) that many
times.  Thus "3p" is the same as "ppp".

"o" and "O".  Given counts, that many copies of text will be inserted.
Thus "oabc" followed by "ESC" will insert 3 lines of "abc" below the
current line.

"/" and "?".  Given a count n, n-th occurrence will be searched.

- Marking.  "m" followed by a lower case <char> marks the point to the
register named <char> as in vi.  In addition to these, we have following
key bindings for marking.

"m<"  Set mark at the beginning of buffer.
"m>"  Set mark at the end of buffer.
"m."  Set mark at the point (and push old mark on mark ring).
"m,"  Jump to mark (and pop mark off the mark ring).

- Region commands.  Vi operators like "d", "c" etc. are usually used in
combination with motion commands.  It is now possible to use current
region as the argument to these operators.  (A region is a part of buffer
delimited by point and mark.)  The key "r" is used for this purpose.
Thus "dr" will delete the current region.  If "R" is used instead of "r"
the region will first be enlarged so that it will become the smallest
region containing the original region and consisting of whole lines.
Thus "m.dR" will have the same effect as "dd".

- New commands added to vi.  Note that the keys below are not used in vi.

"^A"  Move point to the beginning of line.

"^N"  If you have two or more windows in the screen, this key will move
point to the next window.

"^O"  Insert a newline and leave point before it, and then you will be
in insert-mode.

"^S"  Suspend emacs.

"^C", "^X" and "ESC"  These keys will exit from vi-mode and return to
emacs-mode temporarily.  If you hit one of these keys, Emacs will be
in emacs-mode and will believe that you hit that key in emacs-mode.
For example, if you hit "^X" followed by "2", then the current window will 
be splitted into 2 and you will be in vi-mode again.

- "\"  Escape to emacs-mode.  Hitting a "\" will take you to emacs-mode,
and you can execute a single Emacs command.  After executing a single
Emacs command you will be in vi-mode again.  You can give a count before
hitting "\".  Thus "5\+", as well as "\^U5+", will insert "+++++" before
the point.  Similarly "10\^P" will move the point 10 lines above the current
line.

- "K"  Kill current buffer if it is not modified.  Useful when you selected
a buffer which you did not want.

- "Q" and "R"  "Q" is for query replace and "R" is for replace.  By default,
string to be replaced are treated literally.  If you wish to do a regular
expression replace, first do replace with empty string as the string to
be replaced.  In this way, you can toggle between vanilla and regular
expression replacement.

- "v" and "V"  These keys are used to Visit files.  "v" will switch to
a buffer visiting file whose name can be entered in the mini buffer.
"V" is similar, but will use window different from the current window.

- "#"  If followed by a certain <char>, it becomes a operator whose
argument is the region determined by the motion command that follows.
Currently, <char> can be one of "c", "C", "g", "q" and "s".

	"#c"  change upper case characters in the region to lower case.
	      (downcase-region)
	"#C"  change lower case characters in the region to upper case.
	      (upcase-region)
	"#g"  execute last keyboard macro for each line in the region.
	      (global-execute)
	"#q"  insert specified string at the beginning of each line in the
	      region. (quote-region)
	"#s"  check spelling of words in the region. (spell-region)

For instance, "#C3w" will capitalize 3 words from the current point.

- "*"  Call last keyboard macro.

*5* Insert-mode

The key bindings in this mode is the same as in the emacs-mode except for
the following 3 keys.  So, you can move around in the buffer and change
its content while you are in insert mode.

- "ESC"  This key will take you back to vi-mode.

- "^H"  Delete previous character.

- "^W"  Delete previous word.

*6*  Customization

If you have a file called .vip in your home directory, then it will also
be loaded when vi.el is loaded.  This file is thus useful for customizing
VIP.  You will find an example of .vip in Part 3.

sato@SU-Russell.ARPA (Masahiko Sato) (07/24/86)

Below is the elisp code of VIP.

** masahiko (sato@su-russell.arpa)

===============  Cut here  ===============
;; VIP: A VI Package for GNU Emacs
;; Author: sato@su-russell.arpa (Masahiko Sato)
;; Version 17.1 of July 23, 1986
;; This code works under version 17 of GNU Emacs.

;; external variables

(defvar vi-local-map nil
  "Local map used in vi command mode. \(buffer specific\)")

(defvar emacs-local-map nil
  "Local map used in emacs mode. \(buffer specific\)")

(defvar insert-local-map nil
  "Local map used in insert command mode. \(buffer specific\)")
  
(make-variable-buffer-local 'vi-local-map)
(make-variable-buffer-local 'emacs-local-map)
(make-variable-buffer-local 'insert-local-map)

(defvar insert-point nil
  "Remember insert point as a marker. \(buffer specific\)")

(set-default 'insert-point (make-marker))
(make-variable-buffer-local 'insert-point)

(defvar com-point nil
  "Remember com point as a marker. \(buffer specific\)")

(set-default 'com-point (make-marker))
(make-variable-buffer-local 'com-point)

(defvar current-mode nil
  "Current mode.  One of emacs-mode, vi-mode, insert-mode.")

(make-variable-buffer-local 'current-mode)
(set-default 'current-mode 'emacs-mode)

(defvar current-major-mode nil
  "current-major-mode is the major-mode vi considers it is now.
\(buffer specific\)")

(make-variable-buffer-local 'current-major-mode)

(defvar vi-last-shell-com nil
  "last shell command executed by ! command")

(defvar use-register nil
  "name of register to store deleted or yanked strings.")

(defvar d-com nil
  "If non-nil, it's value is a list (M-COM VAL COM), and is used to
re-execute last destrcutive command")

(defconst shift-width 8
  "The number of colums shifted by > and < command.")

(defconst re-replace nil
  "If t then do regexp replace, if nil then do string replace.")

(defvar d-char nil
  "The char rembered by the vi \"r\" command")

(defvar f-char nil
  "for use by \";\" command")

(defvar f-forward nil
  "for use by \";\" command")
  
(defvar f-offset nil
  "for use by \";\" command")

(defconst vi-search-wrap-around t
  "if t, search wraps around")

(defconst re-search nil
  "if t, search is reg-exp search, otherwise vanilla search.")

(defvar s-string nil
  "last search string")

(defvar s-forward nil
  "if t, search is forward.")

(defconst vi-case-fold-search nil
  "if t, search ignores cases.")

(defconst re-query-replace nil
  "If t then do regexp replace, if nil then do string replace.")

(defvar vi-quote-string "> "
  "string inserted at the beginning of region")

;; basic set up

(global-set-key "\C-z" 'change-mode-to-vi)
(global-set-key "\^xo" 'vi-other-window)

(defmacro loop (count body)
  "(COUNT BODY) Execute BODY COUNT times."
  (list 'let (list (list 'count count))
	(list 'while (list '> 'count 0)
	      body
	      (list 'setq 'count (list '1- 'count)))))

(defun push-mark-silent (&optional location)
  "Set mark at location (point, by default) and push old mark on mark ring.
No message."
  (if (null (mark))
      nil
    (setq mark-ring (cons (copy-marker (mark-marker)) mark-ring))
    (if (> (length mark-ring) mark-ring-max)
	(progn
	  (move-marker (car (nthcdr mark-ring-max mark-ring)) nil)
	  (setcdr (nthcdr (1- mark-ring-max) mark-ring) nil))))
  (set-mark (or location (point))))

(defun vi-goto-col (arg)
  "(ARG)  Go to ARG's column."
  (interactive "P")
  (let ((val (p-val arg))
	(com (getcom arg)))
    (save-excursion
      (end-of-line)
      (if (> val (1+ (current-column))) (error "")))
    (if com (move-marker com-point (point)))
    (beginning-of-line)
    (forward-char (1- val))
    (if com (execute-com 'vi-goto-col val com))))

(defun refresh-mode-line ()
  "Redraw mode line."
  (set-buffer-modified-p (buffer-modified-p)))

(defun copy-keymap (arg)
  "(ARG)  Make a copy of the keymap ARG."
  (if (or (keymapp arg) (null arg))
      (if (listp arg)
          (let* ((map (make-sparse-keymap))
		 (atmp (cdr arg)) (mtmp map) key rest)
            (while atmp
              (setq key (car (car atmp))
                    rest (cdr (car atmp)))
              (if (symbolp rest)
                  (rplacd mtmp (cons (cons key rest) nil))
                (rplacd mtmp (cons (cons key (copy-keymap rest)) nil)))
              (setq atmp (cdr atmp)
                    mtmp (cdr mtmp)))
            map)
        (let ((map (make-keymap)) (i 0) bind)
          (while (> 128 i)
            (setq bind (aref arg i))
            (if (symbolp bind)
                (aset map i bind)
              (aset map i (copy-keymap bind)))
            (setq i (1+ i)))
          map))
    (error "Arg is not keymap")))

;; changing mode

(defun change-mode (new-mode)
  "(NEW-MODE)  Change mode to NEW-MODE.  NEW-MODE is either emacs-mode,
vi-mode, or insert-mode."
  (or (eq new-mode current-mode)
      (progn
	(cond ((eq new-mode 'vi-mode)
	       (if (eq current-mode 'insert-mode)
		   (progn
		     (copy-region-as-kill (point) insert-point)
		     (repeat-insert-command))
		 (setq emacs-global-map global-map
		       emacs-local-map (current-local-map)
		       emacs-mode-line-format mode-line-format
		       insert-local-map (copy-keymap (current-local-map))))
	       (change-mode-line "Vi:   ")
	       (use-global-map vi-command-mode-map)
	       (if (eq major-mode current-major-mode)
		   (use-local-map vi-local-map)
		 (progn
		   (setq current-major-mode major-mode)
		   (make-vi-local-map major-mode)
		   (use-local-map vi-local-map))))
	      ((eq new-mode 'insert-mode)
	       (move-marker insert-point (point))
	       (change-mode-line "Insert")
	       (use-global-map emacs-global-map)
	       (use-local-map insert-local-map)
	       (define-key insert-local-map "\e" 'change-mode-to-vi)
	       (define-key insert-local-map "\^h" 'delete-backward-char)
	       (define-key insert-local-map "\^w" 'delete-backward-word))
	      ((eq new-mode 'emacs-mode) 
	       (setq mode-line-format emacs-mode-line-format)
	       (use-global-map emacs-global-map)
	       (use-local-map emacs-local-map))
	      (t
	       (error "Can't change to %s " new-mode)))
	(setq current-mode new-mode)
	(refresh-mode-line))))

(defun change-mode-line (string)
  "Assuming that the mode line format contains the string \"Emacs:\", this
function replaces the string by \"Vi:   \" etc.  If the mode line format does
not contain \"Emacs:\", default mode line format is used instead."
  (let (format)
    (save-window-excursion
      (save-restriction
	(switch-to-buffer " *working-space*")
	(set-mark (point))
	(insert emacs-mode-line-format)
	(narrow-to-region (mark) (point))
	(goto-char (point-min))
	(if (not (search-forward "Emacs:" nil t))
	    (progn
	      (delete-region (point-min) (point-max))
	      (insert "%1*%1*- %[Emacs:%] %16b %3p (%m) %M %-")
	      (goto-char (point-min))
	      (search-forward "Emacs:")))
	(set-mark (point))
	(backward-char 6)
	(delete-region (point) (mark))
	(insert string)
	(setq format (buffer-string))
	(delete-region (point-min) (point-max))))
    (setq mode-line-format format)
    (refresh-mode-line)))

(defun change-mode-to-vi ()
  "()  Change mode to vi."
  (interactive)
  (change-mode 'vi-mode))

(defun change-mode-to-insert ()
  "()  Change mode to insert."
  (interactive)
  (change-mode 'insert-mode))

(defun change-mode-to-emacs ()
  "()  Change mode to emacs."
  (interactive)
  (change-mode 'emacs-mode))

;; escape to emacs mode termporarilly

(defun get-editor-command (l-map g-map &optional str)
  "(L-MAP G-MAP STR)  Read characters from keyboard until an editor command
is formed, using local keymap L-MAP and global keymap G-MAP.  If the
command is a self-insert-command, the character just read is returned
instead.  Optional string STR is used as initial input string."
  (let (char l-bind g-bind)
    (setq char
	  (if (or (null str) (string= str ""))
	      (read-char)
	    (string-to-char str)))
    (setq last-command-char char)
    (setq l-bind (binding-of char l-map))
    (if (null l-bind)
	;; since local binding is empty, we concentrate on global one.
	(progn
	  (setq g-bind (binding-of char g-map))
	  (if (null g-bind)
	      nil ;; return nil, since both bindings are void.
	    (if (keymapp g-bind)
		(get-editor-command nil g-bind (string-tail str))
	      (if (eq g-bind 'self-insert-command) char g-bind))))
      ;; local binding is nonvoid
      (if (keymapp l-bind)
	  ;; since l-bind is a keymap, we consider g-bind as well.
	  (progn
	    (setq g-bind (binding-of char g-map))
	    (if (null g-bind)
		(get-editor-command l-bind nil (string-tail str))
	      (if (keymapp g-bind)
		  ;; both bindings are keymap
		  (get-editor-command l-bind g-bind (string-tail str))
		;; l-bind is a keymap, so we neglect g-bind
		(get-editor-command l-bind nil (string-tail str)))))
	;; l-bind is a command
	(if (eq l-bind 'self-insert-command) char l-bind)))))

(defun binding-of (char map)
  "(CHAR MAP)  Return key-binding of CHAR under keymap MAP.  It is nil if
the binding is void, or a command, or a keymap"
  (let ((val (if (listp map)
		 (cdr (assq char map))
	       (aref map char))))
    (cond ((null val) nil)
	  ((keymapp val)
	   (if (symbolp val) (symbol-function val) val))
	  (t
	   ;; otherwise, it is a function which is either a real function or
	   ;; a keymap fset to val.
	   (let ((fun (symbol-function val)))
	     (if (or (null fun) (keymapp fun)) fun val))))))

(defun escape-to-emacs (arg &optional char)
  "(ARG &optional CHAR)  Escape to emacs mode and execute one emacs
command and then return to vi mode.  ARG is used as the prefix value
for the executed command.  If CHAR is given it becomes the first
character of the command."
  (interactive "P")
  (let (com (buff (current-buffer)) (first t))
    (change-mode-to-emacs)
    (if char (setq unread-command-char char))
    (setq prefix-arg arg)
    (while (or first (>= unread-command-char 0))
      ;; this while loop is executed until unread command char will be
      ;; exhausted.
      (setq first nil)
      (condition-case nil
	  (setq com (get-editor-command (current-local-map) global-map))
	(quit
	 (change-mode-to-vi)
	 (signal 'quit nil)))
      (if (numberp com)
	  (loop (p-val prefix-arg) (insert-string (char-to-string com)))
	(condition-case conditions
	    (command-execute com prefix-arg)
	  (quit
	   (message "Quit")
	   (ding))
	  (error
	   (message-conditions conditions)))))
    (setq prefix-arg nil)  ;; reset prefix arg
    (if (equal buff (current-buffer)) (change-mode-to-vi))))

(defun message-conditions (conditions)
  "Print conditions as a message."
  (let ((case (car conditions)) (msg (cdr conditions)))
    (if (null msg)
	(message "%s" case)
      (message "%s %s" case (prin1-to-string msg)))
    (ding)))

(defun vi-ESC (arg) ""
  (interactive "P")
  (escape-to-emacs arg ?\e))

(defun vi-ctl-c (arg) ""
  (interactive "P")
  (escape-to-emacs arg ?\C-c))

(defun vi-ctl-x (arg) ""
  (interactive "P")
  (escape-to-emacs arg ?\C-x))

(defun vi-ctl-h (arg) ""
  (interactive "P")
  (escape-to-emacs arg ?\C-h))

;; prefix argmument for vi mode

;; In vi mode, prefix argument is a dotted pair (NUM . COM) where NUM
;; represents the numeric value of the prefix argument and COM represents
;; command prefix such as "c", "d", "m" and "y".

(defun vi-prefix-arg-value (char value com)
  "(CHAR VALUE COM)  Compute numeric prefix arg value.  Invoked by
CHAR.  VALUE is the value obtained so far, and COM is the command part
obtained so far."
  (while (and (>= char ?0) (<= char ?9))
    (setq value (+ (* (if (numberp value) value 0) 10) (- char ?0)))	    
    (setq char (read-char)))
  (setq prefix-arg value)
  (if com (setq prefix-arg (cons prefix-arg com)))
  (while (= char ?U)
    (describe-arg prefix-arg)
    (setq char (read-char)))
  (setq unread-command-char char))

(defun vi-prefix-arg-com (char value com)
  "Vi operator as prefix argument."
  (let ((cont t))
    (while (and cont
		(or (= char ?c) (= char ?d) (= char ?y)
		    (= char ?!) (= char ?<) (= char ?>) (= char ?=)
		    (= char ?#) (= char ?r) (= char ?R) (= char ?\")))
      (if com
	  ;; this means that we already have a command character, so we
	  ;; construct a com list and exit while.  however, if char is "
	  ;; it is an error.
	  (progn
	    ;; new com is (CHAR . OLDCOM)
	    (if (or (= char ?#) (= char ?\")) (error ""))
	    (setq com (cons char com))
	    (setq cont nil))
	;; if com is nil we set com as char, and read more.  again, if char
	;; is ", we read the name of register and store it in use-register.
	;; if char is !, =, or #, a copmlete com is formed so we exit while.
	(cond ((or (= char ?!) (= char ?=))
	       (setq com char)
	       (setq char (read-char))
	       (setq cont nil))
	      ((= char ?#)
	       ;; read a char and encode it as com
	       (setq com (+ 128 (read-char)))
	       (setq char (read-char))
	       (setq cont nil))
	      ((or (= char ?<) (= char ?>))
	       (setq com char)
	       (setq char (read-char))
	       (if (= com char) (setq com (cons char com)))
	       (setq cont nil))
	      ((= char ?\")
	       (let ((reg (read-char)))
		 (if (and (<= ?A reg) (<= reg ?z))
		     (setq use-register reg)
		   (error ""))
		 (setq char (read-char))))
	      (t
	       (setq com char)
	       (setq char (read-char)))))))
  (if (atom com)
      ;; com is a single char, so we construct prefix-arg 
      ;; and if char is ?, describe prefix arg, otherwise exit by
      ;; pushing the char back
      (progn
	(setq prefix-arg (cons value com))
	(while (= char ?U)
	  (describe-arg prefix-arg)
	  (setq char (read-char)))
	(setq unread-command-char char))
    ;; as com is non-nil, this means that we have a command to execute
    (if (or (= (car com) ?r) (= (car com) ?R))
	;; execute apropriate region command.
	(let ((char (car com)) (com (cdr com)))
	  (setq prefix-arg (cons value com))
	  (if (= char ?r) (vi-region prefix-arg)
	    (vi-Region prefix-arg))
	  ;; reset prefix-arg
	  (setq prefix-arg nil))
      ;; otherwise, reset prefix arg and call appropriate command
      (setq value (if (null value) 1 value))
      (setq prefix-arg nil)
      (cond ((equal com '(?c . ?c)) (vi-line (cons value ?C)))
	    ((equal com '(?d . ?d)) (vi-line (cons value ?D)))
	    ((equal com '(?d . ?y)) (vi-yank-defun))
	    ((equal com '(?y . ?y)) (vi-line (cons value ?Y)))
	    ((equal com '(?< . ?<)) (vi-line (cons value ?<)))
	    ((equal com '(?> . ?>)) (vi-line (cons value ?>)))
	    ((equal com '(?! . ?!)) (vi-line (cons value ?!)))
	    ((equal com '(?= . ?=)) (vi-line (cons value ?=)))
	    (t (error ""))))))

(defun describe-arg (arg)
  (let (val com)
    (setq val (P-val arg)
	  com (getcom arg))
    (if (null val)
	(if (null com)
	    (message "Value is nil, and commmand is nil.")
	  (message "Value is nil, and command is %c." com))
      (if (null com)
	  (message "Value is %d, and command is nil." val)
	(message "Value is %d, and command is %c." val com)))))

(defun vi-digit-argument (arg)
  "Begin numeric argument for the next command."
  (interactive "P")
  (vi-prefix-arg-value last-command-char nil
		       (if (consp arg) (cdr arg) nil)))

(defun vi-command-argument (arg)
  (interactive "P")
  (vi-prefix-arg-com
   last-command-char   
   (cond ((null arg) nil)
	 ((consp arg) (car arg))
	 ((numberp arg) arg)
	 (t (error "strange arg")))
   (cond ((null arg) nil)
	 ((consp arg) (cdr arg))
	 ((numberp arg) nil)
	 (t (error "strange arg")))))

(defun p-val (arg)
  "(ARG)  Get value part of prefix-argument ARG."
  (cond ((null arg) 1)
	((consp arg) (if (null (car arg)) 1 (car arg)))
	(t arg)))

(defun P-val (arg)
  "(ARG)  Get value part of prefix-argument ARG."
  (cond ((consp arg) (car arg))
	(t arg)))

(defun getcom (arg)
  "(ARG)  Get com part of prefix-argument ARG."
  (cond ((null arg) nil)
	((consp arg) (cdr arg))
	(t nil)))

(defun getCom (arg)
  "(ARG)  Get com part of prefix-argument ARG and modify it."
  (let ((com (getcom arg)))
    (cond ((equal com ?c) ?C)
	  ((equal com ?d) ?D)
	  ((equal com ?y) ?Y)
	  (t com))))

;; repeat last destructive command

(defun execute-com (m-com val com)
  "(M-COM VAL COM)  Execute command COM. The list (M-COM VAL COM) is set
 to d-com for later use by execute-d-com"
  (setq d-com (list m-com val (if (or (= com ?c) (= com ?C) (= com ?!))
				  (- com) com)))
  (if com
      (cond ((= com ?c) (vi-change com-point (point)))
	    ((= com (- ?c)) (vi-change-subr com-point (point)))
	    ((or (= com ?C) (= com (- ?C)))
	     (save-excursion
	       (set-mark com-point)
	       (enlarge-region (mark) (point))
	       (if use-register
		   (progn
		     (copy-to-register use-register (mark) (point) nil)
		     (setq use-register nil)))
	       (delete-region (mark) (point)))
	     (open-line 1)
	     (if (= com ?C) (change-mode-to-insert) (yank)))
	    ((= com ?d)
	     (if use-register
		 (progn
		   (copy-to-register use-register com-point (point) nil)
		   (setq use-register nil)))
	     (setq last-command
		   (if (eq last-command 'd-command) 'kill-region nil))
	     (kill-region com-point (point))
	     (setq this-command 'd-command))
	    ((= com ?D)
	     (save-excursion
	       (set-mark com-point)
	       (enlarge-region (mark) (point))
	       (if use-register
		   (progn
		     (copy-to-register use-register (mark) (point) nil)
		     (setq use-register nil)))
	       (setq last-command
		     (if (eq last-command 'D-command) 'kill-region nil))
	       (kill-region (mark) (point))
	       (if (eq m-com 'vi-line) (setq this-command 'D-command)))
	     (back-to-indentation))
	    ((= com ?y)
	     (if use-register
		 (progn
		   (copy-to-register use-register com-point (point) nil)
		   (setq use-register nil)))
	     (setq last-command nil)
	     (copy-region-as-kill com-point (point))
	     (goto-char com-point))
	    ((= com ?Y)
	     (save-excursion
	       (set-mark com-point)
	       (enlarge-region (mark) (point))
	       (if use-register
		   (progn
		     (copy-to-register use-register (mark) (point) nil)
		     (setq use-register nil)))
	       (setq last-command nil)
	       (copy-region-as-kill (mark) (point)))
	     (goto-char com-point))
	    ((or (= com ?!) (= com (- ?!)))
	     (save-excursion
	       (set-mark com-point)
	       (enlarge-region (mark) (point))
	       (shell-command-on-region
		(mark) (point)
		(if (= com ?!)
		    (setq vi-last-shell-com (vi-read-string "!"))
		  vi-last-shell-com)
		t)))
	    ((= com ?=)
	     (save-excursion
	       (set-mark com-point)
	       (enlarge-region (mark) (point))
	       (if (> (mark) (point)) (exchange-point-and-mark))
	       (indent-region (mark) (point) nil)))
	    ((= com ?<)
	     (save-excursion
	       (set-mark com-point)
	       (enlarge-region (mark) (point))
	       (indent-rigidly (mark) (point) (- shift-width)))
	     (goto-char com-point))
	    ((= com ?>)
	     (save-excursion
	       (set-mark com-point)
	       (enlarge-region (mark) (point))
	       (indent-rigidly (mark) (point) shift-width))
	     (goto-char com-point))
	    ((>= com 128)
	     ;; this is special command #
	     (special-prefix-com (- com 128))))))

(defun execute-d-com (arg)
  "(ARG)  Re-excute last destructive command."
  (interactive "P")
  (if (eq last-command 'vi-undo)
      ;; if the last command was vi-undo, then undo-more
      (vi-undo-more)
    ;; otherwise execute the command stored in d-com.  if arg is non-nil
    ;; its prefix value is used as new prefix value for the command.
    (let ((m-com (car d-com))
	  (val (P-val arg))
	  (com (car (cdr (cdr d-com)))))  
      (if (null val) (setq val (car (cdr d-com))))
      (funcall m-com (cons val com)))))

(defun special-prefix-com (char)
  "This command is invoked interactively by the key sequence #<char>"
  (cond ((= char ?c) (downcase-region (min com-point (point))
				    (max com-point (point))))
	((= char ?C) (upcase-region (min com-point (point))
				    (max com-point (point))))
	((= char ?g)
	 (global-execute (min com-point (point)) (max com-point (point))))
	((= char ?q)
	 (quote-region (min com-point (point)) (max com-point (point))))
	((= char ?s) (vi-spell-region com-point (point)))))

(defun vi-spell-region (beg end)
  (use-global-map emacs-global-map)
  (condition-case conditions
      (spell-region beg end)
    (quit
     (use-global-map vi-command-mode-map)
     (signal 'quit nil)))
  (use-global-map vi-command-mode-map))

;; undoing

(defun vi-undo () ""
  (interactive)
  (message "undo!")
  (undo-start)
  (undo-more 2)
  (setq this-command 'vi-undo))

(defun vi-undo-more () ""
  (message "undo more!")
  (undo-more 1)
  (setq this-command 'vi-undo))

;; redisplay and reset mode

(defun vi-recenter (line)
  (interactive "P")
  (if (null line)
      (progn
	(if (eq current-mode 'vi-mode)
	    (progn
	      (change-mode-to-emacs)
	      (change-mode-to-vi)))
	(recenter))
    (recenter line)))

(global-set-key "\^l" 'vi-recenter)

;; utilities

(defun string-tail (str)
  (if (or (null str) (string= str "")) nil
    (let (val)
      (save-window-excursion
	(save-restriction
	  (switch-to-buffer " *working-space*")
	  (set-mark (point))
	  (insert str)
	  (narrow-to-region (mark) (point))
	  (setq val (buffer-substring (1+ (mark)) (point)))
	  (delete-region (point-min) (point-max))))
      val)))

(defun vi-yank-defun ()
  (mark-defun)
  (copy-region-as-kill (point) (mark)))

(defun enlarge-region (beg end)
  "(BEG END)  Enlarge region between BEG and END."
  (if (< beg end)
      (progn (goto-char beg) (set-mark end))
    (goto-char end)
    (set-mark beg))
  (beginning-of-line)
  (exchange-point-and-mark)
  (next-line 1)
  (beginning-of-line)
  (if (> beg end) (exchange-point-and-mark)))

(defun global-execute (beg end)
  "Call last keyboad macro for each line in the region."
  (if (> (point) (mark)) (exchange-point-and-mark))
  (beginning-of-line)
  (call-last-kbd-macro)
  (while (< (point) (mark))
    (forward-line 1)
    (beginning-of-line)
    (call-last-kbd-macro)))

(defun quote-region (beg end)
  "Quote region by inserting the user supplied string at the beginning of
each line in the region."
  (setq vi-quote-string
	(let ((str
	       (vi-read-string (format "quote string \(default \"%s\"\): "
				       vi-quote-string))))
	  (if (string= str "") vi-quote-string str)))
  (if (> (point) (mark)) (exchange-point-and-mark))
  (beginning-of-line)
  (insert-string vi-quote-string)
  (beginning-of-line)
  (forward-line 1)
  (while (and (< (point) (mark)) (bolp))
    (insert-string vi-quote-string)
    (beginning-of-line)
    (forward-line 1)))

(defun end-with-a-newline-p (string)
  "Check if the string ends with a newline."
  (let (ans)
    (save-excursion
      (switch-to-buffer " *working-space*")
      (set-mark (point))
      (insert string)
      (narrow-to-region (mark) (point))
      (setq ans (search-backward "\n" (1- (point)) t))
      (widen)
      (delete-region (point-min) (point-max)))
    ans))

(defun vi-read-string (prompt &optional init)
  (use-global-map emacs-global-map)
  (let (str)
    (condition-case conditions
	(setq str (read-string prompt init))
      (quit
       (use-global-map vi-command-mode-map)
       (signal 'quit nil)))
    (use-global-map vi-command-mode-map)
    str))

(defun vi-read-buffer (prompt)
  (use-global-map emacs-global-map)
  (let (str)
    (condition-case conditions
	(setq str (read-buffer prompt))
      (quit
       (use-global-map vi-command-mode-map)
       (signal 'quit nil)))
    (use-global-map vi-command-mode-map)
    str))

(defun vi-read-file-name (prompt)
  (use-global-map emacs-global-map)
  (let (str)
    (condition-case conditions
	(setq str (read-file-name prompt))
      (quit
       (use-global-map vi-command-mode-map)
       (signal 'quit nil)))
    (use-global-map vi-command-mode-map)
    str))

;; insertion commands

(defun repeat-insert-command ()
  "()  This function is called when mode changes from insertion mode to
vi command mode.  It will repeat the insertion command if original insertion
command was invoked with argument > 1."
  (let ((i-com (car d-com)) (val (car (cdr d-com))))
    (if (> val 1)
	(progn        
	  (setq d-com (list i-com (1- val) ?r))
	  (execute-d-com nil)
	  (setq d-com (list i-com val ?r))))))

(defun vi-insert (arg) ""
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (setq d-com (list 'vi-insert val ?r))
    (if com (loop val (yank))
      (change-mode-to-insert))))

(defun vi-append (arg) ""
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (setq d-com (list 'vi-append val ?r))
    (if (not (eolp)) (forward-char))
    (if (equal com ?r)
	(loop val (yank))
      (change-mode-to-insert))))

(defun vi-Append (arg) ""
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (setq d-com (list 'vi-Append val ?r))
    (end-of-line)
    (if (equal com ?r)
	(loop val (yank))
      (change-mode-to-insert))))

(defun vi-Insert (arg) ""
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (setq d-com (list 'vi-Insert val ?r))
    (back-to-indentation)
    (if (equal com ?r)
	(loop val (yank))
      (change-mode-to-insert))))

(defun vi-open-line (arg) ""
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (setq d-com (list 'vi-open-line val ?r))
    (let ((col (current-indentation)))
      (if (equal com ?r)
	  (loop val
		(progn
		  (end-of-line)
		  (newline 1)
		  (indent-to col)
		  (yank)))
	(end-of-line)
	(newline 1)
	(indent-to col)
	(change-mode-to-insert)))))

(defun vi-Open-line (arg) ""
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
  (setq d-com (list 'vi-Open-line val ?r))
  (let ((col (current-indentation)))
    (if (equal com ?r)
	(loop val
	      (progn
		(beginning-of-line)
		(open-line 1)
		(indent-to col)
		(yank)))
      (beginning-of-line)
      (open-line 1)
      (indent-to col)
      (change-mode-to-insert)))))

(defun vi-ctl-open-line (arg)
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (setq d-com (list 'vi-ctl-open-line val ?r))
    (if (equal com ?r)
	(loop val
	      (progn
		(open-line 1)
		(yank)))
      (open-line 1)
      (change-mode-to-insert))))

(defun vi-substitute (arg)
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (save-excursion
      (set-mark (point))
      (forward-char val)
      (if (equal com ?r)
	  (vi-change-subr (mark) (point))
	(vi-change (mark) (point))))
    (setq d-com (list 'vi-substitute val ?r))))
  
(defun vi-substitute-line (arg)
  (interactive "p")
  (vi-line (cons arg ?C)))

;; selecting windows

(defun vi-next-window (arg)
  (interactive "p")
  (change-mode-to-emacs)
  (other-window (if (null arg) 1 arg))
  (if (not (string= (buffer-name (current-buffer)) " *Minibuf-1*"))
      (change-mode-to-vi)))

(defun vi-other-window (arg)
  (interactive "p")
  (change-mode-to-emacs)
  (other-window (if (null arg) 1 arg)))
     
;; line command

(defun vi-line (arg)
  (let ((val (car arg)) (com (cdr arg)))
    (move-marker com-point (point))
    (next-line (1- val))
    (execute-com 'vi-line val com)))

(defun vi-yank-line (arg)
  "(ARG)  Yank ARG lines (in vi's sense)"
  (interactive "P")
  (let ((val (p-val arg)))
    (vi-line (cons val ?Y))))
    
;; region command

(defun vi-region (arg)
  (interactive "P")
  (let ((val (P-val arg))
	(com (getcom arg)))
    (move-marker com-point (mark))
    (execute-com 'vi-region val com)))

(defun vi-Region (arg)
  (interactive "P")
  (let ((val (P-val arg))
	(com (getCom arg)))
    (move-marker com-point (point))
    (execute-com 'vi-Region val com)))
  
(defun vi-replace-char (arg)
  "(ARG)  Replace the following ARG chars by the character read."
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (setq d-com (list 'vi-replace-char val ?r))
    (vi-replace-char-subr (if (equal com ?r) d-char (read-char)) val)))

(defun vi-replace-char-subr (char arg)
  (delete-char arg t)
  (setq d-char char)
  (loop (if (> arg 0) arg (- arg)) (insert char))
  (backward-char arg))

(defun vi-replace-string ()
  "()  Replace string.  If you supply null string as the string to be
replaced, the query replace mode will toggle between string replace
and regexp replace."
  (interactive)
  (let (str)
    (setq str (vi-read-string
	       (if re-replace "Replace regexp: " "Replace string: ")))
    (if (string= str "")
	(progn
	  (setq re-replace (not re-replace))
	  (message (format "Replace mode changed to %s."
			   (if re-replace "regexp replace" "string replace"))))
      (if re-replace
	  (replace-regexp
	   str
	   (vi-read-string (format "Replace regexp \"%s\" with: " str)))
	(replace-string
	 str
	 (vi-read-string (format "Replace \"%s\" with: " str)))))))

;; basic cursor movement.  j, k, l, m commands.

(defun vi-forward-char (arg)
  "(ARG)  Move point right ARG characters (left if ARG negative).
On reaching end of buffer, stop and signal error."
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (if com (move-marker com-point (point)))
    (forward-char val)
    (if com (execute-com 'vi-forward-char val com))))

(defun vi-backward-char (arg)
  "(ARG)  Move point left ARG characters (right if ARG negative).
On reaching end of buffer, stop and signal error."
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (if com move-marker com-point (point))
    (backward-char val)
    (if com (execute-com 'vi-backward-char val com))))

;; word command

(defun vi-forward-word (arg) ""
  (interactive "P")
  (let ((val (p-val arg))
	(com (getcom arg)))
    (if com (move-marker com-point (point)))
    (forward-word val)
    (skip-chars-forward " \t\n")
    (if com
	(progn
	  (if (or (= com ?c) (= com (- ?c)))
	      (progn (backward-word 1) (forward-word 1)))
	  (if (or (= com ?d) (= com ?y))
	      (progn
		(backward-word 1)
		(forward-word 1)
		(skip-chars-forward " \t")))
	  (execute-com 'vi-forward-word val com)))))

(defun vi-end-of-word (arg)
  (interactive "P")
  (let ((val (p-val arg))
	(com (getcom arg)))
    (if com (move-marker com-point (point)))
    (forward-char)
    (forward-word val)
    (backward-char)
    (if com
	(progn
	  (forward-char)
	  (execute-com 'vi-end-of-word val com)))))
			 
(defun vi-backward-word (arg) ""
  (interactive "P")
  (let ((val (p-val arg))
	(com (getcom arg)))
    (if com (move-marker com-point (point)))
    (backward-word val)
    (if com (execute-com 'vi-backward-word val com))))

(defun vi-forward-Word (arg) ""
  (interactive "P")
  (let ((val (p-val arg))
	(com (getcom arg)))
    (if com (move-marker com-point (point)))
    (re-search-forward "[^ \t\n]*[ \t\n]+" nil t val)
    (if com
	(progn
	  (if (or (= com ?c) (= com (- ?c)))
	      (progn (backward-word 1) (forward-word 1)))
	  (if (or (= com ?d) (= com ?y))
	      (progn
		(backward-word 1)
		(forward-word 1)
		(skip-chars-forward " \t")))
	  (execute-com 'vi-forward-Word val com)))))

(defun vi-end-of-Word (arg) ""
  (interactive "P")
  (let ((val (p-val arg))
	(com (getcom arg)))
    (if com (move-marker com-point (point)))
    (forward-char)
    (if (re-search-forward "[^ \t\n]+" nil t val) (backward-char))
    (if com
	(progn
	  (forward-char)
	  (execute-com 'vi-end-of-Word val com)))))

(defun vi-backward-Word (arg) ""
  (interactive "P")
  (let ((val (p-val arg))
	(com (getcom arg)))
    (if com (move-marker com-point (point)))
    (if (re-search-backward "[ \t\n]+[^ \t\n]+" nil t val)
	(forward-char)
      (goto-char (point-min)))
    (if com (execute-com 'vi-backward-Word val com))))

(defun vi-beginning-of-line (arg)
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (if com (move-marker com-point (point)))
    (beginning-of-line val)
    (if com (execute-com 'vi-beginning-of-line val com))))

(defun vi-bol-and-skip-white (arg)
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (if com (move-marker com-point (point)))
    (back-to-indentation)
    (if com (execute-com 'vi-bol-and-skip-white val com))))

(defun vi-goto-eol (arg)
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (if com (move-marker com-point (point)))
    (end-of-line val)
    (if com (execute-com 'vi-goto-eol val com))))

(defun vi-next-line (arg)
  (interactive "P")
  (let ((val (p-val arg)) (com (getCom arg)))
    (if com (move-marker com-point (point)))
    (next-line val)
    (setq this-command 'next-line)
    (if com (execute-com 'vi-next-line val com))))

(defun vi-next-line-at-bol (arg)
  (interactive "P")
  (let ((val (p-val arg)) (com (getCom arg)))
    (if com (move-marker com-point (point)))
    (next-line val)
    (back-to-indentation)
    (if com (execute-com 'vi-next-line-at-bol val com))))

(defun vi-previous-line (arg)
  (interactive "P")
  (let ((val (p-val arg)) (com (getCom arg)))
    (if com (move-marker com-point (point)))
    (next-line (- val))
    (setq this-command 'previous-line)
    (if com (execute-com 'vi-previous-line val com))))

(defun vi-previous-line-at-bol (arg)
  (interactive "P")
  (let ((val (p-val arg)) (com (getCom arg)))
    (if com (move-marker com-point (point)))
    (next-line (- val))
    (back-to-indentation)
    (if com (execute-com 'vi-previous-line val com))))

(defun vi-change-to-eol (arg)
  (interactive "P")
  (vi-goto-eol (cons arg ?c)))

(defun vi-kill-line (arg)
  (interactive "P")
  (vi-goto-eol (cons arg ?d)))

;; moving around

(defun vi-G (arg)
  (interactive "P")
  (let ((val (P-val arg)) (com (getCom arg)))
    (move-marker com-point (point))
    (set-mark (point))
    (if (null val)
	(goto-char (point-max))
      (goto-char (point-min))
      (forward-line (1- val)))
    (back-to-indentation)
    (if com (execute-com 'vi-G val com))))

(defun vi-find-char (arg char forward offset)
  "(ARG CHAR FORWARD OFFSET)  Find ARG's occurence of CHAR on the
current line.  If FORWARD then search is forward, otherwise bacekward.
OFFSET is used to adjust point after search."
  (let ((arg (if forward arg (- arg))) point)
    (save-excursion
      (save-restriction
	(if (> arg 0)
	    (narrow-to-region
	     ;; forward search begins here
	     (if (eolp) (error "") (point))
	     ;; forward search ends here
	     (progn (next-line 1) (beginning-of-line) (point)))
	  (narrow-to-region
	   ;; backward search begins from here
	   (if (bolp) (error "") (point))
	   ;; backward search ends here
	   (progn (beginning-of-line) (point))))
	;; if arg > 0, point is forwarded before search.
	(if (> arg 0) (goto-char (1+ (point-min)))
	  (goto-char (point-max)))
	(setq point (scan-buffer (point) arg char))
	(if (or (and (> arg 0) (= point (point-max)))
		(and (< arg 0) (= point (point-min))))
	    (error ""))))
    (goto-char (+ point (if offset (if (> arg 0) -2 0) -1)))))

(defun vi-f (arg)
  "(ARG)  Find char on the line.  If called interactively read the char
to find from the terminal, and if called from execute-d-com, the char
last used is used.  This behaviour is controlled by the sign of prefix
numeric value."
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (if (> val 0)
	;; this means that the function was called interactively
	(setq f-char (read-char)
	      f-forward t
	      f-offset nil)
      (setq val (- val)))
    (if com (move-marker com-point (point)))
    (vi-find-char val f-char t nil)
    (setq val (- val))
    (if com
	(progn
	  (forward-char)
	  (execute-com 'vi-f val com)))))

(defun vi-t (arg) ""
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (if (> val 0)
	;; this means that the function was called interactively
	(setq f-char (read-char)
	      f-forward t
	      f-offset nil)
      (setq val (- val)))
    (if com (move-marker com-point (point)))
    (vi-find-char val f-char t t)
    (setq val (- val))
    (if com
	(progn
	  (forward-char)
	  (execute-com 'vi-t val com)))))

(defun vi-F (arg) ""
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (if (> val 0)
	;; this means that the function was called interactively
	(setq f-char (read-char)
	      f-forward t
	      f-offset nil)
      (setq val (- val)))
    (if com (move-marker com-point (point)))
    (vi-find-char val f-char nil nil)
    (setq val (- val))
    (if com (execute-com 'vi-F val com))))

(defun vi-T (arg) ""
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (if (> val 0)
	;; this means that the function was called interactively
	(setq f-char (read-char)
	      f-forward t
	      f-offset nil)
      (setq val (- val)))
    (if com (move-marker com-point (point)))
    (vi-find-char val f-char nil t)
    (setq val (- val))
    (if com (execute-com 'vi-T val com))))

(defun vi-semi-colon (arg) ""
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (if com (move-marker com-point (point)))
    (vi-find-char val f-char f-forward f-offset)
    (if com
	(progn
	  (if f-forward (forward-char))
	  (execute-com 'vi-semi-colon val com)))))

(defun vi-comma (arg) ""
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (if com (move-marker com-point (point)))
    (vi-find-char val f-char (not f-forward) f-offset)
    (if com
	(progn
	  (if f-forward (forward-char))
	  (execute-com 'vi-comma val com)))))

;; window scrolling etc.

(defun vi-window-top (arg) ""
  (interactive "P")
  (let ((val (p-val arg))
	(com (getCom arg)))
    (if com (move-marker com-point (point)))
    (move-to-window-line (1- val))
    (if com (execute-com 'vi-window-top val com))))

(defun vi-window-middle (arg) ""
  (interactive "P")
  (let ((val (p-val arg))
	(com (getCom arg)))
    (if com (move-marker com-point (point)))
    (move-to-window-line (+ (/ (1- (window-height)) 2) (1- val)))
    (if com (execute-com 'vi-window-middle val com))))

(defun vi-window-bottom (arg) ""
  (interactive "P")
  (let ((val (p-val arg))
	(com (getCom arg)))
    (if com (move-marker com-point (point)))
    (move-to-window-line (- val))
    (if com (execute-com 'vi-window-bottom val com))))

(defun vi-line-to-top (arg) ""
  (interactive "p")
  (recenter (1- arg)))

(defun vi-line-to-middle (arg) ""
  (interactive "p")
  (recenter (+ (1- arg) (/ (1- (window-height)) 2))))

(defun vi-line-to-bottom (arg) ""
  (interactive "p")
  (recenter (- (window-height) (1+ arg))))

;; paren match

(defun vi-paren-match (arg)
  "(ARG)  Go to the matching parenthesis."
  (interactive "P")
  (let ((com (getcom arg)))
    (cond ((looking-at "[\(\[{]")
	   (if com (move-marker com-point (point)))
	   (forward-sexp 1)
	   (if com
	       (execute-com 'vi-paren-match nil com)
	     (backward-char)))
	  ((looking-at "[])}]")
	   (forward-char)
	   (if com (move-marker com-point (point)))
	   (backward-sexp 1)
	   (if com (execute-com 'vi-paren-match nil com)))
	  (t (error "")))))

;; sentence and paragraph

(defun vi-forward-sentence (arg)
  (interactive "P")
  (let ((val (p-val arg))
	(com (getcom arg)))
    (if com (move-marker com-point (point)))
    (forward-sentence val)
    (if com (execute-com 'vi-forward-sentence nil com))))

(defun vi-backward-sentence (arg)
  (interactive "P")
  (let ((val (p-val arg))
	(com (getcom arg)))
    (if com (move-marker com-point (point)))
    (backward-sentence val)
    (if com (execute-com 'vi-backward-sentence nil com))))

(defun vi-forward-paragraph (arg)
  (interactive "P")
  (let ((val (p-val arg))
	(com (getCom arg)))
    (if com (move-marker com-point (point)))
    (forward-paragraph val)
    (if com (execute-com 'vi-forward-paragraph nil com))))

(defun vi-backward-paragraph (arg)
  (interactive "P")
  (let ((val (p-val arg))
	(com (getCom arg)))
    (if com (move-marker com-point (point)))
    (backward-paragraph val)
    (if com (execute-com 'vi-backward-paragraph nil com))))

;; scrolling

(defun vi-scroll (arg) ""
  (interactive "p")
  (if (> arg 0)
      (while (> arg 0)
	(scroll-up)
	(setq arg (1- arg)))
    (while (> 0 arg)
      (scroll-down)
      (setq arg (1+ arg)))))

(defun vi-scroll-back (arg) ""
  (interactive "p")
  (vi-scroll (- arg)))

(defun vi-scroll-down (arg) ""
  (interactive "P")
  (if (null arg) (scroll-down (/ (window-height) 2))
    (scroll-down arg)))

(defun vi-scroll-down-one (arg) ""
  (interactive "p")
  (scroll-down arg))

(defun vi-scroll-up (arg) ""
  (interactive "P")
  (if (null arg) (scroll-up (/ (window-height) 2))
    (scroll-up arg)))

(defun vi-scroll-up-one (arg) ""
  (interactive "p")
  (scroll-up arg))

;; splitting window

(defun buffer-in-two-windows ()
  (interactive)
  (delete-other-windows)
  (split-window-vertically nil))

;; searching

(defun vi-search-forward (arg)
  "(ARG) Search a string forward.  ARG is used to find the ARG's occurence
of the string.  Default is vanilla search.  Search mode can be toggled by
giving null search string."
  (interactive "P")
  (let ((val (P-val arg)) (com (getcom arg)))
    (setq s-forward t
	  s-string (vi-read-string (if re-search "RE-/" "/")))
    (if (string= s-string "")
	(progn
	  (setq re-search (not re-search))
	  (message (format "Search mode changed to %s search."
			   (if re-search "regular expression"
			     "vanilla"))))
      (vi-search s-string t val)
      (if com
	  (progn
	    (move-marker com-point (mark))
	    (execute-com 'vi-n val com))))))

(defun vi-search-backward (arg)
  "(ARG) Search a string backward.  ARG is used to find the ARG's occurence
of the string.  Default is vanilla search.  Search mode can be toggled by
giving null search string."
  (interactive "P")
  (let ((val (P-val arg)) (com (getcom arg)))
    (setq s-forward nil
	  s-string (vi-read-string (if re-search "RE-?" "?")))
    (if (string= s-string "")
	(progn
	  (setq re-search (not re-search))
	  (message (format "Search mode changed to %s search."
			   (if re-search "regular expression"
			     "vanilla"))))
      (vi-search s-string nil val)
      (if com
	  (progn
	    (move-marker com-point (mark))
	    (execute-com 'vi-n val com))))))

(defun vi-search (string forward arg &optional no-offset init-point)
  "(STRING FORWARD COUNT &optional NO-OFFSET) Search COUNT's occurrence of
STRING.  Search will be forward if FORWARD, otherwise backward."
  (let ((val (p-val arg)) (com (getcom arg))
	(null-arg (null (P-val arg))) (offset (not no-offset))
	(case-fold-search vi-case-fold-search)
	start-point)
    (setq start-point (if init-point init-point (point)))
    (if forward
	(condition-case conditions
	    (progn
	      (if (and offset (not (eobp))) (forward-char))
	      (if re-search
		  (progn
		    (re-search-forward string nil nil val)
		    (re-search-backward string))
		(search-forward string nil nil val)
		(search-backward string))
	      (push-mark start-point))
	  (search-failed
	   (if (and null-arg vi-search-wrap-around)
	       (progn
		 (goto-char (point-min))
		 (vi-search string forward (cons 1 com) t start-point))
	     (goto-char start-point)
	     (signal 'search-failed (cdr conditions)))))
      (condition-case conditions
	    (progn
	      (if re-search
		    (re-search-backward string nil nil val)
		(search-backward string nil nil val))
	      (push-mark start-point))
	  (search-failed
	   (if (and null-arg vi-search-wrap-around)
	       (progn
		 (goto-char (point-max))
		 (vi-search string forward (cons 1 com) t start-point))
	     (goto-char start-point)
	     (signal 'search-failed (cdr conditions))))))))

(defun vi-isearch (arg)
  "(ARG)  Primitive incremental search.  Last serch string is provided, which
you can edit and submit again.  Given ARG, search ARG's occurnece."
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (setq s-string
	  (vi-read-string
	   (if s-forward
	       (if re-search "RE-/" "/")
	     (if re-search "RE-?" "?"))
	   s-string))
    (vi-search s-string s-forward val)
    (if com (execute-com 'vi-n val com))))

(defun vi-n (arg)
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (if (null s-string) (error "No previous search string."))
    (vi-search s-string s-forward arg)
    (if com (execute-com 'vi-n val com))))

(defun vi-N (arg)
  (interactive "P")
  (let ((val (p-val arg)) (com (getcom arg)))
    (if (null s-string) (error "No previous search string."))
    (vi-search s-string (not s-forward) arg)
    (if com (execute-com 'vi-N val com))))

;; visiting and killing files, buffers

(defun vi-switch-to-buffer ()
  (interactive)
  (let (buffer)
    (setq buffer
	  (vi-read-buffer
		  (format "switch to buffer \(%s\): "
			  (buffer-name (other-buffer (current-buffer))))))
    (change-mode-to-emacs)
    (switch-to-buffer buffer)
    (change-mode-to-vi)))

(defun vi-switch-to-buffer-other-window ()
  (interactive)
  (let (buffer)
    (setq buffer
	  (vi-read-buffer
		  (format "Switch to buffer \(%s\): "
			  (buffer-name (other-buffer (current-buffer))))))
    (change-mode-to-emacs)
    (switch-to-buffer-other-window buffer)
    (change-mode-to-vi)))

(defun vi-kill-buffer ()
  (interactive)
  (let (buffer buffer-name)
    (setq buffer-name
	  (vi-read-buffer
	   (format "Kill buffer \(%s\): "
		   (buffer-name (current-buffer)))))
    (setq buffer
	  (if (null buffer-name)
	      (current-buffer)
	    (get-buffer buffer-name)))
    (if (null buffer) (error "Buffer %s nonexistent." buffer-name))
    (if (buffer-modified-p buffer)
	(error "I can't kill modified buffer.")
      (if (equal buffer (current-buffer))
	  (progn
	    (change-mode-to-emacs)
	    (kill-buffer buffer)
	    (change-mode-to-vi))
	(kill-buffer buffer)))))

(defun vi-find-file ()
  (interactive)
  (let (file)
    (setq file (vi-read-file-name "visit file: "))
    (change-mode-to-emacs)
    (switch-to-buffer (find-file-noselect file))
    (change-mode-to-vi)))

(defun vi-find-file-other-window ()
  (interactive)
  (let (file)
    (setq file (vi-read-file-name "Visit file: "))
    (change-mode-to-emacs)
    (switch-to-buffer-other-window (find-file-noselect file))
    (change-mode-to-vi)))

(defun vi-insert-file ()
  (interactive)
  (let (file)
    (setq file (vi-read-file-name "insert file: "))
    (change-mode-to-emacs)
    (insert-file file)
    (change-mode-to-vi)))

(defun vi-write-file ()
  (interactive)
  (write-file (vi-read-file-name "write file: ")))

(defun vi-info-on-file ()
  (interactive)
  (message "\"%s\" line %d of %d"
	   (if (buffer-file-name) (buffer-file-name) "")
	   (1+ (count-lines (point-min) (point)))
	   (1+ (count-lines (point-min) (point-max)))))

;; modifying buffer

(defun vi-undo () ""
  (interactive)
  (message "undo!")
  (undo-start)
  (undo-more 2)
  (setq this-command 'vi-undo))

(defun vi-undo-more () ""
  (message "undo more!")
  (undo-more 1)
  (setq this-command 'vi-undo))

;; yank and pop

(defun vi-yank ()
  "yank first element of kill-ring silently."
  (save-excursion
    (push-mark-silent (point))
    (insert (car kill-ring-yank-pointer))
    (exchange-point-and-mark))
  (skip-chars-forward " \t"))

(defun vi-p (arg)
  (interactive "P")
  (let ((val (p-val arg)))
    (setq d-com (list 'vi-p val nil))
    (if (end-with-a-newline-p (car kill-ring-yank-pointer))
	(progn
	  (next-line 1)
	  (beginning-of-line))
      (if (and (not (eolp)) (not (eobp))) (forward-char)))
    (if use-register
	(progn
	  (insert-register use-register)
	  (setq use-register nil))
      (loop val (vi-yank)))))

(defun vi-P (arg)
  (interactive "P")
  (let ((val (p-val arg)))
    (setq d-com (list 'vi-P val nil))
    (if (end-with-a-newline-p (car kill-ring-yank-pointer))
	(beginning-of-line))
    (if use-register
	(progn
	  (insert-register use-register)
	  (setq use-register nil))
      (loop val (vi-yank)))))

(defun vi-delete-char (arg)
  (interactive "P")
  (let ((val (p-val arg)))
    (setq d-com (list 'vi-delete-char val nil))
    (delete-char val t)))

(defun vi-delete-backward-char (arg)
  (interactive "P")
  (let ((val (p-val arg)))
    (setq d-com (list 'vi-delete-backward-char val nil))
    (delete-backward-char val t)))

;; join lines.

(defun vi-join-lines (arg)
  "(ARG)  Join this line to next, if ARG is nil.  Otherwise, join
 ARG lines"
  (interactive "*P")
  (let ((val (P-val arg)))
    (setq d-com (list 'vi-join-lines val nil))
    (loop (if (null val) 1 (1- val))
	  (progn
	    (beginning-of-line)
	    (forward-line 1)
	    (delete-region (point) (1- (point)))
	    (fixup-whitespace)))))

;; making small changes

(defun vi-change (beg end)
  (setq c-string
	(vi-read-string (format "%s => " (buffer-substring beg end))))
  (vi-change-subr beg end))

(defun vi-change-subr (beg end)
  (if use-register
      (progn
	(copy-to-register use-register beg end nil)
	(setq use-register nil)))
  (kill-region beg end)
  (setq this-command 'vi-change)
  (insert-string c-string))

;; query replace

(defun vi-query-replace ()
  "()  Query replace.  If you supply null string as the string to be
replaced, the query replace mode will toggle between string replace
and regexp replace."
  (interactive)
  (let (str)
    (setq str (vi-read-string
	       (if re-query-replace "Query replace regexp: "
		 "Query replace: ")))
    (if (string= str "")
	(progn
	  (setq re-query-replace (not re-query-replace))
	  (message "Query replace mode changed to %s."
		   (if re-query-replace "regexp replace" "string replace")))
      (if re-query-replace
	  (query-replace-regexp
	   str
	   (vi-read-string (format "Query replace regexp \"%s\" with: " str)))
	(query-replace
	 str
	 (vi-read-string (format "Query replace \"%s\" with: " str)))))))

;; marking

(defun vi-mark-beginning-of-buffer ()
  (interactive)
  (set-mark (point))
  (goto-char (point-min))
  (exchange-point-and-mark)
  (message "mark set at the beginning of buffer"))

(defun vi-mark-end-of-buffer ()
  (interactive)
  (set-mark (point))
  (goto-char (point-max))
  (exchange-point-and-mark)
  (message "mark set at the end of buffer"))

(defun vi-mark-point (char)
  (interactive "c")
  (cond ((and (<= ?a char) (<= char ?z))
	 (point-to-register (- char (- ?a ?\C-a))))
	((= char ?<) (vi-mark-beginning-of-buffer))
	((= char ?>) (vi-mark-end-of-buffer))
	((= char ?.) (push-mark))
	((= char ?,) (set-mark-command 1))
	((= char ?D) (mark-defun))
	(t (error ""))))

(defun vi-goto-mark (arg)
  (interactive "P")
  (let ((char (read-char)) (com (getcom arg)))
    (vi-goto-mark-subr char com nil)))

(defun vi-goto-mark-and-skip-white (arg)
  (interactive "P")
  (let ((char (read-char)) (com (getCom arg)))
    (vi-goto-mark-subr char com t)))

(defun vi-goto-mark-subr (char com skip-white)
  (cond ((and (<= ?a char) (<= char ?z))
	 (let ((buff (current-buffer)))
	   (if com (move-marker com-point (point)))
	   (change-mode-to-emacs)
	   (goto-char (register-to-point (- char (- ?a ?\C-a))))
	   (if skip-white (back-to-indentation))
	   (change-mode-to-vi)
	   (if com
	       (if (equal buff (current-buffer))
		   (execute-com (if skip-white
				    'vi-goto-mark-and-skip-white
				  'vi-goto-mark)
				nil com)
		 (change-mode-to-emacs)
		 (switch-to-buffer buff)
		 (goto-char com-point)
		 (change-mode-to-vi)
		 (error "")))))
	((and (not skip-white) (= char ?`))
	 (if com (move-marker com-point (point)))
	 (exchange-point-and-mark)
	 (if com (execute-com 'vi-goto-mark nil com)))
	((and skip-white (= char ?'))
	 (if com (move-marker com-point (point)))
	 (exchange-point-and-mark)
	 (back-to-indentation)
	 (if com (execute-com 'vi-goto-mark-and-skip-white nil com)))
	(t (error ""))))

(defun vi-exchange-point-and-mark ()
  (interactive)
  (exchange-point-and-mark)
  (back-to-indentation))

;; commands in insertion mode

(defun delete-backward-word (arg)
  (interactive "p")
  (save-excursion
    (set-mark (point))
    (backward-word arg)
    (delete-region (point) (mark))))


;; key bindings

(set 'vi-command-mode-map (make-keymap))

(define-key vi-command-mode-map "\e" 'vi-ESC)

(define-key vi-command-mode-map "+" 'vi-next-line-at-bol)
(define-key vi-command-mode-map "-" 'vi-previous-line-at-bol)

(define-key vi-command-mode-map "0" 'vi-beginning-of-line)
(define-key vi-command-mode-map "1" 'vi-digit-argument)
(define-key vi-command-mode-map "2" 'vi-digit-argument)
(define-key vi-command-mode-map "3" 'vi-digit-argument)
(define-key vi-command-mode-map "4" 'vi-digit-argument)
(define-key vi-command-mode-map "5" 'vi-digit-argument)
(define-key vi-command-mode-map "6" 'vi-digit-argument)
(define-key vi-command-mode-map "7" 'vi-digit-argument)
(define-key vi-command-mode-map "8" 'vi-digit-argument)
(define-key vi-command-mode-map "9" 'vi-digit-argument)

(define-key vi-command-mode-map "\^a" 'beginning-of-line)
(define-key vi-command-mode-map "\^b" 'vi-scroll-back)
(define-key vi-command-mode-map "\^c" 'vi-ctl-c)
(define-key vi-command-mode-map "\^d" 'vi-scroll-up)
(define-key vi-command-mode-map "\^e" 'vi-scroll-up-one)
(define-key vi-command-mode-map "\^f" 'vi-scroll)
(define-key vi-command-mode-map "\^g" 'vi-info-on-file)
(define-key vi-command-mode-map "\^h" 'vi-backward-char)
(define-key vi-command-mode-map "\^l" 'vi-recenter)
(define-key vi-command-mode-map "\^m" 'vi-next-line-at-bol)
(define-key vi-command-mode-map "\^n" 'vi-next-window)
(define-key vi-command-mode-map "\^o" 'vi-ctl-open-line)
(define-key vi-command-mode-map "\^s" 'suspend-emacs)
(define-key vi-command-mode-map "\^u" 'vi-scroll-down)
(define-key vi-command-mode-map "\^x" 'vi-ctl-x)
(define-key vi-command-mode-map "\^y" 'vi-scroll-down-one)
(define-key vi-command-mode-map "\^z" 'change-mode-to-emacs)
(define-key vi-command-mode-map ":" 'vi-isearch)
(define-key vi-command-mode-map "." 'execute-d-com)

(define-key vi-command-mode-map " " 'vi-forward-char)
(define-key vi-command-mode-map "|" 'vi-goto-col)
(define-key vi-command-mode-map ";" 'vi-semi-colon)
(define-key vi-command-mode-map "," 'vi-comma)
(define-key vi-command-mode-map "%" 'vi-paren-match)
(define-key vi-command-mode-map "}" 'vi-forward-paragraph)
(define-key vi-command-mode-map "{" 'vi-backward-paragraph)
(define-key vi-command-mode-map ")" 'vi-forward-sentence)
(define-key vi-command-mode-map "(" 'vi-backward-sentence)
(define-key vi-command-mode-map "^" 'vi-bol-and-skip-white)
(define-key vi-command-mode-map "$" 'vi-goto-eol)
(define-key vi-command-mode-map "`" 'vi-goto-mark)
(define-key vi-command-mode-map "'" 'vi-goto-mark-and-skip-white)
(define-key vi-command-mode-map "\"" 'vi-command-argument)
(define-key vi-command-mode-map "/" 'vi-search-forward)
(define-key vi-command-mode-map "\\" 'escape-to-emacs)
(define-key vi-command-mode-map "?" 'vi-search-backward)
(define-key vi-command-mode-map "#" 'vi-command-argument)
(define-key vi-command-mode-map "*" 'call-last-kbd-macro)
(define-key vi-command-mode-map "!" 'vi-command-argument)
(define-key vi-command-mode-map "=" 'vi-command-argument)
(define-key vi-command-mode-map "<" 'vi-command-argument)
(define-key vi-command-mode-map ">" 'vi-command-argument)
(define-key vi-command-mode-map "a" 'vi-append)
(define-key vi-command-mode-map "b" 'vi-backward-word)
(define-key vi-command-mode-map "c" 'vi-command-argument)
(define-key vi-command-mode-map "d" 'vi-command-argument)
(define-key vi-command-mode-map "e" 'vi-end-of-word)
(define-key vi-command-mode-map "f" 'vi-f)
(define-key vi-command-mode-map "g" 'keyboard-quit)
(define-key vi-command-mode-map "h" 'vi-backward-char)
(define-key vi-command-mode-map "i" 'vi-insert)
(define-key vi-command-mode-map "j" 'vi-next-line)
(define-key vi-command-mode-map "k" 'vi-previous-line)
(define-key vi-command-mode-map "l" 'vi-forward-char)
(define-key vi-command-mode-map "m" 'vi-mark-point)
(define-key vi-command-mode-map "n" 'vi-n)
(define-key vi-command-mode-map "o" 'vi-open-line)
(define-key vi-command-mode-map "p" 'vi-p)
(define-key vi-command-mode-map "r" 'vi-replace-char)
(define-key vi-command-mode-map "s" 'vi-substitute)
(define-key vi-command-mode-map "t" 'vi-t)
(define-key vi-command-mode-map "u" 'vi-undo)
(define-key vi-command-mode-map "v" 'vi-find-file)
(define-key vi-command-mode-map "w" 'vi-forward-word)
(define-key vi-command-mode-map "x" 'vi-delete-char)
(define-key vi-command-mode-map "y" 'vi-command-argument)
(define-key vi-command-mode-map "zH" 'vi-line-to-top)
(define-key vi-command-mode-map "zM" 'vi-line-to-middle)
(define-key vi-command-mode-map "zL" 'vi-line-to-bottom)
(define-key vi-command-mode-map "z\^m" 'vi-line-to-top)
(define-key vi-command-mode-map "z+" 'vi-line-to-top)
(define-key vi-command-mode-map "z." 'vi-line-to-middle)
(define-key vi-command-mode-map "z-" 'vi-line-to-bottom)
(define-key vi-command-mode-map "A" 'vi-Append)
(define-key vi-command-mode-map "B" 'vi-backward-Word)
(define-key vi-command-mode-map "C" 'vi-change-to-eol)
(define-key vi-command-mode-map "D" 'vi-kill-line)
(define-key vi-command-mode-map "E" 'vi-end-of-Word)
(define-key vi-command-mode-map "F" 'vi-F)
(define-key vi-command-mode-map "G" 'vi-G)
(define-key vi-command-mode-map "H" 'vi-window-top)
(define-key vi-command-mode-map "I" 'vi-Insert)
(define-key vi-command-mode-map "J" 'vi-join-lines)
(define-key vi-command-mode-map "K" 'vi-kill-buffer)
(define-key vi-command-mode-map "L" 'vi-window-bottom)
(define-key vi-command-mode-map "M" 'vi-window-middle)
(define-key vi-command-mode-map "N" 'vi-N)
(define-key vi-command-mode-map "O" 'vi-Open-line)
(define-key vi-command-mode-map "P" 'vi-P)
(define-key vi-command-mode-map "Q" 'vi-query-replace)
(define-key vi-command-mode-map "R" 'vi-replace-string)
(define-key vi-command-mode-map "S" 'vi-substitute-line)
(define-key vi-command-mode-map "T" 'vi-T)
(define-key vi-command-mode-map "V" 'vi-find-file-other-window)
(define-key vi-command-mode-map "W" 'vi-forward-Word)
(define-key vi-command-mode-map "X" 'vi-delete-backward-char)
(define-key vi-command-mode-map "Y" 'vi-yank-line)
(define-key vi-command-mode-map "ZZ" 'save-buffers-kill-emacs)
(define-key vi-command-mode-map "\177" 'vi-delete-backward-char)

;; set up local keymap for various major modes.

(defun make-vi-local-map (mode)
  (set 'vi-local-map '(keymap))
  (cond ((eq mode 'shell-mode)
	 (define-key vi-local-map "\C-m" 'vi-shell-send-input)
	 (define-key vi-local-map "\C-p" 'copy-previous-shell-input))
	((eq mode 'rmail-mode)
	 (define-key vi-local-map "q" 'vi-rmail-quit))
	(t (setq vi-local-map '(keymap)))))

(defun vi-rmail-quit ()
  (interactive)
  (change-mode-to-emacs)
  (rmail-quit))

;; command for shell mode.

(defun vi-shell-send-input ()
  (interactive)
  (end-of-line)
  (shell-send-input))

(defvar vi-shell-previous-input-point nil)

(defun copy-previous-shell-input (arg)
  "Copy previous shell input.  If ARG, ARG'th previous shell input will be
copied. Repeated application of this command continue to get older input."
  (interactive "p")
  (delete-region (point) (point-max))
  (save-excursion
    (if (eq last-command 'copy-previous-shell-input)
	(goto-char vi-shell-previous-input-point)
      (beginning-of-line))
    (if (re-search-backward shell-prompt-pattern nil t arg)
	(progn
	  (setq vi-shell-previous-input-point (point))
	  (re-search-forward shell-prompt-pattern nil t)
	  (let ((copy (buffer-substring (point)
					(progn (end-of-line) (point)))))
	    (goto-char (point-max))
	    (insert copy)))
      (error "No more previous shell command."))))

(defun vip-version ()
  (interactive)
  (message "VIP version 17.1 of July 23, 1986"))

(if (file-exists-p "~/.vip") (load "~/.vip"))

sato@SU-Russell.ARPA (Masahiko Sato) (07/24/86)

Below you will find an example of .vip file which you can place in your
home directory.  If the file .vip exists in your home directory it will
be automatically loaded when vi.el is loaded.  You can prepare your own
.vip file and use it to customize VIP.

I will explain this particluar .vip.  Loading .vip below will change
the meaning of some keys in vi-mode.

- "^g" and "g"  In vi, "^g" is used to get information about the file
associated to the current buffer.  Here, "g" will do that, and "^g" is
used to abort a command (this is for compatibility with emacs-mode.)

- " " (space) and "^M" (return)  Now these keys will scroll up and
down the text of current window.  Convenient for reading.

- "s" and "S"  They are used to Switch to a specified buffer.  Useful for
switching to already existing buffer since buffer name completion is
provided.  Also a default buffer will be given as part of the prompt,
to which you can switch by just hitting a <return> key.  "s" is used to
select buffer in the current window, while "S" selects buffer in another
window.

- "C" and "X"  These keys will exit from vi-mode and return to
emacs-mode temporarily.  If you hit "C" ("X"), Emacs will be in emacs-mode
and will believe that you hit "^C" ("^X", resp.) in emacs-mode.
Moreover, if the following character you hit is an upper case letter, then
Emacs will believe that you hit the corresponding control character.
You will be in vi-mode again after the command is executed.  For example,
hitting "XS" in vi-mode is the same as hitting "^X^S" in emacs-mode.
You get the same effect by hitting "^X^S" in vi-mode, but the idea here
is that you can execute useful Emacs commands without typing control
characters.
For example, if you hit "^X" followed by "2", then the current window will 
be splited into 2 and you will be in vi-mode again.

In addition to these, "ctl-x-map" is slightly modified:

- "^X3"  This is equivalent to "^X1^X2" (1 + 2 = 3).

Finally, I give a summary of key bindings for basic functions related to
files, buffers and windows.

[window]
"^N"		Next window.
"X1"*, "^X1"	Delete other windows.
"X2"*, "^X2"	Split current window into two windows.
"X3"*, "^X3"	Show current buffer in two windows.

[buffer]
"s"*		(vi-switch-to-buffer) Switch to the specified buffer in the
		current window.
"S"*		(vi-switch-to-buffer-other-window)  Switch to the specified
		buffer in another window.
"K"		Kill the current buffer if it is not modified.
"XS"*, "^X^S"	Save the current buffer in the file associated to the buffer.

[file]
"v"		Visit specified file in the current window.
"V"		Visit specified file in another window.
"XW"*, "^X^W"	Write current buffer into the specified file.
"Xi"*, "^Xi"	Insert specified file after point.

[others]
"X("*, "^X("	Start remembering keyboard macro.
"X)"*, "^X)"	Finish remembering keyboard macro.
"*"		Call last remembered keyboard macro.
"^S", "XZ"*, "^X^Z"	Suspend Emacs.
"Q"		Query replace.
"R"		Replace.

Keys marked by * are effective only .vip file below is loaded.

** masahiko (sato@su-russell.arpa) **

===============  Cut here  ===============
(defun ctl-c-equivalent (arg)
  (interactive "P")
  (ctl-key-equivalent "\^c" arg))

(defun ctl-x-equivalent (arg)
  (interactive "P")
  (ctl-key-equivalent "\^x" arg))

(defun ctl-meta-equivalent (arg)
  (interactive "P")
  (ctl-key-equivalent "\e\^x" arg))

(defun ctl-key-equivalent (key arg)
  (let ((buff (current-buffer)) (char (read-char)))
    (change-mode-to-emacs)
    (if (and (<= ?A char) (<= char ?Z))
	(setq char (- char (- ?A ?\C-a))))
    (condition-case conditions
	(progn
	  (setq prefix-arg arg)
	  (command-execute
	   (get-editor-command (current-local-map) global-map
			       (format "%s%s" key (char-to-string char)))))
      (quit
       (signal 'quit nil))
      (error
       (message-conditions conditions)))
    (if (equal buff (current-buffer)) (change-mode-to-vi))))
  
(setq default-mode-line-format
      "%1*%1*- %[Emacs:%] %14b %3p (%m) <> %M %-")

(define-key vi-command-mode-map "\^g" 'keyboard-quit)
(define-key vi-command-mode-map "\^h" 'vi-ctl-h)
(define-key vi-command-mode-map "\^m" 'vi-scroll-back)
(define-key vi-command-mode-map " " 'vi-scroll)
(define-key vi-command-mode-map "@" 'ctl-meta-equivalent)
(define-key vi-command-mode-map "g" 'vi-info-on-file)
(define-key vi-command-mode-map "s" 'vi-switch-to-buffer)
(define-key vi-command-mode-map "C" 'ctl-c-equivalent)
(define-key vi-command-mode-map "S" 'vi-switch-to-buffer-other-window)
(define-key vi-command-mode-map "X" 'ctl-x-equivalent)

(define-key ctl-x-map "3" 'buffer-in-two-windows)