;;; kmacro.el --- enhanced keyboard macros
-;; Copyright (C) 2002 Free Software Foundation, Inc.
+;; Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc.
;; Author: Kim F. Storm <storm@cua.dk>
;; Keywords: keyboard convenience
"Simplified keyboard macro user interface."
:group 'keyboard
:group 'convenience
+ :version "21.4"
:link '(emacs-commentary-link :tag "Commentary" "kmacro.el")
:link '(emacs-library-link :tag "Lisp File" "kmacro.el"))
(defvar kmacro-keymap
(let ((map (make-sparse-keymap)))
+ ;; Start, end, execute macros
(define-key map "s" 'kmacro-start-macro)
+ (define-key map "\C-s" 'kmacro-start-macro)
(define-key map "\C-k" 'kmacro-end-or-call-macro-repeat)
- (define-key map "\C-e" 'kmacro-edit-macro-repeat)
- (define-key map "\r" 'kmacro-edit-macro)
- (define-key map " " 'kmacro-step-edit-macro)
- (define-key map "l" 'kmacro-edit-lossage)
- (define-key map "\C-i" 'kmacro-insert-counter)
- (define-key map "\C-a" 'kmacro-add-counter)
- (define-key map "\C-v" 'kmacro-view-macro-repeat)
- (define-key map "\C-l" 'kmacro-call-ring-2nd-repeat)
- (define-key map "\C-r" 'kmacro-view-ring-2nd)
+ (define-key map "r" 'apply-macro-to-region-lines)
+ (define-key map "q" 'kbd-macro-query) ;; Like C-x q
+
+ ;; macro ring
(define-key map "\C-n" 'kmacro-cycle-ring-next)
(define-key map "\C-p" 'kmacro-cycle-ring-previous)
+ (define-key map "\C-v" 'kmacro-view-macro-repeat)
+ (define-key map "\C-d" 'kmacro-delete-ring-head)
+ (define-key map "\C-t" 'kmacro-swap-ring)
+ (define-key map "\C-l" 'kmacro-call-ring-2nd-repeat)
+
+ ;; macro counter
(define-key map "\C-f" 'kmacro-set-format)
(define-key map "\C-c" 'kmacro-set-counter)
- (define-key map "\C-t" 'kmacro-swap-ring)
- (define-key map "b" 'kmacro-bind-to-key)
- (define-key map "\C-d" 'kmacro-delete-ring-head)
- ;; Compatibility bindings
- (define-key map "q" 'kbd-macro-query)
- (define-key map "n" 'name-last-kbd-macro)
+ (define-key map "\C-i" 'kmacro-insert-counter)
+ (define-key map "\C-a" 'kmacro-add-counter)
+
+ ;; macro editing
+ (define-key map "\C-e" 'kmacro-edit-macro-repeat)
+ (define-key map "\r" 'kmacro-edit-macro)
(define-key map "e" 'edit-kbd-macro)
- (define-key map "r" 'apply-macro-to-region-lines)
+ (define-key map "l" 'kmacro-edit-lossage)
+ (define-key map " " 'kmacro-step-edit-macro)
+
+ ;; naming and binding
+ (define-key map "b" 'kmacro-bind-to-key)
+ (define-key map "n" 'kmacro-name-last-macro)
map)
"Keymap for keyboard macro commands.")
(defalias 'kmacro-keymap kmacro-keymap)
(global-set-key (vector kmacro-call-mouse-event) 'kmacro-end-call-mouse))
+;;; Called from keyboard-quit
+
+(defun kmacro-keyboard-quit ()
+ (or (not defining-kbd-macro)
+ (eq defining-kbd-macro 'append)
+ (kmacro-ring-empty-p)
+ (kmacro-pop-ring)))
+
;;; Keyboard macro counter
(defvar kmacro-counter 0
"*Current keyboard macro counter.")
+(defvar kmacro-default-counter-format "%d")
+
(defvar kmacro-counter-format "%d"
"*Current keyboard macro counter format.")
(defvar kmacro-counter-value-start kmacro-counter
"Macro counter at start of macro execution.")
-(defvar kmacro-last-counter 0 "Last counter inserted by key macro.")
+(defvar kmacro-last-counter 0
+ "Last counter inserted by key macro.")
+
+(defvar kmacro-initial-counter-value nil
+ "Initial counter value for the next keyboard macro to be defined.")
(defun kmacro-insert-counter (arg)
"Insert macro counter and increment with ARG or 1 if missing.
With \\[universal-argument], insert previous kmacro-counter (but do not modify counter)."
(interactive "P")
+ (if kmacro-initial-counter-value
+ (setq kmacro-counter kmacro-initial-counter-value
+ kmacro-initial-counter-value nil))
(if (and arg (listp arg))
(insert (format kmacro-counter-format kmacro-last-counter))
(insert (format kmacro-counter-format kmacro-counter))
(defun kmacro-set-format (format)
"Set macro counter FORMAT."
- (interactive "sMacro Counter Format (printf format): ")
+ (interactive "sMacro Counter Format: ")
(setq kmacro-counter-format
(if (equal format "") "%d" format))
;; redefine initial macro counter if we are not executing a macro.
(if (not (or defining-kbd-macro executing-kbd-macro))
- (setq kmacro-counter-format-start kmacro-counter-format)))
+ (setq kmacro-default-counter-format kmacro-counter-format)))
(defun kmacro-display-counter (&optional value)
(defun kmacro-set-counter (arg)
"Set kmacro-counter to ARG or prompt if missing.
-With \\[universal-argument], reset counter to its value prior to this iteration of the macro."
+With \\[universal-argument] prefix, reset counter to its value prior to this iteration of the macro."
(interactive "NMacro counter value: ")
- (setq kmacro-last-counter kmacro-counter
- kmacro-counter (if (and current-prefix-arg (listp current-prefix-arg))
- kmacro-counter-value-start
- arg))
- (unless executing-kbd-macro
- (kmacro-display-counter)))
+ (if (not (or defining-kbd-macro executing-kbd-macro))
+ (kmacro-display-counter (setq kmacro-initial-counter-value arg))
+ (setq kmacro-last-counter kmacro-counter
+ kmacro-counter (if (and current-prefix-arg (listp current-prefix-arg))
+ kmacro-counter-value-start
+ arg))
+ (unless executing-kbd-macro
+ (kmacro-display-counter))))
(defun kmacro-add-counter (arg)
"Add numeric prefix arg (prompt if missing) to macro counter.
With \\[universal-argument], restore previous counter value."
(interactive "NAdd to macro counter: ")
+ (if kmacro-initial-counter-value
+ (setq kmacro-counter kmacro-initial-counter-value
+ kmacro-initial-counter-value nil))
(let ((last kmacro-last-counter))
(setq kmacro-last-counter kmacro-counter
kmacro-counter (if (and current-prefix-arg (listp current-prefix-arg))
instead it is available in the variables `last-kbd-macro', `kmacro-counter',
and `kmacro-counter-format'.")
+;; Remember what we are currently looking at with kmacro-view-macro.
+
+(defvar kmacro-view-last-item nil)
+(defvar kmacro-view-item-no 0)
+
(defun kmacro-ring-head ()
"Return pseudo head element in macro ring."
(t nil)))
-(defun kmacro-display (macro &optional trunc descr empty )
- "Display a keyboard MACRO."
+(defun kmacro-display (macro &optional trunc descr empty)
+ "Display a keyboard MACRO.
+Optional arg TRUNC non-nil specifies to limit width of macro to 60 chars.
+Optional arg DESCR is descriptive text for macro; default is \"Macro:\".
+Optional arg EMPTY is message to print if no macros are defined."
(if macro
(let* ((x 60)
(m (format-kbd-macro macro))
(l (length m))
- (z (and nil trunc (> l x))))
- (message (format "%s: %s%s" (or descr "Macro")
- (if z (substring m 0 (1- x)) m) (if z "..." ""))))
+ (z (and trunc (> l x))))
+ (message "%s%s: %s%s" (or descr "Macro")
+ (if (= kmacro-counter 0) ""
+ (format " [%s]"
+ (format kmacro-counter-format-start kmacro-counter)))
+ (if z (substring m 0 (1- x)) m) (if z "..." "")))
(message (or empty "No keyboard macros defined"))))
keys)))
+(defun kmacro-exec-ring-item (item arg)
+ "Execute item ITEM from the macro ring."
+ ;; Use counter and format specific to the macro on the ring!
+ (let ((kmacro-counter (nth 1 item))
+ (kmacro-counter-format-start (nth 2 item)))
+ (execute-kbd-macro (car item) arg #'kmacro-loop-setup-function)
+ (setcar (cdr item) kmacro-counter)))
+
+
(defun kmacro-call-ring-2nd (arg)
"Execute second keyboard macro at in macro ring."
(interactive "P")
(unless (kmacro-ring-empty-p)
- ;; should use counter format specific to the macro on the ring!
- (let ((kmacro-counter (nth 1 (car kmacro-ring)))
- (kmacro-counter-format-start (nth 2 (car kmacro-ring))))
- (execute-kbd-macro (car (car kmacro-ring)) arg #'kmacro-loop-setup-function)
- (setcar (cdr (car kmacro-ring)) kmacro-counter))))
+ (kmacro-exec-ring-item (car kmacro-ring) arg)))
(defun kmacro-call-ring-2nd-repeat (arg)
- "Like `kmacro-call-ring-2nd', but allow repeat without repeating prefix."
+ "Execute second keyboard macro at in macro ring.
+This is like `kmacro-call-ring-2nd', but allows repeating macro commands
+without repeating the prefix."
(interactive "P")
(let ((keys (kmacro-get-repeat-prefix)))
(kmacro-call-ring-2nd arg)
(kmacro-display (car (car kmacro-ring)) "2nd macro")))
-
(defun kmacro-cycle-ring-next (&optional arg)
"Move to next keyboard macro in keyboard macro ring.
Displays the selected macro in the echo area."
The commands are recorded even as they are executed.
Use \\[kmacro-end-macro] to finish recording and make the macro available.
Use \\[kmacro-end-and-call-macro] to execute the macro.
-Use \\[name-last-kbd-macro] to give it a permanent name.
-Non-nil arg (prefix arg) means append to last macro defined;
+
+Non-nil arg (prefix arg) means append to last macro defined.
With \\[universal-argument] prefix, append to last keyboard macro
defined. Depending on `kmacro-execute-before-append', this may begin
Use \\[kmacro-insert-counter] to insert (and increment) the macro counter.
The counter value can be set or modified via \\[kmacro-set-counter] and \\[kmacro-add-counter].
-The format of the counter can be modified via \\[kmacro-set-format]."
+The format of the counter can be modified via \\[kmacro-set-format].
+
+Use \\[kmacro-name-last-macro] to give it a permanent name.
+Use \\[kmacro-bind-to-key] to bind it to a key sequence."
(interactive "P")
(if (or defining-kbd-macro executing-kbd-macro)
(message "Already defining keyboard macro.")
kmacro-ring))
(if (>= len kmacro-ring-max)
(setcdr (nthcdr len kmacro-ring) nil))))
- (setq kmacro-counter (if arg (prefix-numeric-value arg) 0)
+ (setq kmacro-counter (or (if arg (prefix-numeric-value arg))
+ kmacro-initial-counter-value
+ 0)
+ kmacro-initial-counter-value nil
kmacro-counter-value-start kmacro-counter
kmacro-last-counter kmacro-counter
- kmacro-counter-format-start kmacro-counter-format))
+ kmacro-counter-format kmacro-default-counter-format
+ kmacro-counter-format-start kmacro-default-counter-format))
(start-kbd-macro append
(and append
(if kmacro-execute-before-append
(> (car arg) 4)
- (= (car arg) 4)))))))
+ (= (car arg) 4))))
+ (if (and defining-kbd-macro append)
+ (setq defining-kbd-macro 'append)))))
;;;###autoload
"Finish defining a keyboard macro.
The definition was started by \\[kmacro-start-macro].
The macro is now available for use via \\[kmacro-call-macro],
-or it can be given a name with \\[name-last-kbd-macro] and then invoked
+or it can be given a name with \\[kmacro-name-last-macro] and then invoked
under that name.
With numeric arg, repeat macro now that many times,
for details on how to adjust or disable this behaviour.
To make a macro permanent so you can call it even after defining
-others, use M-x name-last-kbd-macro."
+others, use \\[kmacro-name-last-macro]."
(interactive "p")
(let ((repeat-key (and (null no-repeat)
(> (length (this-single-command-keys)) 1)
(if kmacro-call-repeat-key
(kmacro-call-macro arg no-repeat t)
(kmacro-end-macro arg)))
+ ((and (eq this-command 'kmacro-view-macro) ;; We are in repeat mode!
+ kmacro-view-last-item)
+ (kmacro-exec-ring-item (car kmacro-view-last-item) arg))
((and arg (listp arg))
(kmacro-call-ring-2nd 1))
(t
;;;###autoload
(defun kmacro-end-and-call-macro (arg &optional no-repeat)
"Call last keyboard macro, ending it first if currently being defined.
-With numeric prefix ARG, repeat macro that many times."
+With numeric prefix ARG, repeat macro that many times.
+Zero argument means repeat until there is an error.
+
+To give a macro a permanent name, so you can call it
+even after defining other macros, use \\[kmacro-name-last-macro]."
(interactive "P")
(if defining-kbd-macro
(kmacro-end-macro nil))
;;; Misc. commands
+;; An idea for macro bindings:
+;; Create a separate keymap installed as a minor-mode keymap (e.g. in
+;; the emulation-mode-map-alists) in which macro bindings are made
+;; independent of any other bindings. When first binding is made,
+;; the kemap is created, installed, and enabled. Key seq. C-x C-k +
+;; can then be used to toggle the use of this keymap on and off.
+;; This means that it would be safe(r) to bind ordinary keys like
+;; letters and digits, provided that we inhibit the keymap while
+;; executing the macro later on (but that's controversial...)
+
+(defun kmacro-lambda-form (mac &optional counter format)
+ "Create lambda form for macro bound to symbol or key."
+ (if counter
+ (setq mac (list mac counter format)))
+ `(lambda (&optional arg)
+ "Keyboard macro."
+ (interactive "p")
+ (kmacro-exec-ring-item ',mac arg)))
+
+(defun kmacro-extract-lambda (mac)
+ "Extract kmacro from a kmacro lambda form."
+ (and (consp mac)
+ (eq (car mac) 'lambda)
+ (setq mac (assoc 'kmacro-exec-ring-item mac))
+ (consp (cdr mac))
+ (consp (car (cdr mac)))
+ (consp (cdr (car (cdr mac))))
+ (setq mac (car (cdr (car (cdr mac)))))
+ (listp mac)
+ (= (length mac) 3)
+ (arrayp (car mac))
+ mac))
+
+
(defun kmacro-bind-to-key (arg)
- "When not defining or executing a macro, offer to bind last macro to a key."
+ "When not defining or executing a macro, offer to bind last macro to a key.
+The key sequences [C-x C-k 0] through [C-x C-k 9] and [C-x C-k A]
+through [C-x C-k Z] are reserved for user bindings, and to bind to
+one of these sequences, just enter the digit or letter, rather than
+the whole sequence.
+
+You can bind to any valid key sequence, but if you try to bind to
+a key with an existing command binding, you will be asked for
+confirmation whether to replace that binding. Note that the
+binding is made in the `global-map' keymap, so the macro binding
+may be shaded by a local key binding."
(interactive "p")
(if (or defining-kbd-macro executing-kbd-macro)
(if defining-kbd-macro
(message "Cannot save macro while defining it."))
(unless last-kbd-macro
(error "No keyboard macro defined"))
- (let ((key-seq (read-key-sequence "Bind last macro to key: ")))
- (unless (equal key-seq "\a")
- (define-key global-map key-seq last-kbd-macro)))))
+ (let ((key-seq (read-key-sequence "Bind last macro to key: "))
+ ok cmd)
+ (when (= (length key-seq) 1)
+ (let ((ch (aref key-seq 0)))
+ (if (or (and (>= ch ?0) (<= ch ?9))
+ (and (>= ch ?A) (<= ch ?Z)))
+ (setq key-seq (concat "\C-x\C-k" key-seq)
+ ok t))))
+ (when (and (not (equal key-seq "\a"))
+ (or ok
+ (not (setq cmd (key-binding key-seq)))
+ (stringp cmd)
+ (vectorp cmd)
+ (yes-or-no-p (format "%s runs command %S. Bind anyway? "
+ (format-kbd-macro key-seq)
+ cmd))))
+ (define-key global-map key-seq
+ (kmacro-lambda-form (kmacro-ring-head)))
+ (message "Keyboard macro bound to %s" (format-kbd-macro key-seq))))))
+
+
+(defun kmacro-name-last-macro (symbol)
+ "Assign a name to the last keyboard macro defined.
+Argument SYMBOL is the name to define.
+The symbol's function definition becomes the keyboard macro string.
+Such a \"function\" cannot be called from Lisp, but it is a valid editor command."
+ (interactive "SName for last kbd macro: ")
+ (or last-kbd-macro
+ (error "No keyboard macro defined"))
+ (and (fboundp symbol)
+ (not (get symbol 'kmacro))
+ (not (stringp (symbol-function symbol)))
+ (not (vectorp (symbol-function symbol)))
+ (error "Function %s is already defined and not a keyboard macro"
+ symbol))
+ (if (string-equal symbol "")
+ (error "No command name given"))
+ (fset symbol (kmacro-lambda-form (kmacro-ring-head)))
+ (put symbol 'kmacro t))
(defun kmacro-view-macro (&optional arg)
- "Display the last keyboard macro."
+ "Display the last keyboard macro.
+If repeated, it shows previous elements in the macro ring."
(interactive)
- (kmacro-display last-kbd-macro))
-
+ (cond
+ ((or (kmacro-ring-empty-p)
+ (not (eq last-command 'kmacro-view-macro)))
+ (setq kmacro-view-last-item nil))
+ ((null kmacro-view-last-item)
+ (setq kmacro-view-last-item kmacro-ring
+ kmacro-view-item-no 2))
+ ((consp kmacro-view-last-item)
+ (setq kmacro-view-last-item (cdr kmacro-view-last-item)
+ kmacro-view-item-no (1+ kmacro-view-item-no)))
+ (t
+ (setq kmacro-view-last-item nil)))
+ (setq this-command 'kmacro-view-macro
+ last-command this-command) ;; in case we repeat
+ (kmacro-display (if kmacro-view-last-item
+ (car (car kmacro-view-last-item))
+ last-kbd-macro)
+ nil
+ (if kmacro-view-last-item
+ (concat (cond ((= kmacro-view-item-no 2) "2nd")
+ ((= kmacro-view-item-no 3) "3nd")
+ (t (format "%dth" kmacro-view-item-no)))
+ " previous macro")
+ "Last macro")))
(defun kmacro-view-macro-repeat (&optional arg)
- "Like `kmacro-view-macro', but allow repeat without repeating prefix."
+ "Display the last keyboard macro.
+If repeated, it shows previous elements in the macro ring.
+To execute the displayed macro ring item without changing the macro ring,
+just enter C-k.
+This is like `kmacro-view-macro', but allows repeating macro commands
+without repeating the prefix."
(interactive)
(let ((keys (kmacro-get-repeat-prefix)))
(kmacro-view-macro arg)
(if (and last-kbd-macro keys)
(kmacro-repeat-on-last-key keys))))
-(put 'kmacro-view-macro-repeat 'kmacro-repeat 'head)
+(put 'kmacro-view-macro-repeat 'kmacro-repeat 'ring)
(defun kmacro-edit-macro-repeat (&optional arg)
(curmsg (current-message)))
;; TODO: Scroll macro if max-mini-window-height is too small.
- (message (concat
+ (message "%s"
+ (concat
(format "Macro: %s%s%s%s%s\n"
(format-kbd-macro kmacro-step-edit-new-macro 1)
(if (and kmacro-step-edit-new-macro (> (length kmacro-step-edit-new-macro) 0)) " " "")
(setq last-kbd-macro kmacro-step-edit-new-macro))))
(provide 'kmacro)
+
+;;; arch-tag: d3fe0b24-ae41-47de-a4d6-41a77d5559f0
;;; kmacro.el ends here