viper-ex.el 73.2 KB
Newer Older
Michael Kifer's avatar
Michael Kifer committed
1
;;; viper-ex.el --- functions implementing the Ex commands for Viper
Richard M. Stallman's avatar
Richard M. Stallman committed
2

3
;; Copyright (C) 1994, 1995, 1996, 1997, 1998, 2000, 2001, 2002, 2003,
Glenn Morris's avatar
Glenn Morris committed
4
;;   2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
Karl Heuer's avatar
Karl Heuer committed
5

6
;; Author: Michael Kifer <kifer@cs.stonybrook.edu>
7

Karl Heuer's avatar
Karl Heuer committed
8 9
;; This file is part of GNU Emacs.

10
;; GNU Emacs is free software: you can redistribute it and/or modify
Karl Heuer's avatar
Karl Heuer committed
11
;; it under the terms of the GNU General Public License as published by
12 13
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
Karl Heuer's avatar
Karl Heuer committed
14 15 16 17 18 19 20

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
21
;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
Karl Heuer's avatar
Karl Heuer committed
22

23 24 25
;;; Commentary:

;;; Code:
Michael Kifer's avatar
Michael Kifer committed
26

Michael Kifer's avatar
Michael Kifer committed
27
(provide 'viper-ex)
Karl Heuer's avatar
Karl Heuer committed
28

Michael Kifer's avatar
Michael Kifer committed
29
;; Compiler pacifier
Michael Kifer's avatar
Michael Kifer committed
30
(defvar read-file-name-map)
Michael Kifer's avatar
Michael Kifer committed
31 32 33 34 35 36
(defvar viper-use-register)
(defvar viper-s-string)
(defvar viper-shift-width)
(defvar viper-ex-history)
(defvar viper-related-files-and-buffers-ring)
(defvar viper-local-search-start-marker)
Michael Kifer's avatar
Michael Kifer committed
37
(defvar viper-expert-level)
Michael Kifer's avatar
Michael Kifer committed
38 39
(defvar viper-custom-file-name)
(defvar viper-case-fold-search)
Michael Kifer's avatar
Michael Kifer committed
40
(defvar explicit-shell-file-name)
41
(defvar compile-command)
Michael Kifer's avatar
Michael Kifer committed
42

Michael Kifer's avatar
Michael Kifer committed
43 44 45 46
;; loading happens only in non-interactive compilation
;; in order to spare non-viperized emacs from being viperized
(if noninteractive
    (eval-when-compile
47 48
      (if (not (featurep 'viper-cmd))
	  (require 'viper-cmd))
49
      ))
Michael Kifer's avatar
Michael Kifer committed
50 51 52 53
;; end pacifier

(require 'viper-util)

Michael Kifer's avatar
Michael Kifer committed
54
(defgroup viper-ex nil
55
  "Viper support for Ex commands."
Michael Kifer's avatar
Michael Kifer committed
56 57 58 59
  :prefix "ex-"
  :group 'viper)


Michael Kifer's avatar
Michael Kifer committed
60

Karl Heuer's avatar
Karl Heuer committed
61
;;; Variables
Michael Kifer's avatar
Michael Kifer committed
62

Michael Kifer's avatar
Michael Kifer committed
63
(defconst viper-ex-work-buf-name " *ex-working-space*")
64
(defvar viper-ex-work-buf (get-buffer-create viper-ex-work-buf-name))
Michael Kifer's avatar
Michael Kifer committed
65
(defconst viper-ex-tmp-buf-name " *ex-tmp*")
66
(defconst viper-ex-print-buf-name " *ex-print*")
67
(defvar viper-ex-print-buf (get-buffer-create viper-ex-print-buf-name))
Michael Kifer's avatar
Michael Kifer committed
68 69


70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
;;; ex-commands...

(defun ex-cmd-obsolete (name)
  (error "`%s': Obsolete command, not supported by Viper" name))

(defun ex-cmd-not-yet (name)
  (error "`%s': Command not implemented in Viper" name))

;; alist entries: name (in any order), command, cont(??)
;; If command is a string, then that is an alias to the real command
;; to execute (for instance, ":m" -> ":move").
;; command attributes:
;;   is-mashed: the command's args may be jammed right up against the command
;;   one-letter: this is a one-letter token.  Any text appearing after
;;	             the name gets appended as an argument for the command
;;               i.e. ":kabc" gets turned into (ex-mark "abc")
(defconst ex-token-alist '(
	("!"			(ex-command))
	("&"			(ex-substitute t))
	("="			(ex-line-no))
	(">"			(ex-line "right"))
	("<"			(ex-line "left"))
	("Buffer"		(if ex-cycle-other-window
				    (viper-switch-to-buffer)
				  (viper-switch-to-buffer-other-window)))
	("Next"			(ex-next (not ex-cycle-other-window)))
	("PreviousRelatedFile"  (ex-next-related-buffer -1))
	("RelatedFile"	    	(ex-next-related-buffer 1))
	("W"			"Write")
	("WWrite"		(save-some-buffers t))
	("Write"		(save-some-buffers))
	("a"			"append")
	("args"			(ex-args))
	("buffer"		(if ex-cycle-other-window
				    (viper-switch-to-buffer-other-window)
				  (viper-switch-to-buffer)))
	("c"			"change")
	;; ch should be "change" but maintain old viper compatibility
	("ch"			"chdir")
	("cd"			(ex-cd))
	("chdir"		(ex-cd))
	("copy"			(ex-copy nil))
	("customize"	    	(customize-group "viper"))
	("delete"		(ex-delete))
	("edit"			(ex-edit))
115
	("file"			(ex-set-visited-file-name))
116 117 118 119 120 121 122 123
	("g"			"global")
	("global"		(ex-global nil) is-mashed)
	("goto"			(ex-goto))
	("help"			(ex-help))
	("join"			(ex-line "join"))
	("k"			(ex-mark) one-letter)
	("kmark"		(ex-mark))
	("m"			"move")
124
	("make"     	    	(ex-compile))
125 126 127 128 129 130 131
	; old viper doesn't specify a default for "ma" so leave it undefined
	("map"			(ex-map))
	("mark"			(ex-mark))
	("move"			(ex-copy t))
	("next"			(ex-next ex-cycle-other-window))
	("p"			"print")
	("preserve"		(ex-preserve))
132
	("print"		(ex-print))
133 134 135 136 137 138 139 140 141 142 143 144 145 146
	("put"			(ex-put))
	("pwd"			(ex-pwd))
	("quit"			(ex-quit))
	("r"			"read")
	("re"			"read")
	("read"			(ex-read))
	("recover"		(ex-recover))
	("rewind"		(ex-rewind))
	("s"			"substitute")
	("su"			"substitute")
	("sub"			"substitute")
	("set"			(ex-set))
	("shell"		(ex-shell))
	("source"		(ex-source))
147
	("stop"			(suspend-emacs))
148 149 150 151 152 153 154 155 156 157 158 159 160 161
	("sr"			(ex-substitute t t))
	("submitReport"	    	(viper-submit-report))
	("substitute"	    	(ex-substitute) is-mashed)
	("suspend"		(suspend-emacs))
	("t"			"transfer")
	("tag"			(ex-tag))
	("transfer"		(ex-copy nil))
	("u"			"undo")
	("un"			"undo")
	("undo"			(viper-undo))
	("unmap"		(ex-unmap))
	("v"			"vglobal")
	("version"		(viper-version))
	("vglobal"		(ex-global t) is-mashed)
162
	("visual"		(ex-edit))
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
	("w"			"write")
	("wq"			(ex-write t))
	("write"		(ex-write nil))
	("xit"			(ex-write t))
	("yank"			(ex-yank))
	("~"			(ex-substitute t t))

	("append"		(ex-cmd-obsolete "append"))
	("change"		(ex-cmd-obsolete "change"))
	("insert"		(ex-cmd-obsolete "insert"))
	("open"			(ex-cmd-obsolete "open"))

	("list"			(ex-cmd-not-yet "list"))
	("z"			(ex-cmd-not-yet "z"))
	("#"			(ex-cmd-not-yet "#"))

	("abbreviate"	    	(error "`%s': Vi abbreviations are obsolete.  Use the more powerful Emacs abbrevs" ex-token))
	("unabbreviate"	    	(error "`%s': Vi abbreviations are obsolete.  Use the more powerful Emacs abbrevs" ex-token))
	))

;; No code should touch anything in the alist entry!  (other than the name,
;; "car entry", of course)   This way, changing this data structure
;; requires changing only the following ex-cmd functions...

;; Returns cmd if the command may be jammed right up against its
;; arguments, nil if there must be a space.
;; examples of mashable commands: g// g!// v// s// sno// sm//
(defun ex-cmd-is-mashed-with-args (cmd)
  (if (eq 'is-mashed (car (nthcdr 2 cmd))) cmd))

;; Returns true if this is a one-letter command that may be followed
;; by anything, no whitespace needed.  This is a special-case for ":k".
(defun ex-cmd-is-one-letter (cmd)
  (if (eq 'one-letter (car (nthcdr 2 cmd))) cmd))

;; Executes the function associated with the command
(defun ex-cmd-execute (cmd)
  (eval (cadr cmd)))

;; If this is a one-letter magic command, splice in args.
(defun ex-splice-args-in-1-letr-cmd (key list)
204 205
  (let ((oneletter (ex-cmd-is-one-letter (assoc (substring key 0 1) list))))
    (if oneletter
206
	(list key
207
	      (append (cadr oneletter)
208
		      (if (< 1 (length key)) (list (substring key 1))))
209
	      (car (cdr (cdr oneletter))) ))
210 211 212 213 214 215 216 217
	))


;; Returns the alist entry for the appropriate key.
;; Tries to complete the key before using it in the alist.
;; If there is no appropriate key (no match or duplicate matches) return nil
(defun ex-cmd-assoc (key list)
  (let ((entry (try-completion key list))
218
	result)
219 220 221 222 223 224 225 226 227 228 229 230 231 232
    (setq result (cond
		  ((eq entry t)     (assoc key list))
		  ((stringp entry)  (or (ex-splice-args-in-1-letr-cmd key list)
					(assoc entry list)))
		  ((eq entry nil)   (ex-splice-args-in-1-letr-cmd key list))
		  (t nil)
		  ))
    ;; If we end up with an alias, look up the alias...
    (if (stringp (cadr result))
    	(setq result (ex-cmd-assoc (cadr result) list)))
    ;; and return the corresponding alist entry
    result
    ))

Michael Kifer's avatar
Michael Kifer committed
233 234

;; A-list of Ex variables that can be set using the :set command.
235
(defconst ex-variable-alist
Michael Kifer's avatar
Michael Kifer committed
236
  '(("wrapscan") ("ws") ("wrapmargin") ("wm")
Michael Kifer's avatar
Michael Kifer committed
237
    ("tabstop-global") ("ts-g") ("tabstop") ("ts")
Michael Kifer's avatar
Michael Kifer committed
238
    ("showmatch") ("sm") ("shiftwidth") ("sw") ("shell") ("sh")
239
    ("readonly") ("ro")
Michael Kifer's avatar
Michael Kifer committed
240 241 242
    ("nowrapscan") ("nows") ("noshowmatch") ("nosm")
    ("noreadonly") ("noro") ("nomagic") ("noma")
    ("noignorecase") ("noic")
Michael Kifer's avatar
Michael Kifer committed
243
    ("noautoindent-global") ("noai-g") ("noautoindent") ("noai")
Michael Kifer's avatar
Michael Kifer committed
244
    ("magic") ("ma") ("ignorecase") ("ic")
245 246
    ("autoindent-global") ("ai-g") ("autoindent") ("ai")
    ("all")
Michael Kifer's avatar
Michael Kifer committed
247
    ))
Michael Kifer's avatar
Michael Kifer committed
248

249

Michael Kifer's avatar
Michael Kifer committed
250

Michael Kifer's avatar
Michael Kifer committed
251 252 253
;; Token recognized during parsing of Ex commands (e.g., "read", "comma")
(defvar ex-token nil)

254
;; Type of token.
Michael Kifer's avatar
Michael Kifer committed
255 256 257 258 259 260
;; If non-nil, gives type of address; if nil, it is a command.
(defvar ex-token-type nil)

;; List of addresses passed to Ex command
(defvar ex-addresses nil)

Michael Kifer's avatar
Michael Kifer committed
261
;; This flag is supposed to be set only by `#', `print', and `list',
Michael Kifer's avatar
Michael Kifer committed
262 263
;; none of which is implemented.  So, it and the pices of the code it
;; controls are dead weight.  We keep it just in case this might be
Michael Kifer's avatar
Michael Kifer committed
264
;; needed in the future.
Michael Kifer's avatar
Michael Kifer committed
265 266 267 268 269 270 271 272 273
(defvar ex-flag nil)

;; "buffer" where Ex commands keep deleted data.
;; In Emacs terms, this is a register.
(defvar ex-buffer nil)

;; Value of ex count.
(defvar ex-count nil)

Michael Kifer's avatar
Michael Kifer committed
274
;; Flag indicating that :global Ex command is being executed.
Michael Kifer's avatar
Michael Kifer committed
275
(defvar ex-g-flag nil)
Michael Kifer's avatar
Michael Kifer committed
276
;; Flag indicating that :vglobal Ex command is being executed.
Michael Kifer's avatar
Michael Kifer committed
277
(defvar ex-g-variant nil)
278 279
;; Marks to operate on during a :global Ex command.
(defvar ex-g-marks nil)
Michael Kifer's avatar
Michael Kifer committed
280 281 282 283 284 285 286 287 288 289 290

;; Save reg-exp used in substitute.
(defvar ex-reg-exp nil)


;; Replace pattern for substitute.
(defvar ex-repl nil)

;; Pattern for global command.
(defvar ex-g-pat nil)

Michael Kifer's avatar
Michael Kifer committed
291
(defcustom ex-unix-type-shell
Michael Kifer's avatar
Michael Kifer committed
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
  (let ((case-fold-search t))
    (and (stringp shell-file-name)
	 (string-match
	  (concat
	   "\\("
	   "csh$\\|csh.exe$"
	   "\\|"
	   "ksh$\\|ksh.exe$"
	   "\\|"
	   "^sh$\\|sh.exe$"
	   "\\|"
	   "[^a-z]sh$\\|[^a-z]sh.exe$"
	   "\\|"
	   "bash$\\|bash.exe$"
	   "\\)")
	  shell-file-name)))
Michael Kifer's avatar
Michael Kifer committed
308
  "Is the user using a unix-type shell under a non-OS?"
309
  :type 'boolean
Michael Kifer's avatar
Michael Kifer committed
310
  :group 'viper-ex)
Michael Kifer's avatar
Michael Kifer committed
311

Michael Kifer's avatar
Michael Kifer committed
312
(defcustom ex-unix-type-shell-options
Michael Kifer's avatar
Michael Kifer committed
313 314 315 316 317 318 319
  (let ((case-fold-search t))
    (if ex-unix-type-shell
	(cond ((string-match "\\(csh$\\|csh.exe$\\)" shell-file-name)
	       "-f") ; csh: do it fast
	      ((string-match "\\(bash$\\|bash.exe$\\)" shell-file-name)
	       "-noprofile") ; bash: ignore .profile
	      )))
320
  "Options to pass to the Unix-style shell.
Michael Kifer's avatar
Michael Kifer committed
321
Don't put `-c' here, as it is added automatically."
322
  :type '(choice (const nil) string)
Michael Kifer's avatar
Michael Kifer committed
323
  :group 'viper-ex)
Michael Kifer's avatar
Michael Kifer committed
324

325
(defcustom ex-compile-command "make"
326
  "The command to run when the user types :make."
327 328 329
  :type 'string
  :group 'viper-ex)

Michael Kifer's avatar
Michael Kifer committed
330 331 332 333 334 335 336
(defcustom viper-glob-function
  (cond (ex-unix-type-shell 'viper-glob-unix-files)
	(viper-ms-style-os-p 'viper-glob-mswindows-files) ; Microsoft OS
	(t  'viper-glob-unix-files) ; presumably UNIX
	)
  "Expand the file spec containing wildcard symbols.
The default tries to set this variable to work with Unix, Windows,
Dan Nicolaescu's avatar
Dan Nicolaescu committed
337
and OS/2.
Michael Kifer's avatar
Michael Kifer committed
338

339
However, if it doesn't work right for some types of Unix shells or some OS,
Michael Kifer's avatar
Michael Kifer committed
340 341 342 343 344
the user should supply the appropriate function and set this variable to the
corresponding function symbol."
  :type 'symbol
  :group 'viper-ex)

Michael Kifer's avatar
Michael Kifer committed
345

Michael Kifer's avatar
Michael Kifer committed
346 347
;; Remembers the previous Ex tag.
(defvar ex-tag nil)
Michael Kifer's avatar
Michael Kifer committed
348

Michael Kifer's avatar
Michael Kifer committed
349 350
;; file used by Ex commands like :r, :w, :n
(defvar ex-file nil)
Michael Kifer's avatar
Michael Kifer committed
351

Michael Kifer's avatar
Michael Kifer committed
352 353
;; If t, tells Ex that this is a variant-command, i.e., w>>, r!, etc.
(defvar ex-variant nil)
Michael Kifer's avatar
Michael Kifer committed
354

Michael Kifer's avatar
Michael Kifer committed
355 356
;; Specified the offset of an Ex command, such as :read.
(defvar ex-offset nil)
Michael Kifer's avatar
Michael Kifer committed
357

Michael Kifer's avatar
Michael Kifer committed
358 359
;; Tells Ex that this is a w>> command.
(defvar ex-append nil)
Michael Kifer's avatar
Michael Kifer committed
360

Michael Kifer's avatar
Michael Kifer committed
361 362 363
;; File containing the shell command to be executed at Ex prompt,
;; e.g., :r !date
(defvar ex-cmdfile nil)
Michael Kifer's avatar
Michael Kifer committed
364
(defvar ex-cmdfile-args "")
365

Michael Kifer's avatar
Michael Kifer committed
366
;; flag used in viper-ex-read-file-name to indicate that we may be reading
Michael Kifer's avatar
Michael Kifer committed
367
;; multiple file names.  Used for :edit and :next
Michael Kifer's avatar
Michael Kifer committed
368
(defvar viper-keep-reading-filename nil)
Michael Kifer's avatar
Michael Kifer committed
369

Michael Kifer's avatar
Michael Kifer committed
370
(defcustom ex-cycle-other-window t
Michael Kifer's avatar
Michael Kifer committed
371
  "*If t, :n and :b cycles through files and buffers in other window.
Michael Kifer's avatar
Michael Kifer committed
372
Then :N and :B cycles in the current window.  If nil, this behavior is
Michael Kifer's avatar
Michael Kifer committed
373 374 375
reversed."
  :type 'boolean
  :group 'viper-ex)
Michael Kifer's avatar
Michael Kifer committed
376

Michael Kifer's avatar
Michael Kifer committed
377 378 379 380
(defcustom ex-cycle-through-non-files nil
  "*Cycle through *scratch* and other buffers that don't visit any file."
  :type 'boolean
  :group 'viper-ex)
Michael Kifer's avatar
Michael Kifer committed
381

Michael Kifer's avatar
Michael Kifer committed
382
;; Last shell command executed with :! command.
Michael Kifer's avatar
Michael Kifer committed
383
(defvar viper-ex-last-shell-com nil)
384

Michael Kifer's avatar
Michael Kifer committed
385
;; Indicates if Minibuffer was exited temporarily in Ex-command.
Michael Kifer's avatar
Michael Kifer committed
386
(defvar viper-incomplete-ex-cmd nil)
387

Michael Kifer's avatar
Michael Kifer committed
388
;; Remembers the last ex-command prompt.
Michael Kifer's avatar
Michael Kifer committed
389
(defvar viper-last-ex-prompt "")
Michael Kifer's avatar
Michael Kifer committed
390 391


Michael Kifer's avatar
Michael Kifer committed
392
;; Get a complete ex command
Michael Kifer's avatar
Michael Kifer committed
393
(defun viper-get-ex-com-subr ()
394
  (let (cmd case-fold-search)
Michael Kifer's avatar
Michael Kifer committed
395 396 397 398
    (set-mark (point))
    (re-search-forward "[a-zA-Z][a-zA-Z]*")
    (setq ex-token-type 'command)
    (setq ex-token (buffer-substring (point) (mark t)))
399 400 401 402
    (setq cmd (ex-cmd-assoc ex-token ex-token-alist))
    (if cmd
	(setq ex-token (car cmd))
      (setq ex-token-type 'non-command))
Michael Kifer's avatar
Michael Kifer committed
403
    ))
Michael Kifer's avatar
Michael Kifer committed
404

Michael Kifer's avatar
Michael Kifer committed
405 406
;; Get an ex-token which is either an address or a command.
;; A token has a type, \(command, address, end-mark\), and a value
Michael Kifer's avatar
Michael Kifer committed
407
(defun viper-get-ex-token ()
Michael Kifer's avatar
Michael Kifer committed
408
  (save-window-excursion
409
    (setq viper-ex-work-buf (get-buffer-create viper-ex-work-buf-name))
Michael Kifer's avatar
Michael Kifer committed
410
    (set-buffer viper-ex-work-buf)
Michael Kifer's avatar
Michael Kifer committed
411
    (skip-chars-forward " \t|")
Michael Kifer's avatar
Michael Kifer committed
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
    (let ((case-fold-search t))
      (cond ((looking-at "#")
	     (setq ex-token-type 'command)
	     (setq ex-token (char-to-string (following-char)))
	     (forward-char 1))
	    ((looking-at "[a-z]") (viper-get-ex-com-subr))
	    ((looking-at "\\.")
	     (forward-char 1)
	     (setq ex-token-type 'dot))
	    ((looking-at "[0-9]")
	     (set-mark (point))
	     (re-search-forward "[0-9]*")
	     (setq ex-token-type
		   (cond ((eq ex-token-type 'plus) 'add-number)
			 ((eq ex-token-type 'minus) 'sub-number)
			 (t 'abs-number)))
	     (setq ex-token
429
		   (string-to-number (buffer-substring (point) (mark t)))))
Michael Kifer's avatar
Michael Kifer committed
430 431 432 433 434 435 436 437 438 439 440
	    ((looking-at "\\$")
	     (forward-char 1)
	     (setq ex-token-type 'end))
	    ((looking-at "%")
	     (forward-char 1)
	     (setq ex-token-type 'whole))
	    ((looking-at "+")
	     (cond ((or (looking-at "+[-+]") (looking-at "+[\n|]"))
		    (forward-char 1)
		    (insert "1")
		    (backward-char 1)
Michael Kifer's avatar
Michael Kifer committed
441
		  (setq ex-token-type 'plus))
Michael Kifer's avatar
Michael Kifer committed
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
		   ((looking-at "+[0-9]")
		    (forward-char 1)
		    (setq ex-token-type 'plus))
		   (t
		    (error viper-BadAddress))))
	    ((looking-at "-")
	     (cond ((or (looking-at "-[-+]") (looking-at "-[\n|]"))
		    (forward-char 1)
		    (insert "1")
		    (backward-char 1)
		    (setq ex-token-type 'minus))
		   ((looking-at "-[0-9]")
		    (forward-char 1)
		    (setq ex-token-type 'minus))
		   (t
		    (error viper-BadAddress))))
	    ((looking-at "/")
	     (forward-char 1)
	     (set-mark (point))
	     (let ((cont t))
	       (while (and (not (eolp)) cont)
		 ;;(re-search-forward "[^/]*/")
		 (re-search-forward "[^/]*\\(/\\|\n\\)")
		 (if (not (viper-looking-back "[^\\\\]\\(\\\\\\\\\\)*\\\\/"))
		     (setq cont nil))))
	     (backward-char 1)
	     (setq ex-token (buffer-substring (point) (mark t)))
	     (if (looking-at "/") (forward-char 1))
	     (setq ex-token-type 'search-forward))
	    ((looking-at "\\?")
	     (forward-char 1)
	     (set-mark (point))
	     (let ((cont t))
	       (while (and (not (eolp)) cont)
		 ;;(re-search-forward "[^\\?]*\\?")
		 (re-search-forward "[^\\?]*\\(\\?\\|\n\\)")
		 (if (not (viper-looking-back "[^\\\\]\\(\\\\\\\\\\)*\\\\\\?"))
		     (setq cont nil))
		 (backward-char 1)
		 (if (not (looking-at "\n")) (forward-char 1))))
	     (setq ex-token-type 'search-backward)
	     (setq ex-token (buffer-substring (1- (point)) (mark t))))
	    ((looking-at ",")
	     (forward-char 1)
	     (setq ex-token-type 'comma))
	    ((looking-at ";")
	     (forward-char 1)
	     (setq ex-token-type 'semi-colon))
	    ((looking-at "[!=><&~]")
	     (setq ex-token-type 'command)
	     (setq ex-token (char-to-string (following-char)))
	     (forward-char 1))
	    ((looking-at "'")
	     (setq ex-token-type 'goto-mark)
	     (forward-char 1)
	     (cond ((looking-at "'") (setq ex-token nil))
		   ((looking-at "[a-z]") (setq ex-token (following-char)))
		   (t (error "Marks are ' and a-z")))
	     (forward-char 1))
	    ((looking-at "\n")
	     (setq ex-token-type 'end-mark)
	     (setq ex-token "goto"))
	    (t
	     (error viper-BadExCommand))))))
Michael Kifer's avatar
Michael Kifer committed
506

Michael Kifer's avatar
Michael Kifer committed
507 508
;; Reads Ex command.  Tries to determine if it has to exit because command
;; is complete or invalid.  If not, keeps reading command.
Michael Kifer's avatar
Michael Kifer committed
509 510
(defun ex-cmd-read-exit ()
  (interactive)
Michael Kifer's avatar
Michael Kifer committed
511
  (setq viper-incomplete-ex-cmd t)
Michael Kifer's avatar
Michael Kifer committed
512 513 514 515 516 517 518 519 520 521 522 523 524 525 526
  (let ((quit-regex1 (concat
		      "\\(" "set[ \t]*"
		      "\\|" "edit[ \t]*"
		      "\\|" "[nN]ext[ \t]*"
		      "\\|" "unm[ \t]*"
		      "\\|" "^[ \t]*rep"
		      "\\)"))
	(quit-regex2 (concat
		      "[a-zA-Z][ \t]*"
		      "\\(" "!" "\\|" ">>"
		      "\\|" "\\+[0-9]+"
		      "\\)"
		      "*[ \t]*$"))
	(stay-regex (concat
		     "\\(" "^[ \t]*$"
Michael Kifer's avatar
Michael Kifer committed
527
		     "\\|" "[?/].*"
Michael Kifer's avatar
Michael Kifer committed
528 529 530 531 532 533 534 535 536 537 538 539
		     "\\|" "[ktgjmsz][ \t]*$"
		     "\\|" "^[ \t]*ab.*"
		     "\\|" "tr[ansfer \t]*"
		     "\\|" "sr[ \t]*"
		     "\\|" "mo.*"
		     "\\|" "^[ \t]*k?ma[^p]*"
		     "\\|" "^[ \t]*fi.*"
		     "\\|" "v?gl.*"
		     "\\|" "[vg][ \t]*$"
		     "\\|" "jo.*"
		     "\\|" "^[ \t]*ta.*"
		     "\\|" "^[ \t]*una.*"
Michael Kifer's avatar
Michael Kifer committed
540 541 542
		     ;; don't jump up in :s command
		     "\\|" "^[ \t]*\\([`'][a-z]\\|[.,%]\\)*[ \t]*su.*"
		     "\\|" "^[ \t]*\\([`'][a-z]\\|[.,%]\\)*[ \t]*s[^a-z].*"
Michael Kifer's avatar
Michael Kifer committed
543 544 545 546 547
		     "\\|" "['`][a-z][ \t]*"
		     ;; r! assumes that the next one is a shell command
		     "\\|" "\\(r\\|re\\|rea\\|read\\)[ \t]*!"
		     ;; w ! assumes that the next one is a shell command
		     "\\|" "\\(w\\|wr\\|wri\\|writ.?\\)[ \t]+!"
Michael Kifer's avatar
Michael Kifer committed
548 549 550
		     "\\|" "![ \t]*[a-zA-Z].*"
		     "\\)"
		     "!*")))
551

Michael Kifer's avatar
Michael Kifer committed
552
    (save-window-excursion ;; put cursor at the end of the Ex working buffer
553
      (setq viper-ex-work-buf (get-buffer-create viper-ex-work-buf-name))
Michael Kifer's avatar
Michael Kifer committed
554
      (set-buffer viper-ex-work-buf)
Michael Kifer's avatar
Michael Kifer committed
555
      (goto-char (point-max)))
Michael Kifer's avatar
Michael Kifer committed
556 557 558
    (cond ((viper-looking-back quit-regex1) (exit-minibuffer))
	  ((viper-looking-back stay-regex)  (insert " "))
	  ((viper-looking-back quit-regex2) (exit-minibuffer))
Michael Kifer's avatar
Michael Kifer committed
559
	  (t (insert " ")))))
560

Michael Kifer's avatar
Michael Kifer committed
561 562 563 564
;; complete Ex command
(defun ex-cmd-complete ()
  (interactive)
  (let (save-pos dist compl-list string-to-complete completion-result)
565

Michael Kifer's avatar
Michael Kifer committed
566 567 568
    (save-excursion
      (setq dist (skip-chars-backward "[a-zA-Z!=>&~]")
	    save-pos (point)))
569

Michael Kifer's avatar
Michael Kifer committed
570
    (if (or (= dist 0)
Michael Kifer's avatar
Michael Kifer committed
571 572
	    (viper-looking-back "\\([ \t]*['`][ \t]*[a-z]*\\)")
	    (viper-looking-back
Michael Kifer's avatar
Michael Kifer committed
573
	     "^[ \t]*[a-zA-Z!=>&~][ \t]*[/?]*[ \t]+[a-zA-Z!=>&~]+"))
Michael Kifer's avatar
Michael Kifer committed
574 575
	;; Preceding characters are not the ones allowed in an Ex command
	;; or we have typed past command name.
Michael Kifer's avatar
Michael Kifer committed
576
	;; Note: we didn't do parsing, so there can be surprises.
Michael Kifer's avatar
Michael Kifer committed
577 578
	(if (or (viper-looking-back "[a-zA-Z!=>&~][ \t]*[/?]*[ \t]*")
		(viper-looking-back "\\([ \t]*['`][ \t]*[a-z]*\\)")
Michael Kifer's avatar
Michael Kifer committed
579 580
		(looking-at "[^ \t\n\C-m]"))
	    nil
581
	  (with-output-to-temp-buffer "*Completions*"
Michael Kifer's avatar
Michael Kifer committed
582
	    (display-completion-list
Michael Kifer's avatar
Michael Kifer committed
583
	     (viper-alist-to-list ex-token-alist))))
Michael Kifer's avatar
Michael Kifer committed
584 585 586 587
      ;; Preceding chars may be part of a command name
      (setq string-to-complete (buffer-substring save-pos (point)))
      (setq completion-result
	    (try-completion string-to-complete ex-token-alist))
588

Michael Kifer's avatar
Michael Kifer committed
589
      (cond ((eq completion-result t)  ; exact match--do nothing
Michael Kifer's avatar
Michael Kifer committed
590
	     (viper-tmp-insert-at-eob " (Sole completion)"))
Michael Kifer's avatar
Michael Kifer committed
591
	    ((eq completion-result nil)
Michael Kifer's avatar
Michael Kifer committed
592
	     (viper-tmp-insert-at-eob " (No match)"))
Michael Kifer's avatar
Michael Kifer committed
593 594 595 596 597 598
	    (t  ;; partial completion
	     (goto-char save-pos)
	     (delete-region (point) (point-max))
	     (insert completion-result)
	     (let (case-fold-search)
	       (setq compl-list
Michael Kifer's avatar
Michael Kifer committed
599
		     (viper-filter-alist (concat "^" completion-result)
Michael Kifer's avatar
Michael Kifer committed
600 601
				       ex-token-alist)))
	     (if (> (length compl-list) 1)
602
		 (with-output-to-temp-buffer "*Completions*"
Michael Kifer's avatar
Michael Kifer committed
603
		   (display-completion-list
Michael Kifer's avatar
Michael Kifer committed
604
		    (viper-alist-to-list (reverse compl-list)))))))
Michael Kifer's avatar
Michael Kifer committed
605
      )))
Michael Kifer's avatar
Michael Kifer committed
606

607 608

;; Read Ex commands
Michael Kifer's avatar
Michael Kifer committed
609 610 611
;; ARG is a prefix argument. If given, the ex command runs on the region
;;(without the user having to specify the address :a,b
;; STRING is the command to execute. If nil, then Viper asks you to enter the
612
;; command.
Michael Kifer's avatar
Michael Kifer committed
613 614
(defun viper-ex (arg &optional string)
  (interactive "P")
Michael Kifer's avatar
Michael Kifer committed
615 616 617 618 619 620 621
  (or string
      (setq ex-g-flag nil
	    ex-g-variant nil))
  (let* ((map (copy-keymap minibuffer-local-map))
	 (address nil)
	 (cont t)
	 (dot (point))
Michael Kifer's avatar
Michael Kifer committed
622 623 624
	 reg-beg-line reg-end-line
	 reg-beg reg-end
	 initial-str
Michael Kifer's avatar
Michael Kifer committed
625
	 prev-token-type com-str)
Michael Kifer's avatar
Michael Kifer committed
626
    (viper-add-keymap viper-ex-cmd-map map)
Michael Kifer's avatar
Michael Kifer committed
627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642

    (if arg
	(progn
	  (viper-enlarge-region (mark t) (point))
	  (if (> (point) (mark t))
	      (setq reg-beg (mark t)
		    reg-end (point))
	    (setq reg-end (mark t)
		  reg-beg (point)))
	  (save-excursion
	    (goto-char reg-beg)
	    (setq reg-beg-line (1+ (count-lines (point-min) (point)))
		  reg-end-line
		  (+ reg-beg-line (count-lines reg-beg reg-end) -1)))))
    (if reg-beg-line
	(setq initial-str (format "%d,%d" reg-beg-line reg-end-line)))
643 644

    (setq com-str
645 646 647 648 649 650 651 652 653 654 655 656 657
	  (if string
	      (concat initial-str string)
	    (viper-read-string-with-history
	     ":"
	     initial-str
	     'viper-ex-history
	     ;; no default when working on region
	     (if initial-str
		 nil
	       (car viper-ex-history))
	     map
	     (if initial-str
		 " [Type command to execute on current region]"))))
Michael Kifer's avatar
Michael Kifer committed
658 659
    (save-window-excursion
      ;; just a precaution
660
      (setq viper-ex-work-buf (get-buffer-create viper-ex-work-buf-name))
Michael Kifer's avatar
Michael Kifer committed
661
      (set-buffer viper-ex-work-buf)
Michael Kifer's avatar
Michael Kifer committed
662 663 664 665 666 667
      (delete-region (point-min) (point-max))
      (insert com-str "\n")
      (goto-char (point-min)))
    (setq ex-token-type nil
	  ex-addresses nil)
    (while cont
Michael Kifer's avatar
Michael Kifer committed
668
      (viper-get-ex-token)
Michael Kifer's avatar
Michael Kifer committed
669 670
      (cond ((memq ex-token-type '(command end-mark))
	     (if address (setq ex-addresses (cons address ex-addresses)))
671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
	     (viper-deactivate-mark)
	     (let ((cmd (ex-cmd-assoc ex-token ex-token-alist)))
	       (if (null cmd)
		   (error "`%s': %s" ex-token viper-BadExCommand))
	       (ex-cmd-execute cmd)
	       (if (or (ex-cmd-is-mashed-with-args cmd)
		       (ex-cmd-is-one-letter cmd))
		   (setq cont nil)
		 (save-excursion
		   (save-window-excursion
		     (setq viper-ex-work-buf
			   (get-buffer-create viper-ex-work-buf-name))
		     (set-buffer viper-ex-work-buf)
		     (skip-chars-forward " \t")
		     (cond ((looking-at "|")
			    (forward-char 1))
			   ((looking-at "\n")
			    (setq cont nil))
			   (t (error
			       "`%s': %s" ex-token viper-SpuriousText)))
		     )))
	       ))
Michael Kifer's avatar
Michael Kifer committed
693
	    ((eq ex-token-type 'non-command)
Michael Kifer's avatar
Michael Kifer committed
694
	     (error "`%s': %s" ex-token viper-BadExCommand))
Michael Kifer's avatar
Michael Kifer committed
695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711
	    ((eq ex-token-type 'whole)
	     (setq address nil)
	     (setq ex-addresses
		   (if ex-addresses
		       (cons (point-max) ex-addresses)
		     (cons (point-max) (cons (point-min) ex-addresses)))))
	    ((eq ex-token-type 'comma)
	     (if (eq prev-token-type 'whole)
		 (setq address (point-min)))
	     (setq ex-addresses
		   (cons (if (null address) (point) address) ex-addresses)))
	    ((eq ex-token-type 'semi-colon)
	     (if (eq prev-token-type 'whole)
		 (setq address (point-min)))
	     (if address (setq dot address))
	     (setq ex-addresses
		   (cons (if (null address) (point) address) ex-addresses)))
Michael Kifer's avatar
Michael Kifer committed
712
	    (t (let ((ans (viper-get-ex-address-subr address dot)))
Michael Kifer's avatar
Michael Kifer committed
713 714
		 (if ans (setq address ans)))))
      (setq prev-token-type ex-token-type))))
715

Michael Kifer's avatar
Michael Kifer committed
716

Michael Kifer's avatar
Michael Kifer committed
717
;; Get a regular expression and set `ex-variant', if found
718 719
;; Viper doesn't parse the substitution or search patterns.
;; In particular, it doesn't expand ~ into the last substitution.
Michael Kifer's avatar
Michael Kifer committed
720
(defun viper-get-ex-pat ()
Michael Kifer's avatar
Michael Kifer committed
721
  (save-window-excursion
Michael Kifer's avatar
Michael Kifer committed
722 723
    (setq viper-ex-work-buf (get-buffer-create viper-ex-work-buf-name))
    (set-buffer viper-ex-work-buf)
Michael Kifer's avatar
Michael Kifer committed
724 725
    (skip-chars-forward " \t")
    (if (looking-at "!")
Michael Kifer's avatar
Michael Kifer committed
726
	;; this is probably a variant command r!
Michael Kifer's avatar
Michael Kifer committed
727 728 729 730 731 732
	(progn
	  (setq ex-g-variant (not ex-g-variant)
		ex-g-flag (not ex-g-flag))
	  (forward-char 1)
	  (skip-chars-forward " \t")))
    (let ((c (following-char)))
Michael Kifer's avatar
Michael Kifer committed
733 734 735 736
      (cond ((string-match "[0-9A-Za-z]" (format "%c" c))
	     (error
	      "Global regexp must be inside matching non-alphanumeric chars"))
	    ((= c ??) (error "`?' is not an allowed pattern delimiter here")))
Michael Kifer's avatar
Michael Kifer committed
737 738 739 740 741
      (if (looking-at "[^\\\\\n]")
	  (progn
	    (forward-char 1)
	    (set-mark (point))
	    (let ((cont t))
Michael Kifer's avatar
Michael Kifer committed
742 743 744
	      ;; the use of eobp instead of eolp permits the use of newlines in
	      ;; pat2 in s/pat1/pat2/
	      (while (and (not (eobp)) cont)
Michael Kifer's avatar
Michael Kifer committed
745 746
		(if (not (re-search-forward (format "[^%c]*%c" c c) nil t))
		    (if (member ex-token '("global" "vglobal"))
Michael Kifer's avatar
Michael Kifer committed
747
			(error "Missing closing delimiter for global regexp")
Michael Kifer's avatar
Michael Kifer committed
748
		      (goto-char (point-max))))
Michael Kifer's avatar
Michael Kifer committed
749
		(if (not (viper-looking-back
Michael Kifer's avatar
Michael Kifer committed
750
			  (format "[^\\\\]\\(\\\\\\\\\\)*\\\\%c" c)))
Michael Kifer's avatar
Michael Kifer committed
751 752
		    (setq cont nil)
		  ;; we are at an escaped delimiter: unescape it and continue
753
		  (delete-char -2)
Michael Kifer's avatar
Michael Kifer committed
754 755 756 757 758 759 760 761
		  (insert c)
		  (if (eolp)
		      ;; if at eol, exit loop and go to next line
		      ;; later, delim will be inserted at the end
		      (progn
			(setq cont nil)
			(forward-char))))
		))
Michael Kifer's avatar
Michael Kifer committed
762 763 764
	    (setq ex-token
		  (if (= (mark t) (point)) ""
		    (buffer-substring (1- (point)) (mark t))))
Michael Kifer's avatar
Michael Kifer committed
765
	    (backward-char 1)
Michael Kifer's avatar
Michael Kifer committed
766
	    ;; if the user didn't insert the final pattern delimiter, we're
Michael Kifer's avatar
Michael Kifer committed
767
	    ;; at newline now.  In this case, insert the initial delimiter
Michael Kifer's avatar
Michael Kifer committed
768
	    ;; specified in variable c
Michael Kifer's avatar
Michael Kifer committed
769
	    (if (eolp)
Michael Kifer's avatar
Michael Kifer committed
770
		(progn
Michael Kifer's avatar
Michael Kifer committed
771 772
		  (insert c)
		  (backward-char 1)))
Michael Kifer's avatar
Michael Kifer committed
773
	    )
Michael Kifer's avatar
Michael Kifer committed
774 775
	(setq ex-token nil))
      c)))
Michael Kifer's avatar
Michael Kifer committed
776

Michael Kifer's avatar
Michael Kifer committed
777
;; Get an Ex option g or c
Michael Kifer's avatar
Michael Kifer committed
778
(defun viper-get-ex-opt-gc (c)
Michael Kifer's avatar
Michael Kifer committed
779
  (save-window-excursion
780
    (setq viper-ex-work-buf (get-buffer-create viper-ex-work-buf-name))
Michael Kifer's avatar
Michael Kifer committed
781
    (set-buffer viper-ex-work-buf)
Michael Kifer's avatar
Michael Kifer committed
782 783 784 785 786 787 788 789 790 791 792 793 794
    (if (looking-at (format "%c" c)) (forward-char 1))
    (skip-chars-forward " \t")
    (cond ((looking-at "g")
	   (setq ex-token "g")
	   (forward-char 1)
	   t)
	  ((looking-at "c")
	   (setq ex-token "c")
	   (forward-char 1)
	   t)
	  (t nil))))

;; Compute default addresses.  WHOLE-FLAG means use the whole buffer
Michael Kifer's avatar
Michael Kifer committed
795
(defun viper-default-ex-addresses (&optional whole-flag)
Michael Kifer's avatar
Michael Kifer committed
796 797 798
  (cond ((null ex-addresses)
	 (setq ex-addresses
	       (if whole-flag
Michael Kifer's avatar
Michael Kifer committed
799 800
		   (list (point-max) (point-min))
		 (list (point) (point)))))
Michael Kifer's avatar
Michael Kifer committed
801 802 803 804 805
	((null (cdr ex-addresses))
	 (setq ex-addresses
	       (cons (car ex-addresses) ex-addresses)))))

;; Get an ex-address as a marker and set ex-flag if a flag is found
Michael Kifer's avatar
Michael Kifer committed
806
(defun viper-get-ex-address ()
Michael Kifer's avatar
Michael Kifer committed
807 808
  (let ((address (point-marker))
	(cont t))
Michael Kifer's avatar
Michael Kifer committed
809 810 811
    (setq ex-token "")
    (setq ex-flag nil)
    (while cont
Michael Kifer's avatar
Michael Kifer committed
812
      (viper-get-ex-token)
Michael Kifer's avatar
Michael Kifer committed
813 814 815 816 817 818 819 820 821 822 823
      (cond ((eq ex-token-type 'command)
	     (if (member ex-token '("print" "list" "#"))
		 (progn
		   (setq ex-flag t
			 cont nil))
	       (error "Address expected in this Ex command")))
	    ((eq ex-token-type 'end-mark)
	     (setq cont nil))
	    ((eq ex-token-type 'whole)
	     (error "Trailing address expected"))
	    ((eq ex-token-type 'comma)
Michael Kifer's avatar
Michael Kifer committed
824 825
	     (error "`%s': %s" ex-token viper-SpuriousText))
	    (t (let ((ans (viper-get-ex-address-subr address (point-marker))))
Michael Kifer's avatar
Michael Kifer committed
826 827 828 829
		 (if ans (setq address ans))))))
    address))

;; Returns an address as a point
Michael Kifer's avatar
Michael Kifer committed
830
(defun viper-get-ex-address-subr (old-address dot)
Michael Kifer's avatar
Michael Kifer committed
831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851
  (let ((address nil))
    (if (null old-address) (setq old-address dot))
    (cond ((eq ex-token-type 'dot)
	   (setq address dot))
	  ((eq ex-token-type 'add-number)
	   (save-excursion
	     (goto-char old-address)
	     (forward-line (if (= old-address 0) (1- ex-token) ex-token))
	     (setq address (point-marker))))
	  ((eq ex-token-type 'sub-number)
	   (save-excursion
	     (goto-char old-address)
	     (forward-line (- ex-token))
	     (setq address (point-marker))))
	  ((eq ex-token-type 'abs-number)
	   (save-excursion
	     (goto-char (point-min))
	     (if (= ex-token 0) (setq address 0)
	       (forward-line (1- ex-token))
	       (setq address (point-marker)))))
	  ((eq ex-token-type 'end)
852 853 854
	   (save-excursion
	     (goto-char (1- (point-max)))
	     (setq address (point-marker))))
Michael Kifer's avatar
Michael Kifer committed
855 856 857 858 859 860 861 862 863 864 865 866 867 868
	  ((eq ex-token-type 'plus) t)  ; do nothing
	  ((eq ex-token-type 'minus) t) ; do nothing
	  ((eq ex-token-type 'search-forward)
	   (save-excursion
	     (ex-search-address t)
	     (setq address (point-marker))))
	  ((eq ex-token-type 'search-backward)
	   (save-excursion
	     (ex-search-address nil)
	     (setq address (point-marker))))
	  ((eq ex-token-type 'goto-mark)
	   (save-excursion
	     (if (null ex-token)
		 (exchange-point-and-mark)
869
	       (goto-char
870 871
		(viper-register-to-point
		 (viper-int-to-char (1+ (- ex-token ?a))) 'enforce-buffer)))
Michael Kifer's avatar
Michael Kifer committed
872 873 874 875 876
	     (setq address (point-marker)))))
    address))


;; Search pattern and set address
877
;; Doesn't wrap around. Should it?
Michael Kifer's avatar
Michael Kifer committed
878 879
(defun ex-search-address (forward)
  (if (string= ex-token "")
Michael Kifer's avatar
Michael Kifer committed
880 881 882 883
      (if (null viper-s-string)
	  (error viper-NoPrevSearch)
	(setq ex-token viper-s-string))
    (setq viper-s-string ex-token))
Michael Kifer's avatar
Michael Kifer committed
884 885 886 887 888 889 890 891
  (if forward
      (progn
	(forward-line 1)
	(re-search-forward ex-token))
    (forward-line -1)
    (re-search-backward ex-token)))

;; Get a buffer name and set `ex-count' and `ex-flag' if found
Michael Kifer's avatar
Michael Kifer committed
892
(defun viper-get-ex-buffer ()
Michael Kifer's avatar
Michael Kifer committed
893 894 895 896
  (setq ex-buffer nil)
  (setq ex-count nil)
  (setq ex-flag nil)
  (save-window-excursion
897
    (setq viper-ex-work-buf (get-buffer-create viper-ex-work-buf-name))
Michael Kifer's avatar
Michael Kifer committed
898
    (set-buffer viper-ex-work-buf)
Michael Kifer's avatar
Michael Kifer committed
899 900 901 902 903 904 905 906 907 908
    (skip-chars-forward " \t")
    (if (looking-at "[a-zA-Z]")
	(progn
	  (setq ex-buffer (following-char))
	  (forward-char 1)
	  (skip-chars-forward " \t")))
    (if (looking-at "[0-9]")
	(progn
	  (set-mark (point))
	  (re-search-forward "[0-9][0-9]*")
909
	  (setq ex-count (string-to-number (buffer-substring (point) (mark t))))
Michael Kifer's avatar
Michael Kifer committed
910 911 912 913 914 915
	  (skip-chars-forward " \t")))
    (if (looking-at "[pl#]")
	(progn
	  (setq ex-flag t)
	  (forward-char 1)))
    (if (not (looking-at "[\n|]"))
Michael Kifer's avatar
Michael Kifer committed
916
	(error "`%s': %s" ex-token viper-SpuriousText))))
Michael Kifer's avatar
Michael Kifer committed
917

Michael Kifer's avatar
Michael Kifer committed
918
(defun viper-get-ex-count ()
Michael Kifer's avatar
Michael Kifer committed
919 920 921 922
  (setq ex-variant nil
	ex-count nil
	ex-flag nil)
  (save-window-excursion
923
    (setq viper-ex-work-buf (get-buffer-create viper-ex-work-buf-name))
Michael Kifer's avatar
Michael Kifer committed
924
    (set-buffer viper-ex-work-buf)
Michael Kifer's avatar
Michael Kifer committed
925 926 927 928 929 930 931 932 933 934
    (skip-chars-forward " \t")
    (if (looking-at "!")
	(progn
	  (setq ex-variant t)
	  (forward-char 1)))
    (skip-chars-forward " \t")
    (if (looking-at "[0-9]")
	(progn
	  (set-mark (point))
	  (re-search-forward "[0-9][0-9]*")
935
	  (setq ex-count (string-to-number (buffer-substring (point) (mark t))))
Michael Kifer's avatar
Michael Kifer committed
936 937 938 939 940 941 942
	  (skip-chars-forward " \t")))
    (if (looking-at "[pl#]")
	(progn
	  (setq ex-flag t)
	  (forward-char 1)))
    (if (not (looking-at "[\n|]"))
	(error "`%s': %s"
Michael Kifer's avatar
Michael Kifer committed
943 944
	       (buffer-substring
		(point-min) (1- (point-max))) viper-BadExCommand))))
Michael Kifer's avatar
Michael Kifer committed
945 946 947 948

;; Expand \% and \# in ex command
(defun ex-expand-filsyms (cmd buf)
  (let (cf pf ret)
949
    (with-current-buffer buf
Michael Kifer's avatar
Michael Kifer committed
950 951 952 953 954 955
      (setq cf buffer-file-name)
      (setq pf (ex-next nil t))) ; this finds alternative file name
    (if (and (null cf) (string-match "[^\\]%\\|\\`%" cmd))
	(error "No current file to substitute for `%%'"))
    (if (and (null pf) (string-match "[^\\]#\\|\\`#" cmd))
	(error "No alternate file to substitute for `#'"))
956
    (with-current-buffer (get-buffer-create viper-ex-tmp-buf-name)
Michael Kifer's avatar
Michael Kifer committed
957 958 959 960
      (erase-buffer)
      (insert cmd)
      (goto-char (point-min))
      (while (re-search-forward "%\\|#" nil t)
961
	(let ((data (match-data))
Michael Kifer's avatar
Michael Kifer committed
962
	      (char (buffer-substring (match-beginning 0) (match-end 0))))
Michael Kifer's avatar
Michael Kifer committed
963
	  (if (viper-looking-back (concat "\\\\" char))
Michael Kifer's avatar
Michael Kifer committed
964
	      (replace-match char)
Michael Kifer's avatar
Michael Kifer committed
965
	    (store-match-data data)
Michael Kifer's avatar
Michael Kifer committed
966 967 968 969 970 971 972 973
	    (if (string= char "%")
		(replace-match cf)
	      (replace-match pf)))))
      (end-of-line)
      (setq ret (buffer-substring (point-min) (point)))
      (message "%s" ret))
    ret))

Michael Kifer's avatar
Michael Kifer committed
974 975
;; Get a file name and set `ex-variant', `ex-append' and `ex-offset' if found
;; If it is r!, then get the command name and whatever args
Michael Kifer's avatar
Michael Kifer committed
976
(defun viper-get-ex-file ()
Michael Kifer's avatar
Michael Kifer committed
977 978 979 980 981
  (let (prompt)
    (setq ex-file nil
	  ex-variant nil
	  ex-append nil
	  ex-offset nil
Michael Kifer's avatar
Michael Kifer committed
982 983
	  ex-cmdfile nil
	  ex-cmdfile-args "")
Michael Kifer's avatar
Michael Kifer committed
984
    (save-excursion
985 986
      (with-current-buffer (setq viper-ex-work-buf
                                 (get-buffer-create viper-ex-work-buf-name))
Michael Kifer's avatar
Michael Kifer committed
987 988
	(skip-chars-forward " \t")
	(if (looking-at "!")
Michael Kifer's avatar
Michael Kifer committed
989
	    (if (and (not (viper-looking-back "[ \t]"))
Michael Kifer's avatar
Michael Kifer committed
990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021
		     ;; read doesn't have a corresponding :r! form, so ! is
		     ;; immediately interpreted as a shell command.
		     (not (string= ex-token "read")))
		(progn
		  (setq ex-variant t)
		  (forward-char 1)
		  (skip-chars-forward " \t"))
	      (setq ex-cmdfile t)
	      (forward-char 1)
	      (skip-chars-forward " \t")))
	(if (looking-at ">>")
	    (progn
	      (setq ex-append t
		    ex-variant t)
	      (forward-char 2)
	      (skip-chars-forward " \t")))
	(if (looking-at "+")
	    (progn
	      (forward-char 1)
	      (set-mark (point))
	      (re-search-forward "[ \t\n]")
	      (backward-char 1)
	      (setq ex-offset (buffer-substring (point) (mark t)))
	      (forward-char 1)
	      (skip-chars-forward " \t")))
	;; this takes care of :r, :w, etc., when they get file names
	;; from the history list
	(if (member ex-token '("read" "write" "edit" "visual" "next"))
	    (progn
	      (setq ex-file (buffer-substring (point)  (1- (point-max))))
	      (setq ex-file
		    ;; For :e, match multiple non-white strings separated
Michael Kifer's avatar
Michael Kifer committed
1022
		    ;; by white.  For others, find the first non-white string
Michael Kifer's avatar
Michael Kifer committed
1023 1024 1025 1026 1027 1028 1029 1030
		    (if (string-match
			 (if (string= ex-token "edit")
			     "[^ \t\n]+\\([ \t]+[^ \t\n]+\\)*"
			   "[^ \t\n]+")
			 ex-file)
			(progn
			  ;; if file name comes from history, don't leave
			  ;; minibuffer when the user types space
Michael Kifer's avatar
Michael Kifer committed
1031
			  (setq viper-incomplete-ex-cmd nil)
Michael Kifer's avatar
Michael Kifer committed
1032 1033
			  (setq ex-cmdfile-args
				(substring ex-file (match-end 0) nil))
Michael Kifer's avatar
Michael Kifer committed
1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045
			  ;; this must be the last clause in this progn
			  (substring ex-file (match-beginning 0) (match-end 0))
			  )
		      ""))
	      ;; this leaves only the command name in the work area
	      ;; file names are gone
	      (delete-region (point) (1- (point-max)))
	      ))
	(goto-char (point-max))
	(skip-chars-backward " \t\n")
	(setq prompt (buffer-substring (point-min) (point)))
	))
1046

Michael Kifer's avatar
Michael Kifer committed
1047
    (setq viper-last-ex-prompt prompt)
1048

Michael Kifer's avatar
Michael Kifer committed
1049
    ;; If we just finished reading command, redisplay prompt
Michael Kifer's avatar
Michael Kifer committed
1050 1051
    (if viper-incomplete-ex-cmd
	(setq ex-file (viper-ex-read-file-name (format ":%s "