viper-ex.el 74.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

Paul Eggert's avatar
Paul Eggert committed
3
;; Copyright (C) 1994-1998, 2000-2017 Free Software Foundation, Inc.
Karl Heuer's avatar
Karl Heuer committed
4

5
;; Author: Michael Kifer <kifer@cs.stonybrook.edu>
6
;; Package: viper
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)
Glenn Morris's avatar
Glenn Morris committed
42
(require 'viper-keym)
Michael Kifer's avatar
Michael Kifer committed
43 44 45 46
;; end pacifier

(require 'viper-util)

Michael Kifer's avatar
Michael Kifer committed
47
(defgroup viper-ex nil
48
  "Viper support for Ex commands."
Michael Kifer's avatar
Michael Kifer committed
49 50 51 52
  :prefix "ex-"
  :group 'viper)


Michael Kifer's avatar
Michael Kifer committed
53

Karl Heuer's avatar
Karl Heuer committed
54
;;; Variables
Michael Kifer's avatar
Michael Kifer committed
55

Michael Kifer's avatar
Michael Kifer committed
56
(defconst viper-ex-work-buf-name " *ex-working-space*")
57
(defvar viper-ex-work-buf (get-buffer-create viper-ex-work-buf-name))
Michael Kifer's avatar
Michael Kifer committed
58
(defconst viper-ex-tmp-buf-name " *ex-tmp*")
59
(defconst viper-ex-print-buf-name " *ex-print*")
60
(defvar viper-ex-print-buf (get-buffer-create viper-ex-print-buf-name))
Michael Kifer's avatar
Michael Kifer committed
61 62


63 64 65 66 67 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
;;; 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))
108
	("file"			(ex-set-visited-file-name))
109 110 111 112 113 114 115 116
	("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")
117
	("make"     	    	(ex-compile))
118 119 120 121 122 123 124
	; 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))
125
	("print"		(ex-print))
126 127 128 129 130 131 132 133 134 135 136 137 138 139
	("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))
140
	("stop"			(suspend-emacs))
141 142 143 144 145 146 147 148 149 150 151 152 153 154
	("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)
155
	("visual"		(ex-edit))
156 157 158 159 160 161 162 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
	("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)
197 198
  (let ((oneletter (ex-cmd-is-one-letter (assoc (substring key 0 1) list))))
    (if oneletter
199
	(list key
200
	      (append (cadr oneletter)
201
		      (if (< 1 (length key)) (list (substring key 1))))
202
	      (car (cdr (cdr oneletter))) ))
203 204 205 206 207 208 209 210
	))


;; 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))
211
	result)
212 213 214 215 216 217 218 219 220 221 222 223 224 225
    (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
226 227

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

242

Michael Kifer's avatar
Michael Kifer committed
243

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

247
;; Type of token.
Michael Kifer's avatar
Michael Kifer committed
248 249 250 251 252 253
;; 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
254
;; This flag is supposed to be set only by `#', `print', and `list',
Paul Eggert's avatar
Paul Eggert committed
255
;; none of which is implemented.  So, it and the pieces of the code it
Michael Kifer's avatar
Michael Kifer committed
256
;; controls are dead weight.  We keep it just in case this might be
Michael Kifer's avatar
Michael Kifer committed
257
;; needed in the future.
Michael Kifer's avatar
Michael Kifer committed
258 259 260 261 262 263 264 265 266
(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
267
;; Flag indicating that :global Ex command is being executed.
Michael Kifer's avatar
Michael Kifer committed
268
(defvar ex-g-flag nil)
Michael Kifer's avatar
Michael Kifer committed
269
;; Flag indicating that :vglobal Ex command is being executed.
Michael Kifer's avatar
Michael Kifer committed
270
(defvar ex-g-variant nil)
271 272
;; Marks to operate on during a :global Ex command.
(defvar ex-g-marks nil)
Michael Kifer's avatar
Michael Kifer committed
273 274 275 276 277 278 279 280 281 282 283

;; 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
284
(defcustom ex-unix-type-shell
Michael Kifer's avatar
Michael Kifer committed
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
  (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
301
  "Is the user using a unix-type shell under a non-OS?"
302
  :type 'boolean
Michael Kifer's avatar
Michael Kifer committed
303
  :group 'viper-ex)
Michael Kifer's avatar
Michael Kifer committed
304

Michael Kifer's avatar
Michael Kifer committed
305
(defcustom ex-unix-type-shell-options
Michael Kifer's avatar
Michael Kifer committed
306 307 308 309 310 311 312
  (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
	      )))
313
  "Options to pass to the Unix-style shell.
Michael Kifer's avatar
Michael Kifer committed
314
Don't put `-c' here, as it is added automatically."
315
  :type '(choice (const nil) string)
Michael Kifer's avatar
Michael Kifer committed
316
  :group 'viper-ex)
Michael Kifer's avatar
Michael Kifer committed
317

318
(defcustom ex-compile-command "make"
319
  "The command to run when the user types :make."
320 321 322
  :type 'string
  :group 'viper-ex)

Michael Kifer's avatar
Michael Kifer committed
323 324 325 326 327 328
(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.
329
The default tries to set this variable to work with Unix or MS Windows.
Michael Kifer's avatar
Michael Kifer committed
330

331
However, if it doesn't work right for some types of Unix shells or some OS,
Michael Kifer's avatar
Michael Kifer committed
332 333 334 335 336
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
337

Michael Kifer's avatar
Michael Kifer committed
338 339
;; Remembers the previous Ex tag.
(defvar ex-tag nil)
Michael Kifer's avatar
Michael Kifer committed
340

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

Michael Kifer's avatar
Michael Kifer committed
344 345
;; 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
346

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

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

Michael Kifer's avatar
Michael Kifer committed
353 354 355
;; 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
356
(defvar ex-cmdfile-args "")
357

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

Michael Kifer's avatar
Michael Kifer committed
362
(defcustom ex-cycle-other-window t
363
  "If t, :n and :b cycles through files and buffers in other window.
Michael Kifer's avatar
Michael Kifer committed
364
Then :N and :B cycles in the current window.  If nil, this behavior is
Michael Kifer's avatar
Michael Kifer committed
365 366 367
reversed."
  :type 'boolean
  :group 'viper-ex)
Michael Kifer's avatar
Michael Kifer committed
368

Michael Kifer's avatar
Michael Kifer committed
369
(defcustom ex-cycle-through-non-files nil
370
  "Cycle through *scratch* and other buffers that don't visit any file."
Michael Kifer's avatar
Michael Kifer committed
371 372
  :type 'boolean
  :group 'viper-ex)
Michael Kifer's avatar
Michael Kifer committed
373

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

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

Michael Kifer's avatar
Michael Kifer committed
380
;; Remembers the last ex-command prompt.
Michael Kifer's avatar
Michael Kifer committed
381
(defvar viper-last-ex-prompt "")
Michael Kifer's avatar
Michael Kifer committed
382 383


Michael Kifer's avatar
Michael Kifer committed
384
;; Get a complete ex command
Michael Kifer's avatar
Michael Kifer committed
385
(defun viper-get-ex-com-subr ()
386
  (let (cmd case-fold-search)
Michael Kifer's avatar
Michael Kifer committed
387 388 389 390
    (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)))
391 392 393 394
    (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
395
    ))
Michael Kifer's avatar
Michael Kifer committed
396

Michael Kifer's avatar
Michael Kifer committed
397
;; Get an ex-token which is either an address or a command.
398
;; A token has a type, (command, address, end-mark), and a value
Michael Kifer's avatar
Michael Kifer committed
399
(defun viper-get-ex-token ()
Michael Kifer's avatar
Michael Kifer committed
400
  (save-window-excursion
401
    (setq viper-ex-work-buf (get-buffer-create viper-ex-work-buf-name))
Michael Kifer's avatar
Michael Kifer committed
402
    (set-buffer viper-ex-work-buf)
Michael Kifer's avatar
Michael Kifer committed
403
    (skip-chars-forward " \t|")
Michael Kifer's avatar
Michael Kifer committed
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
    (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
421
		   (string-to-number (buffer-substring (point) (mark t)))))
Michael Kifer's avatar
Michael Kifer committed
422 423 424 425 426 427 428 429 430 431 432
	    ((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
433
		  (setq ex-token-type 'plus))
Michael Kifer's avatar
Michael Kifer committed
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
		   ((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\\)")
457 458
		 (if (not (looking-back "[^\\\\]\\(\\\\\\\\\\)*\\\\/"
                                        (line-beginning-position 0)))
Michael Kifer's avatar
Michael Kifer committed
459 460 461 462 463 464 465 466 467 468 469 470
		     (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\\)")
471 472
		 (if (not (looking-back "[^\\\\]\\(\\\\\\\\\\)*\\\\\\?"
                                        (line-beginning-position 0)))
Michael Kifer's avatar
Michael Kifer committed
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
		     (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)))
493
		   (t (error "%s" "Marks are ' and a-z")))
Michael Kifer's avatar
Michael Kifer committed
494 495 496 497 498 499
	     (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
500

Michael Kifer's avatar
Michael Kifer committed
501 502
;; 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
503 504
(defun ex-cmd-read-exit ()
  (interactive)
Michael Kifer's avatar
Michael Kifer committed
505
  (setq viper-incomplete-ex-cmd t)
Michael Kifer's avatar
Michael Kifer committed
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
  (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
521
		     "\\|" "[?/].*"
Michael Kifer's avatar
Michael Kifer committed
522 523 524 525 526 527 528 529 530 531 532 533
		     "\\|" "[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
534 535 536
		     ;; 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
537 538 539 540 541
		     "\\|" "['`][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
542 543 544
		     "\\|" "![ \t]*[a-zA-Z].*"
		     "\\)"
		     "!*")))
545

Michael Kifer's avatar
Michael Kifer committed
546
    (save-window-excursion ;; put cursor at the end of the Ex working buffer
547
      (setq viper-ex-work-buf (get-buffer-create viper-ex-work-buf-name))
Michael Kifer's avatar
Michael Kifer committed
548
      (set-buffer viper-ex-work-buf)
Michael Kifer's avatar
Michael Kifer committed
549
      (goto-char (point-max)))
Glenn Morris's avatar
Glenn Morris committed
550 551 552
    (cond ((looking-back quit-regex1) (exit-minibuffer))
	  ((looking-back stay-regex)  (insert " "))
	  ((looking-back quit-regex2) (exit-minibuffer))
Michael Kifer's avatar
Michael Kifer committed
553
	  (t (insert " ")))))
554

Glenn Morris's avatar
Glenn Morris committed
555 556
(declare-function viper-tmp-insert-at-eob "viper-cmd" (msg))

Michael Kifer's avatar
Michael Kifer committed
557 558 559 560
;; complete Ex command
(defun ex-cmd-complete ()
  (interactive)
  (let (save-pos dist compl-list string-to-complete completion-result)
561

Michael Kifer's avatar
Michael Kifer committed
562 563 564
    (save-excursion
      (setq dist (skip-chars-backward "[a-zA-Z!=>&~]")
	    save-pos (point)))
565

Michael Kifer's avatar
Michael Kifer committed
566
    (if (or (= dist 0)
567 568
	    (looking-back "\\([ \t]*['`][ \t]*[a-z]*\\)"
                          (line-beginning-position))
Glenn Morris's avatar
Glenn Morris committed
569
	    (looking-back
570 571
	     "^[ \t]*[a-zA-Z!=>&~][ \t]*[/?]*[ \t]+[a-zA-Z!=>&~]+"
             (line-beginning-position)))
Michael Kifer's avatar
Michael Kifer committed
572 573
	;; 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
574
	;; Note: we didn't do parsing, so there can be surprises.
575 576 577 578
	(if (or (looking-back "[a-zA-Z!=>&~][ \t]*[/?]*[ \t]*"
                              (line-beginning-position))
		(looking-back "\\([ \t]*['`][ \t]*[a-z]*\\)"
                              (line-beginning-position))
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

Glenn Morris's avatar
Glenn Morris committed
608 609 610 611 612
(declare-function viper-enlarge-region "viper-cmd" (beg end))
(declare-function viper-read-string-with-history "viper-cmd"
		  (prompt &optional viper-initial history-var
			  default keymap init-message))

613
;; Read Ex commands
Michael Kifer's avatar
Michael Kifer committed
614 615 616
;; 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
617
;; command.
Michael Kifer's avatar
Michael Kifer committed
618 619
(defun viper-ex (arg &optional string)
  (interactive "P")
Michael Kifer's avatar
Michael Kifer committed
620 621 622 623 624 625 626
  (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
627 628 629
	 reg-beg-line reg-end-line
	 reg-beg reg-end
	 initial-str
Michael Kifer's avatar
Michael Kifer committed
630
	 prev-token-type com-str)
Michael Kifer's avatar
Michael Kifer committed
631
    (viper-add-keymap viper-ex-cmd-map map)
Michael Kifer's avatar
Michael Kifer committed
632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647

    (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)))
648 649

    (setq com-str
650 651 652 653 654 655 656 657 658 659 660 661 662
	  (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
663 664
    (save-window-excursion
      ;; just a precaution
665
      (setq viper-ex-work-buf (get-buffer-create viper-ex-work-buf-name))
Michael Kifer's avatar
Michael Kifer committed
666
      (set-buffer viper-ex-work-buf)
Michael Kifer's avatar
Michael Kifer committed
667 668 669 670 671 672
      (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
673
      (viper-get-ex-token)
Michael Kifer's avatar
Michael Kifer committed
674 675
      (cond ((memq ex-token-type '(command end-mark))
	     (if address (setq ex-addresses (cons address ex-addresses)))
676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697
	     (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
698
	    ((eq ex-token-type 'non-command)
Michael Kifer's avatar
Michael Kifer committed
699
	     (error "`%s': %s" ex-token viper-BadExCommand))
Michael Kifer's avatar
Michael Kifer committed
700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716
	    ((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
717
	    (t (let ((ans (viper-get-ex-address-subr address dot)))
Michael Kifer's avatar
Michael Kifer committed
718 719
		 (if ans (setq address ans)))))
      (setq prev-token-type ex-token-type))))
720

Michael Kifer's avatar
Michael Kifer committed
721

Michael Kifer's avatar
Michael Kifer committed
722
;; Get a regular expression and set `ex-variant', if found
723 724
;; 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
725
(defun viper-get-ex-pat ()
Michael Kifer's avatar
Michael Kifer committed
726
  (save-window-excursion
Michael Kifer's avatar
Michael Kifer committed
727 728
    (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
729 730
    (skip-chars-forward " \t")
    (if (looking-at "!")
Michael Kifer's avatar
Michael Kifer committed
731
	;; this is probably a variant command r!
Michael Kifer's avatar
Michael Kifer committed
732 733 734 735 736 737
	(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
738 739 740 741
      (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
742 743 744 745 746
      (if (looking-at "[^\\\\\n]")
	  (progn
	    (forward-char 1)
	    (set-mark (point))
	    (let ((cont t))
Michael Kifer's avatar
Michael Kifer committed
747 748 749
	      ;; 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
750 751
		(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
752
			(error "Missing closing delimiter for global regexp")
Michael Kifer's avatar
Michael Kifer committed
753
		      (goto-char (point-max))))
Glenn Morris's avatar
Glenn Morris committed
754
		(if (not (looking-back
755 756
			  (format "[^\\\\]\\(\\\\\\\\\\)*\\\\%c" c)
                          (line-beginning-position 0)))
Michael Kifer's avatar
Michael Kifer committed
757 758
		    (setq cont nil)
		  ;; we are at an escaped delimiter: unescape it and continue
759
		  (delete-char -2)
Michael Kifer's avatar
Michael Kifer committed
760 761 762 763 764 765 766 767
		  (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
768 769 770
	    (setq ex-token
		  (if (= (mark t) (point)) ""
		    (buffer-substring (1- (point)) (mark t))))
Michael Kifer's avatar
Michael Kifer committed
771
	    (backward-char 1)
Michael Kifer's avatar
Michael Kifer committed
772
	    ;; if the user didn't insert the final pattern delimiter, we're
Michael Kifer's avatar
Michael Kifer committed
773
	    ;; at newline now.  In this case, insert the initial delimiter
Michael Kifer's avatar
Michael Kifer committed
774
	    ;; specified in variable c
Michael Kifer's avatar
Michael Kifer committed
775
	    (if (eolp)
Michael Kifer's avatar
Michael Kifer committed
776
		(progn
Michael Kifer's avatar
Michael Kifer committed
777 778
		  (insert c)
		  (backward-char 1)))
Michael Kifer's avatar
Michael Kifer committed
779
	    )
Michael Kifer's avatar
Michael Kifer committed
780 781
	(setq ex-token nil))
      c)))
Michael Kifer's avatar
Michael Kifer committed
782

Michael Kifer's avatar
Michael Kifer committed
783
;; Get an Ex option g or c
Michael Kifer's avatar
Michael Kifer committed
784
(defun viper-get-ex-opt-gc (c)
Michael Kifer's avatar
Michael Kifer committed
785
  (save-window-excursion
786
    (setq viper-ex-work-buf (get-buffer-create viper-ex-work-buf-name))
Michael Kifer's avatar
Michael Kifer committed
787
    (set-buffer viper-ex-work-buf)
Michael Kifer's avatar
Michael Kifer committed
788 789 790 791 792 793 794 795 796 797 798 799 800
    (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
801
(defun viper-default-ex-addresses (&optional whole-flag)
Michael Kifer's avatar
Michael Kifer committed
802 803 804
  (cond ((null ex-addresses)
	 (setq ex-addresses
	       (if whole-flag
Michael Kifer's avatar
Michael Kifer committed
805 806
		   (list (point-max) (point-min))
		 (list (point) (point)))))
Michael Kifer's avatar
Michael Kifer committed
807 808 809 810 811
	((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
812
(defun viper-get-ex-address ()
Michael Kifer's avatar
Michael Kifer committed
813 814
  (let ((address (point-marker))
	(cont t))
Michael Kifer's avatar
Michael Kifer committed
815 816 817
    (setq ex-token "")
    (setq ex-flag nil)
    (while cont
Michael Kifer's avatar
Michael Kifer committed
818
      (viper-get-ex-token)
Michael Kifer's avatar
Michael Kifer committed
819 820 821 822 823 824 825 826 827 828 829
      (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
830 831
	     (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
832 833 834
		 (if ans (setq address ans))))))
    address))

Glenn Morris's avatar
Glenn Morris committed
835 836 837
(declare-function viper-register-to-point "viper-cmd"
		  (char &optional enforce-buffer))

Michael Kifer's avatar
Michael Kifer committed
838
;; Returns an address as a point
Michael Kifer's avatar
Michael Kifer committed
839
(defun viper-get-ex-address-subr (old-address dot)
Michael Kifer's avatar
Michael Kifer committed
840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860
  (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)
861 862 863
	   (save-excursion
	     (goto-char (1- (point-max)))
	     (setq address (point-marker))))
Michael Kifer's avatar
Michael Kifer committed
864 865 866 867 868 869 870 871 872 873 874 875 876 877
	  ((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)
878
	       (goto-char
879 880
		(viper-register-to-point
		 (viper-int-to-char (1+ (- ex-token ?a))) 'enforce-buffer)))
Michael Kifer's avatar
Michael Kifer committed
881 882 883 884 885
	     (setq address (point-marker)))))
    address))


;; Search pattern and set address
886
;; Doesn't wrap around. Should it?
Michael Kifer's avatar
Michael Kifer committed
887 888
(defun ex-search-address (forward)
  (if (string= ex-token "")
Michael Kifer's avatar
Michael Kifer committed
889 890 891 892
      (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
893 894 895 896 897 898 899 900
  (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
901
(defun viper-get-ex-buffer ()
Michael Kifer's avatar
Michael Kifer committed
902 903 904 905
  (setq ex-buffer nil)
  (setq ex-count nil)
  (setq ex-flag nil)
  (save-window-excursion
906
    (setq viper-ex-work-buf (get-buffer-create viper-ex-work-buf-name))
Michael Kifer's avatar
Michael Kifer committed
907
    (set-buffer viper-ex-work-buf)
Michael Kifer's avatar
Michael Kifer committed
908 909 910 911 912 913 914 915 916 917
    (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]*")
918
	  (setq ex-count (string-to-number (buffer-substring (point) (mark t))))
Michael Kifer's avatar
Michael Kifer committed
919 920 921 922 923 924
	  (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
925
	(error "`%s': %s" ex-token viper-SpuriousText))))
Michael Kifer's avatar
Michael Kifer committed
926

Michael Kifer's avatar
Michael Kifer committed
927
(defun viper-get-ex-count ()
Michael Kifer's avatar
Michael Kifer committed
928 929 930 931
  (setq ex-variant nil
	ex-count nil
	ex-flag nil)
  (save-window-excursion
932
    (setq viper-ex-work-buf (get-buffer-create viper-ex-work-buf-name))
Michael Kifer's avatar
Michael Kifer committed
933
    (set-buffer viper-ex-work-buf)
Michael Kifer's avatar
Michael Kifer committed
934 935 936 937 938 939 940 941 942 943
    (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]*")
944
	  (setq ex-count (string-to-number (buffer-substring (point) (mark t))))
Michael Kifer's avatar
Michael Kifer committed
945 946 947 948 949 950 951
	  (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
952 953
	       (buffer-substring
		(point-min) (1- (point-max))) viper-BadExCommand))))
Michael Kifer's avatar
Michael Kifer committed
954 955 956 957

;; Expand \% and \# in ex command
(defun ex-expand-filsyms (cmd buf)
  (let (cf pf ret)
958
    (with-current-buffer buf
Michael Kifer's avatar
Michael Kifer committed
959 960 961 962 963 964
      (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 `#'"))
965
    (with-current-buffer (get-buffer-create viper-ex-tmp-buf-name)
Michael Kifer's avatar
Michael Kifer committed
966 967 968 969
      (erase-buffer)
      (insert cmd)
      (goto-char (point-min))
      (while (re-search-forward "%\\|#" nil t)
970
	(let ((data (match-data))
Michael Kifer's avatar
Michael Kifer committed
971
	      (char (buffer-substring (match-beginning 0) (match-end 0))))
972
	  (if (looking-back "\\\\." (- (point) 2))
Michael Kifer's avatar
Michael Kifer committed
973
	      (replace-match char)
Michael Kifer's avatar
Michael Kifer committed
974
	    (store-match-data data)
Michael Kifer's avatar
Michael Kifer committed
975 976 977 978 979 980 981 982
	    (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
983 984
;; 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
985
(defun viper-get-ex-file ()
Michael Kifer's avatar
Michael Kifer committed
986 987 988 989 990
  (let (prompt)
    (setq ex-file nil
	  ex-variant nil
	  ex-append nil
	  ex-offset nil
Michael Kifer's avatar
Michael Kifer committed
991 992
	  ex-cmdfile nil
	  ex-cmdfile-args "")
Michael Kifer's avatar
Michael Kifer committed
993
    (save-excursion
994 995
      (with-current-buffer (setq viper-ex-work-buf
                                 (get-buffer-create viper-ex-work-buf-name))
Michael Kifer's avatar
Michael Kifer committed
996 997
	(skip-chars-forward " \t")
	(if (looking-at "!")
998
	    (if (and (not (looking-back "[ \t]" (1- (point))))
Michael Kifer's avatar
Michael Kifer committed
999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030
		     ;; 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
1031
		    ;; by white.  For others, find the first non-white string
Michael Kifer's avatar
Michael Kifer committed
1032 1033 1034 1035 1036 1037 1038 1039
		    (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
1040
			  (setq viper-incomplete-ex-cmd nil)
Michael Kifer's avatar
Michael Kifer committed
1041 1042
			  (setq ex-cmdfile-args
				(substring ex-file (match-end 0) nil))
Michael Kifer's avatar
Michael Kifer committed
1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054
			  ;; 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)))
	))
1055

Michael Kifer's avatar
Michael Kifer committed
1056
    (setq viper-last-ex-prompt prompt)
1057

Michael Kifer's avatar
Michael Kifer committed
1058
    ;; If we just finished reading command, redisplay prompt
Michael Kifer's avatar
Michael Kifer committed
1059 1060
    (if viper-incomplete-ex-cmd
	(setq ex-file (viper-ex-read-file-name (format ":%s " prompt)))
Michael Kifer's avatar
Michael Kifer committed
1061 1062 1063
      ;; file was typed in-line
      (setq ex-file (or ex-file "")))
    ))
Michael Kifer's avatar
Michael Kifer committed
1064 1065


Michael Kifer's avatar
Michael Kifer committed
1066
;; Completes file name or exits minibuffer.  If Ex command accepts multiple
Michael Kifer's avatar
Michael Kifer committed
1067
;; file names, arranges to re-enter the minibuffer.
Michael Kifer's avatar
Michael Kifer committed
1068
(defun viper-complete-filename-or-exit ()
Michael Kifer's avatar
Michael Kifer committed
1069
  (interactive)
1070 1071
  (setq viper-keep-reading-filename t)
  ;; don't exit if directory---ex-commands don't
Michael Kifer's avatar
Michael Kifer committed
1072 1073 1074
  (cond ((ex-cmd-accepts-multiple-files-p ex-token) (exit-minibuffer))
	;; apparently the argument to an Ex command is
	;; supposed to be a shell command
1075
	((looking-back "^[ \t]*!.*" (line-beginning-position))
Michael Kifer's avatar
Michael Kifer committed
1076 1077 1078 1079 1080
	 (setq ex-cmdfile t)
	 (insert " "))
	(t
	 (setq ex-cmdfile nil)
	 (minibuffer-complete-word))))
Michael Kifer's avatar
Michael Kifer committed
1081

Michael Kifer's avatar
Michael Kifer committed
1082
(defun viper-handle-! ()
Michael Kifer's avatar
Michael Kifer committed
1083 1084
  (interactive)
  (if (and (string=
Michael Kifer's avatar
Michael Kifer committed
1085
	    (buffer-string) (viper-abbreviate-file-name default-directory))
Michael Kifer's avatar
Michael Kifer committed
1086 1087 1088 1089 1090 1091 1092
	   (member ex-token '("read" "write")))
      (erase-buffer))
  (insert "!"))

(defun ex-cmd-accepts-multiple-files-p (token)
  (member token '("edit" "next" "Next")))

Michael Kifer's avatar
Michael Kifer committed
1093
;; Read file name from the minibuffer in an ex command.
Michael Kifer's avatar
Michael Kifer committed
1094 1095
;; If user doesn't enter anything, then "" is returned, i.e., the
;; prompt-directory is not returned.
Michael Kifer's avatar
Michael Kifer committed
1096
(defun viper-ex-read-file-name (prompt)
Michael Kifer's avatar
Michael Kifer committed
1097 1098 1099 1100
  (let* ((str "")
	 (minibuffer-local-completion-map
	  (copy-keymap minibuffer-local-completion-map))
	 beg end cont val)
1101

Michael Kifer's avatar
Michael Kifer committed
1102
    (viper-add-keymap ex-read-filename-map
1103
		    (if (featurep 'emacs)
Michael Kifer's avatar
Michael Kifer committed
1104
			minibuffer-local-completion-map
1105 1106
		      read-file-name-map))